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