insightly 0.1.1

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