insightly 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +24 -0
- data/README.rdoc +53 -0
- data/insightly.gemspec +20 -0
- data/lib/insightly.rb +14 -0
- data/lib/insightly/base.rb +123 -0
- data/lib/insightly/comment.rb +56 -0
- data/lib/insightly/configuration.rb +81 -0
- data/lib/insightly/opportunity.rb +115 -0
- data/lib/insightly/opportunity_state_reason.rb +46 -0
- data/lib/insightly/read_only.rb +52 -0
- data/lib/insightly/read_write.rb +44 -0
- data/lib/insightly/task.rb +72 -0
- data/lib/insightly/task_link.rb +22 -0
- data/lib/insightly/version.rb +10 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/unit/base_spec.rb +6 -0
- data/spec/unit/comment_spec.rb +92 -0
- data/spec/unit/configuration_spec.rb +50 -0
- data/spec/unit/opportunity_spec.rb +180 -0
- data/spec/unit/opportunity_state_reason_spec.rb +77 -0
- data/spec/unit/task_link_spec.rb +47 -0
- data/spec/unit/task_spec.rb +135 -0
- metadata +182 -0
data/LICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2012 r26D, LLC and Dirk Elmendorf
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
insightly
|
2
|
+
=========
|
3
|
+
|
4
|
+
This is a rest client library to handle talking to http://Insight.ly
|
5
|
+
|
6
|
+
The official API for Insigh.ly was released on August 12, 2012. This library is in the very early stages of implementing access to everything expose in
|
7
|
+
that API. The focus is primarily on opportunities and tasks.
|
8
|
+
|
9
|
+
Getting Started
|
10
|
+
=========
|
11
|
+
|
12
|
+
Besides including the gem you need to use the Configuration class to set your API key.
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
Insightly::Configuration.api_key = "XXX-XXX-XXX-XXXX"
|
16
|
+
```
|
17
|
+
|
18
|
+
This key is provided to you in your User Settings section of Insight.ly.
|
19
|
+
|
20
|
+
|
21
|
+
Custom Fields
|
22
|
+
===========
|
23
|
+
|
24
|
+
Some of the resources allow you to have custom fields. They are returned as number fields (c.f. OPPORTUNITY_FIELD_1). You can call them directly in your code by
|
25
|
+
that name.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
opportunity = Insightly::Opportunity.new(1000)
|
29
|
+
opportunity.opportunity_field_1 = "Ron Campbell"
|
30
|
+
```
|
31
|
+
|
32
|
+
To make your code more readable, you can add in labels for those fields so you can refer to them more readable. The fields are lined up based on the order they are
|
33
|
+
provided to the setting. For example, Opportunities support 10 custom fields. You can provide up to 10 labels to match all 10 fields. You can set this up in the same place
|
34
|
+
you set your API key.
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
Insightly::Configuration.custom_fields_for_opportunities(:person_who_referred_them, :where_they_saw_the_ad)
|
38
|
+
opportunity = Insightly::Opportunity.new(1000)
|
39
|
+
opportunity.opportunity_field_1 = "Ron Campbell"
|
40
|
+
opportunity.person_who_referred_them == "Ron Campbell"
|
41
|
+
```
|
42
|
+
|
43
|
+
Opportunity State Reasons
|
44
|
+
========
|
45
|
+
|
46
|
+
The API allows you to change the state of an opportunity directly by modifying the OPPORTUNITY_STATE field. This doesn't store the reason
|
47
|
+
that the opportunity state was changed. In order to store the reason, you have to PUT to OpportunityStateChange with a valid OpportunityStateReason.
|
48
|
+
OpportunityStateReasons can only be created manually in the web interface and then referred to via the API.
|
49
|
+
|
50
|
+
This is important if you want to have it show you the state changes in the Opportuity details. Direct modifications don't create a log entry.
|
51
|
+
Whereas the log entry is created if you do create them. In order for your code to work, you need to make sure you have valid Opportunity State Reasons for all the states.
|
52
|
+
|
53
|
+
We default to creating two for open - "Created by API", and "Reopened by API". This allows us to set those as reasons if they exist.
|
data/insightly.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require 'insightly/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "insightly"
|
6
|
+
s.summary = "Insight.ly Ruby Client Library"
|
7
|
+
s.description = "Ruby library for integrating with http://Insight.ly"
|
8
|
+
s.version = Insightly::Version::String
|
9
|
+
s.authors = ["Dirk Elmendorf","r26D"]
|
10
|
+
s.email = "code@r26d.com"
|
11
|
+
s.homepage = "https://github.com/r26D/insightly"
|
12
|
+
s.has_rdoc = false
|
13
|
+
s.files = Dir.glob ["README.rdoc", "LICENSE", "{lib,spec}/**/*.rb", "lib/**/*.crt", "*.gemspec"]
|
14
|
+
s.add_dependency "builder", ">= 2.0.0"
|
15
|
+
s.add_dependency "json", ">= 1.7.5"
|
16
|
+
s.add_dependency "rest-client", ">=1.6.7"
|
17
|
+
s.add_dependency "logger", ">=1.2.8"
|
18
|
+
s.add_dependency "active_support", ">=3.0.0"
|
19
|
+
s.add_dependency "i18n", ">=0.6.1"
|
20
|
+
end
|
data/lib/insightly.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'rest-client'
|
3
|
+
require 'logger'
|
4
|
+
require 'insightly/configuration'
|
5
|
+
require "active_support/core_ext" #Needed for Hash.from_xml
|
6
|
+
|
7
|
+
require 'insightly/base'
|
8
|
+
require 'insightly/read_write'
|
9
|
+
require 'insightly/read_only'
|
10
|
+
require 'insightly/task'
|
11
|
+
require 'insightly/task_link'
|
12
|
+
require 'insightly/comment'
|
13
|
+
require 'insightly/opportunity_state_reason'
|
14
|
+
require 'insightly/opportunity'
|
@@ -0,0 +1,123 @@
|
|
1
|
+
#METODO only allow build to set fields that are part of the API fields
|
2
|
+
#METODO make a distinction between fields that you can set and save and ones you can only read - like DATE_UPDATED_UTC
|
3
|
+
|
4
|
+
|
5
|
+
#METODO Build and deploy a gem of this code
|
6
|
+
module Insightly
|
7
|
+
class Base
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_accessor :api_fields,:url_base
|
11
|
+
end
|
12
|
+
self.api_fields = []
|
13
|
+
|
14
|
+
|
15
|
+
def self.custom_fields(*args)
|
16
|
+
|
17
|
+
args.each_with_index do |method, index|
|
18
|
+
next if method.nil? or method == ""
|
19
|
+
method_name = method.to_s.downcase.to_sym
|
20
|
+
send :define_method, method_name do
|
21
|
+
@data["#{self.class.const_get(:CUSTOM_FIELD_PREFIX)}_#{index+1}"]
|
22
|
+
end
|
23
|
+
method_name = "#{method.to_s.downcase}=".to_sym
|
24
|
+
send :define_method, method_name do |value|
|
25
|
+
@data["#{self.class.const_get(:CUSTOM_FIELD_PREFIX)}_#{index+1}"] = value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.api_field(*args)
|
31
|
+
args.each do |field|
|
32
|
+
self.api_fields = [] if !self.api_fields
|
33
|
+
self.api_fields << field
|
34
|
+
method_name = field.downcase.to_sym
|
35
|
+
send :define_method, method_name do
|
36
|
+
@data[field]
|
37
|
+
end
|
38
|
+
method_name = "#{field.downcase}=".to_sym
|
39
|
+
send :define_method, method_name do |value|
|
40
|
+
@data[field] = value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(id = nil)
|
46
|
+
@data = {}
|
47
|
+
load(id) if id
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def url_base
|
52
|
+
self.class.url_base
|
53
|
+
end
|
54
|
+
def remote_id
|
55
|
+
raise ScriptError, "This should be overridden in the subclass"
|
56
|
+
end
|
57
|
+
|
58
|
+
def load(id)
|
59
|
+
@data = get_collection("#{url_base}/#{id}")
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def reload
|
64
|
+
load(remote_id)
|
65
|
+
end
|
66
|
+
|
67
|
+
def build(data)
|
68
|
+
@data = data
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.build(data)
|
73
|
+
self.new.build(data)
|
74
|
+
end
|
75
|
+
|
76
|
+
def ==(other)
|
77
|
+
self.remote_data == other.remote_data
|
78
|
+
end
|
79
|
+
|
80
|
+
def remote_data
|
81
|
+
@data
|
82
|
+
end
|
83
|
+
|
84
|
+
def process(result, content_type)
|
85
|
+
if content_type == :json
|
86
|
+
JSON.parse(result.to_str)
|
87
|
+
elsif content_type == :xml
|
88
|
+
Hash.from_xml(result.to_str)
|
89
|
+
else
|
90
|
+
result
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def config
|
95
|
+
Insightly::Configuration.instantiate
|
96
|
+
end
|
97
|
+
|
98
|
+
def get_collection(path, content_selector = :json)
|
99
|
+
if content_selector == :xml_raw
|
100
|
+
content_type = :xml
|
101
|
+
else
|
102
|
+
content_type = content_selector
|
103
|
+
end
|
104
|
+
response = RestClient::Request.new(:method => :get,
|
105
|
+
:url => "#{config.endpoint}/#{path.to_s}",
|
106
|
+
:user => config.api_key,
|
107
|
+
:password => "",
|
108
|
+
:headers => {:accept => content_type, :content_type => content_type}).execute
|
109
|
+
process(response, content_selector)
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.all
|
113
|
+
item = self.new
|
114
|
+
list = []
|
115
|
+
item.get_collection(item.url_base).each do |d|
|
116
|
+
list << self.new.build(d)
|
117
|
+
end
|
118
|
+
list
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#METODO Add support for FileAttachment xml
|
2
|
+
module Insightly
|
3
|
+
class Comment < ReadWrite
|
4
|
+
URL_BASE = "Comments"
|
5
|
+
|
6
|
+
api_field "COMMENT_ID",
|
7
|
+
"BODY",
|
8
|
+
"OWNER_USER_ID",
|
9
|
+
"DATE_UPDATED_UTC",
|
10
|
+
"DATE_CREATED_UTC",
|
11
|
+
"FILE_ATTACHMENTS"
|
12
|
+
|
13
|
+
def remote_id
|
14
|
+
comment_id
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_from_xml(xml)
|
18
|
+
result = Hash.from_xml(xml.to_str)
|
19
|
+
if result["Comment"]
|
20
|
+
@data = result["Comment"]
|
21
|
+
["COMMENT_ID", "OWNER_USER_ID"].each { |f| field_to_i(f) }
|
22
|
+
@data.delete("xmlns:xsi")
|
23
|
+
@data.delete('xmlns:xsd')
|
24
|
+
end
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_xml
|
29
|
+
<<-END_XML
|
30
|
+
<?xml version="1.0" encoding="utf-8"?>
|
31
|
+
<Comment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
32
|
+
<COMMENT_ID>#{comment_id}</COMMENT_ID>
|
33
|
+
<BODY>#{body}</BODY>
|
34
|
+
<OWNER_USER_ID>#{owner_user_id}</OWNER_USER_ID>
|
35
|
+
<DATE_CREATED_UTC>#{date_created_utc}</DATE_CREATED_UTC>
|
36
|
+
<DATE_UPDATED_UTC>#{date_updated_utc}</DATE_UPDATED_UTC>
|
37
|
+
<FILE_ATTACHMENTS></FILE_ATTACHMENTS>
|
38
|
+
</Comment>
|
39
|
+
END_XML
|
40
|
+
end
|
41
|
+
|
42
|
+
def load(id)
|
43
|
+
load_from_xml(get_collection("#{url_base}/#{id}", :xml_raw))
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def save
|
48
|
+
put_collection("#{url_base}", to_xml, :xml_raw)
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def field_to_i(field)
|
53
|
+
@data[field] = @data[field].to_i if @data[field]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
#This file is based heavily off of the Braintree::Configuration
|
2
|
+
#Copyright (c) 2009-2010 Braintree Payment Solutions
|
3
|
+
|
4
|
+
module Insightly
|
5
|
+
class ConfigurationError < ::StandardError;
|
6
|
+
def initialize(setting, message) # :nodoc:
|
7
|
+
super "Insightly::Configuration.#{setting} #{message}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
class Configuration
|
11
|
+
API_VERSION = "v1" # :nodoc:
|
12
|
+
DEFAULT_ENDPOINT = "https://api.insight.ly/v1" # :nodoc:
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_writer :custom_user_agent, :endpoint, :logger, :api_key
|
16
|
+
end
|
17
|
+
attr_reader :endpoint, :custom_user_agent, :api_key
|
18
|
+
|
19
|
+
def self.expectant_reader(*attributes) # :nodoc:
|
20
|
+
attributes.each do |attribute|
|
21
|
+
(class << self; self; end).send(:define_method, attribute) do
|
22
|
+
attribute_value = instance_variable_get("@#{attribute}")
|
23
|
+
raise ConfigurationError.new(attribute.to_s, "needs to be set") unless attribute_value
|
24
|
+
attribute_value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
expectant_reader :api_key
|
29
|
+
|
30
|
+
def self.instantiate # :nodoc:
|
31
|
+
config = new(
|
32
|
+
:custom_user_agent => @custom_user_agent,
|
33
|
+
:endpoint => @endpoint,
|
34
|
+
:logger => logger,
|
35
|
+
:api_key => api_key
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.custom_fields_for_opportunities(*args)
|
40
|
+
Insightly::Opportunity.custom_fields(*args)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.logger
|
44
|
+
@logger ||= _default_logger
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(options = {})
|
48
|
+
[:endpoint, :api_key, :custom_user_agent, :logger].each do |attr|
|
49
|
+
instance_variable_set "@#{attr}", options[attr]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def api_version # :nodoc:
|
54
|
+
API_VERSION
|
55
|
+
end
|
56
|
+
|
57
|
+
def endpoint
|
58
|
+
@endpoint || DEFAULT_ENDPOINT
|
59
|
+
end
|
60
|
+
|
61
|
+
def logger
|
62
|
+
@logger ||= self.class._default_logger
|
63
|
+
end
|
64
|
+
|
65
|
+
def user_agent # :nodoc:
|
66
|
+
base_user_agent = "Insightly Ruby Gem #{Insightly::Version::String}"
|
67
|
+
@custom_user_agent ? "#{base_user_agent} (#{@custom_user_agent})" : base_user_agent
|
68
|
+
end
|
69
|
+
|
70
|
+
def self._default_logger # :nodoc:
|
71
|
+
logger = Logger.new(STDOUT)
|
72
|
+
logger.level = Logger::INFO
|
73
|
+
logger
|
74
|
+
end
|
75
|
+
def self._debug_logger # :nodoc:
|
76
|
+
logger = Logger.new(STDOUT)
|
77
|
+
logger.level = Logger::DEBUG
|
78
|
+
logger
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
|
2
|
+
#METODO Confirm that we can create an opportunity
|
3
|
+
module Insightly
|
4
|
+
class Opportunity < ReadWrite
|
5
|
+
self.url_base = "Opportunities"
|
6
|
+
CUSTOM_FIELD_PREFIX = "OPPORTUNITY_FIELD"
|
7
|
+
api_field "OPPORTUNITY_FIELD_10",
|
8
|
+
"OPPORTUNITY_FIELD_9",
|
9
|
+
"OPPORTUNITY_FIELD_8",
|
10
|
+
"OPPORTUNITY_FIELD_7",
|
11
|
+
"OPPORTUNITY_FIELD_6",
|
12
|
+
"OPPORTUNITY_FIELD_5",
|
13
|
+
"OPPORTUNITY_FIELD_4",
|
14
|
+
"OPPORTUNITY_FIELD_3",
|
15
|
+
"OPPORTUNITY_FIELD_2",
|
16
|
+
"OPPORTUNITY_FIELD_1",
|
17
|
+
"VISIBLE_TO",
|
18
|
+
"BID_TYPE",
|
19
|
+
"ACTUAL_CLOSE_DATE",
|
20
|
+
"DATE_UPDATED_UTC",
|
21
|
+
"OWNER_USER_ID",
|
22
|
+
"BID_DURATION",
|
23
|
+
"BID_CURRENCY",
|
24
|
+
"PIPELINE_ID",
|
25
|
+
"CATEGORY_ID",
|
26
|
+
"PROBABILITY",
|
27
|
+
"TAGS",
|
28
|
+
"IMAGE_URL",
|
29
|
+
"BID_AMOUNT",
|
30
|
+
"VISIBLE_TEAM_ID",
|
31
|
+
"STAGE_ID",
|
32
|
+
"DATE_CREATED_UTC",
|
33
|
+
"OPPORTUNITY_STATE",
|
34
|
+
"FORECAST_CLOSE_DATE",
|
35
|
+
"OPPORTUNITY_NAME",
|
36
|
+
"OPPORTUNITY_ID",
|
37
|
+
"VISIBLE_USER_IDS",
|
38
|
+
"LINKS",
|
39
|
+
"RESPONSIBLE_USER_ID",
|
40
|
+
"OPPORTUNITY_DETAILS"
|
41
|
+
|
42
|
+
|
43
|
+
def remote_id
|
44
|
+
opportunity_id
|
45
|
+
end
|
46
|
+
|
47
|
+
def tasks
|
48
|
+
list = []
|
49
|
+
Insightly::TaskLink.search_by_opportunity_id(opportunity_id).each do |x|
|
50
|
+
list << Insightly::Task.new(x.task_id)
|
51
|
+
end
|
52
|
+
list
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
OpportunityStateReason::STATES.each do |s|
|
57
|
+
|
58
|
+
self.instance_eval do
|
59
|
+
define_method "#{s.downcase}?".to_sym do
|
60
|
+
self.opportunity_state == s
|
61
|
+
end
|
62
|
+
|
63
|
+
define_method "#{s.downcase}!".to_sym do |*args|
|
64
|
+
reason = args.first
|
65
|
+
@state_reason = nil
|
66
|
+
if reason
|
67
|
+
@state_reason = Insightly::OpportunityStateReason.find_by_state_reason(s, reason)
|
68
|
+
end
|
69
|
+
|
70
|
+
if @state_reason
|
71
|
+
put_collection("OpportunityStateChange/#{opportunity_id}", @state_reason.remote_data.to_json)
|
72
|
+
reload
|
73
|
+
else
|
74
|
+
#Changing state without a reason/log entry
|
75
|
+
|
76
|
+
self.opportunity_state = s
|
77
|
+
save
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.find_by_name(name)
|
85
|
+
Insightly::Opportunity.all.each do |o|
|
86
|
+
return o if o.opportunity_name && o.opportunity_name == name
|
87
|
+
end
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.search_by_name(name)
|
92
|
+
list = []
|
93
|
+
Insightly::Opportunity.all.each do |o|
|
94
|
+
if o.opportunity_name && o.opportunity_name.match(name)
|
95
|
+
list << o
|
96
|
+
end
|
97
|
+
end
|
98
|
+
list
|
99
|
+
end
|
100
|
+
|
101
|
+
def save
|
102
|
+
creating = remote_id ? false : true
|
103
|
+
|
104
|
+
super
|
105
|
+
if creating
|
106
|
+
@state_reason = Insightly::OpportunityStateReason.find_by_state_reason("Open", "Created by API")
|
107
|
+
|
108
|
+
if @state_reason
|
109
|
+
put_collection("OpportunityStateChange/#{opportunity_id}", @state_reason.remote_data.to_json)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|