Update type of trigger is not working in Zapier - zapier

I am trying to create a trigger (Poll based) in Zapier app/integration which should run the zap when an existing item will be updated.
API response contains an array like this:
[
{ id: 1, title: 'AWS', time: '2021-12-03T11:41:13.615Z', modTime: '2021-12-03T11:41:13.615Z' },
{ id: 2, title: 'GCP', time: '2021-12-03T11:41:13.615Z', modTime: '2021-12-03T11:46:13.615Z' },
]
Now, as per the doc, if an item will contain id and updated_at key then it should work if same record will be updated with last modified timestamp field.
To make an update trigger, use an API endpoint that lists all items, both new and updated, or alternately an endpoint that lists only updated items. Zapier needs a composite id field that changes whenever the item is updated (ideally z.hash('md5', item.id + item.updated_at)), so subsequent updates aren’t be filtered out by Zapier’s deduper.
For that, I created a new key updated_at and copied the value of modTime key. But this trigger is working only for new records and not for any update in existing record. Am I missing something ? I can make id every time new like this { id: rec.id + ':' + rec.modTime ... } but it will run for new records as well which I don't want.
Here is my code:
// import statements
const listData = async (z) => {
const records = await getTaskList(z);
return records.map((rec) => ({
...rec,
updated_at: rec.modTime
}));
};
export default {
display: {
description: 'Triggers when a task will be updated.',
label: 'Task Updated',
},
key: 'task_updated',
noun: 'Updated Task',
operation: {
outputFields: outFields,
perform: listData,
sample: getSampleData(),
},
};

Great question! The Zapier deduper only cares about the field named id. That's why your solution of only adding a key only works for new items- the deuper doesn't look at updated_at at all and is working as intended (new items only). From those docs:
Zapier needs a composite id field that changes whenever the item is updated
So your second approach is correct. The id field should be a combination of the original id and the updated_field. This will trigger for all new and updated objects.
Filtering out new objects depends on the API you're sourcing data from. You can do a .filter before your .map, but will need a way to identify new (not updated) objects. Shot in the dark, but if time is the created date and modTime is the updated date, you could filter to only return objects where time !== modTime. But, this may not be the case.
Ultimately, whatever you reeturn from listData will get send to the deduper and triggered on if that zap hasn't seen that id before.
Hope that clears it up!

Related

How can I retrieve all members when querying for appRoleAssignedTo in Microsoft Graph?

