liquidplanner 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. data/HISTORY +3 -0
  2. data/README.rdoc +24 -0
  3. data/Rakefile +35 -0
  4. data/examples/README +14 -0
  5. data/examples/create_task.rb +28 -0
  6. data/examples/list_tasks.rb +62 -0
  7. data/examples/support/helper.rb +34 -0
  8. data/examples/track_time.rb +55 -0
  9. data/lib/liquidplanner.rb +49 -0
  10. data/lib/liquidplanner/base.rb +37 -0
  11. data/lib/liquidplanner/debug.rb +16 -0
  12. data/lib/liquidplanner/ext/connection.rb +11 -0
  13. data/lib/liquidplanner/ext/exceptions.rb +25 -0
  14. data/lib/liquidplanner/ext/hash.rb +6 -0
  15. data/lib/liquidplanner/liquidplanner_resource.rb +58 -0
  16. data/lib/liquidplanner/resources/account.rb +12 -0
  17. data/lib/liquidplanner/resources/activity.rb +6 -0
  18. data/lib/liquidplanner/resources/client.rb +7 -0
  19. data/lib/liquidplanner/resources/comment.rb +6 -0
  20. data/lib/liquidplanner/resources/container.rb +6 -0
  21. data/lib/liquidplanner/resources/document.rb +47 -0
  22. data/lib/liquidplanner/resources/estimate.rb +6 -0
  23. data/lib/liquidplanner/resources/event.rb +6 -0
  24. data/lib/liquidplanner/resources/folder.rb +7 -0
  25. data/lib/liquidplanner/resources/item.rb +73 -0
  26. data/lib/liquidplanner/resources/leaf.rb +22 -0
  27. data/lib/liquidplanner/resources/link.rb +6 -0
  28. data/lib/liquidplanner/resources/luggage.rb +8 -0
  29. data/lib/liquidplanner/resources/member.rb +7 -0
  30. data/lib/liquidplanner/resources/milestone.rb +6 -0
  31. data/lib/liquidplanner/resources/note.rb +21 -0
  32. data/lib/liquidplanner/resources/order.rb +13 -0
  33. data/lib/liquidplanner/resources/priority.rb +13 -0
  34. data/lib/liquidplanner/resources/project.rb +7 -0
  35. data/lib/liquidplanner/resources/relative_resource.rb +12 -0
  36. data/lib/liquidplanner/resources/snapshot.rb +6 -0
  37. data/lib/liquidplanner/resources/task.rb +6 -0
  38. data/lib/liquidplanner/resources/tasklist.rb +7 -0
  39. data/lib/liquidplanner/resources/workspace.rb +34 -0
  40. metadata +155 -0
