Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Previews in PR Conversation #224

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ Unless you wish to contribute to this project, you don't need to fork this repos
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: twitter-together/action@v2
- name: checkout pull request
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Validate Tweets
uses: twitter-together/action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tweet:
Expand Down
7 changes: 6 additions & 1 deletion docs/02-create-twitter-together-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: twitter-together/action@v2
- name: checkout pull request
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Validate Tweets
uses: twitter-together/action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tweet:
Expand Down
4 changes: 4 additions & 0 deletions lib/common/parse-tweet-file-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { existsSync } = require("fs");
const { join } = require("path");
const { parseTweet } = require("twitter-text");
const { load } = require("js-yaml");
const parseTweetId = require("./parse-tweet-id");

const OPTION_REGEX = /^\(\s?\)\s+/;
const FRONT_MATTER_REGEX = new RegExp(
Expand Down Expand Up @@ -85,6 +86,9 @@ function parseTweetFileContent(text, dir, isThread = false) {
}

function validateOptions(options, text, dir) {
if (options.retweet || options.reply)
parseTweetId(options.retweet || options.reply);

if (options.retweet && !text && options.poll)
throw new Error("Cannot attach a poll to a retweet");

Expand Down
16 changes: 16 additions & 0 deletions lib/common/parse-tweet-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = parseTweetId;

const TWEET_REGEX = /^https:\/\/twitter\.com\/[^/]+\/status\/(\d+)$/;

// TODO allow differently formatted URLs and tweet ids ?
// https://github.com/twitter-together/action/issues/221

// TODO: Should we check if the referenced tweet actually exists?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's a great idea, can you file a follow up issue?


function parseTweetId(tweetRef) {
const match = tweetRef.match(TWEET_REGEX);
if (!match) {
throw new Error(`Invalid tweet reference: ${tweetRef}`);
}
return match;
}
11 changes: 4 additions & 7 deletions lib/common/tweet.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const { promises: fs } = require("fs");
const Twitter = require("twitter");
const mime = require("mime-types");

const TWEET_REGEX = /^https:\/\/twitter\.com\/[^/]+\/status\/(\d+)$/;
const parseTweetId = require("./parse-tweet-id");

async function tweet({ twitterCredentials }, tweetData, tweetFile) {
const client = new Twitter(twitterCredentials);
Expand All @@ -13,8 +13,7 @@ async function tweet({ twitterCredentials }, tweetData, tweetFile) {

async function handleTweet(client, tweet, name) {
if (tweet.retweet && !tweet.text) {
// TODO: Should this throw if an invalid tweet is passed and there is no match?
const match = tweet.retweet.match(TWEET_REGEX);
const match = parseTweetId(tweet.retweet);
if (match) return createRetweet(client, match[1]);
}

Expand All @@ -35,17 +34,15 @@ async function handleTweet(client, tweet, name) {
}

if (tweet.reply) {
// TODO: Should this throw if an invalid reply is passed and there is no match?
const match = tweet.reply.match(TWEET_REGEX);
const match = parseTweetId(tweet.reply);
if (match) {
tweetData.in_reply_to_status_id = match[1];
tweetData.auto_populate_reply_metadata = true;
}
}

if (tweet.retweet) {
// TODO: Should this throw if an invalid tweet is passed and there is no match?
const match = tweet.retweet.match(TWEET_REGEX);
const match = parseTweetId(tweet.retweet);
if (match) tweetData.attachment_url = match[0];
}

Expand Down
29 changes: 25 additions & 4 deletions lib/pull-request/create-check-run.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = createCheckRun;

const { autoLink } = require("twitter-text");
const path = require("path");

const parseTweetFileContent = require("../common/parse-tweet-file-content");

Expand Down Expand Up @@ -62,16 +63,36 @@ async function createCheckRun(
toolkit.info(`check run created: ${response.data.html_url}`);
}

function tweetToCheckRunSummary(tweet) {
let text = autoLink(tweet.text)
.replace(/(^|\n)/g, "$1> ")
.replace(/(^|\n)> (\n|$)/g, "$1>$2");
function tweetToCheckRunSummary(tweet, threading) {
let text = !tweet.text
? ""
: autoLink(tweet.text)
.replace(/(^|\n)/g, "$1> ")
.replace(/(^|\n)> (\n|$)/g, "$1>$2");

if (!tweet.valid)
return `### ❌ Invalid\n\n${text}\n\n${tweet.error || "Unknown error"}`;

if (tweet.poll)
text +=
"\n\nThe tweet includes a poll:\n\n> 🔘 " + tweet.poll.join("\n> 🔘 ");

if (tweet.reply) text = `Replying to ${tweet.reply}\n\n${text}`;

if (tweet.retweet) text = `Retweeting ${tweet.retweet}\n\n${text}`.trim();

if (tweet.media.length) {
const media = tweet.media
.map(({ file, alt }) => `- ${path.basename(file)} [${alt}]`)
.join("\n");
text = `Uploading media:\n\n${media}\n\n${text}`.trim();
}

if (tweet.thread || threading) {
let cells = `\n<tr><td>\n\n${text}\n\n</td></tr>`;
if (tweet.thread) cells += `${tweetToCheckRunSummary(tweet.thread, true)}`;
return threading ? cells : `### ✅ Valid\n\n<table>${cells}\n</table>`;
}

return `### ✅ Valid\n\n${text}`;
}
22 changes: 22 additions & 0 deletions test/pull-request-has-tweet-with-front-matter-media/event.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"action": "opened",
"pull_request": {
"number": 123,
"base": {
"ref": "main"
},
"head": {
"sha": "0000000000000000000000000000000000000002",
"repo": {
"fork": false
}
}
},
"repository": {
"default_branch": "main",
"name": "action",
"owner": {
"login": "twitter-together"
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
102 changes: 102 additions & 0 deletions test/pull-request-has-tweet-with-front-matter-media/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* This test checks the happy path of pull request adding a new *.tweet file
*/

const nock = require("nock");
const tap = require("tap");
const path = require("path");

// SETUP
process.env.GITHUB_EVENT_NAME = "pull_request";
process.env.GITHUB_TOKEN = "secret123";
process.env.GITHUB_EVENT_PATH = require.resolve("./event.json");

// set other env variables so action-toolkit is happy
process.env.GITHUB_REF = "";
process.env.GITHUB_WORKSPACE = path.dirname(process.env.GITHUB_EVENT_PATH);
process.env.GITHUB_WORKFLOW = "";
process.env.GITHUB_ACTION = "twitter-together";
process.env.GITHUB_ACTOR = "";
process.env.GITHUB_REPOSITORY = "";
process.env.GITHUB_SHA = "";

// MOCK
nock("https://api.github.com", {
reqheaders: {
authorization: "token secret123",
},
})
// get changed files
.get("/repos/twitter-together/action/pulls/123/files")
.reply(200, [
{
status: "added",
filename: "tweets/hello-world.tweet",
},
]);

// get pull request diff
nock("https://api.github.com", {
reqheaders: {
accept: "application/vnd.github.diff",
authorization: "token secret123",
},
})
.get("/repos/twitter-together/action/pulls/123")
.reply(
200,
`diff --git a/media/cat.jpg b/media/cat.jpg
new file mode 100644
index 0000000..a10d505
Binary files /dev/null and b/media/cat.jpg differ
diff --git a/media/dog.jpg b/media/dog.jpg
new file mode 100644
index 0000000..7a25bfb
Binary files /dev/null and b/media/dog.jpg differ
diff --git a/tweets/media.tweet b/tweets/media.tweet
new file mode 100644
index 0000000..1715c04
--- /dev/null
+++ b/tweets/media.tweet
@@ -0,0 +1,9 @@
+---
+media:
+ - file: cat.jpg
+ alt: A cat
+ - file: dog.jpg
+ alt: A dog
+---
+
+Here are some cute animals!`
);

// create check run
nock("https://api.github.com")
// get changed files
.post("/repos/twitter-together/action/check-runs", (body) => {
tap.equal(body.name, "preview");
tap.equal(body.head_sha, "0000000000000000000000000000000000000002");
tap.equal(body.status, "completed");
tap.equal(body.conclusion, "success");
tap.same(body.output, {
title: "1 tweet(s)",
summary: `### ✅ Valid

Uploading media:

- cat.jpg [A cat]
- dog.jpg [A dog]

> Here are some cute animals!`,
});

return true;
})
.reply(201);

process.on("exit", (code) => {
tap.equal(code, 0);
tap.same(nock.pendingMocks(), []);
});

require("../../lib");
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"action": "opened",
"pull_request": {
"number": 123,
"base": {
"ref": "main"
},
"head": {
"sha": "0000000000000000000000000000000000000002",
"repo": {
"fork": false
}
}
},
"repository": {
"default_branch": "main",
"name": "action",
"owner": {
"login": "twitter-together"
}
}
}
Loading