summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es64
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es647
-rw-r--r--app/assets/javascripts/cycle_analytics/default_event_objects.js.es698
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js10
-rw-r--r--app/assets/javascripts/wikis.js.es66
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss7
-rw-r--r--app/controllers/application_controller.rb17
-rw-r--r--app/controllers/explore/application_controller.rb2
-rw-r--r--app/controllers/groups/group_members_controller.rb2
-rw-r--r--app/controllers/help_controller.rb2
-rw-r--r--app/controllers/koding_controller.rb2
-rw-r--r--app/controllers/projects/uploads_controller.rb4
-rw-r--r--app/controllers/search_controller.rb2
-rw-r--r--app/finders/group_members_finder.rb20
-rw-r--r--app/models/group.rb2
-rw-r--r--app/models/member.rb1
-rw-r--r--app/models/user.rb9
-rw-r--r--app/serializers/analytics_stage_entity.rb1
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml2
-rw-r--r--app/views/projects/environments/terminal.html.haml2
-rw-r--r--app/views/projects/new.html.haml5
-rw-r--r--app/views/shared/_visibility_level.html.haml11
-rw-r--r--app/views/shared/members/_member.html.haml7
-rw-r--r--changelogs/unreleased/1051-api-create-users-without-password.yml4
-rw-r--r--changelogs/unreleased/27336-add-environment-url-link-to-terminal-page.yml4
-rw-r--r--changelogs/unreleased/27991-success-with-warnings-caret.yml4
-rw-r--r--changelogs/unreleased/issue-newproj-layout.yml4
-rw-r--r--changelogs/unreleased/rs-warden-blocked-users.yml4
-rw-r--r--changelogs/unreleased/sh-add-index-to-ci-trigger-requests.yml4
-rw-r--r--db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb11
-rw-r--r--db/schema.rb4
-rw-r--r--doc/api/users.md5
-rw-r--r--lib/api/users.rb16
-rw-r--r--lib/gitlab/cycle_analytics/code_stage.rb4
-rw-r--r--lib/gitlab/cycle_analytics/issue_stage.rb4
-rw-r--r--lib/gitlab/cycle_analytics/plan_stage.rb4
-rw-r--r--lib/gitlab/cycle_analytics/production_stage.rb4
-rw-r--r--lib/gitlab/cycle_analytics/review_stage.rb4
-rw-r--r--lib/gitlab/cycle_analytics/staging_stage.rb4
-rw-r--r--lib/gitlab/cycle_analytics/test_stage.rb4
-rw-r--r--spec/controllers/projects/uploads_controller_spec.rb64
-rw-r--r--spec/factories/users.rb8
-rw-r--r--spec/features/environment_spec.rb16
-rw-r--r--spec/features/groups/members/list_spec.rb40
-rw-r--r--spec/features/login_spec.rb16
-rw-r--r--spec/finders/group_members_finder_spec.rb32
-rw-r--r--spec/models/member_spec.rb8
-rw-r--r--spec/requests/api/users_spec.rb12
48 files changed, 418 insertions, 129 deletions
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6
index c41c57c1dcd..f161eb23795 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6
@@ -97,7 +97,7 @@ $(() => {
}
this.isLoadingStage = true;
- cycleAnalyticsStore.setStageEvents([]);
+ cycleAnalyticsStore.setStageEvents([], stage);
cycleAnalyticsStore.setActiveStage(stage);
cycleAnalyticsService
@@ -107,7 +107,7 @@ $(() => {
})
.done((response) => {
this.isEmptyStage = !response.events.length;
- cycleAnalyticsStore.setStageEvents(response.events);
+ cycleAnalyticsStore.setStageEvents(response.events, stage);
})
.error(() => {
this.isEmptyStage = true;
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6
index be732971c7f..3efeb141008 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6
@@ -1,4 +1,8 @@
/* eslint-disable no-param-reassign */
+
+require('../lib/utils/text_utility');
+const DEFAULT_EVENT_OBJECTS = require('./default_event_objects');
+
((global) => {
global.cycleAnalytics = global.cycleAnalytics || {};
@@ -34,11 +38,12 @@
});
newData.stages.forEach((item) => {
- const stageName = item.title.toLowerCase();
+ const stageSlug = gl.text.dasherize(item.title.toLowerCase());
item.active = false;
- item.isUserAllowed = data.permissions[stageName];
- item.emptyStageText = EMPTY_STAGE_TEXTS[stageName];
- item.component = `stage-${stageName}-component`;
+ item.isUserAllowed = data.permissions[stageSlug];
+ item.emptyStageText = EMPTY_STAGE_TEXTS[stageSlug];
+ item.component = `stage-${stageSlug}-component`;
+ item.slug = stageSlug;
});
newData.analytics = data;
return newData;
@@ -58,31 +63,33 @@
this.deactivateAllStages();
stage.active = true;
},
- setStageEvents(events) {
- this.state.events = this.decorateEvents(events);
+ setStageEvents(events, stage) {
+ this.state.events = this.decorateEvents(events, stage);
},
- decorateEvents(events) {
+ decorateEvents(events, stage) {
const newEvents = [];
events.forEach((item) => {
if (!item) return;
- item.totalTime = item.total_time;
- item.author.webUrl = item.author.web_url;
- item.author.avatarUrl = item.author.avatar_url;
+ const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item);
+
+ eventItem.totalTime = eventItem.total_time;
+ eventItem.author.webUrl = eventItem.author.web_url;
+ eventItem.author.avatarUrl = eventItem.author.avatar_url;
- if (item.created_at) item.createdAt = item.created_at;
- if (item.short_sha) item.shortSha = item.short_sha;
- if (item.commit_url) item.commitUrl = item.commit_url;
+ if (eventItem.created_at) eventItem.createdAt = eventItem.created_at;
+ if (eventItem.short_sha) eventItem.shortSha = eventItem.short_sha;
+ if (eventItem.commit_url) eventItem.commitUrl = eventItem.commit_url;
- delete item.author.web_url;
- delete item.author.avatar_url;
- delete item.total_time;
- delete item.created_at;
- delete item.short_sha;
- delete item.commit_url;
+ delete eventItem.author.web_url;
+ delete eventItem.author.avatar_url;
+ delete eventItem.total_time;
+ delete eventItem.created_at;
+ delete eventItem.short_sha;
+ delete eventItem.commit_url;
- newEvents.push(item);
+ newEvents.push(eventItem);
});
return newEvents;
diff --git a/app/assets/javascripts/cycle_analytics/default_event_objects.js.es6 b/app/assets/javascripts/cycle_analytics/default_event_objects.js.es6
new file mode 100644
index 00000000000..cfaf9835bf8
--- /dev/null
+++ b/app/assets/javascripts/cycle_analytics/default_event_objects.js.es6
@@ -0,0 +1,98 @@
+module.exports = {
+ issue: {
+ created_at: '',
+ url: '',
+ iid: '',
+ title: '',
+ total_time: {},
+ author: {
+ avatar_url: '',
+ id: '',
+ name: '',
+ web_url: '',
+ },
+ },
+ plan: {
+ title: '',
+ commit_url: '',
+ short_sha: '',
+ total_time: {},
+ author: {
+ name: '',
+ id: '',
+ avatar_url: '',
+ web_url: '',
+ },
+ },
+ code: {
+ title: '',
+ iid: '',
+ created_at: '',
+ url: '',
+ total_time: {},
+ author: {
+ name: '',
+ id: '',
+ avatar_url: '',
+ web_url: '',
+ },
+ },
+ test: {
+ name: '',
+ id: '',
+ date: '',
+ url: '',
+ short_sha: '',
+ commit_url: '',
+ total_time: {},
+ branch: {
+ name: '',
+ url: '',
+ },
+ },
+ review: {
+ title: '',
+ iid: '',
+ created_at: '',
+ url: '',
+ state: '',
+ total_time: {},
+ author: {
+ name: '',
+ id: '',
+ avatar_url: '',
+ web_url: '',
+ },
+ },
+ staging: {
+ id: '',
+ short_sha: '',
+ date: '',
+ url: '',
+ commit_url: '',
+ total_time: {},
+ author: {
+ name: '',
+ id: '',
+ avatar_url: '',
+ web_url: '',
+ },
+ branch: {
+ name: '',
+ url: '',
+ },
+ },
+ production: {
+ title: '',
+ created_at: '',
+ url: '',
+ iid: '',
+ total_time: {},
+ author: {
+ name: '',
+ id: '',
+ avatar_url: '',
+ web_url: '',
+ },
+ },
+};
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index d9370db0cf2..326b7cb7f57 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -1,5 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len */
+require('vendor/latinise');
+
(function() {
(function(w) {
var base;
@@ -164,8 +166,14 @@
gl.text.pluralize = function(str, count) {
return str + (count > 1 || count === 0 ? 's' : '');
};
- return gl.text.truncate = function(string, maxLength) {
+ gl.text.truncate = function(string, maxLength) {
return string.substr(0, (maxLength - 3)) + '...';
};
+ gl.text.dasherize = function(str) {
+ return str.replace(/[_\s]+/g, '-');
+ };
+ gl.text.slugify = function(str) {
+ return str.trim().toLowerCase().latinise();
+ };
})(window);
}).call(this);
diff --git a/app/assets/javascripts/wikis.js.es6 b/app/assets/javascripts/wikis.js.es6
index ef99b2e92f0..75fd1394a03 100644
--- a/app/assets/javascripts/wikis.js.es6
+++ b/app/assets/javascripts/wikis.js.es6
@@ -1,14 +1,10 @@
/* eslint-disable no-param-reassign */
/* global Breakpoints */
-require('vendor/latinise');
require('./breakpoints');
require('vendor/jquery.nicescroll');
((global) => {
- const dasherize = str => str.replace(/[_\s]+/g, '-');
- const slugify = str => dasherize(str.trim().toLowerCase().latinise());
-
class Wikis {
constructor() {
this.bp = Breakpoints.get();
@@ -34,7 +30,7 @@ require('vendor/jquery.nicescroll');
if (!this.newWikiForm) return;
const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
- const slug = slugify(slugInput.value);
+ const slug = gl.text.slugify(slugInput.value);
if (slug.length > 0) {
const wikisPath = slugInput.getAttribute('data-wikis-path');
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 692142c5887..1431673027f 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -96,13 +96,6 @@
padding-right: 4px;
}
- &.ci-success_with_warnings {
-
- i {
- color: $gl-warning;
- }
- }
-
@media (max-width: $screen-xs-max) {
flex-wrap: wrap;
}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index bb47e2a8bf7..bf6be3d516b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -12,7 +12,6 @@ class ApplicationController < ActionController::Base
before_action :authenticate_user_from_private_token!
before_action :authenticate_user!
before_action :validate_user_service_ticket!
- before_action :reject_blocked!
before_action :check_password_expiration
before_action :check_2fa_requirement
before_action :ldap_security_check
@@ -87,22 +86,8 @@ class ApplicationController < ActionController::Base
logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"
end
- def reject_blocked!
- if current_user && current_user.blocked?
- sign_out current_user
- flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it."
- redirect_to new_user_session_path
- end
- end
-
def after_sign_in_path_for(resource)
- if resource.is_a?(User) && resource.respond_to?(:blocked?) && resource.blocked?
- sign_out resource
- flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it."
- new_user_session_path
- else
- stored_location_for(:redirect) || stored_location_for(resource) || root_path
- end
+ stored_location_for(:redirect) || stored_location_for(resource) || root_path
end
def after_sign_out_path_for(resource)
diff --git a/app/controllers/explore/application_controller.rb b/app/controllers/explore/application_controller.rb
index a1ab8b99048..baf54520b9c 100644
--- a/app/controllers/explore/application_controller.rb
+++ b/app/controllers/explore/application_controller.rb
@@ -1,5 +1,5 @@
class Explore::ApplicationController < ApplicationController
- skip_before_action :authenticate_user!, :reject_blocked!
+ skip_before_action :authenticate_user!
layout 'explore'
end
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 4f273a8d4f0..0cbf3eb58a3 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -9,7 +9,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
@sort = params[:sort].presence || sort_value_name
@project = @group.projects.find(params[:project_id]) if params[:project_id]
- @members = @group.group_members
+ @members = GroupMembersFinder.new(@group).execute
@members = @members.non_invite unless can?(current_user, :admin_group, @group)
@members = @members.search(params[:search]) if params[:search].present?
@members = @members.sort(@sort)
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index 37feff79999..87c0f8905ff 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -1,5 +1,5 @@
class HelpController < ApplicationController
- skip_before_action :authenticate_user!, :reject_blocked!
+ skip_before_action :authenticate_user!
layout 'help'
diff --git a/app/controllers/koding_controller.rb b/app/controllers/koding_controller.rb
index f3759b4c0ea..6b1e64ce819 100644
--- a/app/controllers/koding_controller.rb
+++ b/app/controllers/koding_controller.rb
@@ -1,5 +1,5 @@
class KodingController < ApplicationController
- before_action :check_integration!, :authenticate_user!, :reject_blocked!
+ before_action :check_integration!
layout 'koding'
def index
diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb
index 50ba33ed570..61686499bd3 100644
--- a/app/controllers/projects/uploads_controller.rb
+++ b/app/controllers/projects/uploads_controller.rb
@@ -1,6 +1,6 @@
class Projects::UploadsController < Projects::ApplicationController
- skip_before_action :reject_blocked!, :project,
- :repository, if: -> { action_name == 'show' && image_or_video? }
+ skip_before_action :project, :repository,
+ if: -> { action_name == 'show' && image_or_video? }
before_action :authorize_upload_file!, only: [:create]
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 6576ebd5235..612d69cf557 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -1,5 +1,5 @@
class SearchController < ApplicationController
- skip_before_action :authenticate_user!, :reject_blocked!
+ skip_before_action :authenticate_user!
include SearchHelper
diff --git a/app/finders/group_members_finder.rb b/app/finders/group_members_finder.rb
new file mode 100644
index 00000000000..9f2206346ce
--- /dev/null
+++ b/app/finders/group_members_finder.rb
@@ -0,0 +1,20 @@
+class GroupMembersFinder < Projects::ApplicationController
+ def initialize(group)
+ @group = group
+ end
+
+ def execute
+ group_members = @group.members
+
+ return group_members unless @group.parent
+
+ parents_members = GroupMember.non_request.
+ where(source_id: @group.ancestors.select(:id)).
+ where.not(user_id: @group.users.select(:id))
+
+ wheres = ["members.id IN (#{group_members.select(:id).to_sql})"]
+ wheres << "members.id IN (#{parents_members.select(:id).to_sql})"
+
+ GroupMember.where(wheres.join(' OR '))
+ end
+end
diff --git a/app/models/group.rb b/app/models/group.rb
index a5b92283daa..cc6624ff4aa 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -206,7 +206,7 @@ class Group < Namespace
end
def members_with_parents
- GroupMember.where(requested_at: nil, source_id: ancestors.map(&:id).push(id))
+ GroupMember.non_request.where(source_id: ancestors.map(&:id).push(id))
end
def users_with_parents
diff --git a/app/models/member.rb b/app/models/member.rb
index 26a6054e00d..d07f270b757 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -47,6 +47,7 @@ class Member < ActiveRecord::Base
scope :invite, -> { where.not(invite_token: nil) }
scope :non_invite, -> { where(invite_token: nil) }
scope :request, -> { where.not(requested_at: nil) }
+ scope :non_request, -> { where(requested_at: nil) }
scope :has_access, -> { active.where('access_level > 0') }
diff --git a/app/models/user.rb b/app/models/user.rb
index 33666b4f35b..f9245cdb82a 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -167,6 +167,15 @@ class User < ActiveRecord::Base
def blocked?
true
end
+
+ def active_for_authentication?
+ false
+ end
+
+ def inactive_message
+ "Your account has been blocked. Please contact your GitLab " \
+ "administrator if you think this is an error."
+ end
end
end
diff --git a/app/serializers/analytics_stage_entity.rb b/app/serializers/analytics_stage_entity.rb
index a559d0850c4..69bf693de8d 100644
--- a/app/serializers/analytics_stage_entity.rb
+++ b/app/serializers/analytics_stage_entity.rb
@@ -2,6 +2,7 @@ class AnalyticsStageEntity < Grape::Entity
include EntityDateHelper
expose :title
+ expose :legend
expose :description
expose :median, as: :value do |stage|
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index 5405ff16bea..ad904a8708e 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -44,7 +44,7 @@
Last 90 days
.stage-panel-container
.panel.panel-default.stage-panel
- .panel-heading
+ .panel-heading
%nav.col-headers
%ul
%li.stage-header
diff --git a/app/views/projects/environments/terminal.html.haml b/app/views/projects/environments/terminal.html.haml
index 1d49e9cbaf7..ef0dd0eda3c 100644
--- a/app/views/projects/environments/terminal.html.haml
+++ b/app/views/projects/environments/terminal.html.haml
@@ -16,6 +16,8 @@
.col-sm-6
.nav-controls
+ = link_to @environment.external_url, class: 'btn btn-default' do
+ = icon('external-link')
= render 'projects/deployments/actions', deployment: @environment.last_deployment
.terminal-container{ class: container_class }
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index cd685f7d0eb..41473fae4de 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -94,9 +94,8 @@
.form-group.project-visibility-level-holder
= f.label :visibility_level, class: 'label-light' do
Visibility Level
- = link_to "(?)", help_page_path("public_access/public_access")
- = render 'shared/visibility_level', f: f, visibility_level: default_project_visibility, can_change_visibility_level: true, form_model: @project
-
+ = link_to icon('question-circle'), help_page_path("public_access/public_access")
+ = render 'shared/visibility_level', f: f, visibility_level: default_project_visibility, can_change_visibility_level: true, form_model: @project, with_label: false
= f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
= link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel'
diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml
index b11257ee0e6..73efec88bb1 100644
--- a/app/views/shared/_visibility_level.html.haml
+++ b/app/views/shared/_visibility_level.html.haml
@@ -1,8 +1,11 @@
+- with_label = local_assigns.fetch(:with_label, true)
+
.form-group.project-visibility-level-holder
- = f.label :visibility_level, class: 'control-label' do
- Visibility Level
- = link_to icon('question-circle'), help_page_path("public_access/public_access")
- .col-sm-10
+ - if with_label
+ = f.label :visibility_level, class: 'control-label' do
+ Visibility Level
+ = link_to icon('question-circle'), help_page_path("public_access/public_access")
+ %div{ :class => ("col-sm-10" if with_label) }
- if can_change_visibility_level
= render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
- else
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index 659d4c905fc..239387fc9fa 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -19,9 +19,9 @@
%label.label.label-danger
%strong Blocked
- - if source.instance_of?(Group) && !@group
+ - if source.instance_of?(Group) && source != @group
&middot;
- = link_to source.name, source, class: "member-group-link"
+ = link_to source.full_name, source, class: "member-group-link"
.hidden-xs.cgray
- if member.request?
@@ -44,8 +44,9 @@
= link_to member.created_by.name, user_path(member.created_by)
= time_ago_with_tooltip(member.created_at)
- if show_roles
+ - current_resource = @project || @group
.controls.member-controls
- - if show_controls && (member.respond_to?(:group) && @group) || (member.respond_to?(:project) && @project)
+ - if show_controls && member.source == current_resource
- if user != current_user
= form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f|
= f.hidden_field :access_level
diff --git a/changelogs/unreleased/1051-api-create-users-without-password.yml b/changelogs/unreleased/1051-api-create-users-without-password.yml
new file mode 100644
index 00000000000..24b5a73b45c
--- /dev/null
+++ b/changelogs/unreleased/1051-api-create-users-without-password.yml
@@ -0,0 +1,4 @@
+---
+title: Optionally make users created via the API set their password
+merge_request: 8957
+author: Joost Rijneveld
diff --git a/changelogs/unreleased/27336-add-environment-url-link-to-terminal-page.yml b/changelogs/unreleased/27336-add-environment-url-link-to-terminal-page.yml
new file mode 100644
index 00000000000..dd4907166c4
--- /dev/null
+++ b/changelogs/unreleased/27336-add-environment-url-link-to-terminal-page.yml
@@ -0,0 +1,4 @@
+---
+title: Added external environment link to web terminal view
+merge_request: 8303
+author:
diff --git a/changelogs/unreleased/27991-success-with-warnings-caret.yml b/changelogs/unreleased/27991-success-with-warnings-caret.yml
new file mode 100644
index 00000000000..703d34a5ede
--- /dev/null
+++ b/changelogs/unreleased/27991-success-with-warnings-caret.yml
@@ -0,0 +1,4 @@
+---
+title: Fix icon colors in merge request widget mini graph
+merge_request:
+author:
diff --git a/changelogs/unreleased/issue-newproj-layout.yml b/changelogs/unreleased/issue-newproj-layout.yml
new file mode 100644
index 00000000000..d15e8b7d1e5
--- /dev/null
+++ b/changelogs/unreleased/issue-newproj-layout.yml
@@ -0,0 +1,4 @@
+---
+title: Removed duplicate "Visibility Level" label on New Project page
+merge_request:
+author: Robert Marcano
diff --git a/changelogs/unreleased/rs-warden-blocked-users.yml b/changelogs/unreleased/rs-warden-blocked-users.yml
new file mode 100644
index 00000000000..c0c23fb6f11
--- /dev/null
+++ b/changelogs/unreleased/rs-warden-blocked-users.yml
@@ -0,0 +1,4 @@
+---
+title: Don't perform Devise trackable updates on blocked User records
+merge_request: 8915
+author:
diff --git a/changelogs/unreleased/sh-add-index-to-ci-trigger-requests.yml b/changelogs/unreleased/sh-add-index-to-ci-trigger-requests.yml
new file mode 100644
index 00000000000..bab76812a17
--- /dev/null
+++ b/changelogs/unreleased/sh-add-index-to-ci-trigger-requests.yml
@@ -0,0 +1,4 @@
+---
+title: Add index to ci_trigger_requests for commit_id
+merge_request:
+author:
diff --git a/db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb b/db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb
new file mode 100644
index 00000000000..61e49c14fc0
--- /dev/null
+++ b/db/migrate/20170210075922_add_index_to_ci_trigger_requests_for_commit_id.rb
@@ -0,0 +1,11 @@
+class AddIndexToCiTriggerRequestsForCommitId < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def change
+ add_concurrent_index :ci_trigger_requests, :commit_id
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 3fef5b82073..d71911eaf14 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170206101030) do
+ActiveRecord::Schema.define(version: 20170210075922) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -367,6 +367,8 @@ ActiveRecord::Schema.define(version: 20170206101030) do
t.integer "commit_id"
end
+ add_index "ci_trigger_requests", ["commit_id"], name: "index_ci_trigger_requests_on_commit_id", using: :btree
+
create_table "ci_triggers", force: :cascade do |t|
t.string "token"
t.integer "project_id"
diff --git a/doc/api/users.md b/doc/api/users.md
index fea9bdf9639..ed3469521fc 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -216,7 +216,7 @@ Parameters:
## User creation
-Creates a new user. Note only administrators can create new users.
+Creates a new user. Note only administrators can create new users. Either `password` or `reset_password` should be specified (`reset_password` takes priority).
```
POST /users
@@ -225,7 +225,8 @@ POST /users
Parameters:
- `email` (required) - Email
-- `password` (required) - Password
+- `password` (optional) - Password
+- `reset_password` (optional) - Send user password reset link - true or false(default)
- `username` (required) - Username
- `name` (required) - Name
- `skype` (optional) - Skype ID
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 4980a90f952..82ac3886ac3 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -82,7 +82,9 @@ module API
end
params do
requires :email, type: String, desc: 'The email of the user'
- requires :password, type: String, desc: 'The password of the new user'
+ optional :password, type: String, desc: 'The password of the new user'
+ optional :reset_password, type: Boolean, desc: 'Flag indicating the user will be sent a password reset token'
+ at_least_one_of :password, :reset_password
requires :name, type: String, desc: 'The name of the user'
requires :username, type: String, desc: 'The username of the user'
use :optional_attributes
@@ -94,8 +96,18 @@ module API
user_params = declared_params(include_missing: false)
identity_attrs = user_params.slice(:provider, :extern_uid)
confirm = user_params.delete(:confirm)
+ user = User.new(user_params.except(:extern_uid, :provider, :reset_password))
+
+ if user_params.delete(:reset_password)
+ user.attributes = {
+ force_random_password: true,
+ password_expires_at: nil,
+ created_by_id: current_user.id
+ }
+ user.generate_password
+ user.generate_reset_token
+ end
- user = User.new(user_params.except(:extern_uid, :provider))
user.skip_confirmation! unless confirm
if identity_attrs.any?
diff --git a/lib/gitlab/cycle_analytics/code_stage.rb b/lib/gitlab/cycle_analytics/code_stage.rb
index d1bc2055ba8..1e52b6614a1 100644
--- a/lib/gitlab/cycle_analytics/code_stage.rb
+++ b/lib/gitlab/cycle_analytics/code_stage.rb
@@ -13,6 +13,10 @@ module Gitlab
:code
end
+ def legend
+ "Related Merge Requests"
+ end
+
def description
"Time until first merge request"
end
diff --git a/lib/gitlab/cycle_analytics/issue_stage.rb b/lib/gitlab/cycle_analytics/issue_stage.rb
index d2068fbc38f..213994988a5 100644
--- a/lib/gitlab/cycle_analytics/issue_stage.rb
+++ b/lib/gitlab/cycle_analytics/issue_stage.rb
@@ -14,6 +14,10 @@ module Gitlab
:issue
end
+ def legend
+ "Related Issues"
+ end
+
def description
"Time before an issue gets scheduled"
end
diff --git a/lib/gitlab/cycle_analytics/plan_stage.rb b/lib/gitlab/cycle_analytics/plan_stage.rb
index 3b4dfc6a30e..45d51d30ccc 100644
--- a/lib/gitlab/cycle_analytics/plan_stage.rb
+++ b/lib/gitlab/cycle_analytics/plan_stage.rb
@@ -14,6 +14,10 @@ module Gitlab
:plan
end
+ def legend
+ "Related Commits"
+ end
+
def description
"Time before an issue starts implementation"
end
diff --git a/lib/gitlab/cycle_analytics/production_stage.rb b/lib/gitlab/cycle_analytics/production_stage.rb
index 2a6bcc80116..9f387a02945 100644
--- a/lib/gitlab/cycle_analytics/production_stage.rb
+++ b/lib/gitlab/cycle_analytics/production_stage.rb
@@ -15,6 +15,10 @@ module Gitlab
:production
end
+ def legend
+ "Related Issues"
+ end
+
def description
"From issue creation until deploy to production"
end
diff --git a/lib/gitlab/cycle_analytics/review_stage.rb b/lib/gitlab/cycle_analytics/review_stage.rb
index fbaa3010d81..4744be834de 100644
--- a/lib/gitlab/cycle_analytics/review_stage.rb
+++ b/lib/gitlab/cycle_analytics/review_stage.rb
@@ -13,6 +13,10 @@ module Gitlab
:review
end
+ def legend
+ "Relative Merged Requests"
+ end
+
def description
"Time between merge request creation and merge/close"
end
diff --git a/lib/gitlab/cycle_analytics/staging_stage.rb b/lib/gitlab/cycle_analytics/staging_stage.rb
index 945909a4d62..3cdbe04fbaf 100644
--- a/lib/gitlab/cycle_analytics/staging_stage.rb
+++ b/lib/gitlab/cycle_analytics/staging_stage.rb
@@ -14,6 +14,10 @@ module Gitlab
:staging
end
+ def legend
+ "Relative Deployed Builds"
+ end
+
def description
"From merge request merge until deploy to production"
end
diff --git a/lib/gitlab/cycle_analytics/test_stage.rb b/lib/gitlab/cycle_analytics/test_stage.rb
index 0079d56e0e4..e96943833bc 100644
--- a/lib/gitlab/cycle_analytics/test_stage.rb
+++ b/lib/gitlab/cycle_analytics/test_stage.rb
@@ -13,6 +13,10 @@ module Gitlab
:test
end
+ def legend
+ "Relative Builds Trigger by Commits"
+ end
+
def description
"Total test time for all commits/merges"
end
diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb
index f1c8891e87a..0347e789576 100644
--- a/spec/controllers/projects/uploads_controller_spec.rb
+++ b/spec/controllers/projects/uploads_controller_spec.rb
@@ -170,68 +170,24 @@ describe Projects::UploadsController do
project.team << [user, :master]
end
- context "when the user is blocked" do
+ context "when the file exists" do
before do
- user.block
- project.team << [user, :master]
- end
-
- context "when the file exists" do
- before do
- allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
- allow(jpg).to receive(:exists?).and_return(true)
- end
-
- context "when the file is an image" do
- before do
- allow_any_instance_of(FileUploader).to receive(:image?).and_return(true)
- end
-
- it "responds with status 200" do
- go
-
- expect(response).to have_http_status(200)
- end
- end
-
- context "when the file is not an image" do
- it "redirects to the sign in page" do
- go
-
- expect(response).to redirect_to(new_user_session_path)
- end
- end
+ allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
+ allow(jpg).to receive(:exists?).and_return(true)
end
- context "when the file doesn't exist" do
- it "redirects to the sign in page" do
- go
+ it "responds with status 200" do
+ go
- expect(response).to redirect_to(new_user_session_path)
- end
+ expect(response).to have_http_status(200)
end
end
- context "when the user isn't blocked" do
- context "when the file exists" do
- before do
- allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
- allow(jpg).to receive(:exists?).and_return(true)
- end
-
- it "responds with status 200" do
- go
-
- expect(response).to have_http_status(200)
- end
- end
-
- context "when the file doesn't exist" do
- it "responds with status 404" do
- go
+ context "when the file doesn't exist" do
+ it "responds with status 404" do
+ go
- expect(response).to have_http_status(404)
- end
+ expect(response).to have_http_status(404)
end
end
end
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index c6f7869516e..1732b1a0081 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -14,6 +14,14 @@ FactoryGirl.define do
admin true
end
+ trait :blocked do
+ after(:build) { |user, _| user.block! }
+ end
+
+ trait :external do
+ external true
+ end
+
trait :two_factor do
two_factor_via_otp
end
diff --git a/spec/features/environment_spec.rb b/spec/features/environment_spec.rb
index 2f49e89b4e4..c203e1f20c1 100644
--- a/spec/features/environment_spec.rb
+++ b/spec/features/environment_spec.rb
@@ -101,6 +101,22 @@ feature 'Environment', :feature do
scenario 'it shows the terminal button' do
expect(page).to have_terminal_button
end
+
+ context 'web terminal', :js do
+ before do
+ # Stub #terminals as it causes js-enabled feature specs to render the page incorrectly
+ allow_any_instance_of(Environment).to receive(:terminals) { nil }
+ visit terminal_namespace_project_environment_path(project.namespace, project, environment)
+ end
+
+ it 'displays a web terminal' do
+ expect(page).to have_selector('#terminal')
+ end
+
+ it 'displays a link to the environment external url' do
+ expect(page).to have_link(nil, href: environment.external_url)
+ end
+ end
end
context 'for developer' do
diff --git a/spec/features/groups/members/list_spec.rb b/spec/features/groups/members/list_spec.rb
new file mode 100644
index 00000000000..109de39b2dd
--- /dev/null
+++ b/spec/features/groups/members/list_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+feature 'Groups members list', feature: true do
+ let(:user1) { create(:user, name: 'John Doe') }
+ let(:user2) { create(:user, name: 'Mary Jane') }
+ let(:group) { create(:group) }
+ let(:nested_group) { create(:group, parent: group) }
+
+ background do
+ login_as(user1)
+ end
+
+ scenario 'show members from current group and parent' do
+ group.add_developer(user1)
+ nested_group.add_developer(user2)
+
+ visit group_group_members_path(nested_group)
+
+ expect(first_row.text).to include(user1.name)
+ expect(second_row.text).to include(user2.name)
+ end
+
+ scenario 'show user once if member of both current group and parent' do
+ group.add_developer(user1)
+ nested_group.add_developer(user1)
+
+ visit group_group_members_path(nested_group)
+
+ expect(first_row.text).to include(user1.name)
+ expect(second_row).to be_blank
+ end
+
+ def first_row
+ page.all('ul.content-list > li')[0]
+ end
+
+ def second_row
+ page.all('ul.content-list > li')[1]
+ end
+end
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index ab7d89306db..ae609160e18 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -32,6 +32,22 @@ feature 'Login', feature: true do
end
end
+ describe 'with a blocked account' do
+ it 'prevents the user from logging in' do
+ user = create(:user, :blocked)
+
+ login_with(user)
+
+ expect(page).to have_content('Your account has been blocked.')
+ end
+
+ it 'does not update Devise trackable attributes' do
+ user = create(:user, :blocked)
+
+ expect { login_with(user) }.not_to change { user.reload.sign_in_count }
+ end
+ end
+
describe 'with two-factor authentication' do
def enter_code(code)
fill_in 'user_otp_attempt', with: code
diff --git a/spec/finders/group_members_finder_spec.rb b/spec/finders/group_members_finder_spec.rb
new file mode 100644
index 00000000000..b762756f9ce
--- /dev/null
+++ b/spec/finders/group_members_finder_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe GroupMembersFinder, '#execute' do
+ let(:group) { create(:group) }
+ let(:nested_group) { create(:group, :access_requestable, parent: group) }
+ let(:user1) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:user3) { create(:user) }
+ let(:user4) { create(:user) }
+
+ it 'returns members for top-level group' do
+ member1 = group.add_master(user1)
+ member2 = group.add_master(user2)
+ member3 = group.add_master(user3)
+
+ result = described_class.new(group).execute
+
+ expect(result.to_a).to eq([member3, member2, member1])
+ end
+
+ it 'returns members for nested group' do
+ group.add_master(user2)
+ nested_group.request_access(user4)
+ member1 = group.add_master(user1)
+ member3 = nested_group.add_master(user2)
+ member4 = nested_group.add_master(user3)
+
+ result = described_class.new(nested_group).execute
+
+ expect(result.to_a).to eq([member4, member3, member1])
+ end
+end
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 16e2144d6a1..c720cc9f2c2 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -129,6 +129,14 @@ describe Member, models: true do
it { expect(described_class.request).not_to include @accepted_request_member }
end
+ describe '.non_request' do
+ it { expect(described_class.non_request).to include @master }
+ it { expect(described_class.non_request).to include @invited_member }
+ it { expect(described_class.non_request).to include @accepted_invite_member }
+ it { expect(described_class.non_request).not_to include @requested_member }
+ it { expect(described_class.non_request).to include @accepted_request_member }
+ end
+
describe '.developers' do
subject { described_class.developers.to_a }
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 8692f9da976..5958012672e 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -190,6 +190,18 @@ describe API::Users, api: true do
expect(new_user.external).to be_truthy
end
+ it "creates user with reset password" do
+ post api('/users', admin), attributes_for(:user, reset_password: true).except(:password)
+
+ expect(response).to have_http_status(201)
+
+ user_id = json_response['id']
+ new_user = User.find(user_id)
+
+ expect(new_user).not_to eq(nil)
+ expect(new_user.recently_sent_password_reset?).to eq(true)
+ end
+
it "does not create user with invalid email" do
post api('/users', admin),
email: 'invalid email',