data/HISTORY ADDED
@@ -0,0 +1,3 @@
1
+ 0.0.1
2
+ -----
3
+ Initial release
@@ -0,0 +1,24 @@
1
+ = LiquidPlanner
2
+ The LiquidPlanner gem provides a simple way to access LiquidPlanner's API.
3
+
4
+ To get started, look at the examples directory. They show how to perform
5
+ some common tasks.
6
+
7
+ Typically you will want to create an API object:
8
+
9
+ lp = LiquidPlanner::Base.new(:email=>email, :password=>password)
10
+
11
+ With the +api+ you can then get the user's account or their workspaces:
12
+
13
+ account = lp.account
14
+ workspaces = lp.workspaces
15
+
16
+ Tasks can be listed and created by accessing a workspace:
17
+
18
+ workspace = lp.workspaces(7) # access a workspace by ID
19
+ tasks = workspace.tasks # list all the tasks
20
+ my_tasks = workspace.tasks(:all, :filter=>'owner_id = me') # use a filter
21
+ new_task = workspace.create_task(:name=>'Learn API') # create and save a new task
22
+
23
+ For more information, visit the LiquidPlanner developer pages at
24
+ http://www.liquidplanner.com/developers
@@ -0,0 +1,35 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require File.dirname(__FILE__) + "/lib/liquidplanner.rb"
5
+
6
+ require 'rubygems'
7
+
8
+ begin
9
+ require 'jeweler'
10
+ Jeweler::Tasks.new do |gemspec|
11
+ gemspec.name = "liquidplanner"
12
+ gemspec.summary = "LiquidPlanner API client"
13
+ gemspec.version = LiquidPlanner::VERSION
14
+ gemspec.description = "LiquidPlanner API client, using ActiveResource"
15
+ gemspec.email = "api@liquidplanner.com"
16
+ gemspec.homepage = "http://github.com/liquidplanner/liquidplanner"
17
+ gemspec.authors = ["Brett Bender", "Adam Sanderson"]
18
+
19
+ gemspec.files = FileList["[A-Z]*", "{examples,lib,test}/**/*"]
20
+
21
+ gemspec.add_dependency 'activeresource', '~> 3.0.0'
22
+ gemspec.add_dependency 'multipart-post', '>= 1.0.1'
23
+
24
+ # For the examples:
25
+ gemspec.add_development_dependency 'highline', '>= 1.5'
26
+ end
27
+ rescue LoadError
28
+ puts "Jeweler not available. Install it with: gem install jeweler"
29
+ end
30
+
31
+ Rake::RDocTask.new do |t|
32
+ t.main = "README.rdoc"
33
+ t.rdoc_files.include("README.rdoc")
34
+ t.rdoc_files.include("lib/**/*.rb")
35
+ end
@@ -0,0 +1,14 @@
1
+ All of the examples expect you to pass in your account name and workspace id,
2
+ for instance:
3
+
4
+ ruby examples/list_tasks.rb alice@example.com 7
5
+
6
+ If you set the VERBOSE environment variable, the examples will print out their
7
+ http requests to the console:
8
+
9
+ ruby examples/list_tasks.rb alice@example.com 7 --verbose
10
+
11
+ These examples all use highline library for prompting the user. Install the
12
+ gem with:
13
+
14
+ sudo gem install highline
@@ -0,0 +1,28 @@
1
+ # Require the LiquidPlanner API.
2
+ require File.dirname(__FILE__) + '/../lib/liquidplanner'
3
+
4
+ # Require support libraries used by this example.
5
+ require 'rubygems'
6
+ require 'highline/import'
7
+ require File.dirname(__FILE__) + '/support/helper'
8
+
9
+ # Get the user's credentials
10
+ email, password, space_id = get_credentials!
11
+
12
+ # Connect to LiquidPlanner and get the workspace
13
+ lp = LiquidPlanner::Base.new(:email=>email, :password=>password)
14
+ workspace = lp.workspaces(space_id)
15
+
16
+ # Ask for a task's name and estimate
17
+ say "Add a new task to '#{workspace.name}'"
18
+ name = ask("New task name")
19
+ low = ask("Min effort", Float)
20
+ high = ask("Max effort", Float){|q| q.above = low}
21
+
22
+ # Submit the task and estimate
23
+ say "Submitting: '#{name}' [#{low} - #{high}] to LiquidPlanner"
24
+ task = workspace.create_task(:name=>name)
25
+ task.create_estimate(:low=>low, :high=>high)
26
+
27
+ # All done
28
+ say "Added task"
@@ -0,0 +1,62 @@
1
+ # Require the LiquidPlanner API.
2
+ require File.dirname(__FILE__) + '/../lib/liquidplanner'
3
+
4
+ # Require support libraries used by this example.
5
+ require 'rubygems'
6
+ require 'highline/import'
7
+ require 'active_support/all'
8
+ require File.dirname(__FILE__) + '/support/helper'
9
+
10
+ # List a set of tasks in a workspace.
11
+ #
12
+ # You will:
13
+ # * Access a workspace
14
+ # * Retrieve the next 5 tasks assigned to you
15
+ # * Display the details of each task
16
+ #
17
+ # This example will use the highline library to handle input and output.
18
+ # You can install it with:
19
+ # gem install highline
20
+
21
+ email, password, space_id = get_credentials!
22
+
23
+ # Create a new LiquidPlanner API object. It requires the login credentials we gathered above.
24
+ lp = LiquidPlanner::Base.new(:email=>email, :password=>password)
25
+
26
+ # Fetch the user's workspace, and then print its name.
27
+ workspace = lp.workspaces(space_id)
28
+ say workspace.name
29
+ say "-" * 40
30
+
31
+ # Fetch up to 5 tasks in the user's workspace, filter to tasks owned by this user
32
+ tasks = workspace.tasks(:all, :limit=>5, :filter=>'owner_id = me')
33
+
34
+ # Print each task.
35
+ # If the task is done, say when it was completed.
36
+ # Otherwise we will print the appropriate information for the task.
37
+ tasks.each do |task|
38
+ say task.name
39
+ if task.is_done
40
+ say "completed on: #{task.done_on.to_date}"
41
+ else
42
+ case task
43
+ when LiquidPlanner::Resources::Task
44
+ # Normal tasks should show their promise date, and expected completion date:
45
+ say "due: #{task.promise_by.to_date}" if task.promise_by
46
+ say "expected: #{task.expected_finish.to_date}" if task.expected_finish
47
+
48
+ when LiquidPlanner::Resources::Milestone
49
+ # Milestones should show their date
50
+ say "date: #{task.date.to_date}"
51
+
52
+ when LiquidPlanner::Resources::Event
53
+ # Events should show their start to completion date
54
+ say "from: #{task.start_date.to_date}"
55
+ say "to: #{task.finish_date.to_date}"
56
+ end
57
+ end
58
+
59
+ # Print the description
60
+ say task.description
61
+ say "-" * 40
62
+ end
@@ -0,0 +1,34 @@
1
+ # Turn on request tracking if the `VERBOSE` environment variable is set:
2
+ if ARGV.last == '--verbose'
3
+ ARGV.pop
4
+ require File.dirname(__FILE__) + '/../../lib/liquidplanner/debug'
5
+ LiquidPlanner.watch_requests! do |method, request, payload|
6
+ say "<%= color('[#{method}] #{request}', :green)%>"
7
+ end
8
+ end
9
+
10
+ # This helper is used by the example files to get a user's email, password,
11
+ # and workspace id.
12
+ def get_credentials!
13
+ # Get the user's Email and Workspace ID from the command line arguments.
14
+ # See usage below for how this should be called.
15
+ if ARGV.length == 2
16
+ email = ARGV.shift
17
+ space_id = ARGV.shift.to_i
18
+ else
19
+ puts "Usage:"
20
+ puts " ruby #{$0} <account> <workspace id>"
21
+ puts "Example:"
22
+ puts " ruby #{$0} alice@example.com 7"
23
+ exit 1
24
+ end
25
+
26
+ # Work around a highline bug:
27
+ HighLine.track_eof = false
28
+
29
+ # We use the highline library here to request the user's password.
30
+ # Passwords should never be stored in plain text.
31
+ password = ask("LiquidPlanner password for #{email}:") {|q| q.echo = false}
32
+
33
+ return email, password, space_id
34
+ end
@@ -0,0 +1,55 @@
1
+ # Require the LiquidPlanner API.
2
+ require File.dirname(__FILE__) + '/../lib/liquidplanner'
3
+
4
+ # Require support libraries used by this example.
5
+ require 'rubygems'
6
+ require 'highline/import'
7
+ require File.dirname(__FILE__) + '/support/helper'
8
+
9
+ # Get the user's credentials
10
+ email, password, space_id = get_credentials!
11
+
12
+ # Create a new LiquidPlanner API object with the login credentials
13
+ lp = LiquidPlanner::Base.new(:email=>email, :password=>password)
14
+
15
+ # Get the user's workspace and account, and then print its name.
16
+ workspace = lp.workspaces(space_id)
17
+ account = lp.account
18
+
19
+ say workspace.name
20
+ say "-" * 40
21
+
22
+ # Get the user's tasks
23
+ tasks = workspace.tasks(:all, :filter=>'owner_id = me', :include_associated=>'activities')
24
+
25
+ if tasks.empty?
26
+ say "No tasks are assigned to you."
27
+ exit
28
+ end
29
+
30
+ # Pick a task to log time for
31
+ choose do |menu|
32
+ menu.prompt = "Log time for:"
33
+ tasks.each do |t|
34
+ menu.choice(t.name) do
35
+ say t.name
36
+ work = ask("Log how much? (hours)", Float)
37
+ low = ask("New low effort: (hours)", Float)
38
+ high = ask("New high effort: (hours)", Float){|q| q.above = low}
39
+
40
+ activity_id = 0
41
+ if !t.activities.empty?
42
+ choose do |activity_menu|
43
+ activity_menu.prompt = "Use activity:"
44
+ t.activities.each do |act|
45
+ activity_menu.choice(act.name) do
46
+ activity_id = act.id
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ t.track_time :low=>low, :high=>high, :member_id=>account.id, :work=>work, :activity_id=>activity_id
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,49 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ module LiquidPlanner
5
+ VERSION = "0.0.1"
6
+ API_BASE_URL = "https://app.liquidplanner.com/api"
7
+ end
8
+
9
+ require "rubygems"
10
+ gem "activeresource", '~> 3.0'
11
+ gem "multipart-post"
12
+
13
+ require "active_resource"
14
+ require "net/http/post/multipart" # for uploading documents
15
+
16
+ # could also glob these paths and require all matches...
17
+ require "liquidplanner/base"
18
+ require "liquidplanner/liquidplanner_resource"
19
+ require "liquidplanner/resources/account"
20
+ require "liquidplanner/resources/member"
21
+ require "liquidplanner/resources/workspace"
22
+
23
+ require "liquidplanner/resources/relative_resource"
24
+ require "liquidplanner/resources/priority"
25
+ require "liquidplanner/resources/order"
26
+
27
+ require "liquidplanner/resources/item"
28
+ require "liquidplanner/resources/container"
29
+ require "liquidplanner/resources/leaf"
30
+ require "liquidplanner/resources/task"
31
+ require "liquidplanner/resources/event"
32
+ require "liquidplanner/resources/milestone"
33
+ require "liquidplanner/resources/tasklist"
34
+ require "liquidplanner/resources/folder"
35
+ require "liquidplanner/resources/project"
36
+ require "liquidplanner/resources/client"
37
+
38
+ require "liquidplanner/resources/luggage"
39
+ require "liquidplanner/resources/note"
40
+ require "liquidplanner/resources/comment"
41
+ require "liquidplanner/resources/document"
42
+ require "liquidplanner/resources/link"
43
+ require "liquidplanner/resources/estimate"
44
+ require "liquidplanner/resources/snapshot"
45
+ require "liquidplanner/resources/activity"
46
+
47
+ require "liquidplanner/ext/hash"
48
+ require "liquidplanner/ext/connection"
49
+ require "liquidplanner/ext/exceptions"
@@ -0,0 +1,37 @@
1
+ module LiquidPlanner
2
+
3
+ # sets up the URL and credentials for an API "session",
4
+ # then provides convenience accessors for the account and workspaces
5
+ #
6
+ # N.B. since ActiveResource uses class variables to configure endpoint and
7
+ # auth, you can only have one such "session" active at a time
8
+ #
9
+ class Base
10
+
11
+ def initialize(options={})
12
+ options.assert_valid_keys(:email, :password, :api_base_url)
13
+ options.assert_required_keys(:email, :password)
14
+ @email = options[:email]
15
+ @password = options[:password]
16
+ @api_base_url = options[:api_base_url] || LiquidPlanner::API_BASE_URL
17
+ configure_base_resource
18
+ end
19
+
20
+ def account
21
+ LiquidPlanner::Resources::Account.find(:one, :from => "/api/account")
22
+ end
23
+
24
+ def workspaces( scope=:all )
25
+ LiquidPlanner::Resources::Workspace.find(scope)
26
+ end
27
+
28
+ private
29
+
30
+ def configure_base_resource
31
+ LiquidPlannerResource.site = @api_base_url
32
+ LiquidPlannerResource.user = @email
33
+ LiquidPlannerResource.password = @password
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,16 @@
1
+ module LiquidPlanner
2
+ # Print out all the outgoing requests from the LiquidPlanner API
3
+ def self.watch_requests!(&block)
4
+ ActiveSupport::Notifications.subscribe('active_resource.request') do |name, time, stamp, id, payload|
5
+ method = payload[:method]
6
+ request = payload[:request_uri]
7
+
8
+ if block
9
+ block.call(method, request, payload)
10
+ else
11
+ puts "[#{method}] #{request}"
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module ActiveResource
2
+ class Connection
3
+
4
+ # get a raw response (not just the decoded response body);
5
+ # used for non-standard responses (e.g. binary / file data)
6
+ def get_raw(path, headers = {})
7
+ with_auth { request(:get, path, build_request_headers(headers, :get, self.site.merge(path))) }
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ module ActiveResource
2
+ #:nodoc:
3
+ class ClientError < ActiveResource::ConnectionError
4
+
5
+ # Extend client errors so that they annotate the response message with the details
6
+ # from a JSON response. LiquidPlanner sends back useful response bodies, so we might as well
7
+ # use them if they are available.
8
+ def initialize(response, message=nil)
9
+ if !message && response['Content-Type']['application/json']
10
+ details = ActiveSupport::JSON.decode(response.body)
11
+ detailed_message = details['message']
12
+ super response, detailed_message
13
+ else
14
+ super response, message
15
+ end
16
+ end
17
+
18
+ # Return the default exception message with the extracted API message as well if available
19
+ def to_s
20
+ str = super
21
+ str << " #{@message}" if @message
22
+ str
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,6 @@
1
+ class Hash
2
+ def assert_required_keys(*required_keys)
3
+ missing_keys = required_keys.select {|key| !keys.include?(key)}
4
+ raise ArgumentError, "Missing required option(s): #{missing_keys.join(", ")}" unless missing_keys.empty?
5
+ end
6
+ end
@@ -0,0 +1,58 @@
1
+ module LiquidPlanner
2
+
3
+ # generic resource, extended by all LiquidPlanner::Resources
4
+ class LiquidPlannerResource < ActiveResource::Base
5
+ self.format = :json
6
+ self.include_root_in_json = true
7
+
8
+ # LiquidPlanner does not send back data that is already in the path (prefix_options), so merge
9
+ # any values that the object already had back into the prefix_options.
10
+ def load(attributes)
11
+ initial_prefix_options = @prefix_options.clone
12
+ super
13
+ @prefix_options.merge!(initial_prefix_options){|k, old_attr,new_attr| old_attr || new_attr }
14
+ self
15
+ end
16
+
17
+ private
18
+ # Override the default instantiate_record to support polymorphic types
19
+ # based on the `type` attribute LiquidPlanner returns.
20
+ def self.instantiate_record(record, prefix_options = {})
21
+ if record['type']
22
+ cls = LiquidPlanner::Resources.const_get(record['type'])
23
+ else
24
+ cls = self
25
+ end
26
+
27
+ cls.new(record).tap do |resource|
28
+ resource.prefix_options = prefix_options
29
+ end
30
+ end
31
+
32
+ # Builds a new resource from the current one.
33
+ def build_new_resource(klass, attributes={})
34
+ raise ArgumentError, "#{klass} should subclass #{LiquidPlannerResource}" unless klass < LiquidPlannerResource
35
+ klass.new(attributes).tap do |item|
36
+ item.prefix_options = self.prefix_options.clone
37
+ end
38
+ end
39
+
40
+ # Special handling for creating associated resources. For instance:
41
+ # workspace.create_task(:name=>'new task').create_estimate(:low_effort=>1, :high_effort=>3)
42
+ def method_missing(name, *args)
43
+ if name.to_s =~ /^(create|build)_(.+)/
44
+ operation = $1.to_sym
45
+ resource = $2
46
+ attributes = args.shift || {}
47
+ klass = LiquidPlanner::Resources.const_get(resource.classify)
48
+ obj = build_new_resource(klass, attributes)
49
+ obj.save if operation == :create
50
+ obj
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,12 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Account < LiquidPlanner::LiquidPlannerResource
4
+
5
+ def self.account
6
+ find( :one, :from => "/api/account" )
7
+ end
8
+
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,6 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Activity < Luggage
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Client < Container
4
+ include Order
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Comment < Luggage
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Container < Item
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,47 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Document < Luggage
4
+
5
+ def self.upload_document( workspace_id, item_id, attached_file_path, options={} )
6
+ file_name = options[:file_name] || File.basename(attached_file_path)
7
+ description = options[:description] || ""
8
+ content_type = options[:content_type] || "application/octet-stream"
9
+ url = site + "/api/workspaces/#{workspace_id}/treeitems/#{item_id}/documents"
10
+ File.open(attached_file_path) do |handle|
11
+ req = Net::HTTP::Post::Multipart.new( url.path, "document[file_name]" => file_name,
12
+ "document[description]" => description,
13
+ "document[attached_file]" => UploadIO.new(handle, content_type, attached_file_path)
14
+ )
15
+ req.basic_auth( user, password )
16
+ res = nil
17
+ begin
18
+ res = Net::HTTP.start(url.host, url.port) do |http|
19
+ http.use_ssl = true if url.scheme == "https"
20
+ http.request(req)
21
+ end
22
+ rescue Timeout::Error => e
23
+ raise TimeoutError.new(e.message)
24
+ rescue OpenSSL::SSL::SSLError => e
25
+ raise SSLError.new(e.message)
26
+ end
27
+ connection.send(:handle_response, res)
28
+ doc = new(format.decode(res.body))
29
+ end
30
+ end
31
+
32
+ def get_raw(custom_method_name, options = {})
33
+ connection.get_raw(custom_method_element_url(custom_method_name, options), self.class.headers)
34
+ end
35
+
36
+ def download
37
+ get_raw(:download)
38
+ end
39
+
40
+ def thumbnail
41
+ get_raw(:thumbnail)
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,6 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Estimate < Luggage
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Event < Leaf
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Folder < Container
4
+ include Order
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,73 @@
1
+ #------------------------------------------------------------------------
2
+ # generic item, could be a task, folder, tasklist, etc.
3
+ #------------------------------------------------------------------------
4
+ module LiquidPlanner
5
+ module Resources
6
+ class Item < LiquidPlanner::LiquidPlannerResource
7
+
8
+ self.prefix = "/api/workspaces/:workspace_id/"
9
+
10
+ def folder
11
+ Folder.find( :one, :from => "/api/workspaces/#{workspace_id}/folders/#{folder_id}" )
12
+ end
13
+
14
+ def tasklist
15
+ Tasklist.find( :one, :from => "/api/workspaces/#{workspace_id}/tasklists/#{tasklist_id}" )
16
+ end
17
+
18
+ def note
19
+ Note.find( :one, :from => "/api/workspaces/#{workspace_id}/#{item_collection}/#{id}/note" ).tap do |n|
20
+ n.prefix_options = luggage_params
21
+ end
22
+ end
23
+
24
+ def comments( scope=:all )
25
+ Comment.find( scope, :params => luggage_params )
26
+ end
27
+
28
+ def documents( scope=:all )
29
+ Document.find( scope, :params => luggage_params )
30
+ end
31
+
32
+ def attach_document( attached_file_path, options={} )
33
+ doc = Document.upload_document( workspace_id, id, attached_file_path, options )
34
+ doc.prefix_options = luggage_params
35
+ doc
36
+ end
37
+
38
+ def links( scope=:all )
39
+ Link.find( scope, :params => luggage_params )
40
+ end
41
+
42
+ def estimates( scope=:all )
43
+ Estimate.find( scope, :params => luggage_params )
44
+ end
45
+
46
+ # LiquidPlanner::Item => 'items'
47
+ def item_collection
48
+ self.class.to_s.split('::').last.downcase.pluralize
49
+ end
50
+
51
+ def workspace_id
52
+ prefix_options[:workspace_id]
53
+ end
54
+
55
+ protected
56
+
57
+ def build_new_resource(klass, attributes={})
58
+ super(klass, attributes).tap do |item|
59
+ item.prefix_options = luggage_params
60
+ end
61
+ end
62
+
63
+ def luggage_params
64
+ {
65
+ :workspace_id => self.workspace_id,
66
+ :item_collection => self.item_collection,
67
+ :item_id => self.id
68
+ }
69
+ end
70
+
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,22 @@
1
+ #------------------------------------------------------------------------
2
+ # leaves
3
+ #------------------------------------------------------------------------
4
+
5
+ module LiquidPlanner
6
+ module Resources
7
+ class Leaf < Item
8
+ include Priority
9
+ include Order
10
+
11
+ TRACK_TIME_KEYS = [ :work, :activity_id, :member_id, :low, :high, :is_done, :done_on, :work_performed_on, :comment ].freeze
12
+ def track_time( options={} )
13
+ options.assert_valid_keys( *TRACK_TIME_KEYS )
14
+ request_body = options.to_json
15
+ response = post(:track_time, {}, request_body)
16
+ load( self.class.format.decode( response.body ) )
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,6 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Link < Luggage
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Luggage < LiquidPlanner::LiquidPlannerResource
4
+ self.prefix = "/api/workspaces/:workspace_id/:item_collection/:item_id/"
5
+ end
6
+ end
7
+ end
8
+
@@ -0,0 +1,7 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Member < LiquidPlanner::LiquidPlannerResource
4
+ self.prefix = "/api/workspaces/:workspace_id/"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Milestone < Leaf
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Note < Luggage
4
+
5
+ # an item has either 0 or 1 note; the "collection" is singular in name,
6
+ # and should be accessed as:
7
+ #
8
+ # /api/workspaces/:workspace_id/:items/:item_id/note <= without /ID.json suffix
9
+ #
10
+ self.collection_name = 'note'
11
+
12
+ def self.element_path(id, prefix_options = {}, query_options = nil)
13
+ path = super(id, prefix_options, query_options)
14
+ path.sub!( "/#{id}.#{format.extension}", "" )
15
+ path
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,13 @@
1
+ module LiquidPlanner
2
+ # For resources that can be prioritized
3
+ module Order
4
+ include RelativeResource
5
+ def organize_before(item)
6
+ move_relative_to :organize, :before, item
7
+ end
8
+
9
+ def organize_after(item)
10
+ move_relative_to :organize, :after, item
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module LiquidPlanner
2
+ # For resources that can be prioritized
3
+ module Priority
4
+ include RelativeResource
5
+ def prioritize_before(item)
6
+ move_relative_to :prioritize, :before, item
7
+ end
8
+
9
+ def prioritize_after(item)
10
+ move_relative_to :prioritize, :after, item
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Project < Item
4
+ include Order
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ module LiquidPlanner
2
+ module RelativeResource
3
+ def move_relative_to(tree, relative, other)
4
+ raise ArgumentError.new("tree must be prioritize or organize") unless [:prioritize, :organize].include?(tree)
5
+ raise ArgumentError.new("relative must be before or after") unless [:before, :after].include?(relative)
6
+ other_id = other.is_a?(LiquidPlanner::LiquidPlannerResource) ? other.id : other
7
+
8
+ response = post("#{tree}_#{relative}", :other_id=>other_id)
9
+ load( self.class.format.decode( response.body ) )
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Snapshot < Luggage
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Task < Leaf
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Tasklist < Container
4
+ include Priority
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ module LiquidPlanner
2
+ module Resources
3
+ class Workspace < LiquidPlanner::LiquidPlannerResource
4
+
5
+ def members( scope=:all, options={} )
6
+ Member.find( scope, :params => { :workspace_id => self.id }.merge(options) )
7
+ end
8
+
9
+ def tasks( scope=:all, options={} )
10
+ Task.find( scope, :params => { :workspace_id => self.id }.merge(options) )
11
+ end
12
+
13
+ def folders( scope=:all, options={} )
14
+ Folder.find( scope, :params => { :workspace_id => self.id, :flat => true }.merge(options) )
15
+ end
16
+
17
+ def tasklists( scope=:all, options={} )
18
+ Tasklist.find( scope, :params => { :workspace_id => self.id, :flat => true }.merge(options) )
19
+ end
20
+
21
+ protected
22
+ # create a new instance of klass (Task, Folder, etc.),
23
+ # with the workspace_id set as a prefix option
24
+ #
25
+ # workspace.build_new_resource( LiquidPlanner::Resources::Task, :name => "new task" ).save
26
+ #
27
+ def build_new_resource( klass, attributes={} )
28
+ super(klass, attributes).tap do |item|
29
+ item.prefix_options[:workspace_id] = self.id
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: liquidplanner
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Brett Bender
14
+ - Adam Sanderson
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-07-14 00:00:00 -07:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: activeresource
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ hash: 7
31
+ segments:
32
+ - 3
33
+ - 0
34
+ - 0
35
+ version: 3.0.0
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: multipart-post
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ hash: 21
47
+ segments:
48
+ - 1
49
+ - 0
50
+ - 1
51
+ version: 1.0.1
52
+ type: :runtime
53
+ version_requirements: *id002
54
+ - !ruby/object:Gem::Dependency
55
+ name: highline
56
+ prerelease: false
57
+ requirement: &id003 !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 5
63
+ segments:
64
+ - 1
65
+ - 5
66
+ version: "1.5"
67
+ type: :development
68
+ version_requirements: *id003
69
+ description: LiquidPlanner API client, using ActiveResource
70
+ email: api@liquidplanner.com
71
+ executables: []
72
+
73
+ extensions: []
74
+
75
+ extra_rdoc_files:
76
+ - README.rdoc
77
+ files:
78
+ - HISTORY
79
+ - README.rdoc
80
+ - Rakefile
81
+ - examples/README
82
+ - examples/create_task.rb
83
+ - examples/list_tasks.rb
84
+ - examples/support/helper.rb
85
+ - examples/track_time.rb
86
+ - lib/liquidplanner.rb
87
+ - lib/liquidplanner/base.rb
88
+ - lib/liquidplanner/debug.rb
89
+ - lib/liquidplanner/ext/connection.rb
90
+ - lib/liquidplanner/ext/exceptions.rb
91
+ - lib/liquidplanner/ext/hash.rb
92
+ - lib/liquidplanner/liquidplanner_resource.rb
93
+ - lib/liquidplanner/resources/account.rb
94
+ - lib/liquidplanner/resources/activity.rb
95
+ - lib/liquidplanner/resources/client.rb
96
+ - lib/liquidplanner/resources/comment.rb
97
+ - lib/liquidplanner/resources/container.rb
98
+ - lib/liquidplanner/resources/document.rb
99
+ - lib/liquidplanner/resources/estimate.rb
100
+ - lib/liquidplanner/resources/event.rb
101
+ - lib/liquidplanner/resources/folder.rb
102
+ - lib/liquidplanner/resources/item.rb
103
+ - lib/liquidplanner/resources/leaf.rb
104
+ - lib/liquidplanner/resources/link.rb
105
+ - lib/liquidplanner/resources/luggage.rb
106
+ - lib/liquidplanner/resources/member.rb
107
+ - lib/liquidplanner/resources/milestone.rb
108
+ - lib/liquidplanner/resources/note.rb
109
+ - lib/liquidplanner/resources/order.rb
110
+ - lib/liquidplanner/resources/priority.rb
111
+ - lib/liquidplanner/resources/project.rb
112
+ - lib/liquidplanner/resources/relative_resource.rb
113
+ - lib/liquidplanner/resources/snapshot.rb
114
+ - lib/liquidplanner/resources/task.rb
115
+ - lib/liquidplanner/resources/tasklist.rb
116
+ - lib/liquidplanner/resources/workspace.rb
117
+ has_rdoc: true
118
+ homepage: http://github.com/liquidplanner/liquidplanner
119
+ licenses: []
120
+
121
+ post_install_message:
122
+ rdoc_options:
123
+ - --charset=UTF-8
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ hash: 3
132
+ segments:
133
+ - 0
134
+ version: "0"
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ none: false
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ hash: 3
141
+ segments:
142
+ - 0
143
+ version: "0"
144
+ requirements: []
145
+
146
+ rubyforge_project:
147
+ rubygems_version: 1.3.7
148
+ signing_key:
149
+ specification_version: 3
150
+ summary: LiquidPlanner API client
151
+ test_files:
152
+ - examples/create_task.rb
153
+ - examples/list_tasks.rb
154
+ - examples/support/helper.rb
155
+ - examples/track_time.rb