whatsup_github 0.1.1 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,14 +1,16 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
  require 'cucumber'
4
6
  require 'cucumber/rake/task'
5
7
 
6
8
  RSpec::Core::RakeTask.new(:spec)
7
9
 
8
- task :default => :spec
10
+ task default: :spec
9
11
 
10
12
  Cucumber::Rake::Task.new(:features) do |t|
11
- t.cucumber_opts = "--format pretty" # Any valid command line option can go here.
13
+ t.cucumber_opts = '--format pretty' # Any valid command line option can go here.
12
14
  end
13
15
 
14
- task :test => [:spec, :features]
16
+ task test: %i[spec features]
data/bin/console CHANGED
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "whatsup_github"
4
+ require 'bundler/setup'
5
+ require 'whatsup_github'
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
7
8
  # with your gem easier. You can also use a different console, if you like.
8
9
 
9
10
  # (If you use this, don't forget to add pry to your Gemfile!)
10
- require "pry"
11
+ require 'pry'
11
12
  Pry.start
12
13
 
13
14
  # require "irb"
data/exe/whatsup_github CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'whatsup_github/cli'
4
5
  WhatsupGithub::CLI.start(ARGV)
@@ -1,16 +1,28 @@
1
1
  # Parameters for a GitHub search query
2
2
  base_branch: master
3
3
 
4
+ # The list of repositories to scan for pull requests
4
5
  repos:
5
6
  - magento/devdocs
7
+ - magento-commerce/devdocs
6
8
 
7
9
  # Labels also will be used as a 'type' value in the output file
8
10
  labels:
9
- - New Topic
10
- - Major Update
11
- - Technical
11
+ required:
12
+ - New Topic
13
+ - Major Update
14
+ optional:
15
+ - Technical
12
16
 
13
17
  # Format of output file
14
18
  output_format:
15
19
  - yaml
16
20
  # - markdown
21
+
22
+ # The phrase that is used as a separator in the pull request descripion.
23
+ # All the lines that follows this phrase are captured as 'description' for this PR's entry in the resulted data file.
24
+ magic_word: whatsnew
25
+
26
+ # An organization to check a contributor for membership.
27
+ # Values: 'true', 'false', empry if not configured.
28
+ membership: magento-commerce
@@ -1,6 +1,6 @@
1
- require "whatsup_github/version"
1
+ # frozen_string_literal: true
2
2
 
3
+ require 'whatsup_github/version'
3
4
 
4
5
  module WhatsupGithub
5
-
6
6
  end
@@ -1,14 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
- require "whatsup_github/runner"
4
+ require 'whatsup_github/runner'
3
5
  module WhatsupGithub
4
6
  class CLI < Thor
5
- desc "since DATE", "Filters pull requests since the specified date till now."
7
+ desc 'since DATE', 'Filters pull requests since the specified date till now.'
6
8
 
7
9
  def since(date = Date.today - 7)
8
-
9
10
  runner = WhatsupGithub::Runner.new(Date.parse(date.to_s))
10
11
  runner.run
11
12
  end
12
13
  default_task :since
13
14
  end
14
- end
15
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'singleton'
5
+
6
+ module WhatsupGithub
7
+ # Creates readable objects from confirurarion files
8
+ class Config
9
+ attr_reader :config
10
+
11
+ include Singleton
12
+
13
+ def initialize
14
+ @file = '.whatsup.yml'
15
+ @config = {}
16
+ end
17
+
18
+ def read
19
+ unless File.exist?(@file)
20
+ dist_file = File.expand_path("../template/#{@file}", __dir__)
21
+ FileUtils.cp dist_file, @file
22
+ end
23
+ @config = YAML.load_file @file
24
+ return {} unless @config
25
+
26
+ @config
27
+ end
28
+
29
+ def repos
30
+ read['repos']
31
+ end
32
+
33
+ def base_branch
34
+ read['base_branch']
35
+ end
36
+
37
+ def output_format
38
+ read['output_format']
39
+ end
40
+
41
+ def labels
42
+ required_labels + optional_labels
43
+ end
44
+
45
+ def required_labels
46
+ res = read.dig 'labels', 'required'
47
+ return [] unless res
48
+
49
+ res
50
+ end
51
+
52
+ def optional_labels
53
+ res = read.dig 'labels', 'optional'
54
+ return [] unless res
55
+
56
+ res
57
+ end
58
+
59
+ def membership
60
+ read['membership']
61
+ end
62
+
63
+ def magic_word
64
+ read['magic_word']
65
+ end
66
+ end
67
+ end
68
+
69
+ if $PROGRAM_NAME == __FILE__
70
+ config = WhatsupGithub::Config.instance
71
+ p config.repos
72
+ p config.base_branch
73
+ p config.output_format
74
+ p config.labels
75
+ p config.required_labels
76
+ p config.optional_labels
77
+ p config.magic_word
78
+ p config.membership
79
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WhatsupGithub
2
4
  # Creates the final table
3
5
  class Generator