I am attempting to programmatically retrieve a list of users (principalType = "User") and their associated appRoleId values for an enterprise app using itsresourceId value from Azure AD. There is a total of ten Users with a combined total of twenty appRoleId values associated with the app. However, when I run my query I receive data for just two users and a combined total of four appRoleId values.
Here's my C# code:
GraphServiceClient myGraphClient = GetGraphServiceClient([scopes]);
// Retrieve the [Id] value for the app. Note [Id] is a pseudonym for the [resourceId] required to retrieve users and app roles assigned.
var servPrinPage = await myGraphClient.ServicePrincipals.Request()
.Select("id,appRoles")
.Filter($"startswith(displayName, 'Display Name')")
.GetAsync()
.ConfigureAwait(false);
// Using the first [Id] value from the [ServicePrincipals] page, retrieve the list of users and their assigned roles for the app.
var appRoleAssignedTo = await myGraphClient.ServicePrincipals[servPrinPage[0].Id].AppRoleAssignedTo.Request().GetAsync().ConfigureAwait(false);
The query returns a ServicePrincipalAppRoleAssignedToCollectionPage (as expected) but the collection only contains four pages (one per User/appRoleId combination).
As an aside, the following query in Microsoft Graph Explorer produces an equivalent result:
https://graph.microsoft.com/v1.0/servicePrincipals/[resourceId]/appRoleAssignedTo
What am I missing here? I need to be able to retrieve the complete list of users and assigned app roles. Any assistance is greatly appreciated.
The issue I was confronting has to do with the pagination feature employed by Azure AD and MS Graph. In a nutshell, I was forced to submit two queries in order to retrieve all twenty records I was expecting.
If you have a larger set of records to be retrieved you may be faced with submitting a much larger number of successive queries. The successive queries are managed using a "skiptoken" passed as a request header each time your query is resubmitted.
Here is my revised code with notation....
// Step #1: Create a class in order to strongly type the <List> which will hold your results.
// Not absolutely necessary but always a good idea when working with <Lists> in C#.
private class AppRoleByUser
{
public string AzureDisplayName;
public string PrincipalDisplayName;
}
// Step #2: Submit a query to acquire the [id] for the Service Principal (i.e. your app).
// Note the [ServicePrincipals].[id] property is synonymous with the [resourceId] needed to
// retrieve [AppRoleAssignedTo] values from Microsoft Graph in the next step.
// Initialize the Microsoft Graph Client.
GraphServiceClient myGraphClient = GetGraphServiceClient("Directory.Read.All");
// Retrieve the Service Principals page containing the app [Id].
var servPrinPage = await myGraphClient.ServicePrincipals.Request().Select("id,appRoles").Filter($"startswith(displayName, 'Your App Name')").GetAsync().ConfigureAwait(false);
// Store the app [Id] in a local variable (for readability).
string resourceId = servPrinPage[0].Id;
// Step #3: Using the [Id]/[ResourceId] value from the previous step, retrieve a list of AppRoleId/DisplayName pairs for your app.
// Results of the successive queries are typed against the class created earlier and are appended to the <List>.
List<AppRoleByUser> appRoleByUser = new List<AppRoleByUser>();
// Note, unlike "Filter" or "Search" parameters, it is not possible to
// add a "Skiptoken" parameter directly to your query in C#.
// Instead, it is necessary to insert the "skiptoken" as request header using the Graph QueryOption class.
// Note the QueryOption List is passed as an empty object on the first pass of the while loop.
var queryOptions = new List<QueryOption>();
// Initialize the variable to hold the anticipated query result.
ServicePrincipalAppRoleAssignedToCollectionPage appRoleAssignedTo = new ServicePrincipalAppRoleAssignedToCollectionPage();
// Note the number of user/role combinations associated with an app is not always known.
// Consequently, you may be faced with the need to acquire multiple pages
// (and submit multiple consecutive queries) in order to obtain a complete
// listing of user/role combinations.
// The "while" loop construct will be utilized to manage query iteration.
// Execution of the "while" loop will be stopped when the "bRepeat" variable is set to false.
bool bRepeat = true;
while (bRepeat == true)
{
appRoleAssignedTo = (ServicePrincipalAppRoleAssignedToCollectionPage) await myGraphClient.ServicePrincipals[resourceId].AppRoleAssignedTo.Request(queryOptions).GetAsync().ConfigureAwait(false);
foreach (AppRoleAssignment myPage in appRoleAssignedTo)
{
// I was not able to find a definitive answer in any of the documents I
// found but it appears the final record in the recordset carries a
// [PrincipalType] = "Group" (all others carry a [PrincipalType] = "User").
if (myPage.PrincipalType != "Group")
{
// Insert "User" data into the List<AppRoleByUser> collection.
appRoleByUser.Add(new AppRoleByUser{ AzureDisplayName = myPage.PrincipalDisplayName, AzureUserRole = myPage.AppRoleId.ToString() });
}
else
{
// The "bRepeat" variable is initially set to true and is set to
// false when the "Group" record is detected thus signaling
// task completion and closing execution of the "while" loop.
bRepeat = false;
}
}
// Acquire the "nextLink" string from the response header.
// The "nextLink" string contains the "skiptoken" string required for the next
// iteration of the query.
string nextLinkValue = appRoleAssignedTo.AdditionalData["#odata.nextLink"].ToString();
// Parse the "skiptoken" value from the response header.
string skipToken = nextLinkValue.Substring(nextLinkValue.IndexOf("=") + 1);
// Include the "skiptoken" as a request header in the next iteration of the query.
queryOptions = new List<QueryOption>()
{
new QueryOption("$skiptoken", skipToken)
};
}
That's a long answer to what should have been a simple question. I am relatively new to Microsoft Graph but it appears to me Microsoft has a long way to go in making this API developer-friendly. All I needed was to know the combination of AppRoles and Users associated with a single, given Azure AD app. One query and one response should have been more than sufficient.
At any rate, I hope my toil might help save time for someone else going forward.
Could you please remove "Filter" from the code and retry the operation. Let us know if that worked.

