Skip to content

Commit

Permalink
Feature/adding file attachments (#108)
Browse files Browse the repository at this point in the history
* File attachments, delete and reload comments with story, create comments with epics
* Specs, rest-calls refactor, README and bugfixes
* CR changes - remove dead code, fix indentation, mark todo
  • Loading branch information
s-ashwinkumar authored and forest committed Aug 14, 2017
1 parent a9eb54d commit bd4639a
Show file tree
Hide file tree
Showing 20 changed files with 263 additions and 37 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,20 @@ comments = story.comments # co

comment = story.create_comment(text: "Use the force!") # Create a new comment on the story

comment = story.create_comment(text: "Use the force again !", # Create a new comment on the story with
files: ['path/to/an/existing/file']) # file attachments

comment.text += " (please be careful)"
comment.save # Update text of an existing comment
comment.delete # Delete an existing comment

comment.create_attachments(files: ['path/to/an/existing/file']) # Add attachments to existing comment
comment.delete_attachments # Delete all attachments from a comment

attachments = comment.attachments # Get attachments associated with a comment
attachments.first.delete # Delete a specific attachment

comment.attachments(reload: true) # Re-load the attachments after modification
task = story.tasks.first # Get story tasks
task.complete = true
task.save # Mark a task complete
Expand Down
6 changes: 6 additions & 0 deletions lib/tracker_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# dependencies
require 'faraday'
require 'faraday_middleware'
require 'pathname'
require 'mimemagic'

if defined?(ActiveSupport)
require 'active_support/core_ext/object/blank'
Expand All @@ -26,6 +28,7 @@ module TrackerApi
autoload :Error, 'tracker_api/error'
autoload :Client, 'tracker_api/client'
autoload :Logger, 'tracker_api/logger'
autoload :FileUtility, 'tracker_api/file_utility'

module Errors
class UnexpectedData < StandardError; end
Expand Down Expand Up @@ -57,6 +60,8 @@ module Endpoints
autoload :Webhook, 'tracker_api/endpoints/webhook'
autoload :Webhooks, 'tracker_api/endpoints/webhooks'
autoload :StoryTransitions, 'tracker_api/endpoints/story_transitions'
autoload :Attachment, 'tracker_api/endpoints/attachment'
autoload :Attachments, 'tracker_api/endpoints/attachments'
end

module Resources
Expand Down Expand Up @@ -87,5 +92,6 @@ module Shared
autoload :Comment, 'tracker_api/resources/comment'
autoload :Webhook, 'tracker_api/resources/webhook'
autoload :StoryTransition, 'tracker_api/resources/story_transition'
autoload :FileAttachment, 'tracker_api/resources/file_attachment'
end
end
35 changes: 5 additions & 30 deletions lib/tracker_api/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,40 +48,15 @@ def initialize(options={}, &block)
end
end

# Make a HTTP GET request
# HTTP requests methods
#
# @param path [String] The path, relative to api endpoint
# @param options [Hash] Query and header params for request
# @return [Faraday::Response]
def get(path, options = {})
request(:get, parse_query_and_convenience_headers(path, options))
end

# Make a HTTP POST request
#
# @param path [String] The path, relative to api endpoint
# @param options [Hash] Query and header params for request
# @return [Faraday::Response]
def post(path, options = {})
request(:post, parse_query_and_convenience_headers(path, options))
end

# Make a HTTP PUT request
#
# @param path [String] The path, relative to api endpoint
# @param options [Hash] Query and header params for request
# @return [Faraday::Response]
def put(path, options = {})
request(:put, parse_query_and_convenience_headers(path, options))
end

# Make a HTTP DELETE request
#
# @param path [String] The path, relative to api endpoint
# @param options [Hash] Query and header params for request
# @return [Faraday::Response]
def delete(path, options = {})
request(:delete, parse_query_and_convenience_headers(path, options))
%i{get post patch put delete}.each do |verb|
define_method verb do |path, options = {}|
request(verb, parse_query_and_convenience_headers(path, options))
end
end

# Make one or more HTTP GET requests, optionally fetching
Expand Down
38 changes: 38 additions & 0 deletions lib/tracker_api/endpoints/attachment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module TrackerApi
module Endpoints
class Attachment
attr_accessor :client

def initialize(client)
@client = client
end

def create(comment, file)
data = client.post("/projects/#{comment.project_id}/uploads", body: FileUtility.get_file_upload(file)).body
Resources::FileAttachment.new({ comment: comment }.merge(data))
end

# TODO : Discuss before implementing this as it orphans the file.
# It deletes source, but the file name appears in the comments
# def delete(comment, file_attachment_id)
# client.delete("/projects/#{comment.project_id}/stories/#{comment.story_id}/comments/#{comment.id}/file_attachments/#{file_attachment_id}").body
# end

def get(comment)
data = client.get("/projects/#{comment.project_id}/stories/#{comment.story_id}/comments/#{comment.id}?fields=file_attachments").body["file_attachments"]
raise Errors::UnexpectedData, 'Array of file attachments expected' unless data.is_a? Array

data.map do |file_attachment|
Resources::FileAttachment.new({ comment: comment }.merge(file_attachment))
end
end

# TODO : Implement this properly.
# This results in either content of the file or an S3 link.
# the S3 link is also present in big_url attribute.
# def download(download_path)
# client.get(download_path, url: '').body
# end
end
end
end
22 changes: 22 additions & 0 deletions lib/tracker_api/endpoints/attachments.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module TrackerApi
module Endpoints
class Attachments
attr_accessor :client

def initialize(client)
@client = client
end


def create(comment, files)
return [] if files.to_a.empty?
#Check files before upload to make it all or none.
FileUtility.check_files_exist(files)
attachment = Endpoints::Attachment.new(client)
files.map do | file |
attachment.create(comment, file)
end
end
end
end
end
11 changes: 9 additions & 2 deletions lib/tracker_api/endpoints/comment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,20 @@ def create(project_id, story_id, params={})
def update(comment, params={})
raise ArgumentError, 'Valid comment required to update.' unless comment.instance_of?(Resources::Comment)

data = client.put("/projects/#{comment.project_id}/stories/#{comment.story_id}/comments/#{comment.id}",
params: params).body
path = "/projects/#{comment.project_id}/stories/#{comment.story_id}/comments/#{comment.id}"
path += "?fields=:default,file_attachments" if params.represented.file_attachment_ids_to_add.present? || params.represented.file_attachment_ids_to_remove.present?
data = client.put(path, params: params).body

comment.attributes = data
comment.clean!
comment
end

def delete(comment)
raise ArgumentError, 'Valid comment required to update.' unless comment.instance_of?(Resources::Comment)

client.delete("/projects/#{comment.project_id}/stories/#{comment.story_id}/comments/#{comment.id}").body
end
end
end
end
16 changes: 16 additions & 0 deletions lib/tracker_api/file_utility.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module TrackerApi
class FileUtility
class << self
def get_file_upload(file)
mime_type = MimeMagic.by_path(file)
{ :file => Faraday::UploadIO.new(file, mime_type) }
end

def check_files_exist(files)
files.each do | file |
raise ArgumentError, 'Attachment file not found.' unless Pathname.new(file).exist?
end
end
end
end
end
35 changes: 35 additions & 0 deletions lib/tracker_api/resources/comment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ class Comment
attribute :created_at, DateTime
attribute :updated_at, DateTime
attribute :file_attachment_ids, [Integer]
attribute :file_attachments, [FileAttachment]
attribute :google_attachment_ids, [Integer]
attribute :file_attachment_ids_to_add, [Integer]
attribute :file_attachment_ids_to_remove, [Integer]
attribute :commit_identifier, String
attribute :commit_type, String
attribute :kind, String
Expand All @@ -24,13 +27,45 @@ class UpdateRepresenter < Representable::Decorator

property :id
property :text
collection :file_attachment_ids_to_add
collection :file_attachment_ids_to_remove
end

def save
raise ArgumentError, 'Cannot update a comment with an unknown story_id.' if story_id.nil?

Endpoints::Comment.new(client).update(self, UpdateRepresenter.new(Comment.new(self.dirty_attributes)))
end

def delete
raise ArgumentError, 'Cannot delete a comment with an unknown story_id.' if story_id.nil?

Endpoints::Comment.new(client).delete(self)
end

# @param [Hash] params attributes to create the comment with
# @return [Comment] newly created Comment
def create_attachments(params)
self.file_attachment_ids_to_add = Endpoints::Attachments.new(client).create(self, params[:files]).collect(&:id)
save
end

def delete_attachments(attachment_ids = nil)
self.file_attachment_ids_to_remove = attachment_ids || attachments.collect(&:id)
save
end

# Provides a list of all the attachments on the comment.
#
# @reload Boolean to reload the attachments
# @return [Array[FileAttachments]]
def attachments(reload: false)
if !reload && @file_attachments.present?
@file_attachments
else
@file_attachments = Endpoints::Attachment.new(client).get(self)
end
end
end
end
end
9 changes: 9 additions & 0 deletions lib/tracker_api/resources/epic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ def save

Endpoints::Epic.new(client).update(self, UpdateRepresenter.new(self))
end

# @param [Hash] params attributes to create the comment with
# @return [Comment] newly created Comment
def create_comment(params)
files = params.delete(:files)
comment = Endpoints::Comment.new(client).create(project_id, id, params)
comment.create_attachments(files: files) if files.present?
comment
end
end
end
end
37 changes: 37 additions & 0 deletions lib/tracker_api/resources/file_attachment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module TrackerApi
module Resources
class FileAttachment
include Shared::Base

attribute :comment, Comment

attribute :id, Integer
attribute :big_url, String
attribute :content_type, String
attribute :created_at, DateTime
attribute :download_url, String
attribute :filename, String
attribute :height, Integer
attribute :kind, String
attribute :size, Integer
attribute :thumbnail_url, String
attribute :thumbnailable, Boolean
attribute :uploaded, Boolean
attribute :uploader_id, Integer
attribute :width, Integer

def delete
comment.delete_attachments([id])
end

# TODO : Implement download properly.
# Look at Attchment#download for more details
# The big_url actually has the AWS S3 link for the file
# def download
# file_data = Endpoints::Attachment.new(comment.client).download(download_url)
# File.open(filename, 'wb') { |fp| fp.write(file_data) }
# end
end
end
end

11 changes: 7 additions & 4 deletions lib/tracker_api/resources/story.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,11 @@ def activity(params = {})
#
# @param [Hash] params
# @return [Array[Comment]]
def comments(params = {})
if params.blank? && @comments.present?
def comments(reload: false)
if !reload && @comments.present?
@comments
else
@comments = Endpoints::Comments.new(client).get(project_id, id, params)
@comments = Endpoints::Comments.new(client).get(project_id, id)
end
end

Expand Down Expand Up @@ -161,7 +161,10 @@ def create_task(params)
# @param [Hash] params attributes to create the comment with
# @return [Comment] newly created Comment
def create_comment(params)
Endpoints::Comment.new(client).create(project_id, id, params)
files = params.delete(:files)
comment = Endpoints::Comment.new(client).create(project_id, id, params)
comment.create_attachments(files: files) if files.present?
comment
end

# Save changes to an existing Story.
Expand Down
42 changes: 42 additions & 0 deletions test/comment_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@
comment.clean?.must_equal true
end

it 'can create a comment with file attachment' do
text = "Test creating a comment"
comment = nil
files = [File.expand_path('../Gemfile', File.dirname(__FILE__))]
VCR.use_cassette('create comment with attachment', record: :new_episodes) do
comment = story.create_comment(text: text, files: files)
end
comment.text.must_equal text
comment.attachments.size.must_equal 1
comment.clean?.must_equal true
end

it 'can update an existing comment' do
new_text = "#{existing_comment.text}+"
existing_comment.text = new_text
Expand All @@ -32,4 +44,34 @@
existing_comment.text.must_equal new_text
existing_comment.clean?.must_equal true
end

it 'can create attachments in a comment' do
files = [File.expand_path('../Gemfile', File.dirname(__FILE__))]
VCR.use_cassette('create attachments', record: :new_episodes) do
existing_comment.create_attachments(files: files)
existing_comment.attachments.size.must_equal 1
existing_comment.clean?.must_equal true
end
end

it 'can delete attachments in a comment' do
files = [File.expand_path('../Gemfile', File.dirname(__FILE__))]
VCR.use_cassette('delete attachments', record: :new_episodes) do
existing_comment.create_attachments(files: files)
existing_comment.attachments.size.must_equal 1
existing_comment.delete_attachments
existing_comment.attachments.size.must_equal 0
end
end

it 'can delete a comment' do
VCR.use_cassette('delete comment', record: :new_episodes) do
current_story = project.story(story_id)
new_comment_id = current_story.create_comment(text: "test comment").id
current_story.comments.last.id.must_equal new_comment_id
current_story.comments.last.delete
current_story = project.story(story_id)
current_story.comments.last.id.wont_equal new_comment_id
end
end
end
Loading

0 comments on commit bd4639a

Please sign in to comment.