├── .gitignore
├── .rubocop.yml
├── README.md
├── app
├── jobs
│ └── regular
│ │ └── create_github_linkback.rb
└── lib
│ └── github_linkback.rb
├── config
├── locales
│ ├── server.en.yml
│ └── server.it.yml
└── settings.yml
├── plugin.rb
└── spec
└── lib
└── github_linkback_spec.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | .rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml
2 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | inherit_from: https://raw.githubusercontent.com/discourse/discourse/master/.rubocop.yml
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ⚠️ This plugin is deprecated. Please use [discourse-github](https://github.com/discourse/discourse-github) instead.
2 |
3 | # Discourse Github Linkback
4 |
5 | This plugin will create a link from a Github pull request or commit
6 | back to a Discourse post where it is mentioned.
7 |
8 | ## Installation
9 |
10 | Follow the [plugin installation guide](https://meta.discourse.org/t/install-a-plugin/19157?u=eviltrout).
11 |
12 | ## After Installation
13 |
14 | 1. You need to enable the plugin on Settings -> Plugins.
15 |
16 | 2. Generate an [access token](https://github.com/settings/tokens) on Github.
17 | Be sure to give it only the `public_repo` scope. Paste that token into the
18 | `github linkback access token` setting.
19 |
20 | 3. Finally, add the projects you wish to post to in the `github linkback projects` site setting in the formats:
21 | - `username/repository` for specific repositories
22 | - `username/*` for all repositories of a certain user
23 |
--------------------------------------------------------------------------------
/app/jobs/regular/create_github_linkback.rb:
--------------------------------------------------------------------------------
1 | module Jobs
2 | class CreateGithubLinkback < Jobs::Base
3 | def execute(args)
4 | return unless SiteSetting.github_linkback_enabled?
5 | GithubLinkback.new(Post.find(args[:post_id])).create
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/app/lib/github_linkback.rb:
--------------------------------------------------------------------------------
1 | require_dependency 'pretty_text'
2 | require 'digest/sha1'
3 |
4 | class GithubLinkback
5 |
6 | class Link
7 | attr_reader :url, :project, :type
8 | attr_accessor :sha, :pr_number
9 |
10 | def initialize(url, project, type)
11 | @url = url
12 | @project = project
13 | @type = type
14 | end
15 | end
16 |
17 | def initialize(post)
18 | @post = post
19 | end
20 |
21 | def should_enqueue?
22 | !!(SiteSetting.github_linkback_enabled? &&
23 | @post.present? &&
24 | @post.raw =~ /github/ &&
25 | Guardian.new.can_see?(@post) &&
26 | @post.topic.visible?)
27 | end
28 |
29 | def enqueue
30 | Jobs.enqueue(:create_github_linkback, post_id: @post.id) if should_enqueue?
31 | end
32 |
33 | def github_links
34 | projects = SiteSetting.github_linkback_projects.split('|')
35 |
36 | return [] if projects.blank?
37 |
38 | result = {}
39 | PrettyText.extract_links(@post.cooked).map(&:url).each do |l|
40 | next if @post.custom_fields[GithubLinkback.field_for(l)].present?
41 |
42 | if l =~ /https?:\/\/github\.com\/([^\/]+)\/([^\/]+)\/commit\/([0-9a-f]+)/
43 | project = "#{Regexp.last_match[1]}/#{Regexp.last_match[2]}"
44 | if is_allowed_project_link?(projects, project)
45 | link = Link.new(Regexp.last_match[0], project, :commit)
46 | link.sha = Regexp.last_match[3]
47 | result[link.url] = link
48 | end
49 | elsif l =~ /https?:\/\/github.com\/([^\/]+)\/([^\/]+)\/pull\/(\d+)/
50 | project = "#{Regexp.last_match[1]}/#{Regexp.last_match[2]}"
51 | if is_allowed_project_link?(projects, project)
52 | link = Link.new(Regexp.last_match[0], project, :pr)
53 | link.pr_number = Regexp.last_match[3].to_i
54 | result[link.url] = link
55 | end
56 | end
57 | end
58 | result.values
59 | end
60 |
61 | def is_allowed_project_link?(projects, project)
62 | return true if projects.include?(project)
63 |
64 | check_user = project.split("/")[0]
65 | projects.any? do |allowed_project|
66 | allowed_user, allowed_all_projects = allowed_project.split("/")
67 | (allowed_user == check_user) && (allowed_all_projects == "*")
68 | end
69 | end
70 |
71 | def create
72 | return [] unless SiteSetting.github_linkback_access_token.present?
73 |
74 | links = github_links
75 | links.each do |link|
76 | next unless [:commit, :pr].include?(link.type)
77 |
78 | if link.type == :commit
79 | post_commit(link)
80 | elsif link.type == :pr
81 | post_pr(link)
82 | end
83 |
84 | # Don't post the same link twice
85 | @post.custom_fields[GithubLinkback.field_for(link.url)] = 'true'
86 | @post.save_custom_fields
87 | end
88 |
89 | links
90 | end
91 |
92 | def self.field_for(url)
93 | "github-linkback:#{Digest::SHA1.hexdigest(url)[0..15]}"
94 | end
95 |
96 | private
97 |
98 | def post_pr(link)
99 | github_url = "https://api.github.com/repos/#{link.project}/issues/#{link.pr_number}/comments"
100 | comment = I18n.t(
101 | 'github_linkback.pr_template',
102 | title: SiteSetting.title,
103 | post_url: "#{Discourse.base_url}#{@post.url}"
104 | )
105 |
106 | Excon.post(
107 | github_url,
108 | body: { body: comment }.to_json,
109 | headers: headers
110 | )
111 | end
112 |
113 | def post_commit(link)
114 | github_url = "https://api.github.com/repos/#{link.project}/commits/#{link.sha}/comments"
115 |
116 | comment = I18n.t(
117 | 'github_linkback.commit_template',
118 | title: SiteSetting.title,
119 | post_url: "#{Discourse.base_url}#{@post.url}"
120 | )
121 |
122 | Excon.post(
123 | github_url,
124 | body: { body: comment }.to_json,
125 | headers: headers
126 | )
127 | end
128 |
129 | def headers
130 | {
131 | "Content-Type" => "application/json",
132 | "Authorization" => "token #{SiteSetting.github_linkback_access_token}",
133 | "User-Agent" => "Discourse-Github-Linkback"
134 | }
135 | end
136 |
137 | end
138 |
--------------------------------------------------------------------------------
/config/locales/server.en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | site_settings:
3 | github_linkback_enabled: "Link github issues back to forum discussions"
4 | github_linkback_projects: "List of projects to link back from"
5 | github_linkback_access_token: "A valid access token for the user who will post the linkback"
6 | github_linkback:
7 | not_supported: 'The discourse-github-linkback plugin is no longer supported. Please migrate to the discourse-github plugin, which supports the same functionality.'
8 | commit_template: |
9 | This commit has been mentioned on **%{title}**. There might be relevant details there:
10 |
11 | %{post_url}
12 | pr_template: |
13 | This pull request has been mentioned on **%{title}**. There might be relevant details there:
14 |
15 | %{post_url}
16 |
--------------------------------------------------------------------------------
/config/locales/server.it.yml:
--------------------------------------------------------------------------------
1 | it:
2 | site_settings:
3 | github_linkback_enabled: "Collega gli issue di github alle relative discussioni sul forum"
4 | github_linkback_projects: "Elenco dei progetti da collegare al forum"
5 | github_linkback_access_token: "Un token di accesso valido per l'utente che invierà il collegamento"
6 | github_linkback:
7 | commit_template: |
8 | This commit has been mentioned on **%{title}**. There might be relevant details there:
9 |
10 | %{post_url}
11 | pr_template: |
12 | This pull request has been mentioned on **%{title}**. There might be relevant details there:
13 |
14 | %{post_url}
15 |
--------------------------------------------------------------------------------
/config/settings.yml:
--------------------------------------------------------------------------------
1 | plugins:
2 | github_linkback_enabled:
3 | default: false
4 | github_linkback_projects:
5 | default: ""
6 | type: list
7 | github_linkback_access_token:
8 | default: ''
9 |
--------------------------------------------------------------------------------
/plugin.rb:
--------------------------------------------------------------------------------
1 | # name: discourse-github-linkback
2 | # about: Links Github content back to a Discourse discussion
3 | # version: 0.1
4 | # authors: Robin Ward
5 | # url: https://github.com/discourse/discourse-github-linkback
6 |
7 | enabled_site_setting :github_linkback_enabled
8 |
9 | after_initialize do
10 | AdminDashboardData.add_problem_check do
11 | I18n.t("github_linkback.not_supported")
12 | end
13 |
14 | require_dependency File.expand_path('../app/lib/github_linkback.rb', __FILE__)
15 | require_dependency File.expand_path('../app/jobs/regular/create_github_linkback.rb', __FILE__)
16 |
17 | DiscourseEvent.on(:post_created) do |post|
18 | GithubLinkback.new(post).enqueue
19 | end
20 |
21 | DiscourseEvent.on(:post_edited) do |post|
22 | GithubLinkback.new(post).enqueue
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/lib/github_linkback_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe GithubLinkback do
4 | let(:github_commit_link) { "https://github.com/discourse/discourse/commit/76981605fa10975e2e7af457e2f6a31909e0c811" }
5 | let(:github_pr_link) { "https://github.com/discourse/discourse/pull/701" }
6 | let(:github_pr_link_wildcard) { "https://github.com/discourse/discourse-github-linkback/pull/3" }
7 |
8 | let(:post) do
9 | Fabricate(
10 | :post,
11 | raw: <<~RAW
12 | cool post
13 |
14 | #{github_commit_link}
15 |
16 | https://eviltrout.com/not-a-gh-link
17 |
18 | #{github_commit_link}
19 |
20 | https://github.com/eviltrout/tis-100/commit/e22b23f354e3a1c31bc7ad37a6a309fd6daf18f4
21 |
22 | #{github_pr_link}
23 |
24 | i have no idea what i'm linking back to
25 |
26 | #{github_pr_link_wildcard}
27 |
28 | end_of_transmission
29 |
30 | RAW
31 | )
32 | end
33 |
34 | context "#should_enqueue?" do
35 | let(:post_without_link) { Fabricate.build(:post) }
36 |
37 | let(:post_with_link) do
38 | Fabricate.build(:post, raw: 'https://github.com/discourse/discourse/commit/5be9bee2307dd517c26e6ef269471aceba5d5acf')
39 | end
40 |
41 | it "returns false when the feature is disabled" do
42 | SiteSetting.github_linkback_enabled = false
43 | expect(GithubLinkback.new(post_with_link).should_enqueue?).to eq(false)
44 | end
45 |
46 | it "returns false without a post" do
47 | SiteSetting.github_linkback_enabled = true
48 | expect(GithubLinkback.new(nil).should_enqueue?).to eq(false)
49 | end
50 |
51 | it "returns false when the post doesn't have the word github in it" do
52 | SiteSetting.github_linkback_enabled = true
53 | expect(GithubLinkback.new(post_without_link).should_enqueue?).to eq(false)
54 | end
55 |
56 | it "returns true when the feature is enabled" do
57 | SiteSetting.github_linkback_enabled = true
58 | expect(GithubLinkback.new(post_with_link).should_enqueue?).to eq(true)
59 | end
60 |
61 | context "private_message" do
62 | it "doesn't enqueue private messages" do
63 | SiteSetting.github_linkback_enabled = true
64 | private_topic = Fabricate(:private_message_topic)
65 | private_post = Fabricate(
66 | :post,
67 | topic: private_topic,
68 | raw: "this post http://github.com should not enqueue"
69 | )
70 | expect(GithubLinkback.new(private_post).should_enqueue?).to eq(false)
71 | end
72 | end
73 |
74 | context "unlisted topics" do
75 | it "doesn't enqueue unlisted topics" do
76 | SiteSetting.github_linkback_enabled = true
77 | unlisted_topic = Fabricate(:topic, visible: false)
78 | unlisted_post = Fabricate(
79 | :post,
80 | topic: unlisted_topic,
81 | raw: "this post http://github.com should not enqueue"
82 | )
83 | expect(GithubLinkback.new(unlisted_post).should_enqueue?).to eq(false)
84 | end
85 | end
86 | end
87 |
88 | context "#github_urls" do
89 | it "returns an empty array with no projects" do
90 | SiteSetting.github_linkback_projects = ""
91 | links = GithubLinkback.new(post).github_links
92 | expect(links).to eq([])
93 | end
94 |
95 | it "doesn't return links that have already been posted" do
96 | SiteSetting.github_linkback_projects = "discourse/discourse|eviltrout/ember-performance|discourse/*"
97 |
98 | post.custom_fields[GithubLinkback.field_for(github_commit_link)] = "true"
99 | post.custom_fields[GithubLinkback.field_for(github_pr_link)] = "true"
100 | post.custom_fields[GithubLinkback.field_for(github_pr_link_wildcard)] = "true"
101 | post.save_custom_fields
102 |
103 | links = GithubLinkback.new(post).github_links
104 | expect(links.size).to eq(0)
105 | end
106 |
107 | it "should return the urls for the selected projects" do
108 | SiteSetting.github_linkback_projects = "discourse/discourse|eviltrout/ember-performance|discourse/*"
109 | links = GithubLinkback.new(post).github_links
110 | expect(links.size).to eq(3)
111 |
112 | expect(links[0].url).to eq(github_commit_link)
113 | expect(links[0].project).to eq("discourse/discourse")
114 | expect(links[0].sha).to eq("76981605fa10975e2e7af457e2f6a31909e0c811")
115 | expect(links[0].type).to eq(:commit)
116 |
117 | expect(links[1].url).to eq(github_pr_link)
118 | expect(links[1].project).to eq("discourse/discourse")
119 | expect(links[1].pr_number).to eq(701)
120 | expect(links[1].type).to eq(:pr)
121 |
122 | expect(links[2].url).to eq(github_pr_link_wildcard)
123 | expect(links[2].project).to eq("discourse/discourse-github-linkback")
124 | expect(links[2].pr_number).to eq(3)
125 | expect(links[2].type).to eq(:pr)
126 | end
127 | end
128 |
129 | context "#create" do
130 | before do
131 | SiteSetting.github_linkback_projects = "discourse/discourse|discourse/*"
132 | end
133 |
134 | it "returns an empty array without an access token" do
135 | expect(GithubLinkback.new(post).create).to be_blank
136 | end
137 |
138 | context "with an access token" do
139 | let(:headers) {
140 | { 'Authorization' => 'token abcdef',
141 | 'Content-Type' => 'application/json',
142 | 'Host' => 'api.github.com',
143 | 'User-Agent' => 'Discourse-Github-Linkback' }
144 | }
145 |
146 | before do
147 | SiteSetting.github_linkback_access_token = "abcdef"
148 |
149 | stub_request(:post, "https://api.github.com/repos/discourse/discourse/commits/76981605fa10975e2e7af457e2f6a31909e0c811/comments").
150 | with(headers: headers).
151 | to_return(status: 200, body: "", headers: {})
152 |
153 | stub_request(:post, "https://api.github.com/repos/discourse/discourse/issues/701/comments").
154 | with(headers: headers).
155 | to_return(status: 200, body: "", headers: {})
156 |
157 | stub_request(:post, "https://api.github.com/repos/discourse/discourse-github-linkback/issues/3/comments").
158 | with(headers: headers).
159 | to_return(status: 200, body: "", headers: {})
160 |
161 | end
162 |
163 | it "returns the URL it linked to and custom fields" do
164 | links = GithubLinkback.new(post).create
165 | expect(links.size).to eq(3)
166 |
167 | expect(links[0].url).to eq(github_commit_link)
168 | field = GithubLinkback.field_for(github_commit_link)
169 | expect(post.custom_fields[field]).to be_present
170 |
171 | expect(links[1].url).to eq(github_pr_link)
172 | field = GithubLinkback.field_for(github_pr_link)
173 | expect(post.custom_fields[field]).to be_present
174 |
175 | expect(links[2].url).to eq(github_pr_link_wildcard)
176 | field = GithubLinkback.field_for(github_pr_link_wildcard)
177 | expect(post.custom_fields[field]).to be_present
178 | end
179 | end
180 | end
181 |
182 | end
183 |
--------------------------------------------------------------------------------