Allowing only one answer in Google forms

Hi I want to create a Google form which i can collect student data,
And i want to make a way to allow my students to only enter one answer one time
in example I want to use this method in the question which asks for the admission number, admission number is a uniqe number which will not be repeated. So if a student enter an admission number that is already entered , I want them to see an error message with "The admission number has been already used"
Issue:
Avoid multiple form responses to be submitted with the same value for a specific form item.
Workflow:
In order to avoid certain responses to be submitted, you have to use some kind of data validation for the Admission number item, as shown in Set rules for your form.
This data validation should be updated every time a form response is submitted, since the newly submitted Admission number should be forbidden in future responses.
In order to update this validation every time the form is submitted, you could use an Apps Script onFormSubmit() trigger, which would then fire a function on every form submission. Let's call this function updateDataValidation.
The onFormSubmit trigger can be installed either manually, following these steps, or programmatically, via executing the function createTrigger from the code sample below once.
Every time updateDataValidation fires, it should do the following:
Find all the previous responses for your Admission number item. You could first retrieve all form responses via Form.getResponses() and then find the responses related to your item by checking its title (see Item.getTitle()).
Transform the array of admission numbers retrieved in previous step into a regex pattern that can be used for the data validation, which could be like this: ^{number1}$|^{number2}$|^{number3}$....
Use TextValidation in order to update the validation rules for your item.
Code sample:
function updateDataValidation(e) {
const itemTitle = "Admission number"; // Name of the form item. Change accordingly
const errorMessage = "The admission number has been already used.";
const form = e ? e.source : FormApp.getActiveForm();
const responses = form.getResponses();
const admissionNumbers = responses.map(response => {
return response.getItemResponses().find(itemResponse => {
return itemTitle === itemResponse.getItem().getTitle();
});
}).map(itemResponse => itemResponse.getResponse());
const numbersPattern = admissionNumbers.map(number => `^${number}\$`).join("|");
const item = form.getItems().find(item => item.getTitle() === itemTitle);
let validation = FormApp.createTextValidation()
.setHelpText(errorMessage)
.requireTextDoesNotMatchPattern(numbersPattern)
.build();
item.asTextItem().setValidation(validation);
}
function createTrigger() {
const form = FormApp.getActiveForm();
ScriptApp.newTrigger('updateDataValidation')
.forForm(form)
.onFormSubmit()
.create();
}
Notes:
In the code sample above, the item Admission number is assumed to be named Admission number. Change that from the code if that's not the case.
In the code sample above, the item Admission number is assumed to be a text item, like Short answer.

Stripe SCA checkout in ruby on rails

