diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md index 411563d448c..92ca5c0962d 100644 --- a/doc/administration/instance_limits.md +++ b/doc/administration/instance_limits.md @@ -43,6 +43,18 @@ A maximum number of project webhooks applies to each GitLab.com tier. Check the [Maximum number of webhooks (per tier)](../user/project/integrations/webhooks.md#maximum-number-of-webhooks-per-tier) section in the Webhooks page. +To set this limit on a self-hosted installation, run the following in the +[GitLab Rails console](https://docs.gitlab.com/omnibus/maintenance/#starting-a-rails-console-session): + +```ruby +# If limits don't exist for the default plan, you can create one with: +# Plan.default.create_limits! + +Plan.default.limits.update!(project_hooks: 100) +``` + +NOTE: **Note:** Set the limit to `0` to disable it. + ## CI/CD limits ### Number of jobs in active pipelines @@ -73,4 +85,4 @@ To set this limit on a self-hosted installation, run the following in the Plan.default.limits.update!(ci_active_jobs: 500) ``` -Set the limit to `0` to disable it. +NOTE: **Note:** Set the limit to `0` to disable it. diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index ff978ee2899..d9b2ace1b5b 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -54,6 +54,17 @@ By default, this seeds an average of 10 issues per week for the last 52 weeks per project. All issues will also be randomly labeled with team, type, severity, and priority. +#### Seeding groups with sub-groups + +You can seed groups with sub-groups that contain milestones/projects/issues +with the `gitlab:seed:group_seed` task: + +```shell +bin/rake "gitlab:seed:group_seed[subgroup_depth, username]" +``` + +Group are additionally seeded with epics if GitLab instance has epics feature available. + ### Automation If you're very sure that you want to **wipe the current database** and refill diff --git a/doc/user/analytics/code_review_analytics.md b/doc/user/analytics/code_review_analytics.md index 0b501b1a56d..5861afd92a2 100644 --- a/doc/user/analytics/code_review_analytics.md +++ b/doc/user/analytics/code_review_analytics.md @@ -7,17 +7,19 @@ description: "Learn how long your open merge requests have spent in code review, > [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/38062) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.7. Code Review Analytics makes it easy to view the longest-running reviews among open merge requests, -enabling you to take action on individual MRs and reduce overall cycle time. +enabling you to take action on individual merge requests and reduce overall cycle time. NOTE: **Note:** Initially, no data will appear. Data is populated as users comment on open merge requests. ## Overview -Code Review Analytics displays a table of open merge requests which are currently considered to be in code review. -The code review period for an MR is automatically identified as the time since the first non-author comment. +Code Review Analytics displays a table of open merge requests that have at least one non-author comment. The review time is measured from the time the first non-author comment was submitted. +The code review period for a merge request is automatically identified as the time since the first non-author comment. -To access Code Review Analytics, from your project's menu, go to **Project Analytics > Code Review**. +To access Code Review Analytics, from your project's menu, go to **{chart}** **Project Analytics > Code Review**. + +![Code Review Analytics](img/code_review_analytics_v12_8.png "List of code reviews; oldest review first.") - The table is sorted by review duration, helping you quickly find the longest-running reviews which may need intervention or to be broken down into smaller parts. - You can filter the list of MRs by milestone and label. diff --git a/doc/user/analytics/img/code_review_analytics_v12_8.png b/doc/user/analytics/img/code_review_analytics_v12_8.png new file mode 100644 index 00000000000..228e03e628a Binary files /dev/null and b/doc/user/analytics/img/code_review_analytics_v12_8.png differ diff --git a/lib/tasks/gitlab/seed/group_seed.rake b/lib/tasks/gitlab/seed/group_seed.rake new file mode 100644 index 00000000000..9c98080eff8 --- /dev/null +++ b/lib/tasks/gitlab/seed/group_seed.rake @@ -0,0 +1,235 @@ +# frozen_string_literal: true + +# Seed test groups with: +# 1. 2 Subgroups per level +# 1. 2 Users & group members per group +# 1. 2 Epics, 2 Milestones & 2 Projects per group +# 1. Project issues +# +# It also assigns each project's issue with one of group's or ascendants +# groups milestone & epic. +# +# @param subgroups_depth - number of subgroup levels +# @param username - user creating subgroups (i.e. GitLab admin) +# +# @example +# bundle exec rake "gitlab:seed:group_seed[5, root]" +# +namespace :gitlab do + namespace :seed do + desc 'Seed groups with sub-groups/projects/epics/milestones for Group Import testing' + task :group_seed, [:subgroups_depth, :username] => :gitlab_environment do |_t, args| + require 'sidekiq/testing' + + GroupSeeder.new( + subgroups_depth: args.subgroups_depth, + username: args.username + ).seed + end + end +end + +class GroupSeeder + PROJECT_URL = 'https://gitlab.com/gitlab-org/gitlab-test.git' + + attr_reader :all_group_ids + + def initialize(subgroups_depth:, username:) + @subgroups_depth = subgroups_depth.to_i + @user = User.find_by_username(username) + @group_names = Set.new + @resource_count = 2 + @all_groups = {} + @all_group_ids = [] + end + + def seed + create_groups + + puts 'Done!' + end + + def create_groups + create_root_group + create_sub_groups + create_users_and_members + create_epics if Gitlab.ee? + create_labels + create_milestones + + Sidekiq::Testing.inline! do + create_projects + end + end + + def create_users_and_members + all_group_ids.each do |group_id| + @resource_count.times do |_| + user = create_user + create_member(user.id, group_id) + end + end + end + + def create_root_group + root_group = ::Groups::CreateService.new(@user, group_params).execute + + track_group_id(1, root_group.id) + end + + def create_sub_groups + (2..@subgroups_depth).each do |level| + parent_level = level - 1 + current_level = level + parent_groups = @all_groups[parent_level] + + parent_groups.each do |parent_id| + @resource_count.times do |_| + sub_group = ::Groups::CreateService.new(@user, group_params(parent_id: parent_id)).execute + + track_group_id(current_level, sub_group.id) + end + end + end + end + + def track_group_id(depth_level, group_id) + @all_groups[depth_level] ||= [] + @all_groups[depth_level] << group_id + @all_group_ids << group_id + end + + def group_params(parent_id: nil) + name = unique_name + + { + name: name, + path: name, + parent_id: parent_id + } + end + + def unique_name + name = ffaker_name + name = ffaker_name until @group_names.add?(name) + name + end + + def ffaker_name + FFaker::Lorem.characters(5) + end + + def create_user + User.create!( + username: FFaker::Internet.user_name, + name: FFaker::Name.name, + email: FFaker::Internet.email, + confirmed_at: DateTime.now, + password: Devise.friendly_token + ) + end + + def create_member(user_id, group_id) + roles = Gitlab::Access.values + + GroupMember.create(user_id: user_id, access_level: roles.sample, source_id: group_id) + end + + def create_epics + all_group_ids.each do |group_id| + @resource_count.times do |_| + group = Group.find(group_id) + + epic_params = { + title: FFaker::Lorem.sentence(6), + description: FFaker::Lorem.paragraphs(3).join("\n\n"), + author: group.users.sample, + group: group + } + + Epic.create!(epic_params) + end + end + end + + def create_labels + all_group_ids.each do |group_id| + @resource_count.times do |_| + group = Group.find(group_id) + label_title = FFaker::Product.brand + + Labels::CreateService.new(title: label_title, color: "##{Digest::MD5.hexdigest(label_title)[0..5]}").execute(group: group) + end + end + end + + def create_milestones + all_group_ids.each do |group_id| + @resource_count.times do |i| + group = Group.find(group_id) + + milestone_params = { + title: "v#{i}.0", + description: FFaker::Lorem.sentence, + state: [:active, :closed].sample + } + + Milestones::CreateService.new(group, group.members.sample, milestone_params).execute + end + end + end + + def create_projects + all_group_ids.each do |group_id| + group = Group.find(group_id) + + @resource_count.times do |i| + _, project_path = PROJECT_URL.split('/')[-2..-1] + + project_path.gsub!('.git', '') + + params = { + import_url: PROJECT_URL, + namespace_id: group.id, + name: project_path.titleize + FFaker::Lorem.characters(10), + description: FFaker::Lorem.sentence, + visibility_level: 0, + skip_disk_validation: true + } + + project = nil + + Sidekiq::Worker.skipping_transaction_check do + project = ::Projects::CreateService.new(@user, params).execute + project.send(:_run_after_commit_queue) + project.import_state.send(:_run_after_commit_queue) + project.repository.expire_all_method_caches + end + + create_project_issues(project) + assign_issues_to_epics_and_milestones(project) + end + end + end + + def create_project_issues(project) + Gitlab::Seeder.quiet do + seeder = Quality::Seeders::Issues.new(project: project) + seeder.seed(backfill_weeks: 2, average_issues_per_week: 2) + end + end + + def assign_issues_to_epics_and_milestones(project) + group_ids = project.group.self_and_ancestors.map(&:id) + + project.issues.each do |issue| + issue_params = { + milestone: Milestone.where(group: group_ids).sample + } + + issue_params[:epic] = Epic.where(group: group_ids).sample if Gitlab.ee? + + issue.update(issue_params) + end + end +end diff --git a/package.json b/package.json index 7f25eb68caf..6b2dd93773b 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-syntax-import-meta": "^7.2.0", "@babel/preset-env": "^7.6.2", - "@gitlab/svgs": "^1.90.0", + "@gitlab/svgs": "^1.91.0", "@gitlab/ui": "^9.0.0", "@gitlab/visual-review-tools": "1.5.1", "@sentry/browser": "^5.10.2", diff --git a/spec/tasks/gitlab/seed/group_seed_rake_spec.rb b/spec/tasks/gitlab/seed/group_seed_rake_spec.rb new file mode 100644 index 00000000000..ecf4e9575ab --- /dev/null +++ b/spec/tasks/gitlab/seed/group_seed_rake_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'rake_helper' + +describe 'gitlab:seed:group_seed rake task' do + let(:username) { 'group_seed' } + let!(:user) { create(:user, username: username) } + let(:task_params) { [2, username] } + + before do + Rake.application.rake_require('tasks/gitlab/seed/group_seed') + end + + subject { run_rake_task('gitlab:seed:group_seed', task_params) } + + it 'performs group seed successfully' do + expect { subject }.not_to raise_error + + group = user.groups.first + + expect(user.groups.count).to be 3 + expect(group.projects.count).to be 2 + expect(group.members.count).to be 3 + expect(group.milestones.count).to be 2 + end +end diff --git a/yarn.lock b/yarn.lock index 170812f7367..bf9b9c1d653 100644 --- a/yarn.lock +++ b/yarn.lock @@ -732,10 +732,10 @@ dependencies: vue-eslint-parser "^6.0.4" -"@gitlab/svgs@^1.90.0": - version "1.90.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.90.0.tgz#e6fe0ca3d353fcdbd792c10d82444383c33f539d" - integrity sha512-6UikaIMGosmrDAd6Lf3QIJWDM4FwhoGIN+CJuFcWeHDMbZT69LnTabuGfvOQmp2+nlI68baRTSDufaux7m9Ajw== +"@gitlab/svgs@^1.91.0": + version "1.91.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.91.0.tgz#cf7b28e43779a929a438dcc0c51f39f92551e58e" + integrity sha512-Sz8aaaNnUUtlrk/FPf6FNceu4XsDLBh6g/c6nmzDVGF9kHrfiXfWL0tYAax8vhkrld5vewGHE0bRpq2ZILq0pw== "@gitlab/ui@^9.0.0": version "9.0.0"