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.
- data/HISTORY +3 -0
- data/README.rdoc +24 -0
- data/Rakefile +35 -0
- data/examples/README +14 -0
- data/examples/create_task.rb +28 -0
- data/examples/list_tasks.rb +62 -0
- data/examples/support/helper.rb +34 -0
- data/examples/track_time.rb +55 -0
- data/lib/liquidplanner.rb +49 -0
- data/lib/liquidplanner/base.rb +37 -0
- data/lib/liquidplanner/debug.rb +16 -0
- data/lib/liquidplanner/ext/connection.rb +11 -0
- data/lib/liquidplanner/ext/exceptions.rb +25 -0
- data/lib/liquidplanner/ext/hash.rb +6 -0
- data/lib/liquidplanner/liquidplanner_resource.rb +58 -0
- data/lib/liquidplanner/resources/account.rb +12 -0
- data/lib/liquidplanner/resources/activity.rb +6 -0
- data/lib/liquidplanner/resources/client.rb +7 -0
- data/lib/liquidplanner/resources/comment.rb +6 -0
- data/lib/liquidplanner/resources/container.rb +6 -0
- data/lib/liquidplanner/resources/document.rb +47 -0
- data/lib/liquidplanner/resources/estimate.rb +6 -0
- data/lib/liquidplanner/resources/event.rb +6 -0
- data/lib/liquidplanner/resources/folder.rb +7 -0
- data/lib/liquidplanner/resources/item.rb +73 -0
- data/lib/liquidplanner/resources/leaf.rb +22 -0
- data/lib/liquidplanner/resources/link.rb +6 -0
- data/lib/liquidplanner/resources/luggage.rb +8 -0
- data/lib/liquidplanner/resources/member.rb +7 -0
- data/lib/liquidplanner/resources/milestone.rb +6 -0
- data/lib/liquidplanner/resources/note.rb +21 -0
- data/lib/liquidplanner/resources/order.rb +13 -0
- data/lib/liquidplanner/resources/priority.rb +13 -0
- data/lib/liquidplanner/resources/project.rb +7 -0
- data/lib/liquidplanner/resources/relative_resource.rb +12 -0
- data/lib/liquidplanner/resources/snapshot.rb +6 -0
- data/lib/liquidplanner/resources/task.rb +6 -0
- data/lib/liquidplanner/resources/tasklist.rb +7 -0
- data/lib/liquidplanner/resources/workspace.rb +34 -0
- metadata +155 -0
data/HISTORY
ADDED
data/README.rdoc
ADDED
@@ -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
|
data/Rakefile
ADDED
@@ -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
|
data/examples/README
ADDED
@@ -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,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,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,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,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,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,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
|