I am trying to update a monolithic rails stripe form to be SCA complaint, this is the documentation I am following https://stripe.com/docs/payments/checkout/migration#api-products-afterenter image description here
but is give in me error, invalid parameter, the error is in the picture, enter image description here
this is my code
const stripe = Stripe('pk_test_BqQVTnHd7yKaGgzUK9q8m8Ub00');
let checkoutButton = document.querySelector('#checkout-button');
checkoutButton.addEventListener('click', function () {
stripe.redirectToCheckout({
items: [{
// Define the product and SKU in the Dashboard first, and use the SKU
name: 'onex',
// ID in your client-side code.
id: 1,
parent: 'sku_Ft94t7sJmbJHlY',
sku: 'sku_123',
quantity: 1
}],
successUrl: 'https://www.example.com/success',
cancelUrl: 'https://www.example.com/cancel'
});
});
and I created a product in stripe dashboard
enter code here
enter image description here
According to Stripe's documentation, the only properties you can pass into the items list of stripe.redirectToCheckout are sku, plan, and quantity.
https://stripe.com/docs/stripe-js/reference#stripe-redirect-to-checkout
So in this case, the name, id, and parent properties are invalid. The "client-side" checkout integration is made to be pretty simplistic, where you create the SKU in the dashboard and simply include it in your JS.
If you need more control, I'd recommend looking at their server-side integration, which involves creating a Checkout Session.
https://stripe.com/docs/payments/checkout/server#create-checkout-session

Mongo and Node.js: unable to look up document by _id

