whatsup_github 0.0.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile CHANGED
@@ -1,6 +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'
5
+ require 'cucumber'
6
+ require 'cucumber/rake/task'
3
7
 
4
8
  RSpec::Core::RakeTask.new(:spec)
5
9
 
6
- task :default => :spec
10
+ task default: :spec
11
+
12
+ Cucumber::Rake::Task.new(:features) do |t|
13
+ t.cucumber_opts = '--format pretty' # Any valid command line option can go here.
14
+ end
15
+
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,18 +1,28 @@
1
- # Name of the base branch in GitHub PRs
1
+ # Parameters for a GitHub search query
2
2
  base_branch: master
3
3
 
4
- # Repositories for scanning. If you want to search private repositories, use credentials to specify
4
+ # The list of repositories to scan for pull requests
5
5
  repos:
6
6
  - magento/devdocs
7
+ - magento-commerce/devdocs
7
8
 
8
- # Labels for filtering (currently, hard-coded)
9
+ # Labels also will be used as a 'type' value in the output file
9
10
  labels:
10
- - New doc
11
- - Major update
12
- - Technical
11
+ required:
12
+ - New Topic
13
+ - Major Update
14
+ optional:
15
+ - Technical
13
16
 
14
- # Output format
17
+ # Format of output file
15
18
  output_format:
16
- # - markdown
17
19
  - yaml
18
-
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,82 @@ 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
 
40
50
  def client
41
- Octokit::Client.new(:netrc => true)
51
+ Octokit::Client.new(netrc: true)
42
52
  end
43
53
 
44
54
  def search_issues(label)
45
55
  auto_paginate
46
- client.search_issues("repo:#{repo} label:\"#{label}\" merged:>=#{since} base:#{base_branch}")
56
+ query = "repo:#{repo} label:\"#{label}\" merged:>=#{since} base:#{base_branch}"
57
+ puts "Searching on GitHub by query #{query}"
58
+ client.search_issues(query)
59
+ end
60
+
61
+ def search_issues_with_magic_word(label)
62
+ auto_paginate
63
+ query = "repo:#{repo} label:\"#{label}\" merged:>=#{since} base:#{base_branch} \"#{magic_word}\" in:body"
64
+ puts "Searching on GitHub by query #{query}"
65
+ client.search_issues(query)
47
66
  end
48
67
 
49
68
  def auto_paginate
50
69
  Octokit.auto_paginate = true
51
70
  end
71
+
72
+ def filtered_issues
73
+ issues = []
74
+ required_labels.each do |label|
75
+ issues += search_issues(label).items
76
+ end
77
+ optional_labels.each do |label|
78
+ issues += search_issues_with_magic_word(label).items
79
+ end
80
+ issues
81
+ end
82
+
83
+ def filtered_numbers
84
+ filtered_issues.map(&:number)
85
+ end
52
86
  end
53
87
  end
88
+
89
+ if $PROGRAM_NAME == __FILE__
90
+ require 'date'
91
+ two_weeks_ago = (Date.today - 14).to_s
92
+ pulls = WhatsupGithub::Pulls.new(repo: 'magento/devdocs', since: two_weeks_ago)
93
+ p pulls.data
94
+ end
@@ -1,24 +1,49 @@
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]
33
+ @config = Config.instance
34
+ end
35
+
36
+ def labels_from_config
37
+ @config.labels
14
38
  end
15
39
 
16
- UPDATED_MASK = 'Major update'.freeze
17
- UPDATED_PHRASE = 'Major update'.freeze
18
- NEW_MASK = 'New topic'.freeze
19
- NEW_PHRASE = 'New topic'.freeze
20
- TECHNICAL_MASK = 'Technical'.freeze
21
- TECHNICAL_PHRASE = 'Technical changes'.freeze
40
+ def required_labels
41
+ @config.required_labels
42
+ end
43
+
44
+ def magic_word
45
+ @config.magic_word
46
+ end
22
47
 
23
48
  def versions
24
49
  label_versions = labels.select { |label| label.start_with?(/\d\./) }
@@ -30,21 +55,14 @@ module WhatsupGithub
30
55
  end
31
56
 
32
57
  def type
33
- labels_string = labels.join(' ')
34
- label_type = /#{ UPDATED_MASK }|#{ NEW_MASK }|#{ TECHNICAL_MASK }/.match(labels_string)
35
- case label_type.to_s
36
- when /#{ UPDATED_MASK }/
37
- UPDATED_PHRASE
38
- when /#{ NEW_MASK }/
39
- NEW_PHRASE
40
- when /#{ TECHNICAL_MASK }/
41
- TECHNICAL_PHRASE
42
- end
58
+ (labels & labels_from_config).join(', ')
43
59
  end
44
60
 
45
61
  def parse_body
46
- whatsnew_splited = body.split('whatsnew')[-1]
47
- 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")
48
66
  cleaned_array = newline_splited.map { |e| e.delete "\r\*" }
49
67
  cleaned_array.delete('')
50
68
  striped_array = cleaned_array.map(&:strip)
@@ -52,10 +70,12 @@ module WhatsupGithub
52
70
  end
53
71
 
54
72
  def description
55
- 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)
56
76
  parse_body
57
77
  else
58
- 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})"
59
79
  puts message
60
80
  message
61
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,38 @@ 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
+ p 'Hello'
87
+ end
88
+
89
+ def member_logins
90
+ load_members
91
+ @members.map(&:login)
64
92
  end
65
93
 
66
94
  def config