Commit 15cdc8bf authored by Clement Ho's avatar Clement Ho

Merge branch 'master' into 'bootstrap4'

# Conflicts:
#   app/views/groups/group_members/index.html.haml
parents 43f83ba7 8c2b73dc
...@@ -6,7 +6,7 @@ end ...@@ -6,7 +6,7 @@ end
gem_versions = {} gem_versions = {}
gem_versions['activerecord_sane_schema_dumper'] = rails5? ? '1.0' : '0.2' 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['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' gem_versions['rails-i18n'] = rails5? ? '~> 5.1' : '~> 4.0.9'
# --- The end of special code for migrating to Rails 5.0 --- # --- The end of special code for migrating to Rails 5.0 ---
......
...@@ -4,43 +4,43 @@ GEM ...@@ -4,43 +4,43 @@ GEM
RedCloth (4.3.2) RedCloth (4.3.2)
abstract_type (0.0.7) abstract_type (0.0.7)
ace-rails-ap (4.1.4) ace-rails-ap (4.1.4)
actioncable (5.0.6) actioncable (5.0.7)
actionpack (= 5.0.6) actionpack (= 5.0.7)
nio4r (>= 1.2, < 3.0) nio4r (>= 1.2, < 3.0)
websocket-driver (~> 0.6.1) websocket-driver (~> 0.6.1)
actionmailer (5.0.6) actionmailer (5.0.7)
actionpack (= 5.0.6) actionpack (= 5.0.7)
actionview (= 5.0.6) actionview (= 5.0.7)
activejob (= 5.0.6) activejob (= 5.0.7)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (5.0.6) actionpack (5.0.7)
actionview (= 5.0.6) actionview (= 5.0.7)
activesupport (= 5.0.6) activesupport (= 5.0.7)
rack (~> 2.0) rack (~> 2.0)
rack-test (~> 0.6.3) rack-test (~> 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.0.6) actionview (5.0.7)
activesupport (= 5.0.6) activesupport (= 5.0.7)
builder (~> 3.1) builder (~> 3.1)
erubis (~> 2.7.0) erubis (~> 2.7.0)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3) rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (5.0.6) activejob (5.0.7)
activesupport (= 5.0.6) activesupport (= 5.0.7)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (5.0.6) activemodel (5.0.7)
activesupport (= 5.0.6) activesupport (= 5.0.7)
activerecord (5.0.6) activerecord (5.0.7)
activemodel (= 5.0.6) activemodel (= 5.0.7)
activesupport (= 5.0.6) activesupport (= 5.0.7)
arel (~> 7.0) arel (~> 7.0)
activerecord_sane_schema_dumper (1.0) activerecord_sane_schema_dumper (1.0)
rails (>= 5, < 6) rails (>= 5, < 6)
activesupport (5.0.6) activesupport (5.0.7)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (~> 0.7) i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
tzinfo (~> 1.1) tzinfo (~> 1.1)
acts-as-taggable-on (5.0.0) acts-as-taggable-on (5.0.0)
...@@ -62,13 +62,13 @@ GEM ...@@ -62,13 +62,13 @@ GEM
asciidoctor (1.5.6.1) asciidoctor (1.5.6.1)
asciidoctor-plantuml (0.0.8) asciidoctor-plantuml (0.0.8)
asciidoctor (~> 1.5) asciidoctor (~> 1.5)
asset_sync (2.2.0) asset_sync (2.4.0)
activemodel (>= 4.1.0) activemodel (>= 4.1.0)
fog-core fog-core
mime-types (>= 2.99) mime-types (>= 2.99)
unf unf
ast (2.4.0) ast (2.4.0)
atomic (1.1.100) atomic (1.1.99)
attr_encrypted (3.1.0) attr_encrypted (3.1.0)
encryptor (~> 3.0.0) encryptor (~> 3.0.0)
attr_required (1.0.1) attr_required (1.0.1)
...@@ -144,12 +144,10 @@ GEM ...@@ -144,12 +144,10 @@ GEM
connection_pool (2.2.1) connection_pool (2.2.1)
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
crass (1.0.3) crass (1.0.4)
creole (0.5.0) creole (0.5.0)
css_parser (1.6.0) css_parser (1.6.0)
addressable addressable
d3_rails (3.5.17)
railties (>= 3.1.0)
daemons (1.2.6) daemons (1.2.6)
database_cleaner (1.5.3) database_cleaner (1.5.3)
debug_inspector (0.0.3) debug_inspector (0.0.3)
...@@ -292,7 +290,7 @@ GEM ...@@ -292,7 +290,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly-proto (0.97.0) gitaly-proto (0.99.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.10) grpc (~> 1.10)
github-linguist (5.3.3) github-linguist (5.3.3)
...@@ -335,9 +333,8 @@ GEM ...@@ -335,9 +333,8 @@ GEM
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
gollum-grit_adapter (1.0.1) gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1) gitlab-grit (~> 2.7, >= 2.7.1)
gon (6.1.0) gon (6.2.0)
actionpack (>= 3.0) actionpack (>= 3.0)
json
multi_json multi_json
request_store (>= 1.0) request_store (>= 1.0)
google-api-client (0.19.8) google-api-client (0.19.8)
...@@ -367,8 +364,8 @@ GEM ...@@ -367,8 +364,8 @@ GEM
rack (>= 1.3.0) rack (>= 1.3.0)
rack-accept rack-accept
virtus (>= 1.0.0) virtus (>= 1.0.0)
grape-entity (0.6.1) grape-entity (0.7.1)
activesupport (>= 5.0.0) activesupport (>= 4.0)
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
grape-route-helpers (2.1.0) grape-route-helpers (2.1.0)
activesupport activesupport
...@@ -420,7 +417,7 @@ GEM ...@@ -420,7 +417,7 @@ GEM
json (~> 1.8) json (~> 1.8)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpclient (2.8.3) httpclient (2.8.3)
i18n (0.9.5) i18n (1.0.1)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
ice_nine (0.11.2) ice_nine (0.11.2)
influxdb (0.5.3) influxdb (0.5.3)
...@@ -515,7 +512,7 @@ GEM ...@@ -515,7 +512,7 @@ GEM
net-ldap (0.16.1) net-ldap (0.16.1)
net-ssh (4.2.0) net-ssh (4.2.0)
netrc (0.11.0) netrc (0.11.0)
nio4r (2.2.0) nio4r (2.3.1)
nokogiri (1.8.2) nokogiri (1.8.2)
mini_portile2 (~> 2.3.0) mini_portile2 (~> 2.3.0)
numerizer (0.1.1) numerizer (0.1.1)
...@@ -545,9 +542,9 @@ GEM ...@@ -545,9 +542,9 @@ GEM
omniauth (~> 1.2) omniauth (~> 1.2)
omniauth-facebook (4.0.0) omniauth-facebook (4.0.0)
omniauth-oauth2 (~> 1.2) omniauth-oauth2 (~> 1.2)
omniauth-github (1.1.2) omniauth-github (1.3.0)
omniauth (~> 1.0) omniauth (~> 1.5)
omniauth-oauth2 (~> 1.1) omniauth-oauth2 (>= 1.4.0, < 2.0)
omniauth-gitlab (1.0.3) omniauth-gitlab (1.0.3)
omniauth (~> 1.0) omniauth (~> 1.0)
omniauth-oauth2 (~> 1.0) omniauth-oauth2 (~> 1.0)
...@@ -633,7 +630,7 @@ GEM ...@@ -633,7 +630,7 @@ GEM
parser parser
unparser unparser
procto (0.0.3) procto (0.0.3)
prometheus-client-mmap (0.9.1) prometheus-client-mmap (0.9.2)
pry (0.11.3) pry (0.11.3)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.9.0) method_source (~> 0.9.0)
...@@ -644,7 +641,7 @@ GEM ...@@ -644,7 +641,7 @@ GEM
pry (>= 0.10.4) pry (>= 0.10.4)
public_suffix (3.0.2) public_suffix (3.0.2)
pyu-ruby-sasl (0.0.3.3) pyu-ruby-sasl (0.0.3.3)
rack (2.0.4) rack (2.0.5)
rack-accept (0.4.5) rack-accept (0.4.5)
rack (>= 0.4) rack (>= 0.4)
rack-attack (4.4.1) rack-attack (4.4.1)
...@@ -662,17 +659,17 @@ GEM ...@@ -662,17 +659,17 @@ GEM
rack rack
rack-test (0.6.3) rack-test (0.6.3)
rack (>= 1.0) rack (>= 1.0)
rails (5.0.6) rails (5.0.7)
actioncable (= 5.0.6) actioncable (= 5.0.7)
actionmailer (= 5.0.6) actionmailer (= 5.0.7)
actionpack (= 5.0.6) actionpack (= 5.0.7)
actionview (= 5.0.6) actionview (= 5.0.7)
activejob (= 5.0.6) activejob (= 5.0.7)
activemodel (= 5.0.6) activemodel (= 5.0.7)
activerecord (= 5.0.6) activerecord (= 5.0.7)
activesupport (= 5.0.6) activesupport (= 5.0.7)
bundler (>= 1.3.0) bundler (>= 1.3.0)
railties (= 5.0.6) railties (= 5.0.7)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.2) rails-controller-testing (1.0.2)
actionpack (~> 5.x, >= 5.0.1) actionpack (~> 5.x, >= 5.0.1)
...@@ -683,21 +680,21 @@ GEM ...@@ -683,21 +680,21 @@ GEM
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.0.3) rails-html-sanitizer (1.0.4)
loofah (~> 2.0) loofah (~> 2.2, >= 2.2.2)
rails-i18n (5.1.1) rails-i18n (5.1.1)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 5.0, < 6) railties (>= 5.0, < 6)
railties (5.0.6) railties (5.0.7)
actionpack (= 5.0.6) actionpack (= 5.0.7)
activesupport (= 5.0.6) activesupport (= 5.0.7)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.2.2) rainbow (2.2.2)
rake rake
raindrops (0.19.0) raindrops (0.19.0)
rake (12.3.0) rake (12.3.1)
rb-fsevent (0.10.3) rb-fsevent (0.10.3)
rb-inotify (0.9.10) rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2) ffi (>= 0.5.0, < 2)
...@@ -1001,7 +998,7 @@ DEPENDENCIES ...@@ -1001,7 +998,7 @@ DEPENDENCIES
asana (~> 0.6.0) asana (~> 0.6.0)
asciidoctor (~> 1.5.6) asciidoctor (~> 1.5.6)
asciidoctor-plantuml (= 0.0.8) asciidoctor-plantuml (= 0.0.8)
asset_sync (~> 2.2.0) asset_sync (~> 2.4)
attr_encrypted (~> 3.1.0) attr_encrypted (~> 3.1.0)
awesome_print (~> 1.2.0) awesome_print (~> 1.2.0)
babosa (~> 1.0.2) babosa (~> 1.0.2)
...@@ -1027,12 +1024,11 @@ DEPENDENCIES ...@@ -1027,12 +1024,11 @@ DEPENDENCIES
concurrent-ruby (~> 1.0.5) concurrent-ruby (~> 1.0.5)
connection_pool (~> 2.0) connection_pool (~> 2.0)
creole (~> 0.5.0) creole (~> 0.5.0)
d3_rails (~> 3.5.0)
database_cleaner (~> 1.5.0) database_cleaner (~> 1.5.0)
deckar01-task_list (= 2.0.0) deckar01-task_list (= 2.0.0)
default_value_for (~> 3.0.5) default_value_for (~> 3.0.5)
device_detector device_detector
devise (~> 4.2) devise (~> 4.4)
devise-two-factor (~> 3.0.0) devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0) diffy (~> 3.1.0)
doorkeeper (~> 4.3) doorkeeper (~> 4.3)
...@@ -1063,7 +1059,7 @@ DEPENDENCIES ...@@ -1063,7 +1059,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.97.0) gitaly-proto (~> 0.99.0)
github-linguist (~> 5.3.3) github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2) gitlab-gollum-lib (~> 4.2)
...@@ -1071,12 +1067,12 @@ DEPENDENCIES ...@@ -1071,12 +1067,12 @@ DEPENDENCIES
gitlab-markup (~> 1.6.2) gitlab-markup (~> 1.6.2)
gitlab-styles (~> 2.3) gitlab-styles (~> 2.3)
gitlab_omniauth-ldap (~> 2.0.4) gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.1.0) gon (~> 6.2)
google-api-client (~> 0.19.8) google-api-client (~> 0.19.8)
google-protobuf (= 3.5.1) google-protobuf (= 3.5.1)
gpgme gpgme
grape (~> 1.0) grape (~> 1.0)
grape-entity (~> 0.6.0) grape-entity (~> 0.7.1)
grape-route-helpers (~> 2.1.0) grape-route-helpers (~> 2.1.0)
grape_logging (~> 1.7) grape_logging (~> 1.7)
grpc (~> 1.11.0) grpc (~> 1.11.0)
...@@ -1117,7 +1113,7 @@ DEPENDENCIES ...@@ -1117,7 +1113,7 @@ DEPENDENCIES
omniauth-azure-oauth2 (~> 0.0.9) omniauth-azure-oauth2 (~> 0.0.9)
omniauth-cas3 (~> 1.1.4) omniauth-cas3 (~> 1.1.4)
omniauth-facebook (~> 4.0.0) omniauth-facebook (~> 4.0.0)
omniauth-github (~> 1.1.1) omniauth-github (~> 1.3)
omniauth-gitlab (~> 1.0.2) omniauth-gitlab (~> 1.0.2)
omniauth-google-oauth2 (~> 0.5.3) omniauth-google-oauth2 (~> 0.5.3)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
...@@ -1136,14 +1132,14 @@ DEPENDENCIES ...@@ -1136,14 +1132,14 @@ DEPENDENCIES
peek-sidekiq (~> 1.0.3) peek-sidekiq (~> 1.0.3)
pg (~> 0.18.2) pg (~> 0.18.2)
premailer-rails (~> 1.9.7) premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.9.1) prometheus-client-mmap (~> 0.9.2)
pry-byebug (~> 3.4.1) pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4) pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1) rack-attack (~> 4.4.1)
rack-cors (~> 1.0.0) rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.2.1) rack-oauth2 (~> 1.2.1)
rack-proxy (~> 0.6.0) rack-proxy (~> 0.6.0)
rails (= 5.0.6) rails (= 5.0.7)
rails-controller-testing rails-controller-testing
rails-deprecated_sanitizer (~> 1.0.3) rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 5.1) rails-i18n (~> 5.1)
......
...@@ -140,7 +140,7 @@ export default { ...@@ -140,7 +140,7 @@ export default {
this.file.staged && this.file.key.indexOf('unstaged-') === 0 ? head : null, 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); this.editor.attachMergeRequestModel(this.model);
} else { } else {
this.editor.attachModel(this.model); this.editor.attachModel(this.model);
......
...@@ -44,6 +44,7 @@ export const dataStructure = () => ({ ...@@ -44,6 +44,7 @@ export const dataStructure = () => ({
size: 0, size: 0,
parentPath: null, parentPath: null,
lastOpenedAt: 0, lastOpenedAt: 0,
mrChange: null,
}); });
export const decorateData = entity => { export const decorateData = entity => {
......
...@@ -111,7 +111,13 @@ ...@@ -111,7 +111,13 @@
</td> </td>
<td> <td>
<span
v-tooltip
:title="tooltipTitle(item.createdAt)"
data-placement="bottom"
>
{{ timeFormated(item.createdAt) }} {{ timeFormated(item.createdAt) }}
</span>
</td> </td>
<td class="content"> <td class="content">
......
...@@ -15,7 +15,7 @@ export default class UserCallout { ...@@ -15,7 +15,7 @@ export default class UserCallout {
init() { init() {
if (!this.isCalloutDismissed || this.isCalloutDismissed === 'false') { 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 { ...@@ -23,12 +23,15 @@ export default class UserCallout {
const $currentTarget = $(e.currentTarget); const $currentTarget = $(e.currentTarget);
if (this.options.setCalloutPerProject) { 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 { } else {
Cookies.set(this.cookieName, 'true', { expires: 365 }); Cookies.set(this.cookieName, 'true', { expires: 365 });
} }
if ($currentTarget.hasClass('close')) { if ($currentTarget.hasClass('close') || $currentTarget.hasClass('js-close')) {
this.userCalloutBody.remove(); this.userCalloutBody.remove();
} }
} }
......
...@@ -109,12 +109,12 @@ export default { ...@@ -109,12 +109,12 @@ export default {
rel="noopener noreferrer nofollow" rel="noopener noreferrer nofollow"
class="deploy-link js-deploy-url" class="deploy-link js-deploy-url"
> >
{{ deployment.external_url_formatted }}
<i <i
class="fa fa-external-link" class="fa fa-external-link"
aria-hidden="true" aria-hidden="true"
> >
</i> </i>
{{ deployment.external_url_formatted }}
</a> </a>
</template> </template>
<span <span
......
...@@ -17,6 +17,16 @@ ...@@ -17,6 +17,16 @@
*/ */
@mixin markdown-table { @mixin markdown-table {
width: auto; 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 @@ ...@@ -17,6 +17,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
line-height: $line-height-base;
.title { .title {
display: flex; display: flex;
...@@ -33,10 +34,14 @@ ...@@ -33,10 +34,14 @@
.navbar-collapse { .navbar-collapse {
padding-right: 0; padding-right: 0;
.navbar-nav {
margin: 0;
}
} }
.nav li a { .nav li {
color: $theme-gray-700; float: none;
} }
} }
......
...@@ -180,11 +180,6 @@ ul.wiki-pages-list.content-list { ...@@ -180,11 +180,6 @@ ul.wiki-pages-list.content-list {
} }
} }
.wiki-holder {
overflow-x: auto;
overflow-y: hidden;
}
.wiki { .wiki {
table { table {
@include markdown-table; @include markdown-table;
......
...@@ -11,13 +11,20 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -11,13 +11,20 @@ class Groups::GroupMembersController < Groups::ApplicationController
:override :override
def index def index
can_manage_members = can?(current_user, :admin_group_member, @group)
@sort = params[:sort].presence || sort_value_name @sort = params[:sort].presence || sort_value_name
@project = @group.projects.find(params[:project_id]) if params[:project_id] @project = @group.projects.find(params[:project_id]) if params[:project_id]
@members = GroupMembersFinder.new(@group).execute @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.search(params[:search]) if params[:search].present?
@members = @members.sort_by_attribute(@sort) @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 = @members.page(params[:page]).per(50)
@members = present_members(@members.includes(:user)) @members = present_members(@members.includes(:user))
......
...@@ -108,7 +108,13 @@ module Ci ...@@ -108,7 +108,13 @@ module Ci
end end
def assign_to(project, current_user = nil) def assign_to(project, current_user = nil)
if shared?
self.is_shared = false 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 self.save
project.runner_projects.create(runner_id: self.id) project.runner_projects.create(runner_id: self.id)
end end
......
...@@ -4,7 +4,9 @@ module Routable ...@@ -4,7 +4,9 @@ module Routable
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do 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 has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
validates :route, presence: true validates :route, presence: true
......
...@@ -22,7 +22,8 @@ module ShaAttribute ...@@ -22,7 +22,8 @@ module ShaAttribute
column = columns.find { |c| c.name == name.to_s } column = columns.find { |c| c.name == name.to_s }
unless column 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 end
unless column.type == :binary unless column.type == :binary
......
...@@ -96,6 +96,17 @@ class Member < ActiveRecord::Base ...@@ -96,6 +96,17 @@ class Member < ActiveRecord::Base
joins(:user).merge(User.search(query)) joins(:user).merge(User.search(query))
end 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) def sort_by_attribute(method)
case method.to_s case method.to_s
when 'access_level_asc' then reorder(access_level: :asc) when 'access_level_asc' then reorder(access_level: :asc)
......
...@@ -237,14 +237,18 @@ class User < ActiveRecord::Base ...@@ -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_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')) } 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") 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 end
def self.without_two_factor def self.without_two_factor
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id") 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 end
# #
......
- page_title "Members" - page_title "Members"
- can_manage_members = can?(current_user, :admin_group_member, @group)
.project-members-page.prepend-top-default .project-members-page.prepend-top-default
%h4 %h4
Members Members
%hr %hr
- if can?(current_user, :admin_group_member, @group) - if can_manage_members
.project-members-new.append-bottom-default .project-members-new.append-bottom-default
%p.clearfix %p.clearfix
Add new member to Add new member to
...@@ -13,20 +14,23 @@ ...@@ -13,20 +14,23 @@
= render 'shared/members/requests', membership_source: @group, requesters: @requesters = render 'shared/members/requests', membership_source: @group, requesters: @requesters
.append-bottom-default.clearfix .clearfix
%h5.member.existing-title %h5.member.existing-title
Existing members Existing members
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do .card
.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 .form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } = 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" } %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
= icon("search") = icon("search")
- if can_manage_members
= render 'shared/members/filter_2fa_dropdown'
= render 'shared/members/sort_dropdown' = render 'shared/members/sort_dropdown'
.card
.card-header
Members with access to
%strong= @group.name
%span.badge.badge-pill= @members.total_count
%ul.content-list.members-list %ul.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member = render partial: 'shared/members/member', collection: @members, as: :member
= paginate @members, theme: 'gitlab' = paginate @members, theme: 'gitlab'
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
.top-area .top-area
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls .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') = icon('rss')
%span.icon-label %span.icon-label
Subscribe Subscribe
......
...@@ -20,10 +20,10 @@ ...@@ -20,10 +20,10 @@
= brand_header_logo = brand_header_logo
- logo_text = brand_header_logo_type - logo_text = brand_header_logo_type
- if logo_text.present? - if logo_text.present?
%span.logo-text.hidden-xs.prepend-left-8 %span.logo-text.prepend-left-8
= logo_text = logo_text
- if header_link?(:user_dropdown) - if header_link?(:user_dropdown)
.navbar-collapse.collapse .navbar-collapse
%ul.nav.navbar-nav %ul.nav.navbar-nav
%li.header-user.dropdown %li.header-user.dropdown
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
......
- if @wiki_home.present? - if @wiki_home.present?
%div{ class: container_class } %div{ class: container_class }
.wiki-holder.prepend-top-default.append-bottom-default .prepend-top-default.append-bottom-default
.wiki .wiki
= render_wiki_content(@wiki_home) = render_wiki_content(@wiki_home)
- else - else
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
- history_link = link_to s_("WikiHistoricalPage|history"), project_wiki_history_path(@project, @page) - 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 = (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 .wiki
= render_wiki_content(@page) = render_wiki_content(@page)
......
- 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 @@ ...@@ -20,6 +20,10 @@
%label.badge.badge-danger %label.badge.badge-danger
%strong Blocked %strong Blocked
- if user.two_factor_enabled?
%label.label.label-info
2FA
- if source.instance_of?(Group) && source != @group - if source.instance_of?(Group) && source != @group
&middot; &middot;
= link_to source.full_name, source, class: "member-group-link" = link_to source.full_name, source, class: "member-group-link"
......
---
title: Moves MR widget external link icon to the right
merge_request: 18828
author: Jacopo Beschi @jacopo-beschi
type: changed
---
title: 46210 Display logo and user dropdown on mobile for terms page and fix styling
merge_request:
author:
type: fixed
---
title: Expand documentation for Runners API
merge_request: 16484
author:
type: other
---
title: Add 2FA filter to the group members page
merge_request: 18483
author:
type: changed
---
title: 'Add missing tooltip to creation date on container registry overview'
merge_request: 18767
author: Lars Greiss
type: fixed
---
title: Finding a wiki page is done by Gitaly by default
merge_request:
author:
type: other
...@@ -26,17 +26,6 @@ def validate_storages_config ...@@ -26,17 +26,6 @@ def validate_storages_config
Gitlab.config.repositories.storages.each do |name, repository_storage| Gitlab.config.repositories.storages.each do |name, repository_storage|
storage_validation_error("\"#{name}\" is not a valid storage name") unless storage_name_valid?(name) 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| %w(failure_count_threshold failure_reset_time storage_timeout).each do |setting|
# Falling back to the defaults is fine! # Falling back to the defaults is fine!
next if repository_storage[setting].nil? next if repository_storage[setting].nil?
......
...@@ -411,3 +411,86 @@ DELETE /projects/:id/runners/:runner_id ...@@ -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" 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 ...@@ -129,8 +129,8 @@ You may see a temporary error message `SchedulerPredicates failed due to Persist
Add the GitLab Helm repository and initialize Helm: Add the GitLab Helm repository and initialize Helm:
```bash ```bash
helm repo add gitlab https://charts.gitlab.io
helm init 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. 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 ...@@ -67,7 +67,8 @@ module Gitlab
end end
def page(title:, version: nil, dir: nil) 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 if is_enabled
gitaly_find_page(title: title, version: version, dir: dir) gitaly_find_page(title: title, version: version, dir: dir)
else else
......
...@@ -5,6 +5,14 @@ module Gitlab ...@@ -5,6 +5,14 @@ module Gitlab
# directly. # directly.
class StorageSettings class StorageSettings
DirectPathAccessError = Class.new(StandardError) 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 # This class will give easily recognizable NoMethodErrors
Deprecated = Class.new Deprecated = Class.new
...@@ -12,7 +20,8 @@ module Gitlab ...@@ -12,7 +20,8 @@ module Gitlab
attr_reader :legacy_disk_path attr_reader :legacy_disk_path
def initialize(storage) 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. # 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'] @legacy_disk_path = File.expand_path(storage['path'], Rails.root) if storage['path']
......
...@@ -5,7 +5,7 @@ module Gitlab ...@@ -5,7 +5,7 @@ module Gitlab
def initialize(*collections, per_page: nil) def initialize(*collections, per_page: nil)
raise ArgumentError.new('Only 2 collections are supported') if collections.size != 2 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 @first_collection, @second_collection = collections
end end
......
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 ...@@ -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.') 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
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 end
describe 'validate_storages_paths' do describe 'validate_storages_paths' do
......
...@@ -24,7 +24,7 @@ describe('RepoEditor', () => { ...@@ -24,7 +24,7 @@ describe('RepoEditor', () => {
f.active = true; f.active = true;
f.tempFile = true; f.tempFile = true;
vm.$store.state.openFiles.push(f); 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.monaco = true;
vm.$mount(); vm.$mount();
...@@ -215,6 +215,30 @@ describe('RepoEditor', () => { ...@@ -215,6 +215,30 @@ describe('RepoEditor', () => {
expect(vm.editor.attachModel).toHaveBeenCalledWith(vm.model); 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', () => { it('adds callback methods', () => {
spyOn(vm.editor, 'onPositionChange').and.callThrough(); spyOn(vm.editor, 'onPositionChange').and.callThrough();
......
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 ...@@ -200,15 +200,29 @@ describe Ci::Runner do
describe '#assign_to' do describe '#assign_to' do
let!(:project) { FactoryBot.create(:project) } let!(:project) { FactoryBot.create(:project) }
let!(:shared_runner) { FactoryBot.create(:ci_runner, :shared) }
before do subject { runner.assign_to(project) }
shared_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
end
context 'with group runner' do
let!(:runner) { FactoryBot.create(:ci_runner, runner_type: :group_type) }
it { expect(shared_runner).to be_specific } it 'raises an error' do
it { expect(shared_runner.projects).to eq([project]) } expect { subject }
it { expect(shared_runner.only_for?(project)).to be_truthy } .to raise_error(ArgumentError, 'Transitioning a group runner to a project runner is not supported')
end
end
end end
describe '.online' do describe '.online' do
......
...@@ -55,13 +55,9 @@ describe Clusters::Applications::Runner do ...@@ -55,13 +55,9 @@ describe Clusters::Applications::Runner do
context 'without a runner' do context 'without a runner' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:cluster) { create(:cluster) } let(:cluster) { create(:cluster, projects: [project]) }
let(:gitlab_runner) { create(:clusters_applications_runner, cluster: cluster) } let(:gitlab_runner) { create(:clusters_applications_runner, cluster: cluster) }
before do
cluster.projects << project
end
it 'creates a runner' do it 'creates a runner' do
expect do expect do
subject subject
......
...@@ -239,17 +239,19 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching ...@@ -239,17 +239,19 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { is_expected.to be_nil } it { is_expected.to be_nil }
end end
context 'when kubernetes responds with valid pods' do context 'when kubernetes responds with valid pods and deployments' do
before do before do
stub_kubeclient_pods stub_kubeclient_pods
stub_kubeclient_deployments
end end
it { is_expected.to eq(pods: [kube_pod]) } it { is_expected.to include(pods: [kube_pod]) }
end end
context 'when kubernetes responds with 500s' do context 'when kubernetes responds with 500s' do
before do before do
stub_kubeclient_pods(status: 500) stub_kubeclient_pods(status: 500)
stub_kubeclient_deployments(status: 500)
end end
it { expect { subject }.to raise_error(Kubeclient::HttpError) } it { expect { subject }.to raise_error(Kubeclient::HttpError) }
...@@ -258,9 +260,10 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching ...@@ -258,9 +260,10 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
context 'when kubernetes responds with 404s' do context 'when kubernetes responds with 404s' do
before do before do
stub_kubeclient_pods(status: 404) stub_kubeclient_pods(status: 404)
stub_kubeclient_deployments(status: 404)
end end
it { is_expected.to eq(pods: []) } it { is_expected.to include(pods: []) }
end end
end end
end end
...@@ -36,24 +36,26 @@ describe ShaAttribute do ...@@ -36,24 +36,26 @@ describe ShaAttribute do
end end
context 'when the table does not exist' do 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) allow(model).to receive(:table_exists?).and_return(false)
expect(model).not_to receive(:columns) expect(model).not_to receive(:columns)
expect(model).to receive(:attribute) expect(model).to receive(:attribute)
expect(model).to receive(:warn)
model.sha_attribute(:name) model.sha_attribute(:name)
end end
end end
context 'when the column does not exist' do 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) allow(model).to receive(:table_exists?).and_return(true)
expect(model).to receive(:columns) 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
end end
......
...@@ -9,8 +9,13 @@ module KubernetesHelpers ...@@ -9,8 +9,13 @@ module KubernetesHelpers
kube_response(kube_pods_body) kube_response(kube_pods_body)
end end
def kube_deployments_response
kube_response(kube_deployments_body)
end
def stub_kubeclient_discover(api_url) 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 + '/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 end
def stub_kubeclient_pods(response = nil) def stub_kubeclient_pods(response = nil)
...@@ -20,6 +25,13 @@ module KubernetesHelpers ...@@ -20,6 +25,13 @@ module KubernetesHelpers
WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response) WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response)
end 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) def stub_kubeclient_get_secrets(api_url, **options)
WebMock.stub_request(:get, api_url + '/api/v1/secrets') WebMock.stub_request(:get, api_url + '/api/v1/secrets')
.to_return(kube_response(kube_v1_secrets_body(options))) .to_return(kube_response(kube_v1_secrets_body(options)))
...@@ -53,6 +65,18 @@ module KubernetesHelpers ...@@ -53,6 +65,18 @@ module KubernetesHelpers
"kind" => "APIResourceList", "kind" => "APIResourceList",
"resources" => [ "resources" => [
{ "name" => "pods", "namespaced" => true, "kind" => "Pod" }, { "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" } { "name" => "secrets", "namespaced" => true, "kind" => "Secret" }
] ]
} }
...@@ -65,14 +89,25 @@ module KubernetesHelpers ...@@ -65,14 +89,25 @@ module KubernetesHelpers
} }
end 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 # This is a partial response, it will have many more elements in reality but
# these are the ones we care about at the moment # 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" => { "metadata" => {
"name" => name, "name" => name,
"generate_name" => "generated-name-with-suffix",
"creationTimestamp" => "2016-11-25T19:55:19Z", "creationTimestamp" => "2016-11-25T19:55:19Z",
"labels" => { "app" => app } "labels" => {
"app" => app,
"track" => track
}
}, },
"spec" => { "spec" => {
"containers" => [ "containers" => [
...@@ -80,7 +115,27 @@ module KubernetesHelpers ...@@ -80,7 +115,27 @@ module KubernetesHelpers
{ "name" => "container-1" } { "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 end
...@@ -101,4 +156,12 @@ module KubernetesHelpers ...@@ -101,4 +156,12 @@ module KubernetesHelpers
terminal terminal
end end
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 end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment