I'm trying to create a slack app that uses incoming webhooks. I want my github repository to post to slack whenever the wiki is updated. I believe I've set up the webhook on github just fine, because I can see that it is attempting a delivery whenever I update the wiki. However, there's always the error, "no_text". I think this error means slack is expecting an item named "text," but the payload from github provides none. I verified this by trying two curl commands from the command prompt (I'm on windows):
curl -X POST -H "Content-type: application/json" --data "{\"text\":\"Hello, World!\"}" [MY_WEBHOOK_URL]
curl -X POST -H "Content-type: application/json" --data "{\"foobar\":\"Hello, World!\"}" [MY_WEBHOOK_URL]
This first one works as expected; the message "Hello, World!" gets posted to the slack channel I wanted, and I got back the "ok" message from curl. The second one did not work; the message was not posted, and I got back the message "no_text" from curl.
I can think of two possible solutions to this problem:
Change the format of the payload coming from github to include an item called "text" and other properties slack actually recognizes.
Get slack to recognize the format the payload is already in, perhaps by telling it to post the contents of a property other than "text."
I don't know how to accomplish either of these, or if they're even possible. Or perhaps there's another solution I haven't thought of?
Note: I already tried to use the github slack app, but couldn't figure out how to get it to post updates to the wiki. (See my other question if you'd like: slack github integration doesn't find wiki repository)
I'm actually looking to do the same thing as you right now. Because the github and slack hooks are fundamentally different, you will need to have something in the middle to process the github webhooks into a Slack message to be posted via an incoming webhook.
You're going to need to do a couple different things (in no particular order):
Set up Github to send out hooks for the specific events you wish to be notified of.
Configure a middle man (I am currently using AWS SNS and Lambda)
Set up slack for the webhook.
For the github webhooks, you will need to leverage the more powerful github API to create the hook. You could do this with curl, but that's kind of a pain so I am using a JS script to take care of it. You will need to npm install github bluebird in the same directory before running something like this:
var GitHubApi = require("github");
var github = new GitHubApi({
// optional
debug: true,
protocol: "https",
host: "api.github.com", // should be api.github.com for GitHub
pathPrefix: "", // for some GHEs; none for GitHub
headers: {
"user-agent": "ocelotsloth-conf" // GitHub is happy with a unique user agent
},
Promise: require('bluebird'),
followRedirects: false, // default: true; there's currently an issue with non-get redirects, so allow ability to disable follow-redirects
timeout: 5000
});
// user token
github.authenticate({
type: "token",
token: "GITHUB_TOKEN_HERE",
});
// https://mikedeboer.github.io/node-github/#api-repos-createHook
github.repos.createHook({
owner: "ocelotsloth",
repo: "lib-ical",
name: "amazonsns",
events: [
//"commit_comment",
//"create",
//"delete",
//"gollum",
//"issue_comment",
"issues"
//"label",
//"milestone",
//"pull_request",
//"pull_request_review",
//"pull_request_review_comment",
//"push",
//"release"
],
config: {
aws_key: "AWS_KEY",
aws_secret: "AWS_SECRET",
sns_region: "us-east-1",
sns_topic: "SNS_TOPIC_ARN"
},
}, function(err, res) {
console.log(JSON.stringify(res, null, '\t'));
});
I remember following a blog post a while ago about setting up the SNS topic to work properly, but I don't remember exactly where it is anymore. Some googling should help. Also, you should be able to set up your own server for github to send these to and avoid having to set up AWS at all if you want to avoid the complexity. See https://mikedeboer.github.io/node-github/#api-repos-createHook for specific instructions on that method. You will need to use editHook after you create the hook, so either get it right the first time or use edit it. You just need to change the method call to editHook and add the id to the call as well.
Something important to see, you can define all of the different Events that you want github to send to you. For all of these, along with their formats, look at https://developer.github.com/v3/activity/events/types/.
To actually post these events to slack, I have a lambda script that currently looks like this (I literally just started writing this today, and haven't implemented more than just posting issue events, but it should do well as a starting point). For this script, you will need to npm install identify-github-event slack-webhook and have your incoming webhook set up as well.
var identifyGithubEvent = require('identify-github-event');
var SlackWebhook = require('slack-webhook')
// slack's link syntax
function link(url, txt) {
return "<" + url + "|" + txt + ">";
}
exports.handler = function(event, context) {
// 1. extract GitHub event from SNS message
var ghEvent = JSON.parse(event.Records[0].Sns.Message);
var eventType, eventName, numb;
console.log(ghEvent);
var ghEventType = identifyGithubEvent(ghEvent);
if (!ghEventType) {
return;
}
var text = "Event! " + ghEventType;
if (ghEventType === 'IssueCommentEvent') {
var who = link(ghEvent.comment.user.html_url, ghEvent.comment.user.login);
var what = link(ghEvent.issue.html_url, "Issue " + ghEvent.issue.number + ": \"" + ghEvent.issue.title + "\"");
text = who + " commented on " + what;
}
else if (ghEventType === 'IssuesEvent') {
var who = link(ghEvent.sender.html_url, ghEvent.sender.login);
var action = ghEvent.action;
var issueNumber = ghEvent.issue.number;
var issueName = link(ghEvent.issue.html_url, ghEvent.issue.title + "\"");
if (action === "opened" | action === "closed") {
text = {
attachments: [{
"fallback": who + " opened Issue" + issueNumber + ": " + issueName,
"color": "#36a64f",
"pretext": "New issue " + action + ":",
"author_name": ghEvent.sender.login,
"author_link": ghEvent.sender.html_url,
"thumb_url": ghEvent.sender.avatar_url,
"title": "#" + issueNumber + ": " + ghEvent.issue.title,
"title_link": ghEvent.issue.html_url,
"text": ghEvent.issue.body,
"fields": [
{
"title": "Status",
"value": ghEvent.issue.state,
"short": true
},
{
"title": "Labels",
"value": ghEvent.issue.labels.map(label => label.name).join("\n"),
"short": true
}
],
"footer": "lib-ical",
"footer_icon": "https://platform.slack-edge.com/img/default_application_icon.png",
"mrkdwn_in": ["text"]
}]
};
} else return;
}
// 'commit_comment':
// 'create':
// 'delete':
// 'issues':
// 'label':
// 'member':
// 'milestone':
// 'pull_request':
// 'pull_request_review':
// 'pull_request_review_comment':
// 'push':
// 'release':
var slack = new SlackWebhook('https://hooks.slack.com/services/SLACK-WEBHOOK-URL', {
defaults: {
username: 'GitHub -- user/project',
channel: '#CHANNEL-NAME',
icon_emoji: ':github:'
}
})
slack.send(text);
};
It's far from perfect, but it gives a really nice result:
For that specific example it's an issue close, but currently that script will also work on open. The script also does limited markdown processing, so if the issue contains any source blocks, it will be rendered properly inside of slack.
I hope this helps you with your approach, feel free to ask me to elaborate on anything else.
Related
I have logged this issue on GitHub but I understand it will take time to get attention. Is there another way of updating Product Variations?
https://github.com/woocommerce/woocommerce/issues/35555
When I PUT a stock_quantity or price update for a product variation nothing changes. This however works 100% on a product but not a variation. The below will have no effect even though I receive an OK status 200.
PUT: wp-json/wc/v3/products/6360/variations/6361
{
"stock_quantity": 7
}
I also tried using the batch endpoint but also nothing gets updated.
/wp-json/wc/v3/products/6360/variations/batch
"update": [
{
"id":6361,
"stock_quantity": 4
}
]
This is not a bug, I was using Postman and the 200 OK returned confused the issue.
Once I added the required Content-Type:application/json header, the record successfully updated.
I also made use of a deprecated NodeJS library woocommerce-api and later tried with the replacement woocommerce-rest-api but both does not seem to handle this correctly.
I can suggest to rather just axios directly to the woocommerce rest api:
const baseUrl = `${process.env.WOOCOMMERCE_URI}/wp-json/wc/v3/`;
const instance = {
headers: {'Content-Type': 'application/json'},
auth: {
username: process.env.WOOCOMMERCE_KEY,
password: process.env.WOOCOMMERCE_SECRET
}
};
let putUrl = `products/${woocommerceImport.onlineProductId}/variations/${woocommerceImport.onlineVariantId}`;
await axios.put(`${baseUrl}${putUrl}`, {
stock_quantity: stock
}, instance);
I have to activate the properties to shuffle the question of a multiple choice type questoin, but I can't find the properties. I found this code that randomizes questions, but not the answers.
form.setShuffleQuestions(true);
Image of the visual component
Issuetracker:
This isn't currently possible. Consider adding a star (on top left) to the following feature requests for Google to prioritize the issue:
https://issuetracker.google.com/issues/36764938
https://issuetracker.google.com/issues/64134484
Partial worksround:
Partial Workaround as already mentioned in this answer is to shuffle the array creating options and setback the array using setChoiceValues(). The drawback of such server side randomizing is
It can only be done whenever the server script runs and not when client opens the form
Even if you randomize each minute, it is possible that users opening the form simultaneously will see the same order of options
Sample script:
const form = FormApp.openById('/*form id*/');
const item = form.addMultipleChoiceItem();
item.setTitle('Car or truck?');
const options = ['Truck', 'Car'];
//Durstenfeld algo
for (let i = options.length - 1; i > 0; i--) {
let rand = Math.floor(Math.random() * i);
[options[i], options[rand]] = [options[rand], options[i]];
}
item.setChoiceValues(options);
I believe that you have to randomize the options yourself with something like this:
function randomizeArray(A) {
var iA=A.slice();
var oA=[];
for(var i=0;i<A.length;i++) {
var index=Math.floor(Math.random()*iA.length);
oA.push(iA[index]);
iA.splice(index,1);
}
return oA;
}
Unfortunately, in the current sage, it seems that this cannot still be achieved by Google Forms service (FormApp).
But, On March 16, 2022, Google Forms API has been officially released. Ref By this, fortunately, I confirmed that your this question can be directly achieved using Google Forms API.
In the current stage, Google Forms API cannot be used with Advanced Google services. So it is required to do the following flow.
Usage:
1. Linking Google Cloud Platform Project to Google Apps Script Project for New IDE.
In order to use Forms API, please link Google Cloud Platform Project to Google Apps Script Project for New IDE, and please enable Forms API at the API console. Ref
When Google Forms API can be used with Advanced Google services, this process can be skipped.
2. Scope.
In order to use this sample script, please use the following scopes. Ref
https://www.googleapis.com/auth/script.external_request
https://www.googleapis.com/auth/forms
https://www.googleapis.com/auth/forms.body
When Google Forms API can be used with Advanced Google services, this process can be skipped.
3. Sample script:
Please copy and paste the following script to the script editor linked to Google Cloud Platform Project and save the script.
function myFunction() {
const formTitle = "sample"; // This is a form title.
// THis is an object for creating quizzes.
const obj = [
{ "question": "sample question 1", "answers": ["answer1", "answer2", "answer3"], "correct": ["answer1"], "point": 1, "type": "RADIO" },
];
// Create new Google Form and set as quize.
const form = FormApp.create(formTitle);
form.setIsQuiz(true).setTitle("Sample");
// form.setShuffleQuestions(true); // If you want to shuffle questions, you can use this line.
// Create request body for Google Forms API.
const url = `https://forms.googleapis.com/v1/forms/${form.getId()}:batchUpdate`;
const requests = obj.map(({ question, answers, correct, point, type }, index) => ({
createItem: {
item: {
title: question,
questionItem: { question: { choiceQuestion: { options: answers.map(value => ({ value })), shuffle: true, type }, grading: { correctAnswers: { answers: correct.map(e => ({ value: e })) }, pointValue: point } } },
}, location: { index }
}
}));
// Request to Google Forms API.
const params = {
method: "post",
contentType: "application/json",
headers: { authorization: "Bearer " + ScriptApp.getOAuthToken() },
payload: JSON.stringify({ requests }),
muteHttpExceptions: true
};
const res = UrlFetchApp.fetch(url, params);
console.log(res.getContentText())
}
When this script is run, as a sample, a new Google Form is created, and a sample question including the radio button is set. And, you can confirm that "Shuffle option order" is checked.
References:
Linking Google Cloud Platform Project to Google Apps Script Project for New IDE
Method: forms.batchUpdate
So I'm writing a chatbot application which requires the intake of parameters and then uses these parameters in post requests sent via a payload.
I'm having problems with grabbing the context value from a context variable within swift and was wondering how I would go about grabbing the value of the context variable and executing an action based on the value of that said context.
An example of this would be the following dialog flow...
Me: Trigger this
Bot: Ok, give me param x
Me: x
Bot: Ok I have x param, will post job now
This is the kind of flow I want to happen in the background of my application under the hood but I'm not sure how to grab value x after my user has input it.
So, suppose that you are using the iOS SDK from Watson Developer Cloud.
In your Conversation, add in your node:
{
"context": {
"myVariable": "<? input.text ?>"
},
"output": {
"text": {
"values": [
"My context variable value is $myVariable."
],
"selection_policy": "sequential"
}, { "etc": "etc" }
Obs.: The input.text will capture all that user types, you need to use regex for extract exactly what you want, try to see my examples in this answer.
And, in the iOS SDK you can see this follow example:
func testMessage() {
let description1 = "Start a conversation."
let expectation1 = self.expectation(description: description1)
let response1 = ["Hi. It looks like a nice drive today. What would you like me to do?"]
let nodes1 = ["node_1_1467221909631"]
var context: Context?
conversation.message(workspaceID: workspaceID, failure: failWithError) {
response in
// verify input
XCTAssertNil(response.input?.text)
// verify context
XCTAssertNotNil(response.context.conversationID)
XCTAssertNotEqual(response.context.conversationID, "")
XCTAssertNotNil(response.context.system)
XCTAssertNotNil(response.context.system.additionalProperties)
XCTAssertFalse(response.context.system.additionalProperties.isEmpty)
// verify entities
XCTAssertTrue(response.entities.isEmpty)
// verify intents
XCTAssertTrue(response.intents.isEmpty)
// verify output
XCTAssertTrue(response.output.logMessages.isEmpty)
XCTAssertEqual(response.output.text, response1)
XCTAssertEqual(response.output.nodesVisited!, nodes1)
context = response.context
expectation1.fulfill()
}
So, you can access your context variable using:
context.myVariable
response.context.myVariable
See more about methods in Watson Conversation here.
iOS SDK from Watson Developer Cloud.
I'm quite the noob using Ionic or Angular for that matter. So as a cheat sheet I'm using the ionic-super-starter template (link below).
I am trying to make a get request to my API and it works just find if I'm doing it like this:
this.api.get('user/'+this.user.userId+'/entries?include=stuff&access_token=TOKEN');
but when I put the url params into an object it stops working:
let options = {
'include':'stuff',
'access_token':'TOKEN'
}
this.api.get('user/'+this.user.userId+'/entries', options);
The only error I get is "Unauthorized Request" since the options object including the access token was not appended to the url.
In the ionic-super-starter template the providers/api/api.ts calls .set() for each key in my params object:
if (params) {
reqOpts.params = new HttpParams();
for (let k in params) {
reqOpts.params.set(k, params[k]);
}
}
but according to Angular University this is not possible since "HTTPParams is immutable".
If it really was wrong to do this, I don't believe it would be in the ionic template. Nor would I believe that I would be the first person to come across this issue.
However, I am stuck here so any help would be appreciated.
Link to Angular University:
https://blog.angular-university.io/angular-http/#httprequestparameters
Link to ionic-super-starter:
https://github.com/ionic-team/starters/tree/master/ionic-angular/official/super
I think I figured it out myself:
if I write (in my src/providers/api/api.ts)
reqOpts.params = reqOpts.params.append(k, params[k]);
instead of
reqOpts.params.set(k, params[k]);
it works.
if you are using a loopback API as I am you might have nested objects like:
let options = {
"filter": {
"order": "date DESC"
},
"access_token":this.user._accessToken
};
this won’t work. try instead:
let options = {
"filter": '{"order":"date DESC"}',
"access_token":this.user._accessToken
};
I have the following Restivus configuration:
if(Meteor.isServer){
Restivus.configure({
});
//Allow Restivus to manage Reports
Restivus.addCollection('reports');
Restivus.addRoute('newReport/:message', {}, {
// POST
post: {
action: function(){
var response = null;
var message = this.urlParams.message;
if(message){
console.log("Message received: " + message);
return {status: "success", data: message};
} else {
console.log("Message empty...");
return {status: "fail", message: "Post not found"};
}
//Response to caller
return;
}
}
})
}
Following the explanation of Restivus, when I make a GET call to http://localhost:3000/api/newReport/ I should get a "Get All" result from the server, on the caller.
However, if I use curl -X GET http://localhost:3000/api/newReport/ on the command line, I seem to be getting the HTML code of the site at api/NewReport/ (which is empty, except for the header and empty body)
Knowing that, I know my error is in the Restivus Route configuration, but I cannot pinpoint the reason.
The expected behavior is that when I make a POST from a Ruby script, I should get a returned message (Ok or Fail), and in my Meteor console, I should see either "Message received" or "Post not found" (both placeholders).
Additional question, is there a way to disable the default GET method Restivus creates when we add a collection?
You have to create a variable in the JavaScript part and use that in the Restivus.addCollection() call.
Reports = Mongo.Collection('reports')
if(Meteor.isServer){
Restivus.configure({
});
//Allow Restivus to manage Reports
Restivus.addCollection(Reports);
...