From ea4777ff501e370a39ae30e76a955136afe3c1fa Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 31 Dec 2015 15:19:13 +0100 Subject: Add features for list and show details of variables in API --- app/models/ci/variable.rb | 1 + lib/api/api.rb | 2 + lib/api/entities.rb | 4 ++ lib/api/variables.rb | 43 +++++++++++++++++++++ spec/factories/ci/variables.rb | 25 +++++++++++++ spec/requests/api/variables_spec.rb | 75 +++++++++++++++++++++++++++++++++++++ 6 files changed, 150 insertions(+) create mode 100644 lib/api/variables.rb create mode 100644 spec/factories/ci/variables.rb create mode 100644 spec/requests/api/variables_spec.rb diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index 56759d3e50f..0e2712086ca 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -9,6 +9,7 @@ # encrypted_value :text # encrypted_value_salt :string(255) # encrypted_value_iv :string(255) +# gl_project_id :integer # module Ci diff --git a/lib/api/api.rb b/lib/api/api.rb index 7834262d612..a9e1913f0f2 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -54,5 +54,7 @@ module API mount Keys mount Tags mount Triggers + + mount Variables end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 26e7c956e8f..f71d072f269 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -365,5 +365,9 @@ module API class TriggerRequest < Grape::Entity expose :id, :variables end + + class Variable < Grape::Entity + expose :id, :key, :value + end end end diff --git a/lib/api/variables.rb b/lib/api/variables.rb new file mode 100644 index 00000000000..6517150f6f4 --- /dev/null +++ b/lib/api/variables.rb @@ -0,0 +1,43 @@ +module API + # Projects variables API + class Variables < Grape::API + before { authenticate! } + before { authorize_admin_project } + + resource :projects do + # Get project variables + # + # Parameters: + # id (required) - The ID of a project + # page (optional) - The page number for pagination + # per_page (optional) - The value of items per page to show + # Example Request: + # GET /projects/:id/variables + get ':id/variables' do + variables = user_project.variables + present paginate(variables), with: Entities::Variable + end + + # Get specifica bariable of a project + # + # Parameters: + # id (required) - The ID of a project + # variable_id (required) - The ID OR `key` of variable to show; if variable_id contains only digits it's treated + # as ID other ways it's treated as `key` + # Example Reuest: + # GET /projects/:id/variables/:variable_id + get ':id/variables/:variable_id' do + variable_id = params[:variable_id] + variables = user_project.variables + variables = + if variable_id.match(/^\d+$/) + variables.where(id: variable_id.to_i) + else + variables.where(key: variable_id) + end + + present variables.first, with: Entities::Variable + end + end + end +end diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb new file mode 100644 index 00000000000..c3dcb678da7 --- /dev/null +++ b/spec/factories/ci/variables.rb @@ -0,0 +1,25 @@ +# == Schema Information +# +# Table name: ci_variables +# +# id :integer not null, primary key +# project_id :integer not null +# key :string(255) +# value :text +# encrypted_value :text +# encrypted_value_salt :string(255) +# encrypted_value_iv :string(255) +# gl_project_id :integer +# + +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :ci_variable, class: Ci::Variable do + id 1 + key 'TEST_VARIABLE_1' + value 'VALUE_1' + + project factory: :empty_project + end +end diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb new file mode 100644 index 00000000000..8f66f5432b6 --- /dev/null +++ b/spec/requests/api/variables_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + let(:user) { create(:user) } + let(:user2) { create(:user) } + let!(:project) { create(:project, creator_id: user.id) } + let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } + let!(:developer) { create(:project_member, user: user2, project: project, access_level: ProjectMember::DEVELOPER) } + let!(:variable) { create(:ci_variable, project: project) } + + describe 'GET /projects/:id/variables' do + context 'authorized user with proper permissions' do + it 'should return project variables' do + get api("/projects/#{project.id}/variables", user) + + expect(response.status).to eq(200) + expect(json_response).to be_a(Array) + end + end + + context 'authorized user with invalid permissions' do + it 'should not return project variables' do + get api("/projects/#{project.id}/variables", user2) + + expect(response.status).to eq(403) + end + end + + context 'unauthorized user' do + it 'should not return project variables' do + get api("/projects/#{project.id}/variables") + + expect(response.status).to eq(401) + end + end + end + + describe 'GET /projects/:id/variables/:variable_id' do + context 'authorized user with proper permissions' do + it 'should return project variable details when ID is used as :variable_id' do + get api("/projects/#{project.id}/variables/1", user) + + expect(response.status).to eq(200) + expect(json_response['key']).to eq('TEST_VARIABLE_1') + expect(json_response['value']).to eq('VALUE_1') + end + + it 'should return project variable details when `key` is used as :variable_id' do + get api("/projects/#{project.id}/variables/TEST_VARIABLE_1", user) + + expect(response.status).to eq(200) + expect(json_response['id']).to eq(1) + expect(json_response['value']).to eq('VALUE_1') + end + end + + context 'authorized user with invalid permissions' do + it 'should not return project variable details' do + get api("/projects/#{project.id}/variables/1", user2) + + expect(response.status).to eq(403) + end + end + + context 'unauthorized user' do + it 'should not return project variable details' do + get api("/projects/#{project.id}/variables/1") + + expect(response.status).to eq(401) + end + end + end +end -- cgit v1.2.1 From a692ce1c079703c4f3947e1d0a29547189e94d0f Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 31 Dec 2015 16:25:49 +0100 Subject: Add update feature for variables API --- lib/api/variables.rb | 21 ++++++++++++++- spec/requests/api/variables_spec.rb | 52 +++++++++++++++++++++++++++++++------ 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/lib/api/variables.rb b/lib/api/variables.rb index 6517150f6f4..6522ecba70c 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -24,7 +24,7 @@ module API # id (required) - The ID of a project # variable_id (required) - The ID OR `key` of variable to show; if variable_id contains only digits it's treated # as ID other ways it's treated as `key` - # Example Reuest: + # Example Request: # GET /projects/:id/variables/:variable_id get ':id/variables/:variable_id' do variable_id = params[:variable_id] @@ -38,6 +38,25 @@ module API present variables.first, with: Entities::Variable end + + # Update existing variable of a project + # + # Parameters: + # id (required) - The ID of a project + # variable_id (required) - The ID of a variable + # key (optional) - new value for `key` field of variable + # value (optional) - new value for `value` field of variable + # Example Request: + # PUT /projects/:id/variables/:variable_id + put ':id/variables/:variable_id' do + variable = user_project.variables.where(id: params[:variable_id].to_i).first + + variable.key = params[:key] + variable.value = params[:value] + variable.save! + + present variable, with: Entities::Variable + end end end end diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index 8f66f5432b6..3f58277c4ae 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -40,25 +40,25 @@ describe API::API, api: true do describe 'GET /projects/:id/variables/:variable_id' do context 'authorized user with proper permissions' do it 'should return project variable details when ID is used as :variable_id' do - get api("/projects/#{project.id}/variables/1", user) + get api("/projects/#{project.id}/variables/#{variable.id}", user) expect(response.status).to eq(200) - expect(json_response['key']).to eq('TEST_VARIABLE_1') - expect(json_response['value']).to eq('VALUE_1') + expect(json_response['key']).to eq(variable.key) + expect(json_response['value']).to eq(variable.value) end it 'should return project variable details when `key` is used as :variable_id' do - get api("/projects/#{project.id}/variables/TEST_VARIABLE_1", user) + get api("/projects/#{project.id}/variables/#{variable.key}", user) expect(response.status).to eq(200) - expect(json_response['id']).to eq(1) - expect(json_response['value']).to eq('VALUE_1') + expect(json_response['id']).to eq(variable.id) + expect(json_response['value']).to eq(variable.value) end end context 'authorized user with invalid permissions' do it 'should not return project variable details' do - get api("/projects/#{project.id}/variables/1", user2) + get api("/projects/#{project.id}/variables/#{variable.id}", user2) expect(response.status).to eq(403) end @@ -66,7 +66,43 @@ describe API::API, api: true do context 'unauthorized user' do it 'should not return project variable details' do - get api("/projects/#{project.id}/variables/1") + get api("/projects/#{project.id}/variables/#{variable.id}") + + expect(response.status).to eq(401) + end + end + end + + describe 'PUT /projects/:id/variables/:variable_id' do + context 'authorized user with proper permissions' do + it 'should update variable data' do + initial_variable = project.variables.first + key_before = initial_variable.key + value_before = initial_variable.value + + put api("/projects/#{project.id}/variables/#{variable.id}", user), key: 'TEST_VARIABLE_1_UP', value: 'VALUE_1_UP' + + updated_variable = project.variables.first + + expect(response.status).to eq(200) + expect(key_before).to eq(variable.key) + expect(value_before).to eq(variable.value) + expect(updated_variable.key).to eq('TEST_VARIABLE_1_UP') + expect(updated_variable.value).to eq('VALUE_1_UP') + end + end + + context 'authorized user with invalid permissions' do + it 'should not update variable' do + put api("/projects/#{project.id}/variables/#{variable.id}", user2) + + expect(response.status).to eq(403) + end + end + + context 'unauthorized user' do + it 'should not return project variable details' do + put api("/projects/#{project.id}/variables/#{variable.id}") expect(response.status).to eq(401) end -- cgit v1.2.1 From 0d014feb1d216e692882976f0d70c3227eaec4ca Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 31 Dec 2015 16:56:03 +0100 Subject: Add delete feature to variables API --- lib/api/variables.rb | 12 ++++++++++++ spec/requests/api/variables_spec.rb | 29 ++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/lib/api/variables.rb b/lib/api/variables.rb index 6522ecba70c..c70c7cd9d7b 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -57,6 +57,18 @@ module API present variable, with: Entities::Variable end + + # Delete existing variable of a project + # + # Parameters: + # id (required) - The ID of a project + # variable_id (required) - The ID of a variable + # Exanoke Reqyest: + # DELETE /projects/:id/variables/:variable_id + delete ':id/variables/:variable_id' do + variable = user_project.variables.where(id: params[:variable_id].to_i).first + variable.destroy + end end end end diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index 3f58277c4ae..385db2409bd 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -101,11 +101,38 @@ describe API::API, api: true do end context 'unauthorized user' do - it 'should not return project variable details' do + it 'should not update variable' do put api("/projects/#{project.id}/variables/#{variable.id}") expect(response.status).to eq(401) end end end + + describe 'DELETE /projects/:id/variables/:variable_id' do + context 'authorized user with proper permissions' do + it 'should delete variable' do + expect do + delete api("/projects/#{project.id}/variables/#{variable.id}", user) + end.to change{project.variables.count}.by(-1) + expect(response.status).to eq(200) + end + end + + context 'authorized user with invalid permissions' do + it 'should not delete variable' do + delete api("/projects/#{project.id}/variables/#{variable.id}", user2) + + expect(response.status).to eq(403) + end + end + + context 'unauthorized user' do + it 'should not delete variable' do + delete api("/projects/#{project.id}/variables/#{variable.id}") + + expect(response.status).to eq(401) + end + end + end end -- cgit v1.2.1 From c5177dd5e2171b047a695802c979cf779522ba8a Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 31 Dec 2015 17:03:11 +0100 Subject: Add missing 'not_found' checks in variables API --- lib/api/variables.rb | 7 +++++++ spec/requests/api/variables_spec.rb | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/lib/api/variables.rb b/lib/api/variables.rb index c70c7cd9d7b..dac2ba679c7 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -36,6 +36,8 @@ module API variables.where(key: variable_id) end + return not_found!('Variable') if variables.empty? + present variables.first, with: Entities::Variable end @@ -51,6 +53,8 @@ module API put ':id/variables/:variable_id' do variable = user_project.variables.where(id: params[:variable_id].to_i).first + return not_found!('Variable') unless variable + variable.key = params[:key] variable.value = params[:value] variable.save! @@ -67,6 +71,9 @@ module API # DELETE /projects/:id/variables/:variable_id delete ':id/variables/:variable_id' do variable = user_project.variables.where(id: params[:variable_id].to_i).first + + return not_found!('Variable') unless variable + variable.destroy end end diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index 385db2409bd..b35ee2d32d1 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -54,6 +54,12 @@ describe API::API, api: true do expect(json_response['id']).to eq(variable.id) expect(json_response['value']).to eq(variable.value) end + + it 'should responde with 404 Not Found if requesting non-existing variable' do + get api("/projects/#{project.id}/variables/9999", user) + + expect(response.status).to eq(404) + end end context 'authorized user with invalid permissions' do @@ -90,6 +96,12 @@ describe API::API, api: true do expect(updated_variable.key).to eq('TEST_VARIABLE_1_UP') expect(updated_variable.value).to eq('VALUE_1_UP') end + + it 'should responde with 404 Not Found if requesting non-existing variable' do + put api("/projects/#{project.id}/variables/9999", user) + + expect(response.status).to eq(404) + end end context 'authorized user with invalid permissions' do @@ -117,6 +129,12 @@ describe API::API, api: true do end.to change{project.variables.count}.by(-1) expect(response.status).to eq(200) end + + it 'should responde with 404 Not Found if requesting non-existing variable' do + delete api("/projects/#{project.id}/variables/9999", user) + + expect(response.status).to eq(404) + end end context 'authorized user with invalid permissions' do -- cgit v1.2.1 From 937567b767e6d7b34dcaa1d9c83fc75464638683 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 31 Dec 2015 22:30:07 +0100 Subject: Add create feature to variables API --- lib/api/variables.rb | 20 +++++++++++++++++++ spec/factories/ci/variables.rb | 2 +- spec/requests/api/variables_spec.rb | 38 +++++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/lib/api/variables.rb b/lib/api/variables.rb index dac2ba679c7..fc63ac2f56a 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -41,6 +41,24 @@ module API present variables.first, with: Entities::Variable end + # Create a new variable in project + # + # Parameters: + # id (required) - The ID of a project + # key (required) - The key of variable being created + # value (required) - The value of variable being created + # Example Request: + # POST /projects/:id/variables + post ':id/variables' do + required_attributes! [:key, :value] + + variable = user_project.variables.create(key: params[:key], value: params[:value]) + return render_validation_error!(variable) unless variable.valid? + variable.save! + + present variable, with: Entities::Variable + end + # Update existing variable of a project # # Parameters: @@ -75,6 +93,8 @@ module API return not_found!('Variable') unless variable variable.destroy + + present variable, with: Entities::Variable end end end diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb index c3dcb678da7..a19b9fc72f2 100644 --- a/spec/factories/ci/variables.rb +++ b/spec/factories/ci/variables.rb @@ -16,7 +16,7 @@ FactoryGirl.define do factory :ci_variable, class: Ci::Variable do - id 1 + id 10 key 'TEST_VARIABLE_1' value 'VALUE_1' diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index b35ee2d32d1..bf0dd77473a 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -79,6 +79,44 @@ describe API::API, api: true do end end + describe 'POST /projects/:id/variables' do + context 'authorized user with proper permissions' do + it 'should create variable' do + expect do + post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2' + end.to change{project.variables.count}.by(1) + + expect(response.status).to eq(201) + expect(json_response['key']).to eq('TEST_VARIABLE_2') + expect(json_response['value']).to eq('VALUE_2') + end + + it 'should not allow to duplicate variable key' do + expect do + post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_1', value: 'VALUE_2' + end.to change{project.variables.count}.by(0) + + expect(response.status).to eq(400) + end + end + + context 'authorized user with invalid permissions' do + it 'should not create variable' do + post api("/projects/#{project.id}/variables", user2) + + expect(response.status).to eq(403) + end + end + + context 'unauthorized user' do + it 'should not create variable' do + post api("/projects/#{project.id}/variables") + + expect(response.status).to eq(401) + end + end + end + describe 'PUT /projects/:id/variables/:variable_id' do context 'authorized user with proper permissions' do it 'should update variable data' do -- cgit v1.2.1 From 16bd4df083135e2e4a263b2e1bdd71b78a875ef7 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 31 Dec 2015 22:59:06 +0100 Subject: Fix a typo in method description --- lib/api/variables.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/variables.rb b/lib/api/variables.rb index fc63ac2f56a..b8bbcb6ce3b 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -18,7 +18,7 @@ module API present paginate(variables), with: Entities::Variable end - # Get specifica bariable of a project + # Get specifica variable of a project # # Parameters: # id (required) - The ID of a project -- cgit v1.2.1 From b60c146267dfa8dc1c170426e1817c6b2a168d1a Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 7 Jan 2016 13:49:38 +0100 Subject: Change :variable_id to :key as resource ID in API --- app/models/ci/variable.rb | 6 +++++- lib/api/variables.rb | 41 ++++++++++++++---------------------- spec/requests/api/variables_spec.rb | 42 +++++++++++++------------------------ 3 files changed, 36 insertions(+), 53 deletions(-) diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index 0e2712086ca..1d9ee91a31c 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -18,8 +18,12 @@ module Ci belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id - validates_presence_of :key validates_uniqueness_of :key, scope: :gl_project_id + validates :key, + presence: true, + length: { within: 0..255 }, + format: { with: /\A[a-zA-Z0-9_]+\z/, + message: "can contain only letters, digits and '_'." } attr_encrypted :value, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base end diff --git a/lib/api/variables.rb b/lib/api/variables.rb index b8bbcb6ce3b..cc038e5731d 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -22,19 +22,12 @@ module API # # Parameters: # id (required) - The ID of a project - # variable_id (required) - The ID OR `key` of variable to show; if variable_id contains only digits it's treated - # as ID other ways it's treated as `key` + # key (required) - The `key` of variable # Example Request: - # GET /projects/:id/variables/:variable_id - get ':id/variables/:variable_id' do - variable_id = params[:variable_id] - variables = user_project.variables - variables = - if variable_id.match(/^\d+$/) - variables.where(id: variable_id.to_i) - else - variables.where(key: variable_id) - end + # GET /projects/:id/variables/:key + get ':id/variables/:key' do + key = params[:key] + variables = user_project.variables.where(key: key) return not_found!('Variable') if variables.empty? @@ -45,8 +38,8 @@ module API # # Parameters: # id (required) - The ID of a project - # key (required) - The key of variable being created - # value (required) - The value of variable being created + # key (required) - The key of variable + # value (required) - The value of variable # Example Request: # POST /projects/:id/variables post ':id/variables' do @@ -63,17 +56,15 @@ module API # # Parameters: # id (required) - The ID of a project - # variable_id (required) - The ID of a variable - # key (optional) - new value for `key` field of variable - # value (optional) - new value for `value` field of variable + # key (optional) - The `key` of variable + # value (optional) - New value for `value` field of variable # Example Request: - # PUT /projects/:id/variables/:variable_id - put ':id/variables/:variable_id' do - variable = user_project.variables.where(id: params[:variable_id].to_i).first + # PUT /projects/:id/variables/:key + put ':id/variables/:key' do + variable = user_project.variables.where(key: params[:key]).first return not_found!('Variable') unless variable - variable.key = params[:key] variable.value = params[:value] variable.save! @@ -84,11 +75,11 @@ module API # # Parameters: # id (required) - The ID of a project - # variable_id (required) - The ID of a variable + # key (required) - The ID of a variable # Exanoke Reqyest: - # DELETE /projects/:id/variables/:variable_id - delete ':id/variables/:variable_id' do - variable = user_project.variables.where(id: params[:variable_id].to_i).first + # DELETE /projects/:id/variables/:key + delete ':id/variables/:key' do + variable = user_project.variables.where(key: params[:key]).first return not_found!('Variable') unless variable diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index bf0dd77473a..214d7d5a0cc 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -37,26 +37,17 @@ describe API::API, api: true do end end - describe 'GET /projects/:id/variables/:variable_id' do + describe 'GET /projects/:id/variables/:key' do context 'authorized user with proper permissions' do - it 'should return project variable details when ID is used as :variable_id' do - get api("/projects/#{project.id}/variables/#{variable.id}", user) - - expect(response.status).to eq(200) - expect(json_response['key']).to eq(variable.key) - expect(json_response['value']).to eq(variable.value) - end - - it 'should return project variable details when `key` is used as :variable_id' do + it 'should return project variable details' do get api("/projects/#{project.id}/variables/#{variable.key}", user) expect(response.status).to eq(200) - expect(json_response['id']).to eq(variable.id) expect(json_response['value']).to eq(variable.value) end it 'should responde with 404 Not Found if requesting non-existing variable' do - get api("/projects/#{project.id}/variables/9999", user) + get api("/projects/#{project.id}/variables/non_existing_variable", user) expect(response.status).to eq(404) end @@ -64,7 +55,7 @@ describe API::API, api: true do context 'authorized user with invalid permissions' do it 'should not return project variable details' do - get api("/projects/#{project.id}/variables/#{variable.id}", user2) + get api("/projects/#{project.id}/variables/#{variable.key}", user2) expect(response.status).to eq(403) end @@ -72,7 +63,7 @@ describe API::API, api: true do context 'unauthorized user' do it 'should not return project variable details' do - get api("/projects/#{project.id}/variables/#{variable.id}") + get api("/projects/#{project.id}/variables/#{variable.key}") expect(response.status).to eq(401) end @@ -117,26 +108,23 @@ describe API::API, api: true do end end - describe 'PUT /projects/:id/variables/:variable_id' do + describe 'PUT /projects/:id/variables/:key' do context 'authorized user with proper permissions' do it 'should update variable data' do initial_variable = project.variables.first - key_before = initial_variable.key value_before = initial_variable.value - put api("/projects/#{project.id}/variables/#{variable.id}", user), key: 'TEST_VARIABLE_1_UP', value: 'VALUE_1_UP' + put api("/projects/#{project.id}/variables/#{variable.key}", user), value: 'VALUE_1_UP' updated_variable = project.variables.first expect(response.status).to eq(200) - expect(key_before).to eq(variable.key) expect(value_before).to eq(variable.value) - expect(updated_variable.key).to eq('TEST_VARIABLE_1_UP') expect(updated_variable.value).to eq('VALUE_1_UP') end it 'should responde with 404 Not Found if requesting non-existing variable' do - put api("/projects/#{project.id}/variables/9999", user) + put api("/projects/#{project.id}/variables/non_existing_variable", user) expect(response.status).to eq(404) end @@ -144,7 +132,7 @@ describe API::API, api: true do context 'authorized user with invalid permissions' do it 'should not update variable' do - put api("/projects/#{project.id}/variables/#{variable.id}", user2) + put api("/projects/#{project.id}/variables/#{variable.key}", user2) expect(response.status).to eq(403) end @@ -152,24 +140,24 @@ describe API::API, api: true do context 'unauthorized user' do it 'should not update variable' do - put api("/projects/#{project.id}/variables/#{variable.id}") + put api("/projects/#{project.id}/variables/#{variable.key}") expect(response.status).to eq(401) end end end - describe 'DELETE /projects/:id/variables/:variable_id' do + describe 'DELETE /projects/:id/variables/:key' do context 'authorized user with proper permissions' do it 'should delete variable' do expect do - delete api("/projects/#{project.id}/variables/#{variable.id}", user) + delete api("/projects/#{project.id}/variables/#{variable.key}", user) end.to change{project.variables.count}.by(-1) expect(response.status).to eq(200) end it 'should responde with 404 Not Found if requesting non-existing variable' do - delete api("/projects/#{project.id}/variables/9999", user) + delete api("/projects/#{project.id}/variables/non_existing_variable", user) expect(response.status).to eq(404) end @@ -177,7 +165,7 @@ describe API::API, api: true do context 'authorized user with invalid permissions' do it 'should not delete variable' do - delete api("/projects/#{project.id}/variables/#{variable.id}", user2) + delete api("/projects/#{project.id}/variables/#{variable.key}", user2) expect(response.status).to eq(403) end @@ -185,7 +173,7 @@ describe API::API, api: true do context 'unauthorized user' do it 'should not delete variable' do - delete api("/projects/#{project.id}/variables/#{variable.id}") + delete api("/projects/#{project.id}/variables/#{variable.key}") expect(response.status).to eq(401) end -- cgit v1.2.1 From b60445906849e84ff52ac6a5d7d501bb5a21eb60 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 7 Jan 2016 14:10:49 +0100 Subject: Update ./doc/api --- doc/api/README.md | 1 + doc/api/variables.md | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++ lib/api/entities.rb | 2 +- 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 doc/api/variables.md diff --git a/doc/api/README.md b/doc/api/README.md index 25a31b235cc..198b4ddfee1 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -23,6 +23,7 @@ - [Namespaces](namespaces.md) - [Settings](settings.md) - [Keys](keys.md) +- [Variables](variables.md) ## Clients diff --git a/doc/api/variables.md b/doc/api/variables.md new file mode 100644 index 00000000000..cdc9ba42254 --- /dev/null +++ b/doc/api/variables.md @@ -0,0 +1,106 @@ +# Variables + +## Variables keys + +All variable keys must contains only letters, digits and '\_'. They must also be no longer than 255 characters. + +## List project variables + +Get list of variables of a project. + +``` +GET /projects/:id/variables +``` + +Parameters: + +- `id` (required) - The ID of a project + +```json +[ + { + "key": "TEST_VARIABLE_1", + "value": "TEST_1" + }, + { + "key": "TEST_VARIABLE_2", + "value": "TEST_2" + } +] +``` + +## Show variable details + +Get details of specifica variable of a project. + +``` +GET /projects/:id/variables/:key +``` + +Parameters: + +- `id` (required) - The ID of a project +- `key` (required) - The `key` of variable + +```json +{ + "key": "TEST_VARIABLE_1", + "value": "TEST_1" +} +``` + +## Create variable + +Create new variable in project. + +``` +POST /projects/:id/variables +``` + +Parameters: + +- `id` (required) - The ID of a project +- `key` (required) - The `key` for variable +- `value` (required) - The `value` for variable + +```json +{ + "key": "NEW_VARIABLE", + "value": "new value" +} +``` + +## Update variable + +Update variable. + +``` +PUT /projects/:id/variables/:key +``` + +Parameters: + +- `id` (required) - The ID of a project +- `key` (required) - The `key` for variable +- `value` (required) - The `value` for variable + +```json +{ + "key": "NEW_VARIABLE", + "value": "updated value" +} +``` + +## Remove variable + +Remove variable. + +``` +DELETE /projects/:id/variables/:key +``` + +Parameters: + +- `id` (required) - The ID of a project +- `key` (required) - The `key` for variable + diff --git a/lib/api/entities.rb b/lib/api/entities.rb index f71d072f269..db3164d9d9c 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -367,7 +367,7 @@ module API end class Variable < Grape::Entity - expose :id, :key, :value + expose :key, :value end end end -- cgit v1.2.1 From bc7ef8e5b7a002ca6bc2d7a5e6be11b4a59b6710 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Tue, 29 Dec 2015 17:53:55 -0200 Subject: Add ldap_blocked as new state to users state machine --- app/models/user.rb | 12 +++++++++++- spec/models/user_spec.rb | 44 ++++++++++++++++++++++++++++---------------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 46b36c605b0..67b47b0f329 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -198,16 +198,26 @@ class User < ActiveRecord::Base transition active: :blocked end + event :ldap_block do + transition active: :ldap_blocked + end + event :activate do transition blocked: :active end + + state :blocked, :ldap_blocked do + def blocked? + true + end + end end mount_uploader :avatar, AvatarUploader # Scopes scope :admins, -> { where(admin: true) } - scope :blocked, -> { with_state(:blocked) } + scope :blocked, -> { with_states(:blocked, :ldap_blocked) } scope :active, -> { with_state(:active) } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 3cd63b2b0e8..0bef68e2885 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -569,27 +569,39 @@ describe User, models: true do end end - describe :ldap_user? do - it "is true if provider name starts with ldap" do - user = create(:omniauth_user, provider: 'ldapmain') - expect( user.ldap_user? ).to be_truthy - end + context 'ldap synchronized user' do + describe :ldap_user? do + it 'is true if provider name starts with ldap' do + user = create(:omniauth_user, provider: 'ldapmain') + expect(user.ldap_user?).to be_truthy + end - it "is false for other providers" do - user = create(:omniauth_user, provider: 'other-provider') - expect( user.ldap_user? ).to be_falsey + it 'is false for other providers' do + user = create(:omniauth_user, provider: 'other-provider') + expect(user.ldap_user?).to be_falsey + end + + it 'is false if no extern_uid is provided' do + user = create(:omniauth_user, extern_uid: nil) + expect(user.ldap_user?).to be_falsey + end end - it "is false if no extern_uid is provided" do - user = create(:omniauth_user, extern_uid: nil) - expect( user.ldap_user? ).to be_falsey + describe :ldap_identity do + it 'returns ldap identity' do + user = create :omniauth_user + expect(user.ldap_identity.provider).not_to be_empty + end end - end - describe :ldap_identity do - it "returns ldap identity" do - user = create :omniauth_user - expect(user.ldap_identity.provider).not_to be_empty + describe '#ldap_block' do + let(:user) { create(:omniauth_user, provider: 'ldapmain', name: 'John Smith') } + + it 'blocks user flaging the action caming from ldap' do + user.ldap_block + expect(user.blocked?).to be_truthy + expect(user.ldap_blocked?).to be_truthy + end end end -- cgit v1.2.1 From ba9855d4877998e3574907cc542fcab15a9d1353 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Tue, 29 Dec 2015 18:58:38 -0200 Subject: Prevent ldap_blocked users from being unblocked by the Admin UI --- app/assets/stylesheets/framework/buttons.scss | 3 +++ app/controllers/admin/users_controller.rb | 4 ++- app/views/admin/users/index.html.haml | 7 ++++- spec/controllers/admin/users_controller_spec.rb | 35 ++++++++++++++++++------- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 97a94638847..e2376363485 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -132,6 +132,9 @@ margin-right: 0px; } } + &.disabled { + pointer-events: auto !important; + } } .btn-block { diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index d7c927d444c..87f4fb455b8 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -40,7 +40,9 @@ class Admin::UsersController < Admin::ApplicationController end def unblock - if user.activate + if user.ldap_blocked? + redirect_back_or_admin_user(alert: "This user cannot be unlocked manually from GitLab") + elsif user.activate redirect_back_or_admin_user(notice: "Successfully unblocked") else redirect_back_or_admin_user(alert: "Error occurred. User was not unblocked") diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index a92c9c152b9..911c4d0cf12 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -90,7 +90,12 @@   = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn btn-xs" - unless user == current_user - - if user.blocked? + - if user.ldap_blocked? + = link_to '#', title: 'Cannot unblock LDAP blocked users', data: {toggle: 'tooltip'}, class: 'btn btn-xs btn-success disabled' do + %i.fa.fa-lock + Unblock + = '' + - elsif user.blocked? = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-xs btn-success" - else = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs btn-warning" diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index 8b7af4d3a0a..5b1f65d7aff 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -34,17 +34,34 @@ describe Admin::UsersController do end describe 'PUT unblock/:id' do - let(:user) { create(:user) } - - before do - user.block + context 'ldap blocked users' do + let(:user) { create(:omniauth_user, provider: 'ldapmain') } + + before do + user.ldap_block + end + + it 'will not unblock user' do + put :unblock, id: user.username + user.reload + expect(user.blocked?).to be_truthy + expect(flash[:alert]).to eq 'This user cannot be unlocked manually from GitLab' + end end - it 'unblocks user' do - put :unblock, id: user.username - user.reload - expect(user.blocked?).to be_falsey - expect(flash[:notice]).to eq 'Successfully unblocked' + context 'manually blocked users' do + let(:user) { create(:user) } + + before do + user.block + end + + it 'unblocks user' do + put :unblock, id: user.username + user.reload + expect(user.blocked?).to be_falsey + expect(flash[:notice]).to eq 'Successfully unblocked' + end end end -- cgit v1.2.1 From 6e7db8e23e169bcbf0847ece27b9e44e00ae572b Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Wed, 30 Dec 2015 16:52:02 -0200 Subject: Prevent ldap_blocked users from being blocked/unblocked by the API --- doc/api/users.md | 6 ++++-- lib/api/users.rb | 12 ++++++++---- spec/requests/api/users_spec.rb | 23 ++++++++++++++++++----- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/doc/api/users.md b/doc/api/users.md index 773fe36d277..b7fc903825e 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -558,7 +558,8 @@ Parameters: - `uid` (required) - id of specified user -Will return `200 OK` on success, or `404 User Not Found` is user cannot be found. +Will return `200 OK` on success, `404 User Not Found` is user cannot be found or +`403 Forbidden` when trying to block an already blocked user by LDAP synchronization. ## Unblock user @@ -572,4 +573,5 @@ Parameters: - `uid` (required) - id of specified user -Will return `200 OK` on success, or `404 User Not Found` is user cannot be found. +Will return `200 OK` on success, `404 User Not Found` is user cannot be found or +`403 Forbidden` when trying to unblock a user blocked by LDAP synchronization. diff --git a/lib/api/users.rb b/lib/api/users.rb index 0d7813428e2..01fd90139b0 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -284,10 +284,12 @@ module API authenticated_as_admin! user = User.find_by(id: params[:id]) - if user + if !user + not_found!('User') + elsif !user.ldap_blocked? user.block else - not_found!('User') + forbidden!('LDAP blocked users cannot be modified by the API') end end @@ -299,10 +301,12 @@ module API authenticated_as_admin! user = User.find_by(id: params[:id]) - if user + if !user + not_found!('User') + elsif !user.ldap_blocked? user.activate else - not_found!('User') + forbidden!('LDAP blocked users cannot be unblocked by the API') end end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 4f278551d07..b82c5c7685f 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -8,6 +8,8 @@ describe API::API, api: true do let(:key) { create(:key, user: user) } let(:email) { create(:email, user: user) } let(:omniauth_user) { create(:omniauth_user) } + let(:ldap_user) { create(:omniauth_user, provider: 'ldapmain') } + let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') } describe "GET /users" do context "when unauthenticated" do @@ -783,6 +785,12 @@ describe API::API, api: true do expect(user.reload.state).to eq('blocked') end + it 'should not re-block ldap blocked users' do + put api("/users/#{ldap_blocked_user.id}/block", admin) + expect(response.status).to eq(403) + expect(ldap_blocked_user.reload.state).to eq('ldap_blocked') + end + it 'should not be available for non admin users' do put api("/users/#{user.id}/block", user) expect(response.status).to eq(403) @@ -797,7 +805,9 @@ describe API::API, api: true do end describe 'PUT /user/:id/unblock' do + let(:blocked_user) { create(:user, state: 'blocked') } before { admin } + it 'should unblock existing user' do put api("/users/#{user.id}/unblock", admin) expect(response.status).to eq(200) @@ -805,12 +815,15 @@ describe API::API, api: true do end it 'should unblock a blocked user' do - put api("/users/#{user.id}/block", admin) - expect(response.status).to eq(200) - expect(user.reload.state).to eq('blocked') - put api("/users/#{user.id}/unblock", admin) + put api("/users/#{blocked_user.id}/unblock", admin) expect(response.status).to eq(200) - expect(user.reload.state).to eq('active') + expect(blocked_user.reload.state).to eq('active') + end + + it 'should not unblock ldap blocked users' do + put api("/users/#{ldap_blocked_user.id}/unblock", admin) + expect(response.status).to eq(403) + expect(ldap_blocked_user.reload.state).to eq('ldap_blocked') end it 'should not be available for non admin users' do -- cgit v1.2.1 From d6dc088affeee4568e771e1d7894e0bcdb955af8 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Wed, 30 Dec 2015 20:56:26 -0200 Subject: LDAP synchronization block/unblock new states --- lib/gitlab/ldap/access.rb | 6 +++--- spec/lib/gitlab/ldap/access_spec.rb | 34 ++++++++++++++-------------------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb index c438a3d167b..76cb48d7aa6 100644 --- a/lib/gitlab/ldap/access.rb +++ b/lib/gitlab/ldap/access.rb @@ -37,15 +37,15 @@ module Gitlab # Block user in GitLab if he/she was blocked in AD if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter) - user.block + user.ldap_block false else - user.activate if user.blocked? && !ldap_config.block_auto_created_users + user.activate if (user.blocked? && !ldap_config.block_auto_created_users) || user.ldap_blocked? true end else # Block the user if they no longer exist in LDAP/AD - user.block + user.ldap_block false end rescue diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb index a628d0c0157..f58d70e809c 100644 --- a/spec/lib/gitlab/ldap/access_spec.rb +++ b/spec/lib/gitlab/ldap/access_spec.rb @@ -13,64 +13,59 @@ describe Gitlab::LDAP::Access, lib: true do end it { is_expected.to be_falsey } - + it 'should block user in GitLab' do access.allowed? expect(user).to be_blocked + expect(user).to be_ldap_blocked end end context 'when the user is found' do before do - allow(Gitlab::LDAP::Person). - to receive(:find_by_dn).and_return(:ldap_user) + allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(:ldap_user) end context 'and the user is disabled via active directory' do before do - allow(Gitlab::LDAP::Person). - to receive(:disabled_via_active_directory?).and_return(true) + allow(Gitlab::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(true) end it { is_expected.to be_falsey } - it "should block user in GitLab" do + it 'should block user in GitLab' do access.allowed? expect(user).to be_blocked + expect(user).to be_ldap_blocked end end context 'and has no disabled flag in active diretory' do before do user.block - - allow(Gitlab::LDAP::Person). - to receive(:disabled_via_active_directory?).and_return(false) + allow(Gitlab::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(false) end it { is_expected.to be_truthy } context 'when auto-created users are blocked' do - before do - allow_any_instance_of(Gitlab::LDAP::Config). - to receive(:block_auto_created_users).and_return(true) + allow_any_instance_of(Gitlab::LDAP::Config).to receive(:block_auto_created_users).and_return(true) end - it "does not unblock user in GitLab" do + it 'does not unblock user in GitLab' do access.allowed? expect(user).to be_blocked + expect(user).not_to be_ldap_blocked # this block is handled by omniauth not by our internal logic end end - context "when auto-created users are not blocked" do - + context 'when auto-created users are not blocked' do before do - allow_any_instance_of(Gitlab::LDAP::Config). - to receive(:block_auto_created_users).and_return(false) + allow_any_instance_of(Gitlab::LDAP::Config).to receive(:block_auto_created_users).and_return(false) end - it "should unblock user in GitLab" do + it 'should unblock user in GitLab' do access.allowed? expect(user).not_to be_blocked end @@ -80,8 +75,7 @@ describe Gitlab::LDAP::Access, lib: true do context 'without ActiveDirectory enabled' do before do allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true) - allow_any_instance_of(Gitlab::LDAP::Config). - to receive(:active_directory).and_return(false) + allow_any_instance_of(Gitlab::LDAP::Config).to receive(:active_directory).and_return(false) end it { is_expected.to be_truthy } -- cgit v1.2.1 From ec67e9be1d7486199b47e19c766202a8bfdefe93 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Wed, 6 Jan 2016 05:38:52 -0200 Subject: Repair ldap_blocked state when no ldap identity exist anymore --- app/controllers/admin/identities_controller.rb | 2 ++ app/models/identity.rb | 4 +++ app/models/user.rb | 1 + app/services/repair_ldap_blocked_user_service.rb | 15 +++++++++ .../admin/identities_controller_spec.rb | 26 +++++++++++++++ spec/models/identity_spec.rb | 38 ++++++++++++++++++++++ .../repair_ldap_blocked_user_service_spec.rb | 23 +++++++++++++ 7 files changed, 109 insertions(+) create mode 100644 app/services/repair_ldap_blocked_user_service.rb create mode 100644 spec/controllers/admin/identities_controller_spec.rb create mode 100644 spec/models/identity_spec.rb create mode 100644 spec/services/repair_ldap_blocked_user_service_spec.rb diff --git a/app/controllers/admin/identities_controller.rb b/app/controllers/admin/identities_controller.rb index e383fe38ea6..9ba10487512 100644 --- a/app/controllers/admin/identities_controller.rb +++ b/app/controllers/admin/identities_controller.rb @@ -26,6 +26,7 @@ class Admin::IdentitiesController < Admin::ApplicationController def update if @identity.update_attributes(identity_params) + RepairLdapBlockedUserService.new(@user, @identity).execute redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully updated.' else render :edit @@ -34,6 +35,7 @@ class Admin::IdentitiesController < Admin::ApplicationController def destroy if @identity.destroy + RepairLdapBlockedUserService.new(@user, @identity).execute redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully removed.' else redirect_to admin_user_identities_path(@user), alert: 'Failed to remove user identity.' diff --git a/app/models/identity.rb b/app/models/identity.rb index 8bcdc194953..830b99fa3f2 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -18,4 +18,8 @@ class Identity < ActiveRecord::Base validates :provider, presence: true validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider } validates :user_id, uniqueness: { scope: :provider } + + def is_ldap? + provider.starts_with?('ldap') + end end diff --git a/app/models/user.rb b/app/models/user.rb index 67b47b0f329..5eed9cf91c7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -196,6 +196,7 @@ class User < ActiveRecord::Base state_machine :state, initial: :active do event :block do transition active: :blocked + transition ldap_blocked: :blocked end event :ldap_block do diff --git a/app/services/repair_ldap_blocked_user_service.rb b/app/services/repair_ldap_blocked_user_service.rb new file mode 100644 index 00000000000..ceca15414e0 --- /dev/null +++ b/app/services/repair_ldap_blocked_user_service.rb @@ -0,0 +1,15 @@ +class RepairLdapBlockedUserService + attr_accessor :user, :identity + + def initialize(user, identity) + @user, @identity = user, identity + end + + def execute + if identity.destroyed? + user.block if identity.is_ldap? && user.ldap_blocked? && !user.ldap_user? + else + user.block if !identity.is_ldap? && user.ldap_blocked? && !user.ldap_user? + end + end +end diff --git a/spec/controllers/admin/identities_controller_spec.rb b/spec/controllers/admin/identities_controller_spec.rb new file mode 100644 index 00000000000..c131d22a30a --- /dev/null +++ b/spec/controllers/admin/identities_controller_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe Admin::IdentitiesController do + let(:admin) { create(:admin) } + before { sign_in(admin) } + + describe 'UPDATE identity' do + let(:user) { create(:omniauth_user, provider: 'ldapmain', extern_uid: 'uid=myuser,ou=people,dc=example,dc=com') } + + it 'repairs ldap blocks' do + expect_any_instance_of(RepairLdapBlockedUserService).to receive(:execute) + + put :update, user_id: user.username, id: user.ldap_identity.id, identity: { provider: 'twitter' } + end + end + + describe 'DELETE identity' do + let(:user) { create(:omniauth_user, provider: 'ldapmain', extern_uid: 'uid=myuser,ou=people,dc=example,dc=com') } + + it 'repairs ldap blocks' do + expect_any_instance_of(RepairLdapBlockedUserService).to receive(:execute) + + delete :destroy, user_id: user.username, id: user.ldap_identity.id + end + end +end diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb new file mode 100644 index 00000000000..107bfc17782 --- /dev/null +++ b/spec/models/identity_spec.rb @@ -0,0 +1,38 @@ +# == Schema Information +# +# Table name: identities +# +# id :integer not null, primary key +# extern_uid :string(255) +# provider :string(255) +# user_id :integer +# created_at :datetime +# updated_at :datetime +# + +require 'spec_helper' + +RSpec.describe Identity, models: true do + + describe 'relations' do + it { is_expected.to belong_to(:user) } + end + + describe 'fields' do + it { is_expected.to respond_to(:provider) } + it { is_expected.to respond_to(:extern_uid) } + end + + describe '#is_ldap?' do + let(:ldap_identity) { create(:identity, provider: 'ldapmain') } + let(:other_identity) { create(:identity, provider: 'twitter') } + + it 'returns true if it is a ldap identity' do + expect(ldap_identity.is_ldap?).to be_truthy + end + + it 'returns false if it is not a ldap identity' do + expect(other_identity.is_ldap?).to be_falsey + end + end +end diff --git a/spec/services/repair_ldap_blocked_user_service_spec.rb b/spec/services/repair_ldap_blocked_user_service_spec.rb new file mode 100644 index 00000000000..2a2114d038c --- /dev/null +++ b/spec/services/repair_ldap_blocked_user_service_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe RepairLdapBlockedUserService, services: true do + let(:user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') } + let(:identity) { user.ldap_identity } + subject(:service) { RepairLdapBlockedUserService.new(user, identity) } + + describe '#execute' do + it 'change to normal block after destroying last ldap identity' do + identity.destroy + service.execute + + expect(user.reload).not_to be_ldap_blocked + end + + it 'change to normal block after changing last ldap identity to another provider' do + identity.update_attribute(:provider, 'twitter') + service.execute + + expect(user.reload).not_to be_ldap_blocked + end + end +end -- cgit v1.2.1 From 47e4613f4adc2d6ef4b066a87ec772ef8044bdd5 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Thu, 7 Jan 2016 14:01:01 -0200 Subject: Code style fixes and some code simplified --- app/controllers/admin/identities_controller.rb | 4 ++-- app/services/repair_ldap_blocked_user_service.rb | 18 ++++++++++-------- lib/gitlab/ldap/access.rb | 4 +++- spec/services/repair_ldap_blocked_user_service_spec.rb | 2 +- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/controllers/admin/identities_controller.rb b/app/controllers/admin/identities_controller.rb index 9ba10487512..79a53556f0a 100644 --- a/app/controllers/admin/identities_controller.rb +++ b/app/controllers/admin/identities_controller.rb @@ -26,7 +26,7 @@ class Admin::IdentitiesController < Admin::ApplicationController def update if @identity.update_attributes(identity_params) - RepairLdapBlockedUserService.new(@user, @identity).execute + RepairLdapBlockedUserService.new(@user).execute redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully updated.' else render :edit @@ -35,7 +35,7 @@ class Admin::IdentitiesController < Admin::ApplicationController def destroy if @identity.destroy - RepairLdapBlockedUserService.new(@user, @identity).execute + RepairLdapBlockedUserService.new(@user).execute redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully removed.' else redirect_to admin_user_identities_path(@user), alert: 'Failed to remove user identity.' diff --git a/app/services/repair_ldap_blocked_user_service.rb b/app/services/repair_ldap_blocked_user_service.rb index ceca15414e0..863cef7ff61 100644 --- a/app/services/repair_ldap_blocked_user_service.rb +++ b/app/services/repair_ldap_blocked_user_service.rb @@ -1,15 +1,17 @@ class RepairLdapBlockedUserService - attr_accessor :user, :identity + attr_accessor :user - def initialize(user, identity) - @user, @identity = user, identity + def initialize(user) + @user = user end def execute - if identity.destroyed? - user.block if identity.is_ldap? && user.ldap_blocked? && !user.ldap_user? - else - user.block if !identity.is_ldap? && user.ldap_blocked? && !user.ldap_user? - end + user.block if ldap_hard_blocked? + end + + private + + def ldap_hard_blocked? + user.ldap_blocked? && !user.ldap_user? end end diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb index 76cb48d7aa6..ebd9260ad5d 100644 --- a/lib/gitlab/ldap/access.rb +++ b/lib/gitlab/ldap/access.rb @@ -40,7 +40,9 @@ module Gitlab user.ldap_block false else - user.activate if (user.blocked? && !ldap_config.block_auto_created_users) || user.ldap_blocked? + if (user.blocked? && !ldap_config.block_auto_created_users) || user.ldap_blocked? + user.activate + end true end else diff --git a/spec/services/repair_ldap_blocked_user_service_spec.rb b/spec/services/repair_ldap_blocked_user_service_spec.rb index 2a2114d038c..ce7d1455975 100644 --- a/spec/services/repair_ldap_blocked_user_service_spec.rb +++ b/spec/services/repair_ldap_blocked_user_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe RepairLdapBlockedUserService, services: true do let(:user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') } let(:identity) { user.ldap_identity } - subject(:service) { RepairLdapBlockedUserService.new(user, identity) } + subject(:service) { RepairLdapBlockedUserService.new(user) } describe '#execute' do it 'change to normal block after destroying last ldap identity' do -- cgit v1.2.1 From ac6a10f3e88c5d2081b8638df63016089517a844 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Tue, 12 Jan 2016 12:29:10 -0200 Subject: Codestyle changes --- app/models/identity.rb | 2 +- lib/api/users.rb | 6 +++--- spec/models/identity_spec.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/models/identity.rb b/app/models/identity.rb index 830b99fa3f2..e1915b079d4 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -19,7 +19,7 @@ class Identity < ActiveRecord::Base validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider } validates :user_id, uniqueness: { scope: :provider } - def is_ldap? + def ldap? provider.starts_with?('ldap') end end diff --git a/lib/api/users.rb b/lib/api/users.rb index 01fd90139b0..fd2128bd179 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -303,10 +303,10 @@ module API if !user not_found!('User') - elsif !user.ldap_blocked? - user.activate - else + elsif user.ldap_blocked? forbidden!('LDAP blocked users cannot be unblocked by the API') + else + user.activate end end end diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb index 107bfc17782..5afe042e154 100644 --- a/spec/models/identity_spec.rb +++ b/spec/models/identity_spec.rb @@ -28,11 +28,11 @@ RSpec.describe Identity, models: true do let(:other_identity) { create(:identity, provider: 'twitter') } it 'returns true if it is a ldap identity' do - expect(ldap_identity.is_ldap?).to be_truthy + expect(ldap_identity.ldap?).to be_truthy end it 'returns false if it is not a ldap identity' do - expect(other_identity.is_ldap?).to be_falsey + expect(other_identity.ldap?).to be_falsey end end end -- cgit v1.2.1 From efb3395b4fc0425ebbc2437ad03f0cd5fc851863 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 12 Jan 2016 19:32:44 +0100 Subject: Remove blank line --- lib/api/api.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/api/api.rb b/lib/api/api.rb index a9e1913f0f2..098dd975840 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -54,7 +54,6 @@ module API mount Keys mount Tags mount Triggers - mount Variables end end -- cgit v1.2.1 From df548285804fdc40ac7c4f36601e87a534792a4a Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 13 Jan 2016 12:47:11 +0100 Subject: Add some fixes after review --- lib/api/variables.rb | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/api/variables.rb b/lib/api/variables.rb index cc038e5731d..0c3fb5c8a77 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -27,11 +27,11 @@ module API # GET /projects/:id/variables/:key get ':id/variables/:key' do key = params[:key] - variables = user_project.variables.where(key: key) + variable = user_project.variables.find_by(key: key.to_s) - return not_found!('Variable') if variables.empty? + return not_found!('Variable') unless variable - present variables.first, with: Entities::Variable + present variable, with: Entities::Variable end # Create a new variable in project @@ -46,10 +46,12 @@ module API required_attributes! [:key, :value] variable = user_project.variables.create(key: params[:key], value: params[:value]) - return render_validation_error!(variable) unless variable.valid? - variable.save! - present variable, with: Entities::Variable + if variable.valid? + present variable, with: Entities::Variable + else + render_validation_error!(variable) + end end # Update existing variable of a project @@ -61,14 +63,16 @@ module API # Example Request: # PUT /projects/:id/variables/:key put ':id/variables/:key' do - variable = user_project.variables.where(key: params[:key]).first + variable = user_project.variables.find_by(key: params[:key].to_s) return not_found!('Variable') unless variable - variable.value = params[:value] - variable.save! - - present variable, with: Entities::Variable + attrs = attributes_for_keys [:value] + if variable.update(attrs) + present variable, with: Entities::Variable + else + render_validation_error!(variable) + end end # Delete existing variable of a project @@ -79,10 +83,9 @@ module API # Exanoke Reqyest: # DELETE /projects/:id/variables/:key delete ':id/variables/:key' do - variable = user_project.variables.where(key: params[:key]).first + variable = user_project.variables.find_by(key: params[:key].to_s) return not_found!('Variable') unless variable - variable.destroy present variable, with: Entities::Variable -- cgit v1.2.1 From 99393cde942841e1bee656cebf8e16a25d1300cb Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 13 Jan 2016 13:49:10 +0100 Subject: Version 8.4.0.rc1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index ce669730119..408340137f0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.4.0.pre +8.4.0.rc1 \ No newline at end of file -- cgit v1.2.1 From b433efcf70dbafd5f76ce7140a4e6eb36008eee5 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 13 Jan 2016 14:27:36 +0100 Subject: Add suport for layout without container Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/_page.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index ec7cd79bc54..26159989777 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -24,7 +24,7 @@ .content-wrapper = render "layouts/flash" = yield :flash_message - %div{ class: container_class } + %div{ class: (container_class unless @no_container) } .content .clearfix = yield -- cgit v1.2.1 From 3b3f755b5bfa5348375fffe22ec56fe0f01ac1f5 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 13 Jan 2016 14:37:25 +0100 Subject: New layout without background color Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/activities.js.coffee | 4 ++-- app/assets/stylesheets/framework/blocks.scss | 12 ++++++----- app/assets/stylesheets/framework/files.scss | 2 -- app/assets/stylesheets/framework/flash.scss | 2 ++ app/assets/stylesheets/framework/header.scss | 1 + app/assets/stylesheets/framework/layout.scss | 2 -- app/assets/stylesheets/framework/lists.scss | 6 ++---- app/assets/stylesheets/framework/sidebar.scss | 1 - app/assets/stylesheets/framework/tables.scss | 6 ++---- app/assets/stylesheets/framework/timeline.scss | 4 +--- app/assets/stylesheets/pages/detail_page.scss | 5 +++-- app/assets/stylesheets/pages/events.scss | 4 +--- app/assets/stylesheets/pages/projects.scss | 8 +++---- app/helpers/events_helper.rb | 8 ++++--- app/views/groups/show.html.haml | 30 +++++++++++++++----------- app/views/projects/_activity.html.haml | 2 +- app/views/projects/show.html.haml | 24 ++++++++++++--------- app/views/shared/_event_filter.html.haml | 2 +- 18 files changed, 63 insertions(+), 60 deletions(-) diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee index 63803747413..8b8e5d48aa0 100644 --- a/app/assets/javascripts/activities.js.coffee +++ b/app/assets/javascripts/activities.js.coffee @@ -1,7 +1,7 @@ class @Activities constructor: -> Pager.init 20, true - $(".event-filter .btn").bind "click", (event) => + $(".event-filter a").bind "click", (event) => event.preventDefault() @toggleFilter($(event.currentTarget)) @reloadActivities() @@ -12,7 +12,7 @@ class @Activities toggleFilter: (sender) -> - sender.toggleClass "active" + sender.parent().toggleClass "active" event_filters = $.cookie("event_filter") filter = sender.attr("id").split("_")[0] if event_filters diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index fa0e70847f3..11df5a377ae 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -20,7 +20,8 @@ .content-block, .gray-content-block { - margin: -$gl-padding; + margin-top: -$gl-padding; + margin-bottom: -$gl-padding; background-color: $background-color; padding: $gl-padding; margin-bottom: 0px; @@ -86,10 +87,7 @@ .cover-block { text-align: center; background: $background-color; - margin: -$gl-padding; - margin-bottom: 0; - padding: 44px $gl-padding; - border-bottom: 1px solid $border-color; + padding-top: 44px; position: relative; .avatar-holder { @@ -131,6 +129,10 @@ right: auto; } } + + .cover-menu { + margin: 0; + } } .block-connector { diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index cbfd4bc29b6..f9058c8c309 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -3,8 +3,6 @@ * */ .file-holder { - margin-left: -$gl-padding; - margin-right: -$gl-padding; border: none; border-top: 1px solid #E7E9EE; border-bottom: 1px solid #E7E9EE; diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index 82eb50ad4be..1bfd0213995 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -8,10 +8,12 @@ .flash-notice { @extend .alert; @extend .alert-info; + margin: 0; } .flash-alert { @extend .alert; @extend .alert-danger; + margin: 0; } } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 4dbbb56104b..ba5e72c8c5a 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -28,6 +28,7 @@ header { min-height: $header-height; background-color: #fff; border: none; + border-bottom: 1px solid #EEE; .container-fluid { width: 100% !important; diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index a1a9990241d..e901c78d02f 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -5,8 +5,6 @@ html { } body { - background-color: #F3F3F3 !important; - &.navless { background-color: white !important; } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index bbdb1c038c5..a5e1afd6022 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -109,10 +109,8 @@ ul.content-list { padding: 0; > li { - padding: $gl-padding; + padding: $gl-padding 0; border-color: $table-border-color; - margin-left: -$gl-padding; - margin-right: -$gl-padding; color: $gl-gray; .avatar { @@ -148,7 +146,7 @@ ul.controls { > li { float: left; margin-right: 10px; - + &:last-child { margin-right: 0; } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 83243dd2457..06ce52e26dc 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -21,7 +21,6 @@ .content-wrapper { width: 100%; - padding: 20px; .container-fluid { background: #FFF; diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 793ab3d9bb9..8ae235520da 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -1,13 +1,11 @@ .table-holder { - margin: -$gl-padding; - margin-top: 0; - margin-bottom: 0; + margin: 0; } table { &.table { margin-bottom: $gl-padding; - + .dropdown-menu a { text-decoration: none; } diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index ff41e26ed8a..47b843e5e3d 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -5,10 +5,8 @@ padding: 0; .timeline-entry { - padding: $gl-padding; + padding: $gl-padding 0; border-color: $table-border-color; - margin-left: -$gl-padding; - margin-right: -$gl-padding; color: $gl-gray; border-bottom: 1px solid $border-white-light; diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index deab805dbc2..6844d5b421d 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -1,6 +1,7 @@ .detail-page-header { - margin: -$gl-padding; - padding: 7px $gl-padding; + margin-top: -$gl-padding; + margin-bottom: -$gl-padding; + padding: 7px 0; margin-bottom: 0px; border-bottom: 1px solid $border-color; color: #5c5d5e; diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 984b4b91216..8fa15b35748 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -4,9 +4,7 @@ */ .event-item { font-size: $gl-font-size; - padding: $gl-padding $gl-padding $gl-padding ($gl-padding + $gl-avatar-size + 15px); - margin-left: -$gl-padding; - margin-right: -$gl-padding; + padding: $gl-padding 0 $gl-padding ($gl-avatar-size + 15px); border-bottom: 1px solid $table-border-color; color: #7f8fa4; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index f24b71963a8..a8bf63539bf 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -26,6 +26,8 @@ } .project-home-panel { + padding-bottom: 40px; + border-bottom: 1px solid $border-color; .cover-controls { .project-settings-dropdown { @@ -416,8 +418,6 @@ ul.nav.nav-projects-tabs { .top-area { border-bottom: 1px solid #EEE; - margin: 0 -16px; - padding: 0 $gl-padding; height: 42px; ul.left-top-menu { @@ -574,10 +574,8 @@ pre.light-well { @include basic-list; .project-row { - padding: $gl-padding; + padding: $gl-padding 0; border-color: $table-border-color; - margin-left: -$gl-padding; - margin-right: -$gl-padding; &.no-description { .project { diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index dde83ff36b5..31bf45baeb7 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -27,13 +27,15 @@ module EventsHelper key = key.to_s active = 'active' if @event_filter.active?(key) link_opts = { - class: "event-filter-link btn btn-default #{active}", + class: "event-filter-link", id: "#{key}_event_filter", title: "Filter by #{tooltip.downcase}", } - link_to request.path, link_opts do - content_tag(:span, ' ' + tooltip) + content_tag :li, class: active do + link_to request.path, link_opts do + content_tag(:span, ' ' + tooltip) + end end end diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 48a544fc834..34719481984 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -5,6 +5,9 @@ - if current_user = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") +- @no_container = true +- @blank_container = true + .cover-block .cover-controls - if @group && can?(current_user, :admin_group, @group) @@ -25,8 +28,8 @@ .cover-desc.description = markdown(@group.description, pipeline: :description) -- if can?(current_user, :read_group, @group) - %ul.center-top-menu.no-top + + %ul.center-top-menu.cover-menu %li.active = link_to "#activity", 'data-toggle' => 'tab' do Activity @@ -35,19 +38,22 @@ = link_to "#projects", 'data-toggle' => 'tab' do Projects - .tab-content - .tab-pane.active#activity - .gray-content-block.activity-filter-block - - if current_user - = render "events/event_last_push", event: @last_push +- if can?(current_user, :read_group, @group) + + %div{ class: container_class } + .tab-content + .tab-pane.active#activity + .activity-filter-block + - if current_user + = render "events/event_last_push", event: @last_push - = render 'shared/event_filter' + = render 'shared/event_filter' - .content_list - = spinner + .content_list + = spinner - .tab-pane#projects - = render "projects", projects: @projects + .tab-pane#projects + = render "projects", projects: @projects - else %p.center-top-menu.no-top diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index 101880bd105..6edc94e7a98 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -1,4 +1,4 @@ -.gray-content-block.activity-filter-block +.activity-filter-block - if current_user .pull-right = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'btn rss-btn' do diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 8436be433b1..25dcedf2b0b 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -7,8 +7,10 @@ = render 'shared/no_ssh' = render 'shared/no_password' -= render 'projects/last_push' +- @no_container = true +- @blank_container = true += render 'projects/last_push' = render "home_panel" .project-stats.gray-content-block.second-block @@ -57,15 +59,17 @@ = link_to add_contribution_guide_path(@project) do Add Contribution guide -- if @project.archived? - .text-warning.center.prepend-top-20 - %p - = icon("exclamation-triangle fw") - Archived project! Repository is read-only - - if @repository.commit .content-block.second-block.white - = render 'projects/last_commit', commit: @repository.commit, project: @project + %div{ class: container_class } + = render 'projects/last_commit', commit: @repository.commit, project: @project + +%div{ class: container_class } + - if @project.archived? + .text-warning.center.prepend-top-20 + %p + = icon("exclamation-triangle fw") + Archived project! Repository is read-only -%div{class: "project-show-#{default_project_view}"} - = render default_project_view + %div{class: "project-show-#{default_project_view}"} + = render default_project_view diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index 8495774accc..4426096f742 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -1,4 +1,4 @@ -.btn-group.btn-group-next.event-filter +%ul.left-top-menu.event-filter.no-top = event_filter_link EventFilter.push, 'Push events' = event_filter_link EventFilter.merged, 'Merge events' = event_filter_link EventFilter.comments, 'Comments' -- cgit v1.2.1 From 8c3debb18aae2cda444d25468315bdf340cb3e9e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 13 Jan 2016 15:03:32 +0100 Subject: Start moving all navigation to one class Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework.scss | 1 + app/assets/stylesheets/framework/blocks.scss | 4 -- app/assets/stylesheets/framework/buttons.scss | 27 ----------- app/assets/stylesheets/framework/common.scss | 69 --------------------------- app/assets/stylesheets/framework/mixins.scss | 35 -------------- app/assets/stylesheets/framework/nav.scss | 38 +++++++++++++++ app/views/groups/show.html.haml | 2 +- app/views/shared/_event_filter.html.haml | 2 +- 8 files changed, 41 insertions(+), 137 deletions(-) create mode 100644 app/assets/stylesheets/framework/nav.scss diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 48a4971c8fc..fa7641b1676 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -24,6 +24,7 @@ @import "framework/lists.scss"; @import "framework/markdown_area.scss"; @import "framework/mobile.scss"; +@import "framework/nav.scss"; @import "framework/pagination.scss"; @import "framework/panels.scss"; @import "framework/selects.scss"; diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 11df5a377ae..7abc5955fbd 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -129,10 +129,6 @@ right: auto; } } - - .cover-menu { - margin: 0; - } } .block-connector { diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 97a94638847..8ee2a9c9886 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -153,33 +153,6 @@ } } -.btn-group-next { - .btn { - padding: 9px 0px; - font-size: 15px; - color: #7f8fa4; - border-color: #e7e9ed; - width: 140px; - - .badge { - font-weight: normal; - background-color: #eee; - color: #78a; - } - - &.active { - border-color: $gl-info; - background: $gl-info; - color: #fff; - - .badge { - color: $gl-info; - background-color: white; - } - } - } -} - .btn-clipboard { border: none; } diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 11730000f85..05645116268 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -374,75 +374,6 @@ table { } } -.center-top-menu, .left-top-menu { - @include nav-menu; - text-align: center; - margin-top: 5px; - margin-bottom: $gl-padding; - height: auto; - margin-top: -$gl-padding; - - &.no-bottom { - margin-bottom: 0; - } - - &.no-top { - margin-top: 0; - } - - li a { - display: inline-block; - padding-top: $gl-padding; - padding-bottom: 11px; - margin-bottom: -1px; - } - - &.bottom-border { - border-bottom: 1px solid $border-color; - height: 57px; - } - - &.wide { - margin-left: -$gl-padding; - margin-right: -$gl-padding; - } -} - -.left-top-menu { - text-align: left; - border-bottom: 1px solid #EEE; -} - -.center-middle-menu { - @include nav-menu; - padding: 0; - text-align: center; - margin: -$gl-padding; - margin-top: 0; - margin-bottom: 0; - height: 58px; - border-bottom: 1px solid $border-color; - - li { - &:after { - content: "|"; - color: $border-gray-light; - } - - &:last-child { - &:after { - content: none; - } - } - - > a { - display: inline-block; - text-transform: uppercase; - font-size: 13px; - } - } -} - .dropzone .dz-preview .dz-progress { border-color: $border-color !important; } diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 41fd890f14f..1d5000fe388 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -118,38 +118,3 @@ font-size: 16px; line-height: 24px; } - -@mixin nav-menu { - padding: 0; - margin: 0; - list-style: none; - height: 56px; - - li { - display: inline-block; - - a { - padding: 14px; - font-size: 15px; - line-height: 28px; - color: #959494; - border-bottom: 2px solid transparent; - - &:hover, &:active, &:focus { - text-decoration: none; - outline: none; - } - } - - &.active a { - color: #616060; - border-bottom: 2px solid #4688f1; - } - - .badge { - font-weight: normal; - background-color: #eee; - color: #78a; - } - } -} diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss new file mode 100644 index 00000000000..ffa91278982 --- /dev/null +++ b/app/assets/stylesheets/framework/nav.scss @@ -0,0 +1,38 @@ +.nav-links { + padding: 0; + margin: 0; + list-style: none; + height: 56px; + border-bottom: 1px solid $border-color; + + li { + display: inline-block; + + a { + display: inline-block; + padding-top: $gl-padding; + padding-bottom: 11px; + margin-bottom: -1px; + font-size: 15px; + line-height: 28px; + color: #959494; + border-bottom: 2px solid transparent; + + &:hover, &:active, &:focus { + text-decoration: none; + outline: none; + } + } + + &.active a { + color: #616060; + border-bottom: 2px solid #4688f1; + } + + .badge { + font-weight: normal; + background-color: #eee; + color: #78a; + } + } +} diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 34719481984..24d2f000b40 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -29,7 +29,7 @@ = markdown(@group.description, pipeline: :description) - %ul.center-top-menu.cover-menu + %ul.nav-links %li.active = link_to "#activity", 'data-toggle' => 'tab' do Activity diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index 4426096f742..c38d9313dba 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -1,4 +1,4 @@ -%ul.left-top-menu.event-filter.no-top +%ul.nav-links.event-filter = event_filter_link EventFilter.push, 'Push events' = event_filter_link EventFilter.merged, 'Merge events' = event_filter_link EventFilter.comments, 'Comments' -- cgit v1.2.1 From 03090a88d80cbc8c4333053081882915beb721da Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 13 Jan 2016 15:58:04 +0100 Subject: Replace all navigation menu with nav-links class Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/blocks.scss | 12 +++++- app/assets/stylesheets/framework/mobile.scss | 2 +- app/assets/stylesheets/framework/nav.scss | 5 ++- app/assets/stylesheets/framework/sidebar.scss | 2 +- app/assets/stylesheets/pages/detail_page.scss | 3 -- app/assets/stylesheets/pages/diff.scss | 2 - app/assets/stylesheets/pages/issuable.scss | 2 +- app/assets/stylesheets/pages/merge_requests.scss | 4 +- app/assets/stylesheets/pages/projects.scss | 9 +--- app/views/admin/builds/index.html.haml | 2 +- app/views/dashboard/_activities.html.haml | 4 +- app/views/dashboard/_activity_head.html.haml | 2 +- app/views/dashboard/_groups_head.html.haml | 2 +- app/views/dashboard/_projects_head.html.haml | 2 +- app/views/dashboard/_snippets_head.html.haml | 2 +- app/views/dashboard/milestones/show.html.haml | 2 +- app/views/dashboard/snippets/index.html.haml | 48 ++++++++++++---------- app/views/groups/milestones/show.html.haml | 2 +- app/views/groups/show.html.haml | 2 +- app/views/help/ui.html.haml | 4 +- app/views/projects/_activity.html.haml | 4 +- app/views/projects/_md_preview.html.haml | 2 +- app/views/projects/blob/edit.html.haml | 2 +- app/views/projects/builds/index.html.haml | 2 +- app/views/projects/builds/show.html.haml | 2 +- app/views/projects/commit/_ci_menu.html.haml | 2 +- app/views/projects/commit/show.html.haml | 4 +- app/views/projects/commits/_head.html.haml | 2 +- app/views/projects/graphs/_head.html.haml | 2 +- .../projects/merge_requests/_new_submit.html.haml | 2 +- app/views/projects/merge_requests/_show.html.haml | 2 +- app/views/projects/milestones/show.html.haml | 2 +- app/views/projects/wikis/_nav.html.haml | 2 +- app/views/shared/_milestones_filter.html.haml | 2 +- app/views/shared/issuable/_filter.html.haml | 2 +- app/views/sherlock/queries/show.html.haml | 2 +- app/views/sherlock/transactions/show.html.haml | 2 +- app/views/users/show.html.haml | 2 +- 38 files changed, 80 insertions(+), 73 deletions(-) diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 7abc5955fbd..7c139d980de 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -20,7 +20,7 @@ .content-block, .gray-content-block { - margin-top: -$gl-padding; + margin-top: 0; margin-bottom: -$gl-padding; background-color: $background-color; padding: $gl-padding; @@ -134,3 +134,13 @@ .block-connector { margin-top: -1px; } + +.nav-block { + .controls { + float: right; + + .btn { + margin-top: 7px; + } + } +} diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index c00709fb6bb..fdb4b42fe8e 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -81,7 +81,7 @@ display: none; } - .center-top-menu, .left-top-menu { + .nav-links, .nav-links { li a { font-size: 14px; padding: 19px 10px; diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index ffa91278982..c537d97fb24 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -2,7 +2,7 @@ padding: 0; margin: 0; list-style: none; - height: 56px; + height: auto; border-bottom: 1px solid $border-color; li { @@ -10,6 +10,7 @@ a { display: inline-block; + padding: 14px; padding-top: $gl-padding; padding-bottom: 11px; margin-bottom: -1px; @@ -25,7 +26,7 @@ } &.active a { - color: #616060; + color: #000000; border-bottom: 2px solid #4688f1; } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 06ce52e26dc..540d0b03163 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -24,7 +24,7 @@ .container-fluid { background: #FFF; - padding: $gl-padding; + padding: 0 $gl-padding; &.container-blank { background: none; diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 6844d5b421d..a9605804fb3 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -1,8 +1,5 @@ .detail-page-header { - margin-top: -$gl-padding; - margin-bottom: -$gl-padding; padding: 7px 0; - margin-bottom: 0px; border-bottom: 1px solid $border-color; color: #5c5d5e; font-size: 16px; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index afd6fb73675..c5515bc418c 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -1,7 +1,5 @@ // Common .diff-file { - margin-left: -$gl-padding; - margin-right: -$gl-padding; border: none; border-bottom: 1px solid #E7E9EE; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index d4b44004f4f..7ab77550496 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -30,7 +30,7 @@ margin-top: 7px; } - .center-top-menu { + .nav-links { text-align: left; } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 82effde0bf3..0e95aaff65a 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -3,9 +3,9 @@ * */ .mr-state-widget { - background: #F7F8FA; + background: $background-color; color: $gl-gray; - border: 1px solid #dce0e6; + border: 1px solid $border-color; @include border-radius(2px); form { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index a8bf63539bf..48fdfb1de66 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -228,7 +228,6 @@ } .projects-search-form { - .input-group .form-control { height: 42px; } @@ -418,9 +417,8 @@ ul.nav.nav-projects-tabs { .top-area { border-bottom: 1px solid #EEE; - height: 42px; - ul.left-top-menu { + ul.nav-links { display: inline-block; width: 50%; margin-bottom: 0px; @@ -523,8 +521,7 @@ pre.light-well { } .projects-search-form { - margin: -$gl-padding; - padding: $gl-padding; + padding: $gl-padding 0; padding-bottom: 0; margin-bottom: 0px; @@ -660,8 +657,6 @@ pre.light-well { } .project-show-readme .readme-holder { - margin-left: -$gl-padding; - margin-right: -$gl-padding; padding: ($gl-padding + 7px); border-top: 0; diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index ddd4e1481eb..ebf2b7b60e7 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -4,7 +4,7 @@ - if @all_builds.running_or_pending.any? = link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post - %ul.center-top-menu + %ul.nav-links %li{class: ('active' if @scope.nil?)} = link_to admin_builds_path do All diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml index f98fd9f06ba..dc76599b776 100644 --- a/app/views/dashboard/_activities.html.haml +++ b/app/views/dashboard/_activities.html.haml @@ -1,9 +1,9 @@ .hidden-xs = render "events/event_last_push", event: @last_push -.gray-content-block +.nav-block - if current_user - .pull-right + .controls = link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'btn rss-btn' do %i.fa.fa-rss = render 'shared/event_filter' diff --git a/app/views/dashboard/_activity_head.html.haml b/app/views/dashboard/_activity_head.html.haml index 9f4be025bf2..b78e70ebc1e 100644 --- a/app/views/dashboard/_activity_head.html.haml +++ b/app/views/dashboard/_activity_head.html.haml @@ -1,4 +1,4 @@ -%ul.center-top-menu +%ul.nav-links %li{ class: ("active" unless params[:filter]) } = link_to activity_dashboard_path, class: 'shortcuts-activity', data: {placement: 'right'} do Your Projects diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml index 64bd356f546..6ca97a692b4 100644 --- a/app/views/dashboard/_groups_head.html.haml +++ b/app/views/dashboard/_groups_head.html.haml @@ -1,4 +1,4 @@ -%ul.center-top-menu +%ul.nav-links = nav_link(page: dashboard_groups_path) do = link_to dashboard_groups_path, title: 'Your groups', data: {placement: 'right'} do Your Groups diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index f4a3e3162bf..5c4b58cd688 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -1,7 +1,7 @@ = content_for :flash_message do = render 'shared/project_limit' .top-area - %ul.left-top-menu + %ul.nav-links = nav_link(page: [dashboard_projects_path, root_path]) do = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do Your Projects diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml index 0ae62d6f1b6..b25e8ea1f0c 100644 --- a/app/views/dashboard/_snippets_head.html.haml +++ b/app/views/dashboard/_snippets_head.html.haml @@ -1,4 +1,4 @@ -%ul.center-top-menu +%ul.nav-links = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do Your Snippets diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml index 49a558e8ac9..3810267577c 100644 --- a/app/views/dashboard/milestones/show.html.haml +++ b/app/views/dashboard/milestones/show.html.haml @@ -48,7 +48,7 @@ #{@milestone.open_items_count} open = milestone_progress_bar(@milestone) -%ul.center-top-menu.no-top.no-bottom +%ul.nav-links.no-top.no-bottom %li.active = link_to '#tab-issues', 'data-toggle' => 'tab' do Issues diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml index 07b6d57932e..d4e7862981c 100644 --- a/app/views/dashboard/snippets/index.html.haml +++ b/app/views/dashboard/snippets/index.html.haml @@ -3,32 +3,36 @@ = render 'dashboard/snippets_head' -.gray-content-block - .pull-right +.nav-block + .controls = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do = icon('plus') New Snippet - .btn-group.btn-group-next.snippet-scope-menu - = link_to dashboard_snippets_path, class: "btn btn-default #{"active" unless params[:scope]}" do - All - %span.badge - = current_user.snippets.count - - = link_to dashboard_snippets_path(scope: 'are_private'), class: "btn btn-default #{"active" if params[:scope] == "are_private"}" do - Private - %span.badge - = current_user.snippets.are_private.count - - = link_to dashboard_snippets_path(scope: 'are_internal'), class: "btn btn-default #{"active" if params[:scope] == "are_internal"}" do - Internal - %span.badge - = current_user.snippets.are_internal.count - - = link_to dashboard_snippets_path(scope: 'are_public'), class: "btn btn-default #{"active" if params[:scope] == "are_public"}" do - Public - %span.badge - = current_user.snippets.are_public.count + .nav-links.snippet-scope-menu + %li{ class: ("active" unless params[:scope]) } + = link_to dashboard_snippets_path do + All + %span.badge + = current_user.snippets.count + + %li{ class: ("active" if params[:scope] == "are_private") } + = link_to dashboard_snippets_path(scope: 'are_private') do + Private + %span.badge + = current_user.snippets.are_private.count + + %li{ class: ("active" if params[:scope] == "are_internal") } + = link_to dashboard_snippets_path(scope: 'are_internal') do + Internal + %span.badge + = current_user.snippets.are_internal.count + + %li{ class: ("active" if params[:scope] == "are_public") } + = link_to dashboard_snippets_path(scope: 'are_public') do + Public + %span.badge + = current_user.snippets.are_public.count = render 'snippets/snippets' diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml index d063b257b5e..1233da85524 100644 --- a/app/views/groups/milestones/show.html.haml +++ b/app/views/groups/milestones/show.html.haml @@ -54,7 +54,7 @@ #{@milestone.open_items_count} open = milestone_progress_bar(@milestone) -%ul.center-top-menu.no-top.no-bottom +%ul.nav-links.no-top.no-bottom %li.active = link_to '#tab-issues', 'data-toggle' => 'tab' do Issues diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 24d2f000b40..4da70358766 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -56,5 +56,5 @@ = render "projects", projects: @projects - else - %p.center-top-menu.no-top + %p.nav-links.no-top No projects to show diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index d9ffda884c8..65335c708fa 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -139,9 +139,9 @@ %h2#navs Navigation %h4 - %code .center-top-menu + %code .nav-links .example - %ul.center-top-menu + %ul.nav-links %li.active %a Open %li diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index 6edc94e7a98..961b61d2e76 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -1,6 +1,6 @@ -.activity-filter-block +.nav-block.activity-filter-block - if current_user - .pull-right + .controls = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'btn rss-btn' do %i.fa.fa-rss diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 54c818baaf4..1fb37ef6621 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -1,6 +1,6 @@ .md-area .md-header.clearfix - %ul.center-top-menu + %ul.nav-links %li.active %a.js-md-write-button(href="#md-write-holder" tabindex="-1") Write diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 09fa148b129..a279e6eda55 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -2,7 +2,7 @@ = render "header_title" .file-editor - %ul.center-top-menu.no-bottom.js-edit-mode + %ul.nav-links.no-bottom.js-edit-mode %li.active = link_to '#editor' do = icon('edit') diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 3bbfdb1e3b0..5d18c0d803a 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -8,7 +8,7 @@ - if @all_builds.running_or_pending.any? = link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post - %ul.center-top-menu + %ul.nav-links %li{class: ('active' if @scope.nil?)} = link_to project_builds_path(@project) do All diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 5b7ecce86ab..08a59d465c0 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -14,7 +14,7 @@ #up-build-trace - if @commit.matrix_for_ref?(@build.ref) - %ul.center-top-menu.no-top.no-bottom + %ul.nav-links.no-top.no-bottom - @commit.latest_builds_for_ref(@build.ref).each do |build| %li{class: ('active' if build == @build) } = link_to namespace_project_build_path(@project.namespace, @project, build) do diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml index f74f8b427ec..ea33aa472a6 100644 --- a/app/views/projects/commit/_ci_menu.html.haml +++ b/app/views/projects/commit/_ci_menu.html.haml @@ -1,4 +1,4 @@ -%ul.center-top-menu.no-top.no-bottom.commit-ci-menu +%ul.nav-links.no-top.no-bottom.commit-ci-menu = nav_link(path: 'commit#show') do = link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do Changes diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 58aa45e8d2c..02297158dec 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -2,7 +2,9 @@ - page_description @commit.description = render "projects/commits/header_title" -= render "commit_box" + +.prepend-top-default + = render "commit_box" - if @ci_commit = render "ci_menu" - else diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index fcccb002d7e..498c5e05b32 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -1,4 +1,4 @@ -%ul.center-top-menu +%ul.nav-links = nav_link(controller: [:commit, :commits]) do = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do Commits diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml index a47643bd09c..79a56647c53 100644 --- a/app/views/projects/graphs/_head.html.haml +++ b/app/views/projects/graphs/_head.html.haml @@ -1,4 +1,4 @@ -%ul.center-top-menu +%ul.nav-links = nav_link(action: :show) do = link_to 'Contributors', namespace_project_graph_path = nav_link(action: :commits) do diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index a14943b15d3..dd2c59e112a 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -18,7 +18,7 @@ = f.hidden_field :target_branch .mr-compare.merge-request - %ul.merge-request-tabs.center-top-menu.no-top.no-bottom + %ul.merge-request-tabs.nav-links.no-top.no-bottom %li.commits-tab = link_to url_for(params), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do Commits diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 095876450a0..200bfa5ac4f 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -45,7 +45,7 @@ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" - if @commits.present? - %ul.merge-request-tabs.center-top-menu.no-top.no-bottom + %ul.merge-request-tabs.nav-links.no-top.no-bottom %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do Discussion diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 1670ea8741a..f258b75e01c 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -57,7 +57,7 @@ %span.pull-right= @milestone.expires_at = milestone_progress_bar(@milestone) -%ul.center-top-menu.no-top.no-bottom +%ul.nav-links.no-top.no-bottom %li.active = link_to '#tab-issues', 'data-toggle' => 'tab' do Issues diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml index e6e6ad5bc4b..69ba301e231 100644 --- a/app/views/projects/wikis/_nav.html.haml +++ b/app/views/projects/wikis/_nav.html.haml @@ -7,7 +7,7 @@ = render 'projects/wikis/new' - %ul.center-top-menu + %ul.nav-links = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml index cbdecda4fff..f77feeb79cd 100644 --- a/app/views/shared/_milestones_filter.html.haml +++ b/app/views/shared/_milestones_filter.html.haml @@ -1,5 +1,5 @@ .milestones-filters - %ul.center-top-menu + %ul.nav-links %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')} = link_to milestones_filter_path(state: 'opened') do Open diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 0e3e9275fc1..8d6f47b38ef 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -1,6 +1,6 @@ .issues-filters .issues-state-filters - %ul.center-top-menu + %ul.nav-links - if defined?(type) && type == :merge_requests - page_context_word = 'merge requests' - else diff --git a/app/views/sherlock/queries/show.html.haml b/app/views/sherlock/queries/show.html.haml index 4a84348ac82..83f61ce4b07 100644 --- a/app/views/sherlock/queries/show.html.haml +++ b/app/views/sherlock/queries/show.html.haml @@ -1,7 +1,7 @@ - page_title t('sherlock.title'), t('sherlock.transaction'), t('sherlock.query') - header_title t('sherlock.title'), sherlock_transactions_path -%ul.center-top-menu +%ul.nav-links %li.active %a(href="#tab-general" data-toggle="tab") = t('sherlock.general') diff --git a/app/views/sherlock/transactions/show.html.haml b/app/views/sherlock/transactions/show.html.haml index 3c8ffb06648..9d4b0b2724c 100644 --- a/app/views/sherlock/transactions/show.html.haml +++ b/app/views/sherlock/transactions/show.html.haml @@ -1,7 +1,7 @@ - page_title t('sherlock.title'), t('sherlock.transaction') - header_title t('sherlock.title'), sherlock_transactions_path -%ul.center-top-menu +%ul.nav-links %li.active %a(href="#tab-general" data-toggle="tab") = t('sherlock.general') diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index ce17fc7bca1..e7848bf1333 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -74,7 +74,7 @@ .user-calendar-activities -%ul.center-top-menu.no-top.no-bottom.bottom-border.wide +%ul.nav-links.no-top.no-bottom.bottom-border.wide %li.active = link_to "#activity", 'data-toggle' => 'tab' do Activity -- cgit v1.2.1 From 96dda5f6ecba417118d80a0bdfa5c0a14e15a04c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 13 Jan 2016 16:00:08 +0100 Subject: Update UI help page with only one nav-link example Signed-off-by: Dmitriy Zaporozhets --- app/views/help/ui.html.haml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index 65335c708fa..7b45bd09050 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -147,23 +147,6 @@ %li %a Closed - %h4 - %code .btn-group.btn-group-next - .example - %div.btn-group.btn-group-next - %a.btn.active Open - %a.btn Closed - - - %h4 - %code .nav.nav-tabs - .example - %ul.nav.nav-tabs - %li.active - %a Open - %li - %a Closed - %h2#buttons Buttons -- cgit v1.2.1 From 132ab484d46044c4c7a88b7df02e67a29255e032 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 13 Jan 2016 16:06:59 +0100 Subject: Increase detail-page-header padding to match height with nav-link Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/detail_page.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index a9605804fb3..529a43548c8 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -1,5 +1,5 @@ .detail-page-header { - padding: 7px 0; + padding: 11px 0; border-bottom: 1px solid $border-color; color: #5c5d5e; font-size: 16px; -- cgit v1.2.1 From c44b927bc00ed75b41f3951a15794c5cc6bdc60e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 13 Jan 2016 16:10:06 +0100 Subject: Allign md header components Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/markdown_area.scss | 7 ------- app/assets/stylesheets/framework/zen.scss | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 4a00a197d9a..f0674205c17 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -65,13 +65,6 @@ position: relative; } -.md-header { - ul { - float: left; - margin-bottom: 1px; - } -} - .referenced-users { color: #4c4e54; padding-top: 10px; diff --git a/app/assets/stylesheets/framework/zen.scss b/app/assets/stylesheets/framework/zen.scss index 002bd7e8ca5..c3f27333fad 100644 --- a/app/assets/stylesheets/framework/zen.scss +++ b/app/assets/stylesheets/framework/zen.scss @@ -4,7 +4,7 @@ position: absolute; top: 0px; right: 4px; - line-height: 40px; + line-height: 56px; } a.js-zen-leave { -- cgit v1.2.1 From 0673fad893e2cd457424bc730b1faa146e1f6f5f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 13 Jan 2016 16:26:40 +0100 Subject: Apply new layout to user page Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/lists.scss | 1 + app/views/groups/show.html.haml | 7 +- app/views/projects/show.html.haml | 6 +- app/views/users/show.html.haml | 127 ++++++++++++++-------------- 4 files changed, 72 insertions(+), 69 deletions(-) diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index a5e1afd6022..c6bc6fb324d 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -131,6 +131,7 @@ ul.content-list { .panel > .content-list { li { margin: 0; + padding: $gl-padding; } } diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 4da70358766..5e39d3ab601 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,3 +1,6 @@ +- @no_container = true +- @blank_container = true + - unless can?(current_user, :read_group, @group) - @disable_search_panel = true @@ -5,9 +8,6 @@ - if current_user = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") -- @no_container = true -- @blank_container = true - .cover-block .cover-controls - if @group && can?(current_user, :admin_group, @group) @@ -39,7 +39,6 @@ Projects - if can?(current_user, :read_group, @group) - %div{ class: container_class } .tab-content .tab-pane.active#activity diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 25dcedf2b0b..65444e7e334 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,3 +1,6 @@ +- @no_container = true +- @blank_container = true + = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "#{@project.name} activity") @@ -7,9 +10,6 @@ = render 'shared/no_ssh' = render 'shared/no_password' -- @no_container = true -- @blank_container = true - = render 'projects/last_push' = render "home_panel" diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index e7848bf1333..284a62e6f47 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -1,6 +1,8 @@ - page_title @user.name - page_description @user.bio - header_title @user.name, user_path(@user) +- @no_container = true +- @blank_container = true = content_for :meta_tags do = auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity") @@ -8,6 +10,25 @@ = render 'shared/show_aside' .cover-block + .cover-controls + - if @user == current_user + = link_to profile_path, class: 'btn btn-gray' do + = icon('pencil') + - elsif current_user + %span.report-abuse + - if @user.abuse_report + %button.btn.btn-danger{ title: 'Already reported for abuse', + data: { toggle: 'tooltip', placement: 'left', container: 'body' }} + = icon('exclamation-circle') + - else + = link_to new_abuse_report_path(user_id: @user.id), class: 'btn btn-gray', + title: 'Report abuse', data: {toggle: 'tooltip', placement: 'left', container: 'body'} do + = icon('exclamation-circle') + - if current_user +   + = link_to user_path(@user, :atom, { private_token: current_user.private_token }), class: 'btn btn-gray' do + = icon('rss') + .avatar-holder = link_to avatar_icon(@user, 400), target: '_blank' do = image_tag avatar_icon(@user, 90), class: "avatar s90", alt: '' @@ -47,74 +68,56 @@ = icon('map-marker') = @user.location + %ul.nav-links.center + %li.active + = link_to "#activity", 'data-toggle' => 'tab' do + Activity + - if @groups.any? + %li + = link_to "#groups", 'data-toggle' => 'tab' do + Groups + - if @contributed_projects.present? + %li + = link_to "#contributed", 'data-toggle' => 'tab' do + Contributed projects + - if @projects.present? + %li + = link_to "#personal", 'data-toggle' => 'tab' do + Personal projects - .cover-controls - - if @user == current_user - = link_to profile_path, class: 'btn btn-gray' do - = icon('pencil') - - elsif current_user - %span.report-abuse - - if @user.abuse_report - %button.btn.btn-danger{ title: 'Already reported for abuse', - data: { toggle: 'tooltip', placement: 'left', container: 'body' }} - = icon('exclamation-circle') - - else - = link_to new_abuse_report_path(user_id: @user.id), class: 'btn btn-gray', - title: 'Report abuse', data: {toggle: 'tooltip', placement: 'left', container: 'body'} do - = icon('exclamation-circle') - - if current_user -   - = link_to user_path(@user, :atom, { private_token: current_user.private_token }), class: 'btn btn-gray' do - = icon('rss') - -.gray-content-block.second-block - .user-calendar - %h4.center.light - %i.fa.fa-spinner.fa-spin - .user-calendar-activities - +%div{ class: container_class } + .tab-content + .tab-pane.active#activity + .gray-content-block.white.second-block + %div{ class: container_class } + .user-calendar + %h4.center.light + %i.fa.fa-spinner.fa-spin + .user-calendar-activities -%ul.nav-links.no-top.no-bottom.bottom-border.wide - %li.active - = link_to "#activity", 'data-toggle' => 'tab' do - Activity - - if @groups.any? - %li - = link_to "#groups", 'data-toggle' => 'tab' do - Groups - - if @contributed_projects.present? - %li - = link_to "#contributed", 'data-toggle' => 'tab' do - Contributed projects - - if @projects.present? - %li - = link_to "#personal", 'data-toggle' => 'tab' do - Personal projects -.tab-content - .tab-pane.active#activity - .content_list - = spinner + .content_list + = spinner - - if @groups.any? - .tab-pane#groups - %ul.content-list - - @groups.each do |group| - = render 'shared/groups/group', group: group + - if @groups.any? + .tab-pane#groups + %ul.content-list + - @groups.each do |group| + = render 'shared/groups/group', group: group - - if @contributed_projects.present? - .tab-pane#contributed - .contributed-projects - = render 'shared/projects/list', - projects: @contributed_projects.sort_by(&:star_count).reverse, - projects_limit: 5, stars: true, avatar: true + - if @contributed_projects.present? + .tab-pane#contributed + .contributed-projects + = render 'shared/projects/list', + projects: @contributed_projects.sort_by(&:star_count).reverse, + projects_limit: 10, stars: true, avatar: true - - if @projects.present? - .tab-pane#personal - .personal-projects - = render 'shared/projects/list', - projects: @projects.sort_by(&:star_count).reverse, - projects_limit: 10, stars: true, avatar: true + - if @projects.present? + .tab-pane#personal + .personal-projects + = render 'shared/projects/list', + projects: @projects.sort_by(&:star_count).reverse, + projects_limit: 10, stars: true, avatar: true :javascript $(".user-calendar").load("#{user_calendar_path}"); -- cgit v1.2.1 From cf1a62e236bd74ce31eda3b07505664bed445a96 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 13 Jan 2016 17:01:11 +0100 Subject: Add top margin for page title Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/typography.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 714369d9f15..ab4f71af039 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -177,7 +177,7 @@ body { } .page-title { - margin-top: 0px; + margin-top: $gl-padding; line-height: 1.3; font-size: 1.25em; font-weight: 600; -- cgit v1.2.1 From 3238b0bc9fddd1a00d4926a7679c454db03d6fed Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 13 Jan 2016 17:13:36 +0100 Subject: Replace nav-tabs with nav-links Signed-off-by: Dmitriy Zaporozhets --- .../javascripts/merge_request_tabs.js.coffee | 2 +- .../stylesheets/framework/markdown_area.scss | 17 ------------ app/assets/stylesheets/framework/mobile.scss | 2 +- app/assets/stylesheets/framework/tw_bootstrap.scss | 31 ---------------------- app/assets/stylesheets/pages/projects.scss | 22 --------------- app/views/admin/logs/show.html.haml | 5 ++-- app/views/admin/users/_head.html.haml | 3 ++- app/views/devise/shared/_signin_box.html.haml | 2 +- app/views/search/_category.html.haml | 2 +- app/views/search/_results.html.haml | 2 +- app/views/search/show.html.haml | 4 ++- 11 files changed, 13 insertions(+), 79 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 9e2dc1250c9..b10e1db7f3f 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -5,7 +5,7 @@ # # ### Example Markup # -#