Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature to be able to upload a file to an issue #6467

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/assets/stylesheets/generic/issue_box.scss
Expand Up @@ -95,6 +95,14 @@
padding: 0 25px 15px 25px;
}

.issue-attachment {
@extend .context;
}

.js-choose-issue-attachment-button {
margin-left: 15px;
}

.title, .context, .description {
.clearfix {
margin: 0;
Expand Down
6 changes: 6 additions & 0 deletions app/assets/stylesheets/sections/issues.scss
Expand Up @@ -143,3 +143,9 @@ form.edit-issue {
border-color: #E5E5E5;
}
}

.issue-image-attach {
@extend .col-md-4;
@extend .thumbnail;
margin-top: 5px;
}
6 changes: 3 additions & 3 deletions app/controllers/files_controller.rb
@@ -1,10 +1,10 @@
class FilesController < ApplicationController
def download
note = Note.find(params[:id])
uploader = note.attachment
model = params[:type].capitalize.constantize.find(params[:id])
uploader = model.attachment

if uploader.file_storage?
if can?(current_user, :read_project, note.project)
if can?(current_user, :read_project, model.project)
send_file uploader.file.path, disposition: 'attachment'
else
not_found!
Expand Down
9 changes: 9 additions & 0 deletions app/controllers/projects/issues_controller.rb
Expand Up @@ -96,6 +96,15 @@ def bulk_update
redirect_to :back, notice: "#{result[:count]} issues updated"
end

def delete_attachment
issue.remove_attachment!
issue.update_attribute(:attachment, nil)

respond_to do |format|
format.js { render nothing: true }
end
end

protected

def issue
Expand Down
9 changes: 8 additions & 1 deletion app/models/issue.rb
Expand Up @@ -15,8 +15,12 @@
# milestone_id :integer
# state :string(255)
# iid :integer
# attachment :string(255)
#

require 'carrierwave/orm/activerecord'
require 'file_size_validator'

class Issue < ActiveRecord::Base
include Issuable
include InternalId
Expand All @@ -25,13 +29,16 @@ class Issue < ActiveRecord::Base

belongs_to :project
validates :project, presence: true
validates :attachment, file_size: { maximum: 10.megabytes.to_i }

mount_uploader :attachment, AttachmentUploader

scope :of_group, ->(group) { where(project_id: group.project_ids) }
scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }

attr_accessible :title, :assignee_id, :position, :description,
:milestone_id, :label_list, :author_id_of_changes,
:state_event
:state_event, :attachment

acts_as_taggable_on :labels

Expand Down
32 changes: 32 additions & 0 deletions app/views/projects/issues/_form.html.haml
Expand Up @@ -21,6 +21,19 @@
.col-sm-10
= f.text_area :description, class: "form-control js-gfm-input", rows: 14
%p.hint Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
.form-group
= f.label :attachment, class: 'control-label' do
%i.icon-paper-clip
File attachment
.col-sm-10
= render partial: 'issue_attachment', locals: { deletable: true }

%a.choose-btn.btn.js-choose-issue-attachment-button
%i.icon-paper-clip
%span Choose File ...
&nbsp;
%span.file_name.js-attachment-filename File name...
= f.file_field :attachment, class: "js-issue-attachment-input hidden"
%hr
.form-group
.issue-assignee
Expand Down Expand Up @@ -61,6 +74,25 @@


:javascript
$(".js-issue-attachment-delete").on('confirm:complete', function(e, answer) {
if (answer)
$(this).closest(".issue-attachment").remove();
});

$(".js-choose-issue-attachment-button").on('click', function(e) {
form = $(this).closest("form");
form.find(".js-issue-attachment-input").click();
});

// update the file name when an attachment is selected
$(".js-issue-attachment-input").on('change', function(e) {
form = $(this).closest("form");

// get only the basename
filename = $(this).val().replace(/^.*[\\\/]/, "");
form.find(".js-attachment-filename").text(filename);
});

