Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-03-30 18:08:19 +00:00
parent f4199ade27
commit f06ebebade
34 changed files with 468 additions and 196 deletions

View File

@ -1 +1 @@
cc42cf8f28dc37bf808dabaac8a055a84b83a5db
8b95897d918a4fefdc0542a72121182cd1f27f8f

View File

@ -13,6 +13,7 @@ import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import Tracking from '~/tracking';
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
import { s__ } from '~/locale';
import searchNamespacesWhereUserCanCreateProjectsQuery from '../queries/search_namespaces_where_user_can_create_projects.query.graphql';
import eventHub from '../event_hub';
@ -43,14 +44,7 @@ export default {
debounce: DEBOUNCE_DELAY,
},
},
inject: [
'namespaceFullPath',
'namespaceId',
'rootUrl',
'trackLabel',
'userNamespaceFullPath',
'userNamespaceId',
],
inject: ['namespaceFullPath', 'namespaceId', 'rootUrl', 'trackLabel', 'userNamespaceId'],
data() {
return {
currentUser: {},
@ -62,10 +56,11 @@ export default {
fullPath: this.namespaceFullPath,
}
: {
id: this.userNamespaceId,
fullPath: this.userNamespaceFullPath,
id: undefined,
fullPath: s__('ProjectsNew|Pick a group or namespace'),
},
shouldSkipQuery: true,
userNamespaceId: this.userNamespaceId,
};
},
computed: {
@ -92,6 +87,9 @@ export default {
hasNoMatches() {
return !this.hasGroupMatches && !this.hasNamespaceMatches;
},
dropdownPlaceholderClass() {
return this.selectedNamespace.id ? '' : 'gl-text-gray-500!';
},
},
created() {
eventHub.$on('select-template', this.handleSelectTemplate);
@ -130,11 +128,18 @@ export default {
</script>
<template>
<gl-button-group class="input-lg">
<gl-button class="gl-text-truncate" label :title="rootUrl">{{ rootUrl }}</gl-button>
<gl-button-group class="gl-w-full">
<gl-button
class="js-group-namespace-button gl-text-truncate gl-flex-grow-0!"
label
:title="rootUrl"
>{{ rootUrl }}</gl-button
>
<gl-dropdown
:text="selectedNamespace.fullPath"
toggle-class="gl-rounded-top-right-base! gl-rounded-bottom-right-base! gl-w-20"
class="js-group-namespace-dropdown gl-flex-grow-1"
:toggle-class="`gl-rounded-top-right-base! gl-rounded-bottom-right-base! gl-w-20 ${dropdownPlaceholderClass}`"
data-qa-selector="select_namespace_dropdown"
@show="track('activate_form_input', { label: trackLabel, property: 'project_path' })"
@shown="handleDropdownShown"
@ -166,11 +171,13 @@ export default {
</template>
</gl-dropdown>
<input type="hidden" name="project[selected_namespace_id]" :value="selectedNamespace.id" />
<input
id="project_namespace_id"
type="hidden"
name="project[namespace_id]"
:value="selectedNamespace.id"
:value="selectedNamespace.id || userNamespaceId"
/>
</gl-button-group>
</template>

View File

@ -58,7 +58,6 @@ export function initNewProjectUrlSelect() {
namespaceId: el.dataset.namespaceId,
rootUrl: el.dataset.rootUrl,
trackLabel: el.dataset.trackLabel,
userNamespaceFullPath: el.dataset.userNamespaceFullPath,
userNamespaceId: el.dataset.userNamespaceId,
},
render: (createElement) => createElement(NewProjectUrlSelect),

View File

@ -14,6 +14,7 @@ import {
let hasUserDefinedProjectPath = false;
let hasUserDefinedProjectName = false;
const invalidInputClass = 'gl-field-error-outline';
const invalidDropdownClass = 'gl-inset-border-1-red-400!';
const cancelSource = axios.CancelToken.source();
const endpoint = `${gon.relative_url_root}/import/url/validate`;
@ -50,6 +51,25 @@ const onProjectPathChange = ($projectNameInput, $projectPathInput, hasExistingPr
}
};
const selectedNamespaceId = () => document.querySelector('[name="project[selected_namespace_id]"]');
const dropdownButton = () => document.querySelector('.js-group-namespace-dropdown > button');
const namespaceButton = () => document.querySelector('.js-group-namespace-button');
const namespaceError = () => document.querySelector('.js-group-namespace-error');
const validateGroupNamespaceDropdown = (e) => {
if (selectedNamespaceId() && !selectedNamespaceId().attributes.value) {
document.querySelector('input[data-qa-selector="project_name"]').reportValidity();
e.preventDefault();
dropdownButton().classList.add(invalidDropdownClass);
namespaceButton().classList.add(invalidDropdownClass);
namespaceError().classList.remove('gl-display-none');
} else {
dropdownButton().classList.remove(invalidDropdownClass);
namespaceButton().classList.remove(invalidDropdownClass);
namespaceError().classList.add('gl-display-none');
}
};
const setProjectNamePathHandlers = ($projectNameInput, $projectPathInput) => {
const specialRepo = document.querySelector('.js-user-readme-repo');
@ -70,6 +90,10 @@ const setProjectNamePathHandlers = ($projectNameInput, $projectPathInput) => {
$projectPathInput.val() !== $projectPathInput.data('username'),
);
});
document.querySelector('.js-create-project-button').addEventListener('click', (e) => {
validateGroupNamespaceDropdown(e);
});
};
const deriveProjectPathFromUrl = ($projectImportUrl) => {

View File

@ -68,6 +68,13 @@ class ProjectsController < Projects::ApplicationController
@namespace = Namespace.find_by(id: params[:namespace_id]) if params[:namespace_id]
return access_denied! if @namespace && !can?(current_user, :create_projects, @namespace)
@current_user_group =
if current_user.manageable_groups(include_groups_with_developer_maintainer_access: true).count == 1
current_user.manageable_groups(include_groups_with_developer_maintainer_access: true).first
else
nil
end
@project = Project.new(namespace_id: @namespace&.id)
end
# rubocop: enable CodeReuse/ActiveRecord

View File

@ -222,7 +222,7 @@ class WikiPage
update_attributes(attrs)
if title.present? && title_changed? && wiki.find_page(title).present?
if title.present? && title_changed? && wiki.find_page(title, load_content: false).present?
attributes[:title] = page.title
raise PageRenameError, s_('WikiEdit|There is already a page with the same title in that path.')
end

View File

@ -9,27 +9,29 @@
= f.label :name, class: 'label-bold' do
%span= _("Project name")
= f.text_field :name, placeholder: "My awesome project", class: "form-control gl-form-input input-lg", data: { qa_selector: 'project_name', track_label: "#{track_label}", track_action: "activate_form_input", track_property: "project_name", track_value: "" }, required: true, aria: { required: true }
.form-group.project-path.col-sm-6
.form-group.project-path.col-sm-6.gl-pr-0
= f.label :namespace_id, class: 'label-bold' do
%span= _('Project URL')
.input-group.gl-flex-nowrap
- if current_user.can_select_namespace?
- namespace_id = namespace_id_from(params)
.js-vue-new-project-url-select{ data: { namespace_full_path: GroupFinder.new(current_user).execute(id: namespace_id)&.full_path,
namespace_id: namespace_id,
.js-vue-new-project-url-select{ data: { namespace_full_path: GroupFinder.new(current_user).execute(id: namespace_id)&.full_path || @current_user_group&.full_path,
namespace_id: namespace_id || @current_user_group&.id,
root_url: root_url,
track_label: track_label,
user_namespace_full_path: current_user.namespace.full_path,
user_namespace_id: current_user.namespace.id } }
- else
.input-group-prepend.static-namespace.flex-shrink-0.has-tooltip{ title: user_url(current_user.username) + '/' }
.input-group-text.border-0
#{user_url(current_user.username)}/
= f.hidden_field :namespace_id, value: current_user.namespace_id
.gl-align-self-center.gl-pl-5 /
.form-group.project-path.col-sm-6
= f.label :path, class: 'label-bold' do
%span= _("Project slug")
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control gl-form-input", required: true, aria: { required: true }, data: { qa_selector: 'project_path', username: current_user.username }
.js-group-namespace-error.form-text.gl-text-red-500.gl-display-none
= s_('ProjectsNew|Pick a group or namespace where you want to create this project.')
- if current_user.can_create_group?
.form-text.text-muted
- link_start_group_path = '<a href="%{path}">' % { path: new_group_path }
@ -73,5 +75,5 @@
= s_('ProjectsNew|Analyze your source code for known security vulnerabilities.')
= link_to _('Learn more.'), help_page_path('user/application_security/sast/index'), target: '_blank', rel: 'noopener noreferrer', data: { track_action: 'followed' }
= f.submit _('Create project'), class: "btn gl-button btn-confirm", data: { qa_selector: 'project_create_button', track_label: "#{track_label}", track_action: "click_button", track_property: "create_project", track_value: "" }
= f.submit _('Create project'), class: "btn gl-button btn-confirm js-create-project-button", data: { qa_selector: 'project_create_button', track_label: "#{track_label}", track_action: "click_button", track_property: "create_project", track_value: "" }
= link_to _('Cancel'), dashboard_projects_path, class: 'btn gl-button btn-default btn-cancel', data: { track_label: "#{track_label}", track_action: "click_button", track_property: "cancel", track_value: "" }

View File

@ -1,22 +1,3 @@
# frozen_string_literal: true
# rubocop:disable Style/SignalException
CHANGED_FILES_MESSAGE = <<~MSG
For the following files, a review from the [Data team and Product Intelligence team](https://gitlab.com/groups/gitlab-org/growth/product-intelligence/engineers/-/group_members?with_inherited_permissions=exclude) is recommended
Please check the ~"product intelligence" [guide](https://docs.gitlab.com/ee/development/usage_ping.html).
For MR review guidelines, see the [Service Ping review guidelines](https://docs.gitlab.com/ee/development/usage_ping/review_guidelines.html) or the [Snowplow review guidelines](https://docs.gitlab.com/ee/development/snowplow/review_guidelines.html).
%<changed_files>s
MSG
# exit if not matching files or if no product intelligence labels
product_intelligence_paths_to_review = helper.changes_by_category[:product_intelligence]
labels_to_add = product_intelligence.missing_labels
return if product_intelligence_paths_to_review.empty? || product_intelligence.skip_review?
warn format(CHANGED_FILES_MESSAGE, changed_files: helper.markdown_list(product_intelligence_paths_to_review)) unless product_intelligence.has_approved_label?
helper.labels_to_add.concat(labels_to_add) unless labels_to_add.empty?
product_intelligence.check!

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
class FixAndBackfillProjectNamespacesForProjectsWithDuplicateName < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
MIGRATION = 'FixDuplicateProjectNameAndPath'
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 1000
class Project < ActiveRecord::Base
include ::EachBatch
self.table_name = 'projects'
scope :without_project_namespace, -> { where(project_namespace_id: nil) }
end
def up
queue_background_migration_jobs_by_range_at_intervals(
Project.without_project_namespace, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE, track_jobs: true
)
end
def down
# no-op
end
end

View File

@ -0,0 +1 @@
6ce75a41607ed1e60ea4ecdfb36703d9f4192dc26ecaa774f2c30818027dd1d7

View File

@ -1,8 +1,7 @@
---
type: reference, dev
stage: none
group: Development
info: "See the Technical Writers assigned to Development Guidelines: https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments-to-development-guidelines"
stage: Enablement
group: Database
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Background migrations

View File

@ -1,6 +1,6 @@
---
stage: none
group: unassigned
stage: Enablement
group: Memory
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---

View File

@ -396,6 +396,11 @@ When you are ready to have your merge request reviewed,
you should [request an initial review](../user/project/merge_requests/getting_started.md#reviewer) by selecting a reviewer from your group or team.
However, you can also assign it to any reviewer. The list of reviewers can be found on [Engineering projects](https://about.gitlab.com/handbook/engineering/projects/) page.
When a merge request has multiple areas for review, it is recommended you specify which area a reviewer should be reviewing, and at which stage (first or second).
This will help team members who qualify as a reviewer for multiple areas to know which area they're being requested to review.
For example, when a merge request has both `backend` and `frontend` concerns, you can mention the reviewer in this manner:
`@john_doe can you please review ~backend?` or `@jane_doe - could you please give this MR a ~frontend maintainer review?`
You can also use `workflow::ready for review` label. That means that your merge request is ready to be reviewed and any reviewer can pick it. It is recommended to use that label only if there isn't time pressure and make sure the merge request is assigned to a reviewer.
When your merge request receives an approval from the first reviewer it can be passed to a maintainer. You should default to choosing a maintainer with [domain expertise](#domain-experts), and otherwise follow the Reviewer Roulette recommendation or use the label `ready for merge`.

View File

@ -1,8 +1,7 @@
---
type: dev, reference
stage: none
group: Development
info: "See the Technical Writers assigned to Development Guidelines: https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments-to-development-guidelines"
stage: Enablement
group: Database
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Client-side connection-pool

View File

@ -1,6 +1,6 @@
---
stage: none
group: unassigned
stage: Enablement
group: Database
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---

View File

@ -1,6 +1,6 @@
---
stage: none
group: unassigned
stage: Enablement
group: Database
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---

View File

@ -1,6 +1,6 @@
---
stage: none
group: unassigned
stage: Enablement
group: Database
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---

View File

@ -1,6 +1,6 @@
---
stage: none
group: unassigned
stage: Enablement
group: Database
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---

View File

@ -94,7 +94,7 @@ from outside the Marketplace, which allows you to install the application:
1. Place your Jira instance into
[development mode](https://developer.atlassian.com/cloud/jira/platform/getting-started-with-connect/#step-2--enable-development-mode).
1. Sign in to your GitLab application as a user with administrator access.
1. Install the GitLab application from your self-managed GitLab instance, as
1. Install the GitLab application from your Jira instance, as
described in the [Atlassian developer guides](https://developer.atlassian.com/cloud/jira/platform/getting-started-with-connect/#step-3--install-and-test-your-app):
1. In your Jira instance, go to **Apps > Manage Apps** and select **Upload app**:

View File

@ -0,0 +1,82 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Fix project name duplicates and backfill missing project namespace ids
class FixDuplicateProjectNameAndPath
SUB_BATCH_SIZE = 10
# isolated project active record
class Project < ActiveRecord::Base
include ::EachBatch
self.table_name = 'projects'
scope :without_project_namespace, -> { where(project_namespace_id: nil) }
scope :id_in, ->(ids) { where(id: ids) }
end
def perform(start_id, end_id)
@project_ids = fetch_project_ids(start_id, end_id)
backfill_project_namespaces_service = init_backfill_service(project_ids)
backfill_project_namespaces_service.cleanup_gin_index('projects')
project_ids.each_slice(SUB_BATCH_SIZE) do |ids|
ActiveRecord::Base.connection.execute(update_projects_name_and_path_sql(ids))
end
backfill_project_namespaces_service.backfill_project_namespaces
mark_job_as_succeeded(start_id, end_id)
end
private
attr_accessor :project_ids
def fetch_project_ids(start_id, end_id)
Project.without_project_namespace.where(id: start_id..end_id)
end
def init_backfill_service(project_ids)
service = Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNamespaces.new
service.project_ids = project_ids
service.sub_batch_size = SUB_BATCH_SIZE
service
end
def update_projects_name_and_path_sql(project_ids)
<<~SQL
WITH cte (project_id, path_from_route ) AS (
#{path_from_route_sql(project_ids).to_sql}
)
UPDATE
projects
SET
name = concat(projects.name, '-', id),
path = CASE
WHEN projects.path <> cte.path_from_route THEN path_from_route
ELSE projects.path
END
FROM
cte
WHERE
projects.id = cte.project_id;
SQL
end
def path_from_route_sql(project_ids)
Project.without_project_namespace.id_in(project_ids)
.joins("INNER JOIN routes ON routes.source_id = projects.id AND routes.source_type = 'Project'")
.select("projects.id, SUBSTRING(routes.path FROM '[^/]+(?=/$|$)') AS path_from_route")
end
def mark_job_as_succeeded(*arguments)
::Gitlab::Database::BackgroundMigrationJob.mark_all_as_succeeded(
'FixDuplicateProjectNameAndPath',
arguments
)
end
end
end
end

View File

@ -7,6 +7,8 @@ module Gitlab
#
# rubocop: disable Metrics/ClassLength
class BackfillProjectNamespaces
attr_accessor :project_ids, :sub_batch_size
SUB_BATCH_SIZE = 25
PROJECT_NAMESPACE_STI_NAME = 'Project'
@ -18,7 +20,7 @@ module Gitlab
case migration_type
when 'up'
backfill_project_namespaces(namespace_id)
backfill_project_namespaces
mark_job_as_succeeded(start_id, end_id, namespace_id, 'up')
when 'down'
cleanup_backfilled_project_namespaces(namespace_id)
@ -28,11 +30,7 @@ module Gitlab
end
end
private
attr_accessor :project_ids, :sub_batch_size
def backfill_project_namespaces(namespace_id)
def backfill_project_namespaces
project_ids.each_slice(sub_batch_size) do |project_ids|
# cleanup gin indexes on namespaces table
cleanup_gin_index('namespaces')
@ -64,6 +62,8 @@ module Gitlab
end
end
private
def cleanup_backfilled_project_namespaces(namespace_id)
project_ids.each_slice(sub_batch_size) do |project_ids|
# IMPORTANT: first nullify project_namespace_id in projects table to avoid removing projects when records

View File

@ -29885,6 +29885,12 @@ msgstr ""
msgid "ProjectsNew|No import options available"
msgstr ""
msgid "ProjectsNew|Pick a group or namespace"
msgstr ""
msgid "ProjectsNew|Pick a group or namespace where you want to create this project."
msgstr ""
msgid "ProjectsNew|Project Configuration"
msgstr ""

View File

@ -90,7 +90,12 @@ module QA
Page::Project::New.perform(&:click_blank_project_link)
Page::Project::New.perform do |new_page|
new_page.choose_test_namespace unless @personal_namespace
if @personal_namespace
new_page.choose_namespace(@personal_namespace)
else
new_page.choose_test_namespace
end
new_page.choose_name(@name)
new_page.add_description(@description)
new_page.set_visibility(@visibility)

View File

@ -30,6 +30,7 @@ function create_gitlab_workhorse_package() {
function extract_gitlab_workhorse_package() {
local tar_working_folder="${TMP_TEST_FOLDER}"
mkdir -p "${tar_working_folder}"
echoinfo "Extracting archive to ${tar_working_folder}"

View File

@ -191,7 +191,8 @@ RSpec.describe 'New project', :js do
click_link 'Create blank project'
end
it 'selects the user namespace' do
it 'does not select the user namespace' do
click_on 'Pick a group or namespace'
expect(page).to have_button user.username
end
end
@ -328,6 +329,14 @@ RSpec.describe 'New project', :js do
click_on 'Create project'
expect(page).to have_content(
s_('ProjectsNew|Pick a group or namespace where you want to create this project.')
)
click_on 'Pick a group or namespace'
click_on user.username
click_on 'Create project'
expect(page).to have_css('#import-project-pane.active')
expect(page).not_to have_css('.toggle-import-form.hide')
end

View File

@ -70,7 +70,7 @@ RSpec.describe 'User creates a project', :js do
fill_in :project_name, with: 'A Subgroup Project'
fill_in :project_path, with: 'a-subgroup-project'
click_button user.username
click_on 'Pick a group or namespace'
click_button subgroup.full_path
click_button('Create project')
@ -97,9 +97,6 @@ RSpec.describe 'User creates a project', :js do
fill_in :project_name, with: 'a-new-project'
fill_in :project_path, with: 'a-new-project'
click_button user.username
click_button group.full_path
page.within('#content-body') do
click_button('Create project')
end

View File

@ -15,6 +15,7 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import eventHub from '~/projects/new/event_hub';
import NewProjectUrlSelect from '~/projects/new/components/new_project_url_select.vue';
import searchQuery from '~/projects/new/queries/search_namespaces_where_user_can_create_projects.query.graphql';
import { s__ } from '~/locale';
describe('NewProjectUrlSelect component', () => {
let wrapper;
@ -61,7 +62,6 @@ describe('NewProjectUrlSelect component', () => {
namespaceId: '28',
rootUrl: 'https://gitlab.com/',
trackLabel: 'blank_project',
userNamespaceFullPath: 'root',
userNamespaceId: '1',
};
@ -91,7 +91,10 @@ describe('NewProjectUrlSelect component', () => {
const findButtonLabel = () => wrapper.findComponent(GlButton);
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findInput = () => wrapper.findComponent(GlSearchBoxByType);
const findHiddenInput = () => wrapper.find('[name="project[namespace_id]"]');
const findHiddenNamespaceInput = () => wrapper.find('[name="project[namespace_id]"]');
const findHiddenSelectedNamespaceInput = () =>
wrapper.find('[name="project[selected_namespace_id]"]');
const clickDropdownItem = async () => {
wrapper.findComponent(GlDropdownItem).vm.$emit('click');
@ -122,11 +125,20 @@ describe('NewProjectUrlSelect component', () => {
});
it('renders a dropdown with the given namespace full path as the text', () => {
expect(findDropdown().props('text')).toBe(defaultProvide.namespaceFullPath);
const dropdownProps = findDropdown().props();
expect(dropdownProps.text).toBe(defaultProvide.namespaceFullPath);
expect(dropdownProps.toggleClass).not.toContain('gl-text-gray-500!');
});
it('renders a dropdown with the given namespace id in the hidden input', () => {
expect(findHiddenInput().attributes('value')).toBe(defaultProvide.namespaceId);
it('renders a hidden input with the given namespace id', () => {
expect(findHiddenNamespaceInput().attributes('value')).toBe(defaultProvide.namespaceId);
});
it('renders a hidden input with the selected namespace id', () => {
expect(findHiddenSelectedNamespaceInput().attributes('value')).toBe(
defaultProvide.namespaceId,
);
});
});
@ -142,11 +154,18 @@ describe('NewProjectUrlSelect component', () => {
});
it("renders a dropdown with the user's namespace full path as the text", () => {
expect(findDropdown().props('text')).toBe(defaultProvide.userNamespaceFullPath);
const dropdownProps = findDropdown().props();
expect(dropdownProps.text).toBe(s__('ProjectsNew|Pick a group or namespace'));
expect(dropdownProps.toggleClass).toContain('gl-text-gray-500!');
});
it("renders a dropdown with the user's namespace id in the hidden input", () => {
expect(findHiddenInput().attributes('value')).toBe(defaultProvide.userNamespaceId);
it("renders a hidden input with the user's namespace id", () => {
expect(findHiddenNamespaceInput().attributes('value')).toBe(defaultProvide.userNamespaceId);
});
it('renders a hidden input with the selected namespace id', () => {
expect(findHiddenSelectedNamespaceInput().attributes('value')).toBe(undefined);
});
});
@ -270,7 +289,7 @@ describe('NewProjectUrlSelect component', () => {
await clickDropdownItem();
expect(findHiddenInput().attributes('value')).toBe(
expect(findHiddenNamespaceInput().attributes('value')).toBe(
getIdFromGraphQLId(data.currentUser.groups.nodes[0].id).toString(),
);
});

View File

@ -0,0 +1,65 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::FixDuplicateProjectNameAndPath, :migration do
let(:migration) { described_class.new }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:routes) { table(:routes) }
let(:namespace1) { namespaces.create!(name: 'batchtest1', type: 'Group', path: 'batch-test1') }
let(:namespace2) { namespaces.create!(name: 'batchtest2', type: 'Group', parent_id: namespace1.id, path: 'batch-test2') }
let(:namespace3) { namespaces.create!(name: 'batchtest3', type: 'Group', parent_id: namespace2.id, path: 'batch-test3') }
let(:project_namespace2) { namespaces.create!(name: 'project2', path: 'project2', type: 'Project', parent_id: namespace2.id, visibility_level: 20) }
let(:project_namespace3) { namespaces.create!(name: 'project3', path: 'project3', type: 'Project', parent_id: namespace3.id, visibility_level: 20) }
let(:project1) { projects.create!(name: 'project1', path: 'project1', namespace_id: namespace1.id, visibility_level: 20) }
let(:project2) { projects.create!(name: 'project2', path: 'project2', namespace_id: namespace2.id, project_namespace_id: project_namespace2.id, visibility_level: 20) }
let(:project2_dup) { projects.create!(name: 'project2', path: 'project2', namespace_id: namespace2.id, visibility_level: 20) }
let(:project3) { projects.create!(name: 'project3', path: 'project3', namespace_id: namespace3.id, project_namespace_id: project_namespace3.id, visibility_level: 20) }
let(:project3_dup) { projects.create!(name: 'project3', path: 'project3', namespace_id: namespace3.id, visibility_level: 20) }
let!(:namespace_route1) { routes.create!(path: 'batch-test1', source_id: namespace1.id, source_type: 'Namespace') }
let!(:namespace_route2) { routes.create!(path: 'batch-test1/batch-test2', source_id: namespace2.id, source_type: 'Namespace') }
let!(:namespace_route3) { routes.create!(path: 'batch-test1/batch-test3', source_id: namespace3.id, source_type: 'Namespace') }
let!(:proj_route1) { routes.create!(path: 'batch-test1/project1', source_id: project1.id, source_type: 'Project') }
let!(:proj_route2) { routes.create!(path: 'batch-test1/batch-test2/project2', source_id: project2.id, source_type: 'Project') }
let!(:proj_route2_dup) { routes.create!(path: "batch-test1/batch-test2/project2-route-#{project2_dup.id}", source_id: project2_dup.id, source_type: 'Project') }
let!(:proj_route3) { routes.create!(path: 'batch-test1/batch-test3/project3', source_id: project3.id, source_type: 'Project') }
let!(:proj_route3_dup) { routes.create!(path: "batch-test1/batch-test3/project3-route-#{project3_dup.id}", source_id: project3_dup.id, source_type: 'Project') }
subject(:perform_migration) { migration.perform(projects.minimum(:id), projects.maximum(:id)) }
describe '#up' do
it 'backfills namespace_id for the selected records', :aggregate_failures do
expect(namespaces.where(type: 'Project').count).to eq(2)
perform_migration
expect(namespaces.where(type: 'Project').count).to eq(5)
expect(project1.reload.name).to eq("project1-#{project1.id}")
expect(project1.path).to eq('project1')
expect(project2.reload.name).to eq('project2')
expect(project2.path).to eq('project2')
expect(project2_dup.reload.name).to eq("project2-#{project2_dup.id}")
expect(project2_dup.path).to eq("project2-route-#{project2_dup.id}")
expect(project3.reload.name).to eq("project3")
expect(project3.path).to eq("project3")
expect(project3_dup.reload.name).to eq("project3-#{project3_dup.id}")
expect(project3_dup.path).to eq("project3-route-#{project3_dup.id}")
projects.all.each do |pr|
project_namespace = namespaces.find(pr.project_namespace_id)
expect(project_namespace).to be_in_sync_with_project(pr)
end
end
end
end

View File

@ -0,0 +1,51 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe FixAndBackfillProjectNamespacesForProjectsWithDuplicateName, :migration do
let(:projects) { table(:projects) }
let(:namespaces) { table(:namespaces) }
let!(:group) { namespaces.create!(name: 'group1', path: 'group1', type: 'Group') }
let!(:project_namespace) { namespaces.create!(name: 'project2', path: 'project2', type: 'Project') }
let!(:project1) { projects.create!(name: 'project1', path: 'project1', project_namespace_id: nil, namespace_id: group.id, visibility_level: 20) }
let!(:project2) { projects.create!(name: 'project2', path: 'project2', project_namespace_id: project_namespace.id, namespace_id: group.id, visibility_level: 20) }
let!(:project3) { projects.create!(name: 'project3', path: 'project3', project_namespace_id: nil, namespace_id: group.id, visibility_level: 20) }
let!(:project4) { projects.create!(name: 'project4', path: 'project4', project_namespace_id: nil, namespace_id: group.id, visibility_level: 20) }
describe '#up' do
it 'schedules background migrations' do
Sidekiq::Testing.fake! do
freeze_time do
described_class.new.up
migration = described_class::MIGRATION
expect(migration).to be_scheduled_delayed_migration(2.minutes, project1.id, project4.id)
expect(BackgroundMigrationWorker.jobs.size).to eq 1
end
end
end
context 'in batches' do
before do
stub_const('FixAndBackfillProjectNamespacesForProjectsWithDuplicateName::BATCH_SIZE', 2)
end
it 'schedules background migrations' do
Sidekiq::Testing.fake! do
freeze_time do
described_class.new.up
migration = described_class::MIGRATION
expect(migration).to be_scheduled_delayed_migration(2.minutes, project1.id, project3.id)
expect(migration).to be_scheduled_delayed_migration(4.minutes, project4.id, project4.id)
expect(BackgroundMigrationWorker.jobs.size).to eq 2
end
end
end
end
end
end

View File

@ -202,7 +202,7 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do
stub_feature_flags(ci_fix_order_of_subsequent_jobs: false)
end
it 'does not mark b1 as processable' do
it 'does not mark b1 as processable', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/356571' do
execute_after_requeue_service(a1)
check_jobs_statuses(

View File

@ -10,7 +10,7 @@ RSpec::Matchers.define :be_in_sync_with_project do |project|
project_namespace.present? &&
project.name == project_namespace.name &&
project.path == project_namespace.path &&
project.namespace == project_namespace.parent &&
project.namespace_id == project_namespace.parent_id &&
project.visibility_level == project_namespace.visibility_level &&
project.shared_runners_enabled == project_namespace.shared_runners_enabled
end

View File

@ -230,76 +230,6 @@ RSpec.shared_examples 'can collect git garbage' do |update_statistics: true|
stub_feature_flags(optimized_housekeeping: false)
end
it 'incremental repack adds a new packfile' do
create_objects(resource)
before_packs = packs(resource)
expect(before_packs.count).to be >= 1
subject.perform(resource.id, 'incremental_repack', lease_key, lease_uuid)
after_packs = packs(resource)
# Exactly one new pack should have been created
expect(after_packs.count).to eq(before_packs.count + 1)
# Previously existing packs are still around
expect(before_packs & after_packs).to eq(before_packs)
end
it 'full repack consolidates into 1 packfile' do
create_objects(resource)
subject.perform(resource.id, 'incremental_repack', lease_key, lease_uuid)
before_packs = packs(resource)
expect(before_packs.count).to be >= 2
subject.perform(resource.id, 'full_repack', lease_key, lease_uuid)
after_packs = packs(resource)
expect(after_packs.count).to eq(1)
# Previously existing packs should be gone now
expect(after_packs - before_packs).to eq(after_packs)
expect(File.exist?(bitmap_path(after_packs.first))).to eq(bitmaps_enabled)
end
it 'gc consolidates into 1 packfile and updates packed-refs' do
create_objects(resource)
before_packs = packs(resource)
before_packed_refs = packed_refs(resource)
expect(before_packs.count).to be >= 1
# It's quite difficult to use `expect_next_instance_of` in this place
# because the RepositoryService is instantiated several times to do
# some repository calls like `exists?`, `create_repository`, ... .
# Therefore, since we're instantiating the object several times,
# RSpec has troubles figuring out which instance is the next and which
# one we want to mock.
# Besides, at this point, we actually want to perform the call to Gitaly,
# otherwise we would just use `instance_double` like in other parts of the
# spec file.
expect_any_instance_of(Gitlab::GitalyClient::RepositoryService) # rubocop:disable RSpec/AnyInstanceOf
.to receive(:garbage_collect)
.with(bitmaps_enabled, prune: false)
.and_call_original
subject.perform(resource.id, 'gc', lease_key, lease_uuid)
after_packed_refs = packed_refs(resource)
after_packs = packs(resource)
expect(after_packs.count).to eq(1)
# Previously existing packs should be gone now
expect(after_packs - before_packs).to eq(after_packs)
# The packed-refs file should have been updated during 'git gc'
expect(before_packed_refs).not_to eq(after_packed_refs)
expect(File.exist?(bitmap_path(after_packs.first))).to eq(bitmaps_enabled)
end
it 'cleans up repository after finishing' do
expect(resource).to receive(:cleanup).and_call_original

View File

@ -20,62 +20,56 @@ RSpec.describe Tooling::Danger::ProductIntelligence do
allow(fake_helper).to receive(:changed_lines).and_return(changed_lines)
end
describe '#missing_labels' do
subject { product_intelligence.missing_labels }
describe '#check!' do
subject { product_intelligence.check! }
let(:markdown_formatted_list) { 'markdown formatted list' }
let(:review_pending_label) { 'product intelligence::review pending' }
let(:approved_label) { 'product intelligence::approved' }
let(:ci_env) { true }
let(:previous_label_to_add) { 'label_to_add' }
let(:labels_to_add) { [previous_label_to_add] }
let(:has_product_intelligence_label) { true }
before do
allow(fake_helper).to receive(:mr_has_labels?).and_return(false)
allow(fake_helper).to receive(:changes_by_category).and_return(product_intelligence: changed_files, database: ['other_files.yml'])
allow(fake_helper).to receive(:ci?).and_return(ci_env)
allow(fake_helper).to receive(:mr_has_labels?).with('product intelligence').and_return(has_product_intelligence_label)
allow(fake_helper).to receive(:markdown_list).with(changed_files).and_return(markdown_formatted_list)
allow(fake_helper).to receive(:labels_to_add).and_return(labels_to_add)
end
context 'with ci? false' do
let(:ci_env) { false }
shared_examples "doesn't add new labels" do
it "doesn't add new labels" do
subject
it { is_expected.to be_empty }
end
context 'with ci? true' do
let(:expected_labels) { ['product intelligence', 'product intelligence::review pending'] }
it { is_expected.to match_array(expected_labels) }
end
context 'with product intelligence label' do
let(:expected_labels) { ['product intelligence::review pending'] }
let(:mr_labels) { [] }
before do
allow(fake_helper).to receive(:mr_has_labels?).with('product intelligence').and_return(true)
allow(fake_helper).to receive(:mr_labels).and_return(mr_labels)
end
it { is_expected.to match_array(expected_labels) }
context 'with product intelligence::review pending' do
let(:mr_labels) { ['product intelligence::review pending'] }
it { is_expected.to be_empty }
end
context 'with product intelligence::approved' do
let(:mr_labels) { ['product intelligence::approved'] }
it { is_expected.to be_empty }
expect(labels_to_add).to match_array [previous_label_to_add]
end
end
end
describe '#skip_review' do
subject { product_intelligence.skip_review? }
shared_examples "doesn't add new warnings" do
it "doesn't add new warnings" do
expect(product_intelligence).not_to receive(:warn)
subject
end
end
shared_examples 'adds new labels' do
it 'adds new labels' do
subject
expect(labels_to_add).to match_array [previous_label_to_add, review_pending_label]
end
end
context 'with growth experiment label' do
before do
allow(fake_helper).to receive(:mr_has_labels?).with('growth experiment').and_return(true)
end
it { is_expected.to be true }
include_examples "doesn't add new labels"
include_examples "doesn't add new warnings"
end
context 'without growth experiment label' do
@ -83,7 +77,48 @@ RSpec.describe Tooling::Danger::ProductIntelligence do
allow(fake_helper).to receive(:mr_has_labels?).with('growth experiment').and_return(false)
end
it { is_expected.to be false }
context 'with approved label' do
let(:mr_labels) { [approved_label] }
include_examples "doesn't add new labels"
include_examples "doesn't add new warnings"
end
context 'without approved label' do
include_examples 'adds new labels'
it 'warns with proper message' do
expect(product_intelligence).to receive(:warn).with(%r{#{markdown_formatted_list}})
subject
end
end
context 'with product intelligence::review pending label' do
let(:mr_labels) { ['product intelligence::review pending'] }
include_examples "doesn't add new labels"
end
context 'with product intelligence::approved label' do
let(:mr_labels) { ['product intelligence::approved'] }
include_examples "doesn't add new labels"
end
context 'with the product intelligence label' do
let(:has_product_intelligence_label) { true }
context 'with ci? false' do
let(:ci_env) { false }
include_examples "doesn't add new labels"
end
context 'with ci? true' do
include_examples 'adds new labels'
end
end
end
end
end

View File

@ -6,12 +6,35 @@ module Tooling
module ProductIntelligence
APPROVED_LABEL = 'product intelligence::approved'
REVIEW_LABEL = 'product intelligence::review pending'
CHANGED_FILES_MESSAGE = <<~MSG
For the following files, a review from the [Data team and Product Intelligence team](https://gitlab.com/groups/gitlab-org/growth/product-intelligence/engineers/-/group_members?with_inherited_permissions=exclude) is recommended
Please check the ~"product intelligence" [guide](https://docs.gitlab.com/ee/development/usage_ping.html).
For MR review guidelines, see the [Service Ping review guidelines](https://docs.gitlab.com/ee/development/usage_ping/review_guidelines.html) or the [Snowplow review guidelines](https://docs.gitlab.com/ee/development/snowplow/review_guidelines.html).
%<changed_files>s
MSG
WORKFLOW_LABELS = [
APPROVED_LABEL,
REVIEW_LABEL
].freeze
def check!
# exit if not matching files or if no product intelligence labels
product_intelligence_paths_to_review = helper.changes_by_category[:product_intelligence]
labels_to_add = missing_labels
return if product_intelligence_paths_to_review.empty? || skip_review?
warn format(CHANGED_FILES_MESSAGE, changed_files: helper.markdown_list(product_intelligence_paths_to_review)) unless has_approved_label?
helper.labels_to_add.concat(labels_to_add) unless labels_to_add.empty?
end
private
def missing_labels
return [] unless helper.ci?
@ -30,8 +53,6 @@ module Tooling
helper.mr_has_labels?('growth experiment')
end
private
def has_workflow_labels?
(WORKFLOW_LABELS & helper.mr_labels).any?
end