How to set Job-specific minPayment in v2 jobs for job type webhook? - chainlink

Chainlink v1 jobs allowed to set job-specific mininmum Payment with the minPayment keyword
{
"initiators": [
{
"type": "RunLog",
"params": { "address": "0x51DE85B0cD5B3684865ECfEedfBAF12777cd0Ff8" }
}
],
"tasks": [
{
"type": "HTTPGet",
"confirmations": 0,
"params": { "get": "https://bitstamp.net/api/ticker/" }
},
{
"type": "JSONParse",
"params": { "path": [ "last" ] }
},
{
"type": "Multiply",
"params": { "times": 100 }
},
{ "type": "EthUint256" },
{ "type": "EthTx" }
],
"startAt": "2020-02-09T15:13:03Z",
"endAt": null,
"minPayment": "1000000000000000000"
}
It seems to be missing in v2 toml jobs. For now only directRequest type v2 jobs have this which were added with this PR
The v1 Job type that serves our purpose is.
name: 'get-request',
initiators: [
{
type: 'external',
params: {
name: process.env.CHAINLINK_EI_NAME,
body: {},
},
},
],
tasks: [
{
type: 'httpget',
},
{
type: 'jsonparse',
},
{
type: process.env.CHAINLINK_BRIDGE_NAME,
},
],
minPayment: '1',
};
How can we set minPayment for webhook type jobs in v2 TOML jobs?

