Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4e9f718e19
commit
e66d6781ef
|
@ -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).
|
||||
- [ ] 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.
|
||||
- [ ] Apply the ~Documentation label.
|
||||
- [ ] Apply the ~documentation label.
|
||||
|
||||
## Review checklist
|
||||
|
||||
|
|
|
@ -4,96 +4,97 @@ import Api from './api';
|
|||
import { normalizeHeaders } from './lib/utils/common_utils';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default function groupsSelect() {
|
||||
import(/* webpackChunkName: 'select2' */ 'select2/select2')
|
||||
.then(() => {
|
||||
// Needs to be accessible in rspec
|
||||
window.GROUP_SELECT_PER_PAGE = 20;
|
||||
$('.ajax-groups-select').each(function setAjaxGroupsSelect2() {
|
||||
const $select = $(this);
|
||||
const allAvailable = $select.data('allAvailable');
|
||||
const skipGroups = $select.data('skipGroups') || [];
|
||||
const parentGroupID = $select.data('parentId');
|
||||
const groupsPath = parentGroupID
|
||||
? Api.subgroupsPath.replace(':id', parentGroupID)
|
||||
: Api.groupsPath;
|
||||
const groupsSelect = () => {
|
||||
// Needs to be accessible in rspec
|
||||
window.GROUP_SELECT_PER_PAGE = 20;
|
||||
$('.ajax-groups-select').each(function setAjaxGroupsSelect2() {
|
||||
const $select = $(this);
|
||||
const allAvailable = $select.data('allAvailable');
|
||||
const skipGroups = $select.data('skipGroups') || [];
|
||||
const parentGroupID = $select.data('parentId');
|
||||
const groupsPath = parentGroupID
|
||||
? Api.subgroupsPath.replace(':id', parentGroupID)
|
||||
: Api.groupsPath;
|
||||
|
||||
$select.select2({
|
||||
placeholder: __('Search for a group'),
|
||||
allowClear: $select.hasClass('allowClear'),
|
||||
multiple: $select.hasClass('multiselect'),
|
||||
minimumInputLength: 0,
|
||||
ajax: {
|
||||
url: Api.buildUrl(groupsPath),
|
||||
dataType: 'json',
|
||||
quietMillis: 250,
|
||||
transport(params) {
|
||||
axios[params.type.toLowerCase()](params.url, {
|
||||
params: params.data,
|
||||
})
|
||||
.then(res => {
|
||||
const results = res.data || [];
|
||||
const headers = normalizeHeaders(res.headers);
|
||||
const currentPage = parseInt(headers['X-PAGE'], 10) || 0;
|
||||
const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0;
|
||||
const more = currentPage < totalPages;
|
||||
$select.select2({
|
||||
placeholder: __('Search for a group'),
|
||||
allowClear: $select.hasClass('allowClear'),
|
||||
multiple: $select.hasClass('multiselect'),
|
||||
minimumInputLength: 0,
|
||||
ajax: {
|
||||
url: Api.buildUrl(groupsPath),
|
||||
dataType: 'json',
|
||||
quietMillis: 250,
|
||||
transport(params) {
|
||||
axios[params.type.toLowerCase()](params.url, {
|
||||
params: params.data,
|
||||
})
|
||||
.then(res => {
|
||||
const results = res.data || [];
|
||||
const headers = normalizeHeaders(res.headers);
|
||||
const currentPage = parseInt(headers['X-PAGE'], 10) || 0;
|
||||
const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0;
|
||||
const more = currentPage < totalPages;
|
||||
|
||||
params.success({
|
||||
results,
|
||||
pagination: {
|
||||
more,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(params.error);
|
||||
},
|
||||
data(search, page) {
|
||||
return {
|
||||
search,
|
||||
page,
|
||||
per_page: window.GROUP_SELECT_PER_PAGE,
|
||||
all_available: allAvailable,
|
||||
};
|
||||
},
|
||||
results(data, page) {
|
||||
if (data.length) return { results: [] };
|
||||
|
||||
const groups = data.length ? data : data.results || [];
|
||||
const more = data.pagination ? data.pagination.more : false;
|
||||
const results = groups.filter(group => skipGroups.indexOf(group.id) === -1);
|
||||
|
||||
return {
|
||||
params.success({
|
||||
results,
|
||||
page,
|
||||
more,
|
||||
};
|
||||
},
|
||||
},
|
||||
// eslint-disable-next-line consistent-return
|
||||
initSelection(element, callback) {
|
||||
const id = $(element).val();
|
||||
if (id !== '') {
|
||||
return Api.group(id, callback);
|
||||
}
|
||||
},
|
||||
formatResult(object) {
|
||||
return `<div class='group-result'> <div class='group-name'>${object.full_name}</div> <div class='group-path'>${object.full_path}</div> </div>`;
|
||||
},
|
||||
formatSelection(object) {
|
||||
return object.full_name;
|
||||
},
|
||||
dropdownCssClass: 'ajax-groups-dropdown select2-infinite',
|
||||
// we do not want to escape markup since we are displaying html in results
|
||||
escapeMarkup(m) {
|
||||
return m;
|
||||
},
|
||||
});
|
||||
pagination: {
|
||||
more,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(params.error);
|
||||
},
|
||||
data(search, page) {
|
||||
return {
|
||||
search,
|
||||
page,
|
||||
per_page: window.GROUP_SELECT_PER_PAGE,
|
||||
all_available: allAvailable,
|
||||
};
|
||||
},
|
||||
results(data, page) {
|
||||
if (data.length) return { results: [] };
|
||||
|
||||
$select.on('select2-loaded', () => {
|
||||
const dropdown = document.querySelector('.select2-infinite .select2-results');
|
||||
dropdown.style.height = `${Math.floor(dropdown.scrollHeight)}px`;
|
||||
});
|
||||
});
|
||||
})
|
||||
const groups = data.length ? data : data.results || [];
|
||||
const more = data.pagination ? data.pagination.more : false;
|
||||
const results = groups.filter(group => skipGroups.indexOf(group.id) === -1);
|
||||
|
||||
return {
|
||||
results,
|
||||
page,
|
||||
more,
|
||||
};
|
||||
},
|
||||
},
|
||||
// eslint-disable-next-line consistent-return
|
||||
initSelection(element, callback) {
|
||||
const id = $(element).val();
|
||||
if (id !== '') {
|
||||
return Api.group(id, callback);
|
||||
}
|
||||
},
|
||||
formatResult(object) {
|
||||
return `<div class='group-result'> <div class='group-name'>${object.full_name}</div> <div class='group-path'>${object.full_path}</div> </div>`;
|
||||
},
|
||||
formatSelection(object) {
|
||||
return object.full_name;
|
||||
},
|
||||
dropdownCssClass: 'ajax-groups-dropdown select2-infinite',
|
||||
// we do not want to escape markup since we are displaying html in results
|
||||
escapeMarkup(m) {
|
||||
return m;
|
||||
},
|
||||
});
|
||||
|
||||
$select.on('select2-loaded', () => {
|
||||
const dropdown = document.querySelector('.select2-infinite .select2-results');
|
||||
dropdown.style.height = `${Math.floor(dropdown.scrollHeight)}px`;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export default () =>
|
||||
import(/* webpackChunkName: 'select2' */ 'select2/select2')
|
||||
.then(groupsSelect)
|
||||
.catch(() => {});
|
||||
}
|
||||
|
|
|
@ -120,7 +120,7 @@ export default class LabelsSelect {
|
|||
labelCount = 0;
|
||||
if (data.labels.length && issueUpdateURL) {
|
||||
template = LabelsSelect.getLabelTemplate({
|
||||
labels: data.labels,
|
||||
labels: _.sortBy(data.labels, 'title'),
|
||||
issueUpdateURL,
|
||||
enableScopedLabels: scopedLabels,
|
||||
scopedLabelsDocumentationLink,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Import::BitbucketController < Import::BaseController
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
|
||||
before_action :verify_bitbucket_import_enabled
|
||||
before_action :bitbucket_auth, except: :callback
|
||||
|
||||
|
@ -21,7 +23,7 @@ class Import::BitbucketController < Import::BaseController
|
|||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def status
|
||||
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? }
|
||||
|
||||
|
@ -104,4 +106,8 @@ class Import::BitbucketController < Import::BaseController
|
|||
refresh_token: session[:bitbucket_refresh_token]
|
||||
}
|
||||
end
|
||||
|
||||
def sanitized_filter_param
|
||||
@filter ||= sanitize(params[:filter])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,6 +9,7 @@ module Groups
|
|||
|
||||
def execute
|
||||
remove_unallowed_params
|
||||
set_visibility_level
|
||||
|
||||
@group = Group.new(params)
|
||||
|
||||
|
@ -68,6 +69,12 @@ module Groups
|
|||
|
||||
true
|
||||
end
|
||||
|
||||
def set_visibility_level
|
||||
return if visibility_level.present?
|
||||
|
||||
params[:visibility_level] = Gitlab::CurrentSettings.current_application_settings.default_group_visibility
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
- if @repos.any?
|
||||
%p.light
|
||||
= _('Select projects you want to import.')
|
||||
%hr
|
||||
%p
|
||||
- if @incompatible_repos.any?
|
||||
= button_tag class: 'btn btn-import btn-success js-import-all' do
|
||||
|
@ -19,6 +18,14 @@
|
|||
= _('Import all projects')
|
||||
= 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.table.import-jobs
|
||||
%colgroup.import-jobs-from-col
|
||||
|
@ -59,7 +66,7 @@
|
|||
- if current_user.can_select_namespace?
|
||||
- selected = params[:namespace_id] || :current_user
|
||||
- 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
|
||||
= text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true
|
||||
%span.input-group-prepend
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
- if current_user.can_select_namespace?
|
||||
- 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)) } : {}
|
||||
= 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
|
||||
= text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true
|
||||
%span.input-group-prepend
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Alphabetically sorts selected sidebar labels.
|
||||
merge_request: 17309
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'Allow to exclude ancestor groups on group labels API'
|
||||
merge_request: 17221
|
||||
author: Mathieu Parent
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add project filtering to Bitbucket Cloud import
|
||||
merge_request: 16828
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix visibility level error when updating group from API
|
||||
merge_request: 17227
|
||||
author: Mathieu Parent
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
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]
|
|||
def up
|
||||
disable_statement_timeout do
|
||||
# this is a plain btree on a single boolean column. It'll never be
|
||||
# selective enough to be valuable. This class is called by
|
||||
# setup_postgresql.rake so it needs to be able to handle this
|
||||
# index not existing.
|
||||
# selective enough to be valuable.
|
||||
if index_exists?(:redirect_routes, :permanent)
|
||||
remove_concurrent_index(:redirect_routes, :permanent)
|
||||
end
|
||||
|
|
|
@ -14,9 +14,7 @@ class AddPathIndexToRedirectRoutes < ActiveRecord::Migration[4.2]
|
|||
# RedirectRoute.matching_path_and_descendants
|
||||
#
|
||||
# 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
|
||||
# `setup_postgresql.rake` task when setting up a new database, in which case
|
||||
# we want to create the index.
|
||||
# is a no-op in most cases.
|
||||
def up
|
||||
return unless Gitlab::Database.postgresql?
|
||||
|
||||
|
@ -31,8 +29,5 @@ class AddPathIndexToRedirectRoutes < ActiveRecord::Migration[4.2]
|
|||
# Do nothing in the DOWN. Since the index above is originally created in the
|
||||
# `ReworkRedirectRoutesIndexes`. This migration wouldn't have actually
|
||||
# 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
|
||||
|
|
|
@ -2917,6 +2917,7 @@ ActiveRecord::Schema.define(version: 2019_09_26_041216) do
|
|||
t.boolean "emails_disabled"
|
||||
t.integer "max_pages_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 ["created_at"], name: "index_projects_on_created_at"
|
||||
t.index ["creator_id"], name: "index_projects_on_creator_id"
|
||||
|
@ -3113,6 +3114,7 @@ ActiveRecord::Schema.define(version: 2019_09_26_041216) do
|
|||
t.string "path", null: false
|
||||
t.datetime "created_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 ["source_type", "source_id"], name: "index_redirect_routes_on_source_type_and_source_id"
|
||||
end
|
||||
|
@ -3641,6 +3643,7 @@ ActiveRecord::Schema.define(version: 2019_09_26_041216) do
|
|||
t.string "first_name", limit: 255
|
||||
t.string "last_name", 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 ["admin"], name: "index_users_on_admin"
|
||||
t.index ["bot_type"], name: "index_users_on_bot_type"
|
||||
|
|
|
@ -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. |
|
||||
| `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
|
||||
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
|
|||
| --------- | ------- | -------- | --------------------- |
|
||||
| `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))_ |
|
||||
| `include_ancestor_groups` | boolean | no | Include ancestor groups. Defaults to `true`. |
|
||||
|
||||
```bash
|
||||
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.
|
|||
![Grant access](img/bitbucket_import_grant_access.png)
|
||||
|
||||
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
|
||||
imported.
|
||||
You can also filter projects by name and select the namespace under which
|
||||
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
|
||||
[social sign-in]: ../../profile/account/social_sign_in.md
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 8.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 110 KiB |
|
@ -18,10 +18,12 @@ module API
|
|||
params do
|
||||
optional :with_counts, type: Boolean, default: false,
|
||||
desc: 'Include issue and merge request counts'
|
||||
optional :include_ancestor_groups, type: Boolean, default: true,
|
||||
desc: 'Include ancestor groups'
|
||||
use :pagination
|
||||
end
|
||||
get ':id/labels' do
|
||||
get_labels(user_group, Entities::GroupLabel)
|
||||
get_labels(user_group, Entities::GroupLabel, include_ancestor_groups: params[:include_ancestor_groups])
|
||||
end
|
||||
|
||||
desc 'Create a new label' do
|
||||
|
|
|
@ -10,8 +10,6 @@ module API
|
|||
optional :description, type: String, desc: 'The description of the group'
|
||||
optional :visibility, type: String,
|
||||
values: Gitlab::VisibilityLevel.string_values,
|
||||
default: Gitlab::VisibilityLevel.string_level(
|
||||
Gitlab::CurrentSettings.current_application_settings.default_group_visibility),
|
||||
desc: 'The visibility of the 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'
|
||||
|
|
|
@ -18,8 +18,8 @@ module API
|
|||
label || not_found!('Label')
|
||||
end
|
||||
|
||||
def get_labels(parent, entity)
|
||||
present paginate(available_labels_for(parent)),
|
||||
def get_labels(parent, entity, include_ancestor_groups: true)
|
||||
present paginate(available_labels_for(parent, include_ancestor_groups: include_ancestor_groups)),
|
||||
with: entity,
|
||||
current_user: current_user,
|
||||
parent: parent,
|
||||
|
|
|
@ -17,10 +17,12 @@ module API
|
|||
params do
|
||||
optional :with_counts, type: Boolean, default: false,
|
||||
desc: 'Include issue and merge request counts'
|
||||
optional :include_ancestor_groups, type: Boolean, default: true,
|
||||
desc: 'Include ancestor groups'
|
||||
use :pagination
|
||||
end
|
||||
get ':id/labels' do
|
||||
get_labels(user_project, Entities::ProjectLabel)
|
||||
get_labels(user_project, Entities::ProjectLabel, include_ancestor_groups: params[:include_ancestor_groups])
|
||||
end
|
||||
|
||||
desc 'Create a new label' do
|
||||
|
|
|
@ -38,8 +38,10 @@ module Bitbucket
|
|||
Representation::Repo.new(parsed_response)
|
||||
end
|
||||
|
||||
def repos
|
||||
def repos(filter: nil)
|
||||
path = "/repositories?role=member"
|
||||
path += "&q=name~\"#{filter}\"" if filter
|
||||
|
||||
get_collection(path, :repo)
|
||||
end
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ namespace :gitlab do
|
|||
terminate_all_connections unless Rails.env.production?
|
||||
|
||||
Rake::Task["db:reset"].invoke
|
||||
Rake::Task["setup_postgresql"].invoke
|
||||
Rake::Task["db:seed_fu"].invoke
|
||||
rescue Gitlab::TaskAbortedByUserError
|
||||
puts "Quitting...".color(:red)
|
||||
|
|
|
@ -1,14 +1,3 @@
|
|||
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'
|
||||
task :postgresql_md5_hash do
|
||||
require 'digest'
|
||||
|
|
|
@ -6795,6 +6795,9 @@ msgstr ""
|
|||
msgid "Filter by two-factor authentication"
|
||||
msgstr ""
|
||||
|
||||
msgid "Filter projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "Filter results by group"
|
||||
msgstr ""
|
||||
|
||||
|
@ -13602,6 +13605,9 @@ msgstr ""
|
|||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search Button"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search an environment spec"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -26,8 +26,16 @@ module QA::Page
|
|||
end
|
||||
|
||||
# Reminder: You may wish to wait for a particular job status before checking output
|
||||
def output
|
||||
find_element(:build_trace).text
|
||||
def output(wait: 5)
|
||||
result = ''
|
||||
|
||||
wait(reload: false, max: wait, interval: 1) do
|
||||
result = find_element(:build_trace).text
|
||||
|
||||
!result.empty?
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -80,6 +80,21 @@ describe Import::BitbucketController do
|
|||
expect(assigns(:already_added_projects)).to eq([@project])
|
||||
expect(assigns(:repos)).to eq([])
|
||||
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
|
||||
|
||||
describe "POST create" do
|
||||
|
|
|
@ -5,6 +5,7 @@ import MockAdapter from 'axios-mock-adapter';
|
|||
import axios from '~/lib/utils/axios_utils';
|
||||
import IssuableContext from '~/issuable_context';
|
||||
import LabelsSelect from '~/labels_select';
|
||||
import _ from 'underscore';
|
||||
|
||||
import '~/gl_dropdown';
|
||||
import 'select2';
|
||||
|
@ -15,6 +16,35 @@ import '~/users_select';
|
|||
let saveLabelCount = 0;
|
||||
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', () => {
|
||||
preloadFixtures('static/issue_sidebar_label.html');
|
||||
|
||||
|
@ -29,7 +59,7 @@ describe('Issue dropdown sidebar', () => {
|
|||
mock.onGet('/root/test/labels.json').reply(() => {
|
||||
const labels = Array(10)
|
||||
.fill()
|
||||
.map((_, i) => ({
|
||||
.map((_val, i) => ({
|
||||
id: i,
|
||||
title: `test ${i}`,
|
||||
color: '#5CB85C',
|
||||
|
@ -41,7 +71,7 @@ describe('Issue dropdown sidebar', () => {
|
|||
mock.onPut('/root/test/issues/2.json').reply(() => {
|
||||
const labels = Array(saveLabelCount)
|
||||
.fill()
|
||||
.map((_, i) => ({
|
||||
.map((_val, i) => ({
|
||||
id: i,
|
||||
title: `test ${i}`,
|
||||
color: '#5CB85C',
|
||||
|
@ -57,61 +87,11 @@ describe('Issue dropdown sidebar', () => {
|
|||
|
||||
it('changes collapsed tooltip when changing labels when less than 5', done => {
|
||||
saveLabelCount = 5;
|
||||
$('.edit-link')
|
||||
.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);
|
||||
testLabelClicks('test 0, test 1, test 2, test 3, test 4', done);
|
||||
});
|
||||
|
||||
it('changes collapsed tooltip when changing labels when more than 5', done => {
|
||||
saveLabelCount = 6;
|
||||
$('.edit-link')
|
||||
.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);
|
||||
testLabelClicks('test 0, test 1, test 2, test 3, test 4, and 1 more', done);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,9 +5,11 @@ require 'spec_helper'
|
|||
describe API::GroupLabels do
|
||||
let(:user) { create(:user) }
|
||||
let(:group) { create(:group) }
|
||||
let(:subgroup) { create(:group, parent: group) }
|
||||
let!(:group_member) { create(:group_member, group: group, user: user) }
|
||||
let!(:label1) { create(:group_label, title: 'feature', group: group) }
|
||||
let!(:label2) { create(:group_label, title: 'bug', group: group) }
|
||||
let!(:group_label1) { create(:group_label, title: 'feature', 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
|
||||
it 'returns all available labels for the group' do
|
||||
|
@ -35,6 +37,34 @@ describe API::GroupLabels do
|
|||
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
|
||||
it 'returns created label when all params are given' do
|
||||
post api("/groups/#{group.id}/labels", user),
|
||||
|
@ -78,7 +108,7 @@ describe API::GroupLabels do
|
|||
it 'returns 409 if label already exists' do
|
||||
post api("/groups/#{group.id}/labels", user),
|
||||
params: {
|
||||
name: label1.name,
|
||||
name: group_label1.name,
|
||||
color: '#FFAABB'
|
||||
}
|
||||
|
||||
|
@ -89,13 +119,13 @@ describe API::GroupLabels do
|
|||
|
||||
describe 'DELETE /groups/:id/labels' 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)
|
||||
end
|
||||
|
||||
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(json_response['message']).to eq('404 Label Not Found')
|
||||
|
@ -115,12 +145,12 @@ describe API::GroupLabels do
|
|||
|
||||
expect(response).to have_gitlab_http_status(204)
|
||||
expect(subgroup.labels.size).to eq(0)
|
||||
expect(group.labels).to include(label1)
|
||||
expect(group.labels).to include(group_label1)
|
||||
end
|
||||
|
||||
it_behaves_like '412 response' do
|
||||
let(:request) { api("/groups/#{group.id}/labels", user) }
|
||||
let(:params) { { name: label1.name } }
|
||||
let(:params) { { name: group_label1.name } }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -128,7 +158,7 @@ describe API::GroupLabels do
|
|||
it 'returns 200 if name and colors and description are changed' do
|
||||
put api("/groups/#{group.id}/labels", user),
|
||||
params: {
|
||||
name: label1.name,
|
||||
name: group_label1.name,
|
||||
new_name: 'New Label',
|
||||
color: '#FFFFFF',
|
||||
description: 'test'
|
||||
|
@ -152,13 +182,13 @@ describe API::GroupLabels do
|
|||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(subgroup.labels[0].name).to eq('New Label')
|
||||
expect(label1.name).to eq('feature')
|
||||
expect(group_label1.name).to eq('feature')
|
||||
end
|
||||
|
||||
it 'returns 404 if label does not exist' do
|
||||
put api("/groups/#{group.id}/labels", user),
|
||||
params: {
|
||||
name: 'label2',
|
||||
name: 'not_exists',
|
||||
new_name: 'label3'
|
||||
}
|
||||
|
||||
|
@ -166,14 +196,14 @@ describe API::GroupLabels do
|
|||
end
|
||||
|
||||
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(json_response['error']).to eq('name is missing')
|
||||
end
|
||||
|
||||
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(json_response['error']).to eq('new_name, color, description are missing, '\
|
||||
|
@ -184,31 +214,31 @@ describe API::GroupLabels do
|
|||
describe 'POST /groups/:id/labels/:label_id/subscribe' do
|
||||
context 'when label_id is a label title' 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(json_response['name']).to eq(label1.title)
|
||||
expect(json_response['name']).to eq(group_label1.title)
|
||||
expect(json_response['subscribed']).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when label_id is a label ID' 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(json_response['name']).to eq(label1.title)
|
||||
expect(json_response['name']).to eq(group_label1.title)
|
||||
expect(json_response['subscribed']).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is already subscribed to label' do
|
||||
before do
|
||||
label1.subscribe(user)
|
||||
group_label1.subscribe(user)
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
@ -225,36 +255,36 @@ describe API::GroupLabels do
|
|||
|
||||
describe 'POST /groups/:id/labels/:label_id/unsubscribe' do
|
||||
before do
|
||||
label1.subscribe(user)
|
||||
group_label1.subscribe(user)
|
||||
end
|
||||
|
||||
context 'when label_id is a label title' 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(json_response['name']).to eq(label1.title)
|
||||
expect(json_response['name']).to eq(group_label1.title)
|
||||
expect(json_response['subscribed']).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when label_id is a label ID' 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(json_response['name']).to eq(label1.title)
|
||||
expect(json_response['name']).to eq(group_label1.title)
|
||||
expect(json_response['subscribed']).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is already unsubscribed from label' do
|
||||
before do
|
||||
label1.unsubscribe(user)
|
||||
group_label1.unsubscribe(user)
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
|
|
@ -497,6 +497,29 @@ describe API::Groups do
|
|||
|
||||
expect(response).to have_gitlab_http_status(404)
|
||||
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
|
||||
|
||||
context 'when authenticated as the admin' do
|
||||
|
|
|
@ -256,6 +256,52 @@ describe API::Labels do
|
|||
'is_project_label' => true)
|
||||
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
|
||||
|
||||
describe 'POST /projects/:id/labels' do
|
||||
|
|
Loading…
Reference in New Issue