I'm using the Express framework and Mongodb for my application.
When I insert objects into the database, I use a custom ObjectID. It's generated using mongo's objectid function, but toString()ed (for reasons i think are irrelevant, so i won't go into them). Inserts look like this:
collection.insert({ _id: oid, ... })
The data is in mongo - i can db.collection.find().pretty() from mongo's cli and see it exactly as it's supposed to be. But when I run db.collection.find({_id: oid}) from my application, I get nothing ([] to be exact). Even when I manually hardcode the objectID that I'm looking for (that I know is in the database) into my query, I still can't get an actual result.
As an experiment, I tried collection.find({title: "test title"}) from the app, and got exactly the result I wanted. The database connection is fine, the data structure is fine, the data itself is fine - the problem is clearly with the _id field.
Query code:
var sid = req.param("sid");
var db = req.db;
var collection = db.get("stories");
collection.find({'_id': sid }, function(err, document) {
console.log(document.title);
});
Any ideas on how I can get my document by searching _id?
update: per JohnnyHK's answer, I am now using findOne() as in the example below. However, it now returns null (instead of the [] that i was getting). I'll update if I find a solution on my own.
collection.findOne({'_id': sid }, function(err, document) {
console.log(document);
});
find provides a cursor as the second parameter to its callback (document in your example). To find a single doc, use findOne instead:
collection.findOne({'_id': sid }, function(err, document) {
console.log(document.title);
});

creating a record with Ember.js & Ember-data & Rails and handling list of records

I'm building an app which has layout like below.
I want to create a new post so I pressed 'new post button' and it took me to 'posts/new' route.
My PostsNewRoute is like below (I followed the method described here)
App.PostsNewRoute = Ember.Route.extend({
model: function() {
// create a separate transaction,
var transaction = this.get('store').transaction();
// create a record using that transaction
var post = transaction.createRecord(App.Post, {
title: 'default placeholder title',
body: 'default placeholder body'
});
return post;
}
});
It immediately create a new record, updates the list of the posts, and displays forms for new post.
(source: sunshineunderground.kr)
Now I have two problems.
One is the order of post list.
I expected new post will be on top of the list, but It's on the bottom of the list.
I'm using rails as my backend and I set the Post model order to
default_scope order('created_at DESC')
so old Post sits below within existing posts. but newly created one is not. (which isn't commited to backend yet)
Another is When I click created post in the list
I can click my newly created post in the posts list. and It took me to a post page with URL
/posts/null
and It's very weird behavior that I must prevent.
I think there will be two solutions.
When I click 'new post button' create a record and commit to server immediately and when server successfully saved my new record then refresh the posts list and enter the edit mode of newly created post.
or initially set Route's model to null and create a record when I click 'submit' button in a PostsNewView.
or show show only posts whose attribute is
'isNew' = false, 'isDirty' = false,
in the list..
But sadly, I don't know where to start either way...
for solution 1, I totally get lost.
for solution 2, I don't know how to bind datas in input forms with not yet existing model.
for solution 3, I totally get lost.
Please help me! which will be ember's intended way?
(I heared that Ember is intended to use same solution for every developer)
Update
now I'm using solution 3 and still having ordering issue. Here is my Posts template code.
<div class="tools">
{{#linkTo posts.new }}new post button{{/linkTo}}
</div>
<ul class="post-list">
{{#each post in filteredContent}}
<li>
{{#linkTo post post }}
<h3>{{ post.title }}</h3>
<p class="date">2013/01/13</p>
<div class="arrow"></div>
{{/linkTo}}
</li>
{{/each}}
</ul>
{{outlet}}
Update
I solved this problem by filtering 'arrangedContent' ,not 'content'
App.PostsController = Ember.ArrayController.extend({
sortProperties: ['id'],
sortAscending: false,
filteredContent: (function() {
var content = this.get('arrangedContent');
return content.filter(function(item, index) {
return !(item.get('isDirty'));
});
}).property('content.#each')
});
We use a variation of solution 3 in several places on our app. IMHO it's the cleanest of the 3 as you don't have to worry about setup/teardown on the server side This is how we implement it:
App.PostsController = Ember.ArrayController.extend({
sortProperties: ['id'],
sortAscending: true,
filteredContent: (function() {
return this.get('content').filter(function(item, index) {
return !(item.get('isDirty'));
});
}).property('content.#each')
});
Then in your Posts template you loop through controller.filteredContent instead of controller.content.
For solution 1, there are many possibilities. You could define the following event:
createPost: function() {
var post,
_this = this;
post = App.Post.createRecord({});
post.one('didCreate', function() {
return Ember.run.next(_this, function() {
return this.transitionTo("posts.edit", post);
});
});
return post.get("store").commit();
}
It creates the post then sets up a promise that will be executed once "didCreate" fires on the post. This promise transitions to the post's route only after it has come back from the server, so it will have the correct ID.
Indeed, very nice write up. Thx for that.
Doesn't your filteredContent have to use the isNew state i.o. isDirty, otherwise the Post that is being edited will not be visible.
In either case, the filteredContent property does not work in my case. I also noticed that, since I use an image as part of every element, all images will be refreshed when filteredContent is changed. This means that I see a request for every image.
I use a slightly different approach. I loop through the content and decide whether or not to display the Post in the template:
# posts.handlebars
<ul class='posts'>
{{#each controller}}
{{#unless isNew}}
<li>
<h3>{{#linkTo post this}}{{title}}{{/linkTo}}</h3>
<img {{bindAttr src="imageUrl"}}/>
<a {{action deletePost}} class="delete-post tiny button">Delete</a>
</li>
{{/unless}}
{{/each}}
</ul>
This will only show the Post object after it is saved. The url in the H3-tag also contain the id of the newly created object i.o. posts/null.
One other thing I noticed in your question: instead of passing default values to createRecord, you can use the defaultValues property on the model itself:
So, instead of:
# App.PostsNewRoute
var post = transaction.createRecord(App.Post, {
title: 'default placeholder title',
body: 'default placeholder body'
});
you can do this:
# App.Post
App.Post = DS.Model.extend({
title: DS.attr('string', {
defaultValue: "default placeholder title"
}),
body: DS.attr('string', {
defaultValue: "default placeholder body"
})
});
# App.PostsNewRoute
var post = transaction.createRecord(App.Post);
I actually wrote a function to reverse the content array a while back:
http://andymatthews.net/read/2012/03/20/Reversing-the-output-of-an-Ember.js-content-array
That's a pretty old article, almost a year old, so it probably won't work as is, but the framework is there...
You can see it in action in this mini-app I wrote:
http://andymatthews.net/code/emberTweets/
Search for users in the input field at the top and watch the left side as they appear in order from newest to oldest (instead of oldest to newest).

Resources