Commit 46c261c3 authored by Ash McKenzie's avatar Ash McKenzie

Merge branch 'rework-vsm-seed-file' into 'master'

Rewrite VSM development fixtures

Closes #195829

See merge request gitlab-org/gitlab!27357
parents c9f452ad 20335291
# frozen_string_literal: true
require './spec/support/sidekiq_middleware' require './spec/support/sidekiq_middleware'
require './spec/support/helpers/test_env' require './spec/support/helpers/test_env'
# Usage:
#
# Simple invocation always creates a new project:
#
# FILTER=cycle_analytics SEED_CYCLE_ANALYTICS=1 bundle exec rake db:seed_fu
#
# Create more issues/MRs:
#
# CYCLE_ANALYTICS_ISSUE_COUNT=100 FILTER=cycle_analytics SEED_CYCLE_ANALYTICS=1 bundle exec rake db:seed_fu
#
# Run for an existing project
#
# CYCLE_ANALYTICS_SEED_PROJECT_ID=10 FILTER=cycle_analytics SEED_CYCLE_ANALYTICS=1 bundle exec rake db:seed_fu
class Gitlab::Seeder::CycleAnalytics class Gitlab::Seeder::CycleAnalytics
def initialize(project, perf: false) attr_reader :project, :issues, :merge_requests, :developers
@project = project
@user = User.admins.first
@issue_count = perf ? 1000 : ENV.fetch('CYCLE_ANALYTICS_ISSUE_COUNT', 5).to_i
end
def seed_metrics! FLAG = 'SEED_CYCLE_ANALYTICS'
@issue_count.times do |index| PERF_TEST = 'CYCLE_ANALYTICS_PERF_TEST'
# Issue
Timecop.travel 5.days.from_now
title = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}"
issue = Issue.create(project: @project, title: title, author: @user)
issue_metrics = issue.metrics
# Milestones / Labels ISSUE_STAGE_MAX_DURATION_IN_HOURS = 72
Timecop.travel 5.days.from_now PLAN_STAGE_MAX_DURATION_IN_HOURS = 48
CODE_STAGE_MAX_DURATION_IN_HOURS = 72
TEST_STAGE_MAX_DURATION_IN_HOURS = 5
REVIEW_STAGE_MAX_DURATION_IN_HOURS = 72
DEPLOYMENT_MAX_DURATION_IN_HOURS = 48
if index.even? def self.seeder_base_on_env(project)
issue_metrics.first_associated_with_milestone_at = rand(6..12).hours.from_now if ENV[FLAG]
else self.new(project: project)
issue_metrics.first_added_to_board_at = rand(6..12).hours.from_now elsif ENV[PERF_TEST]
self.new(project: project, perf: true)
end end
# Commit
Timecop.travel 5.days.from_now
issue_metrics.first_mentioned_in_commit_at = rand(6..12).hours.from_now
# MR
Timecop.travel 5.days.from_now
branch_name = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}"
@project.repository.add_branch(@user, branch_name, 'master')
merge_request = MergeRequest.create(target_project: @project, source_project: @project, source_branch: branch_name, target_branch: 'master', title: branch_name, author: @user)
merge_request_metrics = merge_request.metrics
# MR closing issues
Timecop.travel 5.days.from_now
MergeRequestsClosingIssues.create!(issue: issue, merge_request: merge_request)
# Merge
Timecop.travel 5.days.from_now
merge_request_metrics.merged_at = rand(6..12).hours.from_now
# Start build
Timecop.travel 5.days.from_now
merge_request_metrics.latest_build_started_at = rand(6..12).hours.from_now
# Finish build
Timecop.travel 5.days.from_now
merge_request_metrics.latest_build_finished_at = rand(6..12).hours.from_now
# Deploy to production
Timecop.travel 5.days.from_now
merge_request_metrics.first_deployed_to_production_at = rand(6..12).hours.from_now
issue_metrics.save!
merge_request_metrics.save!
print '.'
end end
def initialize(project: nil, perf: false)
@project = project || create_new_vsm_project
@issue_count = perf ? 1000 : ENV.fetch('CYCLE_ANALYTICS_ISSUE_COUNT', 5).to_i
@issues = []
@merge_requests = []
@developers = []
end end
def seed! def seed!
Sidekiq::Worker.skipping_transaction_check do create_developers!
Sidekiq::Testing.inline! do create_issues!
issues = create_issues
puts '.'
# Stage 1 seed_issue_stage!
Timecop.travel 5.days.from_now seed_plan_stage!
add_milestones_and_list_labels(issues) seed_code_stage!
print '.' seed_test_stage!
seed_review_stage!
seed_staging_stage!
# Stage 2 puts "Successfully seeded '#{project.full_path}' for Value Stream Management!"
Timecop.travel 5.days.from_now puts "URL: #{Rails.application.routes.url_helpers.project_url(project)}"
branches = mention_in_commits(issues) end
print '.'
# Stage 3
Timecop.travel 5.days.from_now
merge_requests = create_merge_requests_closing_issues(issues, branches)
print '.'
# Stage 4 private
Timecop.travel 5.days.from_now
run_builds(merge_requests)
print '.'
# Stage 5 def seed_issue_stage!
Timecop.travel 5.days.from_now issues.each do |issue|
merge_merge_requests(merge_requests) time = within_end_time(issue.created_at + rand(ISSUE_STAGE_MAX_DURATION_IN_HOURS).hours)
print '.'
# Stage 6 / 7 if issue.id.even?
Timecop.travel 5.days.from_now issue.metrics.update!(first_associated_with_milestone_at: time)
deploy_to_production(merge_requests) else
print '.' issue.metrics.update!(first_added_to_board_at: time)
end end
end end
print '.'
end end
private def seed_plan_stage!
issues.each do |issue|
def create_issues plan_stage_start = issue.metrics.first_associated_with_milestone_at || issue.metrics.first_added_to_board_at
Array.new(@issue_count) do
issue_params = {
title: "Value Stream Analytics: #{FFaker::Lorem.sentence(6)}",
description: FFaker::Lorem.sentence,
state: 'opened',
assignees: [@project.team.users.sample]
}
Issues::CreateService.new(@project, @project.team.users.sample, issue_params).execute first_mentioned_in_commit_at = within_end_time(plan_stage_start + rand(PLAN_STAGE_MAX_DURATION_IN_HOURS).hours)
issue.metrics.update!(first_mentioned_in_commit_at: first_mentioned_in_commit_at)
end end
end end
def add_milestones_and_list_labels(issues) def seed_code_stage!
issues.shuffle.map.with_index do |issue, index| issues.each do |issue|
Timecop.travel 12.hours.from_now 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(CODE_STAGE_MAX_DURATION_IN_HOURS).hours)
)
if index.even? @merge_requests << merge_request
issue.update(milestone: @project.milestones.sample)
else
label_name = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}"
list_label = FactoryBot.create(:label, title: label_name, project: issue.project)
FactoryBot.create(:list, board: FactoryBot.create(:board, project: issue.project), label: list_label)
issue.update(labels: [list_label])
end
issue MergeRequestsClosingIssues.create!(issue: issue, merge_request: merge_request)
end end
end end
def mention_in_commits(issues) def seed_test_stage!
issues.map do |issue| merge_requests.each do |merge_request|
Timecop.travel 12.hours.from_now pipeline = FactoryBot.create(:ci_pipeline, :success, project: project)
build = FactoryBot.create(:ci_build, pipeline: pipeline, project: project, user: developers.sample)
branch_name = filename = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}"
issue.project.repository.add_branch(@user, branch_name, 'master')
commit_sha = issue.project.repository.create_file(@user, filename, "content", message: "Commit for #{issue.to_reference}", branch_name: branch_name)
issue.project.repository.commit(commit_sha)
::Git::BranchPushService.new(
issue.project,
@user,
change: {
oldrev: issue.project.repository.commit("master").sha,
newrev: commit_sha,
ref: 'refs/heads/master'
}
).execute
branch_name merge_request.metrics.update!(
latest_build_started_at: merge_request.created_at,
latest_build_finished_at: within_end_time(merge_request.created_at + TEST_STAGE_MAX_DURATION_IN_HOURS.hours),
pipeline_id: build.commit_id
)
end end
end end
def create_merge_requests_closing_issues(issues, branches) def seed_review_stage!
issues.zip(branches).map do |issue, branch| merge_requests.each do |merge_request|
Timecop.travel 12.hours.from_now merge_request.metrics.update!(merged_at: within_end_time(merge_request.created_at + REVIEW_STAGE_MAX_DURATION_IN_HOURS.hours))
end
end
opts = { def seed_staging_stage!
title: 'Value Stream Analytics merge_request', merge_requests.each do |merge_request|
description: "Fixes #{issue.to_reference}", merge_request.metrics.update!(first_deployed_to_production_at: within_end_time(merge_request.metrics.merged_at + DEPLOYMENT_MAX_DURATION_IN_HOURS.hours))
source_branch: branch, end
target_branch: 'master' end
}
MergeRequests::CreateService.new(issue.project, @user, opts).execute def create_issues!
@issue_count.times do
Timecop.travel start_time + rand(5).days do
title = "#{FFaker::Product.brand}-#{suffix}"
@issues << Issue.create!(project: project, title: title, author: developers.sample)
end
end end
end end
def run_builds(merge_requests) def create_developers!
merge_requests.each do |merge_request| 5.times do |i|
Timecop.travel 12.hours.from_now user = FactoryBot.create(
:user,
name: "VSM User#{i}",
username: "vsm-user-#{i}-#{suffix}",
email: "vsm-user-#{i}@#{suffix}.com"
)
service = Ci::CreatePipelineService.new(merge_request.project, project.group.add_developer(user)
@user, project.add_developer(user)
ref: "refs/heads/#{merge_request.source_branch}")
pipeline = service.execute(:push, ignore_skip_ci: true, save_on_errors: false)
pipeline.builds.each(&:enqueue) # make sure all pipelines in pending state @developers << user
pipeline.builds.each(&:run!)
pipeline.update_legacy_status
end end
end end
def merge_merge_requests(merge_requests) def create_new_vsm_project
merge_requests.each do |merge_request| project = FactoryBot.create(
Timecop.travel 12.hours.from_now :project,
name: "Value Stream Management Project #{suffix}",
path: "vsmp-#{suffix}",
creator: admin,
namespace: FactoryBot.create(
:group,
name: "Value Stream Management Group (#{suffix})",
path: "vsmg-#{suffix}"
)
)
MergeRequests::MergeService.new(merge_request.project, @user).execute(merge_request) project.create_repository
end project
end end
def deploy_to_production(merge_requests) def admin
merge_requests.each do |merge_request| @admin ||= User.admins.first
next unless merge_request.head_pipeline end
Timecop.travel 12.hours.from_now def suffix
@suffix ||= Time.now.to_i
end
job = merge_request.head_pipeline.builds.where.not(environment: nil).last def start_time
@start_time ||= 25.days.ago
end
job.success! def end_time
job.pipeline.update_legacy_status @end_time ||= Time.now
end end
def within_end_time(time)
[time, end_time].min
end end
end end
Gitlab::Seeder.quiet do Gitlab::Seeder.quiet do
flag = 'SEED_CYCLE_ANALYTICS' project_id = ENV['CYCLE_ANALYTICS_SEED_PROJECT_ID']
project = Project.find(project_id) if project_id
if ENV[flag]
Project.not_mass_generated.find_each do |project| seeder = Gitlab::Seeder::CycleAnalytics.seeder_base_on_env(project)
# This seed naively assumes that every project has a repository, and every
# repository has a `master` branch, which may be the case for a pristine if seeder
# GDK seed, but is almost never true for a GDK that's actually had
# development performed on it.
next unless project.repository_exists?
next unless project.repository.commit('master')
seeder = Gitlab::Seeder::CycleAnalytics.new(project)
seeder.seed!
end
elsif ENV['CYCLE_ANALYTICS_PERF_TEST']
seeder = Gitlab::Seeder::CycleAnalytics.new(Project.order(:id).first, perf: true)
seeder.seed! seeder.seed!
elsif ENV['CYCLE_ANALYTICS_POPULATE_METRICS_DIRECTLY']
seeder = Gitlab::Seeder::CycleAnalytics.new(Project.order(:id).first, perf: true)
seeder.seed_metrics!
else else
puts "Skipped. Use the `#{flag}` environment variable to enable." puts "Skipped. Use the `#{Gitlab::Seeder::CycleAnalytics::FLAG}` environment variable to enable."
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment