diff options
26 files changed, 206 insertions, 25 deletions
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 2a01bc4d44d..731ce57c245 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -272,7 +272,7 @@ header { .header-user { .dropdown-menu-nav { - width: 140px; + min-width: 140px; margin-top: -5px; } } 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/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/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/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 · - = 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/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/config/initializers/rspec_profiling.rb b/config/initializers/rspec_profiling.rb index f462e654b2c..0ef9f51e5cf 100644 --- a/config/initializers/rspec_profiling.rb +++ b/config/initializers/rspec_profiling.rb @@ -4,6 +4,12 @@ module RspecProfilingConnection end end +module RspecProfilingGitBranchCi + def branch + ENV['CI_BUILD_REF_NAME'] || super + end +end + if Rails.env.test? RspecProfiling.configure do |config| if ENV['RSPEC_PROFILING_POSTGRES_URL'] @@ -11,4 +17,6 @@ if Rails.env.test? config.collector = RspecProfiling::Collectors::PSQL end end + + RspecProfiling::VCS::Git.prepend(RspecProfilingGitBranchCi) if ENV.has_key?('CI') end 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/doc/user/project/img/protected_branches_devs_can_push.png b/doc/user/project/img/protected_branches_devs_can_push.png Binary files differindex 1c05cb8fd36..320e6eb7fee 100644 --- a/doc/user/project/img/protected_branches_devs_can_push.png +++ b/doc/user/project/img/protected_branches_devs_can_push.png 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/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/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', |