@@ -9,7 +11,7 @@ module WhatsupGithub
9
11
  @collector.sort_by_date
10
12
  end
11
13
 
12
- def run formatter, content
14
+ def run(formatter, content)
13
15
  formatter.generate_output_from content
14
16
  end
15
17
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatsupGithub
4
+ # Members of an organization
5
+ class Members
6
+ def initialize(org)
7
+ @org = org
8
+ end
9
+
10
+ def client
11
+ Octokit::Client.new(netrc: true)
12
+ end
13
+
14
+ def members
15
+ client.org_members @org
16
+ end
17
+ end
18
+ end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'octokit'
2
- require_relative 'config-reader'
4
+ require_relative 'config_reader'
3
5
 
4
6
  module WhatsupGithub
5
- # Gets pull filtered pull requests from GitHub
7
+ # Gets issues found on GitHub by query
6
8
  class Pulls
7
9
  attr_reader :since, :repo
8
10
 
@@ -11,43 +13,94 @@ module WhatsupGithub
11
13
  @since = args[:since]
12
14
  end
13
15
 
14
- def filtered
15
- issues = []
16
- labels.each do |label|
17
- issues += search_issues(label).items
16
+ def data
17
+ pull_requests = []
18
+ filtered_numbers.each do |number|
19
+ pull_requests << client.pull_request(@repo, number)
18
20
  end
19
- issues
21
+ pull_requests
20
22
  end
21
23
 
22
24
  private
23
25
 
24
26
  # def access_token
25
- # credentials.read 'github_token'
27
+ # credentials.dig 'github_token'
26
28
  # end
27
29
 
28
30
  def configuration
29
31
  Config.instance
30
32
  end
31
33
 
32
- def labels
33
- configuration.read 'labels'
34
+ def optional_labels
35
+ configuration.optional_labels
36
+ end
37
+
38
+ def required_labels
39
+ configuration.required_labels
40
+ end
41
+
42
+ def magic_word
43
+ configuration.magic_word
34
44
  end
35
45
 
36
46
  def base_branch
37
- configuration.read 'base_branch'
47
+ configuration.base_branch
38
48
  end
39
49
 
50
+ # Authorize with a GitHub token from $WHATSUP_GITHUB_ACCESS_TOKEN if available
51
+ # Otherwise, use credentials from ~/.netrc
52
+ # Otherwise, continue as a Guest
40
53
  def client
41
- Octokit::Client.new(:netrc => true)
54
+ return @client if @client
55
+
56
+ @client =
57
+ if ENV['WHATSUP_GITHUB_ACCESS_TOKEN']
58
+ Octokit::Client.new(access_token: ENV['WHATSUP_GITHUB_ACCESS_TOKEN'])
59
+ elsif File.exist? "#{ENV['HOME']}/.netrc"
60
+ Octokit::Client.new(netrc: true)
61
+ else
62
+ Octokit::Client.new
63
+ end
42
64
  end
43
65
 
44
66
  def search_issues(label)
45
67
  auto_paginate
46
- client.search_issues("repo:#{repo} label:\"#{label}\" merged:>=#{since} base:#{base_branch}")
68
+ query = "repo:#{repo} label:\"#{label}\" merged:>=#{since} base:#{base_branch}"
69
+ puts "Searching on GitHub by query #{query}"
70
+ client.search_issues(query)
71
+ end
72
+
73
+ def search_issues_with_magic_word(label)
74
+ auto_paginate
75
+ query = "repo:#{repo} label:\"#{label}\" merged:>=#{since} base:#{base_branch} \"#{magic_word}\" in:body"
76
+ puts "Searching on GitHub by query #{query}"
77
+ client.search_issues(query)
47
78
  end
48
79
 
49
80
  def auto_paginate
50
81
  Octokit.auto_paginate = true
51
82
  end
83
+
84
+ def filtered_issues
85
+ issues = []
86
+ required_labels.each do |label|
87
+ issues += search_issues(label).items
88
+ end
89
+ optional_labels.each do |label|
90
+ issues += search_issues_with_magic_word(label).items
91
+ end
92
+ issues
93
+ end
94
+
95
+ def filtered_numbers
96
+ filtered_issues.map(&:number)
97
+ end
52
98
  end
53
99
  end
100
+
101
+ if $PROGRAM_NAME == __FILE__
102
+ require 'date'
103
+ two_weeks_ago = (Date.today - 14).to_s
104
+ pulls = WhatsupGithub::Pulls.new(repo: 'magento/devdocs', since: two_weeks_ago)
105
+ p pulls.data
106
+ end
@@ -1,21 +1,48 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WhatsupGithub
2
4
  # Row to be converted to entry in future table
3
5
  class Row
4
- attr_reader :body, :title, :labels, :pr_number, :assignee, :link
6
+ attr_reader :body,
7
+ :title,
8
+ :labels,
9
+ :link,
10
+ :pr_number,
11
+ :assignee,
12
+ :author,
13
+ :author_url,
14
+ :merge_commit,
15
+ :is_private,
16
+ :membership
17
+
5
18
  def initialize(args)
