From ae8a58fec2dd49ad5d4c940bdb6ae481e0df2fa2 Mon Sep 17 00:00:00 2001 From: rhymes Date: Mon, 4 May 2020 22:17:16 +0200 Subject: [PATCH] [deploy] Decouple GitHub authentication from fetching GitHub issues (#7660) * Improve error handling * Add tests for Github::Client * Add GithubIssue validation * Refactor GithubIssue and fix bug with pull requests * Improve tests * Fix PR API URL generation * Improve logic and get rid of the issue fragment * Fix disappeared avatar * Improve rendering * Activate client side caching using Redis * Remove unnecessary file * Add RemoveGithubIssues data update script * Fix comma * Forgot to disable the approvals verify text * Apply PR feedback --- Envfile | 4 +- Gemfile | 1 + Gemfile.lock | 3 + app/assets/stylesheets/ltags/GithubTag.scss | 11 +- .../github_tag/github_issue_tag.rb | 69 +++++-- app/models/github_issue.rb | 115 ++++++----- app/services/github/client.rb | 103 ++++++++++ app/services/github/errors.rb | 15 ++ app/views/liquids/_github_issue.html.erb | 6 +- .../20200503134234_remove_github_issues.rb | 7 + spec/factories/github_issues.rb | 5 + .../github_tag/github_issue_tag_spec.rb | 86 +++++--- spec/models/github_issue_spec.rb | 113 +++++++++-- spec/services/github/client_spec.rb | 22 ++ .../approvals/github_issue_test.approved.html | 19 -- .../github_liquid_tag_default.approved.html | 45 ----- .../vcr_cassettes/github_client_comment.yml | 143 +++++++++++++ .../vcr_cassettes/github_client_issue.yml | 182 +++++++++++++++++ .../github_client_issue_not_found.yml | 70 +++++++ .../github_client_pull_request.yml | 189 ++++++++++++++++++ .../github_client_pull_request_comment.yml | 146 ++++++++++++++ .../vcr_cassettes/github_issue_api.yml | 180 ----------------- 22 files changed, 1177 insertions(+), 357 deletions(-) create mode 100644 app/services/github/client.rb create mode 100644 app/services/github/errors.rb create mode 100644 lib/data_update_scripts/20200503134234_remove_github_issues.rb create mode 100644 spec/factories/github_issues.rb create mode 100644 spec/services/github/client_spec.rb delete mode 100644 spec/support/fixtures/approvals/github_issue_test.approved.html delete mode 100644 spec/support/fixtures/approvals/github_liquid_tag_default.approved.html create mode 100644 spec/support/fixtures/vcr_cassettes/github_client_comment.yml create mode 100644 spec/support/fixtures/vcr_cassettes/github_client_issue.yml create mode 100644 spec/support/fixtures/vcr_cassettes/github_client_issue_not_found.yml create mode 100644 spec/support/fixtures/vcr_cassettes/github_client_pull_request.yml create mode 100644 spec/support/fixtures/vcr_cassettes/github_client_pull_request_comment.yml delete mode 100644 spec/support/fixtures/vcr_cassettes/github_issue_api.yml diff --git a/Envfile b/Envfile index 8556fc6b6..e751bb478 100644 --- a/Envfile +++ b/Envfile @@ -48,8 +48,8 @@ variable :FORCE_SSL_IN_RAILS, :String, default: "not applicable in dev" # Github for github related access # (https://developer.github.com/v3/guides/basics-of-authentication/) -variable :GITHUB_KEY, :String, default: "Optional" -variable :GITHUB_SECRET, :String, default: "Optional" +variable :GITHUB_KEY, :String, default: "" +variable :GITHUB_SECRET, :String, default: "" # Twitter for normal twitter access # (https://developer.twitter.com/en/docs/basics/authentication/guides/access-tokens) diff --git a/Gemfile b/Gemfile index 54f36424a..3a268b636 100644 --- a/Gemfile +++ b/Gemfile @@ -38,6 +38,7 @@ gem "elasticsearch", "~> 7.6" # Powers DEVs core search functionality gem "email_validator", "~> 2.0" # Email validator for Rails and ActiveModel gem "emoji_regex", "~> 3.0" # A pair of Ruby regular expressions for matching Unicode Emoji symbols gem "envied", "~> 0.9" # Ensure presence and type of your app's ENV-variables +gem "faraday-http-cache", "~> 2.2" # Middleware to handle HTTP caching gem "fast_jsonapi", "~> 1.5" # Serializer for Ruby objects gem "fastly", "~> 2.5" # Client library for the Fastly acceleration system gem "feedjira", "~> 3.1" # A feed fetching and parsing library diff --git a/Gemfile.lock b/Gemfile.lock index 11863efb7..6fadee153 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -299,6 +299,8 @@ GEM i18n (>= 1.6, < 2) faraday (1.0.1) multipart-post (>= 1.2, < 3) + faraday-http-cache (2.2.0) + faraday (>= 0.8) faraday_middleware (1.0.0) faraday (~> 1.0) fast_jsonapi (1.5) @@ -880,6 +882,7 @@ DEPENDENCIES exifr (>= 1.3.6) factory_bot_rails (~> 5.2) faker (~> 2.11) + faraday-http-cache (~> 2.2) fast_jsonapi (~> 1.5) fastly (~> 2.5) feedjira (~> 3.1) diff --git a/app/assets/stylesheets/ltags/GithubTag.scss b/app/assets/stylesheets/ltags/GithubTag.scss index c106fa6ae..9be6a15b6 100644 --- a/app/assets/stylesheets/ltags/GithubTag.scss +++ b/app/assets/stylesheets/ltags/GithubTag.scss @@ -1,4 +1,5 @@ @import 'variables'; + .ltag_github-liquid-tag { margin: 1.1em auto 1.3em; max-width: 580px; @@ -95,19 +96,19 @@ display: block; } - img.github-liquid-tag-img { + .github-liquid-tag-img { min-height: inherit; - width: 44px; - height: 44px; + width: 38px; + height: 38px; border-radius: 3px; float: none; - padding: 10px 0 10px 12px; + padding: 10px 12px 10px 0px; margin: inherit; @media screen and (min-width: 500px) { float: left; padding: 0; - margin-left: -75px; + margin-left: -9px; } } } diff --git a/app/liquid_tags/github_tag/github_issue_tag.rb b/app/liquid_tags/github_tag/github_issue_tag.rb index 6043f2b57..3453f32c6 100644 --- a/app/liquid_tags/github_tag/github_issue_tag.rb +++ b/app/liquid_tags/github_tag/github_issue_tag.rb @@ -1,19 +1,22 @@ -# GithubTag generates the following api links +# GithubTag generates the following API links: # getting an issue # https://api.github.com/repos/facebook/react/issues/9218 -# getting comments of an issue -# https://api.github.com/repos/facebook/react/issues/9218/comments -# getting exat comment of an issue +# getting a pull request +# https://api.github.com/repos/facebook/react/pulls/14105 +# getting a comment belonging to an issue # https://api.github.com/repos/facebook/react/issues/comments/287635042 class GithubTag class GithubIssueTag PARTIAL = "liquids/github_issue".freeze + API_BASE_ENDPOINT = "https://api.github.com/repos/".freeze def initialize(link) @orig_link = link @link = parse_link(link) @content = GithubIssue.find_or_fetch(@link) @content_json = @content.issue_serialized + @is_issue = @content_json[:title].present? + @created_at = @content_json[:created_at] @body = @content.processed_html.html_safe end @@ -21,20 +24,24 @@ class GithubTag ActionController::Base.new.render_to_string( partial: PARTIAL, locals: { - title: @content_json[:title], - issue_number: @content_json[:number], - user_html_url: @content_json[:user][:html_url], + body: @body, + created_at: @created_at.rfc3339, + date: @created_at.utc.strftime("%b %d, %Y"), + html_url: @content_json[:html_url], + issue_number: issue_number, + tagline: tagline, + title: title, user_avatar_url: @content_json[:user][:avatar_url], - username: @content_json[:user][:login], - date_link: @content_json[:html_url], - date: Time.zone.parse(@content_json[:created_at].to_s).utc.strftime("%b %d, %Y"), - body: @body + user_html_url: @content_json[:user][:html_url], + username: @content_json[:user][:login] }, ) end private + attr_reader :content_json + def parse_link(link) link = ActionController::Base.helpers.strip_tags(link) link_no_space = link.delete(" ") @@ -46,9 +53,29 @@ class GithubTag end def generate_api_link(input) - input = input.gsub(/\?.*/, "") - input = input.gsub(/\d{1,}#issuecomment-/, "comments/") if input.include?("#issuecomment-") - "https://api.github.com/repos/#{input.gsub(/.*github\.com\//, '')}" + uri = URI.parse(input).normalize + uri.host = nil if uri.host == "github.com" + + # public PRs URLs are "/pull/{id}" but the API requires "/pulls/{id}" + uri.path = uri.path.gsub(%r{/pull/}, "/pulls/") + + # public comments URLs are "/issues/{id}#issuecomment-{comment_id}" + # or "/pull/{id}#issuecomment-{comment_id}" + # but the API requires "/issues/comments/{comment_id}" + if uri.fragment&.start_with?("issuecomment-") + uri.path = uri.path.gsub(%r{(issues|pulls)/\d+}, "issues/comments/") + comment_id = uri.fragment.split("-").last + uri.merge!(comment_id) + end + + # fragments and query params are not needed in the API call + uri.fragment = nil + uri.query = nil + + # remove leading forward slash in the path + path = uri.path.sub(%r{\A/}, "") + + URI.parse(API_BASE_ENDPOINT).merge(path).to_s end def finalize_html(input) @@ -68,7 +95,19 @@ class GithubTag end def raise_error - raise StandardError, "Invalid Github issue link" + raise StandardError, "Invalid GitHub issue, pull request or comment link" + end + + def title + content_json[:title] || "Comment for" + end + + def issue_number + @issue_number ||= content_json[:number] || content_json[:issue_url].split("/").last + end + + def tagline + @is_issue ? "posted on" : "commented on" end end end diff --git a/app/models/github_issue.rb b/app/models/github_issue.rb index 9ec84b341..55eb81cb0 100644 --- a/app/models/github_issue.rb +++ b/app/models/github_issue.rb @@ -1,65 +1,78 @@ +# NOTE: we are using `GithubIssue` to store issues, pull requests and comments class GithubIssue < ApplicationRecord + CATEGORIES = %w[issue issue_comment].freeze + API_URL_REGEXP = %r{\Ahttps://api.github.com/repos/.*\z}.freeze + PATH_COMMENT_REGEXP = %r{/issues/comments/}.freeze + PATH_ISSUE_REGEXP = %r{/issues/}.freeze + PATH_PULL_REQUEST_REGEXP = %r{/pulls/}.freeze + PATH_REPO_REGEXP = %r{.*github.com/repos/}.freeze + serialize :issue_serialized, Hash - validates :category, inclusion: { in: %w[issue issue_comment] } - def self.find_or_fetch(url) - find_by(url: url) || fetch(url) - end + validates :category, inclusion: { in: CATEGORIES } + validates :url, presence: true, length: { maximum: 400 }, format: API_URL_REGEXP - def self.fetch(url) - try_to_get_issue(url) - rescue StandardError => e - raise StandardError, "A GitHub issue 404'ed and could not be found!" if e.message.include?("404 - Not Found") - - raise StandardError, e.message - end - - def self.try_to_get_issue(url) - client = Octokit::Client.new(access_token: random_token) - issue = GithubIssue.new(url: url) - if /\/issues\/comments/.match?(url) - repo, issue_id = url.gsub(/.*github\.com\/repos\//, "").split("/issues/comments/") - issue.issue_serialized = client.issue_comment(repo, issue_id).to_hash - issue.category = "issue_comment" - else - repo, issue_id = url.gsub(/.*github\.com\/repos\//, "").split(issue_or_pull(url)) - issue.issue_serialized = get_issue_serialized(client, repo, issue_id) - issue.category = "issue" + class << self + def find_or_fetch(url) + find_by(url: url) || fetch(url) end - issue.processed_html = get_html(client, issue) - issue.save! - issue - end - def self.get_html(client, issue) - client.markdown(issue.issue_serialized[:body]) - end + private - def self.get_issue_serialized(client, repo, issue_id) - client.issue(repo, issue_id).to_hash - end - - def self.random_token - if Rails.env.test? - "ddsddsdsdssdsds" - else - random_identity.token + def fetch(url) + retrieve_and_save_issue(url) + rescue Github::Errors::NotFound => e + raise e, error_message(url) end - end - def self.random_identity - if Rails.env.production? - Identity.where(provider: "github").last(250).sample - else - Identity.where(provider: "github").last + def retrieve_and_save_issue(url) + issue = new(url: url) + + if PATH_COMMENT_REGEXP.match?(url) + repo, issue_id = comment_repo_and_issue_id(url) + issue.issue_serialized = Github::Client.issue_comment(repo, issue_id).to_h + issue.category = "issue_comment" + else + repo, issue_id = issue_or_pull_repo_and_issue_id(url) + issue.issue_serialized = Github::Client.issue(repo, issue_id).to_h + issue.category = "issue" + end + + # despite the counter intuitive name `.markdown` returns HTML rendered + # from the original markdown + issue.processed_html = Github::Client.markdown(issue.issue_serialized[:body]) + + issue.save! + + issue end - end - def self.issue_or_pull(url) - if !url.include?("pull") - "/issues/" - else - "/pull/" + def clean_url(url) + url.gsub(PATH_REPO_REGEXP, "") + end + + def comment_repo_and_issue_id(url) + clean_url(url).split(PATH_COMMENT_REGEXP) + end + + def issue_or_pull_repo_and_issue_id(url) + splitter = if PATH_PULL_REQUEST_REGEXP.match?(url) + PATH_PULL_REQUEST_REGEXP + else + PATH_ISSUE_REGEXP + end + + clean_url(url).split(splitter) + end + + def error_message(url) + if PATH_COMMENT_REGEXP.match?(url) + _, issue_id = comment_repo_and_issue_id(url) + "Issue comment #{issue_id} not found" + else + _, issue_id = issue_or_pull_repo_and_issue_id(url) + "Issue #{issue_id} not found" + end end end end diff --git a/app/services/github/client.rb b/app/services/github/client.rb new file mode 100644 index 000000000..dcf705a36 --- /dev/null +++ b/app/services/github/client.rb @@ -0,0 +1,103 @@ +module Github + # Github client (uses ocktokit.rb as a backend) + class Client + class << self + # adapted from https://api.rubyonrails.org/classes/Module.html#method-i-delegate_missing_to + def method_missing(method, *args, &block) + return super unless target.respond_to?(method, false) + + # define for re-use + self.class.define_method(method) do |*new_args, &new_block| + request do + target.public_send(method, *new_args, &new_block) + end + end + + # call the original method, this will only be called the first time + # as in subsequent calls, the newly defined method will prevail + request do + target.public_send(method, *args, &block) + end + end + + # adapted from https://api.rubyonrails.org/classes/Module.html#method-i-delegate_missing_to + def respond_to_missing?(method, _include_all = false) + target.respond_to?(method, false) || super + end + + private + + def request + Honeycomb.add_field("app.name", "github.client") + yield + rescue Octokit::Error => e + record_error(e) + handle_error(e) + end + + def record_error(exception) + class_name = exception.class.name.demodulize + + Honeycomb.add_field("github.result", "error") + Honeycomb.add_field("github.error", class_name) + DatadogStatsClient.increment( + "github.errors", + tags: ["error:#{class_name}", "message:#{exception.message}"], + ) + end + + def handle_error(exception) + class_name = exception.class.name.demodulize + + # raise specific error if known, generic one if unknown + error_class = "::Github::Errors::#{class_name}".safe_constantize + raise error_class, exception.message if error_class + + error_class = if exception.class < Octokit::ClientError + Github::Errors::ClientError + elsif exception.class < Octokit::ServerError + Github::Errors::ServerError + else + Github::Errors::Error + end + + raise error_class, exception.message + end + + def target + @target ||= Octokit::Client.new( + client_id: ApplicationConfig["GITHUB_KEY"], + client_secret: ApplicationConfig["GITHUB_SECRET"], + user_agent: "#{Octokit::Default::USER_AGENT} (#{URL.url})", + middleware: faraday_middleware_stack, + connection_options: connection_options, + ) + end + + def faraday_middleware_stack + # Extending the default functionality + # see , + # + # and + Faraday::RackBuilder.new do |builder| + builder.use Faraday::HttpCache, store: Rails.cache, serializer: Marshal, shared_cache: false + builder.use Faraday::Request::Retry, exceptions: [Octokit::ServerError] + builder.use Octokit::Middleware::FollowRedirects + builder.use Octokit::Response::RaiseError + builder.use Octokit::Response::FeedParser + + builder.adapter :patron + end + end + + def connection_options + Octokit.connection_options.merge( + request: { + open_timeout: 5, + timeout: 5 + }, + ) + end + end + end +end diff --git a/app/services/github/errors.rb b/app/services/github/errors.rb new file mode 100644 index 000000000..d8f11cb41 --- /dev/null +++ b/app/services/github/errors.rb @@ -0,0 +1,15 @@ +module Github + module Errors + class Error < StandardError + end + + class ClientError < Error + end + + class ServerError < Error + end + + class NotFound < ClientError + end + end +end diff --git a/app/views/liquids/_github_issue.html.erb b/app/views/liquids/_github_issue.html.erb index 4c6bcf0e1..c400bffc4 100644 --- a/app/views/liquids/_github_issue.html.erb +++ b/app/views/liquids/_github_issue.html.erb @@ -1,6 +1,6 @@
<%= body %>
- + diff --git a/lib/data_update_scripts/20200503134234_remove_github_issues.rb b/lib/data_update_scripts/20200503134234_remove_github_issues.rb new file mode 100644 index 000000000..1b75c6fe4 --- /dev/null +++ b/lib/data_update_scripts/20200503134234_remove_github_issues.rb @@ -0,0 +1,7 @@ +module DataUpdateScripts + class RemoveGithubIssues + def run + GithubIssue.in_batches.delete_all + end + end +end diff --git a/spec/factories/github_issues.rb b/spec/factories/github_issues.rb new file mode 100644 index 000000000..fd0780f20 --- /dev/null +++ b/spec/factories/github_issues.rb @@ -0,0 +1,5 @@ +FactoryBot.define do + factory :github_issue do + category { "issue" } + end +end diff --git a/spec/liquid_tags/github_tag/github_issue_tag_spec.rb b/spec/liquid_tags/github_tag/github_issue_tag_spec.rb index 8e593f9e4..90e27e41c 100644 --- a/spec/liquid_tags/github_tag/github_issue_tag_spec.rb +++ b/spec/liquid_tags/github_tag/github_issue_tag_spec.rb @@ -1,40 +1,80 @@ require "rails_helper" -vcr_option = { - cassette_name: "github_api", - allow_playback_repeats: "true" -} - -RSpec.describe GithubTag::GithubIssueTag, type: :liquid_tag, vcr: vcr_option do +RSpec.describe GithubTag::GithubIssueTag, type: :liquid_tag, vcr: true do describe "#id" do - let(:github_link) { "https://github.com/facebook/react/issues/9218" } + let(:url_issue) { "https://github.com/thepracticaldev/dev.to/issues/7434" } + let(:url_issue_fragment) { "https://github.com/thepracticaldev/dev.to/issues/7434#issue-604653303" } + let(:url_pull_request) { "https://github.com/thepracticaldev/dev.to/pull/7653" } + let(:url_pull_request_issue_fragment) { "https://github.com/thepracticaldev/dev.to/pull/7653#issue-412271322" } + let(:url_issue_comment) { "https://github.com/thepracticaldev/dev.to/issues/7434#issuecomment-621043602" } + let(:url_pull_request_comment) { "https://github.com/thepracticaldev/dev.to/pull/7653#issuecomment-622572436" } + let(:url_not_found) { "https://github.com/thepracticaldev/dev.to/issues/0" } - setup { Liquid::Template.register_tag("github", GithubTag) } - - def generate_github_issue(link) - Liquid::Template.parse("{% github #{link} %}") + def generate_tag(url) + Liquid::Template.register_tag("github", GithubTag) + Liquid::Template.parse("{% github #{url} %}") end - it "rejects github link without domain" do + it "rejects GitHub URL without domain" do expect do - generate_github_issue("/react/issues/9193") + generate_tag("/react/issues/9193") end.to raise_error(StandardError) end - it "rejects invalid github issue link" do + it "rejects invalid GitHub issue URL" do expect do - generate_github_issue("https://github.com/issues/9193") + generate_tag("https://github.com/issues/9193") end.to raise_error(StandardError) end - xit "renders properly" do - output = generate_github_issue(github_link).render - # NB: this approvals test is a little harder to update - # because a legitimate github access token is required for octokit - # which is then captured via vcr. - # IF YOU DO PLAN TO UPDATE THIS: be sure to remove your access token - # from the vcr cassette generated by this - Approvals.verify(output, name: "github_liquid_tag_default", format: :html) + it "rejects a non existing GitHub issue URL" do + VCR.use_cassette("github_client_issue_not_found") do + expect do + generate_tag(url_not_found) + end.to raise_error(StandardError) + end + end + + it "renders an issue URL" do + VCR.use_cassette("github_client_issue") do + html = generate_tag(url_issue).render + expect(html).to include("#7434") + end + end + + it "renders an issue URL with an issue fragment" do + VCR.use_cassette("github_client_issue") do + html = generate_tag(url_issue_fragment).render + expect(html).to include("#7434") + end + end + + it "renders a pull request URL" do + VCR.use_cassette("github_client_pull_request") do + html = generate_tag(url_pull_request).render + expect(html).to include("#7653") + end + end + + it "renders a pull request URL with an issue fragment" do + VCR.use_cassette("github_client_pull_request") do + html = generate_tag(url_pull_request_issue_fragment).render + expect(html).to include("#7653") + end + end + + it "renders an issue comment" do + VCR.use_cassette("github_client_comment") do + html = generate_tag(url_issue_comment).render + expect(html).to include("621043602") + end + end + + it "renders a PR comment" do + VCR.use_cassette("github_client_pull_request_comment") do + html = generate_tag(url_pull_request_comment).render + expect(html).to include("622572436") + end end end end diff --git a/spec/models/github_issue_spec.rb b/spec/models/github_issue_spec.rb index 5d53734f6..e6756671e 100644 --- a/spec/models/github_issue_spec.rb +++ b/spec/models/github_issue_spec.rb @@ -1,20 +1,105 @@ require "rails_helper" -vcr_option = { - cassette_name: "github_issue_api", - allow_playback_repeats: "true" -} +RSpec.describe GithubIssue, type: :model, vcr: true do + let(:url_issue) { "https://api.github.com/repos/thepracticaldev/dev.to/issues/7434" } + let(:url_pull_request) { "https://api.github.com/repos/thepracticaldev/dev.to/pulls/7653" } + let(:url_comment) { "https://api.github.com/repos/thepracticaldev/dev.to/issues/comments/621043602" } + let(:url_not_found) { "https://api.github.com/repos/thepracticaldev/dev.to/issues/0" } -RSpec.describe GithubIssue, type: :model, vcr: vcr_option do - let(:link) { "https://api.github.com/repos/thepracticaldev/dev.to/issues/510#issue-354483683" } + it { is_expected.to validate_length_of(:url).is_at_most(400) } + it { is_expected.to validate_inclusion_of(:category).in_array(%w[issue issue_comment]) } + it { is_expected.to validate_presence_of(:url) } - it "finds or fetches based on URL" do - # NB: this approvals test is a little harder to update - # because a legitimate github access token is required for octokit - # which is then captured via vcr. - # IF YOU DO PLAN TO UPDATE THIS: be sure to remove your access token - # from the vcr cassette generated by this - issue = described_class.find_or_fetch(link) - Approvals.verify(issue.processed_html, name: "github_issue_test", format: :html) + describe ".find_or_fetch" do + context "when retrieving an issue", vcr: { cassette_name: "github_client_issue" } do + it "saves a new issue" do + expect do + described_class.find_or_fetch(url_issue) + end.to change(described_class, :count).by(1) + end + + it "retrieves an existing issue" do + created_issue = described_class.find_or_fetch(url_issue) + + expect do + found_issue = described_class.find_or_fetch(url_issue) + expect(found_issue.id).to eq(created_issue.id) + end.not_to change(described_class, :count) + end + + it "saves the proper fields" do + issue = described_class.find_or_fetch(url_issue) + + expect(issue.url).to eq(url_issue) + expect(issue.issue_serialized[:title]).to be_present + expect(issue.category).to eq("issue") + end + end + + context "when retrieving a pull request", vcr: { cassette_name: "github_client_pull_request" } do + it "saves a new issue" do + expect do + described_class.find_or_fetch(url_pull_request) + end.to change(described_class, :count).by(1) + end + + it "retrieves an existing issue" do + created_issue = described_class.find_or_fetch(url_pull_request) + + expect do + found_issue = described_class.find_or_fetch(url_pull_request) + expect(found_issue.id).to eq(created_issue.id) + end.not_to change(described_class, :count) + end + + it "saves the proper fields" do + issue = described_class.find_or_fetch(url_pull_request) + + expect(issue.url).to eq(url_pull_request) + expect(issue.issue_serialized[:title]).to be_present + expect(issue.category).to eq("issue") + end + end + + context "when retrieving a comment", vcr: { cassette_name: "github_client_comment" } do + it "saves a new issue comment" do + expect do + described_class.find_or_fetch(url_comment) + end.to change(described_class, :count).by(1) + end + + it "retrieves an existing issue comment" do + created_issue = described_class.find_or_fetch(url_comment) + + expect do + found_issue = described_class.find_or_fetch(url_comment) + expect(found_issue.id).to eq(created_issue.id) + end.not_to change(described_class, :count) + end + + it "saves the proper fields" do + issue = described_class.find_or_fetch(url_comment) + + expect(issue.url).to eq(url_comment) + expect(issue.issue_serialized[:title]).not_to be_present + expect(issue.issue_serialized[:body]).to be_present + expect(issue.category).to eq("issue_comment") + end + end + end + + xit "saves HTML in .processed_html" do + VCR.use_cassette("github_client_issue") do + issue = described_class.find_or_fetch(url_issue) + Approvals.verify(issue.processed_html, name: "github_client_issue", format: :html) + end + end + + it "raises Github::Errors::NotFound if the issue is not found" do + VCR.use_cassette("github_client_issue_not_found") do + expect do + described_class.find_or_fetch(url_not_found) + end.to raise_error(Github::Errors::NotFound) + end end end diff --git a/spec/services/github/client_spec.rb b/spec/services/github/client_spec.rb new file mode 100644 index 000000000..54b9d020f --- /dev/null +++ b/spec/services/github/client_spec.rb @@ -0,0 +1,22 @@ +require "rails_helper" + +RSpec.describe Github::Client, type: :service, vcr: true do + let(:repo) { "thepracticaldev/dev.to" } + + describe ".issue" do + it "returns a an issue" do + VCR.use_cassette("github_client_issue") do + issue = described_class.issue(repo, 7434) + expect(issue.title).to be_present + end + end + + it "raises NotFound if the issue does not exist" do + VCR.use_cassette("github_client_issue_not_found") do + expect do + described_class.issue(repo, 0) + end.to raise_error(Github::Errors::NotFound) + end + end + end +end diff --git a/spec/support/fixtures/approvals/github_issue_test.approved.html b/spec/support/fixtures/approvals/github_issue_test.approved.html deleted file mode 100644 index e6b9f749a..000000000 --- a/spec/support/fixtures/approvals/github_issue_test.approved.html +++ /dev/null @@ -1,19 +0,0 @@ - - -

@jessleenyc commented on Tue Jul 31 2018

-

-TASK or Feature -

-

-Request or User Story

-

Getting lots of crawlers clicking on the hidden link that's giving them 404 errors and jamming up honeybadger!

-

-Definition of Done

-

-Notes

-

@arun1595 commented on Wed Aug 01 2018

-

@jessleenyc I would like to tackle this issue. Can you guide me with what "moderate" links are?

-

Thanks.

-

@Zhao-Andy commented on Mon Aug 27 2018

-

Hey @arun1595, we're going to move this to the public repo and continue the conversation there.

- diff --git a/spec/support/fixtures/approvals/github_liquid_tag_default.approved.html b/spec/support/fixtures/approvals/github_liquid_tag_default.approved.html deleted file mode 100644 index 14b3455aa..000000000 --- a/spec/support/fixtures/approvals/github_liquid_tag_default.approved.html +++ /dev/null @@ -1,45 +0,0 @@ - - - -
-

- - - [Feature] granular forceUpdate - - #9218 - -

-
-
- - thysultan avatar - - -
- - thysultan - commented on Mar 19, 2017 -
-
-
-

When forceUpdate is called on a long list at the very least the whole list must be traversed. For example with the following list arr = [1, 2, 3, 4, 5, 6] and if we where to push 7 arr.push(7) the reconciler will go through elements 1 - 6 and then add a 7 at the end of that list. The suggestion to add support for the following forceUpdate signature.

-
forceUpdate(a: {function|number}, b: number?)
-

Where when passed as an integer a is the start index and b the number of items to traverse similar to how str.substr() works.

-

When the function encounters a number as the a argument it changes the start index of where to start when reconciling the components children and when the b argument is set it changes the end index of where to end the traversal of the components children. So with the above mentioned example if i wanted to optimize the reconciliation process for append updates i could do the following.

-
this.forceUpdate(arr.length);
-

and to target an update in the middle of the list of up to 3 items.

-
this.forceUpdate(arr.length/2, 3);
-

This will allow for very precise O(1) updates when you know ahead of time how the data will change.

-

Possible setState counter-part.

-
setState(a: {function|Object}, b: (function|number)?, c: {number})
-
-// usage
-setState({arr: [...]}, this.state.arr.length);
- -
- -
-
- - diff --git a/spec/support/fixtures/vcr_cassettes/github_client_comment.yml b/spec/support/fixtures/vcr_cassettes/github_client_comment.yml new file mode 100644 index 000000000..6eb84379e --- /dev/null +++ b/spec/support/fixtures/vcr_cassettes/github_client_comment.yml @@ -0,0 +1,143 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.github.com/repos/thepracticaldev/dev.to/issues/comments/621043602?per_page=100 + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Octokit Ruby Gem 4.18.0 (http://localhost:3000) + Accept: + - application/vnd.github.v3+json + Content-Type: + - application/json + X-Honeycomb-Trace: + - 1;dataset=,trace_id=b7791fdb-dcc2-4751-84d7-6d5b91d4c8d7,parent_id=f98dfdd2-ef73-44bd-a92c-3cc4ffd3f62c,context=e30= + Expect: + - '' + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 02 May 2020 23:54:45 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '1454' + Server: + - GitHub.com + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4978' + X-Ratelimit-Reset: + - '1588465848' + Cache-Control: + - public, max-age=60, s-maxage=60 + Vary: + - Accept + - Accept-Encoding, Accept, X-Requested-With + Etag: + - '"1589430fd33ed6b00098c14727a2a9f6"' + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - F3AC:786B:2668DF:351A29:5EAE0845 + body: + encoding: ASCII-8BIT + string: '{"url":"https://api.github.com/repos/thepracticaldev/dev.to/issues/comments/621043602","html_url":"https://github.com/thepracticaldev/dev.to/issues/7434#issuecomment-621043602","issue_url":"https://api.github.com/repos/thepracticaldev/dev.to/issues/7434","id":621043602,"node_id":"MDEyOklzc3VlQ29tbWVudDYyMTA0MzYwMg==","user":{"login":"catalinpit","id":25515812,"node_id":"MDQ6VXNlcjI1NTE1ODEy","avatar_url":"https://avatars3.githubusercontent.com/u/25515812?u=2c7e3d9e557cc819c3dd2069f5e4904d6a365ad1&v=4","gravatar_id":"","url":"https://api.github.com/users/catalinpit","html_url":"https://github.com/catalinpit","followers_url":"https://api.github.com/users/catalinpit/followers","following_url":"https://api.github.com/users/catalinpit/following{/other_user}","gists_url":"https://api.github.com/users/catalinpit/gists{/gist_id}","starred_url":"https://api.github.com/users/catalinpit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/catalinpit/subscriptions","organizations_url":"https://api.github.com/users/catalinpit/orgs","repos_url":"https://api.github.com/users/catalinpit/repos","events_url":"https://api.github.com/users/catalinpit/events{/privacy}","received_events_url":"https://api.github.com/users/catalinpit/received_events","type":"User","site_admin":false},"created_at":"2020-04-29T07:46:28Z","updated_at":"2020-04-29T07:46:28Z","author_association":"CONTRIBUTOR","body":"What + do you think, @ludwiczakpawel?"}' + http_version: null + recorded_at: Sat, 02 May 2020 23:54:45 GMT +- request: + method: post + uri: https://api.github.com/markdown + body: + encoding: UTF-8 + string: '{"text":"What do you think, @ludwiczakpawel?"}' + headers: + User-Agent: + - Octokit Ruby Gem 4.18.0 (http://localhost:3000) + Accept: + - application/vnd.github.raw + Content-Type: + - application/json + X-Honeycomb-Trace: + - 1;dataset=,trace_id=c3f4fcd3-12df-4e45-90ed-6eff8690d534,parent_id=7666e3c3-e7c8-4655-b4ab-b6f5f9622c88,context=e30= + Expect: + - '' + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 02 May 2020 23:54:46 GMT + Content-Type: + - text/html;charset=utf-8 + Content-Length: + - '43' + Server: + - GitHub.com + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4977' + X-Ratelimit-Reset: + - '1588465848' + X-Commonmarker-Version: + - 0.21.0 + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + Vary: + - Accept-Encoding, Accept, X-Requested-With + X-Github-Request-Id: + - F3AD:786C:193245:227CB0:5EAE0846 + body: + encoding: ASCII-8BIT + string: "

What do you think, @ludwiczakpawel?

\n" + http_version: null + recorded_at: Sat, 02 May 2020 23:54:46 GMT +recorded_with: VCR 5.1.0 diff --git a/spec/support/fixtures/vcr_cassettes/github_client_issue.yml b/spec/support/fixtures/vcr_cassettes/github_client_issue.yml new file mode 100644 index 000000000..1bf388c3f --- /dev/null +++ b/spec/support/fixtures/vcr_cassettes/github_client_issue.yml @@ -0,0 +1,182 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.github.com/repos/thepracticaldev/dev.to/issues/7434 + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Octokit Ruby Gem 4.18.0 (http://localhost:3000) + Accept: + - application/vnd.github.v3+json + Content-Type: + - application/json + X-Honeycomb-Trace: + - 1;dataset=,trace_id=4085b91d-4649-4fc3-bf1c-96c5a2bc8d57,parent_id=9367c158-33bc-4172-bbbe-b5ac5e8926de,context=e30= + Expect: + - '' + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 02 May 2020 23:54:39 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '3552' + Server: + - GitHub.com + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4983' + X-Ratelimit-Reset: + - '1588465848' + Cache-Control: + - public, max-age=60, s-maxage=60 + Vary: + - Accept + - Accept-Encoding, Accept, X-Requested-With + Etag: + - '"54e1b0dd3fed55dc41e2d4affd48d3d7"' + Last-Modified: + - Sat, 02 May 2020 10:56:49 GMT + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - F3A7:29D1:25238A:33C5C5:5EAE083E + body: + encoding: ASCII-8BIT + string: '{"url":"https://api.github.com/repos/thepracticaldev/dev.to/issues/7434","repository_url":"https://api.github.com/repos/thepracticaldev/dev.to","labels_url":"https://api.github.com/repos/thepracticaldev/dev.to/issues/7434/labels{/name}","comments_url":"https://api.github.com/repos/thepracticaldev/dev.to/issues/7434/comments","events_url":"https://api.github.com/repos/thepracticaldev/dev.to/issues/7434/events","html_url":"https://github.com/thepracticaldev/dev.to/issues/7434","id":604653303,"node_id":"MDU6SXNzdWU2MDQ2NTMzMDM=","number":7434,"title":"Profile + page not displaying the name due to theming and custom colors","user":{"login":"catalinpit","id":25515812,"node_id":"MDQ6VXNlcjI1NTE1ODEy","avatar_url":"https://avatars0.githubusercontent.com/u/25515812?v=4","gravatar_id":"","url":"https://api.github.com/users/catalinpit","html_url":"https://github.com/catalinpit","followers_url":"https://api.github.com/users/catalinpit/followers","following_url":"https://api.github.com/users/catalinpit/following{/other_user}","gists_url":"https://api.github.com/users/catalinpit/gists{/gist_id}","starred_url":"https://api.github.com/users/catalinpit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/catalinpit/subscriptions","organizations_url":"https://api.github.com/users/catalinpit/orgs","repos_url":"https://api.github.com/users/catalinpit/repos","events_url":"https://api.github.com/users/catalinpit/events{/privacy}","received_events_url":"https://api.github.com/users/catalinpit/received_events","type":"User","site_admin":false},"labels":[{"id":1031106689,"node_id":"MDU6TGFiZWwxMDMxMTA2Njg5","url":"https://api.github.com/repos/thepracticaldev/dev.to/labels/accessibility","name":"accessibility","color":"c5aef2","default":false,"description":""},{"id":1290917705,"node_id":"MDU6TGFiZWwxMjkwOTE3NzA1","url":"https://api.github.com/repos/thepracticaldev/dev.to/labels/area:%20profile","name":"area: + profile","color":"2a75b2","default":false,"description":"user''s profile"},{"id":1290878626,"node_id":"MDU6TGFiZWwxMjkwODc4NjI2","url":"https://api.github.com/repos/thepracticaldev/dev.to/labels/area:%20themes","name":"area: + themes","color":"2a75b2","default":false,"description":"UX theming"},{"id":480869965,"node_id":"MDU6TGFiZWw0ODA4Njk5NjU=","url":"https://api.github.com/repos/thepracticaldev/dev.to/labels/type:%20bug","name":"type: + bug","color":"C61C32","default":false,"description":"always open for contribution"}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":6,"created_at":"2020-04-22T10:43:38Z","updated_at":"2020-05-02T10:56:49Z","closed_at":null,"author_association":"CONTRIBUTOR","body":"**Describe + the bug**\r\n\r\nYou are allowed to change the colour for some of the information + from your profile page. There are cases when the profile page doesn''t work + properly when the colours are related.\r\n\r\nSee the pictures below or visit + my profile [dev.to/catalinmpit](https://dev.to/catalinmpit).\r\n\r\n**To Reproduce**\r\n\r\n1. + Go to [dev.to/catalinmpit](https://dev.to/catalinmpit)\r\n\r\n**Expected behavior**\r\n\r\nMy + name and other information should be visible.\r\n\r\n**Screenshots**\r\n\r\n![broken-profile-page](https://i.imgur.com/AJyM7tH.png)\r\n\r\n**Aditional + information**\r\n\r\nPlayed around with the settings and I''ve done this:\r\n\r\n![profile-page-ok](https://i.imgur.com/DLiPXFT.png)\r\n\r\n**Question**\r\n\r\nWhat + do you recommend to fix the issue? If you have any recommendations, I''m happy + to pick the issue.\r\n","closed_by":null}' + http_version: null + recorded_at: Sat, 02 May 2020 23:54:39 GMT +- request: + method: post + uri: https://api.github.com/markdown + body: + encoding: UTF-8 + string: '{"text":"**Describe the bug**\r\n\r\nYou are allowed to change the + colour for some of the information from your profile page. There are cases + when the profile page doesn''t work properly when the colours are related.\r\n\r\nSee + the pictures below or visit my profile [dev.to/catalinmpit](https://dev.to/catalinmpit).\r\n\r\n**To + Reproduce**\r\n\r\n1. Go to [dev.to/catalinmpit](https://dev.to/catalinmpit)\r\n\r\n**Expected + behavior**\r\n\r\nMy name and other information should be visible.\r\n\r\n**Screenshots**\r\n\r\n![broken-profile-page](https://i.imgur.com/AJyM7tH.png)\r\n\r\n**Aditional + information**\r\n\r\nPlayed around with the settings and I''ve done this:\r\n\r\n![profile-page-ok](https://i.imgur.com/DLiPXFT.png)\r\n\r\n**Question**\r\n\r\nWhat + do you recommend to fix the issue? If you have any recommendations, I''m happy + to pick the issue.\r\n"}' + headers: + User-Agent: + - Octokit Ruby Gem 4.18.0 (http://localhost:3000) + Accept: + - application/vnd.github.raw + Content-Type: + - application/json + X-Honeycomb-Trace: + - 1;dataset=,trace_id=63ab979a-3896-4e70-b54d-bf2bf6d375b8,parent_id=b0559d07-130e-4438-ae4a-4852728336a1,context=e30= + Expect: + - '' + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 02 May 2020 23:54:40 GMT + Content-Type: + - text/html;charset=utf-8 + Content-Length: + - '1759' + Server: + - GitHub.com + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4982' + X-Ratelimit-Reset: + - '1588465848' + X-Commonmarker-Version: + - 0.21.0 + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + Vary: + - Accept-Encoding, Accept, X-Requested-With + X-Github-Request-Id: + - F3A8:29D1:2523AC:33C5F9:5EAE0840 + body: + encoding: ASCII-8BIT + string: | +

Describe the bug

+

You are allowed to change the colour for some of the information from your profile page. There are cases when the profile page doesn't work properly when the colours are related.

+

See the pictures below or visit my profile dev.to/catalinmpit.

+

To Reproduce

+
    +
  1. Go to dev.to/catalinmpit +
  2. +
+

Expected behavior

+

My name and other information should be visible.

+

Screenshots

+

broken-profile-page

+

Aditional information

+

Played around with the settings and I've done this:

+

profile-page-ok

+

Question

+

What do you recommend to fix the issue? If you have any recommendations, I'm happy to pick the issue.

+ http_version: null + recorded_at: Sat, 02 May 2020 23:54:40 GMT +recorded_with: VCR 5.1.0 diff --git a/spec/support/fixtures/vcr_cassettes/github_client_issue_not_found.yml b/spec/support/fixtures/vcr_cassettes/github_client_issue_not_found.yml new file mode 100644 index 000000000..d8c697c87 --- /dev/null +++ b/spec/support/fixtures/vcr_cassettes/github_client_issue_not_found.yml @@ -0,0 +1,70 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.github.com/repos/thepracticaldev/dev.to/issues/0 + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Octokit Ruby Gem 4.18.0 (http://localhost:3000) + Accept: + - application/vnd.github.v3+json + Content-Type: + - application/json + X-Honeycomb-Trace: + - 1;dataset=,trace_id=7b55123d-9904-435d-b5d3-7db505fe3bce,parent_id=73128408-7991-4aac-8a85-f6569b0e9d3b,context=e30= + Expect: + - '' + response: + status: + code: 404 + message: Not Found + headers: + Date: + - Sat, 02 May 2020 23:54:41 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '104' + Server: + - GitHub.com + Status: + - 404 Not Found + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4981' + X-Ratelimit-Reset: + - '1588465848' + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + Vary: + - Accept-Encoding, Accept, X-Requested-With + X-Github-Request-Id: + - F3A9:29D1:2523C7:33C61F:5EAE0841 + body: + encoding: ASCII-8BIT + string: '{"message":"Not Found","documentation_url":"https://developer.github.com/v3/issues/#get-a-single-issue"}' + http_version: null + recorded_at: Sat, 02 May 2020 23:54:41 GMT +recorded_with: VCR 5.1.0 diff --git a/spec/support/fixtures/vcr_cassettes/github_client_pull_request.yml b/spec/support/fixtures/vcr_cassettes/github_client_pull_request.yml new file mode 100644 index 000000000..2795f3a92 --- /dev/null +++ b/spec/support/fixtures/vcr_cassettes/github_client_pull_request.yml @@ -0,0 +1,189 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.github.com/repos/thepracticaldev/dev.to/issues/7653 + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Octokit Ruby Gem 4.18.0 (http://localhost:3000) + Accept: + - application/vnd.github.v3+json + Content-Type: + - application/json + X-Honeycomb-Trace: + - 1;dataset=,trace_id=3f8011ec-0d23-49d3-9d19-3acdb553274a,parent_id=e31110e6-dc1e-4884-b01b-ee15846d4236,context=e30= + Expect: + - '' + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 02 May 2020 23:54:43 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '4233' + Server: + - GitHub.com + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4980' + X-Ratelimit-Reset: + - '1588465849' + Cache-Control: + - public, max-age=60, s-maxage=60 + Vary: + - Accept + - Accept-Encoding, Accept, X-Requested-With + Etag: + - '"0494d7edd146d9a6161dabf46f66cf6e"' + Last-Modified: + - Fri, 01 May 2020 21:47:35 GMT + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - F3AA:4B32:24AC63:32E837:5EAE0842 + body: + encoding: ASCII-8BIT + string: '{"url":"https://api.github.com/repos/thepracticaldev/dev.to/issues/7653","repository_url":"https://api.github.com/repos/thepracticaldev/dev.to","labels_url":"https://api.github.com/repos/thepracticaldev/dev.to/issues/7653/labels{/name}","comments_url":"https://api.github.com/repos/thepracticaldev/dev.to/issues/7653/comments","events_url":"https://api.github.com/repos/thepracticaldev/dev.to/issues/7653/events","html_url":"https://github.com/thepracticaldev/dev.to/pull/7653","id":610931120,"node_id":"MDExOlB1bGxSZXF1ZXN0NDEyMjcxMzIy","number":7653,"title":"Log + RateLimit hits to Datadog for Better Reporting","user":{"login":"mstruve","id":1813380,"node_id":"MDQ6VXNlcjE4MTMzODA=","avatar_url":"https://avatars3.githubusercontent.com/u/1813380?v=4","gravatar_id":"","url":"https://api.github.com/users/mstruve","html_url":"https://github.com/mstruve","followers_url":"https://api.github.com/users/mstruve/followers","following_url":"https://api.github.com/users/mstruve/following{/other_user}","gists_url":"https://api.github.com/users/mstruve/gists{/gist_id}","starred_url":"https://api.github.com/users/mstruve/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mstruve/subscriptions","organizations_url":"https://api.github.com/users/mstruve/orgs","repos_url":"https://api.github.com/users/mstruve/repos","events_url":"https://api.github.com/users/mstruve/events{/privacy}","received_events_url":"https://api.github.com/users/mstruve/received_events","type":"User","site_admin":false},"labels":[{"id":1123882148,"node_id":"MDU6TGFiZWwxMTIzODgyMTQ4","url":"https://api.github.com/repos/thepracticaldev/dev.to/labels/PR:%20merged","name":"PR: + merged","color":"786DBE","default":false,"description":"bot applied label + for PR''s that are merged"}],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":2,"created_at":"2020-05-01T19:40:23Z","updated_at":"2020-05-01T21:47:35Z","closed_at":"2020-05-01T21:47:32Z","author_association":"COLLABORATOR","pull_request":{"url":"https://api.github.com/repos/thepracticaldev/dev.to/pulls/7653","html_url":"https://github.com/thepracticaldev/dev.to/pull/7653","diff_url":"https://github.com/thepracticaldev/dev.to/pull/7653.diff","patch_url":"https://github.com/thepracticaldev/dev.to/pull/7653.patch"},"body":"\r\n## + What type of PR is this? (check all applicable)\r\n- [x] Feature\r\n- [x] + Optimization\r\n\r\n## Description\r\nRather than sending (almost) every single + rate limit hit to Slack, lets send a metric to Datadog that we can track and + alert on. The goal here is to send this data to Datadog. Once the data is + there @michael-tharrington can then decide what thresholds he wants to set + for what actions to determine when he gets alerted. Datadog then will be responsible + for sending that information to Slack rather than our app. \r\n\r\nThis will + allow us to set thresholds and alert only when users are excessively hitting + rate limits rather than every single time which sometimes is not concerning + if they only hit it once, like a pentester trying to see if a rate limiter + exists. \r\n\r\n## Related Tickets & Documents\r\nhttps://github.com/thepracticaldev/dev.to/projects/9#card-37397577\r\n\r\n## + Added tests?\r\n- [x] yes\r\n\r\n![alt_text](https://media1.giphy.com/media/l2YWsOymHDRFYqyAM/giphy.gif)\r\n","closed_by":{"login":"mstruve","id":1813380,"node_id":"MDQ6VXNlcjE4MTMzODA=","avatar_url":"https://avatars3.githubusercontent.com/u/1813380?v=4","gravatar_id":"","url":"https://api.github.com/users/mstruve","html_url":"https://github.com/mstruve","followers_url":"https://api.github.com/users/mstruve/followers","following_url":"https://api.github.com/users/mstruve/following{/other_user}","gists_url":"https://api.github.com/users/mstruve/gists{/gist_id}","starred_url":"https://api.github.com/users/mstruve/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mstruve/subscriptions","organizations_url":"https://api.github.com/users/mstruve/orgs","repos_url":"https://api.github.com/users/mstruve/repos","events_url":"https://api.github.com/users/mstruve/events{/privacy}","received_events_url":"https://api.github.com/users/mstruve/received_events","type":"User","site_admin":false}}' + http_version: null + recorded_at: Sat, 02 May 2020 23:54:43 GMT +- request: + method: post + uri: https://api.github.com/markdown + body: + encoding: UTF-8 + string: '{"text":"\r\n## What type of PR is this? (check all applicable)\r\n- + [x] Feature\r\n- [x] Optimization\r\n\r\n## Description\r\nRather than sending + (almost) every single rate limit hit to Slack, lets send a metric to Datadog + that we can track and alert on. The goal here is to send this data to Datadog. + Once the data is there @michael-tharrington can then decide what thresholds + he wants to set for what actions to determine when he gets alerted. Datadog + then will be responsible for sending that information to Slack rather than + our app. \r\n\r\nThis will allow us to set thresholds and alert only when + users are excessively hitting rate limits rather than every single time which + sometimes is not concerning if they only hit it once, like a pentester trying + to see if a rate limiter exists. \r\n\r\n## Related Tickets & Documents\r\nhttps://github.com/thepracticaldev/dev.to/projects/9#card-37397577\r\n\r\n## + Added tests?\r\n- [x] yes\r\n\r\n![alt_text](https://media1.giphy.com/media/l2YWsOymHDRFYqyAM/giphy.gif)\r\n"}' + headers: + User-Agent: + - Octokit Ruby Gem 4.18.0 (http://localhost:3000) + Accept: + - application/vnd.github.raw + Content-Type: + - application/json + X-Honeycomb-Trace: + - 1;dataset=,trace_id=40a6d376-ddbd-4054-8f13-16983a5f5d45,parent_id=8229768b-93f5-4952-8408-fec5650f68a9,context=e30= + Expect: + - '' + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 02 May 2020 23:54:44 GMT + Content-Type: + - text/html;charset=utf-8 + Content-Length: + - '2331' + Server: + - GitHub.com + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4979' + X-Ratelimit-Reset: + - '1588465848' + X-Commonmarker-Version: + - 0.21.0 + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + Vary: + - Accept-Encoding, Accept, X-Requested-With + X-Github-Request-Id: + - F3AB:29D2:192531:22748D:5EAE0843 + body: + encoding: ASCII-8BIT + string: | +

+ What type of PR is this? (check all applicable)

+
    +
  • [x] Feature
  • +
  • [x] Optimization
  • +
+

+ Description

+

Rather than sending (almost) every single rate limit hit to Slack, lets send a metric to Datadog that we can track and alert on. The goal here is to send this data to Datadog. Once the data is there @michael-tharrington can then decide what thresholds he wants to set for what actions to determine when he gets alerted. Datadog then will be responsible for sending that information to Slack rather than our app.

+

This will allow us to set thresholds and alert only when users are excessively hitting rate limits rather than every single time which sometimes is not concerning if they only hit it once, like a pentester trying to see if a rate limiter exists.

+

+ Related Tickets & Documents

+

https://github.com/thepracticaldev/dev.to/projects/9#card-37397577

+

+ Added tests?

+
    +
  • [x] yes
  • +
+

alt_text

+ http_version: null + recorded_at: Sat, 02 May 2020 23:54:44 GMT +recorded_with: VCR 5.1.0 diff --git a/spec/support/fixtures/vcr_cassettes/github_client_pull_request_comment.yml b/spec/support/fixtures/vcr_cassettes/github_client_pull_request_comment.yml new file mode 100644 index 000000000..cb3c83e57 --- /dev/null +++ b/spec/support/fixtures/vcr_cassettes/github_client_pull_request_comment.yml @@ -0,0 +1,146 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.github.com/repos/thepracticaldev/dev.to/issues/comments/622572436?per_page=100 + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Octokit Ruby Gem 4.18.0 (http://localhost:3000) + Accept: + - application/vnd.github.v3+json + Content-Type: + - application/json + X-Honeycomb-Trace: + - 1;dataset=,trace_id=2130d678-d18a-4c28-a9d9-148409ed2ce1,parent_id=11aec9b3-1ad8-4021-9807-eb96f9426d36,context=e30= + Expect: + - '' + response: + status: + code: 200 + message: OK + headers: + Date: + - Mon, 04 May 2020 09:31:20 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '1663' + Server: + - GitHub.com + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4999' + X-Ratelimit-Reset: + - '1588588280' + Cache-Control: + - public, max-age=60, s-maxage=60 + Vary: + - Accept + - Accept-Encoding, Accept, X-Requested-With + Etag: + - '"006cef151248d15abe7e14ac18b6c5df"' + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - F1B8:29D1:5382F7:738F6E:5EAFE0E7 + body: + encoding: ASCII-8BIT + string: !binary |- + eyJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL3RoZXByYWN0aWNhbGRldi9kZXYudG8vaXNzdWVzL2NvbW1lbnRzLzYyMjU3MjQzNiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL3RoZXByYWN0aWNhbGRldi9kZXYudG8vcHVsbC83NjUzI2lzc3VlY29tbWVudC02MjI1NzI0MzYiLCJpc3N1ZV91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL3RoZXByYWN0aWNhbGRldi9kZXYudG8vaXNzdWVzLzc2NTMiLCJpZCI6NjIyNTcyNDM2LCJub2RlX2lkIjoiTURFeU9rbHpjM1ZsUTI5dGJXVnVkRFl5TWpVM01qUXpOZz09IiwidXNlciI6eyJsb2dpbiI6Im1pY2hhZWwtdGhhcnJpbmd0b24iLCJpZCI6MTYwMDcwNzUsIm5vZGVfaWQiOiJNRFE2VlhObGNqRTJNREEzTURjMSIsImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMyLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzE2MDA3MDc1P3U9ZTVmNDUwNjhhYTYyNDM2NThlYjVkMWQ1MmRiYzhlZWZjOWZlOWE1YiZ2PTQiLCJncmF2YXRhcl9pZCI6IiIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvbWljaGFlbC10aGFycmluZ3RvbiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL21pY2hhZWwtdGhhcnJpbmd0b24iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9taWNoYWVsLXRoYXJyaW5ndG9uL2ZvbGxvd2VycyIsImZvbGxvd2luZ191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL21pY2hhZWwtdGhhcnJpbmd0b24vZm9sbG93aW5ney9vdGhlcl91c2VyfSIsImdpc3RzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvbWljaGFlbC10aGFycmluZ3Rvbi9naXN0c3svZ2lzdF9pZH0iLCJzdGFycmVkX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvbWljaGFlbC10aGFycmluZ3Rvbi9zdGFycmVkey9vd25lcn17L3JlcG99Iiwic3Vic2NyaXB0aW9uc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL21pY2hhZWwtdGhhcnJpbmd0b24vc3Vic2NyaXB0aW9ucyIsIm9yZ2FuaXphdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9taWNoYWVsLXRoYXJyaW5ndG9uL29yZ3MiLCJyZXBvc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL21pY2hhZWwtdGhhcnJpbmd0b24vcmVwb3MiLCJldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9taWNoYWVsLXRoYXJyaW5ndG9uL2V2ZW50c3svcHJpdmFjeX0iLCJyZWNlaXZlZF9ldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9taWNoYWVsLXRoYXJyaW5ndG9uL3JlY2VpdmVkX2V2ZW50cyIsInR5cGUiOiJVc2VyIiwic2l0ZV9hZG1pbiI6ZmFsc2V9LCJjcmVhdGVkX2F0IjoiMjAyMC0wNS0wMVQyMToxODoxMFoiLCJ1cGRhdGVkX2F0IjoiMjAyMC0wNS0wMVQyMToxODoxMFoiLCJhdXRob3JfYXNzb2NpYXRpb24iOiJDT05UUklCVVRPUiIsImJvZHkiOiJTbyBleGNpdGVkIGZvciB0aGlzISBJJ20gZ29pbmcgdG8gbWFrZSBzdXJlIHdlIHRhbGsgYWJvdXQgdGhpcyBpbiBvdXIgbmV4dCBzdXBwb3J0IG1lZXRpbmcuIEkgdGhpbmsgdGhpcyBpcyBhIHN1cGVyIPCfhpIgdGhpbmcgdG8gbW9uaXRvci4ifQ== + http_version: null + recorded_at: Mon, 04 May 2020 09:31:20 GMT +- request: + method: post + uri: https://api.github.com/markdown + body: + encoding: UTF-8 + string: "{\"text\":\"So excited for this! I'm going to make sure we talk about + this in our next support meeting. I think this is a super \U0001F192 thing + to monitor.\"}" + headers: + User-Agent: + - Octokit Ruby Gem 4.18.0 (http://localhost:3000) + Accept: + - application/vnd.github.raw + Content-Type: + - application/json + X-Honeycomb-Trace: + - 1;dataset=,trace_id=a4437cb5-738c-4169-894b-ee27dd425fc1,parent_id=1d39a812-0a36-4737-85bc-8efe8039dbda,context=e30= + Expect: + - '' + response: + status: + code: 200 + message: OK + headers: + Date: + - Mon, 04 May 2020 09:31:21 GMT + Content-Type: + - text/html;charset=utf-8 + Content-Length: + - '278' + Server: + - GitHub.com + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4998' + X-Ratelimit-Reset: + - '1588588280' + X-Commonmarker-Version: + - 0.21.0 + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + Vary: + - Accept-Encoding, Accept, X-Requested-With + X-Github-Request-Id: + - F1B9:786A:DA23C:12F2DF:5EAFE0E8 + body: + encoding: ASCII-8BIT + string: !binary |- + PHA+U28gZXhjaXRlZCBmb3IgdGhpcyEgSSdtIGdvaW5nIHRvIG1ha2Ugc3VyZSB3ZSB0YWxrIGFib3V0IHRoaXMgaW4gb3VyIG5leHQgc3VwcG9ydCBtZWV0aW5nLiBJIHRoaW5rIHRoaXMgaXMgYSBzdXBlciA8Zy1lbW9qaSBjbGFzcz0iZy1lbW9qaSIgYWxpYXM9ImNvb2wiIGZhbGxiYWNrLXNyYz0iaHR0cHM6Ly9naXRodWIuZ2l0aHViYXNzZXRzLmNvbS9pbWFnZXMvaWNvbnMvZW1vamkvdW5pY29kZS8xZjE5Mi5wbmciPvCfhpI8L2ctZW1vamk+IHRoaW5nIHRvIG1vbml0b3IuPC9wPgo= + http_version: null + recorded_at: Mon, 04 May 2020 09:31:21 GMT +recorded_with: VCR 5.1.0 diff --git a/spec/support/fixtures/vcr_cassettes/github_issue_api.yml b/spec/support/fixtures/vcr_cassettes/github_issue_api.yml deleted file mode 100644 index 825aa5698..000000000 --- a/spec/support/fixtures/vcr_cassettes/github_issue_api.yml +++ /dev/null @@ -1,180 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://api.github.com/repos/thepracticaldev/dev.to/issues/510 - body: - encoding: US-ASCII - string: '' - headers: - Accept: - - application/vnd.github.v3+json - User-Agent: - - Octokit Ruby Gem 4.12.0 - Content-Type: - - application/json - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - response: - status: - code: 200 - message: OK - headers: - Server: - - GitHub.com - Date: - - Tue, 25 Sep 2018 19:42:19 GMT - Content-Type: - - application/json; charset=utf-8 - Transfer-Encoding: - - chunked - Status: - - 200 OK - X-Ratelimit-Limit: - - '5000' - X-Ratelimit-Remaining: - - '4993' - X-Ratelimit-Reset: - - '1537907261' - Cache-Control: - - private, max-age=60, s-maxage=60 - Vary: - - Accept, Authorization, Cookie, X-GitHub-OTP - Etag: - - W/"cf563de9cf5fd804d0232405d071b71a" - Last-Modified: - - Tue, 04 Sep 2018 21:09:29 GMT - X-Oauth-Scopes: - - public_repo, repo:status - X-Accepted-Oauth-Scopes: - - repo - X-Github-Media-Type: - - github.v3; format=json - Access-Control-Expose-Headers: - - ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, - X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval - Access-Control-Allow-Origin: - - "*" - Strict-Transport-Security: - - max-age=31536000; includeSubdomains; preload - X-Frame-Options: - - deny - X-Content-Type-Options: - - nosniff - X-Xss-Protection: - - 1; mode=block - Referrer-Policy: - - origin-when-cross-origin, strict-origin-when-cross-origin - Content-Security-Policy: - - default-src 'none' - X-Runtime-Rack: - - '0.072510' - X-Github-Request-Id: - - DEF9:675C:3CE1:80FA:5BAA8F9B - body: - encoding: ASCII-8BIT - string: '{"url":"https://api.github.com/repos/thepracticaldev/dev.to/issues/510","repository_url":"https://api.github.com/repos/thepracticaldev/dev.to","labels_url":"https://api.github.com/repos/thepracticaldev/dev.to/issues/510/labels{/name}","comments_url":"https://api.github.com/repos/thepracticaldev/dev.to/issues/510/comments","events_url":"https://api.github.com/repos/thepracticaldev/dev.to/issues/510/events","html_url":"https://github.com/thepracticaldev/dev.to/issues/510","id":354483683,"node_id":"MDU6SXNzdWUzNTQ0ODM2ODM=","number":510,"title":"Add - to no follow to moderate links","user":{"login":"Zhao-Andy","id":17884966,"node_id":"MDQ6VXNlcjE3ODg0OTY2","avatar_url":"https://avatars0.githubusercontent.com/u/17884966?v=4","gravatar_id":"","url":"https://api.github.com/users/Zhao-Andy","html_url":"https://github.com/Zhao-Andy","followers_url":"https://api.github.com/users/Zhao-Andy/followers","following_url":"https://api.github.com/users/Zhao-Andy/following{/other_user}","gists_url":"https://api.github.com/users/Zhao-Andy/gists{/gist_id}","starred_url":"https://api.github.com/users/Zhao-Andy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Zhao-Andy/subscriptions","organizations_url":"https://api.github.com/users/Zhao-Andy/orgs","repos_url":"https://api.github.com/users/Zhao-Andy/repos","events_url":"https://api.github.com/users/Zhao-Andy/events{/privacy}","received_events_url":"https://api.github.com/users/Zhao-Andy/received_events","type":"User","site_admin":false},"labels":[{"id":1018588332,"node_id":"MDU6TGFiZWwxMDE4NTg4MzMy","url":"https://api.github.com/repos/thepracticaldev/dev.to/labels/approved","name":"approved","color":"A3FFBD","default":false},{"id":480869965,"node_id":"MDU6TGFiZWw0ODA4Njk5NjU=","url":"https://api.github.com/repos/thepracticaldev/dev.to/labels/bug","name":"bug","color":"ff050d","default":true},{"id":669356684,"node_id":"MDU6TGFiZWw2NjkzNTY2ODQ=","url":"https://api.github.com/repos/thepracticaldev/dev.to/labels/good%20first%20issue","name":"good - first issue","color":"724ac9","default":true}],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-08-27T21:23:19Z","updated_at":"2018-09-04T21:09:29Z","closed_at":"2018-09-04T21:09:29Z","author_association":"COLLABORATOR","body":"@jessleenyc - commented on [Tue Jul 31 2018](https://github.com/thepracticaldev/dev.to_core/issues/638)\n\n# - _TASK or Feature_\n### Request or User Story\nGetting lots of crawlers clicking - on the hidden link that''s giving them 404 errors and jamming up honeybadger!\n### - Definition of Done\n\n### Notes\n\n\n\n---\n\n@arun1595 commented on [Wed - Aug 01 2018](https://github.com/thepracticaldev/dev.to_core/issues/638#issuecomment-409774531)\n\n@jessleenyc - I would like to tackle this issue. Can you guide me with what \"moderate\" - links are?\r\n\r\nThanks.\n\n---\n\n@Zhao-Andy commented on [Mon Aug 27 2018](https://github.com/thepracticaldev/dev.to_core/issues/638#issuecomment-416373282)\n\nHey - @arun1595, we''re going to move this to the public repo and continue the conversation - there.\n\n","closed_by":{"login":"benhalpern","id":3102842,"node_id":"MDQ6VXNlcjMxMDI4NDI=","avatar_url":"https://avatars0.githubusercontent.com/u/3102842?v=4","gravatar_id":"","url":"https://api.github.com/users/benhalpern","html_url":"https://github.com/benhalpern","followers_url":"https://api.github.com/users/benhalpern/followers","following_url":"https://api.github.com/users/benhalpern/following{/other_user}","gists_url":"https://api.github.com/users/benhalpern/gists{/gist_id}","starred_url":"https://api.github.com/users/benhalpern/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/benhalpern/subscriptions","organizations_url":"https://api.github.com/users/benhalpern/orgs","repos_url":"https://api.github.com/users/benhalpern/repos","events_url":"https://api.github.com/users/benhalpern/events{/privacy}","received_events_url":"https://api.github.com/users/benhalpern/received_events","type":"User","site_admin":false}}' - http_version: - recorded_at: Tue, 25 Sep 2018 19:42:19 GMT -- request: - method: post - uri: https://api.github.com/markdown - body: - encoding: UTF-8 - string: '{"text":"@jessleenyc commented on [Tue Jul 31 2018](https://github.com/thepracticaldev/dev.to_core/issues/638)\n\n# - _TASK or Feature_\n### Request or User Story\nGetting lots of crawlers clicking - on the hidden link that''s giving them 404 errors and jamming up honeybadger!\n### - Definition of Done\n\n### Notes\n\n\n\n---\n\n@arun1595 commented on [Wed - Aug 01 2018](https://github.com/thepracticaldev/dev.to_core/issues/638#issuecomment-409774531)\n\n@jessleenyc - I would like to tackle this issue. Can you guide me with what \"moderate\" - links are?\r\n\r\nThanks.\n\n---\n\n@Zhao-Andy commented on [Mon Aug 27 2018](https://github.com/thepracticaldev/dev.to_core/issues/638#issuecomment-416373282)\n\nHey - @arun1595, we''re going to move this to the public repo and continue the conversation - there.\n\n"}' - headers: - Accept: - - application/vnd.github.raw - User-Agent: - - Octokit Ruby Gem 4.12.0 - Content-Type: - - application/json - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - response: - status: - code: 200 - message: OK - headers: - Server: - - GitHub.com - Date: - - Tue, 25 Sep 2018 19:42:19 GMT - Content-Type: - - text/html;charset=utf-8 - Transfer-Encoding: - - chunked - Status: - - 200 OK - X-Ratelimit-Limit: - - '5000' - X-Ratelimit-Remaining: - - '4992' - X-Ratelimit-Reset: - - '1537907261' - X-Commonmarker-Version: - - 0.17.11 - Access-Control-Expose-Headers: - - ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, - X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval - Access-Control-Allow-Origin: - - "*" - Strict-Transport-Security: - - max-age=31536000; includeSubdomains; preload - X-Frame-Options: - - deny - X-Content-Type-Options: - - nosniff - X-Xss-Protection: - - 1; mode=block - Referrer-Policy: - - origin-when-cross-origin, strict-origin-when-cross-origin - Content-Security-Policy: - - default-src 'none' - X-Runtime-Rack: - - '0.033681' - X-Github-Request-Id: - - DEFA:675C:3CF7:8124:5BAA8F9B - body: - encoding: ASCII-8BIT - string: | -

@jessleenyc commented on Tue Jul 31 2018

-

- TASK or Feature -

-

- Request or User Story

-

Getting lots of crawlers clicking on the hidden link that's giving them 404 errors and jamming up honeybadger!

-

- Definition of Done

-

- Notes

-
-

@arun1595 commented on Wed Aug 01 2018

-

@jessleenyc I would like to tackle this issue. Can you guide me with what "moderate" links are?

-

Thanks.

-
-

@Zhao-Andy commented on Mon Aug 27 2018

-

Hey @arun1595, we're going to move this to the public repo and continue the conversation there.

- http_version: - recorded_at: Tue, 25 Sep 2018 19:42:19 GMT -recorded_with: VCR 4.0.0