Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
bf3e636cc0
commit
13f31ab91a
|
@ -1,43 +0,0 @@
|
|||
---
|
||||
# Cop supports --auto-correct.
|
||||
Rails/Pick:
|
||||
# Offense count: 42
|
||||
# Temporarily disabled due to too many offenses
|
||||
Enabled: false
|
||||
Exclude:
|
||||
- 'app/models/ci/pipeline.rb'
|
||||
- 'app/models/merge_request.rb'
|
||||
- 'app/models/merge_request/metrics.rb'
|
||||
- 'app/models/merge_request_diff.rb'
|
||||
- 'db/post_migrate/20210825193652_backfill_cadence_id_for_boards_scoped_to_iteration.rb'
|
||||
- 'db/post_migrate/20220213103859_remove_integrations_type.rb'
|
||||
- 'db/post_migrate/20220412143552_consume_remaining_encrypt_integration_property_jobs.rb'
|
||||
- 'ee/app/models/concerns/epic_tree_sorting.rb'
|
||||
- 'ee/app/models/ee/group.rb'
|
||||
- 'ee/app/models/ee/namespace.rb'
|
||||
- 'ee/app/models/geo/project_registry.rb'
|
||||
- 'ee/lib/analytics/merge_request_metrics_calculator.rb'
|
||||
- 'ee/lib/ee/gitlab/background_migration/backfill_iteration_cadence_id_for_boards.rb'
|
||||
- 'ee/lib/ee/gitlab/background_migration/populate_status_column_of_security_scans.rb'
|
||||
- 'ee/spec/finders/security/findings_finder_spec.rb'
|
||||
- 'lib/gitlab/background_migration/backfill_ci_namespace_mirrors.rb'
|
||||
- 'lib/gitlab/background_migration/backfill_ci_project_mirrors.rb'
|
||||
- 'lib/gitlab/background_migration/backfill_integrations_type_new.rb'
|
||||
- 'lib/gitlab/background_migration/backfill_namespace_traversal_ids_children.rb'
|
||||
- 'lib/gitlab/background_migration/backfill_namespace_traversal_ids_roots.rb'
|
||||
- 'lib/gitlab/background_migration/backfill_work_item_type_id_for_issues.rb'
|
||||
- 'lib/gitlab/background_migration/batching_strategies/backfill_project_namespace_per_group_batching_strategy.rb'
|
||||
- 'lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy.rb'
|
||||
- 'lib/gitlab/background_migration/cleanup_orphaned_lfs_objects_projects.rb'
|
||||
- 'lib/gitlab/background_migration/drop_invalid_security_findings.rb'
|
||||
- 'lib/gitlab/background_migration/encrypt_static_object_token.rb'
|
||||
- 'lib/gitlab/background_migration/fix_first_mentioned_in_commit_at.rb'
|
||||
- 'lib/gitlab/background_migration/populate_vulnerability_reads.rb'
|
||||
- 'lib/gitlab/background_migration/update_timelogs_null_spent_at.rb'
|
||||
- 'lib/gitlab/database/dynamic_model_helpers.rb'
|
||||
- 'lib/gitlab/database/migrations/background_migration_helpers.rb'
|
||||
- 'lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table.rb'
|
||||
- 'lib/gitlab/github_import/user_finder.rb'
|
||||
- 'lib/gitlab/relative_positioning/item_context.rb'
|
||||
- 'spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb'
|
||||
- 'spec/requests/projects/cycle_analytics_events_spec.rb'
|
|
@ -1 +1 @@
|
|||
8f39a5a9cdf3b8d544153e8297bcf639e5c626e7
|
||||
5ac494300c839eea0980d118d12ffb51f447c0ac
|
||||
|
|
|
@ -149,8 +149,11 @@ const defaultSerializerConfig = {
|
|||
state.renderInline(node);
|
||||
state.ensureNewLine();
|
||||
}),
|
||||
[FootnoteReference.name]: preserveUnchanged((state, node) => {
|
||||
state.write(`[^${node.attrs.identifier}]`);
|
||||
[FootnoteReference.name]: preserveUnchanged({
|
||||
render: (state, node) => {
|
||||
state.write(`[^${node.attrs.identifier}]`);
|
||||
},
|
||||
inline: true,
|
||||
}),
|
||||
[Frontmatter.name]: (state, node) => {
|
||||
const { language } = node.attrs;
|
||||
|
@ -171,7 +174,10 @@ const defaultSerializerConfig = {
|
|||
[HardBreak.name]: preserveUnchanged(renderHardBreak),
|
||||
[Heading.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.heading),
|
||||
[HorizontalRule.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.horizontal_rule),
|
||||
[Image.name]: preserveUnchanged(renderImage),
|
||||
[Image.name]: preserveUnchanged({
|
||||
render: renderImage,
|
||||
inline: true,
|
||||
}),
|
||||
[ListItem.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.list_item),
|
||||
[OrderedList.name]: preserveUnchanged(renderOrderedList),
|
||||
[Paragraph.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.paragraph),
|
||||
|
|
|
@ -330,12 +330,12 @@ export function renderCodeBlock(state, node) {
|
|||
|
||||
const expandPreserveUnchangedConfig = (configOrRender) =>
|
||||
isFunction(configOrRender)
|
||||
? { render: configOrRender, overwriteSourcePreservationStrategy: false }
|
||||
? { render: configOrRender, overwriteSourcePreservationStrategy: false, inline: false }
|
||||
: configOrRender;
|
||||
|
||||
export function preserveUnchanged(configOrRender) {
|
||||
return (state, node, parent, index) => {
|
||||
const { render, overwriteSourcePreservationStrategy } = expandPreserveUnchangedConfig(
|
||||
const { render, overwriteSourcePreservationStrategy, inline } = expandPreserveUnchangedConfig(
|
||||
configOrRender,
|
||||
);
|
||||
|
||||
|
@ -344,7 +344,10 @@ export function preserveUnchanged(configOrRender) {
|
|||
|
||||
if (same && !overwriteSourcePreservationStrategy) {
|
||||
state.write(sourceMarkdown);
|
||||
state.closeBlock(node);
|
||||
|
||||
if (!inline) {
|
||||
state.closeBlock(node);
|
||||
}
|
||||
} else {
|
||||
render(state, node, parent, index, same, sourceMarkdown);
|
||||
}
|
||||
|
|
|
@ -356,7 +356,7 @@ export default {
|
|||
data-testid="alert-member-error"
|
||||
>
|
||||
{{ $options.labels.memberErrorListText }}
|
||||
<ul class="gl-pl-5">
|
||||
<ul class="gl-pl-5 gl-mb-0">
|
||||
<li v-for="(error, member) in invalidMembers" :key="member">
|
||||
<strong>{{ tokenName(member) }}:</strong> {{ error }}
|
||||
</li>
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
<script>
|
||||
import { GlTokenSelector, GlAvatar, GlAvatarLabeled, GlIcon, GlSprintf } from '@gitlab/ui';
|
||||
import { debounce } from 'lodash';
|
||||
import { debounce, isEmpty } from 'lodash';
|
||||
import { __ } from '~/locale';
|
||||
import { getUsers } from '~/rest_api';
|
||||
import { memberName } from '../utils/member_utils';
|
||||
import { SEARCH_DELAY, USERS_FILTER_ALL, USERS_FILTER_SAML_PROVIDER_ID } from '../constants';
|
||||
import {
|
||||
SEARCH_DELAY,
|
||||
USERS_FILTER_ALL,
|
||||
USERS_FILTER_SAML_PROVIDER_ID,
|
||||
VALID_TOKEN_BACKGROUND,
|
||||
INVALID_TOKEN_BACKGROUND,
|
||||
} from '../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -75,6 +81,25 @@ export default {
|
|||
}
|
||||
return this.$options.defaultQueryOptions;
|
||||
},
|
||||
hasInvalidMembers() {
|
||||
return !isEmpty(this.invalidMembers);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// We might not really want this to be *reactive* since we want the "class" state to be
|
||||
// tied to the specific `selectedToken` such that if the token is removed and re-added, this
|
||||
// state is reset.
|
||||
// See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90076#note_1027165312
|
||||
hasInvalidMembers: {
|
||||
handler(updatedInvalidMembers) {
|
||||
// Only update tokens if we receive invalid members
|
||||
if (!updatedInvalidMembers) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateTokenClasses();
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleTextInput(query) {
|
||||
|
@ -83,6 +108,12 @@ export default {
|
|||
this.loading = true;
|
||||
this.retrieveUsers(query);
|
||||
},
|
||||
updateTokenClasses() {
|
||||
this.selectedTokens = this.selectedTokens.map((token) => ({
|
||||
...token,
|
||||
class: this.tokenClass(token),
|
||||
}));
|
||||
},
|
||||
retrieveUsers: debounce(function debouncedRetrieveUsers() {
|
||||
return getUsers(this.query, this.queryOptions)
|
||||
.then((response) => {
|
||||
|
@ -98,6 +129,14 @@ export default {
|
|||
this.loading = false;
|
||||
});
|
||||
}, SEARCH_DELAY),
|
||||
tokenClass(token) {
|
||||
if (this.hasError(token)) {
|
||||
return INVALID_TOKEN_BACKGROUND;
|
||||
}
|
||||
|
||||
// assume success for this token
|
||||
return VALID_TOKEN_BACKGROUND;
|
||||
},
|
||||
handleInput() {
|
||||
this.$emit('input', this.selectedTokens);
|
||||
},
|
||||
|
|
|
@ -2,6 +2,8 @@ import { s__ } from '~/locale';
|
|||
|
||||
export const CLOSE_TO_LIMIT_COUNT = 2;
|
||||
export const SEARCH_DELAY = 200;
|
||||
export const VALID_TOKEN_BACKGROUND = 'gl-bg-green-100';
|
||||
export const INVALID_TOKEN_BACKGROUND = 'gl-bg-red-100';
|
||||
export const INVITE_MEMBERS_FOR_TASK = {
|
||||
minimum_access_level: 30,
|
||||
name: 'invite_members_for_task',
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { GlIcon, GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
||||
import { produce } from 'immer';
|
||||
import { s__ } from '~/locale';
|
||||
import changeWorkItemParentMutation from '../../graphql/change_work_item_parent_link.mutation.graphql';
|
||||
import updateWorkItem from '../../graphql/update_work_item.mutation.graphql';
|
||||
import getWorkItemLinksQuery from '../../graphql/work_item_links.query.graphql';
|
||||
import { WIDGET_TYPE_HIERARCHY } from '../../constants';
|
||||
|
||||
|
@ -58,8 +58,10 @@ export default {
|
|||
},
|
||||
async addChild(data) {
|
||||
const { data: resp } = await this.$apollo.mutate({
|
||||
mutation: changeWorkItemParentMutation,
|
||||
variables: { id: this.workItemId, parentId: this.parentWorkItemId },
|
||||
mutation: updateWorkItem,
|
||||
variables: {
|
||||
input: { id: this.workItemId, hierarchyWidget: { parentId: this.parentWorkItemId } },
|
||||
},
|
||||
update: this.toggleChildFromCache.bind(this, data),
|
||||
});
|
||||
|
||||
|
@ -69,8 +71,8 @@ export default {
|
|||
},
|
||||
async removeChild() {
|
||||
const { data } = await this.$apollo.mutate({
|
||||
mutation: changeWorkItemParentMutation,
|
||||
variables: { id: this.workItemId, parentId: null },
|
||||
mutation: updateWorkItem,
|
||||
variables: { input: { id: this.workItemId, hierarchyWidget: { parentId: null } } },
|
||||
update: this.toggleChildFromCache.bind(this, null),
|
||||
});
|
||||
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
mutation changeWorkItemParentLink($id: WorkItemID!, $parentId: WorkItemID) {
|
||||
workItemUpdate(input: { id: $id, hierarchyWidget: { parentId: $parentId } }) {
|
||||
workItem {
|
||||
id
|
||||
workItemType {
|
||||
id
|
||||
}
|
||||
title
|
||||
state
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
|
@ -478,6 +478,11 @@ span.idiff {
|
|||
background-color: transparent;
|
||||
border: transparent;
|
||||
}
|
||||
|
||||
.gl-dark & {
|
||||
background: transparent;
|
||||
filter: invert(1) hue-rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.code-navigation-line:hover {
|
||||
|
|
|
@ -390,7 +390,7 @@ module Ci
|
|||
end
|
||||
|
||||
def self.latest_status(ref = nil)
|
||||
newest_first(ref: ref).pluck(:status).first
|
||||
newest_first(ref: ref).pick(:status)
|
||||
end
|
||||
|
||||
def self.latest_successful_for_ref(ref)
|
||||
|
|
|
@ -430,8 +430,7 @@ class MergeRequest < ApplicationRecord
|
|||
def self.total_time_to_merge
|
||||
join_metrics
|
||||
.merge(MergeRequest::Metrics.with_valid_time_to_merge)
|
||||
.pluck(MergeRequest::Metrics.time_to_merge_expression)
|
||||
.first
|
||||
.pick(MergeRequest::Metrics.time_to_merge_expression)
|
||||
end
|
||||
|
||||
after_save :keep_around_commit, unless: :importing?
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MergeRequest::ApprovalRemovalSettings # rubocop:disable Style/ClassAndModuleChildren
|
||||
include ActiveModel::Validations
|
||||
|
||||
attr_accessor :project
|
||||
|
||||
validate :mutually_exclusive_settings
|
||||
|
||||
def initialize(project, reset_approvals_on_push, selective_code_owner_removals)
|
||||
@project = project
|
||||
@reset_approvals_on_push = reset_approvals_on_push
|
||||
@selective_code_owner_removals = selective_code_owner_removals
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def selective_code_owner_removals
|
||||
if @selective_code_owner_removals.nil?
|
||||
project.project_setting.selective_code_owner_removals
|
||||
else
|
||||
@selective_code_owner_removals
|
||||
end
|
||||
end
|
||||
|
||||
def reset_approvals_on_push
|
||||
if @reset_approvals_on_push.nil?
|
||||
project.reset_approvals_on_push
|
||||
else
|
||||
@reset_approvals_on_push
|
||||
end
|
||||
end
|
||||
|
||||
def mutually_exclusive_settings
|
||||
return unless selective_code_owner_removals && reset_approvals_on_push
|
||||
|
||||
errors.add(:base, 'selective_code_owner_removals can only be enabled when reset_approvals_on_push is disabled')
|
||||
end
|
||||
end
|
|
@ -41,8 +41,7 @@ class MergeRequest::Metrics < ApplicationRecord
|
|||
|
||||
def self.total_time_to_merge
|
||||
with_valid_time_to_merge
|
||||
.pluck(time_to_merge_expression)
|
||||
.first
|
||||
.pick(time_to_merge_expression)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -706,8 +706,7 @@ class MergeRequestDiff < ApplicationRecord
|
|||
latest_id = MergeRequest
|
||||
.where(id: merge_request_id)
|
||||
.limit(1)
|
||||
.pluck(:latest_merge_request_diff_id)
|
||||
.first
|
||||
.pick(:latest_merge_request_diff_id)
|
||||
|
||||
latest_id && self.id < latest_id
|
||||
end
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddSelectiveCodeOwnerRemovalsToProjectSettings < Gitlab::Database::Migration[2.0]
|
||||
enable_lock_retries!
|
||||
|
||||
def change
|
||||
add_column :project_settings, :selective_code_owner_removals, :boolean, default: false, null: false
|
||||
end
|
||||
end
|
|
@ -1,23 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ScheduleUpdateJiraTrackerDataDeploymentTypeBasedOnUrl < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
MIGRATION = 'UpdateJiraTrackerDataDeploymentTypeBasedOnUrl'
|
||||
DELAY_INTERVAL = 2.minutes.to_i
|
||||
BATCH_SIZE = 2_500
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
say "Scheduling #{MIGRATION} jobs"
|
||||
queue_background_migration_jobs_by_range_at_intervals(
|
||||
define_batchable_model('jira_tracker_data'),
|
||||
MIGRATION,
|
||||
DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE
|
||||
)
|
||||
# no-op (being re-run in 20220324152945_update_jira_tracker_data_deployment_type_based_on_url.rb)
|
||||
# due to this migration causing this issue: https://gitlab.com/gitlab-org/gitlab/-/issues/336849
|
||||
# The migration is rescheduled in
|
||||
# db/post_migrate/20220725150127_update_jira_tracker_data_deployment_type_based_on_url.rb
|
||||
# Related discussion: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82103#note_862401816
|
||||
end
|
||||
|
||||
def down
|
||||
|
|
|
@ -20,7 +20,7 @@ class BackfillCadenceIdForBoardsScopedToIteration < Gitlab::Database::Migration[
|
|||
|
||||
def down
|
||||
MigrationBoard.where.not(iteration_cadence_id: nil).each_batch(of: BATCH_SIZE) do |batch, index|
|
||||
range = batch.pluck(Arel.sql('MIN(id)'), Arel.sql('MAX(id)')).first
|
||||
range = batch.pick(Arel.sql('MIN(id)'), Arel.sql('MAX(id)'))
|
||||
delay = index * DELAY
|
||||
|
||||
migrate_in(delay, MIGRATION, ['none', 'down', *range])
|
||||
|
@ -31,7 +31,7 @@ class BackfillCadenceIdForBoardsScopedToIteration < Gitlab::Database::Migration[
|
|||
|
||||
def schedule_backfill_project_boards
|
||||
MigrationBoard.where(iteration_id: -4).where.not(project_id: nil).where(iteration_cadence_id: nil).each_batch(of: BATCH_SIZE) do |batch, index|
|
||||
range = batch.pluck(Arel.sql('MIN(id)'), Arel.sql('MAX(id)')).first
|
||||
range = batch.pick(Arel.sql('MIN(id)'), Arel.sql('MAX(id)'))
|
||||
delay = index * DELAY
|
||||
|
||||
migrate_in(delay, MIGRATION, ['project', 'up', *range])
|
||||
|
@ -40,7 +40,7 @@ class BackfillCadenceIdForBoardsScopedToIteration < Gitlab::Database::Migration[
|
|||
|
||||
def schedule_backfill_group_boards
|
||||
MigrationBoard.where(iteration_id: -4).where.not(group_id: nil).where(iteration_cadence_id: nil).each_batch(of: BATCH_SIZE) do |batch, index|
|
||||
range = batch.pluck(Arel.sql('MIN(id)'), Arel.sql('MAX(id)')).first
|
||||
range = batch.pick(Arel.sql('MIN(id)'), Arel.sql('MAX(id)'))
|
||||
delay = index * DELAY
|
||||
|
||||
migrate_in(delay, MIGRATION, ['group', 'up', *range])
|
||||
|
|
|
@ -73,7 +73,7 @@ class RemoveIntegrationsType < Gitlab::Database::Migration[1.0]
|
|||
add_concurrent_index :integrations, :id, where: 'type_new is null', name: tmp_index_name
|
||||
|
||||
define_batchable_model(:integrations).where(type_new: nil).each_batch do |batch|
|
||||
min_id, max_id = batch.pluck(Arel.sql('MIN(id), MAX(id)')).first
|
||||
min_id, max_id = batch.pick(Arel.sql('MIN(id), MAX(id)'))
|
||||
|
||||
connection.execute(<<~SQL)
|
||||
WITH mapping(old_type, new_type) AS (VALUES
|
||||
|
|
|
@ -12,7 +12,7 @@ class ConsumeRemainingEncryptIntegrationPropertyJobs < Gitlab::Database::Migrati
|
|||
relation = model.where.not(properties: nil).where(encrypted_properties: nil)
|
||||
|
||||
relation.each_batch(of: BATCH_SIZE) do |batch|
|
||||
range = batch.pluck('MIN(id)', 'MAX(id)').first
|
||||
range = batch.pick('MIN(id)', 'MAX(id)')
|
||||
|
||||
Gitlab::BackgroundMigration::EncryptIntegrationProperties.new.perform(*range)
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ class FinalizeBackfillNullNoteDiscussionIds < Gitlab::Database::Migration[2.0]
|
|||
Gitlab::BackgroundMigration.steal(MIGRATION)
|
||||
|
||||
define_batchable_model('notes').where(discussion_id: nil).each_batch(of: BATCH_SIZE) do |batch|
|
||||
range = batch.pluck('MIN(id)', 'MAX(id)').first
|
||||
range = batch.pick('MIN(id)', 'MAX(id)')
|
||||
|
||||
Gitlab::BackgroundMigration::BackfillNoteDiscussionId.new.perform(*range)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UpdateJiraTrackerDataDeploymentTypeBasedOnUrl < Gitlab::Database::Migration[2.0]
|
||||
MIGRATION = 'UpdateJiraTrackerDataDeploymentTypeBasedOnUrl'
|
||||
DELAY_INTERVAL = 2.minutes.to_i
|
||||
BATCH_SIZE = 2_500
|
||||
SUB_BATCH_SIZE = 2_500
|
||||
|
||||
disable_ddl_transaction!
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
def up
|
||||
say "Scheduling #{MIGRATION} jobs"
|
||||
delete_queued_jobs(MIGRATION)
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:jira_tracker_data,
|
||||
:id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
34a9ec48e8480f3a235089f01944f60e93e4b87909a660f18a42bc47a3a0fe51
|
|
@ -0,0 +1 @@
|
|||
78563f41df5a49803c59b4e41845c985fd1e5f19b1050998fb78d53a9dfe7a28
|
|
@ -19723,6 +19723,7 @@ CREATE TABLE project_settings (
|
|||
legacy_open_source_license_available boolean DEFAULT true NOT NULL,
|
||||
target_platforms character varying[] DEFAULT '{}'::character varying[] NOT NULL,
|
||||
enforce_auth_checks_on_uploads boolean DEFAULT true NOT NULL,
|
||||
selective_code_owner_removals boolean DEFAULT false NOT NULL,
|
||||
CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)),
|
||||
CONSTRAINT check_b09644994b CHECK ((char_length(squash_commit_template) <= 500)),
|
||||
CONSTRAINT check_bde223416c CHECK ((show_default_award_emojis IS NOT NULL)),
|
||||
|
|
|
@ -19,7 +19,9 @@ To view or edit merge request approval settings:
|
|||
1. Go to your project and select **Settings > General**.
|
||||
1. Expand **Merge request (MR) approvals**.
|
||||
|
||||
In this section of general settings, you can configure the following settings:
|
||||
### Approval settings
|
||||
|
||||
These settings limit who can approve merge requests.
|
||||
|
||||
| Setting | Description |
|
||||
| ------ | ------ |
|
||||
|
@ -27,7 +29,14 @@ In this section of general settings, you can configure the following settings:
|
|||
| [Prevent approvals by users who add commits](#prevent-approvals-by-users-who-add-commits) | When enabled, users who have committed to a merge request cannot approve it. |
|
||||
| [Prevent editing approval rules in merge requests](#prevent-editing-approval-rules-in-merge-requests) | When enabled, users can't override the project's approval rules on merge requests. |
|
||||
| [Require user password to approve](#require-user-password-to-approve) | Force potential approvers to first authenticate with a password. |
|
||||
| [Remove all approvals when commits are added to the source branch](#remove-all-approvals-when-commits-are-added-to-the-source-branch) | When enabled, remove all existing approvals on a merge request when more changes are added to it. |
|
||||
|
||||
You can further define what happens to existing approvals when commits are added to the merge request.
|
||||
|
||||
| Setting | Description |
|
||||
| ------ | ------ |
|
||||
| Keep approvals | Do not remove approvals. |
|
||||
| [Remove all approvals](#remove-all-approvals-when-commits-are-added-to-the-source-branch) | Remove all existing approvals. |
|
||||
| [Remove approvals by Code Owners if their files changed](#remove-approvals-by-code-owners-if-their-files-changed) | If a Code Owner has approved the merge request, and the commit changes files they are the Code Owner for, their approval is removed. |
|
||||
|
||||
## Prevent approval by author
|
||||
|
||||
|
@ -119,9 +128,21 @@ when more changes are added to it:
|
|||
1. Select the **Remove all approvals when commits are added to the source branch** checkbox.
|
||||
1. Select **Save changes**.
|
||||
|
||||
Approvals aren't reset when a merge request is [rebased from the UI](../methods/index.md#rebasing-in-semi-linear-merge-methods).
|
||||
Approvals aren't removed when a merge request is [rebased from the UI](../methods/index.md#rebasing-in-semi-linear-merge-methods)
|
||||
However, approvals are reset if the target branch is changed.
|
||||
|
||||
## Remove approvals by Code Owners if their files changed
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90578) in GitLab 15.3.
|
||||
|
||||
If you only want to remove approvals by Code Owners whose files have been changed:
|
||||
|
||||
1. On the top bar, select **Menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Settings > General**.
|
||||
1. Expand **Merge request (MR) approvals**.
|
||||
1. Select **Remove approvals by Code Owners if their files changed**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Code coverage check approvals
|
||||
|
||||
You can require specific approvals if a merge request would result in a decline in code test
|
||||
|
|
|
@ -21,7 +21,7 @@ module Gitlab
|
|||
def perform(start_id, end_id)
|
||||
batch_query = Namespace.base_query.where(id: start_id..end_id)
|
||||
batch_query.each_batch(of: SUB_BATCH_SIZE) do |sub_batch|
|
||||
first, last = sub_batch.pluck(Arel.sql('MIN(id), MAX(id)')).first
|
||||
first, last = sub_batch.pick(Arel.sql('MIN(id), MAX(id)'))
|
||||
ranged_query = Namespace.unscoped.base_query.where(id: first..last)
|
||||
|
||||
update_sql = <<~SQL
|
||||
|
|
|
@ -20,7 +20,7 @@ module Gitlab
|
|||
def perform(start_id, end_id)
|
||||
batch_query = Project.base_query.where(id: start_id..end_id)
|
||||
batch_query.each_batch(of: SUB_BATCH_SIZE) do |sub_batch|
|
||||
first, last = sub_batch.pluck(Arel.sql('MIN(id), MAX(id)')).first
|
||||
first, last = sub_batch.pick(Arel.sql('MIN(id), MAX(id)'))
|
||||
ranged_query = Project.unscoped.base_query.where(id: first..last)
|
||||
|
||||
update_sql = <<~SQL
|
||||
|
|
|
@ -27,7 +27,7 @@ module Gitlab
|
|||
|
||||
def process_sub_batch(sub_batch)
|
||||
# Extract the start/stop IDs from the current sub-batch
|
||||
sub_start_id, sub_stop_id = sub_batch.pluck(Arel.sql('MIN(id), MAX(id)')).first
|
||||
sub_start_id, sub_stop_id = sub_batch.pick(Arel.sql('MIN(id), MAX(id)'))
|
||||
|
||||
# This matches the mapping from the INSERT trigger added in
|
||||
# db/migrate/20210721135638_add_triggers_to_integrations_type_new.rb
|
||||
|
|
|
@ -19,7 +19,7 @@ module Gitlab
|
|||
def perform(start_id, end_id, sub_batch_size)
|
||||
batch_query = Namespace.base_query.where(id: start_id..end_id)
|
||||
batch_query.each_batch(of: sub_batch_size) do |sub_batch|
|
||||
first, last = sub_batch.pluck(Arel.sql('min(id), max(id)')).first
|
||||
first, last = sub_batch.pick(Arel.sql('min(id), max(id)'))
|
||||
ranged_query = Namespace.unscoped.base_query.where(id: first..last)
|
||||
|
||||
update_sql = <<~SQL
|
||||
|
|
|
@ -22,7 +22,7 @@ module Gitlab
|
|||
.where("traversal_ids = '{}'")
|
||||
|
||||
ranged_query.each_batch(of: sub_batch_size) do |sub_batch|
|
||||
first, last = sub_batch.pluck(Arel.sql('min(id), max(id)')).first
|
||||
first, last = sub_batch.pick(Arel.sql('min(id), max(id)'))
|
||||
|
||||
# The query need to be reconstructed because .each_batch modifies the default scope
|
||||
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/330510
|
||||
|
|
|
@ -20,7 +20,7 @@ module Gitlab
|
|||
parent_batch_relation = relation_scoped_to_range(batch_table, batch_column, start_id, end_id, base_type)
|
||||
|
||||
parent_batch_relation.each_batch(column: batch_column, of: sub_batch_size) do |sub_batch|
|
||||
first, last = sub_batch.pluck(Arel.sql('min(id), max(id)')).first
|
||||
first, last = sub_batch.pick(Arel.sql('min(id), max(id)'))
|
||||
|
||||
# The query need to be reconstructed because .each_batch modifies the default scope
|
||||
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/330510
|
||||
|
|
|
@ -25,7 +25,7 @@ module Gitlab
|
|||
relation = model_class.where(projects_table[:namespace_id].in(hierarchy_cte_sql)).where("#{quoted_column_name} >= ?", batch_min_value)
|
||||
|
||||
relation.each_batch(of: batch_size, column: column_name) do |batch| # rubocop:disable Lint/UnreachableLoop
|
||||
next_batch_bounds = batch.pluck(Arel.sql("MIN(#{quoted_column_name}), MAX(#{quoted_column_name})")).first
|
||||
next_batch_bounds = batch.pick(Arel.sql("MIN(#{quoted_column_name}), MAX(#{quoted_column_name})"))
|
||||
|
||||
break
|
||||
end
|
||||
|
|
|
@ -24,7 +24,7 @@ module Gitlab
|
|||
next_batch_bounds = nil
|
||||
|
||||
relation.distinct_each_batch(of: batch_size, column: column_name) do |batch| # rubocop:disable Lint/UnreachableLoop
|
||||
next_batch_bounds = batch.pluck(Arel.sql("MIN(#{quoted_column_name}), MAX(#{quoted_column_name})")).first
|
||||
next_batch_bounds = batch.pick(Arel.sql("MIN(#{quoted_column_name}), MAX(#{quoted_column_name})"))
|
||||
|
||||
break
|
||||
end
|
||||
|
|
|
@ -28,7 +28,7 @@ module Gitlab
|
|||
next_batch_bounds = nil
|
||||
|
||||
relation.each_batch(of: batch_size, column: column_name) do |batch| # rubocop:disable Lint/UnreachableLoop
|
||||
next_batch_bounds = batch.pluck(Arel.sql("MIN(#{quoted_column_name}), MAX(#{quoted_column_name})")).first
|
||||
next_batch_bounds = batch.pick(Arel.sql("MIN(#{quoted_column_name}), MAX(#{quoted_column_name})"))
|
||||
|
||||
break
|
||||
end
|
||||
|
|
|
@ -62,7 +62,7 @@ module Gitlab
|
|||
batch = LfsObjectsProject.where(id: start_id..end_id)
|
||||
|
||||
batch.each_batch(of: SUB_BATCH_SIZE) do |sub_batch|
|
||||
first, last = sub_batch.pluck(Arel.sql('min(lfs_objects_projects.id), max(lfs_objects_projects.id)')).first
|
||||
first, last = sub_batch.pick(Arel.sql('min(lfs_objects_projects.id), max(lfs_objects_projects.id)'))
|
||||
|
||||
lfs_objects_without_association =
|
||||
LfsObjectsProject
|
||||
|
|
|
@ -19,7 +19,7 @@ module Gitlab
|
|||
.no_uuid
|
||||
|
||||
ranged_query.each_batch(of: sub_batch_size) do |sub_batch|
|
||||
first, last = sub_batch.pluck(Arel.sql('min(id), max(id)')).first
|
||||
first, last = sub_batch.pick(Arel.sql('min(id), max(id)'))
|
||||
|
||||
# The query need to be reconstructed because .each_batch modifies the default scope
|
||||
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/330510
|
||||
|
|
|
@ -23,7 +23,7 @@ module Gitlab
|
|||
.without_static_object_token_encrypted
|
||||
|
||||
ranged_query.each_batch(of: BATCH_SIZE) do |sub_batch|
|
||||
first, last = sub_batch.pluck(Arel.sql('min(id), max(id)')).first
|
||||
first, last = sub_batch.pick(Arel.sql('min(id), max(id)'))
|
||||
|
||||
batch_query = User.unscoped
|
||||
.where(id: first..last)
|
||||
|
|
|
@ -29,7 +29,7 @@ module Gitlab
|
|||
|
||||
def perform(start_id, end_id)
|
||||
scope(start_id, end_id).each_batch(of: SUB_BATCH_SIZE, column: :issue_id) do |sub_batch|
|
||||
first, last = sub_batch.pluck(Arel.sql('min(issue_id), max(issue_id)')).first
|
||||
first, last = sub_batch.pick(Arel.sql('min(issue_id), max(issue_id)'))
|
||||
|
||||
# The query need to be reconstructed because .each_batch modifies the default scope
|
||||
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/330510
|
||||
|
|
|
@ -10,7 +10,7 @@ module Gitlab
|
|||
|
||||
def perform(start_id, end_id, sub_batch_size)
|
||||
vulnerability_model.where(id: start_id..end_id).each_batch(of: sub_batch_size) do |sub_batch|
|
||||
first, last = sub_batch.pluck(Arel.sql('min(id), max(id)')).first
|
||||
first, last = sub_batch.pick(Arel.sql('min(id), max(id)'))
|
||||
connection.execute(insert_query(first, last))
|
||||
|
||||
sleep PAUSE_SECONDS
|
||||
|
|
|
@ -1,42 +1,74 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# rubocop: disable Style/Documentation
|
||||
class Gitlab::BackgroundMigration::UpdateJiraTrackerDataDeploymentTypeBasedOnUrl
|
||||
# rubocop: disable Gitlab/NamespacedClass
|
||||
class JiraTrackerData < ActiveRecord::Base
|
||||
self.table_name = "jira_tracker_data"
|
||||
self.inheritance_column = :_type_disabled
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
class UpdateJiraTrackerDataDeploymentTypeBasedOnUrl < Gitlab::BackgroundMigration::BatchedMigrationJob
|
||||
# rubocop: disable Gitlab/NamespacedClass
|
||||
class JiraTrackerData < ActiveRecord::Base
|
||||
self.table_name = "jira_tracker_data"
|
||||
self.inheritance_column = :_type_disabled
|
||||
|
||||
include ::Integrations::BaseDataFields
|
||||
attr_encrypted :url, encryption_options
|
||||
attr_encrypted :api_url, encryption_options
|
||||
include ::Integrations::BaseDataFields
|
||||
attr_encrypted :url, encryption_options
|
||||
attr_encrypted :api_url, encryption_options
|
||||
|
||||
enum deployment_type: { unknown: 0, server: 1, cloud: 2 }, _prefix: :deployment
|
||||
end
|
||||
# rubocop: enable Gitlab/NamespacedClass
|
||||
enum deployment_type: { unknown: 0, server: 1, cloud: 2 }, _prefix: :deployment
|
||||
end
|
||||
# rubocop: enable Gitlab/NamespacedClass
|
||||
|
||||
# https://rubular.com/r/uwgK7k9KH23efa
|
||||
JIRA_CLOUD_REGEX = %r{^https?://[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.atlassian\.net$}ix.freeze
|
||||
# https://rubular.com/r/uwgK7k9KH23efa
|
||||
JIRA_CLOUD_REGEX = %r{^https?://[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.atlassian\.net$}ix.freeze
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def perform(start_id, end_id)
|
||||
trackers_data = JiraTrackerData
|
||||
.where(deployment_type: 'unknown')
|
||||
.where(id: start_id..end_id)
|
||||
def perform
|
||||
cloud = []
|
||||
server = []
|
||||
unknown = []
|
||||
|
||||
cloud, server = trackers_data.partition { |tracker_data| tracker_data.url.match?(JIRA_CLOUD_REGEX) }
|
||||
trackers_data.each do |tracker_data|
|
||||
client_url = tracker_data.api_url.presence || tracker_data.url
|
||||
|
||||
cloud_mappings = cloud.each_with_object({}) do |tracker_data, hash|
|
||||
hash[tracker_data] = { deployment_type: 2 }
|
||||
if client_url.blank?
|
||||
unknown << tracker_data
|
||||
elsif client_url.match?(JIRA_CLOUD_REGEX)
|
||||
cloud << tracker_data
|
||||
else
|
||||
server << tracker_data
|
||||
end
|
||||
end
|
||||
|
||||
cloud_mappings = cloud.each_with_object({}) do |tracker_data, hash|
|
||||
hash[tracker_data] = { deployment_type: 2 }
|
||||
end
|
||||
|
||||
server_mappings = server.each_with_object({}) do |tracker_data, hash|
|
||||
hash[tracker_data] = { deployment_type: 1 }
|
||||
end
|
||||
|
||||
unknown_mappings = unknown.each_with_object({}) do |tracker_data, hash|
|
||||
hash[tracker_data] = { deployment_type: 0 }
|
||||
end
|
||||
|
||||
mappings = cloud_mappings.merge(server_mappings, unknown_mappings)
|
||||
|
||||
update_records(mappings)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_records(mappings)
|
||||
return if mappings.empty?
|
||||
|
||||
::Gitlab::Database::BulkUpdate.execute(%i[deployment_type], mappings)
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def trackers_data
|
||||
@trackers_data ||= JiraTrackerData
|
||||
.where(deployment_type: 'unknown')
|
||||
.where(batch_column => start_id..end_id)
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
||||
server_mapppings = server.each_with_object({}) do |tracker_data, hash|
|
||||
hash[tracker_data] = { deployment_type: 1 }
|
||||
end
|
||||
|
||||
mappings = cloud_mappings.merge(server_mapppings)
|
||||
|
||||
::Gitlab::Database::BulkUpdate.execute(%i[deployment_type], mappings)
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ module Gitlab
|
|||
define_batchable_model('timelogs', connection: connection)
|
||||
.where(spent_at: nil, id: start_id..stop_id)
|
||||
.each_batch(of: 100) do |subbatch|
|
||||
batch_start, batch_end = subbatch.pluck('min(id), max(id)').first
|
||||
batch_start, batch_end = subbatch.pick('min(id), max(id)')
|
||||
|
||||
update_timelogs(batch_start, batch_end)
|
||||
end
|
||||
|
|
|
@ -32,7 +32,7 @@ module Gitlab
|
|||
|
||||
def each_batch_range(table_name, connection:, scope: ->(table) { table.all }, of: BATCH_SIZE)
|
||||
each_batch(table_name, connection: connection, scope: scope, of: of) do |batch|
|
||||
yield batch.pluck('MIN(id), MAX(id)').first
|
||||
yield batch.pick('MIN(id), MAX(id)')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -76,7 +76,7 @@ module Gitlab
|
|||
max = relation.arel_table[primary_column_name].maximum
|
||||
min = relation.arel_table[primary_column_name].minimum
|
||||
|
||||
start_id, end_id = relation.pluck(min, max).first
|
||||
start_id, end_id = relation.pick(min, max)
|
||||
|
||||
# `SingleDatabaseWorker.bulk_perform_in` schedules all jobs for
|
||||
# the same time, which is not helpful in most cases where we wish to
|
||||
|
|
|
@ -25,7 +25,7 @@ module Gitlab
|
|||
parent_batch_relation = relation_scoped_to_range(source_table, source_column, start_id, stop_id)
|
||||
|
||||
parent_batch_relation.each_batch(of: SUB_BATCH_SIZE) do |sub_batch|
|
||||
sub_start_id, sub_stop_id = sub_batch.pluck(Arel.sql("MIN(#{source_column}), MAX(#{source_column})")).first
|
||||
sub_start_id, sub_stop_id = sub_batch.pick(Arel.sql("MIN(#{source_column}), MAX(#{source_column})"))
|
||||
|
||||
bulk_copy.copy_between(sub_start_id, sub_stop_id)
|
||||
sleep(PAUSE_SECONDS)
|
||||
|
|
|
@ -158,7 +158,7 @@ module Gitlab
|
|||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def query_id_for_github_email(email)
|
||||
User.by_any_email(email).pluck(:id).first
|
||||
User.by_any_email(email).pick(:id)
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
|
|
|
@ -91,8 +91,7 @@ module Gitlab
|
|||
relation = yield relation if block_given?
|
||||
|
||||
relation
|
||||
.pluck(grouping_column, Arel.sql("#{calculation}(relative_position) AS position"))
|
||||
.first&.last
|
||||
.pick(grouping_column, Arel.sql("#{calculation}(relative_position) AS position"))&.last
|
||||
end
|
||||
|
||||
def grouping_column
|
||||
|
@ -164,8 +163,7 @@ module Gitlab
|
|||
.from(items_with_next_pos, :items)
|
||||
.where('next_pos IS NULL OR ABS(pos::bigint - next_pos::bigint) >= ?', MIN_GAP)
|
||||
.limit(1)
|
||||
.pluck(:pos, :next_pos)
|
||||
.first
|
||||
.pick(:pos, :next_pos)
|
||||
|
||||
return if gap.nil? || gap.first == default_end
|
||||
|
||||
|
|
|
@ -4792,6 +4792,9 @@ msgstr ""
|
|||
msgid "ApprovalRule|Try for free"
|
||||
msgstr ""
|
||||
|
||||
msgid "ApprovalSettings|Keep approvals"
|
||||
msgstr ""
|
||||
|
||||
msgid "ApprovalSettings|Merge request approval settings have been updated."
|
||||
msgstr ""
|
||||
|
||||
|
@ -4810,7 +4813,10 @@ msgstr ""
|
|||
msgid "ApprovalSettings|Prevent editing approval rules in projects and merge requests."
|
||||
msgstr ""
|
||||
|
||||
msgid "ApprovalSettings|Remove all approvals when commits are added to the source branch"
|
||||
msgid "ApprovalSettings|Remove all approvals"
|
||||
msgstr ""
|
||||
|
||||
msgid "ApprovalSettings|Remove approvals by Code Owners if their files changed"
|
||||
msgstr ""
|
||||
|
||||
msgid "ApprovalSettings|Require user password to approve"
|
||||
|
@ -4828,6 +4834,9 @@ msgstr ""
|
|||
msgid "ApprovalSettings|This setting is configured in %{groupName} and can only be changed in the group settings by an administrator or group owner."
|
||||
msgstr ""
|
||||
|
||||
msgid "ApprovalSettings|When a commit is added:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Approvals are optional."
|
||||
msgstr ""
|
||||
|
||||
|
@ -23022,9 +23031,6 @@ msgstr ""
|
|||
msgid "Last successful update"
|
||||
msgstr ""
|
||||
|
||||
msgid "Last time checked"
|
||||
msgstr ""
|
||||
|
||||
msgid "Last time verified"
|
||||
msgstr ""
|
||||
|
||||
|
@ -47116,6 +47122,9 @@ msgstr ""
|
|||
msgid "security Reports|There was an error creating the merge request"
|
||||
msgstr ""
|
||||
|
||||
msgid "selective_code_owner_removals can only be enabled when retain_approvals_on_push is enabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "severity|Blocker"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -74,8 +74,8 @@ RSpec.describe 'Groups > Members > Manage members' do
|
|||
|
||||
invite_member(user1.name, role: 'Reporter', refresh: false)
|
||||
|
||||
expect(page).to have_selector(invite_modal_selector)
|
||||
expect(page).to have_content("not authorized to update member")
|
||||
invite_modal = page.find(invite_modal_selector)
|
||||
expect(invite_modal).to have_content("not authorized to update member")
|
||||
|
||||
page.refresh
|
||||
|
||||
|
|
|
@ -1213,45 +1213,47 @@ paragraph
|
|||
};
|
||||
|
||||
it.each`
|
||||
mark | markdown | modifiedMarkdown | editAction
|
||||
${'bold'} | ${'**bold**'} | ${'**bold modified**'} | ${defaultEditAction}
|
||||
${'bold'} | ${'__bold__'} | ${'__bold modified__'} | ${defaultEditAction}
|
||||
${'bold'} | ${'<strong>bold</strong>'} | ${'<strong>bold modified</strong>'} | ${defaultEditAction}
|
||||
${'bold'} | ${'<b>bold</b>'} | ${'<b>bold modified</b>'} | ${defaultEditAction}
|
||||
${'italic'} | ${'_italic_'} | ${'_italic modified_'} | ${defaultEditAction}
|
||||
${'italic'} | ${'*italic*'} | ${'*italic modified*'} | ${defaultEditAction}
|
||||
${'italic'} | ${'<em>italic</em>'} | ${'<em>italic modified</em>'} | ${defaultEditAction}
|
||||
${'italic'} | ${'<i>italic</i>'} | ${'<i>italic modified</i>'} | ${defaultEditAction}
|
||||
${'link'} | ${'[gitlab](https://gitlab.com)'} | ${'[gitlab modified](https://gitlab.com)'} | ${defaultEditAction}
|
||||
${'link'} | ${'<a href="https://gitlab.com">link</a>'} | ${'<a href="https://gitlab.com/">link modified</a>'} | ${defaultEditAction}
|
||||
${'link'} | ${'link www.gitlab.com'} | ${'modified link www.gitlab.com'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link https://www.gitlab.com'} | ${'modified link https://www.gitlab.com'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link(https://www.gitlab.com)'} | ${'modified link(https://www.gitlab.com)'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link(engineering@gitlab.com)'} | ${'modified link(engineering@gitlab.com)'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link <https://www.gitlab.com>'} | ${'modified link <https://www.gitlab.com>'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link [https://www.gitlab.com>'} | ${'modified link \\[https://www.gitlab.com>'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link <https://www.gitlab.com'} | ${'modified link <https://www.gitlab.com'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link https://www.gitlab.com>'} | ${'modified link [https://www.gitlab.com>](https://www.gitlab.com%3E)'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link https://www.gitlab.com/path'} | ${'modified link https://www.gitlab.com/path'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link https://www.gitlab.com?query=search'} | ${'modified link https://www.gitlab.com?query=search'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link https://www.gitlab.com/#fragment'} | ${'modified link https://www.gitlab.com/#fragment'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link https://www.gitlab.com/?query=search'} | ${'modified link https://www.gitlab.com/?query=search'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link https://www.gitlab.com#fragment'} | ${'modified link https://www.gitlab.com#fragment'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link **https://www.gitlab.com]**'} | ${'modified link [**https://www.gitlab.com\\]**](https://www.gitlab.com%5D)'} | ${prependContentEditAction}
|
||||
${'code'} | ${'`code`'} | ${'`code modified`'} | ${defaultEditAction}
|
||||
${'code'} | ${'<code>code</code>'} | ${'<code>code modified</code>'} | ${defaultEditAction}
|
||||
${'strike'} | ${'~~striked~~'} | ${'~~striked modified~~'} | ${defaultEditAction}
|
||||
${'strike'} | ${'<del>striked</del>'} | ${'<del>striked modified</del>'} | ${defaultEditAction}
|
||||
${'strike'} | ${'<strike>striked</strike>'} | ${'<strike>striked modified</strike>'} | ${defaultEditAction}
|
||||
${'strike'} | ${'<s>striked</s>'} | ${'<s>striked modified</s>'} | ${defaultEditAction}
|
||||
${'list'} | ${'- list item'} | ${'- list item modified'} | ${defaultEditAction}
|
||||
${'list'} | ${'* list item'} | ${'* list item modified'} | ${defaultEditAction}
|
||||
${'list'} | ${'+ list item'} | ${'+ list item modified'} | ${defaultEditAction}
|
||||
${'list'} | ${'- list item 1\n- list item 2'} | ${'- list item 1\n- list item 2 modified'} | ${defaultEditAction}
|
||||
${'list'} | ${'2) list item'} | ${'2) list item modified'} | ${defaultEditAction}
|
||||
${'list'} | ${'1. list item'} | ${'1. list item modified'} | ${defaultEditAction}
|
||||
${'taskList'} | ${'2) [ ] task list item'} | ${'2) [ ] task list item modified'} | ${defaultEditAction}
|
||||
${'taskList'} | ${'2) [x] task list item'} | ${'2) [x] task list item modified'} | ${defaultEditAction}
|
||||
mark | markdown | modifiedMarkdown | editAction
|
||||
${'bold'} | ${'**bold**'} | ${'**bold modified**'} | ${defaultEditAction}
|
||||
${'bold'} | ${'__bold__'} | ${'__bold modified__'} | ${defaultEditAction}
|
||||
${'bold'} | ${'<strong>bold</strong>'} | ${'<strong>bold modified</strong>'} | ${defaultEditAction}
|
||||
${'bold'} | ${'<b>bold</b>'} | ${'<b>bold modified</b>'} | ${defaultEditAction}
|
||||
${'italic'} | ${'_italic_'} | ${'_italic modified_'} | ${defaultEditAction}
|
||||
${'italic'} | ${'*italic*'} | ${'*italic modified*'} | ${defaultEditAction}
|
||||
${'italic'} | ${'<em>italic</em>'} | ${'<em>italic modified</em>'} | ${defaultEditAction}
|
||||
${'italic'} | ${'<i>italic</i>'} | ${'<i>italic modified</i>'} | ${defaultEditAction}
|
||||
${'link'} | ${'[gitlab](https://gitlab.com)'} | ${'[gitlab modified](https://gitlab.com)'} | ${defaultEditAction}
|
||||
${'link'} | ${'<a href="https://gitlab.com">link</a>'} | ${'<a href="https://gitlab.com/">link modified</a>'} | ${defaultEditAction}
|
||||
${'link'} | ${'link www.gitlab.com'} | ${'modified link www.gitlab.com'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link https://www.gitlab.com'} | ${'modified link https://www.gitlab.com'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link(https://www.gitlab.com)'} | ${'modified link(https://www.gitlab.com)'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link(engineering@gitlab.com)'} | ${'modified link(engineering@gitlab.com)'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link <https://www.gitlab.com>'} | ${'modified link <https://www.gitlab.com>'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link [https://www.gitlab.com>'} | ${'modified link \\[https://www.gitlab.com>'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link <https://www.gitlab.com'} | ${'modified link <https://www.gitlab.com'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link https://www.gitlab.com>'} | ${'modified link [https://www.gitlab.com>](https://www.gitlab.com%3E)'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link https://www.gitlab.com/path'} | ${'modified link https://www.gitlab.com/path'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link https://www.gitlab.com?query=search'} | ${'modified link https://www.gitlab.com?query=search'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link https://www.gitlab.com/#fragment'} | ${'modified link https://www.gitlab.com/#fragment'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link https://www.gitlab.com/?query=search'} | ${'modified link https://www.gitlab.com/?query=search'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link https://www.gitlab.com#fragment'} | ${'modified link https://www.gitlab.com#fragment'} | ${prependContentEditAction}
|
||||
${'link'} | ${'link **https://www.gitlab.com]**'} | ${'modified link [**https://www.gitlab.com\\]**](https://www.gitlab.com%5D)'} | ${prependContentEditAction}
|
||||
${'code'} | ${'`code`'} | ${'`code modified`'} | ${defaultEditAction}
|
||||
${'code'} | ${'<code>code</code>'} | ${'<code>code modified</code>'} | ${defaultEditAction}
|
||||
${'strike'} | ${'~~striked~~'} | ${'~~striked modified~~'} | ${defaultEditAction}
|
||||
${'strike'} | ${'<del>striked</del>'} | ${'<del>striked modified</del>'} | ${defaultEditAction}
|
||||
${'strike'} | ${'<strike>striked</strike>'} | ${'<strike>striked modified</strike>'} | ${defaultEditAction}
|
||||
${'strike'} | ${'<s>striked</s>'} | ${'<s>striked modified</s>'} | ${defaultEditAction}
|
||||
${'list'} | ${'- list item'} | ${'- list item modified'} | ${defaultEditAction}
|
||||
${'list'} | ${'* list item'} | ${'* list item modified'} | ${defaultEditAction}
|
||||
${'list'} | ${'+ list item'} | ${'+ list item modified'} | ${defaultEditAction}
|
||||
${'list'} | ${'- list item 1\n- list item 2'} | ${'- list item 1\n- list item 2 modified'} | ${defaultEditAction}
|
||||
${'list'} | ${'2) list item'} | ${'2) list item modified'} | ${defaultEditAction}
|
||||
${'list'} | ${'1. list item'} | ${'1. list item modified'} | ${defaultEditAction}
|
||||
${'taskList'} | ${'2) [ ] task list item'} | ${'2) [ ] task list item modified'} | ${defaultEditAction}
|
||||
${'taskList'} | ${'2) [x] task list item'} | ${'2) [x] task list item modified'} | ${defaultEditAction}
|
||||
${'image'} | ${'![image](image.png)'} | ${'![image](image.png) modified'} | ${defaultEditAction}
|
||||
${'footnoteReference'} | ${'[^1] footnote\n\n[^1]: footnote definition'} | ${'modified [^1] footnote\n\n[^1]: footnote definition'} | ${prependContentEditAction}
|
||||
`(
|
||||
'preserves original $mark syntax when sourceMarkdown is available for $markdown',
|
||||
async ({ markdown, modifiedMarkdown, editAction }) => {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { stubComponent } from 'helpers/stub_component';
|
|||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import * as UserApi from '~/api/user_api';
|
||||
import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
|
||||
import { VALID_TOKEN_BACKGROUND, INVALID_TOKEN_BACKGROUND } from '~/invite_members/constants';
|
||||
|
||||
const label = 'testgroup';
|
||||
const placeholder = 'Search for a member';
|
||||
|
@ -49,6 +50,39 @@ describe('MembersTokenSelect', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when there are invalidMembers', () => {
|
||||
it('adds in the correct class values for the tokens', async () => {
|
||||
const badToken = { ...user1, class: INVALID_TOKEN_BACKGROUND };
|
||||
const goodToken = { ...user2, class: VALID_TOKEN_BACKGROUND };
|
||||
|
||||
wrapper = createComponent();
|
||||
|
||||
findTokenSelector().vm.$emit('input', [user1, user2]);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findTokenSelector().props('selectedTokens')).toEqual([user1, user2]);
|
||||
|
||||
await wrapper.setProps({ invalidMembers: { one_1: 'bad stuff' } });
|
||||
|
||||
expect(findTokenSelector().props('selectedTokens')).toEqual([badToken, goodToken]);
|
||||
});
|
||||
|
||||
it('does not change class when invalid members are cleared', async () => {
|
||||
// arrange - invalidMembers is non-empty and then tokens are added
|
||||
wrapper = createComponent();
|
||||
await wrapper.setProps({ invalidMembers: { one_1: 'bad stuff' } });
|
||||
findTokenSelector().vm.$emit('input', [user1, user2]);
|
||||
await waitForPromises();
|
||||
|
||||
// act - invalidMembers clears out
|
||||
await wrapper.setProps({ invalidMembers: {} });
|
||||
|
||||
// assert - we didn't try to update the tokens
|
||||
expect(findTokenSelector().props('selectedTokens')).toEqual([user1, user2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('users', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(UserApi, 'getUsers').mockResolvedValue({ data: allUsers });
|
||||
|
|
|
@ -6,7 +6,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import WorkItemLinksMenu from '~/work_items/components/work_item_links/work_item_links_menu.vue';
|
||||
import changeWorkItemParentMutation from '~/work_items/graphql/change_work_item_parent_link.mutation.graphql';
|
||||
import changeWorkItemParentMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
|
||||
import getWorkItemLinksQuery from '~/work_items/graphql/work_item_links.query.graphql';
|
||||
import { WIDGET_TYPE_HIERARCHY } from '~/work_items/constants';
|
||||
import { workItemHierarchyResponse, changeWorkItemParentMutationResponse } from '../../mock_data';
|
||||
|
@ -87,8 +87,12 @@ describe('WorkItemLinksMenu', () => {
|
|||
await waitForPromises();
|
||||
|
||||
expect(mutationHandler).toHaveBeenCalledWith({
|
||||
id: WORK_ITEM_ID,
|
||||
parentId: null,
|
||||
input: {
|
||||
id: WORK_ITEM_ID,
|
||||
hierarchyWidget: {
|
||||
parentId: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -480,14 +480,19 @@ export const changeWorkItemParentMutationResponse = {
|
|||
data: {
|
||||
workItemUpdate: {
|
||||
workItem: {
|
||||
description: null,
|
||||
id: 'gid://gitlab/WorkItem/2',
|
||||
workItemType: {
|
||||
id: 'gid://gitlab/WorkItems::Type/5',
|
||||
__typename: 'WorkItemType',
|
||||
},
|
||||
title: 'Foo',
|
||||
state: 'OPEN',
|
||||
__typename: 'WorkItem',
|
||||
title: 'Foo',
|
||||
widgets: [
|
||||
{
|
||||
type: 'HIERARCHY',
|
||||
parent: null,
|
||||
children: {
|
||||
nodes: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: [],
|
||||
__typename: 'WorkItemUpdatePayload',
|
||||
|
|
|
@ -39,7 +39,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migrat
|
|||
|
||||
let(:file_name) { 'file_name.rb' }
|
||||
let(:content) { 'content' }
|
||||
let(:ids) { snippets.pluck('MIN(id)', 'MAX(id)').first }
|
||||
let(:ids) { snippets.pick('MIN(id)', 'MAX(id)') }
|
||||
let(:service) { described_class.new }
|
||||
|
||||
subject { service.perform(*ids) }
|
||||
|
|
|
@ -2,10 +2,26 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::UpdateJiraTrackerDataDeploymentTypeBasedOnUrl, schema: 20210421163509 do
|
||||
let(:services_table) { table(:services) }
|
||||
let(:service_jira_cloud) { services_table.create!(id: 1, type: 'JiraService') }
|
||||
let(:service_jira_server) { services_table.create!(id: 2, type: 'JiraService') }
|
||||
RSpec.describe Gitlab::BackgroundMigration::UpdateJiraTrackerDataDeploymentTypeBasedOnUrl do
|
||||
let(:integrations_table) { table(:integrations) }
|
||||
let(:service_jira_cloud) { integrations_table.create!(id: 1, type_new: 'JiraService') }
|
||||
let(:service_jira_server) { integrations_table.create!(id: 2, type_new: 'JiraService') }
|
||||
let(:service_jira_unknown) { integrations_table.create!(id: 3, type_new: 'JiraService') }
|
||||
|
||||
let(:table_name) { :jira_tracker_data }
|
||||
let(:batch_column) { :id }
|
||||
let(:sub_batch_size) { 1 }
|
||||
let(:pause_ms) { 0 }
|
||||
let(:migration) do
|
||||
described_class.new(start_id: 1, end_id: 10,
|
||||
batch_table: table_name, batch_column: batch_column,
|
||||
sub_batch_size: sub_batch_size, pause_ms: pause_ms,
|
||||
connection: ApplicationRecord.connection)
|
||||
end
|
||||
|
||||
subject(:perform_migration) do
|
||||
migration.perform
|
||||
end
|
||||
|
||||
before do
|
||||
jira_tracker_data = Class.new(ApplicationRecord) do
|
||||
|
@ -27,18 +43,21 @@ RSpec.describe Gitlab::BackgroundMigration::UpdateJiraTrackerDataDeploymentTypeB
|
|||
end
|
||||
|
||||
stub_const('JiraTrackerData', jira_tracker_data)
|
||||
|
||||
stub_const('UNKNOWN', 0)
|
||||
stub_const('SERVER', 1)
|
||||
stub_const('CLOUD', 2)
|
||||
end
|
||||
|
||||
let!(:tracker_data_cloud) { JiraTrackerData.create!(id: 1, service_id: service_jira_cloud.id, url: "https://test-domain.atlassian.net", deployment_type: 0) }
|
||||
let!(:tracker_data_server) { JiraTrackerData.create!(id: 2, service_id: service_jira_server.id, url: "http://totally-not-jira-server.company.org", deployment_type: 0) }
|
||||
|
||||
subject { described_class.new.perform(tracker_data_cloud.id, tracker_data_server.id) }
|
||||
let!(:tracker_data_cloud) { JiraTrackerData.create!(id: 1, service_id: service_jira_cloud.id, url: "https://test-domain.atlassian.net", deployment_type: UNKNOWN) }
|
||||
let!(:tracker_data_server) { JiraTrackerData.create!(id: 2, service_id: service_jira_server.id, url: "http://totally-not-jira-server.company.org", deployment_type: UNKNOWN) }
|
||||
let!(:tracker_data_unknown) { JiraTrackerData.create!(id: 3, service_id: service_jira_unknown.id, url: "", deployment_type: UNKNOWN) }
|
||||
|
||||
it "changes unknown deployment_types based on URL" do
|
||||
expect(JiraTrackerData.pluck(:deployment_type)).to eq([0, 0])
|
||||
expect(JiraTrackerData.pluck(:deployment_type)).to match_array([UNKNOWN, UNKNOWN, UNKNOWN])
|
||||
|
||||
subject
|
||||
perform_migration
|
||||
|
||||
expect(JiraTrackerData.pluck(:deployment_type)).to eq([2, 1])
|
||||
expect(JiraTrackerData.order(:id).pluck(:deployment_type)).to match_array([CLOUD, SERVER, UNKNOWN])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe ScheduleUpdateJiraTrackerDataDeploymentTypeBasedOnUrl, :migration do
|
||||
let(:services_table) { table(:services) }
|
||||
let(:service_jira_cloud) { services_table.create!(id: 1, type: 'JiraService') }
|
||||
let(:service_jira_server) { services_table.create!(id: 2, type: 'JiraService') }
|
||||
RSpec.describe UpdateJiraTrackerDataDeploymentTypeBasedOnUrl, :migration do
|
||||
let(:integrations_table) { table(:integrations) }
|
||||
let(:service_jira_cloud) { integrations_table.create!(id: 1, type_new: 'JiraService') }
|
||||
let(:service_jira_server) { integrations_table.create!(id: 2, type_new: 'JiraService') }
|
||||
|
||||
before do
|
||||
jira_tracker_data = Class.new(ApplicationRecord) do
|
||||
|
@ -29,20 +29,30 @@ RSpec.describe ScheduleUpdateJiraTrackerDataDeploymentTypeBasedOnUrl, :migration
|
|||
|
||||
stub_const('JiraTrackerData', jira_tracker_data)
|
||||
stub_const("#{described_class}::BATCH_SIZE", 1)
|
||||
stub_const("#{described_class}::SUB_BATCH_SIZE", 1)
|
||||
end
|
||||
|
||||
# rubocop:disable Layout/LineLength
|
||||
# rubocop:disable RSpec/ScatteredLet
|
||||
let!(:tracker_data_cloud) { JiraTrackerData.create!(id: 1, service_id: service_jira_cloud.id, url: "https://test-domain.atlassian.net", deployment_type: 0) }
|
||||
let!(:tracker_data_server) { JiraTrackerData.create!(id: 2, service_id: service_jira_server.id, url: "http://totally-not-jira-server.company.org", deployment_type: 0) }
|
||||
# rubocop:enable Layout/LineLength
|
||||
# rubocop:enable RSpec/ScatteredLet
|
||||
|
||||
around do |example|
|
||||
freeze_time { Sidekiq::Testing.fake! { example.run } }
|
||||
end
|
||||
|
||||
let(:migration) { described_class::MIGRATION } # rubocop:disable RSpec/ScatteredLet
|
||||
|
||||
it 'schedules background migration' do
|
||||
migrate!
|
||||
|
||||
expect(BackgroundMigrationWorker.jobs.size).to eq(2)
|
||||
expect(described_class::MIGRATION).to be_scheduled_migration(tracker_data_cloud.id, tracker_data_cloud.id)
|
||||
expect(described_class::MIGRATION).to be_scheduled_migration(tracker_data_server.id, tracker_data_server.id)
|
||||
expect(migration).to have_scheduled_batched_migration(
|
||||
table_name: :jira_tracker_data,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
gitlab_schema: :gitlab_main
|
||||
)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe MergeRequest::ApprovalRemovalSettings do
|
||||
describe 'validations' do
|
||||
let(:reset_approvals_on_push) { }
|
||||
let(:selective_code_owner_removals) { }
|
||||
|
||||
subject { described_class.new(project, reset_approvals_on_push, selective_code_owner_removals) }
|
||||
|
||||
context 'when enabling selective_code_owner_removals and reset_approvals_on_push is disabled' do
|
||||
let(:project) { create(:project, reset_approvals_on_push: false) }
|
||||
let(:selective_code_owner_removals) { true }
|
||||
|
||||
it { is_expected.to be_valid }
|
||||
end
|
||||
|
||||
context 'when enabling selective_code_owner_removals and reset_approvals_on_push is enabled' do
|
||||
let(:project) { create(:project) }
|
||||
let(:selective_code_owner_removals) { true }
|
||||
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
||||
context 'when enabling reset_approvals_on_push and selective_code_owner_removals is disabled' do
|
||||
let(:project) { create(:project) }
|
||||
let(:reset_approvals_on_push) { true }
|
||||
|
||||
it { is_expected.to be_valid }
|
||||
end
|
||||
|
||||
context 'when enabling reset_approvals_on_push and selective_code_owner_removals is enabled' do
|
||||
let(:project) { create(:project) }
|
||||
let(:reset_approvals_on_push) { true }
|
||||
|
||||
before do
|
||||
project.project_setting.update!(selective_code_owner_removals: true)
|
||||
end
|
||||
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
||||
context 'when enabling reset_approvals_on_push and selective_code_owner_removals' do
|
||||
let(:project) { create(:project) }
|
||||
let(:reset_approvals_on_push) { true }
|
||||
let(:selective_code_owner_removals) { true }
|
||||
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -157,6 +157,7 @@ project_setting:
|
|||
- cve_id_request_enabled
|
||||
- mr_default_target_self
|
||||
- target_platforms
|
||||
- selective_code_owner_removals
|
||||
|
||||
build_service_desk_setting: # service_desk_setting
|
||||
unexposed_attributes:
|
||||
|
|
|
@ -10,8 +10,8 @@ RSpec.describe 'value stream analytics events' do
|
|||
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
|
||||
|
||||
describe 'GET /:namespace/:project/value_stream_analytics/events/issues' do
|
||||
let(:first_issue_iid) { project.issues.sort_by_attribute(:created_desc).pluck(:iid).first.to_s }
|
||||
let(:first_mr_iid) { project.merge_requests.sort_by_attribute(:created_desc).pluck(:iid).first.to_s }
|
||||
let(:first_issue_iid) { project.issues.sort_by_attribute(:created_desc).pick(:iid).to_s }
|
||||
let(:first_mr_iid) { project.merge_requests.sort_by_attribute(:created_desc).pick(:iid).to_s }
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
|
|
|
@ -11,7 +11,7 @@ module Spec
|
|||
page.within invite_modal_selector do
|
||||
select_members(names)
|
||||
choose_options(role, expires_at)
|
||||
click_button 'Invite'
|
||||
submit_invites
|
||||
end
|
||||
|
||||
page.refresh if refresh
|
||||
|
@ -42,11 +42,15 @@ module Spec
|
|||
click_button name
|
||||
choose_options(role, expires_at)
|
||||
|
||||
click_button 'Invite'
|
||||
submit_invites
|
||||
|
||||
page.refresh
|
||||
end
|
||||
|
||||
def submit_invites
|
||||
click_button 'Invite'
|
||||
end
|
||||
|
||||
def choose_options(role, expires_at)
|
||||
unless role == 'Guest'
|
||||
click_button 'Guest'
|
||||
|
@ -92,6 +96,29 @@ module Spec
|
|||
end
|
||||
end
|
||||
|
||||
def expect_to_have_successful_invite_indicator(page, user)
|
||||
expect(page).to have_selector("#{member_token_selector(user.id)} .gl-bg-green-100")
|
||||
expect(page).not_to have_text("#{user.name}: ")
|
||||
end
|
||||
|
||||
def expect_to_have_invalid_invite_indicator(page, user)
|
||||
expect(page).to have_selector("#{member_token_selector(user.id)} .gl-bg-red-100")
|
||||
expect(page).to have_selector(member_token_error_selector(user.id))
|
||||
expect(page).to have_text("#{user.name}: Access level should be greater than or equal to")
|
||||
end
|
||||
|
||||
def expect_to_have_normal_invite_indicator(page, user)
|
||||
expect(page).to have_selector(member_token_selector(user.id))
|
||||
expect(page).not_to have_selector("#{member_token_selector(user.id)} .gl-bg-red-100")
|
||||
expect(page).not_to have_selector("#{member_token_selector(user.id)} .gl-bg-green-100")
|
||||
expect(page).not_to have_text("#{user.name}: ")
|
||||
end
|
||||
|
||||
def expect_to_have_invite_removed(page, user)
|
||||
expect(page).not_to have_selector(member_token_selector(user.id))
|
||||
expect(page).not_to have_text("#{user.name}: Access level should be greater than or equal to")
|
||||
end
|
||||
|
||||
def expect_to_have_group(group)
|
||||
expect(page).to have_selector("[entity-id='#{group.id}']")
|
||||
end
|
||||
|
|
|
@ -147,9 +147,9 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
|
|||
|
||||
invite_member(user2.name, role: role, refresh: false)
|
||||
|
||||
expect(page).to have_selector(invite_modal_selector)
|
||||
expect(page).to have_content "#{user2.name}: Access level should be greater than or equal to Developer " \
|
||||
"inherited membership from group #{group.name}"
|
||||
invite_modal = page.find(invite_modal_selector)
|
||||
expect(invite_modal).to have_content "#{user2.name}: Access level should be greater than or equal to " \
|
||||
"Developer inherited membership from group #{group.name}"
|
||||
|
||||
page.refresh
|
||||
|
||||
|
@ -166,31 +166,44 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
|
|||
group.add_maintainer(user3)
|
||||
end
|
||||
|
||||
it 'shows the user errors and then removes them from the form', :js do
|
||||
it 'shows the partial user error and success and then removes them from the form', :js do
|
||||
user4 = create(:user)
|
||||
user5 = create(:user)
|
||||
|
||||
visit subentity_members_page_path
|
||||
|
||||
invite_member([user2.name, user3.name], role: role, refresh: false)
|
||||
invite_member([user2.name, user3.name, user4.name], role: role, refresh: false)
|
||||
|
||||
expect(page).to have_selector(invite_modal_selector)
|
||||
expect(page).to have_selector(member_token_error_selector(user2.id))
|
||||
expect(page).to have_selector(member_token_error_selector(user3.id))
|
||||
expect(page).to have_text("The following 2 members couldn't be invited")
|
||||
expect(page).to have_text("#{user2.name}: Access level should be greater than or equal to")
|
||||
expect(page).to have_text("#{user3.name}: Access level should be greater than or equal to")
|
||||
invite_modal = page.find(invite_modal_selector)
|
||||
expect(invite_modal).to have_text("The following 2 members couldn't be invited")
|
||||
expect_to_have_invalid_invite_indicator(invite_modal, user2)
|
||||
expect_to_have_invalid_invite_indicator(invite_modal, user3)
|
||||
expect_to_have_successful_invite_indicator(invite_modal, user4)
|
||||
|
||||
# adds new token, but doesn't submit
|
||||
select_members(user5.name)
|
||||
|
||||
expect_to_have_normal_invite_indicator(invite_modal, user5)
|
||||
|
||||
remove_token(user2.id)
|
||||
|
||||
expect(page).not_to have_selector(member_token_error_selector(user2.id))
|
||||
expect(page).to have_selector(member_token_error_selector(user3.id))
|
||||
expect(page).to have_text("The following member couldn't be invited")
|
||||
expect(page).not_to have_text("#{user2.name}: Access level should be greater than or equal to")
|
||||
expect(invite_modal).to have_text("The following member couldn't be invited")
|
||||
expect_to_have_invite_removed(invite_modal, user2)
|
||||
expect_to_have_invalid_invite_indicator(invite_modal, user3)
|
||||
expect_to_have_successful_invite_indicator(invite_modal, user4)
|
||||
expect_to_have_normal_invite_indicator(invite_modal, user5)
|
||||
|
||||
remove_token(user3.id)
|
||||
|
||||
expect(page).not_to have_selector(member_token_error_selector(user3.id))
|
||||
expect(page).not_to have_text("The following member couldn't be invited")
|
||||
expect(page).not_to have_text("Review the invite errors and try again")
|
||||
expect(page).not_to have_text("#{user3.name}: Access level should be greater than or equal to")
|
||||
expect(invite_modal).not_to have_text("The following member couldn't be invited")
|
||||
expect(invite_modal).not_to have_text("Review the invite errors and try again")
|
||||
expect_to_have_invite_removed(invite_modal, user3)
|
||||
expect_to_have_successful_invite_indicator(invite_modal, user4)
|
||||
expect_to_have_normal_invite_indicator(invite_modal, user5)
|
||||
|
||||
submit_invites
|
||||
|
||||
expect(page).not_to have_selector(invite_modal_selector)
|
||||
|
||||
page.refresh
|
||||
|
||||
|
@ -203,6 +216,10 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
|
|||
expect(page).to have_content('Maintainer')
|
||||
expect(page).not_to have_button('Maintainer')
|
||||
end
|
||||
|
||||
page.within find_invited_member_row(user4.name) do
|
||||
expect(page).to have_button(role)
|
||||
end
|
||||
end
|
||||
|
||||
it 'only shows the error for an invalid formatted email and does not display other member errors', :js do
|
||||
|
@ -210,12 +227,12 @@ RSpec.shared_examples 'inviting members' do |snowplow_invite_label|
|
|||
|
||||
invite_member([user2.name, user3.name, 'bad@email'], role: role, refresh: false)
|
||||
|
||||
expect(page).to have_selector(invite_modal_selector)
|
||||
expect(page).to have_text('email contains an invalid email address')
|
||||
expect(page).not_to have_text("The following 2 members couldn't be invited")
|
||||
expect(page).not_to have_text("Review the invite errors and try again")
|
||||
expect(page).not_to have_text("#{user2.name}: Access level should be greater than or equal to")
|
||||
expect(page).not_to have_text("#{user3.name}: Access level should be greater than or equal to")
|
||||
invite_modal = page.find(invite_modal_selector)
|
||||
expect(invite_modal).to have_text('email contains an invalid email address')
|
||||
expect(invite_modal).not_to have_text("The following 2 members couldn't be invited")
|
||||
expect(invite_modal).not_to have_text("Review the invite errors and try again")
|
||||
expect(invite_modal).not_to have_text("#{user2.name}: Access level should be greater than or equal to")
|
||||
expect(invite_modal).not_to have_text("#{user3.name}: Access level should be greater than or equal to")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue