liquidplanner 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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