Address UX review
- Keep 'Deploy Section' open upon save, otherwise the token might get lost - When an error appears, display the error inside the form and also keep the Deploy Section open - Changue copy of revoke modal
This commit is contained in:
parent
370fc05da7
commit
345ac03b7a
12 changed files with 103 additions and 50 deletions
|
@ -3,16 +3,12 @@ class Projects::DeployTokensController < Projects::ApplicationController
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@token = DeployTokens::CreateService.new(@project, current_user, deploy_token_params).execute
|
@token = DeployTokens::CreateService.new(@project, current_user, deploy_token_params).execute
|
||||||
token_params = {}
|
|
||||||
|
|
||||||
if @token.valid?
|
if @token.valid?
|
||||||
flash[:notice] = 'Your new project deploy token has been created.'
|
flash[:notice] = 'Your new project deploy token has been created.'
|
||||||
else
|
|
||||||
token_params = @token.attributes.slice("name", "scopes", "expires_at")
|
|
||||||
flash[:alert] = @token.errors.full_messages.join(', ').html_safe
|
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to project_settings_repository_path(project, deploy_token: token_params)
|
redirect_to project_settings_repository_path(project)
|
||||||
end
|
end
|
||||||
|
|
||||||
def revoke
|
def revoke
|
||||||
|
|
|
@ -55,11 +55,9 @@ module Projects
|
||||||
end
|
end
|
||||||
|
|
||||||
def define_deploy_token
|
def define_deploy_token
|
||||||
@deploy_token = @project.deploy_tokens.build(deploy_token_attributes)
|
attributes = @deploy_tokens.attributes_deploy_token
|
||||||
end
|
@deploy_token = @project.deploy_tokens.build(attributes)
|
||||||
|
@deploy_token.valid? unless attributes.empty?
|
||||||
def deploy_token_attributes
|
|
||||||
params.fetch(:deploy_token, {}).permit(:name, :expires_at, scopes: [])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
7
app/helpers/deploy_tokens_helper.rb
Normal file
7
app/helpers/deploy_tokens_helper.rb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
module DeployTokensHelper
|
||||||
|
def expand_deploy_tokens_section?(temporal_token, deploy_token)
|
||||||
|
temporal_token.present? ||
|
||||||
|
deploy_token.errors.present? ||
|
||||||
|
Rails.env.test?
|
||||||
|
end
|
||||||
|
end
|
|
@ -19,8 +19,8 @@ class DeployToken < ActiveRecord::Base
|
||||||
update!(revoked: true)
|
update!(revoked: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.redis_shared_state_key(user_id)
|
def redis_shared_state_key(user_id)
|
||||||
"gitlab:personal_access_token:#{user_id}"
|
"gitlab:deploy_token:#{project_id}:#{user_id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def active?
|
def active?
|
||||||
|
|
|
@ -23,14 +23,23 @@ module Projects
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def new_deploy_token
|
def temporal_token
|
||||||
@new_deploy_token ||= Gitlab::Redis::SharedState.with do |redis|
|
@temporal_token ||= Gitlab::Redis::SharedState.with do |redis|
|
||||||
token = redis.get(deploy_token_key)
|
token = redis.get(deploy_token_key)
|
||||||
redis.del(deploy_token_key)
|
redis.del(deploy_token_key)
|
||||||
token
|
token
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def attributes_deploy_token
|
||||||
|
@attributes_deploy_token ||= Gitlab::Redis::SharedState.with do |redis|
|
||||||
|
attributes_key = deploy_token_key + ":attributes"
|
||||||
|
attributes_content = redis.get(attributes_key) || '{}'
|
||||||
|
redis.del(attributes_key)
|
||||||
|
JSON.parse(attributes_content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def scope_descriptions
|
def scope_descriptions
|
||||||
|
@ -41,7 +50,7 @@ module Projects
|
||||||
end
|
end
|
||||||
|
|
||||||
def deploy_token_key
|
def deploy_token_key
|
||||||
DeployToken.redis_shared_state_key(current_user.id)
|
@deploy_token_key ||= project.deploy_tokens.new.redis_shared_state_key(current_user.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,22 +3,36 @@ module DeployTokens
|
||||||
REDIS_EXPIRY_TIME = 3.minutes
|
REDIS_EXPIRY_TIME = 3.minutes
|
||||||
|
|
||||||
def execute
|
def execute
|
||||||
@deploy_token = @project.deploy_tokens.create(params)
|
@project.deploy_tokens.build.tap do |deploy_token|
|
||||||
store_in_redis if @deploy_token.persisted?
|
deploy_token.attributes = params
|
||||||
|
deploy_token.save
|
||||||
@deploy_token
|
store_deploy_token_info_in_redis(deploy_token)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def store_in_redis
|
def store_deploy_token_info_in_redis(deploy_token)
|
||||||
Gitlab::Redis::SharedState.with do |redis|
|
deploy_token_key = deploy_token.redis_shared_state_key(current_user.id)
|
||||||
redis.set(deploy_token_key, @deploy_token.token, ex: REDIS_EXPIRY_TIME)
|
|
||||||
|
if deploy_token.persisted?
|
||||||
|
store_in_redis(deploy_token_key, deploy_token.token)
|
||||||
|
else
|
||||||
|
store_deploy_attributes(deploy_token_key, deploy_token)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def deploy_token_key
|
def store_deploy_attributes(deploy_token_key, deploy_token)
|
||||||
DeployToken.redis_shared_state_key(current_user.id)
|
attributes = deploy_token.attributes.slice("name", "expires_at")
|
||||||
|
deploy_token_attributes_key = deploy_token_key + ":attributes"
|
||||||
|
|
||||||
|
store_in_redis(deploy_token_attributes_key, attributes.to_json)
|
||||||
|
end
|
||||||
|
|
||||||
|
def store_in_redis(key, value)
|
||||||
|
Gitlab::Redis::SharedState.with do |redis|
|
||||||
|
redis.set(key, value, ex: REDIS_EXPIRY_TIME)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
- expanded = Rails.env.test?
|
- expanded = expand_deploy_tokens_section?(@deploy_tokens.temporal_token, @deploy_token)
|
||||||
|
|
||||||
%section.settings.no-animate{ class: ('expanded' if expanded) }
|
%section.settings.no-animate{ class: ('expanded' if expanded) }
|
||||||
.settings-header
|
.settings-header
|
||||||
|
@ -9,8 +9,8 @@
|
||||||
%p
|
%p
|
||||||
Deploy tokens allow read-only access to your repository and registry images.
|
Deploy tokens allow read-only access to your repository and registry images.
|
||||||
.settings-content
|
.settings-content
|
||||||
- if @deploy_tokens.new_deploy_token
|
- if @deploy_tokens.temporal_token
|
||||||
= render 'projects/deploy_tokens/new_deploy_token', new_token: @deploy_tokens.new_deploy_token
|
= render 'projects/deploy_tokens/new_deploy_token', new_token: @deploy_tokens.temporal_token
|
||||||
|
|
||||||
%h5.prepend-top-0
|
%h5.prepend-top-0
|
||||||
Add a deploy token
|
Add a deploy token
|
||||||
|
|
|
@ -9,7 +9,9 @@
|
||||||
%span{ "aria-hidden" => "true" } ×
|
%span{ "aria-hidden" => "true" } ×
|
||||||
.modal-body
|
.modal-body
|
||||||
%p
|
%p
|
||||||
Are you sure you want to revoke this Deploy Token? This action cannot be undone
|
You are about to revoke
|
||||||
|
%b #{token.name}.
|
||||||
|
This action cannot be undone.
|
||||||
.modal-footer
|
.modal-footer
|
||||||
%a{ href: '#', data: { dismiss: 'modal' }, class: 'btn btn-default' } Cancel
|
%a{ href: '#', data: { dismiss: 'modal' }, class: 'btn btn-default' } Cancel
|
||||||
= link_to "Revoke #{token.name}", revoke_project_deploy_token_path(project, token), method: :put, class: 'btn btn-danger'
|
= link_to "Revoke #{token.name}", revoke_project_deploy_token_path(project, token), method: :put, class: 'btn btn-danger'
|
||||||
|
|
|
@ -27,6 +27,11 @@ describe Projects::DeployTokensController do
|
||||||
subject
|
subject
|
||||||
expect(flash[:notice]).to eq('Your new project deploy token has been created.')
|
expect(flash[:notice]).to eq('Your new project deploy token has been created.')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'should redirect to project settings repository' do
|
||||||
|
subject
|
||||||
|
expect(response).to redirect_to project_settings_repository_path(project)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with invalid params' do
|
context 'with invalid params' do
|
||||||
|
@ -36,9 +41,9 @@ describe Projects::DeployTokensController do
|
||||||
expect { subject }.not_to change(DeployToken, :count)
|
expect { subject }.not_to change(DeployToken, :count)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should include a flash alert with the error message' do
|
it 'should redirect to project settings repository' do
|
||||||
subject
|
subject
|
||||||
expect(flash[:alert]).to eq("Scopes can't be blank")
|
expect(response).to redirect_to project_settings_repository_path(project)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe Projects::Settings::RepositoryController do
|
describe Projects::Settings::RepositoryController, :clean_gitlab_redis_shared_state do
|
||||||
let(:project) { create(:project_empty_repo, :public) }
|
let(:project) { create(:project_empty_repo, :public) }
|
||||||
let(:user) { create(:user) }
|
let(:user) { create(:user) }
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ describe Projects::Settings::RepositoryController do
|
||||||
expect(response).to render_template(:show)
|
expect(response).to render_template(:show)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with no deploy token params' do
|
context 'with no deploy token attributes present' do
|
||||||
it 'should build an empty instance of DeployToken' do
|
it 'should build an empty instance of DeployToken' do
|
||||||
get :show, namespace_id: project.namespace, project_id: project
|
get :show, namespace_id: project.namespace, project_id: project
|
||||||
|
|
||||||
|
@ -29,17 +29,29 @@ describe Projects::Settings::RepositoryController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when rendering an invalid deploy token' do
|
context 'with deploy token attributes present' do
|
||||||
let(:deploy_token_attributes) { attributes_for(:deploy_token, project_id: project.id) }
|
let(:deploy_token_key) { "gitlab:deploy_token:#{project.id}:#{user.id}:attributes" }
|
||||||
|
|
||||||
|
let(:deploy_token_attributes) do
|
||||||
|
{
|
||||||
|
name: 'test-token',
|
||||||
|
expires_at: Date.today + 1.month
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
Gitlab::Redis::SharedState.with do |redis|
|
||||||
|
redis.set(deploy_token_key, deploy_token_attributes.to_json)
|
||||||
|
end
|
||||||
|
|
||||||
|
get :show, namespace_id: project.namespace, project_id: project
|
||||||
|
end
|
||||||
|
|
||||||
it 'should build an instance of DeployToken' do
|
it 'should build an instance of DeployToken' do
|
||||||
get :show, namespace_id: project.namespace, project_id: project, deploy_token: deploy_token_attributes
|
|
||||||
|
|
||||||
deploy_token = assigns(:deploy_token)
|
deploy_token = assigns(:deploy_token)
|
||||||
expect(deploy_token).to be_an_instance_of(DeployToken)
|
expect(deploy_token).to be_an_instance_of(DeployToken)
|
||||||
expect(deploy_token.name).to eq(deploy_token_attributes[:name])
|
expect(deploy_token.name).to eq(deploy_token_attributes[:name])
|
||||||
expect(deploy_token.expires_at.to_date).to eq(deploy_token_attributes[:expires_at].to_date)
|
expect(deploy_token.expires_at.to_date).to eq(deploy_token_attributes[:expires_at].to_date)
|
||||||
expect(deploy_token.scopes).to match_array(deploy_token_attributes[:scopes])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -90,17 +90,16 @@ feature 'Repository settings' do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'Deploy tokens' do
|
context 'Deploy tokens' do
|
||||||
let(:deploy_token) { create(:deploy_token, project: project, expires_at: Date.today + 2.days) }
|
let(:deploy_token) { create(:deploy_token, project: project) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
project.deploy_tokens << deploy_token
|
project.deploy_tokens << deploy_token
|
||||||
visit project_settings_repository_path(project)
|
visit project_settings_repository_path(project)
|
||||||
end
|
end
|
||||||
|
|
||||||
scenario 'view deploy tokens' do
|
scenario 'view deploy tokens' do
|
||||||
within('.deploy-tokens') do
|
within('.deploy-tokens') do
|
||||||
expect(page).to have_content(deploy_token.name)
|
expect(page).to have_content(deploy_token.name)
|
||||||
expect(page).to have_content('In 1 day')
|
|
||||||
expect(page).to have_content(deploy_token.scopes.join(", "))
|
expect(page).to have_content(deploy_token.scopes.join(", "))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -121,7 +120,6 @@ feature 'Repository settings' do
|
||||||
click_link "Revoke #{deploy_token.name}"
|
click_link "Revoke #{deploy_token.name}"
|
||||||
|
|
||||||
expect(page).not_to have_content(deploy_token.name)
|
expect(page).not_to have_content(deploy_token.name)
|
||||||
expect(page).not_to have_content('In 1 day')
|
|
||||||
expect(page).not_to have_content(deploy_token.scopes.join(", "))
|
expect(page).not_to have_content(deploy_token.scopes.join(", "))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,40 +6,52 @@ describe DeployTokens::CreateService, :clean_gitlab_redis_shared_state do
|
||||||
let(:deploy_token_params) { attributes_for(:deploy_token) }
|
let(:deploy_token_params) { attributes_for(:deploy_token) }
|
||||||
|
|
||||||
describe '#execute' do
|
describe '#execute' do
|
||||||
subject { described_class.new(project, user, deploy_token_params) }
|
subject { described_class.new(project, user, deploy_token_params).execute }
|
||||||
|
|
||||||
context 'when the deploy token is valid' do
|
context 'when the deploy token is valid' do
|
||||||
it 'should create a new DeployToken' do
|
it 'should create a new DeployToken' do
|
||||||
expect { subject.execute }.to change { DeployToken.count }.by(1)
|
expect { subject }.to change { DeployToken.count }.by(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a DeployToken' do
|
||||||
|
expect(subject).to be_an_instance_of DeployToken
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should assign the DeployToken to the project' do
|
it 'should assign the DeployToken to the project' do
|
||||||
subject.execute
|
|
||||||
|
|
||||||
expect(subject.project).to eq(project)
|
expect(subject.project).to eq(project)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should store the token on redis' do
|
it 'should store the token on redis' do
|
||||||
subject.execute
|
redis_key = subject.redis_shared_state_key(user.id)
|
||||||
redis_key = DeployToken.redis_shared_state_key(user.id)
|
|
||||||
|
|
||||||
expect(Gitlab::Redis::SharedState.with { |redis| redis.get(redis_key) }).not_to be_nil
|
expect(Gitlab::Redis::SharedState.with { |redis| redis.get(redis_key) }).not_to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'should not store deploy token attributes on redis' do
|
||||||
|
redis_key = subject.redis_shared_state_key(user.id) + ":attributes"
|
||||||
|
|
||||||
|
expect(Gitlab::Redis::SharedState.with { |redis| redis.get(redis_key) }).to be_nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the deploy token is invalid' do
|
context 'when the deploy token is invalid' do
|
||||||
let(:deploy_token_params) { attributes_for(:deploy_token, scopes: []) }
|
let(:deploy_token_params) { attributes_for(:deploy_token, scopes: []) }
|
||||||
|
|
||||||
it 'it should not create a new DeployToken' do
|
it 'it should not create a new DeployToken' do
|
||||||
expect { subject.execute }.not_to change { DeployToken.count }
|
expect { subject }.not_to change { DeployToken.count }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should not store the token on redis' do
|
it 'should not store the token on redis' do
|
||||||
subject.execute
|
redis_key = subject.redis_shared_state_key(user.id)
|
||||||
redis_key = DeployToken.redis_shared_state_key(user.id)
|
|
||||||
|
|
||||||
expect(Gitlab::Redis::SharedState.with { |redis| redis.get(redis_key) }).to be_nil
|
expect(Gitlab::Redis::SharedState.with { |redis| redis.get(redis_key) }).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'should store deploy token attributes on redis' do
|
||||||
|
redis_key = subject.redis_shared_state_key(user.id) + ":attributes"
|
||||||
|
|
||||||
|
expect(Gitlab::Redis::SharedState.with { |redis| redis.get(redis_key) }).not_to be_nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue