Commit 4f265c3b authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 27932-merge-request-pipelines-displays-json

* master:
  Added env external link and light web terminal spec
  Don't perform Devise trackable updates on blocked User records
  Remove orange caret icon from mr widget
  Add index to ci_trigger_requests for commit_id
  Show parent group members for nested group
  Make sure events have most properties defined
  Restore exposure of legend property for events
  Optionally make users created via the API set their password
  Removed duplicate "Visibility Level" label on New Project page
parents 487667e6 b7c5ca49
...@@ -97,7 +97,7 @@ $(() => { ...@@ -97,7 +97,7 @@ $(() => {
} }
this.isLoadingStage = true; this.isLoadingStage = true;
cycleAnalyticsStore.setStageEvents([]); cycleAnalyticsStore.setStageEvents([], stage);
cycleAnalyticsStore.setActiveStage(stage); cycleAnalyticsStore.setActiveStage(stage);
cycleAnalyticsService cycleAnalyticsService
...@@ -107,7 +107,7 @@ $(() => { ...@@ -107,7 +107,7 @@ $(() => {
}) })
.done((response) => { .done((response) => {
this.isEmptyStage = !response.events.length; this.isEmptyStage = !response.events.length;
cycleAnalyticsStore.setStageEvents(response.events); cycleAnalyticsStore.setStageEvents(response.events, stage);
}) })
.error(() => { .error(() => {
this.isEmptyStage = true; this.isEmptyStage = true;
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
require('../lib/utils/text_utility');
const DEFAULT_EVENT_OBJECTS = require('./default_event_objects');
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
...@@ -34,11 +38,12 @@ ...@@ -34,11 +38,12 @@
}); });
newData.stages.forEach((item) => { newData.stages.forEach((item) => {
const stageName = item.title.toLowerCase(); const stageSlug = gl.text.dasherize(item.title.toLowerCase());
item.active = false; item.active = false;
item.isUserAllowed = data.permissions[stageName]; item.isUserAllowed = data.permissions[stageSlug];
item.emptyStageText = EMPTY_STAGE_TEXTS[stageName]; item.emptyStageText = EMPTY_STAGE_TEXTS[stageSlug];
item.component = `stage-${stageName}-component`; item.component = `stage-${stageSlug}-component`;
item.slug = stageSlug;
}); });
newData.analytics = data; newData.analytics = data;
return newData; return newData;
...@@ -58,31 +63,33 @@ ...@@ -58,31 +63,33 @@
this.deactivateAllStages(); this.deactivateAllStages();
stage.active = true; stage.active = true;
}, },
setStageEvents(events) { setStageEvents(events, stage) {
this.state.events = this.decorateEvents(events); this.state.events = this.decorateEvents(events, stage);
}, },
decorateEvents(events) { decorateEvents(events, stage) {
const newEvents = []; const newEvents = [];
events.forEach((item) => { events.forEach((item) => {
if (!item) return; if (!item) return;
item.totalTime = item.total_time; const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item);
item.author.webUrl = item.author.web_url;
item.author.avatarUrl = item.author.avatar_url; 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 (eventItem.created_at) eventItem.createdAt = eventItem.created_at;
if (item.short_sha) item.shortSha = item.short_sha; if (eventItem.short_sha) eventItem.shortSha = eventItem.short_sha;
if (item.commit_url) item.commitUrl = item.commit_url; if (eventItem.commit_url) eventItem.commitUrl = eventItem.commit_url;
delete item.author.web_url; delete eventItem.author.web_url;
delete item.author.avatar_url; delete eventItem.author.avatar_url;
delete item.total_time; delete eventItem.total_time;
delete item.created_at; delete eventItem.created_at;
delete item.short_sha; delete eventItem.short_sha;
delete item.commit_url; delete eventItem.commit_url;
newEvents.push(item); newEvents.push(eventItem);
}); });
return newEvents; return newEvents;
......
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: '',
},
},
};
/* 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 */ /* 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() {
(function(w) { (function(w) {
var base; var base;
...@@ -164,8 +166,14 @@ ...@@ -164,8 +166,14 @@
gl.text.pluralize = function(str, count) { gl.text.pluralize = function(str, count) {
return str + (count > 1 || count === 0 ? 's' : ''); 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)) + '...'; 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); })(window);
}).call(this); }).call(this);
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Breakpoints */ /* global Breakpoints */
require('vendor/latinise');
require('./breakpoints'); require('./breakpoints');
require('vendor/jquery.nicescroll'); require('vendor/jquery.nicescroll');
((global) => { ((global) => {
const dasherize = str => str.replace(/[_\s]+/g, '-');
const slugify = str => dasherize(str.trim().toLowerCase().latinise());
class Wikis { class Wikis {
constructor() { constructor() {
this.bp = Breakpoints.get(); this.bp = Breakpoints.get();
...@@ -34,7 +30,7 @@ require('vendor/jquery.nicescroll'); ...@@ -34,7 +30,7 @@ require('vendor/jquery.nicescroll');
if (!this.newWikiForm) return; if (!this.newWikiForm) return;
const slugInput = this.newWikiForm.querySelector('#new_wiki_path'); const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
const slug = slugify(slugInput.value); const slug = gl.text.slugify(slugInput.value);
if (slug.length > 0) { if (slug.length > 0) {
const wikisPath = slugInput.getAttribute('data-wikis-path'); const wikisPath = slugInput.getAttribute('data-wikis-path');
......
...@@ -96,13 +96,6 @@ ...@@ -96,13 +96,6 @@
padding-right: 4px; padding-right: 4px;
} }
&.ci-success_with_warnings {
i {
color: $gl-warning;
}
}
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
flex-wrap: wrap; flex-wrap: wrap;
} }
......
...@@ -12,7 +12,6 @@ class ApplicationController < ActionController::Base ...@@ -12,7 +12,6 @@ class ApplicationController < ActionController::Base
before_action :authenticate_user_from_private_token! before_action :authenticate_user_from_private_token!
before_action :authenticate_user! before_action :authenticate_user!
before_action :validate_user_service_ticket! before_action :validate_user_service_ticket!
before_action :reject_blocked!
before_action :check_password_expiration before_action :check_password_expiration
before_action :check_2fa_requirement before_action :check_2fa_requirement
before_action :ldap_security_check before_action :ldap_security_check
...@@ -87,22 +86,8 @@ class ApplicationController < ActionController::Base ...@@ -87,22 +86,8 @@ class ApplicationController < ActionController::Base
logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}" logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"
end 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) def after_sign_in_path_for(resource)
if resource.is_a?(User) && resource.respond_to?(:blocked?) && resource.blocked? stored_location_for(:redirect) || stored_location_for(resource) || root_path
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
end end
def after_sign_out_path_for(resource) def after_sign_out_path_for(resource)
......
class Explore::ApplicationController < ApplicationController class Explore::ApplicationController < ApplicationController
skip_before_action :authenticate_user!, :reject_blocked! skip_before_action :authenticate_user!
layout 'explore' layout 'explore'
end end
...@@ -9,7 +9,7 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -9,7 +9,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
@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 = @group.group_members @members = GroupMembersFinder.new(@group).execute
@members = @members.non_invite unless can?(current_user, :admin_group, @group) @members = @members.non_invite unless can?(current_user, :admin_group, @group)
@members = @members.search(params[:search]) if params[:search].present? @members = @members.search(params[:search]) if params[:search].present?
@members = @members.sort(@sort) @members = @members.sort(@sort)
......
class HelpController < ApplicationController class HelpController < ApplicationController
skip_before_action :authenticate_user!, :reject_blocked! skip_before_action :authenticate_user!
layout 'help' layout 'help'
......
class KodingController < ApplicationController class KodingController < ApplicationController
before_action :check_integration!, :authenticate_user!, :reject_blocked! before_action :check_integration!
layout 'koding' layout 'koding'
def index def index
......
class Projects::UploadsController < Projects::ApplicationController class Projects::UploadsController < Projects::ApplicationController
skip_before_action :reject_blocked!, :project, skip_before_action :project, :repository,
:repository, if: -> { action_name == 'show' && image_or_video? } if: -> { action_name == 'show' && image_or_video? }
before_action :authorize_upload_file!, only: [:create] before_action :authorize_upload_file!, only: [:create]
......
class SearchController < ApplicationController class SearchController < ApplicationController
skip_before_action :authenticate_user!, :reject_blocked! skip_before_action :authenticate_user!
include SearchHelper include SearchHelper
......
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
...@@ -206,7 +206,7 @@ class Group < Namespace ...@@ -206,7 +206,7 @@ class Group < Namespace
end end
def members_with_parents 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 end
def users_with_parents def users_with_parents
......
...@@ -47,6 +47,7 @@ class Member < ActiveRecord::Base ...@@ -47,6 +47,7 @@ class Member < ActiveRecord::Base
scope :invite, -> { where.not(invite_token: nil) } scope :invite, -> { where.not(invite_token: nil) }
scope :non_invite, -> { where(invite_token: nil) } scope :non_invite, -> { where(invite_token: nil) }
scope :request, -> { where.not(requested_at: nil) } scope :request, -> { where.not(requested_at: nil) }
scope :non_request, -> { where(requested_at: nil) }
scope :has_access, -> { active.where('access_level > 0') } scope :has_access, -> { active.where('access_level > 0') }
......
...@@ -167,6 +167,15 @@ class User < ActiveRecord::Base ...@@ -167,6 +167,15 @@ class User < ActiveRecord::Base
def blocked? def blocked?
true true
end 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
end end
......
...@@ -2,6 +2,7 @@ class AnalyticsStageEntity < Grape::Entity ...@@ -2,6 +2,7 @@ class AnalyticsStageEntity < Grape::Entity
include EntityDateHelper include EntityDateHelper
expose :title expose :title
expose :legend
expose :description expose :description
expose :median, as: :value do |stage| expose :median, as: :value do |stage|
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
Last 90 days Last 90 days
.stage-panel-container .stage-panel-container
.panel.panel-default.stage-panel .panel.panel-default.stage-panel
.panel-heading .panel-heading
%nav.col-headers %nav.col-headers
%ul %ul
%li.stage-header %li.stage-header
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
.col-sm-6 .col-sm-6
.nav-controls .nav-controls
= link_to @environment.external_url, class: 'btn btn-default' do
= icon('external-link')
= render 'projects/deployments/actions', deployment: @environment.last_deployment = render 'projects/deployments/actions', deployment: @environment.last_deployment
.terminal-container{ class: container_class } .terminal-container{ class: container_class }
......
...@@ -94,9 +94,8 @@ ...@@ -94,9 +94,8 @@
.form-group.project-visibility-level-holder .form-group.project-visibility-level-holder
= f.label :visibility_level, class: 'label-light' do = f.label :visibility_level, class: 'label-light' do
Visibility Level Visibility Level
= link_to "(?)", help_page_path("public_access/public_access") = 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 = 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 = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
= link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel' = link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel'
......
- with_label = local_assigns.fetch(:with_label, true)
.form-group.project-visibility-level-holder .form-group.project-visibility-level-holder
= f.label :visibility_level, class: 'control-label' do - if with_label
Visibility Level = f.label :visibility_level, class: 'control-label' do
= link_to icon('question-circle'), help_page_path("public_access/public_access") Visibility Level
.col-sm-10 = 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 - if can_change_visibility_level
= render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model) = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
- else - else
......
...@@ -19,9 +19,9 @@ ...@@ -19,9 +19,9 @@
%label.label.label-danger %label.label.label-danger
%strong Blocked %strong Blocked
- if source.instance_of?(Group) && !@group - if source.instance_of?(Group) && source != @group
&middot; &middot;
= link_to source.name, source, class: "member-group-link" = link_to source.full_name, source, class: "member-group-link"
.hidden-xs.cgray .hidden-xs.cgray
- if member.request? - if member.request?
...@@ -44,8 +44,9 @@ ...@@ -44,8 +44,9 @@
= link_to member.created_by.name, user_path(member.created_by) = link_to member.created_by.name, user_path(member.created_by)
= time_ago_with_tooltip(member.created_at) = time_ago_with_tooltip(member.created_at)
- if show_roles - if show_roles
- current_resource = @project || @group
.controls.member-controls .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 - if user != current_user
= form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f| = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f|
= f.hidden_field :access_level = f.hidden_field :access_level
......
---
title: Optionally make users created via the API set their password
merge_request: 8957
author: Joost Rijneveld
---
title: Added external environment link to web terminal view
merge_request: 8303
author:
---
title: Fix icon colors in merge request widget mini graph
merge_request:
author:
---
title: Removed duplicate "Visibility Level" label on New Project page
merge_request:
author: Robert Marcano
---
title: Don't perform Devise trackable updates on blocked User records
merge_request: 8915
author:
---
title: Add index to ci_trigger_requests for commit_id
merge_request:
author:
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
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -367,6 +367,8 @@ ActiveRecord::Schema.define(version: 20170206101030) do ...@@ -367,6 +367,8 @@ ActiveRecord::Schema.define(version: 20170206101030) do
t.integer "commit_id" t.integer "commit_id"
end 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| create_table "ci_triggers", force: :cascade do |t|
t.string "token" t.string "token"
t.integer "project_id" t.integer "project_id"
......
...@@ -216,7 +216,7 @@ Parameters: ...@@ -216,7 +216,7 @@ Parameters:
## User creation ## 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 POST /users
...@@ -225,7 +225,8 @@ POST /users ...@@ -225,7 +225,8 @@ POST /users
Parameters: Parameters:
- `email` (required) - Email - `email` (required) - Email
- `password` (required) - Password - `password` (optional) - Password
- `reset_password` (optional) - Send user password reset link - true or false(default)
- `username` (required) - Username - `username` (required) - Username
- `name` (required) - Name - `name` (required) - Name
- `skype` (optional) - Skype ID - `skype` (optional) - Skype ID
......
...@@ -82,7 +82,9 @@ module API ...@@ -82,7 +82,9 @@ module API
end end
params do params do
requires :email, type: String, desc: 'The email of the user' 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 :name, type: String, desc: 'The name of the user'
requires :username, type: String, desc: 'The username of the user' requires :username, type: String, desc: 'The username of the user'
use :optional_attributes use :optional_attributes
...@@ -94,8 +96,18 @@ module API ...@@ -94,8 +96,18 @@ module API
user_params = declared_params(include_missing: false) user_params = declared_params(include_missing: false)
identity_attrs = user_params.slice(:provider, :extern_uid) identity_attrs = user_params.slice(:provider, :extern_uid)
confirm = user_params.delete(:confirm) 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 user.skip_confirmation! unless confirm
if identity_attrs.any? if identity_attrs.any?
......
...@@ -13,6 +13,10 @@ module Gitlab ...@@ -13,6 +13,10 @@ module Gitlab
:code :code
end end
def legend
"Related Merge Requests"
end
def description def description
"Time until first merge request" "Time until first merge request"
end end
......
...@@ -14,6 +14,10 @@ module Gitlab ...@@ -14,6 +14,10 @@ module Gitlab
:issue :issue
end end
def legend
"Related Issues"
end
def description def description
"Time before an issue gets scheduled" "Time before an issue gets scheduled"
end end
......
...@@ -14,6 +14,10 @@ module Gitlab ...@@ -14,6 +14,10 @@ module Gitlab
:plan :plan
end end
def legend
"Related Commits"
end
def description def description
"Time before an issue starts implementation" "Time before an issue starts implementation"
end end
......
...@@ -15,6 +15,10 @@ module Gitlab ...@@ -15,6 +15,10 @@ module Gitlab
:production :production
end end
def legend
"Related Issues"
end
def description def description
"From issue creation until deploy to production" "From issue creation until deploy to production"
end end
......
...@@ -13,6 +13,10 @@ module Gitlab ...@@ -13,6 +13,10 @@ module Gitlab
:review :review
end end
def legend
"Relative Merged Requests"
end
def description def description
"Time between merge request creation and merge/close" "Time between merge request creation and merge/close"
end end
......
...@@ -14,6 +14,10 @@ module Gitlab ...@@ -14,6 +14,10 @@ module Gitlab
:staging :staging
end end
def legend
"Relative Deployed Builds"
end
def description def description
"From merge request merge until deploy to production" "From merge request merge until deploy to production"
end end
......
...@@ -13,6 +13,10 @@ module Gitlab ...@@ -13,6 +13,10 @@ module Gitlab
:test :test
end end
def legend
"Relative Builds Trigger by Commits"
end
def description def description
"Total test time for all commits/merges" "Total test time for all commits/merges"
end end
......
...@@ -170,68 +170,24 @@ describe Projects::UploadsController do ...@@ -170,68 +170,24 @@ describe Projects::UploadsController do
project.team << [user, :master] project.team << [user, :master]
end end
context "when the user is blocked" do context "when the file exists" do
before do before do
user.block allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
project.team << [user, :master] allow(jpg).to receive(:exists?).and_return(true)
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
end end
context "when the file doesn't exist" do it "responds with status 200" do
it "redirects to the sign in page" do go
go
expect(response).to redirect_to(new_user_session_path) expect(response).to have_http_status(200)
end
end end
end end
context "when the user isn't blocked" do context "when the file doesn't exist" do
context "when the file exists" do it "responds with status 404" do
before do go
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
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end
end end
end end
end end
......
...@@ -14,6 +14,14 @@ FactoryGirl.define do ...@@ -14,6 +14,14 @@ FactoryGirl.define do
admin true admin true
end end
trait :blocked do
after(:build) { |user, _| user.block! }
end
trait :external do
external true
end
trait :two_factor do trait :two_factor do
two_factor_via_otp two_factor_via_otp
end end
......
...@@ -101,6 +101,22 @@ feature 'Environment', :feature do ...@@ -101,6 +101,22 @@ feature 'Environment', :feature do
scenario 'it shows the terminal button' do scenario 'it shows the terminal button' do
expect(page).to have_terminal_button expect(page).to have_terminal_button
end 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 end
context 'for developer' do context 'for developer' do
......
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
...@@ -32,6 +32,22 @@ feature 'Login', feature: true do ...@@ -32,6 +32,22 @@ feature 'Login', feature: true do
end end
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 describe 'with two-factor authentication' do
def enter_code(code) def enter_code(code)
fill_in 'user_otp_attempt', with: code fill_in 'user_otp_attempt', with: code
......
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
...@@ -129,6 +129,14 @@ describe Member, models: true do ...@@ -129,6 +129,14 @@ describe Member, models: true do
it { expect(described_class.request).not_to include @accepted_request_member } it { expect(described_class.request).not_to include @accepted_request_member }
end 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 describe '.developers' do
subject { described_class.developers.to_a } subject { described_class.developers.to_a }
......
...@@ -190,6 +190,18 @@ describe API::Users, api: true do ...@@ -190,6 +190,18 @@ describe API::Users, api: true do
expect(new_user.external).to be_truthy expect(new_user.external).to be_truthy
end 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 it "does not create user with invalid email" do
post api('/users', admin), post api('/users', admin),
email: 'invalid email', email: 'invalid email',
......
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