You can do that using the minContractPaymentLinkJuels.
For example:
type = "directrequest"
schemaVersion = 1
name = "my job"
contractAddress = "ORACLE_ADDRESS_HERE"
minContractPaymentLinkJuels = 1000000000000000000
observationSource = """
ds1 [type=bridge name="bridge-data-feed" requestData="{\\"data\\": {\\"from\\":\\"eth\\", \\"to\\", \\"USD\\"}}"];
ds1
For webhook type jobs, you'll actually want to use a custom spec (for example with an external initiator)
type = "webhook"
schemaVersion = 1
externalInitiators = [
{ name = "my-external-initiator-1", spec = "{\"minContractPaymentLinkJuels\": 1000000000000000000}" },
]
observationSource = """
ds1 [type=bridge name="bridge-data-feed" requestData="{\\"data\\": {\\"from\\":\\"eth\\", \\"to\\", \\"USD\\"}}"];
ds1
"""
The spec defines the JSON payload that will be sent to the External Initiator on job creation if the external initiator has a URL, and you can check the amount sent to the node is correct.

Related

Triggering an Action.Execute on an adaptive card sent by a chat bot on Teams mobile apps caused a "Something Went Wrong" error

Versions
Package: botbuilder#4.11.0
Nodejs: v16.19.0
Teams Android Client: 1416/1.0.0/2023012702/0115
I have a chatbot that functions as a notification bot that sends an adaptive card notification to our users.
On the card, the user can reply to the notification by typing in the Input.Text field and then clicking a send button that triggers an Action.Execute, our Nodejs bot will then process the activity by sending notifications to corresponding user and then updating the card with a new card.
This works perfectly on Web and Desktop app but in the Teams Android App the card will briefly shows an error message saying "Something went wrong" before our card is finally updated.
These is the card that I send:
{
"type": "AdaptiveCard",
"appId": process.env.MicrosoftBotAppId,
"body": [
{
"type": "TextBlock",
"text": message,
"wrap": true
},
{
"id": "history_title",
"type": "TextBlock",
"text": cardString.pastEvents,
"wrap": true
},
{
"type": "Container",
"spacing": "None",
"items": [
{
"id": "last_trail_name",
"type": "TextBlock",
"text": lastTrail ? cardString.action(lastTrail.userName, lastTrailAction) : "",
"spacing": "None",
// "size": "Small",
"wrap": true
},
{
"id": "last_trail_comment",
"type": "TextBlock",
"text": lastTrail ? `${lastTrail.comment.replace(/<\/?[^>]+(>|$)/g, "")}` : "",
"spacing": "None",
// "size": "Small",
"wrap": true
},
{
"id": "last_trail_date",
"type": "TextBlock",
"text": lastTrail ? `${lastTrailDateString}` : "",
"spacing": "None",
"isSubtle": true,
"size": "Small",
"wrap": true
}
]
},
{
"type": "Container",
"items": [
{
"id": "second_last_trail_name",
"type": "TextBlock",
"text": secondLastTrail ? cardString.action(secondLastTrail.userName, secondLastTrailAction) : "",
"spacing": "None",
// "size": "Small",
"wrap": true
},
{
"id": "second_last_trail_comment",
"type": "TextBlock",
"text": secondLastTrail ? `${secondLastTrail.comment.replace(/<\/?[^>]+(>|$)/g, "")}` : "",
"spacing": "None",
// "size": "Small",
"wrap": true
},
{
"id": "second_last_trail_date",
"type": "TextBlock",
"text": secondLastTrail ? `${secondLastTrailDateString}` : "",
"spacing": "None",
"isSubtle": true,
"size": "Small",
"wrap": true
}
]
},
{
"id": "comment",
"isRequired": true,
"type": "Input.Text",
"placeholder": cardString.commentPlaceholder,
"isMultiline": true
},
{
"id": "success-msg",
"type": "TextBlock",
"text": success ? cardString.commentSentAlert : "",
"spacing": "None",
"isSubtle": true,
"size": "Small",
"wrap": true,
"color": "good"
}
],
"actions": [
{
// "tooltip": isPremium ? cardString.send : cardString.premiumTooltip,
// "isEnabled": isPremium,
"type": "Action.Execute",
"verb": "comment",
"title": cardString.send,
"data": data,
},
{
"type": "Action.OpenUrl",
"title": cardString.goToTicketButton,
"url": deeplink
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4"
}
And this is the bot that processed the action:
class Bot extends TeamsActivityHandler {
constructor() {
super();
this.onMembersAdded(async (turnContext: TurnContext, next: () => Promise<any>) => {
const gettingStartedUrl: string = 'https://www.teamswork.app/gettingstarted';
const membersAdded = turnContext.activity.membersAdded;
for (let cnt = 0; cnt < membersAdded.length; cnt++) {
if (membersAdded[cnt].id !== turnContext.activity.recipient.id) {
const welcomeMessage = "Welcome message"
// add conv references
if (await this.storeConversationReference(turnContext))
await turnContext.sendActivity(welcomeMessage); // Only send notification if conversation reference store (one time)
}
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
}
// Listener for activity e.g. comment activity on notification card
async onInvokeActivity(turnContext: TurnContext): Promise<InvokeResponse<any>> {
// Listen for activity invocation and check if the comment action on adaptiveCard has been called
const activity: Activity = turnContext.activity;
// context.log('activity: ', activity);
// turnContext.sendActivity("Hello World!");
let card: object;
if (activity.name === "adaptiveCard/action") {
// context.log("action activity");
const action = activity.value.action;
// context.log("action : ", action);
if (action.verb === "comment") {
const userName: string = activity.from.name;
const userId: string = activity.from.aadObjectId;
const data: IActionData = action.data;
// context.log('data: ', action.data);
const date: Date = new Date();
const tenantId: string = activity.conversation.tenantId;
const comment: string = data.comment;
const lang: string = data.lang || "en";
// assignee Ticket == user commennt
if ((data.ticket.assigneeId == userId) && (data.ticket.firstTimeResponse == "")) {
this.updateTicket(data.ticket)
}
// Check for ticketId
if (!!!data.ticket) {
context.log("No ticketId found, aborting");
// throw ("No ticketId found, aborting")
}
// Construct new audit trail / comment
let newComment: IAuditTrail = {
userName: userName,
userId: userId,
ticketId: data.ticket.id,
action: "commented",
comment: comment,
date: date,
}
// initialize parameters for card to be sent
const cardContent: ICardContent = {
userName: userName,
type: "comment",
action: "commented",
comment: comment,
isRequestor: false,
}
// Prepare response message
const encodedDeeplink: string = constructDeeplink(data.ticket, data.ticketingAppUrl, data.channelId, data.entityId);
const message: string = constructMessage(data.ticket, data.cardContent, encodedDeeplink, lang);
const isPremium: boolean = await getPremium(tenantId);
// const card = invokeSuccessResponse(encodedDeeplink, message, data);
// Save audit trail
const insertCommentEndpoint: string = process.env["InsertCommentEndpoint"];
try {
await cosmosService.insertItem(insertCommentEndpoint + encodeQueryParams({ instanceId: data.ticket.instanceId }), { comment: newComment });
} catch (error) {
context.log(error);
}
// Send request to SendCardNotification API
try {
// Notification recipient id array
let recipientIds: string[] = [];
if (data.ticket.requestorId)
recipientIds.push(data.ticket.requestorId)
if (data.ticket.assigneeId)
recipientIds.push(data.ticket.assigneeId)
// If ticket have custom fields get people from them
if (data.ticket.customFields) {
// Get instance so we can get customFieldSetting
const instanceContainer: Container = ticketDatabase.container(process.env["InstanceContainer"]);
const { resource: instance } = await instanceContainer.item(data.ticket.instanceId, tenantId).read<IInstance>();
// Create array for user who will receive the notification
let activePersonaCustomFields: ICustomField[] = [];
if (instance.customFieldsLeft)
activePersonaCustomFields.push(...instance.customFieldsLeft);
if (instance.customFieldsRight)
activePersonaCustomFields.push(...instance.customFieldsRight);
activePersonaCustomFields = activePersonaCustomFields.filter((value) => value.type.key === "6_peoplepicker");
activePersonaCustomFields = activePersonaCustomFields.filter((value) => value.isReceiveNotification);
if (activePersonaCustomFields.length > 0) {
for (const fields of activePersonaCustomFields) {
if (data.ticket.customFields[fields.id]?.length > 0) {
const personas: IPersonaProps[] = data.ticket.customFields[fields.id];
for (const element of personas) {
recipientIds.push(element.id)
}
}
}
}
}
// Remove message sender from recipient list
recipientIds = recipientIds.filter((id) => id !== userId);
// Get unique notification recipient array
const uniqueRecipientIds: string[] = [... new Set(recipientIds)];
context.log("Sending notifications");
for (const id of uniqueRecipientIds) {
await this.sendNotification(cardContent, id, tenantId, data);
}
card = NotificationCard(encodedDeeplink, message, data, true, isPremium, lang);
const cardAttachment: Attachment = CardFactory.adaptiveCard(card);
const activityPayload = MessageFactory.attachment(cardAttachment);
activityPayload.id = turnContext.activity.replyToId;
context.log("Updating cards");
await turnContext.updateActivity(activityPayload);
} catch (error) {
context.log(error)
}
}
}
const cardRes = {
statusCode: StatusCodes.OK,
type: 'application/vnd.microsoft.card.adaptive',
value: card
};
const res = {
status: StatusCodes.OK,
body: cardRes
};
return res;
}
}
It actually does not matter what I do on the card, even if I do nothing and then only return the InvokeResponse, the error still happens.
Expected behavior
The card is updated without any error message shown.
Screenshots

pass data from one Struct to another Struct and convert to json

Hi I have json data which I Unmarshal to machines slice . Now I am looking to copy/append each cluster information, hostname from machines slice struct to Cluster Struct []Cluster and try to populate different values in that struct.
Each machine record has an associated serviceName . I am Looking for the out json format in the desired output below where service_name ,required, vars, value are only passed once even though they are associated with each json record when passed from machine slice.
current Code :
https://go.dev/play/p/6zVRIaLIgdN
Desired output :
{
"cluster_name": "dev",
"services": [
{
"service_name": "serviceA",
"required" : true,
"vars": {
"common_vars": {
"user": "custom-user",
"group": "custom-group"
}
},
"hosts": [
{
"host_name": "host1",
"vars": {
"common_vars" :{
"id": 1
}
}
},
{
"host_name": "host2",
"vars": {
"common_vars":{
"id": 2
}
}
}
]
},
{
"service_name": "serviceB",
"required" : false
.....
}
}
]
}
Current OutPut:
where the ServiceName is repeated with every machine name , I want it to have service_name once in the slice as above output
"cluster_name": "dev",
"services": [
{
"service_name": "serviceA",
"required": true,
"hosts": [
{
"host_name": "Machine-1",
"vars": {
"common_vars": {
"id": "1"
},
"custom_listeners": {}
}
}
],
"vars": {
"custom_listeners": {}
}
},
{
**"service_name": "serviceA"**,
"required": true,
"hosts": [
{
"host_name": "Machine-2",
"vars": {
"common_vars": {
"id": "2"
},
"custom_listeners": {}
}
}
],
"vars": {
"custom_listeners": {}
}
}
]
}
You have to implement some logic for merging service records with same name.
map[<ServiceName>]<Service> could be used to avoid iterating through slice of services every time.
m := map[string]*Service{}
for i := range machines {
s, found := m[machines[i].Servicename]
if !found {
s = &Service{ServiceName: machines[i].Servicename, Required: true}
m[machines[i].Servicename] = s
}
s.Hosts = append(s.Hosts, Host{HostName: machines[i].Hostname, Vars: Var{CommonVars: map[string]interface{}{"id": machines[i].ID}}})
}
for _, s := range m {
cService.Services = append(cService.Services, *s)
}

Filter Criteria in Lambda Function

I want to enable DynamoDB streams on my lambda using AWS CDK which I am able to do but I also want to enable the filter criteria on lambda
But I am getting this error:
Invalid filter pattern definition. (Service: AWSLambda; Status Code: 400; Error Code: InvalidParameterValueException
This is the event I am getting from DynamoDB streams:
{
"input": {
"Records": [
{
"eventID": "e92e0072a661a06df0e62e411f",
"eventName": "INSERT",
"eventVersion": "1.1",
"eventSource": "aws:dynamodb",
"awsRegion": "<region>",
"dynamodb": {
"ApproximateCreationDateTime": 1639500357,
"Keys": {
"service": {
"S": "service"
},
"key": {
"S": "key"
}
},
"NewImage": {
"service": {
"S": "service"
},
"channel": {
"S": "email"
},
"key": {
"S": "key"
}
},
"SequenceNumber": "711500000000015864417",
"SizeBytes": 168,
"StreamViewType": "NEW_IMAGE"
},
"eventSourceARN": "arn:aws:dynamodb:<region>:<account>:table/table-name/stream/2021-12-14T13:00:29.888"
}
]
},
"env": {
"lambdaContext": {
"callbackWaitsForEmptyEventLoop": true,
"functionVersion": "$LATEST",
"functionName": "functionName",
"memoryLimitInMB": "128",
"logGroupName": "/aws/lambda/functionName",
"logStreamName": "2021/12/14/[$LATEST]028531c7b489b8ec69bace700acc0",
"invokedFunctionArn": "arn:aws:lambda:<region>:<account>:function:functionName",
"awsRequestId": "c72e80252-4722-b9f0-a03b7f8b820e"
},
"region": "<region-name>"
}
}
The event source mapping code is:
const mapping = new lambda.CfnEventSourceMapping(this, 'event', {
functionName: "functionName,
batchSize: 1,
bisectBatchOnFunctionError: true,
startingPosition: lambda.StartingPosition.TRIM_HORIZON,
eventSourceArn: <stream-arn>,
filterCriteria: filter,
});
I want to get the eventName to be INSERT and the channel to be email here. What should be the value of the filter criteria? Its not working for me
<Edit> CDK filter helpers added in v2.42.0
The original workaround is no longer necessary. The CDK now has event-source filters for Lambda, Kinesis and SQS. Pass the filter to the L2 EventSourceMapping construct:
const source: EventSourceMapping = new lambda.EventSourceMapping(this, "EventSourceMapping",{
target: func,
eventSourceArn: table.tableStreamArn,
startingPosition: lambda.StartingPosition.TRIM_HORIZON,
filters: [
lambda.FilterCriteria.filter({
eventName: lambda.FilterRule.isEqual("INSERT"),
dynamodb: { NewImage: { channel: { S: lambda.FilterRule.isEqual("email") } },},
}),
],
}
);
</Edit>
Here's the DynamoDB streams filter Pattern syntax for new records with a channel of email:
`{ \"eventName\": [\"INSERT\"], \"dynamodb\": { \"NewImage\": {\"channel\": { \"S\" : [\"email\"]}} } }`
In other words, the Pattern is a stringified JSON filter rule with escaped quotes. The pattern is applied against each stream record.
Here is the full CDK syntax. The code starts with the usual L2 EventSourceMapping. It then uses escape hatch syntax to set FilterCriteria on the underlying L1 CfnEventSourceMapping:
// start with the L2 type - Note: the OP code starts with a L1 `CfnEventSourceMapping`
const source: EventSourceMapping = new lambda.EventSourceMapping(this, 'EventSourceMapping', {
target: func,
eventSourceArn: table.tableStreamArn,
startingPosition: lambda.StartingPosition.TRIM_HORIZON,
});
// escape hatch - get a L1 reference
const cfnSouce = source.node.defaultChild as lambda.CfnEventSourceMapping;
cfnSouce.addPropertyOverride('FilterCriteria', {
Filters: [
{
Pattern: `{ \"eventName\": [\"INSERT\"], \"dynamodb\": { \"NewImage\": {\"channel\": { \"S\" : [\"email\"]}} } }`,
},
],
});

Incrementing a value in nested attributes in AWS Lambda and DynamoDB

This is my query to add a new field or increment a nested attribute
const params = {
TableName: process.env.DYNAMODB_GAMES_TABLE,
Key: {
id: gameId
},
UpdateExpression: 'set players.#player.#score = players.#player.#score + :s',
ExpressionAttributeNames: {
'#player': playerId,
'#score': 'score'
},
ExpressionAttributeValues: {
':s': 1
},
ReturnValues: "ALL_NEW"
};
This is the error I get
{
"message": "The document path provided in the update expression is invalid for update",
"code": "ValidationException",
"time": "2020-05-21T03:03:14.328Z",
"requestId": "Q04QEP1G3E2LAM43I04ADLM4IRVV4KQNSO5AEMVJF66Q9ASUAAJG",
"statusCode": 400,
"retryable": false,
"retryDelay": 27.814212380235393
}
My object looks like
{
"id": "09e7a690",
"players": {
"M3EDJeHtoAMCLJg": [
{
"cardId": "1",
"cardTitle": "test",
"pun": "this is a pun"
}
]
}
}

How do I tell Alexa to prompt user for input

I need to tell alexa to prompt user for input then store that input in a variable to be used in my code.
InvocationName: send mail
Alexa: Tell me mail subject
User: Test email
Alexa: Okay, tell me message body.
User: This is just a sample test
Alexa, okay, tell me receiver email
User: test#gmail.com
Below is my intent schema:
{
"interactionModel": {
"languageModel": {
"invocationName": "send mail",
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AMAZON.FallbackIntent",
"samples": []
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
},
{
"name": "SendMailIntent",
"slots": [
{
"name": "ReceiverEmail",
"type": "AMAZON.SearchQuery"
}
],
"samples": [
"mail",
"send mail"
]
}
],
"types": []
},
"dialog": {
"intents": [
{
"name": "SendMailIntent",
"confirmationRequired": false,
"prompts": {},
"slots": [
{
"name": "ReceiverEmail",
"type": "AMAZON.SearchQuery",
"confirmationRequired": false,
"elicitationRequired": true,
"prompts": {
"elicitation": "Elicit.Slot.838288524310.965699312002"
}
}
]
}
],
"delegationStrategy": "ALWAYS"
},
"prompts": [
{
"id": "Elicit.Slot.838288524310.965699312002",
"variations": [
{
"type": "PlainText",
"value": "Enter subject"
}
]
}
]
}
}
and below is the code I have been able to come up with:
// sets up dependencies
const Alexa = require('ask-sdk-core');
const i18n = require('i18next');
const languageStrings = require('./languageStrings');
const SendMailHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
// var code = this.event.request.intent.slots.code.value;
//console.log(code)
// checks request type
return request.type === 'LaunchRequest'
|| (request.type === 'IntentRequest'
&& request.intent.name === 'SendMailIntent');
},
handle(handlerInput) {
const speechText = 'Ok. Tell me the mail subject'
const response = handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText) // <--- Here is our reprompt
.getResponse();
console.log(response)
return response;
},
};
// Omitted default Alexa handlers
const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
.addRequestHandlers(
SendMailHandler,
)
.lambda();
You should use dialog management with 2 slots.
As I can see currently you only collect one slot (ReceiverEmail) with dialoge management.
But you need also to create a slot for the text you want to send.
Later in your code you need to check if the dialogue is in status COMPLETED.
See the example https://github.com/alexa/skill-sample-nodejs-petmatch/ or this video: https://www.youtube.com/watch?v=u99WMljnQXI

Resources