Merge branch 'master' into 'bootstrap4'
# Conflicts: # app/views/groups/group_members/index.html.haml
This commit is contained in:
commit
15cdc8bfc8
2
Gemfile
2
Gemfile
|
@ -6,7 +6,7 @@ end
|
|||
gem_versions = {}
|
||||
gem_versions['activerecord_sane_schema_dumper'] = rails5? ? '1.0' : '0.2'
|
||||
gem_versions['default_value_for'] = rails5? ? '~> 3.0.5' : '~> 3.0.0'
|
||||
gem_versions['rails'] = rails5? ? '5.0.6' : '4.2.10'
|
||||
gem_versions['rails'] = rails5? ? '5.0.7' : '4.2.10'
|
||||
gem_versions['rails-i18n'] = rails5? ? '~> 5.1' : '~> 4.0.9'
|
||||
# --- The end of special code for migrating to Rails 5.0 ---
|
||||
|
||||
|
|
|
@ -4,43 +4,43 @@ GEM
|
|||
RedCloth (4.3.2)
|
||||
abstract_type (0.0.7)
|
||||
ace-rails-ap (4.1.4)
|
||||
actioncable (5.0.6)
|
||||
actionpack (= 5.0.6)
|
||||
actioncable (5.0.7)
|
||||
actionpack (= 5.0.7)
|
||||
nio4r (>= 1.2, < 3.0)
|
||||
websocket-driver (~> 0.6.1)
|
||||
actionmailer (5.0.6)
|
||||
actionpack (= 5.0.6)
|
||||
actionview (= 5.0.6)
|
||||
activejob (= 5.0.6)
|
||||
actionmailer (5.0.7)
|
||||
actionpack (= 5.0.7)
|
||||
actionview (= 5.0.7)
|
||||
activejob (= 5.0.7)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.0.6)
|
||||
actionview (= 5.0.6)
|
||||
activesupport (= 5.0.6)
|
||||
actionpack (5.0.7)
|
||||
actionview (= 5.0.7)
|
||||
activesupport (= 5.0.7)
|
||||
rack (~> 2.0)
|
||||
rack-test (~> 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.0.6)
|
||||
activesupport (= 5.0.6)
|
||||
actionview (5.0.7)
|
||||
activesupport (= 5.0.7)
|
||||
builder (~> 3.1)
|
||||
erubis (~> 2.7.0)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
activejob (5.0.6)
|
||||
activesupport (= 5.0.6)
|
||||
activejob (5.0.7)
|
||||
activesupport (= 5.0.7)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.0.6)
|
||||
activesupport (= 5.0.6)
|
||||
activerecord (5.0.6)
|
||||
activemodel (= 5.0.6)
|
||||
activesupport (= 5.0.6)
|
||||
activemodel (5.0.7)
|
||||
activesupport (= 5.0.7)
|
||||
activerecord (5.0.7)
|
||||
activemodel (= 5.0.7)
|
||||
activesupport (= 5.0.7)
|
||||
arel (~> 7.0)
|
||||
activerecord_sane_schema_dumper (1.0)
|
||||
rails (>= 5, < 6)
|
||||
activesupport (5.0.6)
|
||||
activesupport (5.0.7)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (~> 0.7)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
acts-as-taggable-on (5.0.0)
|
||||
|
@ -62,13 +62,13 @@ GEM
|
|||
asciidoctor (1.5.6.1)
|
||||
asciidoctor-plantuml (0.0.8)
|
||||
asciidoctor (~> 1.5)
|
||||
asset_sync (2.2.0)
|
||||
asset_sync (2.4.0)
|
||||
activemodel (>= 4.1.0)
|
||||
fog-core
|
||||
mime-types (>= 2.99)
|
||||
unf
|
||||
ast (2.4.0)
|
||||
atomic (1.1.100)
|
||||
atomic (1.1.99)
|
||||
attr_encrypted (3.1.0)
|
||||
encryptor (~> 3.0.0)
|
||||
attr_required (1.0.1)
|
||||
|
@ -144,12 +144,10 @@ GEM
|
|||
connection_pool (2.2.1)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
crass (1.0.3)
|
||||
crass (1.0.4)
|
||||
creole (0.5.0)
|
||||
css_parser (1.6.0)
|
||||
addressable
|
||||
d3_rails (3.5.17)
|
||||
railties (>= 3.1.0)
|
||||
daemons (1.2.6)
|
||||
database_cleaner (1.5.3)
|
||||
debug_inspector (0.0.3)
|
||||
|
@ -292,7 +290,7 @@ GEM
|
|||
po_to_json (>= 1.0.0)
|
||||
rails (>= 3.2.0)
|
||||
gherkin-ruby (0.3.2)
|
||||
gitaly-proto (0.97.0)
|
||||
gitaly-proto (0.99.0)
|
||||
google-protobuf (~> 3.1)
|
||||
grpc (~> 1.10)
|
||||
github-linguist (5.3.3)
|
||||
|
@ -335,9 +333,8 @@ GEM
|
|||
activesupport (>= 4.2.0)
|
||||
gollum-grit_adapter (1.0.1)
|
||||
gitlab-grit (~> 2.7, >= 2.7.1)
|
||||
gon (6.1.0)
|
||||
gon (6.2.0)
|
||||
actionpack (>= 3.0)
|
||||
json
|
||||
multi_json
|
||||
request_store (>= 1.0)
|
||||
google-api-client (0.19.8)
|
||||
|
@ -367,8 +364,8 @@ GEM
|
|||
rack (>= 1.3.0)
|
||||
rack-accept
|
||||
virtus (>= 1.0.0)
|
||||
grape-entity (0.6.1)
|
||||
activesupport (>= 5.0.0)
|
||||
grape-entity (0.7.1)
|
||||
activesupport (>= 4.0)
|
||||
multi_json (>= 1.3.2)
|
||||
grape-route-helpers (2.1.0)
|
||||
activesupport
|
||||
|
@ -420,7 +417,7 @@ GEM
|
|||
json (~> 1.8)
|
||||
multi_xml (>= 0.5.2)
|
||||
httpclient (2.8.3)
|
||||
i18n (0.9.5)
|
||||
i18n (1.0.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
ice_nine (0.11.2)
|
||||
influxdb (0.5.3)
|
||||
|
@ -515,7 +512,7 @@ GEM
|
|||
net-ldap (0.16.1)
|
||||
net-ssh (4.2.0)
|
||||
netrc (0.11.0)
|
||||
nio4r (2.2.0)
|
||||
nio4r (2.3.1)
|
||||
nokogiri (1.8.2)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
numerizer (0.1.1)
|
||||
|
@ -545,9 +542,9 @@ GEM
|
|||
omniauth (~> 1.2)
|
||||
omniauth-facebook (4.0.0)
|
||||
omniauth-oauth2 (~> 1.2)
|
||||
omniauth-github (1.1.2)
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2 (~> 1.1)
|
||||
omniauth-github (1.3.0)
|
||||
omniauth (~> 1.5)
|
||||
omniauth-oauth2 (>= 1.4.0, < 2.0)
|
||||
omniauth-gitlab (1.0.3)
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2 (~> 1.0)
|
||||
|
@ -633,7 +630,7 @@ GEM
|
|||
parser
|
||||
unparser
|
||||
procto (0.0.3)
|
||||
prometheus-client-mmap (0.9.1)
|
||||
prometheus-client-mmap (0.9.2)
|
||||
pry (0.11.3)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
|
@ -644,7 +641,7 @@ GEM
|
|||
pry (>= 0.10.4)
|
||||
public_suffix (3.0.2)
|
||||
pyu-ruby-sasl (0.0.3.3)
|
||||
rack (2.0.4)
|
||||
rack (2.0.5)
|
||||
rack-accept (0.4.5)
|
||||
rack (>= 0.4)
|
||||
rack-attack (4.4.1)
|
||||
|
@ -662,17 +659,17 @@ GEM
|
|||
rack
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rails (5.0.6)
|
||||
actioncable (= 5.0.6)
|
||||
actionmailer (= 5.0.6)
|
||||
actionpack (= 5.0.6)
|
||||
actionview (= 5.0.6)
|
||||
activejob (= 5.0.6)
|
||||
activemodel (= 5.0.6)
|
||||
activerecord (= 5.0.6)
|
||||
activesupport (= 5.0.6)
|
||||
rails (5.0.7)
|
||||
actioncable (= 5.0.7)
|
||||
actionmailer (= 5.0.7)
|
||||
actionpack (= 5.0.7)
|
||||
actionview (= 5.0.7)
|
||||
activejob (= 5.0.7)
|
||||
activemodel (= 5.0.7)
|
||||
activerecord (= 5.0.7)
|
||||
activesupport (= 5.0.7)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 5.0.6)
|
||||
railties (= 5.0.7)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.2)
|
||||
actionpack (~> 5.x, >= 5.0.1)
|
||||
|
@ -683,21 +680,21 @@ GEM
|
|||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.0.3)
|
||||
loofah (~> 2.0)
|
||||
rails-html-sanitizer (1.0.4)
|
||||
loofah (~> 2.2, >= 2.2.2)
|
||||
rails-i18n (5.1.1)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 5.0, < 6)
|
||||
railties (5.0.6)
|
||||
actionpack (= 5.0.6)
|
||||
activesupport (= 5.0.6)
|
||||
railties (5.0.7)
|
||||
actionpack (= 5.0.7)
|
||||
activesupport (= 5.0.7)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (2.2.2)
|
||||
rake
|
||||
raindrops (0.19.0)
|
||||
rake (12.3.0)
|
||||
rake (12.3.1)
|
||||
rb-fsevent (0.10.3)
|
||||
rb-inotify (0.9.10)
|
||||
ffi (>= 0.5.0, < 2)
|
||||
|
@ -1001,7 +998,7 @@ DEPENDENCIES
|
|||
asana (~> 0.6.0)
|
||||
asciidoctor (~> 1.5.6)
|
||||
asciidoctor-plantuml (= 0.0.8)
|
||||
asset_sync (~> 2.2.0)
|
||||
asset_sync (~> 2.4)
|
||||
attr_encrypted (~> 3.1.0)
|
||||
awesome_print (~> 1.2.0)
|
||||
babosa (~> 1.0.2)
|
||||
|
@ -1027,12 +1024,11 @@ DEPENDENCIES
|
|||
concurrent-ruby (~> 1.0.5)
|
||||
connection_pool (~> 2.0)
|
||||
creole (~> 0.5.0)
|
||||
d3_rails (~> 3.5.0)
|
||||
database_cleaner (~> 1.5.0)
|
||||
deckar01-task_list (= 2.0.0)
|
||||
default_value_for (~> 3.0.5)
|
||||
device_detector
|
||||
devise (~> 4.2)
|
||||
devise (~> 4.4)
|
||||
devise-two-factor (~> 3.0.0)
|
||||
diffy (~> 3.1.0)
|
||||
doorkeeper (~> 4.3)
|
||||
|
@ -1063,7 +1059,7 @@ DEPENDENCIES
|
|||
gettext (~> 3.2.2)
|
||||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.3)
|
||||
gitaly-proto (~> 0.97.0)
|
||||
gitaly-proto (~> 0.99.0)
|
||||
github-linguist (~> 5.3.3)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab-gollum-lib (~> 4.2)
|
||||
|
@ -1071,12 +1067,12 @@ DEPENDENCIES
|
|||
gitlab-markup (~> 1.6.2)
|
||||
gitlab-styles (~> 2.3)
|
||||
gitlab_omniauth-ldap (~> 2.0.4)
|
||||
gon (~> 6.1.0)
|
||||
gon (~> 6.2)
|
||||
google-api-client (~> 0.19.8)
|
||||
google-protobuf (= 3.5.1)
|
||||
gpgme
|
||||
grape (~> 1.0)
|
||||
grape-entity (~> 0.6.0)
|
||||
grape-entity (~> 0.7.1)
|
||||
grape-route-helpers (~> 2.1.0)
|
||||
grape_logging (~> 1.7)
|
||||
grpc (~> 1.11.0)
|
||||
|
@ -1117,7 +1113,7 @@ DEPENDENCIES
|
|||
omniauth-azure-oauth2 (~> 0.0.9)
|
||||
omniauth-cas3 (~> 1.1.4)
|
||||
omniauth-facebook (~> 4.0.0)
|
||||
omniauth-github (~> 1.1.1)
|
||||
omniauth-github (~> 1.3)
|
||||
omniauth-gitlab (~> 1.0.2)
|
||||
omniauth-google-oauth2 (~> 0.5.3)
|
||||
omniauth-kerberos (~> 0.3.0)
|
||||
|
@ -1136,14 +1132,14 @@ DEPENDENCIES
|
|||
peek-sidekiq (~> 1.0.3)
|
||||
pg (~> 0.18.2)
|
||||
premailer-rails (~> 1.9.7)
|
||||
prometheus-client-mmap (~> 0.9.1)
|
||||
prometheus-client-mmap (~> 0.9.2)
|
||||
pry-byebug (~> 3.4.1)
|
||||
pry-rails (~> 0.3.4)
|
||||
rack-attack (~> 4.4.1)
|
||||
rack-cors (~> 1.0.0)
|
||||
rack-oauth2 (~> 1.2.1)
|
||||
rack-proxy (~> 0.6.0)
|
||||
rails (= 5.0.6)
|
||||
rails (= 5.0.7)
|
||||
rails-controller-testing
|
||||
rails-deprecated_sanitizer (~> 1.0.3)
|
||||
rails-i18n (~> 5.1)
|
||||
|
|
|
@ -140,7 +140,7 @@ export default {
|
|||
this.file.staged && this.file.key.indexOf('unstaged-') === 0 ? head : null,
|
||||
);
|
||||
|
||||
if (this.viewer === viewerTypes.mr) {
|
||||
if (this.viewer === viewerTypes.mr && this.file.mrChange) {
|
||||
this.editor.attachMergeRequestModel(this.model);
|
||||
} else {
|
||||
this.editor.attachModel(this.model);
|
||||
|
|
|
@ -44,6 +44,7 @@ export const dataStructure = () => ({
|
|||
size: 0,
|
||||
parentPath: null,
|
||||
lastOpenedAt: 0,
|
||||
mrChange: null,
|
||||
});
|
||||
|
||||
export const decorateData = entity => {
|
||||
|
|
|
@ -111,7 +111,13 @@
|
|||
</td>
|
||||
|
||||
<td>
|
||||
{{ timeFormated(item.createdAt) }}
|
||||
<span
|
||||
v-tooltip
|
||||
:title="tooltipTitle(item.createdAt)"
|
||||
data-placement="bottom"
|
||||
>
|
||||
{{ timeFormated(item.createdAt) }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td class="content">
|
||||
|
|
|
@ -15,7 +15,7 @@ export default class UserCallout {
|
|||
|
||||
init() {
|
||||
if (!this.isCalloutDismissed || this.isCalloutDismissed === 'false') {
|
||||
$('.js-close-callout').on('click', e => this.dismissCallout(e));
|
||||
this.userCalloutBody.find('.js-close-callout').on('click', e => this.dismissCallout(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,12 +23,15 @@ export default class UserCallout {
|
|||
const $currentTarget = $(e.currentTarget);
|
||||
|
||||
if (this.options.setCalloutPerProject) {
|
||||
Cookies.set(this.cookieName, 'true', { expires: 365, path: this.userCalloutBody.data('projectPath') });
|
||||
Cookies.set(this.cookieName, 'true', {
|
||||
expires: 365,
|
||||
path: this.userCalloutBody.data('projectPath'),
|
||||
});
|
||||
} else {
|
||||
Cookies.set(this.cookieName, 'true', { expires: 365 });
|
||||
}
|
||||
|
||||
if ($currentTarget.hasClass('close')) {
|
||||
if ($currentTarget.hasClass('close') || $currentTarget.hasClass('js-close')) {
|
||||
this.userCalloutBody.remove();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,12 +109,12 @@ export default {
|
|||
rel="noopener noreferrer nofollow"
|
||||
class="deploy-link js-deploy-url"
|
||||
>
|
||||
{{ deployment.external_url_formatted }}
|
||||
<i
|
||||
class="fa fa-external-link"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
{{ deployment.external_url_formatted }}
|
||||
</a>
|
||||
</template>
|
||||
<span
|
||||
|
|
|
@ -17,6 +17,16 @@
|
|||
*/
|
||||
@mixin markdown-table {
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
overflow-x: auto;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 0;
|
||||
|
||||
@supports(width: fit-content) {
|
||||
display: block;
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
line-height: $line-height-base;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
|
@ -33,10 +34,14 @@
|
|||
|
||||
.navbar-collapse {
|
||||
padding-right: 0;
|
||||
|
||||
.navbar-nav {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.nav li a {
|
||||
color: $theme-gray-700;
|
||||
.nav li {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -180,11 +180,6 @@ ul.wiki-pages-list.content-list {
|
|||
}
|
||||
}
|
||||
|
||||
.wiki-holder {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.wiki {
|
||||
table {
|
||||
@include markdown-table;
|
||||
|
|
|
@ -11,13 +11,20 @@ class Groups::GroupMembersController < Groups::ApplicationController
|
|||
:override
|
||||
|
||||
def index
|
||||
can_manage_members = can?(current_user, :admin_group_member, @group)
|
||||
|
||||
@sort = params[:sort].presence || sort_value_name
|
||||
@project = @group.projects.find(params[:project_id]) if params[:project_id]
|
||||
|
||||
@members = GroupMembersFinder.new(@group).execute
|
||||
@members = @members.non_invite unless can?(current_user, :admin_group, @group)
|
||||
@members = @members.non_invite unless can_manage_members
|
||||
@members = @members.search(params[:search]) if params[:search].present?
|
||||
@members = @members.sort_by_attribute(@sort)
|
||||
|
||||
if can_manage_members && params[:two_factor].present?
|
||||
@members = @members.filter_by_2fa(params[:two_factor])
|
||||
end
|
||||
|
||||
@members = @members.page(params[:page]).per(50)
|
||||
@members = present_members(@members.includes(:user))
|
||||
|
||||
|
|
|
@ -108,7 +108,13 @@ module Ci
|
|||
end
|
||||
|
||||
def assign_to(project, current_user = nil)
|
||||
self.is_shared = false if shared?
|
||||
if shared?
|
||||
self.is_shared = false if shared?
|
||||
self.runner_type = :project_type
|
||||
elsif group_type?
|
||||
raise ArgumentError, 'Transitioning a group runner to a project runner is not supported'
|
||||
end
|
||||
|
||||
self.save
|
||||
project.runner_projects.create(runner_id: self.id)
|
||||
end
|
||||
|
|
|
@ -4,7 +4,9 @@ module Routable
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_one :route, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
# Remove `inverse_of: source` when upgraded to rails 5.2
|
||||
# See https://github.com/rails/rails/pull/28808
|
||||
has_one :route, as: :source, autosave: true, dependent: :destroy, inverse_of: :source # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
validates :route, presence: true
|
||||
|
|
|
@ -22,7 +22,8 @@ module ShaAttribute
|
|||
column = columns.find { |c| c.name == name.to_s }
|
||||
|
||||
unless column
|
||||
raise ArgumentError.new("sha_attribute #{name.inspect} is invalid since the column doesn't exist")
|
||||
warn "WARNING: sha_attribute #{name.inspect} is invalid since the column doesn't exist - you may need to run database migrations"
|
||||
return
|
||||
end
|
||||
|
||||
unless column.type == :binary
|
||||
|
|
|
@ -96,6 +96,17 @@ class Member < ActiveRecord::Base
|
|||
joins(:user).merge(User.search(query))
|
||||
end
|
||||
|
||||
def filter_by_2fa(value)
|
||||
case value
|
||||
when 'enabled'
|
||||
left_join_users.merge(User.with_two_factor_indistinct)
|
||||
when 'disabled'
|
||||
left_join_users.merge(User.without_two_factor)
|
||||
else
|
||||
all
|
||||
end
|
||||
end
|
||||
|
||||
def sort_by_attribute(method)
|
||||
case method.to_s
|
||||
when 'access_level_asc' then reorder(access_level: :asc)
|
||||
|
|
|
@ -237,14 +237,18 @@ class User < ActiveRecord::Base
|
|||
scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) }
|
||||
scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) }
|
||||
|
||||
def self.with_two_factor
|
||||
def self.with_two_factor_indistinct
|
||||
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
|
||||
.where("u2f.id IS NOT NULL OR otp_required_for_login = ?", true).distinct(arel_table[:id])
|
||||
.where("u2f.id IS NOT NULL OR users.otp_required_for_login = ?", true)
|
||||
end
|
||||
|
||||
def self.with_two_factor
|
||||
with_two_factor_indistinct.distinct(arel_table[:id])
|
||||
end
|
||||
|
||||
def self.without_two_factor
|
||||
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
|
||||
.where("u2f.id IS NULL AND otp_required_for_login = ?", false)
|
||||
.where("u2f.id IS NULL AND users.otp_required_for_login = ?", false)
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
- page_title "Members"
|
||||
- can_manage_members = can?(current_user, :admin_group_member, @group)
|
||||
|
||||
.project-members-page.prepend-top-default
|
||||
%h4
|
||||
Members
|
||||
%hr
|
||||
- if can?(current_user, :admin_group_member, @group)
|
||||
- if can_manage_members
|
||||
.project-members-new.append-bottom-default
|
||||
%p.clearfix
|
||||
Add new member to
|
||||
|
@ -13,20 +14,23 @@
|
|||
|
||||
= render 'shared/members/requests', membership_source: @group, requesters: @requesters
|
||||
|
||||
.append-bottom-default.clearfix
|
||||
.clearfix
|
||||
%h5.member.existing-title
|
||||
Existing members
|
||||
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
|
||||
.form-group
|
||||
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
|
||||
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
|
||||
= icon("search")
|
||||
= render 'shared/members/sort_dropdown'
|
||||
.card
|
||||
.card-header
|
||||
Members with access to
|
||||
%strong= @group.name
|
||||
%span.badge.badge-pill= @members.total_count
|
||||
.card-header.flex-project-members-panel
|
||||
%span.flex-project-title
|
||||
Members with access to
|
||||
%strong= @group.name
|
||||
%span.badge= @members.total_count
|
||||
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
|
||||
.form-group
|
||||
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
|
||||
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
|
||||
= icon("search")
|
||||
- if can_manage_members
|
||||
= render 'shared/members/filter_2fa_dropdown'
|
||||
= render 'shared/members/sort_dropdown'
|
||||
%ul.content-list.members-list
|
||||
= render partial: 'shared/members/member', collection: @members, as: :member
|
||||
= paginate @members, theme: 'gitlab'
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
.top-area
|
||||
= render 'shared/issuable/nav', type: :issues
|
||||
.nav-controls
|
||||
= link_to params.merge(rss_url_options), class: 'btn' do
|
||||
= link_to safe_params.merge(rss_url_options), class: 'btn' do
|
||||
= icon('rss')
|
||||
%span.icon-label
|
||||
Subscribe
|
||||
|
|
|
@ -20,10 +20,10 @@
|
|||
= brand_header_logo
|
||||
- logo_text = brand_header_logo_type
|
||||
- if logo_text.present?
|
||||
%span.logo-text.hidden-xs.prepend-left-8
|
||||
%span.logo-text.prepend-left-8
|
||||
= logo_text
|
||||
- if header_link?(:user_dropdown)
|
||||
.navbar-collapse.collapse
|
||||
.navbar-collapse
|
||||
%ul.nav.navbar-nav
|
||||
%li.header-user.dropdown
|
||||
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
- if @wiki_home.present?
|
||||
%div{ class: container_class }
|
||||
.wiki-holder.prepend-top-default.append-bottom-default
|
||||
.prepend-top-default.append-bottom-default
|
||||
.wiki
|
||||
= render_wiki_content(@wiki_home)
|
||||
- else
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
- history_link = link_to s_("WikiHistoricalPage|history"), project_wiki_history_path(@project, @page)
|
||||
= (s_("WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}.") % { most_recent_link: most_recent_link, history_link: history_link }).html_safe
|
||||
|
||||
.wiki-holder.prepend-top-default.append-bottom-default
|
||||
.prepend-top-default.append-bottom-default
|
||||
.wiki
|
||||
= render_wiki_content(@page)
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
- filter = params[:two_factor] || 'everyone'
|
||||
- filter_options = { 'everyone' => 'Everyone', 'enabled' => 'Enabled', 'disabled' => 'Disabled' }
|
||||
.dropdown.inline.member-filter-2fa-dropdown
|
||||
= dropdown_toggle('2FA: ' + filter_options[filter], { toggle: 'dropdown' })
|
||||
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
|
||||
%li.dropdown-header
|
||||
Filter by two-factor authentication
|
||||
- filter_options.each do |value, title|
|
||||
%li
|
||||
= link_to filter_group_project_member_path(two_factor: value), class: ("is-active" if filter == value) do
|
||||
= title
|
|
@ -20,6 +20,10 @@
|
|||
%label.badge.badge-danger
|
||||
%strong Blocked
|
||||
|
||||
- if user.two_factor_enabled?
|
||||
%label.label.label-info
|
||||
2FA
|
||||
|
||||
- if source.instance_of?(Group) && source != @group
|
||||
·
|
||||
= link_to source.full_name, source, class: "member-group-link"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Moves MR widget external link icon to the right
|
||||
merge_request: 18828
|
||||
author: Jacopo Beschi @jacopo-beschi
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 46210 Display logo and user dropdown on mobile for terms page and fix styling
|
||||
merge_request:
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Expand documentation for Runners API
|
||||
merge_request: 16484
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add 2FA filter to the group members page
|
||||
merge_request: 18483
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'Add missing tooltip to creation date on container registry overview'
|
||||
merge_request: 18767
|
||||
author: Lars Greiss
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Finding a wiki page is done by Gitaly by default
|
||||
merge_request:
|
||||
author:
|
||||
type: other
|
|
@ -26,17 +26,6 @@ def validate_storages_config
|
|||
Gitlab.config.repositories.storages.each do |name, repository_storage|
|
||||
storage_validation_error("\"#{name}\" is not a valid storage name") unless storage_name_valid?(name)
|
||||
|
||||
if repository_storage.is_a?(String)
|
||||
raise "#{name} is not a valid storage, because it has no `path` key. " \
|
||||
"It may be configured as:\n\n#{name}:\n path: #{repository_storage}\n\n" \
|
||||
"For source installations, update your config/gitlab.yml Refer to gitlab.yml.example for an updated example.\n\n" \
|
||||
"If you're using the Gitlab Development Kit, you can update your configuration running `gdk reconfigure`.\n"
|
||||
end
|
||||
|
||||
if !repository_storage.is_a?(Gitlab::GitalyClient::StorageSettings) || repository_storage.legacy_disk_path.nil?
|
||||
storage_validation_error("#{name} is not a valid storage, because it has no `path` key. Refer to gitlab.yml.example for an updated example")
|
||||
end
|
||||
|
||||
%w(failure_count_threshold failure_reset_time storage_timeout).each do |setting|
|
||||
# Falling back to the defaults is fine!
|
||||
next if repository_storage[setting].nil?
|
||||
|
|
|
@ -411,3 +411,86 @@ DELETE /projects/:id/runners/:runner_id
|
|||
```
|
||||
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/9/runners/9"
|
||||
```
|
||||
|
||||
## Register a new Runner
|
||||
|
||||
Register a new Runner for the instance.
|
||||
|
||||
```
|
||||
POST /runners
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|-------------|---------|----------|---------------------|
|
||||
| `token` | string | yes | Registration token ([Read how to obtain a token](../ci/runners/README.md)) |
|
||||
| `description`| string | no | Runner's description|
|
||||
| `info` | hash | no | Runner's metadata |
|
||||
| `active` | boolean| no | Whether the Runner is active |
|
||||
| `locked` | boolean| no | Whether the Runner should be locked for current project |
|
||||
| `run_untagged` | boolean | no | Whether the Runner should handle untagged jobs |
|
||||
| `tag_list` | Array[String] | no | List of Runner's tags |
|
||||
| `maximum_timeout` | integer | no | Maximum timeout set when this Runner will handle the job |
|
||||
|
||||
```
|
||||
curl --request POST "https://gitlab.example.com/api/v4/runners" --form "token=ipzXrMhuyyJPifUt6ANz" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2"
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
| Status | Description |
|
||||
|-----------|---------------------------------|
|
||||
| 201 | Runner was created |
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "12345",
|
||||
"token": "6337ff461c94fd3fa32ba3b1ff4125"
|
||||
}
|
||||
```
|
||||
|
||||
## Delete a registered Runner
|
||||
|
||||
Deletes a registed Runner.
|
||||
|
||||
```
|
||||
DELETE /runners
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|-------------|---------|----------|---------------------|
|
||||
| `token` | string | yes | Runner's authentication token |
|
||||
|
||||
```
|
||||
curl --request DELETE "https://gitlab.example.com/api/v4/runners" --form "token=ebb6fc00521627750c8bb750f2490e"
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
| Status | Description |
|
||||
|-----------|---------------------------------|
|
||||
| 204 | Runner was deleted |
|
||||
|
||||
## Verify authentication for a registered Runner
|
||||
|
||||
Validates authentication credentials for a registered Runner.
|
||||
|
||||
```
|
||||
POST /runners/verify
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|-------------|---------|----------|---------------------|
|
||||
| `token` | string | yes | Runner's authentication token |
|
||||
|
||||
```
|
||||
curl --request POST "https://gitlab.example.com/api/v4/runners/verify" --form "token=ebb6fc00521627750c8bb750f2490e"
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
| Status | Description |
|
||||
|-----------|---------------------------------|
|
||||
| 200 | Credentials are valid |
|
||||
| 403 | Credentials are invalid |
|
||||
|
|
|
@ -129,8 +129,8 @@ You may see a temporary error message `SchedulerPredicates failed due to Persist
|
|||
Add the GitLab Helm repository and initialize Helm:
|
||||
|
||||
```bash
|
||||
helm repo add gitlab https://charts.gitlab.io
|
||||
helm init
|
||||
helm repo add gitlab https://charts.gitlab.io
|
||||
```
|
||||
|
||||
Once you have reviewed the [configuration settings](#configuring-and-installing-gitlab) you can install the chart. We recommending saving your configuration options in a `values.yaml` file for easier upgrades in the future.
|
||||
|
|
|
@ -67,7 +67,8 @@ module Gitlab
|
|||
end
|
||||
|
||||
def page(title:, version: nil, dir: nil)
|
||||
@repository.gitaly_migrate(:wiki_find_page) do |is_enabled|
|
||||
@repository.gitaly_migrate(:wiki_find_page,
|
||||
status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
|
||||
if is_enabled
|
||||
gitaly_find_page(title: title, version: version, dir: dir)
|
||||
else
|
||||
|
|
|
@ -5,6 +5,14 @@ module Gitlab
|
|||
# directly.
|
||||
class StorageSettings
|
||||
DirectPathAccessError = Class.new(StandardError)
|
||||
InvalidConfigurationError = Class.new(StandardError)
|
||||
|
||||
INVALID_STORAGE_MESSAGE = <<~MSG.freeze
|
||||
Storage is invalid because it has no `path` key.
|
||||
|
||||
For source installations, update your config/gitlab.yml Refer to gitlab.yml.example for an updated example.
|
||||
If you're using the Gitlab Development Kit, you can update your configuration running `gdk reconfigure`.
|
||||
MSG
|
||||
|
||||
# This class will give easily recognizable NoMethodErrors
|
||||
Deprecated = Class.new
|
||||
|
@ -12,7 +20,8 @@ module Gitlab
|
|||
attr_reader :legacy_disk_path
|
||||
|
||||
def initialize(storage)
|
||||
raise "expected a Hash, got a #{storage.class.name}" unless storage.is_a?(Hash)
|
||||
raise InvalidConfigurationError, "expected a Hash, got a #{storage.class.name}" unless storage.is_a?(Hash)
|
||||
raise InvalidConfigurationError, INVALID_STORAGE_MESSAGE unless storage.has_key?('path')
|
||||
|
||||
# Support a nil 'path' field because some of the circuit breaker tests use it.
|
||||
@legacy_disk_path = File.expand_path(storage['path'], Rails.root) if storage['path']
|
||||
|
|
|
@ -5,7 +5,7 @@ module Gitlab
|
|||
def initialize(*collections, per_page: nil)
|
||||
raise ArgumentError.new('Only 2 collections are supported') if collections.size != 2
|
||||
|
||||
@per_page = per_page || Kaminari.config.default_per_page
|
||||
@per_page = (per_page || Kaminari.config.default_per_page).to_i
|
||||
@first_collection, @second_collection = collections
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'Groups > Members > Filter members' do
|
||||
let(:user) { create(:user) }
|
||||
let(:user_with_2fa) { create(:user, :two_factor_via_otp) }
|
||||
let(:group) { create(:group) }
|
||||
|
||||
background do
|
||||
group.add_owner(user)
|
||||
group.add_master(user_with_2fa)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
scenario 'shows all members' do
|
||||
visit_members_list
|
||||
|
||||
expect(first_member).to include(user.name)
|
||||
expect(second_member).to include(user_with_2fa.name)
|
||||
expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Everyone')
|
||||
end
|
||||
|
||||
scenario 'shows only 2FA members' do
|
||||
visit_members_list(two_factor: 'enabled')
|
||||
|
||||
expect(first_member).to include(user_with_2fa.name)
|
||||
expect(members_list.size).to eq(1)
|
||||
expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Enabled')
|
||||
end
|
||||
|
||||
scenario 'shows only non 2FA members' do
|
||||
visit_members_list(two_factor: 'disabled')
|
||||
|
||||
expect(first_member).to include(user.name)
|
||||
expect(members_list.size).to eq(1)
|
||||
expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Disabled')
|
||||
end
|
||||
|
||||
def visit_members_list(options = {})
|
||||
visit group_group_members_path(group.to_param, options)
|
||||
end
|
||||
|
||||
def members_list
|
||||
page.all('ul.content-list > li')
|
||||
end
|
||||
|
||||
def first_member
|
||||
members_list.first.text
|
||||
end
|
||||
|
||||
def second_member
|
||||
members_list.last.text
|
||||
end
|
||||
end
|
|
@ -42,26 +42,6 @@ describe '6_validations' do
|
|||
expect { validate_storages_config }.to raise_error('"name with spaces" is not a valid storage name. Please fix this in your gitlab.yml before starting GitLab.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with incomplete settings' do
|
||||
before do
|
||||
mock_storages('foo' => {})
|
||||
end
|
||||
|
||||
it 'throws an error suggesting the user to update its settings' do
|
||||
expect { validate_storages_config }.to raise_error('foo is not a valid storage, because it has no `path` key. Refer to gitlab.yml.example for an updated example. Please fix this in your gitlab.yml before starting GitLab.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with deprecated settings structure' do
|
||||
before do
|
||||
mock_storages('foo' => 'tmp/tests/paths/a/b/c')
|
||||
end
|
||||
|
||||
it 'throws an error suggesting the user to update its settings' do
|
||||
expect { validate_storages_config }.to raise_error("foo is not a valid storage, because it has no `path` key. It may be configured as:\n\nfoo:\n path: tmp/tests/paths/a/b/c\n\nFor source installations, update your config/gitlab.yml Refer to gitlab.yml.example for an updated example.\n\nIf you're using the Gitlab Development Kit, you can update your configuration running `gdk reconfigure`.\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'validate_storages_paths' do
|
||||
|
|
|
@ -24,7 +24,7 @@ describe('RepoEditor', () => {
|
|||
f.active = true;
|
||||
f.tempFile = true;
|
||||
vm.$store.state.openFiles.push(f);
|
||||
vm.$store.state.entries[f.path] = f;
|
||||
Vue.set(vm.$store.state.entries, f.path, f);
|
||||
vm.monaco = true;
|
||||
|
||||
vm.$mount();
|
||||
|
@ -215,6 +215,30 @@ describe('RepoEditor', () => {
|
|||
expect(vm.editor.attachModel).toHaveBeenCalledWith(vm.model);
|
||||
});
|
||||
|
||||
it('attaches model to merge request editor', () => {
|
||||
vm.$store.state.viewer = 'mrdiff';
|
||||
vm.file.mrChange = true;
|
||||
spyOn(vm.editor, 'attachMergeRequestModel');
|
||||
|
||||
Editor.editorInstance.modelManager.dispose();
|
||||
|
||||
vm.setupEditor();
|
||||
|
||||
expect(vm.editor.attachMergeRequestModel).toHaveBeenCalledWith(vm.model);
|
||||
});
|
||||
|
||||
it('does not attach model to merge request editor when not a MR change', () => {
|
||||
vm.$store.state.viewer = 'mrdiff';
|
||||
vm.file.mrChange = false;
|
||||
spyOn(vm.editor, 'attachMergeRequestModel');
|
||||
|
||||
Editor.editorInstance.modelManager.dispose();
|
||||
|
||||
vm.setupEditor();
|
||||
|
||||
expect(vm.editor.attachMergeRequestModel).not.toHaveBeenCalledWith(vm.model);
|
||||
});
|
||||
|
||||
it('adds callback methods', () => {
|
||||
spyOn(vm.editor, 'onPositionChange').and.callThrough();
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::GitalyClient::StorageSettings do
|
||||
describe "#initialize" do
|
||||
context 'when the storage contains no path' do
|
||||
it 'raises an error' do
|
||||
expect do
|
||||
described_class.new("foo" => {})
|
||||
end.to raise_error(described_class::InvalidConfigurationError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the argument isn't a hash" do
|
||||
it 'raises an error' do
|
||||
expect do
|
||||
described_class.new("test")
|
||||
end.to raise_error("expected a Hash, got a String")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the storage is valid' do
|
||||
it 'raises no error' do
|
||||
expect do
|
||||
described_class.new("path" => Rails.root)
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -200,15 +200,29 @@ describe Ci::Runner do
|
|||
|
||||
describe '#assign_to' do
|
||||
let!(:project) { FactoryBot.create(:project) }
|
||||
let!(:shared_runner) { FactoryBot.create(:ci_runner, :shared) }
|
||||
|
||||
before do
|
||||
shared_runner.assign_to(project)
|
||||
subject { runner.assign_to(project) }
|
||||
|
||||
context 'with shared_runner' do
|
||||
let!(:runner) { FactoryBot.create(:ci_runner, :shared) }
|
||||
|
||||
it 'transitions shared runner to project runner and assigns project' do
|
||||
subject
|
||||
expect(runner).to be_specific
|
||||
expect(runner).to be_project_type
|
||||
expect(runner.projects).to eq([project])
|
||||
expect(runner.only_for?(project)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
it { expect(shared_runner).to be_specific }
|
||||
it { expect(shared_runner.projects).to eq([project]) }
|
||||
it { expect(shared_runner.only_for?(project)).to be_truthy }
|
||||
context 'with group runner' do
|
||||
let!(:runner) { FactoryBot.create(:ci_runner, runner_type: :group_type) }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { subject }
|
||||
.to raise_error(ArgumentError, 'Transitioning a group runner to a project runner is not supported')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.online' do
|
||||
|
|
|
@ -55,13 +55,9 @@ describe Clusters::Applications::Runner do
|
|||
|
||||
context 'without a runner' do
|
||||
let(:project) { create(:project) }
|
||||
let(:cluster) { create(:cluster) }
|
||||
let(:cluster) { create(:cluster, projects: [project]) }
|
||||
let(:gitlab_runner) { create(:clusters_applications_runner, cluster: cluster) }
|
||||
|
||||
before do
|
||||
cluster.projects << project
|
||||
end
|
||||
|
||||
it 'creates a runner' do
|
||||
expect do
|
||||
subject
|
||||
|
|
|
@ -239,17 +239,19 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
|
|||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'when kubernetes responds with valid pods' do
|
||||
context 'when kubernetes responds with valid pods and deployments' do
|
||||
before do
|
||||
stub_kubeclient_pods
|
||||
stub_kubeclient_deployments
|
||||
end
|
||||
|
||||
it { is_expected.to eq(pods: [kube_pod]) }
|
||||
it { is_expected.to include(pods: [kube_pod]) }
|
||||
end
|
||||
|
||||
context 'when kubernetes responds with 500s' do
|
||||
before do
|
||||
stub_kubeclient_pods(status: 500)
|
||||
stub_kubeclient_deployments(status: 500)
|
||||
end
|
||||
|
||||
it { expect { subject }.to raise_error(Kubeclient::HttpError) }
|
||||
|
@ -258,9 +260,10 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
|
|||
context 'when kubernetes responds with 404s' do
|
||||
before do
|
||||
stub_kubeclient_pods(status: 404)
|
||||
stub_kubeclient_deployments(status: 404)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(pods: []) }
|
||||
it { is_expected.to include(pods: []) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,24 +36,26 @@ describe ShaAttribute do
|
|||
end
|
||||
|
||||
context 'when the table does not exist' do
|
||||
it 'allows the attribute to be added' do
|
||||
it 'allows the attribute to be added and issues a warning' do
|
||||
allow(model).to receive(:table_exists?).and_return(false)
|
||||
|
||||
expect(model).not_to receive(:columns)
|
||||
expect(model).to receive(:attribute)
|
||||
expect(model).to receive(:warn)
|
||||
|
||||
model.sha_attribute(:name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the column does not exist' do
|
||||
it 'raises ArgumentError' do
|
||||
it 'allows the attribute to be added and issues a warning' do
|
||||
allow(model).to receive(:table_exists?).and_return(true)
|
||||
|
||||
expect(model).to receive(:columns)
|
||||
expect(model).not_to receive(:attribute)
|
||||
expect(model).to receive(:attribute)
|
||||
expect(model).to receive(:warn)
|
||||
|
||||
expect { model.sha_attribute(:no_name) }.to raise_error(ArgumentError)
|
||||
model.sha_attribute(:no_name)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -9,8 +9,13 @@ module KubernetesHelpers
|
|||
kube_response(kube_pods_body)
|
||||
end
|
||||
|
||||
def kube_deployments_response
|
||||
kube_response(kube_deployments_body)
|
||||
end
|
||||
|
||||
def stub_kubeclient_discover(api_url)
|
||||
WebMock.stub_request(:get, api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body))
|
||||
WebMock.stub_request(:get, api_url + '/apis/extensions/v1beta1').to_return(kube_response(kube_v1beta1_discovery_body))
|
||||
end
|
||||
|
||||
def stub_kubeclient_pods(response = nil)
|
||||
|
@ -20,6 +25,13 @@ module KubernetesHelpers
|
|||
WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response)
|
||||
end
|
||||
|
||||
def stub_kubeclient_deployments(response = nil)
|
||||
stub_kubeclient_discover(service.api_url)
|
||||
deployments_url = service.api_url + "/apis/extensions/v1beta1/namespaces/#{service.actual_namespace}/deployments"
|
||||
|
||||
WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response)
|
||||
end
|
||||
|
||||
def stub_kubeclient_get_secrets(api_url, **options)
|
||||
WebMock.stub_request(:get, api_url + '/api/v1/secrets')
|
||||
.to_return(kube_response(kube_v1_secrets_body(options)))
|
||||
|
@ -53,6 +65,18 @@ module KubernetesHelpers
|
|||
"kind" => "APIResourceList",
|
||||
"resources" => [
|
||||
{ "name" => "pods", "namespaced" => true, "kind" => "Pod" },
|
||||
{ "name" => "deployments", "namespaced" => true, "kind" => "Deployment" },
|
||||
{ "name" => "secrets", "namespaced" => true, "kind" => "Secret" }
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def kube_v1beta1_discovery_body
|
||||
{
|
||||
"kind" => "APIResourceList",
|
||||
"resources" => [
|
||||
{ "name" => "pods", "namespaced" => true, "kind" => "Pod" },
|
||||
{ "name" => "deployments", "namespaced" => true, "kind" => "Deployment" },
|
||||
{ "name" => "secrets", "namespaced" => true, "kind" => "Secret" }
|
||||
]
|
||||
}
|
||||
|
@ -65,14 +89,25 @@ module KubernetesHelpers
|
|||
}
|
||||
end
|
||||
|
||||
def kube_deployments_body
|
||||
{
|
||||
"kind" => "DeploymentList",
|
||||
"items" => [kube_deployment]
|
||||
}
|
||||
end
|
||||
|
||||
# This is a partial response, it will have many more elements in reality but
|
||||
# these are the ones we care about at the moment
|
||||
def kube_pod(name: "kube-pod", app: "valid-pod-label")
|
||||
def kube_pod(name: "kube-pod", app: "valid-pod-label", status: "Running", track: nil)
|
||||
{
|
||||
"metadata" => {
|
||||
"name" => name,
|
||||
"generate_name" => "generated-name-with-suffix",
|
||||
"creationTimestamp" => "2016-11-25T19:55:19Z",
|
||||
"labels" => { "app" => app }
|
||||
"labels" => {
|
||||
"app" => app,
|
||||
"track" => track
|
||||
}
|
||||
},
|
||||
"spec" => {
|
||||
"containers" => [
|
||||
|
@ -80,7 +115,27 @@ module KubernetesHelpers
|
|||
{ "name" => "container-1" }
|
||||
]
|
||||
},
|
||||
"status" => { "phase" => "Running" }
|
||||
"status" => { "phase" => status }
|
||||
}
|
||||
end
|
||||
|
||||
def kube_deployment(name: "kube-deployment", app: "valid-deployment-label", track: nil)
|
||||
{
|
||||
"metadata" => {
|
||||
"name" => name,
|
||||
"generation" => 4,
|
||||
"labels" => {
|
||||
"app" => app,
|
||||
"track" => track
|
||||
}.compact
|
||||
},
|
||||
"spec" => { "replicas" => 3 },
|
||||
"status" => {
|
||||
"observedGeneration" => 4,
|
||||
"replicas" => 3,
|
||||
"updatedReplicas" => 3,
|
||||
"availableReplicas" => 3
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -101,4 +156,12 @@ module KubernetesHelpers
|
|||
terminal
|
||||
end
|
||||
end
|
||||
|
||||
def kube_deployment_rollout_status
|
||||
::Gitlab::Kubernetes::RolloutStatus.from_deployments(kube_deployment)
|
||||
end
|
||||
|
||||
def empty_deployment_rollout_status
|
||||
::Gitlab::Kubernetes::RolloutStatus.from_deployments()
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue