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
@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