I'm using the DocuSign REST API to automate document signing. I use the sender view to allow my clients to place signing tabs on their documents and ultimately send them.
However, I'm running into a problem where the client, for whatever reason, needs to start over in our workflow, and so abandons the embed without saving it and contacting us to restart the process.
Normally, we'd call Update to mark the Envelope as voided and then create a new one, but because the client didn't exit the embed properly, the Envelope is still marked as locked.
I thought the Delete Lock endpoint would allow us to remove the lock so that we could edit the envelope, but it's returning an error message saying that we can't delete the lock as we weren't the ones who put it there.
Given that the same credentials were used for both the embed window and the delete lock call, why does DocuSign treat us like two different users? And is there a way to get around the lock?
This might help some folks in the future.
"by appending a lockToken parameter to the view URL, you can give your
integration control over the envelope lock, enabling it to make
changes to the envelope without having to wait for the DocuSign token
to expire."
http://m.mmffs.com/the-trenches-whos-locking-my-envelopes.html
The article shows how to accomplish this using the API directly. In the code snippet below, this is how you would do it going against the DocuSign Nuget package.
var lockInfo = envelopesApi.CreateLock(accountID, envelopeID, new LockRequest()
{
LockedByApp = "[Your App]",
LockType = "edit"
});
existingViewUrl += "&lockToken={lockInfo.LockToken}";
Note, the third LockRequest parameter is required for this to work even though it is not required by the CreateLock method. Credit to Inbar Gazit - Error trying to add a document to envelope and getting a user lock error message
Now, the normal DeleteLock method will work.
try
{
var lockInfo = envelopesApi.GetLock(accountId, envelopeID);
if (lockInfo?.LockDurationInSeconds?.Length > 0)
{
envelopesApi.ApiClient.Configuration.AddDefaultHeader("X-DocuSign-Edit", $"{{\"lockToken\":\"{lockInfo.LockToken}\"}}");
envelopesApi.DeleteLock(accountId, envelopeID);
}
}
catch(ApiException e)
{
// Not locked.
}
Lock is done by NDSE not your credentials, solution is to wait for sometime and lock will be released by NDSE or you have to redirect user to same screen again and the user needs to select Save and Close instead of Discard Changes. Once user selects Discard Changes then envelope is moved to Deleted folder.
You can try code below:
var config = new Configuration(new ApiClient(basePath));
config.AddDefaultHeader("Authorization", "Bearer " + accessToken);
var envelopesApi = new EnvelopesApi(config);
try
{
// this line will throw error if envelope is not locked, so handle it using try & catch block
LockInformation lockInfo = envelopesApi.GetLock(accountId, envelopeId);
// check if this app locked it
if (lockInfo?.LockDurationInSeconds?.Length > 0)
{
// add a header with the LockToken to ensure this app has the right to unlock
string LockHeader = $"{{\"lockToken\":\"{lockInfo.LockToken}\"}}";
envelopesApi.Configuration.AddDefaultHeader("X-DocuSign-Edit", LockHeader);
envelopesApi.DeleteLock(accountId, envelopeId);
}
}
catch (ApiException exp)
{
// Do Whatever you want to do
}
Related
I have this bit of code that creates a calendar for a specific delivery, and then attaches it to an e-mail to be send. The problem is, when I attach the .ics file, the template breaks (no text, no pictures... nothing). Only the calendar file gets send (even though it does get send to the right e-mail).
const event = await this.calendarService.getEventForDelivery(deliveryId);
await this.sendMailjetTemplate('4069368', d.email, [{contentType: 'text/calendar', content: event}], {
ASN: d.identifier,
});
I fixed my issue by specifying a "text" attribute to my mail.
I'm setting up a reset password intent using Dialogflow, where I'm performing some validation via webhooks. Unfortunately, I'm not able to figure out how to reprompt the user in case of failed validation.
I've tried to trigger the intent again using an event, but it doesn't seem to be working. I've also tried setting the same input contexts to trigger the intent again, but neither seem to work.
So I've created 2 parameters within the intent, which are being filled via prompts, following which I am performing the validation. Here's the code:
function getPasscode(agent) {
console.log(agent.parameters);
if(/^\d{6}$/.test(agent.parameters.code1) && agent.parameters.code1 == agent.parameters.code2) {
// Reset passcode call
} else {
return new Promise((resolve, reject) => {
agent.add("Your codes don't match. Please try again.");
var output = JSON.stringify({"followupEvent": {"name": "GetPasscode", "data": {}}})
resolve(output);
});
}
}
The bot outputs the text properly, but isn't triggering the event, as intended.
Am I missing something?
Remember that Intents represent what the user does and not what your action is trying to do. In general, you don't "trigger" an Intent - the user does.
So if you're "reprompting" the user - send that prompt in your reply to them. Then make sure the Intent is setup to capture their reply. This may involve in your setting an Output Context to narrow which Intents are evaluated to consider the reply.
You can't both send back a response and trigger an Intent with an event. Sending an event from your fulfillment is almost never needed and, when done, discards anything you may already have set for a response. All it does is cause the Intent with the event registered to it to be triggered. (Your code has two problems in this respect - you both try to send a response, and you're trying to send the followup event incorrectly.)
In your use-case, you do not need to call the event as per my understanding. Better way to do this is :
Set-up intent where you ask and confirm the password and store it
Validate this in your webhook
Here is the pseudo code:
if validationPassed {
call your api to reset password
send reset password confirmation output to user
}
if validationFailed {
setup output context to ask-password intent again
send output to user to re-enter the password
}
As #Prisoner says, you do not trigger an intent, the user does. We do the processing and send the response once the intent is triggered.
Hope it helps.
I'm having some issues to update an interactive message after responding to a slack dialog. I'm using botkit on a node.js server.
Here is my workflow:
User trigger an interactive message via a slash command
User click a button on that message
A dialog pops up, user fill the form and validate
Something is done on the server side
The first message should update
Now, here is the logic I'm using:
User trigger an interactive message via a slash command
Nothing fancy, I use:
controller.on('slash_command', function (bot, message)
Then I parse the command, and send the appropriate message, with the appropriate attachments (buttons)
User click a button on that message
Same, I use the event sent by botkit:
controller.on('interactive_message_callback', function (bot, message)
Then I create a dialog:
var dialog = bot.createDialog(
'Which book?',
JSON.stringify(callback),
'Ok'
)
Here I'm doing something really (really) dirty, and should not be done. But that's the only way I found to update the initial message after the dialog is filled.
The callback_id actually contains an object, with the response_urlof the initial message (and something to identify the form).
A dialog pops up, user fill the form and validate
Something is done on the server side
Here, I use once more the event provided by botkit:
controller.on('dialog_submission', function (bot, message)
then I parse the message.submission.callback_id and detect the response_url. With this, I can create an object I call originalMessage.
The first message should update
At the moment I use :
bot.replyInteractive(originalMessage, 'DONE, everything is saved.');
with originalMessagecontaining the response_url of the first message.
It does work. The first message is being replaced by the new one.
But I'm really not happy with that solution, and was wondering if I was missing something somewhere. I've seen couple apps having that type of workflow, so there must be a way.
Thank you for your help :)
I wrote to Slack to ask about this situation and got a great suggestion from Mark P:
Use the state dialog field to pass the original response_url to the dialog. Then when you receive the dialog data, you can use state instead of response_url.
I just tried it and it worked great. No need to store any state on your own server.
I don't know how that would work exactly with Node and botkit, since that's not what I use.
To flesh this out a bit more:
Someone clicks a button and Slack POSTs about that interaction to your configured "Request URL".
From Slack's payload, get the "response_url" value.
When you call dialog.open in the Slack API, pass along this response_url as the "state" value.
When the dialog is submitted, Slack again POSTs to your "Request URL".
From Slack's payload, get the "state" value and use it as a response_url.
Profit!
This only works if you hold the original message object somewhere on your server for future reference.
So on creating the interactive dialog store it somewhere and add a reference. I use uuids.
let newId = uuid();
messageStore[newId] = message;
var dialog = bot.createDialog(
'My Dialog',
'idPrefix_' + newId,
'Submit'
).addText('Sample Input', 'input', '');
bot.replyWithDialog(message, dialog.asObject());
Then once you get your interactive dialog response back disassemble the prefix and the uuid and get your original message object back from the servers memory. Then use ´replayInteractive` there.
controller.on('dialog_submission', function handler(bot, message) {
if (message.callback_id.indexOf('idPrefix') === 0) {
let id = message.callback_id.substr('idPrefix_'.length);
bot.dialogOk();
let originalMessage = messageStore[id];
bot.replyInteractive(originalMessage, {
text: 'replacing the original message with this.'
});
}
});
Be careful that you do not create a memory leak here. You have to find a way to clean up your messageStore over time.
I want to prevent a record from being deleted based on certain conditions in NetSuite. However, I can't seem to find a failure event on validation? BeforeSubmit on a UserEvent has a delete type, but it doesn't have a return value for the function, so I can't just say return false, can I?
How do I then prevent the deletion of a record, or fail a record submission of a certain type?
Instead of returning false, just throw an error. Not user friendly but it is the only option, as far as I know.
Since it is in the Before Submit User Event, the submission or deletion will fail.
You'll want to use a beforeSubmit user event script and throw an error using nlapiCreateError. Here's an example:
function beforeSubmit(type) {
if(type == 'delete') {
// NOTE `nlapiGetFieldValue` returns nil/empty during a delete event
// `nlapiGetOldRecord` needs to be used to pull the entire record
var payment = nlapiGetOldRecord();
if(payment.getFieldText('paymentmethod') == 'cash') {
throw nlapiCreateError(
'AN_ERROR',
'Message to the user!',
true
);
}
}
}
You can also use a Workflow to prevent the Delete button to display to the user:
You could add client-side code to pop up a message to the user when trying to delete a record you want to prevent. With that said, you would still want to put a UserEvent script preventing the delete actions as well. This would prevent the user from by passing the restriction
I have a document library setup to recieve emails. The emails coming in have a single picture and a csv file which I use for some processing.
The override emailrecieved works perfectly but of course as I override I lose the nice SharePoint functionaliy that saves the incomming email as configured in the settings.
It was my understanding that I could call MyBase.EmailRecieved in my event for the underlying functionality to still work. This however is not working and no record of the email coming in is getting retained.
For now I am explicitly creating an audit trail but I would like to rely on SharePoints existing functionality as I believe it will be more robust.
What am I doing wrong with the MyBase.EmailRecieved call? Or what can I do instead if this doesnt work?
Thanks in advance.
When writing your own EmailReceived event receiver you will loose the default functionality.
What you will have to do is to implement this default functionality yourself. Let me give you a simple example. The following example saves all mail attachments to the list if they are *.csv files. You can do the same with the emailMessage and save it to the list as well. As you can see it is as easy as to add Files.Add to add a file to a document library.
public override void EmailReceived(SPList list, SPEmailMessage emailMessage, string receiverData)
{
SPFolder folder = list.RootFolder;
//save attachments to list
foreach (SPEmailAttachment attachment in emailMessage.Attachments)
{
if (attachment.FileName.EndsWith(".csv"))
{
var attachmentFileName = attachment.FileName;
folder.Files.Add(folder.Url + "/" + attachmentFileName, attachment.ContentStream, true);
}
}
list.Update();
}