From 759b976be00de097ae58a578313154eaf9589acc Mon Sep 17 00:00:00 2001 From: Jacob Horbulyk Date: Fri, 20 Sep 2019 16:33:44 +0200 Subject: [PATCH 1/7] Revisions Sept 2019. --- .../StandardizedActionsAndTriggers.md | 191 ++++++++++++------ 1 file changed, 133 insertions(+), 58 deletions(-) diff --git a/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md b/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md index abba514..46ee280 100644 --- a/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md +++ b/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md @@ -1,18 +1,26 @@ # Descriptions of standardized actions or triggers -**Version Publish Date:** 07.11.2019 +**Version Publish Date:** 09.20.2019 -**Semantic Version of Document:** 2.2.1 +**Semantic Version of Document:** 2.3.0 ## Table of Contents - [Actions](#actions) * [Upsert Object](#upsert-object) + + [Iteration 1: Upsert Object By ID](#iteration-1-upsert-object-by-id) + + [Iteration 2: Update Object By Unique Criteria](#iteration-2-update-object-by-unique-criteria) * [Lookup Object (at most 1)](#lookup-object-at-most-1) + + [Iteration 1: Lookup Object By ID](#iteration-1-lookup-object-by-id) + + [Iteration 2: Lookup Object By Unique Criteria](#iteration-2-lookup-object-by-unique-criteria) * [Lookup Objects (Plural)](#lookup-objects-plural) * [Delete Object](#delete-object) + + [Iteration 1: Delete Object By ID](#iteration-1-delete-object-by-id) + + [Iteration 2: Delete Object By Unique Criteria](#iteration-2-delete-object-by-unique-criteria) * [Make RAW Request](#make-raw-request) * [Lookup Set Of Objects By Unique Criteria](#lookup-set-of-objects-by-unique-criteria) + + [Iteration 1: Lookup Object By ID](#iteration-1-lookup-object-by-id-1) + + [Iteration 2: Lookup Object By Unique Criteria](#iteration-2-lookup-object-by-unique-criteria-1) * [Update Object](#update-object) * [Create Object](#create-object) * [Linking/Unlinking Objects](#linkingunlinking-objects) @@ -24,6 +32,8 @@ - [Triggers](#triggers) * [Get New and Updated Objects Polling](#get-new-and-updated-objects-polling) * [Webhooks](#webhooks) + * [Get Recently Deleted Objects Polling](#get-recently-deleted-objects-polling) + * [Event subscription](#event-subscription) * [Bulk Extract](#bulk-extract) It is important to define common rules on how an adapter responds to changes @@ -116,7 +126,8 @@ I have a contact who works for a company. I have an ID or other distinguishing - Object Type (dropdown) - Allow ID to be omitted (dropdown/checkbox: yes/no) -- Allow zero results (dropdown/checkbox: yes/no) +- Allow zero results (dropdown/checkbox: yes/no). If zero results are not allowed, in many cases it makes sense to apply some rebounds to wait for it to exists. +- Linked objects to populate (optional, dropdown). Select which linked objects to fetch if supported by the API. ##### Input Metadata @@ -141,7 +152,7 @@ I have a contact who works for a company. I have an ID or other distinguishing if(allowZeroResults) { emitData({}); } else { - throw e; + emitRebound(); } } } @@ -154,10 +165,6 @@ I have a contact who works for a company. I have an ID or other distinguishing - Make sure to Url Encode IDs appearing in HTTP urls -##### Not defined now - -- How to handle populating linked objects. - #### Iteration 2: Lookup Object By Unique Criteria ##### Additional Config Fields @@ -185,7 +192,7 @@ I have a contact who works for a company. I have an ID or other distinguishing if(allowZeroResults) { emitData({}); } else { - throw new Error('Not found'); + emitRebound(); } } else if (foundObjects.length ==1) { emitData(foundObjects[0]); @@ -203,7 +210,8 @@ I want to search my CRM for data based on some criteria. - Object Type (dropdown) - Behavior (dropdown: Fetch all, Fetch Page, Emit Individually) -- Number of search terms (text field: integer >= 1) (iteration 2) +- Number of search terms (text field: integer >= 0) (iteration 2) (0 indicates return all items) +- Linked objects to populate (optional, dropdown). Select which linked objects to fetch if supported by the API. ##### Input Metadata @@ -324,7 +332,7 @@ A simple action to allow integrators to assemble requests to be sent to the syst I'm a technically advanced user who wants to interact with a system in a way not permissible by the existing component actions but would like some simplification relative to using the REST component. ### Lookup Set Of Objects By Unique Criteria -Given an array of information where each item in the array uniquely describes exactly one object. It can be assumed that the array is short enough to reasonably fit the results in a single message. +Given an array of information where each item in the array uniquely describes exactly one object. It can be assumed that the array is short enough to reasonably fit the results in a single message. If any of the objects are not found it makes sense to rebound. ##### Example Use Case I salesperson is responsible for 0 to N accounts. I would like to look up a piece of information for each account associated with the salesperson. @@ -347,7 +355,8 @@ I salesperson is responsible for 0 to N accounts. I would like to look up a pie const results = itemUniqueCriteriaListToLookup.map(itemUniqueCriteria => { const matchingItems = GetObjectsByCriteria(itemUniqueCriteria); if(matchingItems.length != 1) { - throw new Error(`Lookup failed for ${itemUniqueCriteria}`); + emitRebound({}); + return; } return { key: itemCriteria, @@ -371,7 +380,8 @@ I salesperson is responsible for 0 to N accounts. I would like to look up a pie for each (let itemId of itemIdsToLookup) { const matchingItems = searchResults.filter(result.Id = itemId); if(matchingItems.length != 1) { - throw new Error(`Lookup failed for ${itemUniqueCriteria}`); + emitRebound({}); + return; } resultDictionary[itemId] = matchingItems[0]; } @@ -387,10 +397,6 @@ I salesperson is responsible for 0 to N accounts. I would like to look up a pie ##### Gotcha’s to lookout for - Make sure to Url Encode IDs appearing in HTTP urls - -##### Not defined now -- Encode any IDs in URLs -- Rebounds when an object is not found - There are different structures depending on the input structure ### Update Object @@ -399,6 +405,7 @@ I salesperson is responsible for 0 to N accounts. I would like to look up a pie - We will not create the object if it does not exist - The ID/other unique criteria is required - No other fields are required +If the object is not found, then rebounds should be done. ##### Example Use Case I want to update the price of a product based on its SKU but I don't want to look up other required attributes such as name since I know those have already been set and are not changing. @@ -421,18 +428,25 @@ See above. - the types of the two objects - two sets of unique criteria which describe the two objects - Information about the relationship (e.g. if assigning user to company membership, identify the role of the user) +- Rebounds should be emitted if results aren't found. ``` function linkObjects(obj1, obj2, linkMetadata) { const matchingObjects1 = lookupObjectByCriteria(obj1.type, obj1.uniqueCriteria); - if (matchingObjects1.length != 1) { - throw new Error('Not found/too many found.'); - } + if (matchingObjects1.length > 1) { + throw new Error('Too many found.'); + } else if (matchingObjects1.length == 0) { + emitRebound(); + return; + } const object1Id = matchingObjects1[0].id; const matchingObjects2 = lookupObjectByCriteria(obj2.type, obj2.uniqueCriteria); - if (matchingObjects2.length != 1) { - throw new Error('Not found/too many found.'); + if (matchingObjects2.length > 1) { + throw new Error('Too many found.'); + } else if (matchingObjects2.length == 0) { + emitRebound(); + return; } const object2Id = matchingObjects2[0].id; @@ -590,74 +604,134 @@ I want to learn about changes to contacts in my CRM when they happen. - End Time (string, optional): If provided, don’t fetch records modified after this time (defaults to never) - Size of Polling Page (optional; positive integer) Indicates the size of pages to be fetched. Defaults to 1000. - Single Page per Interval (dropdown/checkbox: yes/no; default yes) Indicates that if the number of changed records exceeds the maximum number of results in a page, instead of fetching the next page immediately, wait until the next flow start to fetch the next page. +- Time stamp field to poll on (dropdown: created or modified). Indicates just new items or new and modified items. ##### Input Metadata N/A +##### Gotcha’s to lookout for + +- If `previousLastModified` is set to `lastSeenTime` and we have `lastModified >= previousLastModified` then each execution will include records from previous execution. But if at the first execution `previousLastModified` could be equal `cfg.startTime` and we have `lastModified > previousLastModified` then we will lose objects whose last modified date is equal to the `cfg.startTime`. This is why we compare `previousLastModified` and `cfg.startTime || new Date(0)` and if they are equal, use condition `lastModified >= previousLastModified,` else: `lastModified > previousLastModified,` +- We use `lastModified <= maxTime` as it is more understandable for user. +- We have `Single Page per Interval` default to yes because it is slightly safer. +- We need to be careful about more than a page worth of records having the same timestamp. +- We need to be careful about the last record on one page having the same timestamp as the first record on the next page +- We need to be careful about records on page N being modified before reading page N+1 (thus causing records to be skipped as they move from page N+1 to page N). + +##### Assumptions About Server Behavior: +In order for the bellow polling algorithm to work, all of the following must be true about the way the server behaves: +* It is possible to order results by last modified and then by primary key. +* If record A has a timestamp of X and appears within a search but not record B, then if record B appears in a later search than record B MUST have a timestamp that is later (i.e. Not the same or earlier) than record A. + ##### Pseudo-Code +**High level steps:** +1. Retrieve a page of data. +2. If the size of the page is less than the max page size, emit the timestamp of the last record on the page. +3. Compare the timestamps of the last and second last items on the page. If they are different, store the timestamp of the second last record of the page and don't emit the last record. +4. If the timestamps are the same, then store that timestamp and the primary key of the last item. On the next iteration fetch page where the timestamps are equal and primary key is larger than last seen item. + function getObjectsPolling(cfg, snapshot) { - const previousLastModified = snapshot.previousLastModified || cfg.startTime || new Date(0); - const maxTime = cfg.endTime || Date.MaxDate(); - let hasMorePages = true; - snapshot.pageNumber = snapshot.pageNumber || 0; - let lastSeenTime = previousLastModified; + const pollingField = cfg.timeStampFieldToPollOn; + let attemptMorePages = !cfg.singlePagePerInterval; do { + const previousLastModified = snapshot.previousLastModified || cfg.startTime || new Date(0); + const maxTime = cfg.endTime || Date.MaxDate(); + let whereCondition; - if (previousLastModified === cfg.startTime || new Date(0)){ + if(snapshot.previousId) { + whereCondition = [ + pollingField = previousLastModified, + Id > snapshot.previousId + ]; + } else if (previousLastModified === cfg.startTime || new Date(0)){ whereCondition = [ - lastModified >= previousLastModified, - lastModified <= maxTime + pollingField >= previousLastModified, + pollingField <= maxTime ]; } else { whereCondition = [ - lastModified > previousLastModified, - lastModified <= maxTime + pollingField > previousLastModified, + pollingField <= maxTime ]; } const pageOfResults = GetPageOfResults({ - orderBy: Time ascending + orderBy: [Time ascending, Primary Key Ascending] where: whereCondition, - top: sizeOfPollingPage, - skip: snapshot.pageNumber * sizeOfPollingPage + top: cfg.sizeOfPollingPage }); - pageOfResults.forEach(result => { - emitData(result); - }; - snapshot.pageNumber++; - hasMorePages = pageOfResults.length == pageSize; - if(pageOfResults.length > 0) { - lastSeenTime = pageOfResults[pageOfResults.length - 1].lastModified; - } - emitSnapshot(snapshot); - if(singlePagePerInterval && hasMorePages) { - return; - } - } while (hasMorePages) - delete snapshot.pageNumber; - snapshot.previousLastModified = lastSeenTime; - emitSnapshot(snapshot); + + const hasMorePages = pageOfResults.length == cfg.sizeOfPollinPage; + + if(!hasMorePages) { + attemptMorePages = attemptMorePages && !snapshot.previousId; + pageOfResults.forEach(result => { + emitData(result); + }; + if(pageOfResults.length > 0) { + snapshot.previousLastModified = pageOfResults[pageOfResults.length - 1].lastModified; + delete snapshot.previousId; + emitSnapshot(snapshot); + } + } else { + const lastResult = pageOfResults.pop(); + pageOfResults.forEach(result => { + emitData(result); + }; + const secondLastResult = pageOfResults[pageOfResults.length - 1]; + snapshot.previousLastModified = secondLastResult.lastModified; + if(lastResult.lastModified !== secondLastResult.lastModified) { + delete snapshot.previousId; + } else { + snapshot.previousId = secondLastResult.id; + } + emitSnapshot(snapshot); + } + } } ##### Output Data - Each object emitted individually. -##### Gotcha’s to lookout for - -- If `previousLastModified` is set to `lastSeenTime` and we have `lastModified >= previousLastModified` then each execution will include records from previous execution. But if at the first execution `previousLastModified` could be equal `cfg.startTime` and we have `lastModified > previousLastModified` then we will lose objects whose last modified date is equal to the `cfg.startTime`. This is why we compare `previousLastModified` and `cfg.startTime || new Date(0)` and if they are equal, use condition `lastModified >= previousLastModified,` else: `lastModified > previousLastModified,` -- We use `lastModified <= maxTime` as it is more understandable for user. -- We have `Single Page per Interval` default to yes because it is slightly safer. -- TODO - ### Webhooks *This action has not been fully standardized.* Receives data pushed to the iPaas from an external system. +### Get Recently Deleted Objects Polling +##### Example Use Case +I want to learn about contacts in my CRM that are deleted so that I can propagate those deletes. + +##### Config Fields + +Same as `Get New and Updated Objects Polling`. +##### Input Metadata + +N/A + +##### Pseudo-Code + +Same as `Get New and Updated Objects Polling`. +##### Output Data + +- Each object emitted individually. + +### Event subscription + +*This action has not been fully standardized.* + +The platform must have a part that is actively awake and is able to receive events based on some protocol. Examples: +* Salesforce Event Bus +* AMQP component +* Socket component +* JMX component +* CometD protocol +* Long polling + ### Bulk Extract Useful for: @@ -665,3 +739,4 @@ Useful for: - Systems that do no track last_modified - Systems that don’t support filtering by timestamp range - Systems which have dedicated bulk export functionality +- Providing a way to track object deletions From 9083f9fb821c5ae2fe60b722b6481f7653ed984a Mon Sep 17 00:00:00 2001 From: Jacob Horbulyk Date: Fri, 20 Sep 2019 16:35:11 +0200 Subject: [PATCH 2/7] Fix ToC --- .../StandardizedActionsAndTriggers.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md b/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md index 46ee280..168e982 100644 --- a/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md +++ b/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md @@ -8,19 +8,11 @@ - [Actions](#actions) * [Upsert Object](#upsert-object) - + [Iteration 1: Upsert Object By ID](#iteration-1-upsert-object-by-id) - + [Iteration 2: Update Object By Unique Criteria](#iteration-2-update-object-by-unique-criteria) * [Lookup Object (at most 1)](#lookup-object-at-most-1) - + [Iteration 1: Lookup Object By ID](#iteration-1-lookup-object-by-id) - + [Iteration 2: Lookup Object By Unique Criteria](#iteration-2-lookup-object-by-unique-criteria) * [Lookup Objects (Plural)](#lookup-objects-plural) * [Delete Object](#delete-object) - + [Iteration 1: Delete Object By ID](#iteration-1-delete-object-by-id) - + [Iteration 2: Delete Object By Unique Criteria](#iteration-2-delete-object-by-unique-criteria) * [Make RAW Request](#make-raw-request) * [Lookup Set Of Objects By Unique Criteria](#lookup-set-of-objects-by-unique-criteria) - + [Iteration 1: Lookup Object By ID](#iteration-1-lookup-object-by-id-1) - + [Iteration 2: Lookup Object By Unique Criteria](#iteration-2-lookup-object-by-unique-criteria-1) * [Update Object](#update-object) * [Create Object](#create-object) * [Linking/Unlinking Objects](#linkingunlinking-objects) From bddbb71fbe526b1aa0c1b196fcd7f3cee2ec40fb Mon Sep 17 00:00:00 2001 From: Jacob Horbulyk Date: Fri, 20 Sep 2019 16:46:04 +0200 Subject: [PATCH 3/7] Touchups. --- .../StandardizedActionsAndTriggers.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md b/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md index 168e982..2df7f58 100644 --- a/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md +++ b/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md @@ -25,7 +25,7 @@ * [Get New and Updated Objects Polling](#get-new-and-updated-objects-polling) * [Webhooks](#webhooks) * [Get Recently Deleted Objects Polling](#get-recently-deleted-objects-polling) - * [Event subscription](#event-subscription) + * [Event Subscription](#event-subscription) * [Bulk Extract](#bulk-extract) It is important to define common rules on how an adapter responds to changes @@ -119,7 +119,7 @@ I have a contact who works for a company. I have an ID or other distinguishing - Object Type (dropdown) - Allow ID to be omitted (dropdown/checkbox: yes/no) - Allow zero results (dropdown/checkbox: yes/no). If zero results are not allowed, in many cases it makes sense to apply some rebounds to wait for it to exists. -- Linked objects to populate (optional, dropdown). Select which linked objects to fetch if supported by the API. +- Linked objects to populate (optional, multi-select dropdown). Select which linked objects to fetch if supported by the API. ##### Input Metadata @@ -203,7 +203,7 @@ I want to search my CRM for data based on some criteria. - Object Type (dropdown) - Behavior (dropdown: Fetch all, Fetch Page, Emit Individually) - Number of search terms (text field: integer >= 0) (iteration 2) (0 indicates return all items) -- Linked objects to populate (optional, dropdown). Select which linked objects to fetch if supported by the API. +- Linked objects to populate (optional, multi-select dropdown). Select which linked objects to fetch if supported by the API. ##### Input Metadata @@ -335,6 +335,7 @@ I salesperson is responsible for 0 to N accounts. I would like to look up a pie ##### Config Fields - Object Type (dropdown) +- Linked objects to populate (optional, multi-select dropdown). Select which linked objects to fetch if supported by the API. - Iteration 2: Unique Criteria (dropdown) ##### Input Metadata @@ -663,7 +664,7 @@ In order for the bellow polling algorithm to work, all of the following must be emitData(result); }; if(pageOfResults.length > 0) { - snapshot.previousLastModified = pageOfResults[pageOfResults.length - 1].lastModified; + snapshot.previousLastModified = pageOfResults[pageOfResults.length - 1][pollingField]]; delete snapshot.previousId; emitSnapshot(snapshot); } @@ -673,8 +674,8 @@ In order for the bellow polling algorithm to work, all of the following must be emitData(result); }; const secondLastResult = pageOfResults[pageOfResults.length - 1]; - snapshot.previousLastModified = secondLastResult.lastModified; - if(lastResult.lastModified !== secondLastResult.lastModified) { + snapshot.previousLastModified = secondLastResult[pollingField]; + if(lastResult[pollingField] !== secondLastResult[pollingField]) { delete snapshot.previousId; } else { snapshot.previousId = secondLastResult.id; @@ -712,7 +713,7 @@ Same as `Get New and Updated Objects Polling`. - Each object emitted individually. -### Event subscription +### Event Subscription *This action has not been fully standardized.* From 365e1701b7f832e5d51ae79730b8fa2ad1a55358 Mon Sep 17 00:00:00 2001 From: Jacob Horbulyk Date: Thu, 31 Oct 2019 14:20:14 +0100 Subject: [PATCH 4/7] Fix GitHub formating. --- .../StandardizedActionsAndTriggers.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md b/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md index 2df7f58..af5314c 100644 --- a/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md +++ b/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md @@ -625,6 +625,7 @@ In order for the bellow polling algorithm to work, all of the following must be 3. Compare the timestamps of the last and second last items on the page. If they are different, store the timestamp of the second last record of the page and don't emit the last record. 4. If the timestamps are the same, then store that timestamp and the primary key of the last item. On the next iteration fetch page where the timestamps are equal and primary key is larger than last seen item. +``` function getObjectsPolling(cfg, snapshot) { const pollingField = cfg.timeStampFieldToPollOn; let attemptMorePages = !cfg.singlePagePerInterval; @@ -684,6 +685,7 @@ In order for the bellow polling algorithm to work, all of the following must be } } } +``` ##### Output Data From da4d4bacaddf909d41a00c2be8529200f6b38dd4 Mon Sep 17 00:00:00 2001 From: Jacob Horbulyk Date: Wed, 5 Feb 2020 16:37:49 +0100 Subject: [PATCH 5/7] Add rebound as a separate option. Other minor touchups. --- .../StandardizedActionsAndTriggers.md | 55 +++++++++++++------ 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md b/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md index 5381176..7c499cf 100644 --- a/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md +++ b/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md @@ -110,7 +110,7 @@ I have some contact data that I want to add to my CRM. I don't necessarily know ### Lookup Object (at most 1) ##### Example Use Case -I have a contact who works for a company. I have an ID or other distinguishing characteristic (e.g. legal name) of the company and I want to learn some detail about the company (e.g. number of employees). +I have a contact who works for a company. I have an ID or other distinguishing characteristic (e.g. legal name) of the company and I want to learn some detail about the company (e.g. country of company). #### Iteration 1: Lookup Object By ID @@ -118,7 +118,8 @@ I have a contact who works for a company. I have an ID or other distinguishing - Object Type (dropdown) - Allow ID to be omitted (dropdown/checkbox: yes/no); when selected, the ID field becomes optional, otherwise it is a required field -- Allow zero results (dropdown/checkbox: yes/no); hen selected, if zero results are returned, the empty object `{}` is emitted, otherwise typically an error would be thrown. If zero results are not allowed, in many cases it makes sense to apply some rebounds to wait for it to exists. +- Allow zero results (dropdown/checkbox: yes/no); When selected, if zero results are returned, the empty object `{}` is emitted, otherwise typically an error would be thrown. +- Wait for object to exist (dropdown/checkbox: yes/no); When selected, if no results are found, apply rebounds and wait until the object exits. - Linked objects to populate (optional, multi-select dropdown). Select which linked objects to fetch if supported by the API. ##### Input Metadata @@ -138,13 +139,15 @@ I have a contact who works for a company. I have an ID or other distinguishing } try { - const foundObject = GetObjectById(id); // Usually GET verb + const foundObject = GetObjectById(id, linkedObjectsToPopulate); // Usually GET verb emitData(foundObject); } catch (NotFoundException e) { - if(allowZeroResults) { + if(waitForObjectToExist && notAllReboundsExhausted) { + emitRebound({}); + } else if(allowZeroResults) { emitData({}); } else { - emitRebound(); + throw e; } } } @@ -179,12 +182,14 @@ I have a contact who works for a company. I have an ID or other distinguishing } } - const foundObjects = GetObjectsByCriteria(uniqueCriteria); // Usually GET verb + const foundObjects = GetObjectsByCriteria(uniqueCriteria, linkedObjectsToPopulate); // Usually GET verb if(foundObjects.length == 0) { - if(allowZeroResults) { + if(waitForObjectToExist && notAllReboundsExhausted) { + emitRebound({}); + } else if(allowZeroResults) { emitData({}); } else { - emitRebound(); + throw e; } } else if (foundObjects.length ==1) { emitData(foundObjects[0]); @@ -223,20 +228,20 @@ I want to search my CRM for data based on some criteria. function lookupObjects(criteria) { switch(mode) { case 'fetchAll': - const results = GetObjectsByCriteria(criteria); + const results = GetObjectsByCriteria(criteria, linkedObjectsToPopulate); if(results.length >= maxResultSize) { throw new Error('Too many results'); } emitData({results: results}); break; case 'emitIndividually': - const results = GetObjectsByCriteria(criteria); + const results = GetObjectsByCriteria(criteria, linkedObjectsToPopulate); results.forEach(result => { emitData(result); } break; case 'fetchPage': - const results = GetObjectsByCritieria(criteria, top: pageSize, skip: pageSize * pageNumber, orderBy: orderByTerms); + const results = GetObjectsByCritieria(criteria, top: pageSize, skip: pageSize * pageNumber, orderBy: orderByTerms, linkedObjectsToPopulate); emitData({results: results}); break; } @@ -254,6 +259,7 @@ I want to search my CRM for data based on some criteria. - Order of operations in multiple terms - How to get total number of matching objects +- How to handle variable number of search terms (perhaps integrator mode?) ### Delete Object ##### Example Use Case @@ -320,6 +326,10 @@ I know the ID of a customer that I want to delete. A simple action to allow integrators to assemble requests to be sent to the system. The component should expose the parts that vary in a typical request. The component should handle authentication and error reporting. +Additional Options to Consider: +* Consider that it may make sense to turn off error reporting & to return things like the HTTP status code & headers +* Consider that it may make sense to allow the option to make a series of sequential array requests. + ##### Example Use Case I'm a technically advanced user who wants to interact with a system in a way not permissible by the existing component actions but would like some simplification relative to using the REST component. @@ -327,7 +337,7 @@ I'm a technically advanced user who wants to interact with a system in a way not Given an array of information where each item in the array uniquely describes exactly one object. It can be assumed that the array is short enough to reasonably fit the results in a single message. If any of the objects are not found it makes sense to rebound. ##### Example Use Case -I salesperson is responsible for 0 to N accounts. I would like to look up a piece of information for each account associated with the salesperson. +I salesperson is responsible for 0 to N accounts (N being reasonably small). I would like to look up a piece of information for each account associated with the salesperson. #### Iteration 1: Lookup Object By ID #### Iteration 2: Lookup Object By Unique Criteria @@ -336,6 +346,7 @@ I salesperson is responsible for 0 to N accounts. I would like to look up a pie - Object Type (dropdown) - Linked objects to populate (optional, multi-select dropdown). Select which linked objects to fetch if supported by the API. +- Wait for object to exist (dropdown/checkbox: yes/no); When selected, if no results are found, apply rebounds and wait until the object exits. - Iteration 2: Unique Criteria (dropdown) ##### Input Metadata @@ -346,8 +357,11 @@ I salesperson is responsible for 0 to N accounts. I would like to look up a pie function lookupSetOfObjects(itemUniqueCriteriaListToLookup) { const results = itemUniqueCriteriaListToLookup.map(itemUniqueCriteria => { - const matchingItems = GetObjectsByCriteria(itemUniqueCriteria); + const matchingItems = GetObjectsByCriteria(itemUniqueCriteria, linkedObjectsToPopulate); if(matchingItems.length != 1) { + if(!waitForObjectToExist) { + throw new NotFoundError(); + } emitRebound({}); return; } @@ -367,12 +381,15 @@ I salesperson is responsible for 0 to N accounts. I would like to look up a pie return; } - const searchResults = FetchObjectsWhereIdIn(itemIdsToLookup); + const searchResults = FetchObjectsWhereIdIn(itemIdsToLookup, linkedObjectsToPopulate); const resultDictionary = {}; for each (let itemId of itemIdsToLookup) { const matchingItems = searchResults.filter(result.Id = itemId); if(matchingItems.length != 1) { + if(!waitForObjectToExist) { + throw new NotFoundError(); + } emitRebound({}); return; } @@ -398,7 +415,7 @@ I salesperson is responsible for 0 to N accounts. I would like to look up a pie - We will not create the object if it does not exist - The ID/other unique criteria is required - No other fields are required -If the object is not found, then rebounds should be done. +If the object is not found, then rebounds should be done based on the rebound option. ##### Example Use Case I want to update the price of a product based on its SKU but I don't want to look up other required attributes such as name since I know those have already been set and are not changing. @@ -421,7 +438,7 @@ See above. - the types of the two objects - two sets of unique criteria which describe the two objects - Information about the relationship (e.g. if assigning user to company membership, identify the role of the user) -- Rebounds should be emitted if results aren't found. +- Ther should be an option to emit rebounds should be emitted if results aren't found. ``` function linkObjects(obj1, obj2, linkMetadata) { @@ -429,6 +446,9 @@ See above. if (matchingObjects1.length > 1) { throw new Error('Too many found.'); } else if (matchingObjects1.length == 0) { + if(!waitForObjectToExist) { + throw new NotFoundError(); + } emitRebound(); return; } @@ -438,6 +458,9 @@ See above. if (matchingObjects2.length > 1) { throw new Error('Too many found.'); } else if (matchingObjects2.length == 0) { + if(!waitForObjectToExist) { + throw new NotFoundError(); + } emitRebound(); return; } From 3cc3944f455e80545b1ffb0ea20d86f7e27c7f27 Mon Sep 17 00:00:00 2001 From: Jacob Horbulyk Date: Wed, 5 Feb 2020 16:49:23 +0100 Subject: [PATCH 6/7] Fix typos. --- .../StandardizedActionsAndTriggers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md b/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md index 7c499cf..fb87951 100644 --- a/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md +++ b/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md @@ -189,7 +189,7 @@ I have a contact who works for a company. I have an ID or other distinguishing } else if(allowZeroResults) { emitData({}); } else { - throw e; + throw new Error('Not found'); } } else if (foundObjects.length ==1) { emitData(foundObjects[0]); @@ -334,7 +334,7 @@ Additional Options to Consider: I'm a technically advanced user who wants to interact with a system in a way not permissible by the existing component actions but would like some simplification relative to using the REST component. ### Lookup Set Of Objects By Unique Criteria -Given an array of information where each item in the array uniquely describes exactly one object. It can be assumed that the array is short enough to reasonably fit the results in a single message. If any of the objects are not found it makes sense to rebound. +Given an array of information where each item in the array uniquely describes exactly one object. It can be assumed that the array is short enough to reasonably fit the results in a single message. If any of the objects are not found then it indicates a logic problem in the integration. ##### Example Use Case I salesperson is responsible for 0 to N accounts (N being reasonably small). I would like to look up a piece of information for each account associated with the salesperson. From 5c5b9e4b6da958a018d2cd6da5f8822f9b712d6d Mon Sep 17 00:00:00 2001 From: Jacob Horbulyk Date: Tue, 18 Feb 2020 11:38:56 +0100 Subject: [PATCH 7/7] Fix typos and update date. --- .../StandardizedActionsAndTriggers.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md b/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md index fb87951..881d789 100644 --- a/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md +++ b/Adapters/AdapterBehaviorStandardization/StandardizedActionsAndTriggers.md @@ -1,6 +1,6 @@ # Descriptions of standardized actions or triggers -**Version Publish Date:** 05.02.2020 +**Version Publish Date:** 18.02.2020 **Semantic Version of Document:** 2.3.1 @@ -438,7 +438,7 @@ See above. - the types of the two objects - two sets of unique criteria which describe the two objects - Information about the relationship (e.g. if assigning user to company membership, identify the role of the user) -- Ther should be an option to emit rebounds should be emitted if results aren't found. +- There should be an option to emit rebounds should be emitted if results aren't found. ``` function linkObjects(obj1, obj2, linkMetadata) { @@ -474,7 +474,7 @@ See above. A student can be a participant in a class and a class can have many students. Given a student ID and a course ID I want to enroll that student in that course. ### Execute Query or Statement in Query Language -Examples of this include constructing a query or statement in SQL, Salesforce’s SOQL, etc. Queries return a table of data when executed. Statements do not reutrn results (other than execution statistics). +Examples of this include constructing a query or statement in SQL, Salesforce’s SOQL, etc. Queries return a table of data when executed. Statements do not return results (other than execution statistics). ##### Example Use Case Execute SQL query in SQL database @@ -616,7 +616,7 @@ I want to learn about changes to contacts in my CRM when they happen. ##### Config Fields - Object Type (dropdown) -- Start Time (string, optional): Indicates the beginning time to start polling from (defaults to the begining of time) +- Start Time (string, optional): Indicates the beginning time to start polling from (defaults to the beginning of time) - End Time (string, optional): If provided, don’t fetch records modified after this time (defaults to never) - Size of Polling Page (optional; positive integer) Indicates the size of pages to be fetched. Defaults to 1000. - Single Page per Interval (dropdown/checkbox: yes/no; default yes) Indicates that if the number of changed records exceeds the maximum number of results in a page, instead of fetching the next page immediately, wait until the next flow start to fetch the next page.