Commit 20335291 authored by Adam Hegyi's avatar Adam Hegyi

Rewrite VSM development fixtures

This change rewrites VSM development fixtures by creating the seed data
only within an isolated project.
parent 0c342a8c
# 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 FLAG = 'SEED_CYCLE_ANALYTICS'
@issue_count = perf ? 1000 : ENV.fetch('CYCLE_ANALYTICS_ISSUE_COUNT', 5).to_i PERF_TEST = 'CYCLE_ANALYTICS_PERF_TEST'
ISSUE_STAGE_MAX_DURATION_IN_HOURS = 72
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
def self.seeder_base_on_env(project)
if ENV[FLAG]
self.new(project: project)
elsif ENV[PERF_TEST]
self.new(project: project, perf: true)
end
end end
def seed_metrics! def initialize(project: nil, perf: false)
@issue_count.times do |index| @project = project || create_new_vsm_project
# Issue @issue_count = perf ? 1000 : ENV.fetch('CYCLE_ANALYTICS_ISSUE_COUNT', 5).to_i
Timecop.travel 5.days.from_now @issues = []
title = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}" @merge_requests = []
issue = Issue.create(project: @project, title: title, author: @user) @developers = []
issue_metrics = issue.metrics
# Milestones / Labels
Timecop.travel 5.days.from_now
if index.even?
issue_metrics.first_associated_with_milestone_at = rand(6..12).hours.from_now
else
issue_metrics.first_added_to_board_at = rand(6..12).hours.from_now
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 end
def seed! def seed!
Sidekiq::Worker.skipping_transaction_check do create_developers!
Sidekiq::Testing.inline! do create_issues!
issues = create_issues
puts '.' seed_issue_stage!
seed_plan_stage!
# Stage 1 seed_code_stage!
Timecop.travel 5.days.from_now seed_test_stage!
add_milestones_and_list_labels(issues) seed_review_stage!
print '.' 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)
print '.'
# Stage 3
Timecop.travel 5.days.from_now
merge_requests = create_merge_requests_closing_issues(issues, branches)
print '.'
# Stage 4
Timecop.travel 5.days.from_now
run_builds(merge_requests)
print '.'
# Stage 5
Timecop.travel 5.days.from_now
merge_merge_requests(merge_requests)
print '.'
# Stage 6 / 7
Timecop.travel 5.days.from_now
deploy_to_production(merge_requests)
print '.'
end
end
print '.'
end end
private private
def create_issues def seed_issue_stage!
Array.new(@issue_count) do issues.each do |issue|
issue_params = { time = within_end_time(issue.created_at + rand(ISSUE_STAGE_MAX_DURATION_IN_HOURS).hours)
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
end
end
def add_milestones_and_list_labels(issues)
issues.shuffle.map.with_index do |issue, index|
Timecop.travel 12.hours.from_now
if index.even? if issue.id.even?
issue.update(milestone: @project.milestones.sample) issue.metrics.update!(first_associated_with_milestone_at: time)
else else
label_name = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}" issue.metrics.update!(first_added_to_board_at: time)
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 end
issue
end end
end end
def mention_in_commits(issues) def seed_plan_stage!
issues.map do |issue| issues.each do |issue|
Timecop.travel 12.hours.from_now plan_stage_start = issue.metrics.first_associated_with_milestone_at || issue.metrics.first_added_to_board_at
branch_name = filename = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}" 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
issue.project.repository.add_branch(@user, branch_name, 'master') 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(CODE_STAGE_MAX_DURATION_IN_HOURS).hours)
)
commit_sha = issue.project.repository.create_file(@user, filename, "content", message: "Commit for #{issue.to_reference}", branch_name: branch_name) @merge_requests << merge_request
issue.project.repository.commit(commit_sha)
::Git::BranchPushService.new( MergeRequestsClosingIssues.create!(issue: issue, merge_request: merge_request)
issue.project, end
@user, end
change: {
oldrev: issue.project.repository.commit("master").sha,
newrev: commit_sha,
ref: 'refs/heads/master'
}
).execute
branch_name def seed_test_stage!
merge_requests.each do |merge_request|
pipeline = FactoryBot.create(:ci_pipeline, :success, project: project)
build = FactoryBot.create(:ci_build, pipeline: pipeline, project: project, user: developers.sample)
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}"
)
)
project.create_repository
project
end
MergeRequests::MergeService.new(merge_request.project, @user).execute(merge_request) def admin
end @admin ||= User.admins.first
end end
def deploy_to_production(merge_requests) def suffix
merge_requests.each do |merge_request| @suffix ||= Time.now.to_i
next unless merge_request.head_pipeline end
Timecop.travel 12.hours.from_now def start_time
@start_time ||= 25.days.ago
end
job = merge_request.head_pipeline.builds.where.not(environment: nil).last def end_time
@end_time ||= Time.now
end
job.success! def within_end_time(time)
job.pipeline.update_legacy_status [time, end_time].min
end
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