Commit e66d6781 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 4e9f718e
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
- [ ] Follow the [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/) and [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide.html). - [ ] Follow the [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/) and [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide.html).
- [ ] If applicable, update the [permissions table](https://docs.gitlab.com/ee/user/permissions.html). - [ ] If applicable, update the [permissions table](https://docs.gitlab.com/ee/user/permissions.html).
- [ ] Link docs to and from the higher-level index page, plus other related docs where helpful. - [ ] Link docs to and from the higher-level index page, plus other related docs where helpful.
- [ ] Apply the ~Documentation label. - [ ] Apply the ~documentation label.
## Review checklist ## Review checklist
......
...@@ -4,96 +4,97 @@ import Api from './api'; ...@@ -4,96 +4,97 @@ import Api from './api';
import { normalizeHeaders } from './lib/utils/common_utils'; import { normalizeHeaders } from './lib/utils/common_utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
export default function groupsSelect() { const groupsSelect = () => {
import(/* webpackChunkName: 'select2' */ 'select2/select2') // Needs to be accessible in rspec
.then(() => { window.GROUP_SELECT_PER_PAGE = 20;
// Needs to be accessible in rspec $('.ajax-groups-select').each(function setAjaxGroupsSelect2() {
window.GROUP_SELECT_PER_PAGE = 20; const $select = $(this);
$('.ajax-groups-select').each(function setAjaxGroupsSelect2() { const allAvailable = $select.data('allAvailable');
const $select = $(this); const skipGroups = $select.data('skipGroups') || [];
const allAvailable = $select.data('allAvailable'); const parentGroupID = $select.data('parentId');
const skipGroups = $select.data('skipGroups') || []; const groupsPath = parentGroupID
const parentGroupID = $select.data('parentId'); ? Api.subgroupsPath.replace(':id', parentGroupID)
const groupsPath = parentGroupID : Api.groupsPath;
? Api.subgroupsPath.replace(':id', parentGroupID)
: Api.groupsPath;
$select.select2({ $select.select2({
placeholder: __('Search for a group'), placeholder: __('Search for a group'),
allowClear: $select.hasClass('allowClear'), allowClear: $select.hasClass('allowClear'),
multiple: $select.hasClass('multiselect'), multiple: $select.hasClass('multiselect'),
minimumInputLength: 0, minimumInputLength: 0,
ajax: { ajax: {
url: Api.buildUrl(groupsPath), url: Api.buildUrl(groupsPath),
dataType: 'json', dataType: 'json',
quietMillis: 250, quietMillis: 250,
transport(params) { transport(params) {
axios[params.type.toLowerCase()](params.url, { axios[params.type.toLowerCase()](params.url, {
params: params.data, params: params.data,
}) })
.then(res => { .then(res => {
const results = res.data || []; const results = res.data || [];
const headers = normalizeHeaders(res.headers); const headers = normalizeHeaders(res.headers);
const currentPage = parseInt(headers['X-PAGE'], 10) || 0; const currentPage = parseInt(headers['X-PAGE'], 10) || 0;
const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0; const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0;
const more = currentPage < totalPages; const more = currentPage < totalPages;
params.success({ params.success({
results, results,
pagination: { pagination: {
more, more,
}, },
}); });
}) })
.catch(params.error); .catch(params.error);
}, },
data(search, page) { data(search, page) {
return { return {
search, search,
page, page,
per_page: window.GROUP_SELECT_PER_PAGE, per_page: window.GROUP_SELECT_PER_PAGE,
all_available: allAvailable, all_available: allAvailable,
}; };
}, },
results(data, page) { results(data, page) {
if (data.length) return { results: [] }; if (data.length) return { results: [] };
const groups = data.length ? data : data.results || []; const groups = data.length ? data : data.results || [];
const more = data.pagination ? data.pagination.more : false; const more = data.pagination ? data.pagination.more : false;
const results = groups.filter(group => skipGroups.indexOf(group.id) === -1); const results = groups.filter(group => skipGroups.indexOf(group.id) === -1);
return { return {
results, results,
page, page,
more, more,
}; };
}, },
}, },
// eslint-disable-next-line consistent-return // eslint-disable-next-line consistent-return
initSelection(element, callback) { initSelection(element, callback) {
const id = $(element).val(); const id = $(element).val();
if (id !== '') { if (id !== '') {
return Api.group(id, callback); return Api.group(id, callback);
} }
}, },
formatResult(object) { formatResult(object) {
return `<div class='group-result'> <div class='group-name'>${object.full_name}</div> <div class='group-path'>${object.full_path}</div> </div>`; return `<div class='group-result'> <div class='group-name'>${object.full_name}</div> <div class='group-path'>${object.full_path}</div> </div>`;
}, },
formatSelection(object) { formatSelection(object) {
return object.full_name; return object.full_name;
}, },
dropdownCssClass: 'ajax-groups-dropdown select2-infinite', dropdownCssClass: 'ajax-groups-dropdown select2-infinite',
// we do not want to escape markup since we are displaying html in results // we do not want to escape markup since we are displaying html in results
escapeMarkup(m) { escapeMarkup(m) {
return m; return m;
}, },
}); });
$select.on('select2-loaded', () => { $select.on('select2-loaded', () => {
const dropdown = document.querySelector('.select2-infinite .select2-results'); const dropdown = document.querySelector('.select2-infinite .select2-results');
dropdown.style.height = `${Math.floor(dropdown.scrollHeight)}px`; dropdown.style.height = `${Math.floor(dropdown.scrollHeight)}px`;
}); });
}); });
}) };
export default () =>
import(/* webpackChunkName: 'select2' */ 'select2/select2')
.then(groupsSelect)
.catch(() => {}); .catch(() => {});
}
...@@ -120,7 +120,7 @@ export default class LabelsSelect { ...@@ -120,7 +120,7 @@ export default class LabelsSelect {
labelCount = 0; labelCount = 0;
if (data.labels.length && issueUpdateURL) { if (data.labels.length && issueUpdateURL) {
template = LabelsSelect.getLabelTemplate({ template = LabelsSelect.getLabelTemplate({
labels: data.labels, labels: _.sortBy(data.labels, 'title'),
issueUpdateURL, issueUpdateURL,
enableScopedLabels: scopedLabels, enableScopedLabels: scopedLabels,
scopedLabelsDocumentationLink, scopedLabelsDocumentationLink,
......
# frozen_string_literal: true # frozen_string_literal: true
class Import::BitbucketController < Import::BaseController class Import::BitbucketController < Import::BaseController
include ActionView::Helpers::SanitizeHelper
before_action :verify_bitbucket_import_enabled before_action :verify_bitbucket_import_enabled
before_action :bitbucket_auth, except: :callback before_action :bitbucket_auth, except: :callback
...@@ -21,7 +23,7 @@ class Import::BitbucketController < Import::BaseController ...@@ -21,7 +23,7 @@ class Import::BitbucketController < Import::BaseController
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def status def status
bitbucket_client = Bitbucket::Client.new(credentials) bitbucket_client = Bitbucket::Client.new(credentials)
repos = bitbucket_client.repos repos = bitbucket_client.repos(filter: sanitized_filter_param)
@repos, @incompatible_repos = repos.partition { |repo| repo.valid? } @repos, @incompatible_repos = repos.partition { |repo| repo.valid? }
...@@ -104,4 +106,8 @@ class Import::BitbucketController < Import::BaseController ...@@ -104,4 +106,8 @@ class Import::BitbucketController < Import::BaseController
refresh_token: session[:bitbucket_refresh_token] refresh_token: session[:bitbucket_refresh_token]
} }
end end
def sanitized_filter_param
@filter ||= sanitize(params[:filter])
end
end end
...@@ -9,6 +9,7 @@ module Groups ...@@ -9,6 +9,7 @@ module Groups
def execute def execute
remove_unallowed_params remove_unallowed_params
set_visibility_level
@group = Group.new(params) @group = Group.new(params)
...@@ -68,6 +69,12 @@ module Groups ...@@ -68,6 +69,12 @@ module Groups
true true
end end
def set_visibility_level
return if visibility_level.present?
params[:visibility_level] = Gitlab::CurrentSettings.current_application_settings.default_group_visibility
end
end end
end end
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
- if @repos.any? - if @repos.any?
%p.light %p.light
= _('Select projects you want to import.') = _('Select projects you want to import.')
%hr
%p %p
- if @incompatible_repos.any? - if @incompatible_repos.any?
= button_tag class: 'btn btn-import btn-success js-import-all' do = button_tag class: 'btn btn-import btn-success js-import-all' do
...@@ -19,6 +18,14 @@ ...@@ -19,6 +18,14 @@
= _('Import all projects') = _('Import all projects')
= icon('spinner spin', class: 'loading-icon') = icon('spinner spin', class: 'loading-icon')
.position-relative.ms-no-clear.d-flex.flex-fill.float-right.append-bottom-10
= form_tag status_import_bitbucket_path, method: 'get' do
= text_field_tag :filter, @filter, class: 'form-control pr-5', placeholder: _('Filter projects'), size: 40, autofocus: true, 'aria-label': _('Search')
.position-absolute.position-top-0.d-flex.align-items-center.text-muted.position-right-0.h-100
.border-left
%button{ class: 'btn btn-transparent btn-secondary', 'aria-label': _('Search Button'), type: 'submit' }
%i{ class: 'fa fa-search', 'aria-hidden': true }
.table-responsive .table-responsive
%table.table.import-jobs %table.table.import-jobs
%colgroup.import-jobs-from-col %colgroup.import-jobs-from-col
...@@ -59,7 +66,7 @@ ...@@ -59,7 +66,7 @@
- if current_user.can_select_namespace? - if current_user.can_select_namespace?
- selected = params[:namespace_id] || :current_user - selected = params[:namespace_id] || :current_user
- opts = current_user.can_create_group? ? { extra_group: Group.new(name: repo.owner, path: repo.owner) } : {} - opts = current_user.can_create_group? ? { extra_group: Group.new(name: repo.owner, path: repo.owner) } : {}
= select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'input-group-text select2 js-select-namespace', tabindex: 1 } = select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 }
- else - else
= text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true = text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true
%span.input-group-prepend %span.input-group-prepend
......
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
- if current_user.can_select_namespace? - if current_user.can_select_namespace?
- selected = params[:namespace_id] || :extra_group - selected = params[:namespace_id] || :extra_group
- opts = current_user.can_create_group? ? { extra_group: Group.new(name: sanitize_project_name(repo.project_key), path: sanitize_project_name(repo.project_key)) } : {} - opts = current_user.can_create_group? ? { extra_group: Group.new(name: sanitize_project_name(repo.project_key), path: sanitize_project_name(repo.project_key)) } : {}
= select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'input-group-text select2 js-select-namespace', tabindex: 1 } = select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 }
- else - else
= text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true = text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true
%span.input-group-prepend %span.input-group-prepend
......
---
title: Alphabetically sorts selected sidebar labels.
merge_request: 17309
author:
type: fixed
---
title: 'Allow to exclude ancestor groups on group labels API'
merge_request: 17221
author: Mathieu Parent
type: added
---
title: Add project filtering to Bitbucket Cloud import
merge_request: 16828
author:
type: added
---
title: Fix visibility level error when updating group from API
merge_request: 17227
author: Mathieu Parent
type: fixed
---
title: Remove Postgresql specific setup tasks and move to schema.rb
merge_request:
author:
type: other
...@@ -20,9 +20,7 @@ class ReworkRedirectRoutesIndexes < ActiveRecord::Migration[4.2] ...@@ -20,9 +20,7 @@ class ReworkRedirectRoutesIndexes < ActiveRecord::Migration[4.2]
def up def up
disable_statement_timeout do disable_statement_timeout do
# this is a plain btree on a single boolean column. It'll never be # this is a plain btree on a single boolean column. It'll never be
# selective enough to be valuable. This class is called by # selective enough to be valuable.
# setup_postgresql.rake so it needs to be able to handle this
# index not existing.
if index_exists?(:redirect_routes, :permanent) if index_exists?(:redirect_routes, :permanent)
remove_concurrent_index(:redirect_routes, :permanent) remove_concurrent_index(:redirect_routes, :permanent)
end end
......
...@@ -14,9 +14,7 @@ class AddPathIndexToRedirectRoutes < ActiveRecord::Migration[4.2] ...@@ -14,9 +14,7 @@ class AddPathIndexToRedirectRoutes < ActiveRecord::Migration[4.2]
# RedirectRoute.matching_path_and_descendants # RedirectRoute.matching_path_and_descendants
# #
# This same index is also added in the `ReworkRedirectRoutesIndexes` so this # This same index is also added in the `ReworkRedirectRoutesIndexes` so this
# is a no-op in most cases. But this migration is also called from the # is a no-op in most cases.
# `setup_postgresql.rake` task when setting up a new database, in which case
# we want to create the index.
def up def up
return unless Gitlab::Database.postgresql? return unless Gitlab::Database.postgresql?
...@@ -31,8 +29,5 @@ class AddPathIndexToRedirectRoutes < ActiveRecord::Migration[4.2] ...@@ -31,8 +29,5 @@ class AddPathIndexToRedirectRoutes < ActiveRecord::Migration[4.2]
# Do nothing in the DOWN. Since the index above is originally created in the # Do nothing in the DOWN. Since the index above is originally created in the
# `ReworkRedirectRoutesIndexes`. This migration wouldn't have actually # `ReworkRedirectRoutesIndexes`. This migration wouldn't have actually
# created any new index. # created any new index.
#
# This migration is only here to be called form `setup_postgresql.rake` so
# any newly created database would have this index.
end end
end end
...@@ -2917,6 +2917,7 @@ ActiveRecord::Schema.define(version: 2019_09_26_041216) do ...@@ -2917,6 +2917,7 @@ ActiveRecord::Schema.define(version: 2019_09_26_041216) do
t.boolean "emails_disabled" t.boolean "emails_disabled"
t.integer "max_pages_size" t.integer "max_pages_size"
t.integer "max_artifacts_size" t.integer "max_artifacts_size"
t.index "lower((name)::text)", name: "index_projects_on_lower_name"
t.index ["archived", "pending_delete", "merge_requests_require_code_owner_approval"], name: "projects_requiring_code_owner_approval", where: "((pending_delete = false) AND (archived = false) AND (merge_requests_require_code_owner_approval = true))" t.index ["archived", "pending_delete", "merge_requests_require_code_owner_approval"], name: "projects_requiring_code_owner_approval", where: "((pending_delete = false) AND (archived = false) AND (merge_requests_require_code_owner_approval = true))"
t.index ["created_at"], name: "index_projects_on_created_at" t.index ["created_at"], name: "index_projects_on_created_at"
t.index ["creator_id"], name: "index_projects_on_creator_id" t.index ["creator_id"], name: "index_projects_on_creator_id"
...@@ -3113,6 +3114,7 @@ ActiveRecord::Schema.define(version: 2019_09_26_041216) do ...@@ -3113,6 +3114,7 @@ ActiveRecord::Schema.define(version: 2019_09_26_041216) do
t.string "path", null: false t.string "path", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.index "lower((path)::text) varchar_pattern_ops", name: "index_redirect_routes_on_path_unique_text_pattern_ops", unique: true
t.index ["path"], name: "index_redirect_routes_on_path", unique: true t.index ["path"], name: "index_redirect_routes_on_path", unique: true
t.index ["source_type", "source_id"], name: "index_redirect_routes_on_source_type_and_source_id" t.index ["source_type", "source_id"], name: "index_redirect_routes_on_source_type_and_source_id"
end end
...@@ -3641,6 +3643,7 @@ ActiveRecord::Schema.define(version: 2019_09_26_041216) do ...@@ -3641,6 +3643,7 @@ ActiveRecord::Schema.define(version: 2019_09_26_041216) do
t.string "first_name", limit: 255 t.string "first_name", limit: 255
t.string "last_name", limit: 255 t.string "last_name", limit: 255
t.string "static_object_token", limit: 255 t.string "static_object_token", limit: 255
t.index "lower((name)::text)", name: "index_on_users_name_lower"
t.index ["accepted_term_id"], name: "index_users_on_accepted_term_id" t.index ["accepted_term_id"], name: "index_users_on_accepted_term_id"
t.index ["admin"], name: "index_users_on_admin" t.index ["admin"], name: "index_users_on_admin"
t.index ["bot_type"], name: "index_users_on_bot_type" t.index ["bot_type"], name: "index_users_on_bot_type"
......
...@@ -16,6 +16,7 @@ GET /groups/:id/labels ...@@ -16,6 +16,7 @@ GET /groups/:id/labels
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. | | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `with_counts` | boolean | no | Whether or not to include issue and merge request counts. Defaults to `false`. _([Introduced in GitLab 12.2](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31543))_ | | `with_counts` | boolean | no | Whether or not to include issue and merge request counts. Defaults to `false`. _([Introduced in GitLab 12.2](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31543))_ |
| `include_ancestor_groups` | boolean | no | Include ancestor groups. Defaults to `true`. |
```bash ```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels?with_counts=true curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels?with_counts=true
......
...@@ -12,6 +12,7 @@ GET /projects/:id/labels ...@@ -12,6 +12,7 @@ GET /projects/:id/labels
| --------- | ------- | -------- | --------------------- | | --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `with_counts` | boolean | no | Whether or not to include issue and merge request counts. Defaults to `false`. _([Introduced in GitLab 12.2](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31543))_ | | `with_counts` | boolean | no | Whether or not to include issue and merge request counts. Defaults to `false`. _([Introduced in GitLab 12.2](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31543))_ |
| `include_ancestor_groups` | boolean | no | Include ancestor groups. Defaults to `true`. |
```bash ```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/labels?with_counts=true curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/labels?with_counts=true
......
...@@ -56,10 +56,10 @@ namespace that started the import process. ...@@ -56,10 +56,10 @@ namespace that started the import process.
![Grant access](img/bitbucket_import_grant_access.png) ![Grant access](img/bitbucket_import_grant_access.png)
1. Click on the projects that you'd like to import or **Import all projects**. 1. Click on the projects that you'd like to import or **Import all projects**.
You can also select the namespace under which each project will be You can also filter projects by name and select the namespace under which
imported. each project will be imported.
![Import projects](img/bitbucket_import_select_project.png) ![Import projects](img/bitbucket_import_select_project_v12_3.png)
[bb-import]: ../../../integration/bitbucket.md [bb-import]: ../../../integration/bitbucket.md
[social sign-in]: ../../profile/account/social_sign_in.md [social sign-in]: ../../profile/account/social_sign_in.md
...@@ -18,10 +18,12 @@ module API ...@@ -18,10 +18,12 @@ module API
params do params do
optional :with_counts, type: Boolean, default: false, optional :with_counts, type: Boolean, default: false,
desc: 'Include issue and merge request counts' desc: 'Include issue and merge request counts'
optional :include_ancestor_groups, type: Boolean, default: true,
desc: 'Include ancestor groups'
use :pagination use :pagination
end end
get ':id/labels' do get ':id/labels' do
get_labels(user_group, Entities::GroupLabel) get_labels(user_group, Entities::GroupLabel, include_ancestor_groups: params[:include_ancestor_groups])
end end
desc 'Create a new label' do desc 'Create a new label' do
......
...@@ -10,8 +10,6 @@ module API ...@@ -10,8 +10,6 @@ module API
optional :description, type: String, desc: 'The description of the group' optional :description, type: String, desc: 'The description of the group'
optional :visibility, type: String, optional :visibility, type: String,
values: Gitlab::VisibilityLevel.string_values, values: Gitlab::VisibilityLevel.string_values,
default: Gitlab::VisibilityLevel.string_level(
Gitlab::CurrentSettings.current_application_settings.default_group_visibility),
desc: 'The visibility of the group' desc: 'The visibility of the group'
optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group' optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group'
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
......
...@@ -18,8 +18,8 @@ module API ...@@ -18,8 +18,8 @@ module API
label || not_found!('Label') label || not_found!('Label')
end end
def get_labels(parent, entity) def get_labels(parent, entity, include_ancestor_groups: true)
present paginate(available_labels_for(parent)), present paginate(available_labels_for(parent, include_ancestor_groups: include_ancestor_groups)),
with: entity, with: entity,
current_user: current_user, current_user: current_user,
parent: parent, parent: parent,
......
...@@ -17,10 +17,12 @@ module API ...@@ -17,10 +17,12 @@ module API
params do params do
optional :with_counts, type: Boolean, default: false, optional :with_counts, type: Boolean, default: false,
desc: 'Include issue and merge request counts' desc: 'Include issue and merge request counts'
optional :include_ancestor_groups, type: Boolean, default: true,
desc: 'Include ancestor groups'
use :pagination use :pagination
end end
get ':id/labels' do get ':id/labels' do
get_labels(user_project, Entities::ProjectLabel) get_labels(user_project, Entities::ProjectLabel, include_ancestor_groups: params[:include_ancestor_groups])
end end
desc 'Create a new label' do desc 'Create a new label' do
......
...@@ -38,8 +38,10 @@ module Bitbucket ...@@ -38,8 +38,10 @@ module Bitbucket
Representation::Repo.new(parsed_response) Representation::Repo.new(parsed_response)
end end
def repos def repos(filter: nil)
path = "/repositories?role=member" path = "/repositories?role=member"
path += "&q=name~\"#{filter}\"" if filter
get_collection(path, :repo) get_collection(path, :repo)
end end
......
...@@ -31,7 +31,6 @@ namespace :gitlab do ...@@ -31,7 +31,6 @@ namespace :gitlab do
terminate_all_connections unless Rails.env.production? terminate_all_connections unless Rails.env.production?
Rake::Task["db:reset"].invoke Rake::Task["db:reset"].invoke
Rake::Task["setup_postgresql"].invoke
Rake::Task["db:seed_fu"].invoke Rake::Task["db:seed_fu"].invoke
rescue Gitlab::TaskAbortedByUserError rescue Gitlab::TaskAbortedByUserError
puts "Quitting...".color(:red) puts "Quitting...".color(:red)
......
desc 'GitLab | Sets up PostgreSQL'
task setup_postgresql: :environment do
require Rails.root.join('db/migrate/20180215181245_users_name_lower_index.rb')
require Rails.root.join('db/migrate/20180504195842_project_name_lower_index.rb')
require Rails.root.join('db/post_migrate/20180306164012_add_path_index_to_redirect_routes.rb')
UsersNameLowerIndex.new.up
ProjectNameLowerIndex.new.up
AddPathIndexToRedirectRoutes.new.up
end
desc 'GitLab | Generate PostgreSQL Password Hash' desc 'GitLab | Generate PostgreSQL Password Hash'
task :postgresql_md5_hash do task :postgresql_md5_hash do
require 'digest' require 'digest'
......
...@@ -6795,6 +6795,9 @@ msgstr "" ...@@ -6795,6 +6795,9 @@ msgstr ""
msgid "Filter by two-factor authentication" msgid "Filter by two-factor authentication"
msgstr "" msgstr ""
msgid "Filter projects"
msgstr ""
msgid "Filter results by group" msgid "Filter results by group"
msgstr "" msgstr ""
...@@ -13602,6 +13605,9 @@ msgstr "" ...@@ -13602,6 +13605,9 @@ msgstr ""
msgid "Search" msgid "Search"
msgstr "" msgstr ""
msgid "Search Button"
msgstr ""
msgid "Search an environment spec" msgid "Search an environment spec"
msgstr "" msgstr ""
......
...@@ -26,8 +26,16 @@ module QA::Page ...@@ -26,8 +26,16 @@ module QA::Page
end end
# Reminder: You may wish to wait for a particular job status before checking output # Reminder: You may wish to wait for a particular job status before checking output
def output def output(wait: 5)
find_element(:build_trace).text result = ''
wait(reload: false, max: wait, interval: 1) do
result = find_element(:build_trace).text
!result.empty?
end
result
end end
private private
......
...@@ -80,6 +80,21 @@ describe Import::BitbucketController do ...@@ -80,6 +80,21 @@ describe Import::BitbucketController do
expect(assigns(:already_added_projects)).to eq([@project]) expect(assigns(:already_added_projects)).to eq([@project])
expect(assigns(:repos)).to eq([]) expect(assigns(:repos)).to eq([])
end end
context 'when filtering' do
let(:filter) { '<html>test</html>' }
let(:expected_filter) { 'test' }
subject { get :status, params: { filter: filter }, as: :json }
it 'passes sanitized filter param to bitbucket client' do
expect_next_instance_of(Bitbucket::Client) do |client|
expect(client).to receive(:repos).with(filter: expected_filter).and_return([@repo])
end
subject
end
end
end end
describe "POST create" do describe "POST create" do
......
...@@ -5,6 +5,7 @@ import MockAdapter from 'axios-mock-adapter'; ...@@ -5,6 +5,7 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import IssuableContext from '~/issuable_context'; import IssuableContext from '~/issuable_context';
import LabelsSelect from '~/labels_select'; import LabelsSelect from '~/labels_select';
import _ from 'underscore';
import '~/gl_dropdown'; import '~/gl_dropdown';
import 'select2'; import 'select2';
...@@ -15,6 +16,35 @@ import '~/users_select'; ...@@ -15,6 +16,35 @@ import '~/users_select';
let saveLabelCount = 0; let saveLabelCount = 0;
let mock; let mock;
function testLabelClicks(labelOrder, done) {
$('.edit-link')
.get(0)
.click();
setTimeout(() => {
const labelsInDropdown = $('.dropdown-content a');
expect(labelsInDropdown.length).toBe(10);
const arrayOfLabels = labelsInDropdown.get();
const randomArrayOfLabels = _.shuffle(arrayOfLabels);
randomArrayOfLabels.forEach((label, i) => {
if (i < saveLabelCount) {
$(label).click();
}
});
$('.edit-link')
.get(0)
.click();
setTimeout(() => {
expect($('.sidebar-collapsed-icon').attr('data-original-title')).toBe(labelOrder);
done();
}, 0);
}, 0);
}
describe('Issue dropdown sidebar', () => { describe('Issue dropdown sidebar', () => {
preloadFixtures('static/issue_sidebar_label.html'); preloadFixtures('static/issue_sidebar_label.html');
...@@ -29,7 +59,7 @@ describe('Issue dropdown sidebar', () => { ...@@ -29,7 +59,7 @@ describe('Issue dropdown sidebar', () => {
mock.onGet('/root/test/labels.json').reply(() => { mock.onGet('/root/test/labels.json').reply(() => {
const labels = Array(10) const labels = Array(10)
.fill() .fill()
.map((_, i) => ({ .map((_val, i) => ({
id: i, id: i,
title: `test ${i}`, title: `test ${i}`,
color: '#5CB85C', color: '#5CB85C',
...@@ -41,7 +71,7 @@ describe('Issue dropdown sidebar', () => { ...@@ -41,7 +71,7 @@ describe('Issue dropdown sidebar', () => {
mock.onPut('/root/test/issues/2.json').reply(() => { mock.onPut('/root/test/issues/2.json').reply(() => {
const labels = Array(saveLabelCount) const labels = Array(saveLabelCount)
.fill() .fill()
.map((_, i) => ({ .map((_val, i) => ({
id: i, id: i,
title: `test ${i}`, title: `test ${i}`,
color: '#5CB85C', color: '#5CB85C',
...@@ -57,61 +87,11 @@ describe('Issue dropdown sidebar', () => { ...@@ -57,61 +87,11 @@ describe('Issue dropdown sidebar', () => {
it('changes collapsed tooltip when changing labels when less than 5', done => { it('changes collapsed tooltip when changing labels when less than 5', done => {
saveLabelCount = 5; saveLabelCount = 5;
$('.edit-link') testLabelClicks('test 0, test 1, test 2, test 3, test 4', done);
.get(0)
.click();
setTimeout(() => {
expect($('.dropdown-content a').length).toBe(10);
$('.dropdown-content a').each(function(i) {
if (i < saveLabelCount) {
$(this)
.get(0)
.click();
}
});
$('.edit-link')
.get(0)
.click();
setTimeout(() => {
expect($('.sidebar-collapsed-icon').attr('data-original-title')).toBe(
'test 0, test 1, test 2, test 3, test 4',
);
done();
}, 0);
}, 0);
}); });
it('changes collapsed tooltip when changing labels when more than 5', done => { it('changes collapsed tooltip when changing labels when more than 5', done => {
saveLabelCount = 6; saveLabelCount = 6;
$('.edit-link') testLabelClicks('test 0, test 1, test 2, test 3, test 4, and 1 more', done);
.get(0)
.click();
setTimeout(() => {
expect($('.dropdown-content a').length).toBe(10);
$('.dropdown-content a').each(function(i) {
if (i < saveLabelCount) {
$(this)
.get(0)
.click();
}
});
$('.edit-link')
.get(0)
.click();
setTimeout(() => {
expect($('.sidebar-collapsed-icon').attr('data-original-title')).toBe(
'test 0, test 1, test 2, test 3, test 4, and 1 more',
);
done();
}, 0);
}, 0);
}); });
}); });
...@@ -5,9 +5,11 @@ require 'spec_helper' ...@@ -5,9 +5,11 @@ require 'spec_helper'
describe API::GroupLabels do describe API::GroupLabels do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group) { create(:group) } let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
let!(:group_member) { create(:group_member, group: group, user: user) } let!(:group_member) { create(:group_member, group: group, user: user) }
let!(:label1) { create(:group_label, title: 'feature', group: group) } let!(:group_label1) { create(:group_label, title: 'feature', group: group) }
let!(:label2) { create(:group_label, title: 'bug', group: group) } let!(:group_label2) { create(:group_label, title: 'bug', group: group) }
let!(:subgroup_label) { create(:group_label, title: 'support', group: subgroup) }
describe 'GET :id/labels' do describe 'GET :id/labels' do
it 'returns all available labels for the group' do it 'returns all available labels for the group' do
...@@ -35,6 +37,34 @@ describe API::GroupLabels do ...@@ -35,6 +37,34 @@ describe API::GroupLabels do
end end
end end
describe 'GET :subgroup_id/labels' do
context 'when the include_ancestor_groups parameter is not set' do
it 'returns all available labels for the group and ancestor groups' do
get api("/groups/#{subgroup.id}/labels", user)
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response).to all(match_schema('public_api/v4/labels/label'))
expect(json_response.size).to eq(3)
expect(json_response.map {|r| r['name'] }).to contain_exactly('feature', 'bug', 'support')
end
end
context 'when the include_ancestor_groups parameter is set to false' do
it 'returns all available labels for the group but not for ancestor groups' do
get api("/groups/#{subgroup.id}/labels", user), params: { include_ancestor_groups: false }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response).to all(match_schema('public_api/v4/labels/label'))
expect(json_response.size).to eq(1)
expect(json_response.map {|r| r['name'] }).to contain_exactly('support')
end
end
end
describe 'POST /groups/:id/labels' do describe 'POST /groups/:id/labels' do
it 'returns created label when all params are given' do it 'returns created label when all params are given' do
post api("/groups/#{group.id}/labels", user), post api("/groups/#{group.id}/labels", user),
...@@ -78,7 +108,7 @@ describe API::GroupLabels do ...@@ -78,7 +108,7 @@ describe API::GroupLabels do
it 'returns 409 if label already exists' do it 'returns 409 if label already exists' do
post api("/groups/#{group.id}/labels", user), post api("/groups/#{group.id}/labels", user),
params: { params: {
name: label1.name, name: group_label1.name,
color: '#FFAABB' color: '#FFAABB'
} }
...@@ -89,13 +119,13 @@ describe API::GroupLabels do ...@@ -89,13 +119,13 @@ describe API::GroupLabels do
describe 'DELETE /groups/:id/labels' do describe 'DELETE /groups/:id/labels' do
it 'returns 204 for existing label' do it 'returns 204 for existing label' do
delete api("/groups/#{group.id}/labels", user), params: { name: label1.name } delete api("/groups/#{group.id}/labels", user), params: { name: group_label1.name }
expect(response).to have_gitlab_http_status(204) expect(response).to have_gitlab_http_status(204)
end end
it 'returns 404 for non existing label' do it 'returns 404 for non existing label' do
delete api("/groups/#{group.id}/labels", user), params: { name: 'label2' } delete api("/groups/#{group.id}/labels", user), params: { name: 'not_exists' }
expect(response).to have_gitlab_http_status(404) expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Label Not Found') expect(json_response['message']).to eq('404 Label Not Found')
...@@ -115,12 +145,12 @@ describe API::GroupLabels do ...@@ -115,12 +145,12 @@ describe API::GroupLabels do
expect(response).to have_gitlab_http_status(204) expect(response).to have_gitlab_http_status(204)
expect(subgroup.labels.size).to eq(0) expect(subgroup.labels.size).to eq(0)
expect(group.labels).to include(label1) expect(group.labels).to include(group_label1)
end end
it_behaves_like '412 response' do it_behaves_like '412 response' do
let(:request) { api("/groups/#{group.id}/labels", user) } let(:request) { api("/groups/#{group.id}/labels", user) }
let(:params) { { name: label1.name } } let(:params) { { name: group_label1.name } }
end end
end end
...@@ -128,7 +158,7 @@ describe API::GroupLabels do ...@@ -128,7 +158,7 @@ describe API::GroupLabels do
it 'returns 200 if name and colors and description are changed' do it 'returns 200 if name and colors and description are changed' do
put api("/groups/#{group.id}/labels", user), put api("/groups/#{group.id}/labels", user),
params: { params: {
name: label1.name, name: group_label1.name,
new_name: 'New Label', new_name: 'New Label',
color: '#FFFFFF', color: '#FFFFFF',
description: 'test' description: 'test'
...@@ -152,13 +182,13 @@ describe API::GroupLabels do ...@@ -152,13 +182,13 @@ describe API::GroupLabels do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(subgroup.labels[0].name).to eq('New Label') expect(subgroup.labels[0].name).to eq('New Label')
expect(label1.name).to eq('feature') expect(group_label1.name).to eq('feature')
end end
it 'returns 404 if label does not exist' do it 'returns 404 if label does not exist' do
put api("/groups/#{group.id}/labels", user), put api("/groups/#{group.id}/labels", user),
params: { params: {
name: 'label2', name: 'not_exists',
new_name: 'label3' new_name: 'label3'
} }
...@@ -166,14 +196,14 @@ describe API::GroupLabels do ...@@ -166,14 +196,14 @@ describe API::GroupLabels do
end end
it 'returns 400 if no label name given' do it 'returns 400 if no label name given' do
put api("/groups/#{group.id}/labels", user), params: { new_name: label1.name } put api("/groups/#{group.id}/labels", user), params: { new_name: group_label1.name }
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('name is missing') expect(json_response['error']).to eq('name is missing')
end end
it 'returns 400 if no new parameters given' do it 'returns 400 if no new parameters given' do
put api("/groups/#{group.id}/labels", user), params: { name: label1.name } put api("/groups/#{group.id}/labels", user), params: { name: group_label1.name }
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('new_name, color, description are missing, '\ expect(json_response['error']).to eq('new_name, color, description are missing, '\
...@@ -184,31 +214,31 @@ describe API::GroupLabels do ...@@ -184,31 +214,31 @@ describe API::GroupLabels do
describe 'POST /groups/:id/labels/:label_id/subscribe' do describe 'POST /groups/:id/labels/:label_id/subscribe' do
context 'when label_id is a label title' do context 'when label_id is a label title' do
it 'subscribes to the label' do it 'subscribes to the label' do
post api("/groups/#{group.id}/labels/#{label1.title}/subscribe", user) post api("/groups/#{group.id}/labels/#{group_label1.title}/subscribe", user)
expect(response).to have_gitlab_http_status(201) expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(label1.title) expect(json_response['name']).to eq(group_label1.title)
expect(json_response['subscribed']).to be_truthy expect(json_response['subscribed']).to be_truthy
end end
end end
context 'when label_id is a label ID' do context 'when label_id is a label ID' do
it 'subscribes to the label' do it 'subscribes to the label' do
post api("/groups/#{group.id}/labels/#{label1.id}/subscribe", user) post api("/groups/#{group.id}/labels/#{group_label1.id}/subscribe", user)
expect(response).to have_gitlab_http_status(201) expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(label1.title) expect(json_response['name']).to eq(group_label1.title)
expect(json_response['subscribed']).to be_truthy expect(json_response['subscribed']).to be_truthy
end end
end end
context 'when user is already subscribed to label' do context 'when user is already subscribed to label' do
before do before do
label1.subscribe(user) group_label1.subscribe(user)
end end
it 'returns 304' do it 'returns 304' do
post api("/groups/#{group.id}/labels/#{label1.id}/subscribe", user) post api("/groups/#{group.id}/labels/#{group_label1.id}/subscribe", user)
expect(response).to have_gitlab_http_status(304) expect(response).to have_gitlab_http_status(304)
end end
...@@ -225,36 +255,36 @@ describe API::GroupLabels do ...@@ -225,36 +255,36 @@ describe API::GroupLabels do
describe 'POST /groups/:id/labels/:label_id/unsubscribe' do describe 'POST /groups/:id/labels/:label_id/unsubscribe' do
before do before do
label1.subscribe(user) group_label1.subscribe(user)
end end
context 'when label_id is a label title' do context 'when label_id is a label title' do
it 'unsubscribes from the label' do it 'unsubscribes from the label' do
post api("/groups/#{group.id}/labels/#{label1.title}/unsubscribe", user) post api("/groups/#{group.id}/labels/#{group_label1.title}/unsubscribe", user)
expect(response).to have_gitlab_http_status(201) expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(label1.title) expect(json_response['name']).to eq(group_label1.title)
expect(json_response['subscribed']).to be_falsey expect(json_response['subscribed']).to be_falsey
end end
end end
context 'when label_id is a label ID' do context 'when label_id is a label ID' do
it 'unsubscribes from the label' do it 'unsubscribes from the label' do
post api("/groups/#{group.id}/labels/#{label1.id}/unsubscribe", user) post api("/groups/#{group.id}/labels/#{group_label1.id}/unsubscribe", user)
expect(response).to have_gitlab_http_status(201) expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(label1.title) expect(json_response['name']).to eq(group_label1.title)
expect(json_response['subscribed']).to be_falsey expect(json_response['subscribed']).to be_falsey
end end
end end
context 'when user is already unsubscribed from label' do context 'when user is already unsubscribed from label' do
before do before do
label1.unsubscribe(user) group_label1.unsubscribe(user)
end end
it 'returns 304' do it 'returns 304' do
post api("/groups/#{group.id}/labels/#{label1.id}/unsubscribe", user) post api("/groups/#{group.id}/labels/#{group_label1.id}/unsubscribe", user)
expect(response).to have_gitlab_http_status(304) expect(response).to have_gitlab_http_status(304)
end end
......
...@@ -497,6 +497,29 @@ describe API::Groups do ...@@ -497,6 +497,29 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(404) expect(response).to have_gitlab_http_status(404)
end end
context 'within a subgroup' do
let(:group3) { create(:group, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
let!(:subgroup) { create(:group, parent: group3, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
before do
group3.add_owner(user3)
end
it 'does not change visibility when not requested' do
put api("/groups/#{group3.id}", user3), params: { description: 'Bug #23083' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['visibility']).to eq('public')
end
it 'prevents making private a group containing public subgroups' do
put api("/groups/#{group3.id}", user3), params: { visibility: 'private' }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['visibility_level']).to contain_exactly('private is not allowed since there are sub-groups with higher visibility.')
end
end
end end
context 'when authenticated as the admin' do context 'when authenticated as the admin' do
......
...@@ -256,6 +256,52 @@ describe API::Labels do ...@@ -256,6 +256,52 @@ describe API::Labels do
'is_project_label' => true) 'is_project_label' => true)
end end
end end
context 'when the include_ancestor_groups parameter is not set' do
let(:group) { create(:group) }
let!(:group_label) { create(:group_label, title: 'feature', group: group) }
let(:subgroup) { create(:group, parent: group) }
let!(:subgroup_label) { create(:group_label, title: 'support', group: subgroup) }
before do
subgroup.add_owner(user)
project.update!(group: subgroup)
end
it 'returns all available labels for the project, parent group and ancestor groups' do
get api("/projects/#{project.id}/labels", user)
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response).to all(match_schema('public_api/v4/labels/label'))
expect(json_response.size).to eq(4)
expect(json_response.map {|r| r['name'] }).to contain_exactly(group_label.name, subgroup_label.name, priority_label.name, label1.name)
end
end
context 'when the include_ancestor_groups parameter is set to false' do
let(:group) { create(:group) }
let!(:group_label) { create(:group_label, title: 'feature', group: group) }
let(:subgroup) { create(:group, parent: group) }
let!(:subgroup_label) { create(:group_label, title: 'support', group: subgroup) }
before do
subgroup.add_owner(user)
project.update!(group: subgroup)
end
it 'returns all available labels for the project and the parent group only' do
get api("/projects/#{project.id}/labels", user), params: { include_ancestor_groups: false }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response).to all(match_schema('public_api/v4/labels/label'))
expect(json_response.size).to eq(3)
expect(json_response.map {|r| r['name'] }).to contain_exactly(subgroup_label.name, priority_label.name, label1.name)
end
end
end end
describe 'POST /projects/:id/labels' do describe 'POST /projects/:id/labels' do
......
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