Commit a961e951 authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch 'feat/milestone-description-auto-complete' into 'master'

Milestone description: add autocomplete [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!59564
parents c4ef6fee 5eca31cb
...@@ -1998,6 +1998,8 @@ Gitlab/NamespacedClass: ...@@ -1998,6 +1998,8 @@ Gitlab/NamespacedClass:
- 'app/serializers/group_entity.rb' - 'app/serializers/group_entity.rb'
- 'app/serializers/group_group_link_entity.rb' - 'app/serializers/group_group_link_entity.rb'
- 'app/serializers/group_group_link_serializer.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/group_serializer.rb'
- 'app/serializers/issuable_entity.rb' - 'app/serializers/issuable_entity.rb'
- 'app/serializers/issuable_sidebar_basic_entity.rb' - 'app/serializers/issuable_sidebar_basic_entity.rb'
...@@ -2500,8 +2502,6 @@ Gitlab/NamespacedClass: ...@@ -2500,8 +2502,6 @@ Gitlab/NamespacedClass:
- 'ee/app/serializers/geo_project_registry_entity.rb' - 'ee/app/serializers/geo_project_registry_entity.rb'
- 'ee/app/serializers/geo_project_registry_serializer.rb' - 'ee/app/serializers/geo_project_registry_serializer.rb'
- 'ee/app/serializers/group_analytics_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_entity.rb'
- 'ee/app/serializers/group_vulnerability_autocomplete_serializer.rb' - 'ee/app/serializers/group_vulnerability_autocomplete_serializer.rb'
- 'ee/app/serializers/invited_group_entity.rb' - 'ee/app/serializers/invited_group_entity.rb'
......
import initForm from '../../../../shared/milestones/form'; import initForm from '../../../../shared/milestones/form';
initForm(false); initForm();
import initForm from '../../../../shared/milestones/form'; import initForm from '../../../../shared/milestones/form';
initForm(false); initForm();
# frozen_string_literal: true # frozen_string_literal: true
class Groups::AutocompleteSourcesController < Groups::ApplicationController class Groups::AutocompleteSourcesController < Groups::ApplicationController
before_action :load_autocomplete_service, except: [:members]
feature_category :subgroups, [:members] feature_category :subgroups, [:members]
feature_category :issue_tracking, [:issues, :labels, :milestones, :commands] feature_category :issue_tracking, [:issues, :labels, :milestones, :commands]
feature_category :code_review, [:merge_requests] feature_category :code_review, [:merge_requests]
feature_category :epics, [:epics]
feature_category :vulnerability_management, [:vulnerabilities]
def members def members
render json: ::Groups::ParticipantsService.new(@group, current_user).execute(target) render json: ::Groups::ParticipantsService.new(@group, current_user).execute(target)
...@@ -15,52 +11,37 @@ class Groups::AutocompleteSourcesController < Groups::ApplicationController ...@@ -15,52 +11,37 @@ class Groups::AutocompleteSourcesController < Groups::ApplicationController
def issues def issues
render json: issuable_serializer.represent( render json: issuable_serializer.represent(
@autocomplete_service.issues(confidential_only: params[:confidential_only], issue_types: params[:issue_types]), autocomplete_service.issues(confidential_only: params[:confidential_only], issue_types: params[:issue_types]),
parent_group: @group parent_group: @group
) )
end end
def merge_requests def merge_requests
render json: issuable_serializer.represent(@autocomplete_service.merge_requests, parent_group: @group) render json: issuable_serializer.represent(autocomplete_service.merge_requests, parent_group: @group)
end end
def labels def labels
render json: @autocomplete_service.labels_as_hash(target) render json: autocomplete_service.labels_as_hash(target)
end
def epics
render json: issuable_serializer.represent(
@autocomplete_service.epics(confidential_only: params[:confidential_only]),
parent_group: @group
)
end
def vulnerabilities
render json: vulnerability_serializer.represent(@autocomplete_service.vulnerabilities, parent_group: @group)
end end
def commands def commands
render json: @autocomplete_service.commands(target) render json: autocomplete_service.commands(target)
end end
def milestones def milestones
render json: @autocomplete_service.milestones render json: autocomplete_service.milestones
end end
private private
def load_autocomplete_service def autocomplete_service
@autocomplete_service = ::Groups::AutocompleteService.new(@group, current_user, params) @autocomplete_service ||= ::Groups::AutocompleteService.new(@group, current_user, params)
end end
def issuable_serializer def issuable_serializer
GroupIssuableAutocompleteSerializer.new GroupIssuableAutocompleteSerializer.new
end end
def vulnerability_serializer
GroupVulnerabilityAutocompleteSerializer.new
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def target def target
QuickActions::TargetService QuickActions::TargetService
...@@ -69,3 +50,5 @@ class Groups::AutocompleteSourcesController < Groups::ApplicationController ...@@ -69,3 +50,5 @@ class Groups::AutocompleteSourcesController < Groups::ApplicationController
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
Groups::AutocompleteSourcesController.prepend_if_ee('EE::Groups::AutocompleteSourcesController')
...@@ -21,7 +21,7 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -21,7 +21,7 @@ class Groups::MilestonesController < Groups::ApplicationController
end end
def new def new
@milestone = Milestone.new @noteable = @milestone = Milestone.new
end end
def create def create
...@@ -70,7 +70,7 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -70,7 +70,7 @@ class Groups::MilestonesController < Groups::ApplicationController
end end
def milestone def milestone
@milestone = group.milestones.find_by_iid(params[:id]) @noteable = @milestone ||= group.milestones.find_by_iid(params[:id])
render_404 unless @milestone render_404 unless @milestone
end end
......
...@@ -39,7 +39,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -39,7 +39,7 @@ class Projects::MilestonesController < Projects::ApplicationController
end end
def new def new
@milestone = @project.milestones.new @noteable = @milestone = @project.milestones.new
respond_with(@milestone) respond_with(@milestone)
end end
...@@ -125,7 +125,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -125,7 +125,7 @@ class Projects::MilestonesController < Projects::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def milestone def milestone
@milestone ||= @project.milestones.find_by!(iid: params[:id]) @noteable = @milestone ||= @project.milestones.find_by!(iid: params[:id])
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -382,15 +382,26 @@ module ApplicationHelper ...@@ -382,15 +382,26 @@ module ApplicationHelper
def autocomplete_data_sources(object, noteable_type) def autocomplete_data_sources(object, noteable_type)
return {} unless object && noteable_type return {} unless object && noteable_type
{ if object.is_a?(Group)
members: members_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]), {
issues: issues_project_autocomplete_sources_path(object), members: members_group_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
mergeRequests: merge_requests_project_autocomplete_sources_path(object), issues: issues_group_autocomplete_sources_path(object),
labels: labels_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]), mergeRequests: merge_requests_group_autocomplete_sources_path(object),
milestones: milestones_project_autocomplete_sources_path(object), labels: labels_group_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
commands: commands_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]), milestones: milestones_group_autocomplete_sources_path(object),
snippets: snippets_project_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 end
def asset_to_string(name) def asset_to_string(name)
......
...@@ -26,24 +26,6 @@ module Groups ...@@ -26,24 +26,6 @@ module Groups
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def epics(confidential_only: false)
finder_params = { group_id: group.id }
finder_params[:confidential] = true if confidential_only.present?
# TODO: use include_descendant_groups: true optional parameter once frontend supports epics from external groups.
# See https://gitlab.com/gitlab-org/gitlab/issues/6837
EpicsFinder.new(current_user, finder_params)
.execute
.select(:iid, :title, :group_id)
end
def vulnerabilities
::Autocomplete::VulnerabilitiesAutocompleteFinder
.new(current_user, group, params)
.execute
.select([:id, :title, :project_id])
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def milestones def milestones
group_ids = group.self_and_ancestors.public_or_visible_to_user(current_user).pluck(:id) group_ids = group.self_and_ancestors.public_or_visible_to_user(current_user).pluck(:id)
...@@ -63,3 +45,5 @@ module Groups ...@@ -63,3 +45,5 @@ module Groups
end end
end end
end end
Groups::AutocompleteService.prepend_if_ee('EE::Groups::AutocompleteService')
...@@ -12,7 +12,11 @@ ...@@ -12,7 +12,11 @@
= f.label :description, _("Description") = f.label :description, _("Description")
.col-sm-10 .col-sm-10
= render layout: 'shared/md_preview', locals: { url: group_preview_markdown_path } do = 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 .clearfix
.error-alert .error-alert
= render "shared/milestones/form_dates", f: f = render "shared/milestones/form_dates", f: f
......
...@@ -13,7 +13,11 @@ ...@@ -13,7 +13,11 @@
= f.label :description, _('Description') = f.label :description, _('Description')
.col-sm-10 .col-sm-10
= render layout: 'shared/md_preview', locals: { url: preview_markdown_path(@project) } do = 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' = render 'shared/notes/hints'
.clearfix .clearfix
.error-alert .error-alert
......
---
title: Add autocomplete to milestone description
merge_request: 59564
author: Jonas Wälter @wwwjon
type: added
...@@ -114,6 +114,17 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -114,6 +114,17 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resources :container_registries, only: [:index, :show], controller: 'registry/repositories' resources :container_registries, only: [:index, :show], controller: 'registry/repositories'
resource :dependency_proxy, only: [:show, :update] resource :dependency_proxy, only: [:show, :update]
resources :email_campaigns, only: :index 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 end
scope(path: '*id', scope(path: '*id',
......
# frozen_string_literal: true
module EE
module Groups
module AutocompleteSourcesController
extend ActiveSupport::Concern
prepended do
feature_category :epics, [:epics]
feature_category :vulnerability_management, [:vulnerabilities]
end
def epics
render json: issuable_serializer.represent(
autocomplete_service.epics(confidential_only: params[:confidential_only]),
parent_group: group
)
end
def vulnerabilities
render json: vulnerability_serializer.represent(autocomplete_service.vulnerabilities, parent_group: group)
end
private
def vulnerability_serializer
GroupVulnerabilityAutocompleteSerializer.new
end
end
end
end
...@@ -98,15 +98,9 @@ module EE ...@@ -98,15 +98,9 @@ module EE
if object.is_a?(Group) if object.is_a?(Group)
{ {
members: members_group_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
labels: labels_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),
epics: epics_group_autocomplete_sources_path(object), epics: epics_group_autocomplete_sources_path(object),
vulnerabilities: enabled_for_vulnerabilities ? vulnerabilities_group_autocomplete_sources_path(object) : nil, vulnerabilities: enabled_for_vulnerabilities ? vulnerabilities_group_autocomplete_sources_path(object) : nil
commands: commands_group_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]), }.compact.merge(super)
milestones: milestones_group_autocomplete_sources_path(object)
}.compact
else else
{ {
epics: object.group&.feature_available?(:epics) ? epics_project_autocomplete_sources_path(object) : nil, epics: object.group&.feature_available?(:epics) ? epics_project_autocomplete_sources_path(object) : nil,
......
# frozen_string_literal: true
module EE
module Groups
module AutocompleteService
def epics(confidential_only: false)
finder_params = { group_id: group.id }
finder_params[:confidential] = true if confidential_only.present?
# TODO: use include_descendant_groups: true optional parameter once frontend supports epics from external groups.
# See https://gitlab.com/gitlab-org/gitlab/issues/6837
EpicsFinder.new(current_user, finder_params)
.execute
.select(:iid, :title, :group_id)
end
def vulnerabilities
::Autocomplete::VulnerabilitiesAutocompleteFinder
.new(current_user, group, params)
.execute
.select([:id, :title, :project_id])
end
end
end
end
...@@ -90,14 +90,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -90,14 +90,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resources :autocomplete_sources, only: [] do resources :autocomplete_sources, only: [] do
collection do collection do
get 'members'
get 'issues'
get 'merge_requests'
get 'labels'
get 'epics' get 'epics'
get 'vulnerabilities' get 'vulnerabilities'
get 'commands'
get 'milestones'
end end
end end
......
...@@ -16,6 +16,35 @@ RSpec.describe Groups::AutocompleteSourcesController do ...@@ -16,6 +16,35 @@ RSpec.describe Groups::AutocompleteSourcesController do
sign_in(user) sign_in(user)
end end
describe '#issues' do
using RSpec::Parameterized::TableSyntax
let_it_be(:project) { create(:project, group: group) }
let_it_be(:test_case) { create(:quality_test_case, project: project) }
where(:issue_types, :expected) do
nil | :test_case
'' | :test_case
'invalid' | []
'test_case' | :test_case
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 '#epics' do describe '#epics' do
it 'returns 200 status' do it 'returns 200 status' do
get :epics, params: { group_id: group } get :epics, params: { group_id: group }
...@@ -58,61 +87,6 @@ RSpec.describe Groups::AutocompleteSourcesController do ...@@ -58,61 +87,6 @@ RSpec.describe Groups::AutocompleteSourcesController do
end end
end 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_it_be(:test_case) { create(:quality_test_case, project: project) }
let(:none) { [] }
let(:all) { [issue, incident, test_case] }
where(:issue_types, :expected) do
nil | :all
'' | :all
'invalid' | :none
'issue' | :issue
'incident' | :incident
'test_case' | :test_case
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
describe '#commands' do describe '#commands' do
it 'returns 200 status' do it 'returns 200 status' do
get :commands, params: { group_id: group, type: 'Epic', type_id: epic.iid } get :commands, params: { group_id: group, type: 'Epic', type_id: epic.iid }
......
...@@ -26,14 +26,6 @@ RSpec.describe Groups::AutocompleteService do ...@@ -26,14 +26,6 @@ RSpec.describe Groups::AutocompleteService do
let!(:sub_group_label) { create(:group_label, group: sub_group) } let!(:sub_group_label) { create(:group_label, group: sub_group) }
let!(:parent_group_label) { create(:group_label, group: group.parent) } let!(:parent_group_label) { create(:group_label, group: group.parent) }
it 'returns labels from own group and ancestor groups' do
results = subject.labels_as_hash(nil)
expected_labels = [label1, label2, parent_group_label]
expect_labels_to_equal(results, expected_labels)
end
context 'some labels are already assigned' do context 'some labels are already assigned' do
before do before do
epic.labels << label1 epic.labels << label1
...@@ -57,41 +49,6 @@ RSpec.describe Groups::AutocompleteService do ...@@ -57,41 +49,6 @@ RSpec.describe Groups::AutocompleteService do
end end
end end
describe '#issues' do
let(:project) { create(:project, group: group) }
let(:sub_group_project) { create(:project, group: sub_group) }
let!(:project_issue) { create(:issue, project: project) }
let!(:sub_group_project_issue) { create(:issue, confidential: true, project: sub_group_project) }
it 'returns issues in group and subgroups' do
issues = subject.issues
expect(issues.map(&:iid)).to contain_exactly(project_issue.iid, sub_group_project_issue.iid)
expect(issues.map(&:title)).to contain_exactly(project_issue.title, sub_group_project_issue.title)
end
it 'returns only confidential issues if confidential_only is true' do
issues = subject.issues(confidential_only: true)
expect(issues.map(&:iid)).to contain_exactly(sub_group_project_issue.iid)
expect(issues.map(&:title)).to contain_exactly(sub_group_project_issue.title)
end
end
describe '#merge_requests' do
let(:project) { create(:project, :repository, group: group) }
let(:sub_group_project) { create(:project, :repository, group: sub_group) }
let!(:project_mr) { create(:merge_request, source_project: project) }
let!(:sub_group_project_mr) { create(:merge_request, source_project: sub_group_project) }
it 'returns merge requests in group and subgroups' do
expect(subject.merge_requests.map(&:iid)).to contain_exactly(project_mr.iid, sub_group_project_mr.iid)
expect(subject.merge_requests.map(&:title)).to contain_exactly(project_mr.title, sub_group_project_mr.title)
end
end
describe '#epics' do describe '#epics' do
let(:expected_attributes) { %i(iid title group_id) } let(:expected_attributes) { %i(iid title group_id) }
...@@ -202,52 +159,4 @@ RSpec.describe Groups::AutocompleteService do ...@@ -202,52 +159,4 @@ RSpec.describe Groups::AutocompleteService do
end end
end end
end end
describe '#milestones' do
let!(:group_milestone) { create(:milestone, group: group) }
let!(:subgroup_milestone) { create(:milestone, group: sub_group) }
before do
sub_group.add_maintainer(user)
end
context 'when group is public' do
let(:public_group) { create(:group, :public) }
let(:public_subgroup) { create(:group, :public, parent: public_group) }
before do
group_milestone.update(group: public_group)
subgroup_milestone.update(group: public_subgroup)
end
it 'returns milestones from groups and subgroups' do
subject = described_class.new(public_subgroup, user)
expect(subject.milestones.map(&:iid)).to contain_exactly(group_milestone.iid, subgroup_milestone.iid)
expect(subject.milestones.map(&:title)).to contain_exactly(group_milestone.title, subgroup_milestone.title)
end
end
it 'returns milestones from group' do
expect(subject.milestones.map(&:iid)).to contain_exactly(group_milestone.iid)
expect(subject.milestones.map(&:title)).to contain_exactly(group_milestone.title)
end
it 'returns milestones from groups and subgroups' do
milestones = described_class.new(sub_group, user).milestones
expect(milestones.map(&:iid)).to contain_exactly(group_milestone.iid, subgroup_milestone.iid)
expect(milestones.map(&:title)).to contain_exactly(group_milestone.title, subgroup_milestone.title)
end
it 'returns only milestones that user can read' do
user = create(:user)
sub_group.add_guest(user)
milestones = described_class.new(sub_group, user).milestones
expect(milestones.map(&:iid)).to contain_exactly(subgroup_milestone.iid)
expect(milestones.map(&:title)).to contain_exactly(subgroup_milestone.title)
end
end
end end
# 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
...@@ -54,11 +54,11 @@ RSpec.describe 'Group milestones' do ...@@ -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')) expect(find('.start_date')).to have_content(Date.today.at_beginning_of_month.strftime('%b %-d, %Y'))
end end
it 'description input does not support autocomplete' do it 'description input support autocomplete' do
description = find('.note-textarea') description = find('.note-textarea')
description.native.send_keys('!') description.native.send_keys('!')
expect(page).not_to have_selector('.atwho-view') expect(page).to have_selector('.atwho-view')
end end
end end
......
# 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
# 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
...@@ -284,14 +284,29 @@ RSpec.describe ApplicationHelper do ...@@ -284,14 +284,29 @@ RSpec.describe ApplicationHelper do
end end
describe '#autocomplete_data_sources' do describe '#autocomplete_data_sources' do
let(:project) { create(:project) } context 'group' do
let(:noteable_type) { Issue } let(:group) { create(:group) }
let(:noteable_type) { Issue }
it 'returns paths for autocomplete_sources_controller' do
sources = helper.autocomplete_data_sources(group, noteable_type)
expect(sources.keys).to include(:members, :issues, :mergeRequests, :labels, :milestones, :commands)
sources.keys.each do |key|
expect(sources[key]).not_to be_nil
end
end
end
context 'project' do
let(:project) { create(:project) }
let(:noteable_type) { Issue }
it 'returns paths for autocomplete_sources_controller' do it 'returns paths for autocomplete_sources_controller' do
sources = helper.autocomplete_data_sources(project, noteable_type) sources = helper.autocomplete_data_sources(project, noteable_type)
expect(sources.keys).to match_array([:members, :issues, :mergeRequests, :labels, :milestones, :commands, :snippets]) expect(sources.keys).to match_array([:members, :issues, :mergeRequests, :labels, :milestones, :commands, :snippets])
sources.keys.each do |key| sources.keys.each do |key|
expect(sources[key]).not_to be_nil expect(sources[key]).not_to be_nil
end
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Groups::AutocompleteService do
let_it_be(:group, refind: true) { create(:group, :nested, :private, avatar: fixture_file_upload('spec/fixtures/dk.png')) }
let_it_be(:sub_group) { create(:group, :private, parent: group) }
let(:user) { create(:user) }
subject { described_class.new(group, user) }
before do
group.add_developer(user)
end
def expect_labels_to_equal(labels, expected_labels)
extract_title = lambda { |label| label['title'] }
expect(labels.map(&extract_title)).to match_array(expected_labels.map(&extract_title))
end
describe '#labels_as_hash' do
let!(:label1) { create(:group_label, group: group) }
let!(:label2) { create(:group_label, group: group) }
let!(:sub_group_label) { create(:group_label, group: sub_group) }
let!(:parent_group_label) { create(:group_label, group: group.parent) }
it 'returns labels from own group and ancestor groups' do
results = subject.labels_as_hash(nil)
expected_labels = [label1, label2, parent_group_label]
expect_labels_to_equal(results, expected_labels)
end
end
describe '#issues' do
let(:project) { create(:project, group: group) }
let(:sub_group_project) { create(:project, group: sub_group) }
let!(:project_issue) { create(:issue, project: project) }
let!(:sub_group_project_issue) { create(:issue, confidential: true, project: sub_group_project) }
it 'returns issues in group and subgroups' do
issues = subject.issues
expect(issues.map(&:iid)).to contain_exactly(project_issue.iid, sub_group_project_issue.iid)
expect(issues.map(&:title)).to contain_exactly(project_issue.title, sub_group_project_issue.title)
end
it 'returns only confidential issues if confidential_only is true' do
issues = subject.issues(confidential_only: true)
expect(issues.map(&:iid)).to contain_exactly(sub_group_project_issue.iid)
expect(issues.map(&:title)).to contain_exactly(sub_group_project_issue.title)
end
end
describe '#merge_requests' do
let(:project) { create(:project, :repository, group: group) }
let(:sub_group_project) { create(:project, :repository, group: sub_group) }
let!(:project_mr) { create(:merge_request, source_project: project) }
let!(:sub_group_project_mr) { create(:merge_request, source_project: sub_group_project) }
it 'returns merge requests in group and subgroups' do
expect(subject.merge_requests.map(&:iid)).to contain_exactly(project_mr.iid, sub_group_project_mr.iid)
expect(subject.merge_requests.map(&:title)).to contain_exactly(project_mr.title, sub_group_project_mr.title)
end
end
describe '#milestones' do
let!(:group_milestone) { create(:milestone, group: group) }
let!(:subgroup_milestone) { create(:milestone, group: sub_group) }
before do
sub_group.add_maintainer(user)
end
context 'when group is public' do
let(:public_group) { create(:group, :public) }
let(:public_subgroup) { create(:group, :public, parent: public_group) }
before do
group_milestone.update!(group: public_group)
subgroup_milestone.update!(group: public_subgroup)
end
it 'returns milestones from groups and subgroups' do
subject = described_class.new(public_subgroup, user)
expect(subject.milestones.map(&:iid)).to contain_exactly(group_milestone.iid, subgroup_milestone.iid)
expect(subject.milestones.map(&:title)).to contain_exactly(group_milestone.title, subgroup_milestone.title)
end
end
it 'returns milestones from group' do
expect(subject.milestones.map(&:iid)).to contain_exactly(group_milestone.iid)
expect(subject.milestones.map(&:title)).to contain_exactly(group_milestone.title)
end
it 'returns milestones from groups and subgroups' do
milestones = described_class.new(sub_group, user).milestones
expect(milestones.map(&:iid)).to contain_exactly(group_milestone.iid, subgroup_milestone.iid)
expect(milestones.map(&:title)).to contain_exactly(group_milestone.title, subgroup_milestone.title)
end
it 'returns only milestones that user can read' do
user = create(:user)
sub_group.add_guest(user)
milestones = described_class.new(sub_group, user).milestones
expect(milestones.map(&:iid)).to contain_exactly(subgroup_milestone.iid)
expect(milestones.map(&:title)).to contain_exactly(subgroup_milestone.title)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment