require 'active_support/all' require_relative 'limiters/date_conditions_limiter' require_relative 'limiters/votes_conditions_limiter' require_relative 'command_builders/comment_command_builder' require_relative 'command_builders/label_command_builder' require_relative 'command_builders/cc_command_builder' require_relative 'command_builders/status_command_builder' require_relative 'filter_builders/single_filter_builder' require_relative 'filter_builders/multi_filter_builder' require_relative 'url_builders/url_builder' require_relative 'network' require_relative 'network_adapters/httparty_adapter' require_relative 'ui' module Gitlab module Triage class Engine attr_reader :host_url, :api_version, :per_page, :policies, :options def initialize(policies:, options:, network_adapter: Gitlab::Triage::NetworkAdapters::HttpartyAdapter.new) @host_url = policies.delete(:host_url) { 'https://gitlab.com' } @api_version = policies.delete(:api_version) { 'v4' } @per_page = policies.delete(:per_page) { 100 } @policies = policies @options = options @network_adapter = network_adapter options.dry_run = true if ENV['TEST'] == 'true' assert_project_id! assert_token! end def perform puts "Performing a dry run.\n\n" if options.dry_run resource_rules.each do |type, resource| puts Gitlab::Triage::UI.header("Processing rules for #{type}") puts resource[:rules].each do |rule| puts Gitlab::Triage::UI.header("Processing rule: #{rule[:name]}", char: '~') puts process_rule(type, rule) end end end private def assert_project_id! return if options.project_id raise ArgumentError, 'A project_id is needed (pass it with the `--project-id` option)!' end def assert_token! return if options.token raise ArgumentError, 'A token is needed (pass it with the `--token` option)!' end def resource_rules @resource_rules ||= policies.delete(:resource_rules) { {} } end def network @network ||= Network.new(@network_adapter) end def rule_conditions(rule) rule.fetch(:conditions) { {} } end def rule_actions(rule) rule.fetch(:actions) { {} } end def process_rule(resource_type, rule) # retrieving the resources for every rule is inefficient # however, previous rules may affect those upcoming resources = network.query_api(options.token, build_get_url(resource_type, rule_conditions(rule))) puts "\n\nFound #{resources.count} resources..." puts "Limiting resources..." resources = limit_resources(resources, rule_conditions(rule)) puts "Total resource after limiting: #{resources.count} resources" process_resources(resource_type, resources, rule) end def limit_resources(resources, conditions) resources.select do |resource| results = [] results << Limiters::DateConditionsLimiter.new(resource, conditions[:date]).calculate if conditions[:date] results << Limiters::VotesConditionsLimiter.new(resource, conditions[:upvotes]).calculate if conditions[:upvotes] !results.uniq.include?(false) end end def process_resources(resource_type, resources, rule) comment = build_comment(rule_actions(rule)) if options.dry_run && resources.any? puts "\nThe following comment would be posted for the rule '#{rule[:name]}':\n\n" puts ">>>\n#{comment}\n>>>" return end resources.each do |resource| network.post_api(options.token, build_post_url(resource_type, resource), comment) end end def build_comment(actions) CommandBuilders::CommentCommandBuilder.new( [ actions[:comment], CommandBuilders::LabelCommandBuilder.new(actions[:labels]).build_command, CommandBuilders::CcCommandBuilder.new(actions[:mention]).build_command, CommandBuilders::StatusCommandBuilder.new(actions[:status]).build_command ] ).build_command end def build_get_url(resource_type, conditions) # Example issues query with state and labels # https://gitlab.com/api/v4/projects/test-triage%2Fissue-project/issues?state=open&labels=project%20label%20with%20spaces,group_label_no_spaces params = { per_page: per_page } condition_builders = [] condition_builders << FilterBuilders::MultiFilterBuilder.new('labels', conditions[:labels], ',') if conditions[:labels] condition_builders << FilterBuilders::SingleFilterBuilder.new('state', conditions[:state]) if conditions[:state] condition_builders << FilterBuilders::MultiFilterBuilder.new('milestone', conditions[:milestone], ',') if conditions[:milestone] condition_builders.each do |condition_builder| params[condition_builder.filter_name] = condition_builder.filter_content end get_url = UrlBuilders::UrlBuilder.new( host_url: host_url, api_version: api_version, project_id: options.project_id, resource_type: resource_type, params: params ).build puts Gitlab::Triage::UI.debug "get_url: #{get_url}" if options.debug get_url end def build_post_url(resource_type, resource) # POST /projects/:id/issues/:issue_iid/notes post_url = UrlBuilders::UrlBuilder.new( host_url: host_url, api_version: api_version, project_id: options.project_id, resource_type: resource_type, resource_id: resource['iid'], sub_resource_type: 'notes' ).build puts Gitlab::Triage::UI.debug "post_url: #{post_url}" if options.debug post_url end end end end