$("#issue_label_list")
.bind( "keydown", function( event ) {
if ( event.keyCode === $.ui.keyCode.TAB &&
Expand Down
14 changes: 14 additions & 0 deletions app/views/projects/issues/_issue_attachment.html.haml
@@ -0,0 +1,14 @@
- if @issue.attachment.url
.issue-attachment
.attachment
= link_to @issue.attachment.secure_url, target: "_blank" do
%i.icon-paper-clip
= @issue.attachment_identifier
- if deletable
= link_to delete_attachment_project_issue_path(@project, @issue),
title: "Delete this attachment", method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: "danger js-issue-attachment-delete" do
%i.icon-trash.cred
- if @issue.attachment.image?
= link_to @issue.attachment.url, target: '_blank' do
= image_tag @issue.attachment.url, class: 'issue-image-attach'
.clearfix
3 changes: 3 additions & 0 deletions app/views/projects/issues/show.html.haml
Expand Up @@ -48,6 +48,9 @@
.wiki
= preserve do
= markdown @issue.description

= render partial: 'issue_attachment', locals: { deletable: false }

.context
%cite.cgray
= render partial: 'issue_context', locals: { issue: @issue }
Expand Down
4 changes: 4 additions & 0 deletions config/routes.rb
Expand Up @@ -297,6 +297,10 @@
end

resources :issues, constraints: {id: /\d+/}, except: [:destroy] do
member do
delete :delete_attachment
end

collection do
post :bulk_update
end
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20140224122008_add_attachment_to_issue.rb
@@ -0,0 +1,5 @@
class AddAttachmentToIssue < ActiveRecord::Migration
def change
add_column :issues, :attachment, :string
end
end
1 change: 1 addition & 0 deletions db/schema.rb
Expand Up @@ -84,6 +84,7 @@
t.integer "milestone_id"
t.string "state"
t.integer "iid"
t.string "attachment"
end

add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
Expand Down
40 changes: 40 additions & 0 deletions features/project/issues/file_upload.feature
@@ -0,0 +1,40 @@
Feature: Project Issue File Upload
Background:
Given I sign in as a user
And I own project "Shop"
And project "Shop" have "Release 0.4" open issue
And I visit issue page "Release 0.4"

Scenario: I should see attachment button
And I click link "Edit"
Then I should see button "Choose File ..."

Scenario: I can add and see filename in description
And I click link "Edit"
And I attach file "test_ss.ods"
And I click button "Save changes"
Then I should see link "test_ss.ods"

Scenario: I should see filename in edit form
And issue "Release 0.4" has attachment "test_ss.ods"
And I click link "Edit"
Then I should see link "test_ss.ods"

Scenario: I can add and see image in description
And I click link "Edit"
And I attach image "insane-senior.jpg"
And I click button "Save changes"
Then I should see image "insane-senior.jpg"

Scenario: I should see image in edit form
And issue "Release 0.4" has attachment "insane-senior.jpg"
And I click link "Edit"
Then I should see image "insane-senior.jpg"

@javascript
Scenario: I delete attachment
And issue "Release 0.4" has attachment "test_ss.ods"
And I click link "Edit"
And I click link "Delete"
And I visit issue page "Release 0.4"
Then I should not see link "test_ss.ods"
66 changes: 66 additions & 0 deletions features/steps/project/project_issue_file_upload.rb
@@ -0,0 +1,66 @@
class ProjectIssueFileUpload < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths

And 'project "Shop" have "Release 0.4" open issue' do
project = Project.find_by(name: "Shop")
create(:issue,
title: "Release 0.4",
project: project,
author: project.users.first,
description: "# Description header"
)
end

And 'I click link "Edit"' do
click_link "Edit"
end

And 'I click button "Save changes"' do
click_button "Save changes"
end

And 'I click link "Delete"' do
within(".issue-attachment") do
find(".js-issue-attachment-delete").trigger("click")
sleep 0.05
end
end

Then 'I should see button "Choose File ..."' do
page.should have_content "Choose File ..."
end

And 'issue "Release 0.4" has attachment "test_ss.ods"' do
issue = Issue.find_by(title: "Release 0.4")
issue.attachment = File.open("#{Rails.root}/features/support/test_ss.ods")
issue.save!
end

And 'issue "Release 0.4" has attachment "insane-senior.jpg"' do
issue = Issue.find_by(title: "Release 0.4")
issue.attachment = File.open("#{Rails.root}/features/support/insane-senior.jpg")
issue.save!
end

And 'I attach file "test_ss.ods"' do
attach_file('issue_attachment', File.join(Rails.root, '/features/support/test_ss.ods'))
end

And 'I attach image "insane-senior.jpg"' do
attach_file('issue_attachment', File.join(Rails.root, '/features/support/insane-senior.jpg'))
end

Then 'I should see link "test_ss.ods"' do
page.should have_link "test_ss.ods"
end

Then 'I should not see link "test_ss.ods"' do
page.should_not have_link "test_ss.ods"
end

Then 'I should see image "insane-senior.jpg"' do
page.should have_xpath("//img[contains(@src, \"insane-senior.jpg\")]")
end
end
Binary file added features/support/insane-senior.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added features/support/test_ss.ods
Binary file not shown.