2021-04-29 21:17:54 +05:30
#!/usr/bin/env ruby
# frozen_string_literal: true
####
# Prints a report which helps reconcile occurrences of the `QueryLimiting.disable(ISSUE_LINK)`
# allowlist block against the corresponding open issues.
#
# If everything is consistent, the script should ideally not report any issues or code lines,
# other than possibly remaining "calls with no issue iid" which use variables/etc.
#
# - See https://gitlab.com/gitlab-org/gitlab/-/issues/325640
# - See https://gitlab.com/groups/gitlab-org/-/epics/5670
require 'rubygems'
require 'gitlab'
require 'optparse'
class QueryLimitingReport
GITLAB_PROJECT_ID = 278964 # gitlab-org/gitlab project
ISSUES_SEARCH_LABEL = 'querylimiting-disable'
CODE_LINES_SEARCH_STRING = 'QueryLimiting.disable'
PAGINATION_LIMIT = 500
DEFAULT_OPTIONS = {
api_token : ENV [ 'API_TOKEN' ]
} . freeze
def initialize ( options )
@options = options
Gitlab . configure do | config |
config . endpoint = 'https://gitlab.com/api/v4'
config . private_token = options . fetch ( :api_token )
end
end
def execute
# PLAN:
# Read all issues matching criteria and extract array of issue iids
# Find all code references and extract issue iids
# Print list of all issues without code references
# Print list of all code references issue iids that don't have search label
# Print list of all code references with no issue iids (i.e. dynamic or variable argument)
total_issues = find_issues_by_label ( ISSUES_SEARCH_LABEL )
issues = total_issues . select { | issue | issue [ :state ] == 'opened' }
code_lines = find_code_lines
code_lines_grouped = code_lines . group_by { | code_line | code_line [ :has_issue_iid ] }
code_lines_without_issue_iid = code_lines_grouped [ false ]
code_lines_with_issue_iid = code_lines_grouped [ true ]
all_issue_iids_in_code_lines = code_lines_with_issue_iid . map { | line | line [ :issue_iid ] }
issues_without_code_references = issues . reject do | issue |
all_issue_iids_in_code_lines . include? ( issue [ :iid ] )
end
all_issue_iids = issues . map { | issue | issue [ :iid ] }
code_lines_with_missing_issues = code_lines_with_issue_iid . reject do | code_line |
all_issue_iids . include? ( code_line [ :issue_iid ] )
end
puts " \n \n \n REPORT: "
puts " \n \n Found #{ total_issues . length } total issues with ' #{ ISSUES_SEARCH_LABEL } ' search label, #{ issues . length } are still opened... "
puts " \n \n Found #{ code_lines . length } total occurrences of ' #{ CODE_LINES_SEARCH_STRING } ' in code... "
puts " \n " + '-' * 80
puts " \n \n Issues without any ' #{ CODE_LINES_SEARCH_STRING } ' code references ( #{ issues_without_code_references . length } total): "
pp issues_without_code_references
puts " \n " + '-' * 80
puts " \n \n ' #{ CODE_LINES_SEARCH_STRING } ' calls with references to an issue which doesn't have ' #{ ISSUES_SEARCH_LABEL } ' search label ( #{ code_lines_with_missing_issues . length } total): "
pp code_lines_with_missing_issues
puts " \n " + '-' * 80
puts " \n \n ' #{ CODE_LINES_SEARCH_STRING } ' calls with no issue iid ( #{ code_lines_without_issue_iid & . length || 0 } total): "
pp code_lines_without_issue_iid
end
private
attr_reader :options
def find_issues_by_label ( label )
issues = [ ]
puts ( " Finding issues by label #{ label } ... " )
paginated_issues = Gitlab . issues ( GITLAB_PROJECT_ID , 'labels' = > label )
paginated_issues . paginate_with_limit ( PAGINATION_LIMIT ) do | item |
item_hash = item . to_hash
issue_iid = item_hash . fetch ( 'iid' )
issue = {
iid : issue_iid ,
state : item_hash . fetch ( 'state' ) ,
title : item_hash . fetch ( 'title' ) ,
issue_url : " https://gitlab.com/gitlab-org/gitlab/issues/ #{ issue_iid } "
}
issues << issue
end
issues
end
def find_code_lines
code_lines = [ ]
puts ( " Finding code lines... " )
paginated_blobs = Gitlab . search_in_project ( GITLAB_PROJECT_ID , 'blobs' , CODE_LINES_SEARCH_STRING )
paginated_blobs . paginate_with_limit ( PAGINATION_LIMIT ) do | item |
item_hash = item . to_hash
filename = item_hash . fetch ( 'filename' )
next if filename !~ / \ .rb \ Z /
file_contents = Gitlab . file_contents ( GITLAB_PROJECT_ID , filename )
file_lines = file_contents . split ( " \n " )
file_lines . each_index do | index |
line = file_lines [ index ]
2022-08-27 11:52:29 +05:30
if line =~ / #{ CODE_LINES_SEARCH_STRING } /o
2021-04-29 21:17:54 +05:30
issue_iid = line . slice ( %r{ issues/( \ d+) \ D } , 1 )
line_number = index + 1
code_line = {
file_location : " #{ filename } : #{ line_number } " ,
filename : filename ,
line_number : line_number ,
line : line ,
issue_iid : issue_iid . to_i ,
has_issue_iid : ! issue_iid . nil?
}
code_lines << code_line
end
end
end
code_lines . sort_by! { | line | " #{ line [ :filename ] } - #{ line [ :line_number ] . to_s . rjust ( 4 , '0' ) } " }
code_lines . map do | line |
line . delete ( :filename )
line . delete ( :line_number )
line
end
end
end
if $0 == __FILE__
options = QueryLimitingReport :: DEFAULT_OPTIONS . dup
OptionParser . new do | opts |
opts . on ( " -t " , " --api-token API_TOKEN " , String , " A value API token with the `read_api` scope. Can be set as an env variable 'API_TOKEN'. " ) do | value |
options [ :api_token ] = value
end
opts . on ( " -h " , " --help " , " Prints this help " ) do
puts opts
exit
end
end . parse!
QueryLimitingReport . new ( options ) . execute
end