require 'active_support/all' require_relative 'expand_condition' require_relative 'filters/date_conditions_filter' require_relative 'filters/votes_conditions_filter' require_relative 'filters/forbidden_labels_conditions_filter' require_relative 'filters/no_additional_labels_conditions_filter' require_relative 'filters/author_member_conditions_filter' require_relative 'filters/assignee_member_conditions_filter' require_relative 'filters/ruby_conditions_filter' require_relative 'limiters/date_field_limiter' require_relative 'action' require_relative 'policies/rule_policy' require_relative 'policies/summary_policy' require_relative 'api_query_builders/single_query_param_builder' require_relative 'api_query_builders/multi_query_param_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_class: Gitlab::Triage::NetworkAdapters::HttpartyAdapter) @host_url = policies.delete(:host_url) { options.host_url } @api_version = policies.delete(:api_version) { 'v4' } @per_page = policies.delete(:per_page) { 100 } @policies = policies @options = options @network_adapter_class = network_adapter_class 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 puts Gitlab::Triage::UI.header("Triaging the `#{options.project_id}` project", char: '=') puts resource_rules.each do |resource_type, resource| puts Gitlab::Triage::UI.header("Processing rules for #{resource_type}", char: '-') puts process_summaries(resource_type, resource[:summaries]) process_rules(resource_type, resource[:rules]) 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, options) end def net @net ||= { host_url: host_url, api_version: api_version, token: options.token, source_id: options.project_id, debug: options.debug, network: network } end def network_adapter @network_adapter ||= @network_adapter_class.new(options) end def rule_conditions(rule) rule.fetch(:conditions) { {} } end def rule_limits(rule) rule.fetch(:limits) { {} } end def process_summaries(resource_type, summaries) return if summaries.blank? summaries.each do |summary| process_summary(resource_type, summary) end end def process_rules(resource_type, rules) return if rules.blank? rules.each do |rule| process_action(Policies::RulePolicy.new(resource_type, rule, resources_for_rule(resource_type, rule), net)) end end def process_summary(resource_type, summary) puts Gitlab::Triage::UI.header("Processing summary: **#{summary[:name]}**", char: '~') puts resources = resources_for_rules(resource_type, summary[:rules]) # { summary_rule => resources } summary_parts = Hash[summary[:rules].zip(resources)] process_action(Policies::SummaryPolicy.new(resource_type, summary, summary_parts, net)) end def resources_for_rules(resource_type, rules) rules.map { |rule| resources_for_rule(resource_type, rule) } end def resources_for_rule(resource_type, rule) puts Gitlab::Triage::UI.header("Processing rule: **#{rule[:name]}**", char: '-') resources = [] ExpandCondition.perform(rule_conditions(rule)) do |conditions| # 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, conditions)) puts "\n\n* Found #{resources.count} resources..." print "* Filtering resources..." resources = filter_resources(resources, conditions) puts "\n* Total after filtering: #{resources.count} resources" print "* Limiting resources..." resources = limit_resources(resources, rule_limits(rule)) puts "\n* Total after limiting: #{resources.count} resources" puts end resources end def process_action(policy) Action.process( policy: policy, net: net, dry: options.dry_run) puts end def filter_resources(resources, conditions) resources.select do |resource| results = [] results << Filters::DateConditionsFilter.new(resource, conditions[:date]).calculate if conditions[:date] results << Filters::VotesConditionsFilter.new(resource, conditions[:upvotes]).calculate if conditions[:upvotes] results << Filters::ForbiddenLabelsConditionsFilter.new(resource, conditions[:forbidden_labels]).calculate if conditions[:forbidden_labels] results << Filters::NoAdditionalLabelsConditionsFilter.new(resource, conditions.fetch(:labels) { [] }).calculate if conditions[:no_additional_labels] results << Filters::AuthorMemberConditionsFilter.new(resource, conditions[:author_member], net).calculate if conditions[:author_member] results << Filters::AssigneeMemberConditionsFilter.new(resource, conditions[:assignee_member], net).calculate if conditions[:assignee_member] results << Filters::RubyConditionsFilter.new(resource, conditions, net).calculate if conditions[:ruby] results.all? end end def limit_resources(resources, limits) if limits.empty? resources else Limiters::DateFieldLimiter.new(resources, limits).limit end 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 << APIQueryBuilders::MultiQueryParamBuilder.new('labels', conditions[:labels], ',') if conditions[:labels] condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('state', conditions[:state]) if conditions[:state] condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('milestone', Array(conditions[:milestone])[0]) if conditions[:milestone] condition_builders.each do |condition_builder| params[condition_builder.param_name] = condition_builder.param_content end get_url = UrlBuilders::UrlBuilder.new( host_url: host_url, api_version: api_version, source_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 end end end