Linking to edit file directly
This commit is contained in:
parent
3f60fe1a60
commit
b42dc1a52b
|
@ -0,0 +1,15 @@
|
|||
function BlobForkSuggestion(openButton, cancelButton, suggestionSection) {
|
||||
if (openButton) {
|
||||
openButton.addEventListener('click', () => {
|
||||
suggestionSection.classList.remove('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
if (cancelButton) {
|
||||
cancelButton.addEventListener('click', () => {
|
||||
suggestionSection.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default BlobForkSuggestion;
|
|
@ -43,6 +43,7 @@ import GroupsList from './groups_list';
|
|||
import ProjectsList from './projects_list';
|
||||
import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
|
||||
import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
|
||||
import BlobForkSuggestion from './blob/blob_fork_suggestion';
|
||||
import UserCallout from './user_callout';
|
||||
|
||||
const ShortcutsBlob = require('./shortcuts_blob');
|
||||
|
@ -86,6 +87,12 @@ const ShortcutsBlob = require('./shortcuts_blob');
|
|||
skipResetBindings: true,
|
||||
fileBlobPermalinkUrl,
|
||||
});
|
||||
|
||||
new BlobForkSuggestion(
|
||||
document.querySelector('.js-edit-blob-link-fork-toggler'),
|
||||
document.querySelector('.js-cancel-fork-suggestion'),
|
||||
document.querySelector('.js-file-fork-suggestion-section'),
|
||||
);
|
||||
}
|
||||
|
||||
switch (page) {
|
||||
|
|
|
@ -281,3 +281,16 @@ span.idiff {
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.file-fork-suggestion {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
background-color: $gray-light;
|
||||
border-bottom: 1px solid $border-color;
|
||||
padding: 5px $gl-padding;
|
||||
}
|
||||
|
||||
.file-fork-suggestion-note {
|
||||
margin-right: 1.5em;
|
||||
}
|
||||
|
|
|
@ -7,9 +7,11 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
# Raised when given an invalid file path
|
||||
InvalidPathError = Class.new(StandardError)
|
||||
|
||||
prepend_before_action :authenticate_user!, only: [:edit]
|
||||
|
||||
before_action :require_non_empty_project, except: [:new, :create]
|
||||
before_action :authorize_download_code!
|
||||
before_action :authorize_edit_tree!, only: [:new, :create, :edit, :update, :destroy]
|
||||
before_action :authorize_edit_tree!, only: [:new, :create, :update, :destroy]
|
||||
before_action :assign_blob_vars
|
||||
before_action :commit, except: [:new, :create]
|
||||
before_action :blob, except: [:new, :create]
|
||||
|
@ -37,7 +39,11 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def edit
|
||||
blob.load_all_data!(@repository)
|
||||
if can_collaborate_with_project?
|
||||
blob.load_all_data!(@repository)
|
||||
else
|
||||
redirect_to action: 'show'
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
|
|
|
@ -8,31 +8,36 @@ module BlobHelper
|
|||
%w(credits changelog news copying copyright license authors)
|
||||
end
|
||||
|
||||
def edit_blob_link(project = @project, ref = @ref, path = @path, options = {})
|
||||
return unless current_user
|
||||
def edit_path(project = @project, ref = @ref, path = @path, options = {})
|
||||
namespace_project_edit_blob_path(project.namespace, project,
|
||||
tree_join(ref, path),
|
||||
options[:link_opts])
|
||||
end
|
||||
|
||||
def fork_path(project = @project, ref = @ref, path = @path, options = {})
|
||||
continue_params = {
|
||||
to: edit_path,
|
||||
notice: edit_in_new_fork_notice,
|
||||
notice_now: edit_in_new_fork_notice_now
|
||||
}
|
||||
namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
|
||||
end
|
||||
|
||||
def edit_blob_link(project = @project, ref = @ref, path = @path, options = {})
|
||||
blob = options.delete(:blob)
|
||||
blob ||= project.repository.blob_at(ref, path) rescue nil
|
||||
|
||||
return unless blob
|
||||
|
||||
edit_path = namespace_project_edit_blob_path(project.namespace, project,
|
||||
tree_join(ref, path),
|
||||
options[:link_opts])
|
||||
common_classes = "btn js-edit-blob #{options[:extra_class]}"
|
||||
|
||||
if !on_top_of_branch?(project, ref)
|
||||
button_tag "Edit", class: "btn disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
|
||||
elsif can_edit_blob?(blob, project, ref)
|
||||
link_to "Edit", edit_path, class: 'btn btn-sm'
|
||||
elsif can?(current_user, :fork_project, project)
|
||||
continue_params = {
|
||||
to: edit_path,
|
||||
notice: edit_in_new_fork_notice,
|
||||
notice_now: edit_in_new_fork_notice_now
|
||||
}
|
||||
fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
|
||||
|
||||
link_to "Edit", fork_path, class: 'btn', method: :post
|
||||
button_tag 'Edit', class: "#{common_classes} disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
|
||||
# This condition applies to anonymous or users who can edit directly
|
||||
elsif !current_user || (current_user && can_edit_blob?(blob, project, ref))
|
||||
link_to 'Edit', edit_path(project, ref, path, options), class: "#{common_classes} btn-sm"
|
||||
elsif current_user && can?(current_user, :fork_project, project)
|
||||
button_tag 'Edit', class: "#{common_classes} js-edit-blob-link-fork-toggler"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -25,4 +25,11 @@
|
|||
#blob-content-holder.blob-content-holder
|
||||
%article.file-holder
|
||||
= render "projects/blob/header", blob: blob
|
||||
- if current_user
|
||||
.js-file-fork-suggestion-section.file-fork-suggestion.hidden
|
||||
%span.file-fork-suggestion-note
|
||||
You don't have permission to edit this file. Try forking this project to edit the file.
|
||||
= link_to 'Fork', fork_path, method: :post, class: 'btn btn-grouped btn-inverted btn-new'
|
||||
%button.js-cancel-fork-suggestion.btn.btn-grouped{ type: 'button' }
|
||||
Cancel
|
||||
= render blob.to_partial_path(@project), blob: blob
|
||||
|
|
|
@ -32,8 +32,8 @@
|
|||
= link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
|
||||
tree_join(@commit.sha, @path)), class: 'btn btn-sm js-data-file-blob-permalink-url'
|
||||
|
||||
- if current_user
|
||||
.btn-group{ role: "group" }<
|
||||
= edit_blob_link if blob_text_viewable?(blob)
|
||||
.btn-group{ role: "group" }<
|
||||
= edit_blob_link if blob_text_viewable?(blob)
|
||||
- if current_user
|
||||
= replace_blob_link
|
||||
= delete_blob_link
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Linking to blob edit page handles anonymous users and users without enough permissions
|
||||
to edit directly
|
||||
merge_request:
|
||||
author:
|
|
@ -158,6 +158,8 @@ Feature: Project Source Browse Files
|
|||
Given I don't have write access
|
||||
And I click on ".gitignore" file in repo
|
||||
And I click button "Edit"
|
||||
Then I should see a Fork/Cancel combo
|
||||
And I click button "Fork"
|
||||
Then I should see a notice about a new fork having been created
|
||||
And I can edit code
|
||||
|
||||
|
@ -180,6 +182,8 @@ Feature: Project Source Browse Files
|
|||
Given I don't have write access
|
||||
And I click on ".gitignore" file in repo
|
||||
And I click button "Edit"
|
||||
Then I should see a Fork/Cancel combo
|
||||
And I click button "Fork"
|
||||
And I edit code
|
||||
And I fill the commit message
|
||||
And I click on "Commit Changes"
|
||||
|
|
|
@ -56,13 +56,17 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
step 'I click button "Edit"' do
|
||||
click_link 'Edit'
|
||||
find('.js-edit-blob').click
|
||||
end
|
||||
|
||||
step 'I cannot see the edit button' do
|
||||
expect(page).not_to have_link 'edit'
|
||||
end
|
||||
|
||||
step 'I click button "Fork"' do
|
||||
click_link 'Fork'
|
||||
end
|
||||
|
||||
step 'I can edit code' do
|
||||
set_new_content
|
||||
expect(evaluate_script('ace.edit("editor").getValue()')).to eq new_gitignore_content
|
||||
|
@ -366,6 +370,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
|
|||
end
|
||||
end
|
||||
|
||||
step 'I should see a Fork/Cancel combo' do
|
||||
expect(page).to have_link 'Fork'
|
||||
expect(page).to have_button 'Cancel'
|
||||
expect(page).to have_content 'You don\'t have permission to edit this file. Try forking this project to edit the file.'
|
||||
end
|
||||
|
||||
step 'I should see a notice about a new fork having been created' do
|
||||
expect(page).to have_content "You're not allowed to make changes to this project directly. A fork of this project has been created that you can make changes in, so you can submit a merge request."
|
||||
end
|
||||
|
|
|
@ -2,15 +2,10 @@ require 'rails_helper'
|
|||
|
||||
describe Projects::BlobController do
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.team << [user, :master]
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
describe 'GET diff' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
render_views
|
||||
|
||||
def do_get(opts = {})
|
||||
|
@ -20,6 +15,12 @@ describe Projects::BlobController do
|
|||
get :diff, params.merge(opts)
|
||||
end
|
||||
|
||||
before do
|
||||
project.team << [user, :master]
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when essential params are missing' do
|
||||
it 'renders nothing' do
|
||||
do_get
|
||||
|
@ -37,7 +38,69 @@ describe Projects::BlobController do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'GET edit' do
|
||||
let(:default_params) do
|
||||
{
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: 'master/CHANGELOG'
|
||||
}
|
||||
end
|
||||
|
||||
context 'anonymous' do
|
||||
before do
|
||||
get :edit, default_params
|
||||
end
|
||||
|
||||
it 'redirects to sign in and returns' do
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as guest' do
|
||||
let(:guest) { create(:user) }
|
||||
|
||||
before do
|
||||
sign_in(guest)
|
||||
get :edit, default_params
|
||||
end
|
||||
|
||||
it 'redirects to blob show' do
|
||||
expect(response).to redirect_to(namespace_project_blob_path(project.namespace, project, 'master/CHANGELOG'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'as developer' do
|
||||
let(:developer) { create(:user) }
|
||||
|
||||
before do
|
||||
project.team << [developer, :developer]
|
||||
sign_in(developer)
|
||||
get :edit, default_params
|
||||
end
|
||||
|
||||
it 'redirects to blob show' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as master' do
|
||||
let(:master) { create(:user) }
|
||||
|
||||
before do
|
||||
project.team << [master, :master]
|
||||
sign_in(master)
|
||||
get :edit, default_params
|
||||
end
|
||||
|
||||
it 'redirects to blob show' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT update' do
|
||||
let(:user) { create(:user) }
|
||||
let(:default_params) do
|
||||
{
|
||||
namespace_id: project.namespace,
|
||||
|
@ -53,6 +116,12 @@ describe Projects::BlobController do
|
|||
namespace_project_blob_path(project.namespace, project, 'master/CHANGELOG')
|
||||
end
|
||||
|
||||
before do
|
||||
project.team << [user, :master]
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'redirects to blob' do
|
||||
put :update, default_params
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'File blob', feature: true do
|
||||
include WaitForAjax
|
||||
include TreeHelper
|
||||
|
||||
let(:project) { create(:project, :public, :test_repo) }
|
||||
let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') }
|
||||
let(:branch) { 'master' }
|
||||
let(:file_path) { project.repository.ls_files(project.repository.root_ref)[1] }
|
||||
|
||||
context 'anonymous' do
|
||||
context 'from blob file path' do
|
||||
before do
|
||||
visit namespace_project_blob_path(project.namespace, project, tree_join(branch, file_path))
|
||||
end
|
||||
|
||||
it 'updates content' do
|
||||
expect(page).to have_link 'Edit'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,44 +2,135 @@ require 'spec_helper'
|
|||
|
||||
feature 'Editing file blob', feature: true, js: true do
|
||||
include WaitForAjax
|
||||
include TreeHelper
|
||||
|
||||
given(:user) { create(:user) }
|
||||
given(:role) { :developer }
|
||||
given(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master') }
|
||||
given(:project) { merge_request.target_project }
|
||||
let(:project) { create(:project, :public, :test_repo) }
|
||||
let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') }
|
||||
let(:branch) { 'master' }
|
||||
let(:file_path) { project.repository.ls_files(project.repository.root_ref)[1] }
|
||||
|
||||
background do
|
||||
login_as(user)
|
||||
project.team << [user, role]
|
||||
end
|
||||
context 'as a developer' do
|
||||
let(:user) { create(:user) }
|
||||
let(:role) { :developer }
|
||||
|
||||
def edit_and_commit
|
||||
wait_for_ajax
|
||||
first('.file-actions').click_link 'Edit'
|
||||
execute_script('ace.edit("editor").setValue("class NextFeature\nend\n")')
|
||||
click_button 'Commit Changes'
|
||||
end
|
||||
|
||||
context 'from MR diff' do
|
||||
before do
|
||||
visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
|
||||
edit_and_commit
|
||||
project.team << [user, role]
|
||||
login_as(user)
|
||||
end
|
||||
|
||||
scenario 'returns me to the mr' do
|
||||
expect(page).to have_content(merge_request.title)
|
||||
def edit_and_commit
|
||||
wait_for_ajax
|
||||
find('.js-edit-blob').click
|
||||
execute_script('ace.edit("editor").setValue("class NextFeature\nend\n")')
|
||||
click_button 'Commit Changes'
|
||||
end
|
||||
|
||||
context 'from MR diff' do
|
||||
before do
|
||||
visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
|
||||
edit_and_commit
|
||||
end
|
||||
|
||||
it 'returns me to the mr' do
|
||||
expect(page).to have_content(merge_request.title)
|
||||
end
|
||||
end
|
||||
|
||||
context 'from blob file path' do
|
||||
before do
|
||||
visit namespace_project_blob_path(project.namespace, project, tree_join(branch, file_path))
|
||||
edit_and_commit
|
||||
end
|
||||
|
||||
it 'updates content' do
|
||||
expect(page).to have_content 'successfully committed'
|
||||
expect(page).to have_content 'NextFeature'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'from blob file path' do
|
||||
before do
|
||||
visit namespace_project_blob_path(project.namespace, project, '/feature/files/ruby/feature.rb')
|
||||
edit_and_commit
|
||||
context 'visit blob edit' do
|
||||
context 'redirects to sign in and returns' do
|
||||
context 'as developer' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.team << [user, :developer]
|
||||
visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))
|
||||
end
|
||||
|
||||
it 'redirects to sign in and returns' do
|
||||
expect(page).to have_current_path(new_user_session_path)
|
||||
|
||||
login_as(user)
|
||||
|
||||
expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path)))
|
||||
end
|
||||
end
|
||||
|
||||
context 'as guest' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))
|
||||
end
|
||||
|
||||
it 'redirects to sign in and returns' do
|
||||
expect(page).to have_current_path(new_user_session_path)
|
||||
|
||||
login_as(user)
|
||||
|
||||
expect(page).to have_current_path(namespace_project_blob_path(project.namespace, project, tree_join(branch, file_path)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scenario 'updates content' do
|
||||
expect(page).to have_content 'successfully committed'
|
||||
expect(page).to have_content 'NextFeature'
|
||||
context 'as developer' do
|
||||
let(:user) { create(:user) }
|
||||
let(:protected_branch) { 'protected-branch' }
|
||||
|
||||
before do
|
||||
project.team << [user, :developer]
|
||||
project.repository.add_branch(user, protected_branch, 'master')
|
||||
create(:protected_branch, project: project, name: protected_branch)
|
||||
login_as(user)
|
||||
end
|
||||
|
||||
context 'on some branch' do
|
||||
before do
|
||||
visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))
|
||||
end
|
||||
|
||||
it 'shows blob editor with same branch' do
|
||||
expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path)))
|
||||
expect(find('.js-target-branch .dropdown-toggle-text').text).to eq(branch)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with protected branch' do
|
||||
before do
|
||||
visit namespace_project_edit_blob_path(project.namespace, project, tree_join(protected_branch, file_path))
|
||||
end
|
||||
|
||||
it 'shows blob editor with patch branch' do
|
||||
expect(find('.js-target-branch .dropdown-toggle-text').text).to eq('patch-1')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'as master' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.team << [user, :master]
|
||||
login_as(user)
|
||||
visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))
|
||||
end
|
||||
|
||||
it 'shows blob editor with same branch' do
|
||||
expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path)))
|
||||
expect(find('.js-target-branch .dropdown-toggle-text').text).to eq(branch)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -73,7 +73,7 @@ describe BlobHelper do
|
|||
let(:project) { create(:project, :repository, namespace: namespace) }
|
||||
|
||||
before do
|
||||
allow(self).to receive(:current_user).and_return(double)
|
||||
allow(self).to receive(:current_user).and_return(nil)
|
||||
allow(self).to receive(:can_collaborate_with_project?).and_return(true)
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue