diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js index a50bc4a9057..bc88dc2d092 100644 --- a/app/assets/javascripts/groups_select.js +++ b/app/assets/javascripts/groups_select.js @@ -59,11 +59,11 @@ } else { avatar = gon.default_avatar_url; } - return "
" + group.name + "
" + group.path + "
"; + return "
" + group.full_name + "
" + group.full_path + "
"; }; GroupsSelect.prototype.formatSelection = function(group) { - return group.name; + return group.full_name; }; return GroupsSelect; diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js index 489e567259c..b1c0dc37b4d 100644 --- a/app/assets/javascripts/search.js +++ b/app/assets/javascripts/search.js @@ -13,12 +13,12 @@ filterable: true, fieldName: 'group_id', search: { - fields: ['name'] + fields: ['full_name'] }, data: function(term, callback) { return Api.groups(term, {}, function(data) { data.unshift({ - name: 'Any' + full_name: 'Any' }); data.splice(1, 0, 'divider'); return callback(data); @@ -28,10 +28,10 @@ return obj.id; }, text: function(obj) { - return obj.name; + return obj.full_name; }, toggleLabel: function(obj) { - return ($groupDropdown.data('default-label')) + " " + obj.name; + return ($groupDropdown.data('default-label')) + " " + obj.full_name; }, clicked: (function(_this) { return function() { diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 6654f6997ce..37b69423c97 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -89,7 +89,7 @@ module SearchHelper { category: "Groups", id: group.id, - label: "#{search_result_sanitize(group.name)}", + label: "#{search_result_sanitize(group.full_name)}", url: group_path(group) } end diff --git a/app/models/project.rb b/app/models/project.rb index 4396ea9ef36..37f4705adbd 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -225,6 +225,7 @@ class Project < ActiveRecord::Base scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') } scope :with_statistics, -> { includes(:statistics) } scope :with_shared_runners, -> { where(shared_runners_enabled: true) } + scope :inside_path, ->(path) { joins(:route).where('routes.path LIKE ?', "#{path}/%") } # "enabled" here means "not disabled". It includes private features! scope :with_feature_enabled, ->(feature) { diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb index aa9837038a6..781cd13b44b 100644 --- a/app/services/search/global_service.rb +++ b/app/services/search/global_service.rb @@ -9,7 +9,10 @@ module Search def execute group = Group.find_by(id: params[:group_id]) if params[:group_id].present? projects = ProjectsFinder.new.execute(current_user) - projects = projects.in_namespace(group.id) if group + + if group + projects = projects.inside_path(group.full_path) + end Gitlab::SearchResults.new(current_user, projects, params[:search]) end diff --git a/changelogs/unreleased/dz-nested-groups-improvements-2.yml b/changelogs/unreleased/dz-nested-groups-improvements-2.yml new file mode 100644 index 00000000000..8e4eb7f1fff --- /dev/null +++ b/changelogs/unreleased/dz-nested-groups-improvements-2.yml @@ -0,0 +1,4 @@ +--- +title: Add read-only full_path and full_name attributes to Group API +merge_request: 8827 +author: diff --git a/doc/api/groups.md b/doc/api/groups.md index f7807390e68..3b38e3e1bee 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -25,7 +25,14 @@ GET /groups "id": 1, "name": "Foobar Group", "path": "foo-bar", - "description": "An interesting group" + "description": "An interesting group", + "visibility_level": 20, + "lfs_enabled": true, + "avatar_url": "http://localhost:3000/uploads/group/avatar/1/foo.jpg", + "web_url": "http://localhost:3000/groups/foo-bar", + "request_access_enabled": false, + "full_name": "Foobar Group", + "full_path": "foo-bar" } ] ``` @@ -149,6 +156,8 @@ Example response: "avatar_url": null, "web_url": "https://gitlab.example.com/groups/twitter", "request_access_enabled": false, + "full_name": "Foobar Group", + "full_path": "foo-bar", "projects": [ { "id": 7, @@ -372,6 +381,8 @@ Example response: "avatar_url": null, "web_url": "http://gitlab.example.com/groups/h5bp", "request_access_enabled": false, + "full_name": "Foobar Group", + "full_path": "foo-bar", "projects": [ { "id": 9, diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 9f59939e9ae..a07b2a9ca0f 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -137,6 +137,7 @@ module API expose :avatar_url expose :web_url expose :request_access_enabled + expose :full_name, :full_path expose :statistics, if: :statistics do with_options format_with: -> (value) { value.to_i } do diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index e51720f10ed..b7e547dc1f5 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -41,6 +41,11 @@ describe SearchHelper do expect(search_autocomplete_opts("gro").size).to eq(1) end + it "includes nested group" do + create(:group, :nested, name: 'foo').add_owner(user) + expect(search_autocomplete_opts('foo').size).to eq(1) + end + it "includes the user's projects" do project = create(:empty_project, namespace: create(:namespace, owner: user)) expect(search_autocomplete_opts(project.name).size).to eq(1) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 527eb704036..48b085781e7 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1833,6 +1833,14 @@ describe Project, models: true do end end + describe 'inside_path' do + let!(:project1) { create(:empty_project) } + let!(:project2) { create(:empty_project) } + let!(:path) { project1.namespace.path } + + it { expect(Project.inside_path(path)).to eq([project1]) } + end + def enable_lfs allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index edbf0140583..1187d2e609d 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -176,6 +176,9 @@ describe API::Groups, api: true do expect(json_response['visibility_level']).to eq(group1.visibility_level) expect(json_response['avatar_url']).to eq(group1.avatar_url) expect(json_response['web_url']).to eq(group1.web_url) + expect(json_response['request_access_enabled']).to eq(group1.request_access_enabled) + expect(json_response['full_name']).to eq(group1.full_name) + expect(json_response['full_path']).to eq(group1.full_path) expect(json_response['projects']).to be_an Array expect(json_response['projects'].length).to eq(2) expect(json_response['shared_projects']).to be_an Array diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index bd89c4a7c11..bed1031e40a 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -41,6 +41,25 @@ describe 'Search::GlobalService', services: true do results = context.execute expect(results.objects('projects')).to match_array [found_project] end + + context 'nested group' do + let!(:nested_group) { create(:group, :nested) } + let!(:project) { create(:project, namespace: nested_group) } + + before { project.add_master(user) } + + it 'returns result from nested group' do + context = Search::GlobalService.new(user, search: project.path) + results = context.execute + expect(results.objects('projects')).to match_array [project] + end + + it 'returns result from descendants when search inside group' do + context = Search::GlobalService.new(user, search: project.path, group_id: nested_group.parent) + results = context.execute + expect(results.objects('projects')).to match_array [project] + end + end end end end