6
19
  @repo = args[:repo]
20
+ @repo_url = args[:repo_url]
21
+ @is_private = args[:private]
7
22
  @title = args[:pr_title]
8
23
  @body = args[:pr_body]
9
24
  @date = args[:date]
10
25
  @labels = args[:pr_labels]
11
26
  @assignee = args[:assignee]
27
+ @author = args[:author]
28
+ @author_url = args[:author_url]
12
29
  @pr_number = args[:pr_number]
13
30
  @link = args[:pr_url]
31
+ @merge_commit = args[:merge_commit_sha]
32
+ @membership = args[:membership]
14
33
  @config = Config.instance
15
34
  end
16
35
 
17
36
  def labels_from_config
18
- @config.read('labels')
37
+ @config.labels
38
+ end
39
+
40
+ def required_labels
41
+ @config.required_labels
42
+ end
43
+
44
+ def magic_word
45
+ @config.magic_word
19
46
  end
20
47
 
21
48
  def versions
@@ -32,8 +59,10 @@ module WhatsupGithub
32
59
  end
33
60
 
34
61
  def parse_body
35
- whatsnew_splited = body.split('whatsnew')[-1]
36
- newline_splited = whatsnew_splited.split("\n")
62
+ # Split PR description in two parts by 'magic word', and take the second half
63
+ description_splited = body.split(magic_word)[-1]
64
+ # Convert new line separators to <br> tags
65
+ newline_splited = description_splited.split("\n")
37
66
  cleaned_array = newline_splited.map { |e| e.delete "\r\*" }
38
67
  cleaned_array.delete('')
39
68
  striped_array = cleaned_array.map(&:strip)
@@ -41,10 +70,12 @@ module WhatsupGithub
41
70
  end
42
71
 
43
72
  def description
44
- if body.include?('whatsnew')
73
+ # If a PR body includes a phrase 'whatsnew', then parse the body.
74
+ # If there are at least one required label but PR body does not include what's new, warn about missing 'whatsnew'
75
+ if body.include?(magic_word)
45
76
  parse_body
46
77
  else
47
- message = "MISSING whatsnew in the #{type} PR \##{pr_number}: \"#{title}\" assigned to #{assignee} (#{link})"
78
+ message = "MISSING #{magic_word} in the #{type} PR \##{pr_number}: \"#{title}\" assigned to #{assignee} (#{link})"
48
79
  puts message
49
80
  message
50
81
  end
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'row'
2
4
  require_relative 'pulls'
3
- require_relative 'config-reader'
5
+ require_relative 'config_reader'
6
+ require_relative 'members'
4
7
 
5
8
  module WhatsupGithub
6
9
  # Creates Row objects for the future table
@@ -8,7 +11,7 @@ module WhatsupGithub
8
11
  attr_reader :repos, :since
9
12
 
10
13
  def initialize(args = {})
11
- @repos = config.read('repos')
14
+ @repos = config.repos
12
15
  @since = args[:since]
13
16
  end
14
17
 
@@ -24,12 +27,19 @@ module WhatsupGithub
24
27
  pulls(repo).map do |pull|
25
28
  Row.new(
26
29
  repo: repo,
30
+ repo_url: pull.base.repo.html_url,
31
+ private: pull.base.repo.private?,
27
32
  pr_number: pull.number,
28
33
  pr_title: pull.title,
29
34
  pr_body: pull.body,
30
- date: pull.closed_at,
35
+ date: pull.merged_at,
31
36
  pr_labels: label_names(pull.labels),
32
- assignee: assignee(pull.assignee),
37
+ assignee: assignee(pull.assignees),
38
+ membership: member?(pull.user.login),
39
+ merger: pull.merged_by.login,
40
+ merge_commit_sha: pull.merge_commit_sha,
41
+ author: pull.user.login,
42
+ author_url: pull.user.html_url,
33
43
  pr_url: pull.html_url
34
44
  )
35
45
  end
@@ -47,20 +57,37 @@ module WhatsupGithub
47
57
 
48
58
  private
49
59
 
50
- def assignee(assignee)
51
- if assignee.nil?
60
+ def assignee(assignees)
61
+ if assignees.empty?
52
62
  'NOBODY'
53
63
  else
54
- assignee.login
64
+ assignees.map(&:login).join(', ')
55
65
  end
56
66
  end
57
67
 
68
+ def member?(login)
69
+ return nil unless config.membership
70
+
71
+ member_logins.include? login
72
+ end
73
+
58
74
  def label_names(labels)
59
75
  labels.map(&:name)
60
76
  end
61
77
 
62
78
  def pulls(repo)
63
- Pulls.new(repo: repo, since: since).filtered
79
+ Pulls.new(repo: repo, since: since).data
80
+ end
81
+
82
+ def load_members
83
+ return if @members
84
+
85
+ @members = Members.new(config.membership).members
86
+ end
87
+
88
+ def member_logins
89
+ load_members
90
+ @members.map(&:login)
64
91
  end
65
92
 
66
93
  def config