Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-05-06 12:10:38 +00:00
parent c06178d51a
commit 25db9c1230
134 changed files with 1987 additions and 562 deletions

View File

@ -75,7 +75,10 @@ Are there any other stages or teams involved that need to be kept in the loop?
issue](https://about.gitlab.com/handbook/engineering/infrastructure/change-management/#feature-flags-and-the-change-management-process). Cross
link the issue here if it does.
- [ ] Ensure that you or a representative in development can be available for at least 2 hours after feature flag updates in production. If a different developer will be covering, or an exception is needed, please inform the oncall SRE by using the `@sre-oncall` Slack alias.
*Partial Rollout Phase*
- [ ] Enable on GitLab.com for individual groups/projects listed above and verify behaviour (`/chatops run feature set --project=gitlab-org/gitlab feature_name true`)
- [ ] Verify behaviour (See Beta Groups) and add details with screenshots as a comment on this issue

View File

@ -1998,6 +1998,8 @@ Gitlab/NamespacedClass:
- 'app/serializers/group_entity.rb'
- 'app/serializers/group_group_link_entity.rb'
- 'app/serializers/group_group_link_serializer.rb'
- 'app/serializers/group_issuable_autocomplete_entity.rb'
- 'app/serializers/group_issuable_autocomplete_serializer.rb'
- 'app/serializers/group_serializer.rb'
- 'app/serializers/issuable_entity.rb'
- 'app/serializers/issuable_sidebar_basic_entity.rb'
@ -2500,8 +2502,6 @@ Gitlab/NamespacedClass:
- 'ee/app/serializers/geo_project_registry_entity.rb'
- 'ee/app/serializers/geo_project_registry_serializer.rb'
- 'ee/app/serializers/group_analytics_serializer.rb'
- 'ee/app/serializers/group_issuable_autocomplete_entity.rb'
- 'ee/app/serializers/group_issuable_autocomplete_serializer.rb'
- 'ee/app/serializers/group_vulnerability_autocomplete_entity.rb'
- 'ee/app/serializers/group_vulnerability_autocomplete_serializer.rb'
- 'ee/app/serializers/invited_group_entity.rb'

View File

@ -1 +1 @@
1481a9195c200e375a177cf201058b88bebe271b
c67f1a2bb56d8fa3403b529fd3bf36dba3a6488c

View File

@ -481,7 +481,7 @@ end
gem 'spamcheck', '~> 0.0.5'
# Gitaly GRPC protocol definitions
gem 'gitaly', '~> 13.11.0.pre.rc1'
gem 'gitaly', '~> 13.12.0.pre.rc1'
gem 'grpc', '~> 1.30.2'

View File

@ -439,7 +439,7 @@ GEM
rails (>= 3.2.0)
git (1.7.0)
rchardet (~> 1.8)
gitaly (13.11.0.pre.rc1)
gitaly (13.12.0.pre.rc1)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab (4.16.1)
@ -1446,7 +1446,7 @@ DEPENDENCIES
gettext (~> 3.3)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly (~> 13.11.0.pre.rc1)
gitaly (~> 13.12.0.pre.rc1)
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 2.0.0)

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -1,5 +1,5 @@
<script>
import { GlBadge, GlTable } from '@gitlab/ui';
import { GlBadge, GlTable, GlLink, GlEmptyState } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { sprintf, s__ } from '~/locale';
@ -13,11 +13,19 @@ export default {
GlBadge,
GlTable,
GlSingleStat,
GlLink,
GlEmptyState,
},
inject: {
devopsScoreMetrics: {
default: null,
},
devopsReportDocsPath: {
default: '',
},
noDataImagePath: {
default: '',
},
},
computed: {
titleHelperText() {
@ -28,6 +36,9 @@ export default {
{ timestamp: this.devopsScoreMetrics.createdAt },
);
},
isEmpty() {
return this.devopsScoreMetrics.averageScore === undefined;
},
},
tableHeaderFields: [
{
@ -54,7 +65,19 @@ export default {
};
</script>
<template>
<div data-testid="devops-score-app">
<gl-empty-state
v-if="isEmpty"
:title="__('Data is still calculating...')"
:svg-path="noDataImagePath"
>
<template #description>
<p class="gl-mb-0">{{ __('It may be several days before you see feature usage data.') }}</p>
<gl-link :href="devopsReportDocsPath">{{
__('See example DevOps Score page in our documentation.')
}}</gl-link>
</template>
</gl-empty-state>
<div v-else data-testid="devops-score-app">
<div class="gl-text-gray-400 gl-my-4" data-testid="devops-score-note-text">
{{ titleHelperText }}
</div>

View File

@ -6,12 +6,14 @@ export default () => {
if (!el) return false;
const { devopsScoreMetrics } = el.dataset;
const { devopsScoreMetrics, devopsReportDocsPath, noDataImagePath } = el.dataset;
return new Vue({
el,
provide: {
devopsScoreMetrics: JSON.parse(devopsScoreMetrics),
devopsReportDocsPath,
noDataImagePath,
},
render(h) {
return h(DevopsScore);

View File

@ -6,7 +6,7 @@ export default () => {
// eslint-disable-next-line no-new
new UserCallout();
const emptyStateContainer = document.getElementById('js-devops-empty-state');
const emptyStateContainer = document.getElementById('js-devops-usage-ping-disabled');
if (!emptyStateContainer) return false;

View File

@ -1,5 +1,5 @@
import initDevOpsScore from '~/analytics/devops_report/devops_score';
import initDevOpsScoreEmptyState from '~/analytics/devops_report/devops_score_empty_state';
import initDevOpsScoreDisabledUsagePing from '~/analytics/devops_report/devops_score_disabled_usage_ping';
initDevOpsScoreEmptyState();
initDevOpsScoreDisabledUsagePing();
initDevOpsScore();

View File

@ -1,3 +1,3 @@
import initForm from '../../../../shared/milestones/form';
initForm(false);
initForm();

View File

@ -1,3 +1,3 @@
import initForm from '../../../../shared/milestones/form';
initForm(false);
initForm();

View File

@ -28,7 +28,7 @@ export default {
<status-icon :show-disabled-button="true" status="warning" />
<div class="media-body">
<span class="gl-ml-3 gl-font-weight-bold gl-display-block gl-w-100">{{
s__('mrWidget|Before this can be merged, one or more threads must be resolved.')
s__('mrWidget|Merge blocked: all threads must be resolved.')
}}</span>
<gl-button
data-testid="jump-to-first"

View File

@ -147,7 +147,7 @@
display: block;
&:hover {
box-shadow: inset 0 0 0 0.0625rem $dropdown-toggle-active-border-color;
box-shadow: inset 0 0 0 2px var(--gray-400, $gray-400);
background-color: var(--gray-50, $gray-50);
}

View File

@ -1,6 +0,0 @@
@import 'mixins_and_variables_and_functions';
.devops-empty svg {
margin: 64px auto 32px;
max-width: 420px;
}

View File

@ -0,0 +1,54 @@
# frozen_string_literal: true
class Groups::AutocompleteSourcesController < Groups::ApplicationController
feature_category :subgroups, [:members]
feature_category :issue_tracking, [:issues, :labels, :milestones, :commands]
feature_category :code_review, [:merge_requests]
def members
render json: ::Groups::ParticipantsService.new(@group, current_user).execute(target)
end
def issues
render json: issuable_serializer.represent(
autocomplete_service.issues(confidential_only: params[:confidential_only], issue_types: params[:issue_types]),
parent_group: @group
)
end
def merge_requests
render json: issuable_serializer.represent(autocomplete_service.merge_requests, parent_group: @group)
end
def labels
render json: autocomplete_service.labels_as_hash(target)
end
def commands
render json: autocomplete_service.commands(target)
end
def milestones
render json: autocomplete_service.milestones
end
private
def autocomplete_service
@autocomplete_service ||= ::Groups::AutocompleteService.new(@group, current_user, params)
end
def issuable_serializer
GroupIssuableAutocompleteSerializer.new
end
# rubocop: disable CodeReuse/ActiveRecord
def target
QuickActions::TargetService
.new(nil, current_user, group: @group)
.execute(params[:type], params[:type_id])
end
# rubocop: enable CodeReuse/ActiveRecord
end
Groups::AutocompleteSourcesController.prepend_if_ee('EE::Groups::AutocompleteSourcesController')

View File

@ -21,7 +21,7 @@ class Groups::MilestonesController < Groups::ApplicationController
end
def new
@milestone = Milestone.new
@noteable = @milestone = Milestone.new
end
def create
@ -70,7 +70,7 @@ class Groups::MilestonesController < Groups::ApplicationController
end
def milestone
@milestone = group.milestones.find_by_iid(params[:id])
@noteable = @milestone ||= group.milestones.find_by_iid(params[:id])
render_404 unless @milestone
end

View File

@ -9,7 +9,7 @@ module Groups
feature_category :package_registry
def index
def show
end
private

View File

@ -39,7 +39,7 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def new
@milestone = @project.milestones.new
@noteable = @milestone = @project.milestones.new
respond_with(@milestone)
end
@ -125,7 +125,7 @@ class Projects::MilestonesController < Projects::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def milestone
@milestone ||= @project.milestones.find_by!(iid: params[:id])
@noteable = @milestone ||= @project.milestones.find_by!(iid: params[:id])
end
# rubocop: enable CodeReuse/ActiveRecord

View File

@ -10,7 +10,7 @@ module Projects
feature_category :package_registry
def index
def show
end
private

View File

@ -60,12 +60,17 @@ module Types
field :committed_at, Types::TimeType, null: true,
description: "Timestamp of the pipeline's commit."
field :stages, Types::Ci::StageType.connection_type, null: true,
field :stages,
type: Types::Ci::StageType.connection_type,
null: true,
authorize: :read_commit_status,
description: 'Stages of the pipeline.',
extras: [:lookahead],
resolver: Resolvers::Ci::PipelineStagesResolver
field :user, Types::UserType, null: true,
field :user,
type: Types::UserType,
null: true,
description: 'Pipeline user.'
field :retryable, GraphQL::BOOLEAN_TYPE,
@ -81,12 +86,14 @@ module Types
field :jobs,
::Types::Ci::JobType.connection_type,
null: true,
authorize: :read_commit_status,
description: 'Jobs belonging to the pipeline.',
resolver: ::Resolvers::Ci::JobsResolver
field :job,
type: ::Types::Ci::JobType,
null: true,
authorize: :read_commit_status,
description: 'A specific job in this pipeline, either by name or ID.' do
argument :id,
type: ::Types::GlobalIDType[::CommitStatus],
@ -98,7 +105,10 @@ module Types
description: 'Name of the job.'
end
field :source_job, Types::Ci::JobType, null: true,
field :source_job,
type: Types::Ci::JobType,
null: true,
authorize: :read_commit_status,
description: 'Job where pipeline was triggered from.'
field :downstream, Types::Ci::PipelineType.connection_type, null: true,

View File

@ -2,20 +2,26 @@
module Types
module Ci
# rubocop: disable Graphql/AuthorizeTypes
class StageType < BaseObject
graphql_name 'CiStage'
authorize :read_commit_status
field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the stage.'
field :groups, Ci::GroupType.connection_type, null: true,
extras: [:lookahead],
description: 'Group of jobs for the stage.'
field :detailed_status, Types::Ci::DetailedStatusType, null: true,
description: 'Detailed status of the stage.'
field :jobs, Ci::JobType.connection_type, null: true,
description: 'Jobs for the stage.',
method: 'latest_statuses'
field :name,
type: GraphQL::STRING_TYPE,
null: true,
description: 'Name of the stage.'
field :groups,
type: Ci::GroupType.connection_type,
null: true,
extras: [:lookahead],
description: 'Group of jobs for the stage.'
field :detailed_status, Types::Ci::DetailedStatusType,
null: true,
description: 'Detailed status of the stage.'
field :jobs, Ci::JobType.connection_type,
null: true,
description: 'Jobs for the stage.',
method: 'latest_statuses'
def detailed_status
object.detailed_status(current_user)
@ -37,33 +43,6 @@ module Types
key = indexed[stage_id]
groups = ::Ci::Group.fabricate(project, key.stage, statuses)
if Feature.enabled?(:ci_no_empty_groups, project)
groups.each do |group|
rejected = group.jobs.reject { |job| Ability.allowed?(current_user, :read_commit_status, job) }
group.jobs.select! { |job| Ability.allowed?(current_user, :read_commit_status, job) }
next unless group.jobs.empty?
exc = StandardError.new('Empty Ci::Group')
traces = rejected.map do |job|
trace = []
policy = Ability.policy_for(current_user, job)
policy.debug(:read_commit_status, trace)
trace
end
extra = {
current_user_id: current_user&.id,
project_id: project.id,
pipeline_id: pl.id,
stage_id: stage_id,
group_name: group.name,
rejected_job_ids: rejected.map(&:id),
rejected_traces: traces
}
Gitlab::ErrorTracking.track_exception(exc, extra)
end
groups.reject! { |group| group.jobs.empty? }
end
loader.call(key, groups)
end
end

View File

@ -144,7 +144,7 @@ module Types
resolver: Resolvers::GroupLabelsResolver
field :timelogs, ::Types::TimelogType.connection_type, null: false,
description: 'Time logged on issues in the group and its subgroups.',
description: 'Time logged on issues and merge requests in the group and its subgroups.',
extras: [:lookahead],
complexity: 5,
resolver: ::Resolvers::TimelogResolver

View File

@ -180,14 +180,15 @@ module Types
resolver: Resolvers::IssuesResolver.single
field :packages,
description: 'Packages of the project.',
resolver: Resolvers::ProjectPackagesResolver
description: 'Packages of the project.',
resolver: Resolvers::ProjectPackagesResolver
field :jobs,
Types::Ci::JobType.connection_type,
null: true,
description: 'Jobs of a project. This field can only be resolved for one project in any single request.',
resolver: Resolvers::ProjectJobsResolver
type: Types::Ci::JobType.connection_type,
null: true,
authorize: :read_commit_status,
description: 'Jobs of a project. This field can only be resolved for one project in any single request.',
resolver: Resolvers::ProjectJobsResolver
field :pipelines,
null: true,

View File

@ -26,6 +26,11 @@ module Types
null: true,
description: 'The issue that logged time was added to.'
field :merge_request,
Types::MergeRequestType,
null: true,
description: 'The merge request that logged time was added to.'
field :note,
Types::Notes::NoteType,
null: true,

View File

@ -382,15 +382,26 @@ module ApplicationHelper
def autocomplete_data_sources(object, noteable_type)
return {} unless object && noteable_type
{
members: members_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
issues: issues_project_autocomplete_sources_path(object),
mergeRequests: merge_requests_project_autocomplete_sources_path(object),
labels: labels_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
milestones: milestones_project_autocomplete_sources_path(object),
commands: commands_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
snippets: snippets_project_autocomplete_sources_path(object)
}
if object.is_a?(Group)
{
members: members_group_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
issues: issues_group_autocomplete_sources_path(object),
mergeRequests: merge_requests_group_autocomplete_sources_path(object),
labels: labels_group_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
milestones: milestones_group_autocomplete_sources_path(object),
commands: commands_group_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id])
}
else
{
members: members_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
issues: issues_project_autocomplete_sources_path(object),
mergeRequests: merge_requests_project_autocomplete_sources_path(object),
labels: labels_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
milestones: milestones_project_autocomplete_sources_path(object),
commands: commands_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
snippets: snippets_project_autocomplete_sources_path(object)
}
end
end
def asset_to_string(name)

View File

@ -2,6 +2,8 @@
module DevOpsReportHelper
def devops_score_metrics(metric)
return {} if metric.blank?
{
averageScore: average_score_data(metric),
cards: devops_score_card_data(metric),

View File

@ -25,7 +25,7 @@ module GroupsHelper
applications#index
applications#show
applications#edit
packages_and_registries#index
packages_and_registries#show
groups/runners#show
groups/runners#edit
]

View File

@ -11,6 +11,10 @@ module InviteMembersHelper
Feature.enabled?(:invite_members_group_modal, project.group) && can_manage_project_members?(project)
end
def can_invite_group_for_project?(project)
Feature.enabled?(:invite_members_group_modal, project.group) && project.allowed_to_share_with_group?
end
def directly_invite_members?
strong_memoize(:directly_invite_members) do
can_import_members?

View File

@ -680,7 +680,7 @@ module ProjectsHelper
operations#show
badges#index
pages#show
packages_and_registries#index
packages_and_registries#show
projects/runners#show
projects/runners#edit
]

View File

@ -15,6 +15,6 @@ module HasTimelogsReport
private
def timelogs_for(start_time, end_time)
Timelog.between_times(start_time, end_time).for_issues_in_group(self)
Timelog.between_times(start_time, end_time).in_group(self)
end
end

View File

@ -14,12 +14,8 @@ class Timelog < ApplicationRecord
belongs_to :user
belongs_to :note
scope :for_issues_in_group, -> (group) do
joins(:issue).where(
'EXISTS (?)',
Project.select(1).where(namespace: group.self_and_descendants)
.where('issues.project_id = projects.id')
)
scope :in_group, -> (group) do
joins(:project).where(projects: { namespace: group.self_and_descendants })
end
scope :between_times, -> (start_time, end_time) do

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
module Ci
class StagePolicy < BasePolicy
delegate :pipeline
end
end

View File

@ -5,18 +5,20 @@ module Ci
class CodeQualityMrDiffPresenter < Gitlab::View::Presenter::Delegated
include Gitlab::Utils::StrongMemoize
def for_files(filenames)
quality_files = raw_report["files"].select { |key| filenames.include?(key) }
def for_files(merge_request)
filenames = merge_request.new_paths
mr_diff_report = raw_report(merge_request.id)
quality_files = mr_diff_report["files"]&.select { |key| filenames.include?(key) }
{ files: quality_files }
end
private
def raw_report
def raw_report(merge_request_id)
strong_memoize(:raw_report) do
self.each_blob do |blob|
Gitlab::Json.parse(blob).with_indifferent_access
Gitlab::Json.parse(blob).with_indifferent_access.fetch("merge_request_#{merge_request_id}", {})
end
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class GroupIssuableAutocompleteEntity < Grape::Entity
expose :iid
expose :title
expose :reference do |issuable, options|
issuable.to_reference(options[:parent_group])
end
end

View File

@ -0,0 +1,5 @@
# frozen_string_literal: true
class GroupIssuableAutocompleteSerializer < BaseSerializer
entity GroupIssuableAutocompleteEntity
end

View File

@ -12,7 +12,7 @@ module Ci
{
status: :parsed,
key: key(base_pipeline, head_pipeline),
data: head_pipeline.pipeline_artifacts.find_by_file_type(:code_quality_mr_diff).present.for_files(merge_request.new_paths)
data: head_pipeline.pipeline_artifacts.find_by_file_type(:code_quality_mr_diff).present.for_files(merge_request)
}
rescue StandardError => e
Gitlab::ErrorTracking.track_exception(e, project_id: project.id)

View File

@ -2,11 +2,18 @@
module Ci
module PipelineArtifacts
class CreateCodeQualityMrDiffReportService
def execute(pipeline)
include Gitlab::Utils::StrongMemoize
def initialize(pipeline)
@pipeline = pipeline
end
def execute
return unless pipeline.can_generate_codequality_reports?
return if pipeline.has_codequality_mr_diff_report?
return unless new_errors_introduced?
file = build_carrierwave_file(pipeline)
file = build_carrierwave_file!
pipeline.pipeline_artifacts.create!(
project_id: pipeline.project_id,
@ -20,18 +27,54 @@ module Ci
private
def build_carrierwave_file(pipeline)
attr_reader :pipeline
def merge_requests
strong_memoize(:merge_requests) do
pipeline.merge_requests_as_head_pipeline
end
end
def head_report
strong_memoize(:head_report) do
pipeline.codequality_reports
end
end
def base_report(merge_request)
strong_memoize(:base_report) do
merge_request&.base_pipeline&.codequality_reports
end
end
def mr_diff_report_by_merge_requests
strong_memoize(:mr_diff_report_by_merge_requests) do
merge_requests.each_with_object({}) do |merge_request, hash|
key = "merge_request_#{merge_request.id}"
new_errors = Gitlab::Ci::Reports::CodequalityReportsComparer.new(base_report(merge_request), head_report).new_errors
next if new_errors.empty?
hash[key] = Gitlab::Ci::Reports::CodequalityMrDiff.new(new_errors)
end
end
end
def new_errors_introduced?
mr_diff_report_by_merge_requests.present?
end
def build_carrierwave_file!
CarrierWaveStringFile.new_file(
file_content: build_quality_mr_diff_report(pipeline),
file_content: build_quality_mr_diff_report(mr_diff_report_by_merge_requests),
filename: Ci::PipelineArtifact::DEFAULT_FILE_NAMES.fetch(:code_quality_mr_diff),
content_type: 'application/json'
)
end
def build_quality_mr_diff_report(pipeline)
mr_diff_report = Gitlab::Ci::Reports::CodequalityMrDiff.new(pipeline.codequality_reports)
Ci::CodequalityMrDiffReportSerializer.new.represent(mr_diff_report).to_json # rubocop: disable CodeReuse/Serializer
def build_quality_mr_diff_report(mr_diff_report)
mr_diff_report.each_with_object({}) do |diff_report, hash|
hash[diff_report.first] = Ci::CodequalityMrDiffReportSerializer.new.represent(diff_report.second) # rubocop: disable CodeReuse/Serializer
end.to_json
end
end
end

View File

@ -0,0 +1,49 @@
# frozen_string_literal: true
module Groups
class AutocompleteService < Groups::BaseService
include LabelsAsHash
# rubocop: disable CodeReuse/ActiveRecord
def issues(confidential_only: false, issue_types: nil)
finder_params = { group_id: group.id, include_subgroups: true, state: 'opened' }
finder_params[:confidential] = true if confidential_only.present?
finder_params[:issue_types] = issue_types if issue_types.present?
IssuesFinder.new(current_user, finder_params)
.execute
.preload(project: :namespace)
.select(:iid, :title, :project_id)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def merge_requests
MergeRequestsFinder.new(current_user, group_id: group.id, include_subgroups: true, state: 'opened')
.execute
.preload(target_project: :namespace)
.select(:iid, :title, :target_project_id)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def milestones
group_ids = group.self_and_ancestors.public_or_visible_to_user(current_user).pluck(:id)
MilestonesFinder.new(group_ids: group_ids).execute.select(:iid, :title, :due_date)
end
# rubocop: enable CodeReuse/ActiveRecord
def labels_as_hash(target)
super(target, group_id: group.id, only_group_labels: true, include_ancestor_groups: true)
end
def commands(noteable)
return [] unless noteable
QuickActions::InterpretService.new(nil, current_user).available_commands(noteable)
end
end
end
Groups::AutocompleteService.prepend_if_ee('EE::Groups::AutocompleteService')

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
module Groups
class ParticipantsService < Groups::BaseService
include Users::ParticipableService
def execute(noteable)
@noteable = noteable
participants =
noteable_owner +
participants_in_noteable +
all_members +
groups +
group_members
render_participants_as_hash(participants.uniq)
end
def all_members
count = group_members.count
[{ username: "all", name: "All Group Members", count: count }]
end
def group_members
return [] unless noteable
@group_members ||= sorted(noteable.group.direct_and_indirect_users)
end
end
end

View File

@ -1,7 +0,0 @@
.container.devops-empty
.col-sm-12.justify-content-center.text-center
= custom_icon('dev_ops_report_no_data')
%h4= _('Data is still calculating...')
%p
= _('It may be several days before you see feature usage data.')
= link_to _('Our documentation includes an example DevOps Score report.'), help_page_path('user/admin_area/analytics/dev_ops_report'), target: '_blank'

View File

@ -4,9 +4,7 @@
= render 'callout'
- if !usage_ping_enabled
#js-devops-empty-state{ data: { is_admin: current_user&.admin.to_s, empty_state_svg_path: image_path('illustrations/convdev/convdev_no_index.svg'), enable_usage_ping_link: metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), docs_link: help_page_path('development/usage_ping/index.md') } }
- elsif @metric.blank?
= render 'no_data'
#js-devops-usage-ping-disabled{ data: { is_admin: current_user&.admin.to_s, empty_state_svg_path: image_path('illustrations/convdev/convdev_no_index.svg'), enable_usage_ping_link: metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), docs_link: help_page_path('development/usage_ping/index.md') } }
- else
#js-devops-score{ data: { devops_score_metrics: devops_score_metrics(@metric).to_json } }
#js-devops-score{ data: { devops_score_metrics: devops_score_metrics(@metric).to_json, devops_report_docs_path: help_page_path('user/admin_area/analytics/dev_ops_report'), no_data_image_path: image_path('dev_ops_report_no_data.svg') } }

View File

@ -12,7 +12,11 @@
= f.label :description, _("Description")
.col-sm-10
= render layout: 'shared/md_preview', locals: { url: group_preview_markdown_path } do
= render 'shared/zen', f: f, attr: :description, classes: 'note-textarea', qa_selector: 'milestone_description_field', placeholder: _('Write milestone description...'), supports_autocomplete: false
= render 'shared/zen', f: f, attr: :description,
classes: 'note-textarea',
qa_selector: 'milestone_description_field',
supports_autocomplete: true,
placeholder: _('Write milestone description...')
.clearfix
.error-alert
= render "shared/milestones/form_dates", f: f

View File

@ -1,34 +1,3 @@
- if project_nav_tab?(:confluence)
- confluence_url = project_wikis_confluence_path(@project)
= nav_link do
= link_to confluence_url, class: 'shortcuts-confluence' do
.nav-icon-container
= image_tag 'confluence.svg', alt: _('Confluence')
%span.nav-item-name
= _('Confluence')
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(html_options: { class: 'fly-out-top-item' } ) do
= link_to confluence_url, target: '_blank', rel: 'noopener noreferrer' do
%strong.fly-out-top-item-name
= _('Confluence')
- if project_nav_tab? :wiki
= render 'layouts/nav/sidebar/wiki_link', wiki_url: wiki_path(@project.wiki)
- if project_nav_tab?(:external_wiki)
- external_wiki_url = @project.external_wiki.external_wiki_url
= nav_link do
= link_to external_wiki_url, class: 'shortcuts-external_wiki' do
.nav-icon-container
= sprite_icon('external-link')
%span.nav-item-name
= s_('ExternalWikiService|External wiki')
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(html_options: { class: "fly-out-top-item" } ) do
= link_to external_wiki_url do
%strong.fly-out-top-item-name
= s_('ExternalWikiService|External wiki')
- if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
= link_to project_snippets_path(@project), class: 'shortcuts-snippets', data: { qa_selector: 'snippets_link' } do

View File

@ -13,7 +13,11 @@
= f.label :description, _('Description')
.col-sm-10
= render layout: 'shared/md_preview', locals: { url: preview_markdown_path(@project) } do
= render 'shared/zen', f: f, attr: :description, classes: 'note-textarea', qa_selector: 'milestone_description_field', placeholder: _('Write milestone description...')
= render 'shared/zen', f: f, attr: :description,
classes: 'note-textarea',
qa_selector: 'milestone_description_field',
supports_autocomplete: true,
placeholder: _('Write milestone description...')
= render 'shared/notes/hints'
.clearfix
.error-alert

View File

@ -4,7 +4,7 @@
.js-remove-member-modal
.row.gl-mt-3
.col-lg-12
- if can_invite_members_for_project?(@project)
- if can_invite_members_for_project?(@project) || can_invite_group_for_project?(@project)
.row
.col-md-12.col-lg-6.gl-display-flex
.gl-flex-direction-column.gl-flex-wrap.align-items-baseline
@ -18,12 +18,15 @@
= html_escape(_("Members can be added by project %{i_open}Maintainers%{i_close} or %{i_open}Owners%{i_close}")) % { i_open: '<i>'.html_safe, i_close: '</i>'.html_safe }
.col-md-12.col-lg-6
.gl-display-flex.gl-flex-wrap.gl-justify-content-end
= link_to _("Import a project"),
import_project_project_members_path(@project),
class: "btn btn-default btn-md gl-button gl-mt-3 gl-sm-w-auto gl-w-full",
title: _("Import members from another project")
.js-invite-group-trigger{ data: { classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite a group') } }
.js-invite-members-trigger{ data: { variant: 'success', classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite members') } }
- if can_import_members?
= link_to _("Import a project"),
import_project_project_members_path(@project),
class: "btn btn-default btn-md gl-button gl-mt-3 gl-sm-w-auto gl-w-full",
title: _("Import members from another project")
- if @project.allowed_to_share_with_group?
.js-invite-group-trigger{ data: { classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite a group') } }
- if !membership_locked?
.js-invite-members-trigger{ data: { variant: 'success', classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite members') } }
= render 'projects/invite_members_modal', project: @project
- else
@ -36,7 +39,7 @@
%p
= html_escape(_("Members can be added by project %{i_open}Maintainers%{i_close} or %{i_open}Owners%{i_close}")) % { i_open: '<i>'.html_safe, i_close: '</i>'.html_safe }
- if !can_invite_members_for_project?(@project) && can_manage_project_members?(@project) && project_can_be_shared?
- if Feature.disabled?(:invite_members_group_modal, @project.group) && can_manage_project_members?(@project) && project_can_be_shared?
- if !membership_locked? && @project.allowed_to_share_with_group?
%ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' }
%li.nav-tab{ role: 'presentation' }

View File

@ -15,7 +15,7 @@ module Ci
def perform(pipeline_id)
Ci::Pipeline.find_by_id(pipeline_id).try do |pipeline|
Ci::PipelineArtifacts::CreateCodeQualityMrDiffReportService.new.execute(pipeline)
Ci::PipelineArtifacts::CreateCodeQualityMrDiffReportService.new(pipeline).execute
end
end
end

View File

@ -0,0 +1,5 @@
---
title: Migrate DevOps Score empty state to Vue
merge_request: 60715
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: updating hover state to match other pipeline graph buttons
merge_request: 60801
author: Matt Saddington @mattsaddo
type: other

View File

@ -0,0 +1,5 @@
---
title: Adds field authorization to pipeline fields
merge_request: 60754
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Partial index optimization for namespaces id
merge_request: 61098
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Expose merge request timelogs via GraphQL
merge_request: 57322
author: Lee Tickett @leetickett
type: added

View File

@ -0,0 +1,5 @@
---
title: Add autocomplete to milestone description
merge_request: 59564
author: Jonas Wälter @wwwjon
type: added

View File

@ -0,0 +1,5 @@
---
title: Fix Openshift template to run on main branch
merge_request: 60811
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: 'Fix false positive for codequality mr diff report'
merge_request: 59421
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Add slack integration individual usage ping
merge_request: 60847
author:
type: changed

View File

@ -1,8 +1,8 @@
---
name: usage_data_track_ecosystem_slack_service
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54347
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/322588
milestone: '13.10'
name: cached_encoding_detection
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60128
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/328819
milestone: '13.12'
type: development
group: group::ecosystem
group: group::source code
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: find_remote_root_refs_inmemory
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60583
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/329664
milestone: '13.12'
type: development
group: group::gitaly
default_enabled: true

View File

@ -0,0 +1,8 @@
---
name: limited_diff_highlighting
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53768
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/323566
milestone: '13.12'
type: development
group: group::source code
default_enabled: true

View File

@ -1,8 +1,8 @@
---
name: ci_no_empty_groups
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58789
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/327139
milestone: '13.11'
name: track_highlight_timeouts
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60956
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/329909
milestone: '13.12'
type: development
group: group::verify
group: group::code review
default_enabled: false

View File

@ -52,7 +52,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resources :applications
resources :packages_and_registries, only: [:index]
resource :packages_and_registries, only: [:show]
end
resource :variables, only: [:show, :update]
@ -114,6 +114,17 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resources :container_registries, only: [:index, :show], controller: 'registry/repositories'
resource :dependency_proxy, only: [:show, :update]
resources :email_campaigns, only: :index
resources :autocomplete_sources, only: [] do
collection do
get 'members'
get 'issues'
get 'merge_requests'
get 'labels'
get 'commands'
get 'milestones'
end
end
end
scope(path: '*id',

View File

@ -130,7 +130,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
resources :packages_and_registries, only: [:index]
resource :packages_and_registries, only: [:show]
end
resources :autocomplete_sources, only: [] do

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
class CreateNamespacesIdParentIdInversePartialIndex < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
NAME = 'index_namespaces_id_parent_id_is_not_null'
disable_ddl_transaction!
def up
add_concurrent_index :namespaces, :id, where: 'parent_id IS NOT NULL', name: NAME
end
def down
remove_concurrent_index :namespaces, :id, name: NAME
end
end

View File

@ -0,0 +1 @@
f400225e6caa854f825422b9799e61ea557ab4bd3e4a33dc3cd3193ed3ce1db2

View File

@ -23454,6 +23454,8 @@ CREATE UNIQUE INDEX index_namespace_root_storage_statistics_on_namespace_id ON n
CREATE UNIQUE INDEX index_namespace_statistics_on_namespace_id ON namespace_statistics USING btree (namespace_id);
CREATE INDEX index_namespaces_id_parent_id_is_not_null ON namespaces USING btree (id) WHERE (parent_id IS NOT NULL);
CREATE INDEX index_namespaces_id_parent_id_is_null ON namespaces USING btree (id) WHERE (parent_id IS NULL);
CREATE INDEX index_namespaces_on_created_at ON namespaces USING btree (created_at);

View File

@ -9005,7 +9005,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
##### `Group.timelogs`
Time logged on issues in the group and its subgroups.
Time logged on issues and merge requests in the group and its subgroups.
Returns [`TimelogConnection!`](#timelogconnection).
@ -12498,6 +12498,7 @@ Represents a historically accurate report about the timebox.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="timelogissue"></a>`issue` | [`Issue`](#issue) | The issue that logged time was added to. |
| <a id="timelogmergerequest"></a>`mergeRequest` | [`MergeRequest`](#mergerequest) | The merge request that logged time was added to. |
| <a id="timelognote"></a>`note` | [`Note`](#note) | The note where the quick action to add the logged time was executed. |
| <a id="timelogspentat"></a>`spentAt` | [`Time`](#time) | Timestamp of when the time tracked was spent at. |
| <a id="timelogtimespent"></a>`timeSpent` | [`Int!`](#int) | The time spent displayed in seconds. |

View File

@ -6,33 +6,36 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Group DevOps Adoption **(ULTIMATE)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/321083) in GitLab 13.11.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/321083) as a [Beta feature](https://about.gitlab.com/handbook/product/gitlab-the-product/#beta) in GitLab 13.11.
> - [Deployed behind a feature flag](../../../user/feature_flags.md), disabled by default.
> - Disabled on GitLab.com.
> - Not recommended for production use.
> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-group-devops-adoption).
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
This in-development feature might not be available for your use. There can be
[risks when enabling features still in development](../../feature_flags.md#risks-when-enabling-features-still-in-development).
Refer to this feature's version history for more details.
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/321083) in GitLab 13.11 as a [Beta feature](https://about.gitlab.com/handbook/product/gitlab-the-product/#beta).
Prerequisites:
To access Group DevOps Adoption, navigate to your group sidebar and select **Analytics > DevOps Adoption**
- A minimum of [Reporter access](../../permissions.md) to the group.
To access Group DevOps Adoption, go to your group and select **Analytics > DevOps Adoption**.
Group DevOps Adoption shows you how individual groups and sub-groups within your organization use the following features:
- Approvals
- Deployments
- Issues
- Merge Requests
- Approvals
- Runners
- Pipelines
- Deployments
- Runners
- Scans
When managing groups in the UI, you can manage your sub-groups with the **Add/Remove sub-groups**
button, in the top right hand section of your Groups pages.
DevOps Adoption allows you to:
With DevOps Adoption you can:
- Verify whether you are getting the return on investment that you expected from GitLab.
- Identify specific sub-groups that are lagging in their adoption of GitLab so you can help them along in their DevOps journey.

View File

@ -41,9 +41,9 @@ For example, the following policy document allows assuming a role whose name sta
}
```
### Administration settings
### Configure Amazon authentication
Generate an access key for the IAM user, and configure GitLab with the credentials:
To configure Amazon authentication in GitLab, generate an access key for the IAM user in the Amazon AWS console, and following the steps below.
1. Navigate to **Admin Area > Settings > General** and expand the **Amazon EKS** section.
1. Check **Enable Amazon EKS integration**.
@ -232,7 +232,7 @@ sequenceDiagram
First, GitLab must obtain an initial set of credentials to communicate with the AWS API.
These credentials can be retrieved in one of two ways:
- Statically through the [Administration settings](#administration-settings).
- Statically through the [Configure Amazon authentication](#configure-amazon-authentication).
- Dynamically via an IAM instance profile ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/291015) in GitLab 13.7).
After GitLab retrieves the AWS credentials, it makes an
@ -272,7 +272,7 @@ arn:aws:iam::123456789012:role/gitlab-eks-provision'
#### Access denied: User `arn:aws:iam::x` is not authorized to perform: `sts:AssumeRole` on resource: `arn:aws:iam::y`
This error occurs when the credentials defined in the
[Administration settings](#administration-settings) cannot assume the role defined by the
[Configure Amazon authentication](#configure-amazon-authentication) cannot assume the role defined by the
Provision Role ARN. Check that:
1. The initial set of AWS credentials [has the AssumeRole policy](#additional-requirements-for-self-managed-instances).
@ -290,6 +290,10 @@ because GitLab has successfully assumed your provided role, but the role has
insufficient permissions to retrieve the resources needed for the form. Make sure
you've assigned the role the correct permissions.
### Key Pairs are not loaded
GitLab loads the key pairs from the **Cluster Region** specified. Ensure that key pair exists in that region.
#### `ROLLBACK_FAILED` during cluster creation
The creation process halted because GitLab encountered an error when creating

View File

@ -99,3 +99,8 @@ With this option enabled, `75h` is displayed instead of `1w 4d 3h`.
## Other interesting links
- [Time Tracking solutions page](https://about.gitlab.com/solutions/time-tracking/)
- Time Tracking GraphQL references:
- [Connection](../../api/graphql/reference/index.md#timelogconnection)
- [Edge](../../api/graphql/reference/index.md#timelogedge)
- [Fields](../../api/graphql/reference/index.md#timelog)
- [Group Timelogs](../../api/graphql/reference/index.md#grouptimelogs)

View File

@ -38,7 +38,7 @@ module Gitlab
# If Charlock says its binary
else
detect_encoding[:type] == :binary
find_encoding[:type] == :binary
end
end
@ -137,23 +137,25 @@ module Gitlab
end
def ruby_encoding
if hash = detect_encoding
if hash = find_encoding
hash[:ruby_encoding]
end
end
def encoding
if hash = detect_encoding
if hash = find_encoding
hash[:encoding]
end
end
def detect_encoding
@detect_encoding ||= CharlockHolmes::EncodingDetector.new.detect(data) if data # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def empty?
data.nil? || data == ""
end
private
def find_encoding
@find_encoding ||= Gitlab::EncodingHelper.detect_encoding(data) if data # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
end
end

View File

@ -6,8 +6,8 @@ module Gitlab
class CodequalityMrDiff
attr_reader :files
def initialize(raw_report)
@raw_report = raw_report
def initialize(new_errors)
@new_errors = new_errors
@files = {}
build_report!
end
@ -15,7 +15,7 @@ module Gitlab
private
def build_report!
codequality_files = @raw_report.all_degradations.each_with_object({}) do |degradation, codequality_files|
codequality_files = @new_errors.each_with_object({}) do |degradation, codequality_files|
unless codequality_files[degradation.dig(:location, :path)].present?
codequality_files[degradation.dig(:location, :path)] = []
end

View File

@ -46,27 +46,23 @@ review:
name: review/$CI_COMMIT_REF_NAME
url: http://$CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG.$OPENSHIFT_DOMAIN
on_stop: stop-review
only:
- branches
except:
- master
rules:
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
stop-review:
<<: *deploy
stage: cleanup
script:
- oc delete all -l "app=$APP"
when: manual
variables:
APP: review-$CI_COMMIT_REF_NAME
GIT_STRATEGY: none
environment:
name: review/$CI_COMMIT_REF_NAME
action: stop
only:
- branches
except:
- master
rules:
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
when: manual
staging:
<<: *deploy
@ -77,8 +73,8 @@ staging:
environment:
name: staging
url: http://$CI_PROJECT_NAME-staging.$OPENSHIFT_DOMAIN
only:
- master
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
production:
<<: *deploy
@ -86,9 +82,9 @@ production:
variables:
APP: production
APP_HOST: $CI_PROJECT_NAME.$OPENSHIFT_DOMAIN
when: manual
environment:
name: production
url: http://$CI_PROJECT_NAME.$OPENSHIFT_DOMAIN
only:
- master
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual

View File

@ -87,6 +87,7 @@ module Gitlab
def highlight_line(diff_line)
return unless diff_file && diff_file.diff_refs
return diff_line_highlighting(diff_line, plain: true) if blobs_too_large?
if Feature.enabled?(:diff_line_syntax_highlighting, project, default_enabled: :yaml)
diff_line_highlighting(diff_line)
@ -95,9 +96,10 @@ module Gitlab
end
end
def diff_line_highlighting(diff_line)
def diff_line_highlighting(diff_line, plain: false)
rich_line = syntax_highlighter(diff_line).highlight(
diff_line.text(prefix: false),
plain: plain,
context: { line_number: diff_line.line }
)
@ -158,6 +160,13 @@ module Gitlab
blob.load_all_data!
blob.present.highlight.lines
end
def blobs_too_large?
return false unless Feature.enabled?(:limited_diff_highlighting, project, default_enabled: :yaml)
return true if Gitlab::Highlight.too_large?(diff_file.old_blob&.size)
Gitlab::Highlight.too_large?(diff_file.new_blob&.size)
end
end
end
end

View File

@ -20,7 +20,7 @@ module Gitlab
return message if message.valid_encoding?
# return message if message type is binary
detect = CharlockHolmes::EncodingDetector.detect(message)
detect = detect_encoding(message)
return message.force_encoding("BINARY") if detect_binary?(message, detect)
if detect && detect[:encoding] && detect[:confidence] > ENCODING_CONFIDENCE_THRESHOLD
@ -37,16 +37,30 @@ module Gitlab
"--broken encoding: #{encoding}"
end
def detect_encoding(data, limit: CharlockHolmes::EncodingDetector::DEFAULT_BINARY_SCAN_LEN, cache_key: nil)
return if data.nil?
if Feature.enabled?(:cached_encoding_detection, type: :development, default_enabled: :yaml)
return CharlockHolmes::EncodingDetector.new(limit).detect(data) unless cache_key.present?
Rails.cache.fetch([:detect_binary, CharlockHolmes::VERSION, cache_key], expires_in: 1.week) do
CharlockHolmes::EncodingDetector.new(limit).detect(data)
end
else
CharlockHolmes::EncodingDetector.new(limit).detect(data)
end
end
def detect_binary?(data, detect = nil)
detect ||= CharlockHolmes::EncodingDetector.detect(data)
detect ||= detect_encoding(data)
detect && detect[:type] == :binary && detect[:confidence] == 100
end
def detect_libgit2_binary?(data)
# EncodingDetector checks the first 1024 * 1024 bytes for NUL byte, libgit2 checks
# only the first 8000 (https://github.com/libgit2/libgit2/blob/2ed855a9e8f9af211e7274021c2264e600c0f86b/src/filter.h#L15),
# which is what we use below to keep a consistent behavior.
detect = CharlockHolmes::EncodingDetector.new(8000).detect(data)
# EncodingDetector checks the first 1024 * 1024 bytes for NUL byte, libgit2 checks
# only the first 8000 (https://github.com/libgit2/libgit2/blob/2ed855a9e8f9af211e7274021c2264e600c0f86b/src/filter.h#L15),
# which is what we use below to keep a consistent behavior.
def detect_libgit2_binary?(data, cache_key: nil)
detect = detect_encoding(data, limit: 8000, cache_key: cache_key)
detect && detect[:type] == :binary
end
@ -54,7 +68,8 @@ module Gitlab
message = force_encode_utf8(message)
return message if message.valid_encoding?
detect = CharlockHolmes::EncodingDetector.detect(message)
detect = detect_encoding(message)
if detect && detect[:encoding]
begin
CharlockHolmes::Converter.convert(message, detect[:encoding], 'UTF-8')

View File

@ -110,8 +110,8 @@ module Gitlab
end
end
def binary?(data)
EncodingHelper.detect_libgit2_binary?(data)
def binary?(data, cache_key: nil)
EncodingHelper.detect_libgit2_binary?(data, cache_key: cache_key)
end
def size_could_be_lfs?(size)

View File

@ -700,11 +700,11 @@ module Gitlab
end
end
def find_remote_root_ref(remote_name)
return unless remote_name.present?
def find_remote_root_ref(remote_name, remote_url, authorization = nil)
return unless remote_name.present? && remote_url.present?
wrapped_gitaly_errors do
gitaly_remote_client.find_remote_root_ref(remote_name)
gitaly_remote_client.find_remote_root_ref(remote_name, remote_url, authorization)
end
end

View File

@ -41,7 +41,7 @@ module Gitlab
size: blob_data[:size],
commit_id: blob_data[:revision],
data: data,
binary: Gitlab::Git::Blob.binary?(data)
binary: Gitlab::Git::Blob.binary?(data, cache_key: blob_data[:oid])
)
end
end

View File

@ -43,11 +43,20 @@ module Gitlab
GitalyClient.call(@storage, :remote_service, :remove_remote, request, timeout: GitalyClient.long_timeout).result
end
def find_remote_root_ref(remote_name)
request = Gitaly::FindRemoteRootRefRequest.new(
repository: @gitaly_repo,
remote: remote_name
)
# The remote_name parameter is deprecated and will be removed soon.
def find_remote_root_ref(remote_name, remote_url, authorization)
request = if Feature.enabled?(:find_remote_root_refs_inmemory, default_enabled: :yaml)
Gitaly::FindRemoteRootRefRequest.new(
repository: @gitaly_repo,
remote_url: remote_url,
http_authorization_header: authorization
)
else
Gitaly::FindRemoteRootRefRequest.new(
repository: @gitaly_repo,
remote: remote_name
)
end
response = GitalyClient.call(@storage, :remote_service,
:find_remote_root_ref, request, timeout: GitalyClient.medium_timeout)

View File

@ -10,6 +10,10 @@ module Gitlab
.highlight(blob_content, continue: false, plain: plain)
end
def self.too_large?(size)
size.to_i > Gitlab.config.extra['maximum_text_highlight_size_kilobytes']
end
attr_reader :blob_name
def initialize(blob_name, blob_content, language: nil)
@ -22,7 +26,7 @@ module Gitlab
def highlight(text, continue: false, plain: false, context: {})
@context = context
plain ||= text.length > maximum_text_highlight_size
plain ||= self.class.too_large?(text.length)
highlighted_text = highlight_text(text, continue: continue, plain: plain)
highlighted_text = link_dependencies(text, highlighted_text) if blob_name
@ -64,6 +68,8 @@ module Gitlab
tokens = lexer.lex(text, continue: continue)
Timeout.timeout(timeout_time) { @formatter.format(tokens, context.merge(tag: tag)).html_safe }
rescue Timeout::Error => e
add_highlight_timeout_metric
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
highlight_plain(text)
rescue StandardError
@ -78,8 +84,17 @@ module Gitlab
Gitlab::DependencyLinker.link(blob_name, text, highlighted_text)
end
def maximum_text_highlight_size
Gitlab.config.extra['maximum_text_highlight_size_kilobytes']
def add_highlight_timeout_metric
return unless Feature.enabled?(:track_highlight_timeouts)
highlight_timeout.increment(source: Gitlab::Runtime.sidekiq? ? "background" : "foreground")
end
def highlight_timeout
@highlight_timeout ||= Gitlab::Metrics.counter(
:highlight_timeout,
'Counts the times highlights have timed out'
)
end
end
end

View File

@ -24,45 +24,35 @@
category: ecosystem
redis_slot: ecosystem
aggregation: weekly
feature_flag: usage_data_track_ecosystem_slack_service
- name: i_ecosystem_slack_service_push_notification
category: ecosystem
redis_slot: ecosystem
aggregation: weekly
feature_flag: usage_data_track_ecosystem_slack_service
- name: i_ecosystem_slack_service_deployment_notification
category: ecosystem
redis_slot: ecosystem
aggregation: weekly
feature_flag: usage_data_track_ecosystem_slack_service
- name: i_ecosystem_slack_service_wiki_page_notification
category: ecosystem
redis_slot: ecosystem
aggregation: weekly
feature_flag: usage_data_track_ecosystem_slack_service
- name: i_ecosystem_slack_service_merge_request_notification
category: ecosystem
redis_slot: ecosystem
aggregation: weekly
feature_flag: usage_data_track_ecosystem_slack_service
- name: i_ecosystem_slack_service_note_notification
category: ecosystem
redis_slot: ecosystem
aggregation: weekly
feature_flag: usage_data_track_ecosystem_slack_service
- name: i_ecosystem_slack_service_tag_push_notification
category: ecosystem
redis_slot: ecosystem
aggregation: weekly
feature_flag: usage_data_track_ecosystem_slack_service
- name: i_ecosystem_slack_service_confidential_note_notification
category: ecosystem
redis_slot: ecosystem
aggregation: weekly
feature_flag: usage_data_track_ecosystem_slack_service
- name: i_ecosystem_slack_service_confidential_issue_notification
category: ecosystem
redis_slot: ecosystem
aggregation: weekly
feature_flag: usage_data_track_ecosystem_slack_service

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
module Sidebars
module Projects
module Menus
class ConfluenceMenu < ::Sidebars::Menu
override :link
def link
project_wikis_confluence_path(context.project)
end
override :extra_container_html_options
def extra_container_html_options
{
class: 'shortcuts-confluence'
}
end
override :title
def title
_('Confluence')
end
override :image_path
def image_path
'confluence.svg'
end
override :image_html_options
def image_html_options
{
alt: title
}
end
override :render?
def render?
context.project.has_confluence?
end
end
end
end
end

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
module Sidebars
module Projects
module Menus
class ExternalWikiMenu < ::Sidebars::Menu
override :link
def link
external_wiki.external_wiki_url
end
override :extra_container_html_options
def extra_container_html_options
{
target: '_blank',
rel: 'noopener noreferrer',
class: 'shortcuts-external_wiki'
}
end
override :extra_collapsed_container_html_options
def extra_collapsed_container_html_options
{
target: '_blank',
rel: 'noopener noreferrer'
}
end
override :title
def title
s_('ExternalWikiService|External wiki')
end
override :sprite_icon
def sprite_icon
'external-link'
end
override :render?
def render?
external_wiki.present?
end
private
def external_wiki
@external_wiki ||= context.project.external_wiki
end
end
end
end
end

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
module Sidebars
module Projects
module Menus
class WikiMenu < ::Sidebars::Menu
override :link
def link
wiki_path(context.project.wiki)
end
override :extra_container_html_options
def extra_container_html_options
{
class: 'shortcuts-wiki'
}
end
override :title
def title
_('Wiki')
end
override :sprite_icon
def sprite_icon
'book'
end
override :render?
def render?
can?(context.current_user, :read_wiki, context.project)
end
override :active_routes
def active_routes
{ controller: :wikis }
end
end
end
end
end

View File

@ -20,6 +20,8 @@ module Sidebars
add_menu(Sidebars::Projects::Menus::InfrastructureMenu.new(context))
add_menu(Sidebars::Projects::Menus::PackagesRegistriesMenu.new(context))
add_menu(Sidebars::Projects::Menus::AnalyticsMenu.new(context))
add_menu(confluence_or_wiki_menu)
add_menu(Sidebars::Projects::Menus::ExternalWikiMenu.new(context))
end
override :render_raw_menus_partial
@ -31,6 +33,14 @@ module Sidebars
def aria_label
_('Project navigation')
end
private
def confluence_or_wiki_menu
confluence_menu = ::Sidebars::Projects::Menus::ConfluenceMenu.new(context)
confluence_menu.render? ? confluence_menu : Sidebars::Projects::Menus::WikiMenu.new(context)
end
end
end
end

View File

@ -5040,6 +5040,15 @@ msgstr ""
msgid "BillingPlan|Upgrade for free"
msgstr ""
msgid "Billings|Verify User Account"
msgstr ""
msgid "Billings|Verify account"
msgstr ""
msgid "Billings|Your user account has been flagged for potential abuse for running a large number of concurrent pipelines. To continue to run a large number of concurrent pipelines, you'll need to validate your account with a credit card. %{strongStart}GitLab will not charge your credit card, it will only be used for validation.%{strongEnd}"
msgstr ""
msgid "Billing|An email address is only visible for users with public emails."
msgstr ""
@ -22984,9 +22993,6 @@ msgstr ""
msgid "Otherwise, click the link below to complete the process:"
msgstr ""
msgid "Our documentation includes an example DevOps Score report."
msgstr ""
msgid "Out-of-compliance with this project's policies and should be removed"
msgstr ""
@ -28804,6 +28810,9 @@ msgstr ""
msgid "SecurityReports|Your feedback is important to us! We will ask again in a week."
msgstr ""
msgid "See example DevOps Score page in our documentation."
msgstr ""
msgid "See metrics"
msgstr ""
@ -37181,6 +37190,9 @@ msgstr ""
msgid "Your authorized applications"
msgstr ""
msgid "Your browser does not support iFrames"
msgstr ""
msgid "Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer)."
msgstr ""
@ -38255,9 +38267,6 @@ msgstr ""
msgid "mrWidget|Are you adding technical debt or code vulnerabilities?"
msgstr ""
msgid "mrWidget|Before this can be merged, one or more threads must be resolved."
msgstr ""
msgid "mrWidget|Cancel automatic merge"
msgstr ""
@ -38324,6 +38333,9 @@ msgstr ""
msgid "mrWidget|Merge"
msgstr ""
msgid "mrWidget|Merge blocked: all threads must be resolved."
msgstr ""
msgid "mrWidget|Merge failed."
msgstr ""

View File

@ -18,10 +18,6 @@ module QA
element :members_link
end
view 'app/views/layouts/nav/sidebar/_wiki_link.html.haml' do
element :wiki_link
end
def click_merge_requests
within_sidebar do
click_element(:sidebar_menu_link, menu_item: 'Merge requests')
@ -30,7 +26,7 @@ module QA
def click_wiki
within_sidebar do
click_element(:wiki_link)
click_element(:sidebar_menu_link, menu_item: 'Wiki')
end
end

View File

@ -0,0 +1,69 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Groups::AutocompleteSourcesController do
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:group) { create(:group, :private) }
before_all do
group.add_developer(user)
end
before do
sign_in(user)
end
describe '#issues' do
using RSpec::Parameterized::TableSyntax
let_it_be(:project) { create(:project, group: group) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:incident) { create(:incident, project: project) }
let(:none) { [] }
let(:all) { [issue, incident] }
where(:issue_types, :expected) do
nil | :all
'' | :all
'invalid' | :none
'issue' | :issue
'incident' | :incident
end
with_them do
it 'returns the correct response', :aggregate_failures do
issues = Array(expected).flat_map { |sym| public_send(sym) }
params = { group_id: group, issue_types: issue_types }.compact
get :issues, params: params
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an(Array)
expect(json_response.size).to eq(issues.size)
expect(json_response.map { |issue| issue['iid'] })
.to match_array(issues.map(&:iid))
end
end
end
describe '#milestones' do
it 'returns correct response' do
parent_group = create(:group, :private)
group.update!(parent: parent_group)
sub_group = create(:group, :private, parent: sub_group)
create(:milestone, group: parent_group)
create(:milestone, group: sub_group)
group_milestone = create(:milestone, group: group)
get :milestones, params: { group_id: group }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq(1)
expect(json_response.first).to include(
'iid' => group_milestone.iid, 'title' => group_milestone.title
)
end
end
end

View File

@ -369,6 +369,12 @@ FactoryBot.define do
end
end
trait :codequality_reports_without_degradation do
after(:build) do |build|
build.job_artifacts << create(:ci_job_artifact, :codequality_without_errors, job: build)
end
end
trait :terraform_reports do
after(:build) do |build|
build.job_artifacts << create(:ci_job_artifact, :terraform, job: build)

View File

@ -12,18 +12,39 @@ RSpec.describe 'Groups > Members > Manage groups', :js do
sign_in(user)
end
context 'when group link does not exist' do
let_it_be(:group) { create(:group) }
let_it_be(:group_to_add) { create(:group) }
context 'with invite_members_group_modal disabled' do
context 'when group link does not exist' do
let_it_be(:group) { create(:group) }
let_it_be(:group_to_add) { create(:group) }
before do
stub_feature_flags(invite_members_group_modal: false)
group.add_owner(user)
visit group_group_members_path(group)
before do
stub_feature_flags(invite_members_group_modal: false)
group.add_owner(user)
visit group_group_members_path(group)
end
it 'add group to group' do
add_group(group_to_add.id, 'Reporter')
click_groups_tab
page.within(first_row) do
expect(page).to have_content(group_to_add.name)
expect(page).to have_content('Reporter')
end
end
end
end
it 'add group to group' do
add_group(group_to_add.id, 'Reporter')
context 'when group link does not exist' do
it 'can share a group with group' do
group = create(:group)
group_to_add = create(:group)
group.add_owner(user)
group_to_add.add_owner(user)
visit group_group_members_path(group)
invite_group(group_to_add.name, 'Reporter')
click_groups_tab
@ -126,6 +147,21 @@ RSpec.describe 'Groups > Members > Manage groups', :js do
end
end
def invite_group(name, role)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
click_button name
click_button 'Guest'
wait_for_requests
click_button role
click_button 'Invite'
page.refresh
end
def click_groups_tab
expect(page).to have_link 'Groups'
click_link "Groups"

View File

@ -54,11 +54,11 @@ RSpec.describe 'Group milestones' do
expect(find('.start_date')).to have_content(Date.today.at_beginning_of_month.strftime('%b %-d, %Y'))
end
it 'description input does not support autocomplete' do
it 'description input support autocomplete' do
description = find('.note-textarea')
description.native.send_keys('!')
expect(page).not_to have_selector('.atwho-view')
expect(page).to have_selector('.atwho-view')
end
end

View File

@ -0,0 +1,80 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'GFM autocomplete', :js do
let_it_be(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
let_it_be(:group) { create(:group, name: 'Ancestor') }
let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:issue) { create(:issue, project: project, assignees: [user], title: 'My special issue') }
let_it_be(:label) { create(:group_label, group: group, title: 'special+') }
let_it_be(:milestone) { create(:milestone, resource_parent: group, title: "group milestone") }
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
shared_examples 'displays autocomplete menu for all entities' do
it 'autocompletes all available entities' do
fill_in 'Description', with: User.reference_prefix
wait_for_requests
expect(find_autocomplete_menu).to be_visible
expect_autocomplete_entry(group.name)
fill_in 'Description', with: Label.reference_prefix
wait_for_requests
expect(find_autocomplete_menu).to be_visible
expect_autocomplete_entry(label.title)
fill_in 'Description', with: Milestone.reference_prefix
wait_for_requests
expect(find_autocomplete_menu).to be_visible
expect_autocomplete_entry(milestone.title)
fill_in 'Description', with: Issue.reference_prefix
wait_for_requests
expect(find_autocomplete_menu).to be_visible
expect_autocomplete_entry(issue.title)
fill_in 'Description', with: MergeRequest.reference_prefix
wait_for_requests
expect(find_autocomplete_menu).to be_visible
expect_autocomplete_entry(merge_request.title)
end
end
before_all do
group.add_maintainer(user)
end
describe 'new milestone page' do
before do
sign_in(user)
visit new_group_milestone_path(group)
wait_for_requests
end
it_behaves_like 'displays autocomplete menu for all entities'
end
describe 'update milestone page' do
before do
sign_in(user)
visit edit_group_milestone_path(group, milestone)
wait_for_requests
end
it_behaves_like 'displays autocomplete menu for all entities'
end
private
def find_autocomplete_menu
find('.atwho-view ul', visible: true)
end
def expect_autocomplete_entry(entry)
page.within('.atwho-container') do
expect(page).to have_content(entry)
end
end
end

View File

@ -72,7 +72,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j
end
it 'shows a warning that the merge request contains unresolved threads' do
expect(page).to have_content 'Before this can be merged,'
expect(page).to have_content 'all threads must be resolved'
end
it 'has a link to resolve all threads by creating an issue' do

View File

@ -21,7 +21,7 @@ RSpec.describe 'Merge request > User sees merge button depending on unresolved t
context 'with unresolved threads' do
it 'does not allow to merge' do
expect(page).not_to have_button 'Merge'
expect(page).to have_content('Before this can be merged,')
expect(page).to have_content('all threads must be resolved')
end
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Diff file viewer', :js do
RSpec.describe 'Diff file viewer', :js, :with_clean_rails_cache do
let(:project) { create(:project, :public, :repository) }
def visit_commit(sha, anchor: nil)

View File

@ -9,22 +9,44 @@ RSpec.describe 'Project > Members > Invite group', :js do
let(:maintainer) { create(:user) }
before do
stub_feature_flags(invite_members_group_modal: false)
using RSpec::Parameterized::TableSyntax
where(:invite_members_group_modal_enabled, :expected_invite_group_selector) do
true | 'button[data-qa-selector="invite_a_group_button"]'
false | '#invite-group-tab'
end
with_them do
before do
stub_feature_flags(invite_members_group_modal: invite_members_group_modal_enabled)
end
it 'displays either the invite group button or the form with tabs based on the feature flag' do
project = create(:project, namespace: create(:group))
project.add_maintainer(maintainer)
sign_in(maintainer)
visit project_project_members_path(project)
expect(page).to have_selector(expected_invite_group_selector)
end
end
describe 'Share with group lock' do
let(:invite_group_selector) { 'button[data-qa-selector="invite_a_group_button"]' }
shared_examples 'the project can be shared with groups' do
it 'the "Invite group" tab exists' do
it 'the "Invite a group" button exists' do
visit project_project_members_path(project)
expect(page).to have_selector('#invite-group-tab')
expect(page).to have_selector(invite_group_selector)
end
end
shared_examples 'the project cannot be shared with groups' do
it 'the "Invite group" tab does not exist' do
it 'the "Invite a group" button does not exist' do
visit project_project_members_path(project)
expect(page).not_to have_selector('#invite-group-tab')
expect(page).not_to have_selector(invite_group_selector)
end
end
@ -41,7 +63,9 @@ RSpec.describe 'Project > Members > Invite group', :js do
context 'when the group has "Share with group lock" disabled' do
it_behaves_like 'the project can be shared with groups'
it 'the project can be shared with another group' do
it 'the project can be shared with another group when the feature flag invite_members_group_modal is disabled' do
stub_feature_flags(invite_members_group_modal: false)
visit project_project_members_path(project)
expect(page).not_to have_link 'Groups'
@ -56,6 +80,27 @@ RSpec.describe 'Project > Members > Invite group', :js do
expect(members_table).to have_content(group_to_share_with.name)
end
it 'the project can be shared with another group when the feature flag invite_members_group_modal is enabled' do
stub_feature_flags(invite_members_group_modal: true)
visit project_project_members_path(project)
expect(page).not_to have_link 'Groups'
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
click_button group_to_share_with.name
click_button 'Invite'
visit project_project_members_path(project)
click_link 'Groups'
expect(members_table).to have_content(group_to_share_with.name)
end
end
context 'when the group has "Share with group lock" enabled' do
@ -127,13 +172,14 @@ RSpec.describe 'Project > Members > Invite group', :js do
visit project_project_members_path(project)
click_on 'invite-group-tab'
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
click_button group.name
fill_in 'YYYY-MM-DD', with: 5.days.from_now.strftime('%Y-%m-%d')
click_button 'Invite'
select2 group.id, from: '#link_group_id'
fill_in 'expires_at_groups', with: 5.days.from_now.strftime('%Y-%m-%d')
click_on 'invite-group-tab'
find('.btn-confirm').click
page.refresh
end
it 'the group link shows the expiration time with a warning class' do
@ -149,29 +195,23 @@ RSpec.describe 'Project > Members > Invite group', :js do
context 'with multiple groups to choose from' do
let(:project) { create(:project) }
before do
it 'includes multiple groups' do
project.add_maintainer(maintainer)
sign_in(maintainer)
create(:group).add_owner(maintainer)
create(:group).add_owner(maintainer)
group1 = create(:group)
group1.add_owner(maintainer)
group2 = create(:group)
group2.add_owner(maintainer)
visit project_project_members_path(project)
click_link 'Invite group'
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
find('.ajax-groups-select.select2-container')
execute_script 'GROUP_SELECT_PER_PAGE = 1;'
open_select2 '#link_group_id'
end
it 'infinitely scrolls' do
expect(find('.select2-drop .select2-results')).to have_selector('.select2-result', count: 1)
scroll_select2_to_bottom('.select2-drop .select2-results:visible')
expect(find('.select2-drop .select2-results')).to have_selector('.select2-result', count: 2)
expect(page).to have_button(group1.name)
expect(page).to have_button(group2.name)
end
end
@ -188,16 +228,19 @@ RSpec.describe 'Project > Members > Invite group', :js do
group_to_share_with.add_maintainer(maintainer)
end
it 'the groups dropdown does not show ancestors' do
# This behavior should be changed to exclude the ancestor and project
# group from the options once issue is fixed for the modal:
# https://gitlab.com/gitlab-org/gitlab/-/issues/329835
it 'the groups dropdown does show ancestors and the project group' do
visit project_project_members_path(project)
click_on 'invite-group-tab'
click_link 'Search for a group'
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within '.select2-drop' do
expect(page).to have_content(group_to_share_with.name)
expect(page).not_to have_content(group.name)
end
expect(page).to have_button(group_to_share_with.name)
expect(page).to have_button(group.name)
expect(page).to have_button(nested_group.name)
end
end
end

View File

@ -0,0 +1,80 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'GFM autocomplete', :js do
let_it_be(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
let_it_be(:group) { create(:group, name: 'Ancestor') }
let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:issue) { create(:issue, project: project, assignees: [user], title: 'My special issue') }
let_it_be(:label) { create(:label, project: project, title: 'special+') }
let_it_be(:milestone) { create(:milestone, resource_parent: project, title: "project milestone") }
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
shared_examples 'displays autocomplete menu for all entities' do
it 'autocompletes all available entities' do
fill_in 'Description', with: User.reference_prefix
wait_for_requests
expect(find_autocomplete_menu).to be_visible
expect_autocomplete_entry(user.name)
fill_in 'Description', with: Label.reference_prefix
wait_for_requests
expect(find_autocomplete_menu).to be_visible
expect_autocomplete_entry(label.title)
fill_in 'Description', with: Milestone.reference_prefix
wait_for_requests
expect(find_autocomplete_menu).to be_visible
expect_autocomplete_entry(milestone.title)
fill_in 'Description', with: Issue.reference_prefix
wait_for_requests
expect(find_autocomplete_menu).to be_visible
expect_autocomplete_entry(issue.title)
fill_in 'Description', with: MergeRequest.reference_prefix
wait_for_requests
expect(find_autocomplete_menu).to be_visible
expect_autocomplete_entry(merge_request.title)
end
end
before_all do
group.add_maintainer(user)
end
describe 'new milestone page' do
before do
sign_in(user)
visit new_project_milestone_path(project)
wait_for_requests
end
it_behaves_like 'displays autocomplete menu for all entities'
end
describe 'update milestone page' do
before do
sign_in(user)
visit edit_project_milestone_path(project, milestone)
wait_for_requests
end
it_behaves_like 'displays autocomplete menu for all entities'
end
private
def find_autocomplete_menu
find('.atwho-view ul', visible: true)
end
def expect_autocomplete_entry(entry)
page.within('.atwho-container') do
expect(page).to have_content(entry)
end
end
end

Some files were not shown because too many files have changed in this diff Show More