# frozen_string_literal: true require './spec/support/sidekiq_middleware' require './spec/support/helpers/test_env' require 'active_support/testing/time_helpers' # Usage: # # Simple invocation always creates a new project: # # FILTER=cycle_analytics SEED_VSA=1 bundle exec rake db:seed_fu # # Create more issues/MRs: # # VSA_ISSUE_COUNT=100 FILTER=cycle_analytics SEED_VSA=1 bundle exec rake db:seed_fu # # Run for an existing project # # VSA_SEED_PROJECT_ID=10 FILTER=cycle_analytics SEED_VSA=1 bundle exec rake db:seed_fu # rubocop:disable Rails/Output class Gitlab::Seeder::CycleAnalytics # rubocop:disable Style/ClassAndModuleChildren include ActiveSupport::Testing::TimeHelpers attr_reader :project, :issues, :merge_requests, :developers FLAG = 'SEED_VSA' PERF_TEST = 'VSA_PERF_TEST' MAX_DURATIONS = { # in hours issue: 72, plan: 48, code: 72, test: 5, review: 72, deployment: 48, lead_time: 32 }.freeze def self.seeder_based_on_env(project) if ENV[FLAG] new(project: project) elsif ENV[PERF_TEST] new(project: project, perf: true) end end def initialize(project: nil, perf: false) @project = project || create_new_vsm_project @issue_count = perf ? 1000 : ENV.fetch('VSA_ISSUE_COUNT', 5).to_i @issues = [] @merge_requests = [] @developers = [] end def seed! unless project.repository_exists? puts puts 'WARNING' puts '=======' puts "Seeding #{self.class} is not possible because the given project " \ "(#{project.full_path}) doesn't have a repository." puts 'Try specifying a project with working repository or omit the VSA_SEED_PROJECT_ID parameter ' \ 'so the seed script will automatically create one.' puts return end create_developers! create_issues! seed_lead_time! seed_issue_stage! seed_plan_stage! seed_code_stage! seed_test_stage! seed_review_stage! seed_staging_stage! puts "Successfully seeded '#{project.full_path}' for Value Stream Management!" puts "URL: #{Rails.application.routes.url_helpers.project_url(project)}" end private def seed_issue_stage! issues.each do |issue| time = within_end_time(issue.created_at + rand(MAX_DURATIONS[:issue]).hours) if issue.id.even? issue.metrics.update!(first_associated_with_milestone_at: time) else issue.metrics.update!(first_added_to_board_at: time) end end end def seed_plan_stage! issues.each do |issue| plan_stage_start = issue.metrics.first_associated_with_milestone_at || issue.metrics.first_added_to_board_at first_mentioned_in_commit_at = within_end_time(plan_stage_start + rand(MAX_DURATIONS[:plan]).hours) issue.metrics.update!(first_mentioned_in_commit_at: first_mentioned_in_commit_at) end end def seed_code_stage! issues.each do |issue| merge_request = FactoryBot.create( :merge_request, target_project: project, source_project: project, source_branch: "#{issue.iid}-feature-branch", target_branch: 'master', author: developers.sample, created_at: within_end_time(issue.metrics.first_mentioned_in_commit_at + rand(MAX_DURATIONS[:code]).hours) ) @merge_requests << merge_request MergeRequestsClosingIssues.create!(issue: issue, merge_request: merge_request) end end def seed_test_stage! merge_requests.each do |merge_request| pipeline = FactoryBot.create(:ci_pipeline, :success, project: project, partition_id: Ci::Pipeline.current_partition_value) build = FactoryBot.create(:ci_build, pipeline: pipeline, project: project, user: developers.sample) # Required because seeds run in a transaction and these are now # created in an `after_commit` hook. merge_request.ensure_metrics! merge_request.metrics.update!( latest_build_started_at: merge_request.created_at, latest_build_finished_at: within_end_time(merge_request.created_at + MAX_DURATIONS[:test].hours), pipeline_id: build.commit_id ) end end def seed_review_stage! merge_requests.each do |merge_request| merge_request.metrics.update!( merged_at: within_end_time(merge_request.created_at + MAX_DURATIONS[:review].hours) ) end end def seed_staging_stage! merge_requests.each do |merge_request| first_deployed_to_production_at = merge_request.metrics.merged_at + MAX_DURATIONS[:deployment].hours merge_request.metrics.update!( first_deployed_to_production_at: within_end_time(first_deployed_to_production_at) ) end end def seed_lead_time! issues.each do |issue| created_at = issue.created_at - MAX_DURATIONS[:lead_time].hours issue.update!(created_at: created_at, closed_at: Time.now) end end def create_issues! @issue_count.times do travel_to(start_time + rand(5).days) do title = "#{FFaker::Product.brand}-#{suffix}" @issues << Issue.create!(project: project, title: title, author: developers.sample) end end end def create_developers! 5.times do |i| user = FactoryBot.create( :user, name: "VSM User#{i}", username: "vsm-user-#{i}-#{suffix}", email: "vsm-user-#{i}@#{suffix}.com" ) project.group&.add_developer(user) project.add_developer(user) @developers << user end AuthorizedProjectUpdate::ProjectRecalculateService.new(project).execute end def create_new_vsm_project namespace = FactoryBot.create( :group, name: "Value Stream Management Group #{suffix}", path: "vsmg-#{suffix}" ) project = FactoryBot.create( :project, :repository, name: "Value Stream Management Project #{suffix}", path: "vsmp-#{suffix}", creator: admin, namespace: namespace ) project.create_repository project end def admin @admin ||= User.admins.first end def suffix @suffix ||= Time.now.to_i end def start_time @start_time ||= 25.days.ago end def end_time @end_time ||= Time.now end def within_end_time(time) [time, end_time].min end end Gitlab::Seeder.quiet do project_id = ENV['VSA_SEED_PROJECT_ID'] project = Project.find(project_id) if project_id seeder = Gitlab::Seeder::CycleAnalytics.seeder_based_on_env(project) if seeder seeder.seed! else puts "Skipped. Use the `#{Gitlab::Seeder::CycleAnalytics::FLAG}` environment variable to enable." end end # rubocop:enable Rails/Output