How do I interrupt a sequence flow in OpenWhisk? - ibm-cloud

I'm writing a trigger on a Cloudant database that should convert each new document into a Slack notification.
I've created a sequence of two actions: one to prepare the Slack message, one to send it. To send the Slack message I'm using the package action provided by IBM Bluemix OpenWhisk.
Cloudant changes feed --> Prepare Text --> Slack Post --> response
As the triggers send me all Cloudant events (new/modified/deleted documents), how can I only forward to Slack the NEW document event and ignore things like deleted documents.

For synchronous processing, simply return an error in our action
function main(doc) {
if (doc._deleted) {
// ignore deleted documents
return { error: "ignoring deleted doc" };
} else {
// prepare the text for the Slack post action
const slackMessage = ...
return { text: slackMessage };
}
}
return new Error("ignoring deleted doc") works too.
Or using the Promise object, one can call reject(reason) to interrupt the flow of a sequence.
function main(doc) {
return new Promise((resolve, reject) => {
if (doc._deleted) {
// ignore deleted documents
reject({ error: "ignoring deleted doc" });
} else {
// prepare the text for the Slack post action
const slackMessage = ...
resolve({ text: slackMessage });
}
};
}
The call to reject will stop the sequence flow. Any of reject('interrupted!'), reject(new Error('interrupted!'), reject({ error: 'interrupted!' }).
Warning: reject() will not work. You must provide a reason to reject.

Related

Amplify Datastore subscription cost

I am trying to understand the cost of Datastore. It seems that it subscribes to all Mutations. So if there are 50 users, then each message will be send 50 times, even if it not required.
As each real time mutation costs money, we will be paying unnecessary 49 times for this real time message mutation.
Also , it seems to me SyncExpression doesn't have any effect on this Subscription.
I am really stuck here. It will be great of someone can clarify
Amplify generates the datastore boilerplate code for you, but you still need to call it. You won't pay for every user and every mutation.
You will only subscribe to a mutation (explicitly call the code to listen for changes) on a per-user basis for things that user is interested in. e.g. if you are viewing a TODO item, you'd subscribe the user to that item and they'll immediately see if someone else modify it on another device.
UPDATE
Long story... I was triggering back-end computation via GraphQL by making a lambda resolver. The computation took too long and the GQL call would timeout. I updated the code so the GQL call called itself asynchronously (re-trigger the lambda), and returned immediately. Then when the long-running task completed in the spun-up lambda, I updated the a record in the database.
I update the record using AppSync instead of direct GQL so it would trigger mutations, and in the react client, I listen to a mutation for the specific record that will be updated. This way, there is just 1 user listening (if they've triggered the long running action) and that user is only notified about changes to the single DB record they're interested in, and not receiving other user's updates.
I don't know if all this is applicable to your situation. The code snippets below may help you, but they're somewhat out of context.
// In amplify/backend/api/projectname/schema.graphql
type Subscription {
onCouponWithIdUpdated(id: ID!): Coupon #aws_subscribe(mutations: ["updateCoupon"])
}
// In my useSendCoupon hook...
// Subscribe to coupon updates
useEffect(() => {
if (0 === couponId) {
return
}
console.log(`subscribe to coupon updates for couponId:`, couponId)
const onCouponWithIdUpdated = /* GraphQL */ `
subscription OnCouponWithIdUpdated($id: ID!) {
onCouponWithIdUpdated(id: $id) {
id
proofLink
owner
}
}
`
const subscription = API
.graphql(graphqlOperation(onCouponWithIdUpdated, { id: couponId }))
.subscribe({
next: ({ provider, value }) => {
const coupon = value.data.onCouponWithIdUpdated
//console.log(`Proof Link:`, coupon.proofLink)
setProofLinks([coupon.proofLink])
setSendCouponState(COUPON_STATE_PREVIEW_SUCCESS)
},
error: error => console.warn(error)
})
console.log('subscribed: ', subscription)
return () => {
console.log(`unsubscribe to coupon updates`)
subscription.unsubscribe()
}
}, [couponId])
// inside a lambda...
const updateCouponWithProof = async (authorization, couponId, proofLink) => {
const initializeClient = () => new AWSAppSyncClient({
url: process.env.API_XXXX_GRAPHQLAPIENDPOINTOUTPUT,
region: process.env.REGION,
auth: {
type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS,
jwtToken: authorization
},
disableOffline: true,
})
const executeMutation = async (mutation, operationName, variables) => {
const client = initializeClient()
try {
const response = await client.mutate({
mutation: gql(mutation),
variables,
fetchPolicy: "no-cache",
})
return response.data[operationName]
} catch (err) {
console.log("Error while trying to mutate data", err)
throw JSON.stringify(err)
}
}
const updateCoupon = /* GraphQL */ `
mutation UpdateCoupon(
$input: UpdateCouponInput!
$condition: ModelCouponConditionInput
) {
updateCoupon(input: $input, condition: $condition) {
id
proofLink
owner
}
}
`
const variables = { input: { id: couponId, proofLink } }
try {
return await executeMutation(updateCoupon, 'updateCoupon', variables)
} catch (error) {
console.log(`executeMutation error`, error)
}
}

Cloud function http function fails on first run

I am testing with a payment processing system and every time a transaction is completed, the payment processor should hit my endpoint with a POST request with payment details so I can save it to my database (Firestore).
Only thing is the function fails on the first try. What I mean is, say a customer pays, the payment processor hits my cloud function, it fails to save to my database. When a second customer makes the transaction a minute, 5 minutes or even 18 minutes later according to my observation, everything works as expected.
Am I facing a cold start problem or what is happening. And how do I solve this.
Here is my function
exports.stkCallback = functions.https.onRequest(async (request, response) => {
if (request.method === 'POST') {
if (request.body.Body.stkCallback.ResultCode === 0) {
const jsonData = request.body.Body.CallbackMetadata;
console.log("USER HAS COMPLETED THE TRANSACTION");
var transactionID;
///This below line logs successfully everytime meaning my payment processor has sent the POST
/// request
console.log("checkoutid:", request.body.Body.CheckoutRequestID)
///I have saved the CheckoutRequestID previously to Firestore so I first query the document
//// with that ID (CheckoutRequestID) and get its data so I can update the transaction as
//// complete
var docRef=db.collection("Transactions").doc(request.body.Body.CheckoutRequestID);
await docRef.get().then((doc) =>{
// eslint-disable-next-line promise/always-return
if (doc.exists) {
//console.log("Document data:", doc.data());
transactionID=doc.id;
transactionData.push(doc.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch((error)=> {
console.log("Error getting document:", error);
});
///Once I get the data I can then go ahead and do other operations.
///Only the above query fails the first time which I don't know why
///By failing Saying No such Document. Which the document does exist
***carrying out other operations using the fetched transactionID and transactionData***
response.sendStatus(200);
} else {
console.log("USER HAS CANCELLED THE TRANSACTION");
response.sendStatus(200);
}
I have refactored my code and reproduced it to the below
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.stkCallback = functions.https.onRequest(async (request, response) => {
const accountSid = "#";
const authToken = "#";
const client = require("twilio")(accountSid, authToken);
if (request.method === 'POST') {
if (request.body.Body.ResultCode === 0) {
const jsonData = request.body.Body.CallbackMetadata;
console.log("USER HAS COMPLETED THE TRANSACTION");
var transactionID;
///This below line logs successfully everytime meaning my
/////payment processor has sent the POST request
console.log("checkoutid:",
request.body.Body.CheckoutRequestID)
////The below function is critical to all the other below functions below it as
///it supplies the necessary data all the way down
///It is also the function that fails on the first run
var docRef= await db.collection("Transactions").doc(request.body.Body.CheckoutRequestID).get()
.catch((error)=> {
console.log("Error getting document:", error);
});
//// a log of some data from the above function
//// when it fails, the below log is undefined,
console.log("tyyy",docRef.data().Home)
transactionID=docRef.id;
////the data returned from above function is used to perform other operations.
////Below is just one of them
////consequently, it will fail as some values like doc.data().Uid will be ////undefined
await db.collection("Users").doc(doc.data().Uid).collection("Transactions").doc(transactionID).update({
TransactionComplete: true,
transactionCompletedTimeDb: admin.firestore.FieldValue.serverTimestamp(),
Amount: jsonData.Item[0].Value,
ReceiptNO: jsonData.Item[1].Value,
TransactionDate: jsonData.Item[3].Value,
PhoneNumber: jsonData.Item[4].Value,
UserId: doc.data().Uid
})
// eslint-disable-next-line promise/always-return
.catch((error)=> {
// The document probably doesn't exist.
console.error("Error updating document: ", error);
});
response.sendStatus(200);
} else {
console.log("USER HAS CANCELLED THE TRANSACTION");
response.sendStatus(200);
}
});
Attaching an image of a failed function, do note the time
An image of logs of the same triggered function right after (3 minutes later). As you can see the function completes successfully
This seems like a Cold Start Issue
The mitigation of this issue will depend on many information that you are not sharing with us like the complete function, dependencies that you are using, and instance size.
Spreading a loaded function into multiple small functions will help with the cold start time, also using smaller, updated, and cloud oriented libraries will also help.
Also, the size of the payload could be an important factor here, how big is the size of the payload sent to the function and how big is the size of the info that you are writing into the logs? All these small pieces have an important influence on the performance of a cold start.
As a quick solution for your Issue, I can safely say that creating a Scheduled task that triggers your functions every 30 minutes, for example, would be enough to mitigate your issue in the short term.

How can I catch errors in my firebase function when setting a document fails?

I have a firebase cloud function to create a user document with user data whenever a user registers. How would I return an error when the set() fails? Since this is not an http request (an I don't want to use an http request in this case) I have no response. So how would I catch errors?
export const onUserCreated = functions.region('europe-west1').auth.user().onCreate(async user => {
const privateUserData = {
phoneNumber: user.phoneNumber
}
const publicUserData = {
name: 'Nameless'
}
try
{
await firestore.doc('users').collection('private').doc('data').set(privateUserData);
}catch(error)
{
//What do I put here?
}
try
{
await firestore.doc('users').collection('public').doc('data').set(publicUserData);
}catch(error)
{
//What do I put here?
}
});
You can't "return" an error, since the client doesn't even "know" about this function running, there is nobody to respond to.
You can make a registration collection, and in your function make a document there for the current user (using the uid as the document id). In that document, you can put any information you'd like your user to know (status, errors, etc).
So your clients would have to add a listener to this document to learn about their registration.
In your particular code, I think the error is in doc('users'). I guess you meant doc('users/'+user.uid).
Your catch -block will receive errors that occur on your set -call:
try {
await firestore.doc('users').collection('public').doc('data').set(publicUserData);
} catch (error) {
// here you have the error info.
}

How to stop the user from entering the duplicate record on default save

I have a custom module where there is an email field. Now i want to stop the user if the email is already in the database.
I want to stop the user on save button and show the error. Like when a required field goes empty.
I tried to get some help but was not able to understand it.
Note: I realized after posting this that you are using suitecrm which this answer will not be applicable toward but I will leave it in case anyone using Sugar has this question.
There are a couple of ways to accomplish this so I'll do my best to walk through them in the order I would recommend. This would apply if you are using a version of Sugar post 7.0.0.
1) The first route is to manually create an email address relationship. This approach would use the out of box features which will ensure your system only keeps track of a single email address. If that would work for your needs, you can review this cookbook article and let me know if you have any questions:
https://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_9.2/Cookbook/Adding_the_Email_Field_to_a_Bean/
2) The second approach, where you are using a custom field, is to use field validation. Documentation on field validation can be found here:
https://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_9.2/Cookbook/Adding_Field_Validation_to_the_Record_View/index.html
The code example I would focus on is:
https://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_9.2/Cookbook/Adding_Field_Validation_to_the_Record_View/#Method_1_Extending_the_RecordView_and_CreateView_Controllers
For your example, I would imagine you would do something like this:
Create a language key for your error message:
./custom/Extension/application/Ext/Language/en_us.error_email_exists_message.php
<?php
$app_strings['ERROR_EMAIL_EXISTS_MESSAGE'] = 'This email already exists.';
Create a custom controller for the record creation (you may also want to do this in your record.js):
./custom/modules//clients/base/views/create/create.js
({
extendsFrom: 'RecordView',
initialize: function (options) {
this._super('initialize', [options]);
//reference your language key here
app.error.errorName2Keys['email_exists'] = 'ERROR_EMAIL_EXISTS_MESSAGE';
//add validation tasks
this.model.addValidationTask('check_email', _.bind(this._doValidateEmail, this));
},
_doValidateEmail: function(fields, errors, callback) {
var emailAddress = this.model.get('your_email_field');
//this may take some time so lets give the user an alert message
app.alert.show('email-check', {
level: 'process',
title: 'Checking for existing email address...'
});
//make an api call to a custom (or stock) endpoint of your choosing to see if the email exists
app.api.call('read', app.api.buildURL("your_custom_endpoint/"+emailAddress), {}, {
success: _.bind(function (response) {
//dismiss the alert
app.alert.dismiss('email-check');
//analyze your response here
if (response == '<email exists>') {
errors['your_email_field'] = errors['your_email_field'] || {};
errors['your_email_field'].email_exists = true;
}
callback(null, fields, errors);
}, this),
error: _.bind(function (response) {
//dismiss the alert
app.alert.dismiss('email-check');
//throw an error alert
app.alert.show('email-check-error', {
level: 'error',
messages: "There was an error!",
autoClose: false
});
callback(null, fields, errors);
})
});
},
})
Obviously, this isn't a fully working example but it should get you most of the way there. Hope this helps!

Parse - Sending push notifications using cloud code (Swift)

I'm trying to set up push notifications from one user to another using Back4App which is a parse server. I have followed their guide here
the Javascript cloud code they use is below:
Parse.Cloud.define("pushsample", function (request, response) {
Parse.Push.send({
channels: ["News"],
data: {
title: "Hello from the Cloud Code",
alert: "Back4App rocks!",
}
}, {
success: function () {
// Push was successful
response.success("push sent");
console.log("Success: push sent");
},
error: function (error) {
// Push was unsucessful
response.error("error with push: " + error);
console.log("Error: " + error);
},
useMasterKey: true
});
});
I am updating a custom parse class called notifications within the app which I would also like to send to the user the notification is directed at. When saving this class I am grabbing the UserID which is also stored in the installation class used to send pushes. I am completely new to Javascript so am wondering if someone could tell me how to edit the above code to receive the userID from the method on the device and then run a query to send to just this user.
The push notification feature allows to configure options and customizing the push.
You can send a query to update one specific user. Please, take a look at the example below:
Parse.Cloud.define("sendPushToUser", async (request) => {
var query = new Parse.Query(Parse.Installation);
let userId = request.params.userId;
query.equalTo('userId', userId);
Parse.Push.send({
where: query,
data: {
alert: "Ricky Vaughn was injured in last night's game!",
name: "Vaughn"
}
})
.then(function() {
// Push was successful
}, function(error) {
// Handle error
});
});
At the moment, you can read more about these options here.