ludo-roart 0.1.11

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,45 @@
1
+ ==0.1.9 / 2010-05-23
2
+ Removed the String/Hash extensions copied from ActiveSupport and just added a dependency on ActiveSupport. Removed to_hash added to Array, as it conflicts with ActiveSupport's deep_merge. (via hennk)
3
+ ==0.1.8 / 2010-03-16
4
+ Added validate presence of.
5
+ Updated specs to test validations.
6
+ No Longer depend on mechanize version 0.9.3 and get rid of those pesky deprecation warnings.
7
+ The connection adapter classes are now suffixed with "Adapter", so Mechanize is now MechanizeAdapter. You still specify it with :adapter => 'mechanize', however.
8
+
9
+ ==0.1.7 / 2010-03-11
10
+ Added some new error classes, add removed any extraneous raise 'text' (via jrbingham)
11
+ Removed debugging puts from the ticket file.
12
+
13
+ ==0.1.6 / 2010-03-09
14
+ Updated the search feature to pull all the ticket information at the same time, instead of only loading the id and subject.
15
+ Fixed tests that were failing because of out-of-order strings.
16
+ Returned to a sane version numbering scheme, sorry about that.
17
+
18
+ ==0.1.5.2 / 2010-01-20
19
+ New ticket objects now come with a default attribute :text that specifies the initial log of the ticket.
20
+
21
+ ==0.1.5.1 / 2010-01-20
22
+ Fixed a bug that caused custom fields not to show up in the ticket attributes
23
+
24
+ ==0.1.5 / 2010-01-19
25
+ Can add transactions ticket using #comment method. (via btucker)
26
+ Can authenticate again after the Ticket class is declared. (via newellista)
27
+ More search fields are now avaliable. (via threetee)
28
+
29
+ ==0.1.4 / 2009-08-19
30
+ Implemented callbacks, you can now hook into the ticket life-cycle with before and after create and update.
31
+ Saving works like ActiveRecord now, with save returning true if successful and false otherwise, and save! raising an error if unsuccessful.
32
+ Creating tickets is more ActiveRecord like now, with new not saving to the ticketing system automatically, you must call save or save!. There is a create function to do this now. Although this does break backward compatability, I'm not going to bump the minor because a) nobody has forked this yet, and b) i skipped 0.
33
+ You can now specify a default queue when you subclass Roart::Ticket. just add a default_queue 'queue name' to your class and that queue will automatically be searched unless you specify a queue.
34
+
35
+ ==0.1.3 / 2009-08-18
36
+ Can now update tickets with #save. Returns true if saved, raises an error if not.
37
+
38
+ ==0.1.2 / 2009-08-18
39
+ Added Create ticket functionality. Ticket.new will create a ticket in the RT system and return that ticket.
40
+
41
+ == 0.1.1 / 2009-08-17
42
+ Added History functionality. #histories now loads an array of history objects into memory and you can access them like you would a regular array. A few connivence methods (last, count) have also been added.
43
+
44
+ == 0.1.0 / 2009-08-13
45
+ Initial Commit. Base functionality added. Can search for tickets, and see all info on tickets. Ticket History not yet implemented.
data/README.rdoc ADDED
@@ -0,0 +1,89 @@
1
+ == Roart
2
+
3
+ \___\__o/
4
+ / \
5
+
6
+
7
+ by PJ Davis
8
+ http://github.com/pjdavis/roart
9
+
10
+ == DESCRIPTION:
11
+ If you are using Best Practical's Request Tracker (RT) and you need to interact with tickets from other applications, Roart provides an interface that is slightly reminiscent of ActiveRecord.
12
+
13
+
14
+ == FEATURES/PROBLEMS:
15
+
16
+ * Access RT Tickets through an ActiveRecord like API
17
+
18
+ * This has only been tested against RT 3.6. Changes to the REST interface in later versions of RT may break stuff.
19
+
20
+ == SYNOPSIS:
21
+
22
+ * Create a class to interact with your ticket system
23
+
24
+ require 'rubygems'
25
+ require 'roart'
26
+
27
+ class Ticket < Roart::Ticket
28
+ connection :server => 'http://my.rt.server.com', :user => 'myuser', :pass => 'mypass'
29
+
30
+ end
31
+
32
+ * Search for tickets
33
+
34
+ my_tickets = Ticket.find(:all, :queue => 'Scutters', :status => [:new, :open])
35
+ my_tickets.each do |ticket|
36
+ puts ticket.subject
37
+ end
38
+
39
+ #-> New John Wayne packages
40
+ #-> Medi-lab training
41
+
42
+ * See all info for a ticket
43
+
44
+ my_ticket = Ticket.find(:first, :queue => 'Issues', :status => :new)
45
+ ticket.creator #-> rimmer@reddwarf.com
46
+ ticket.subject #-> Where is the Bomb?
47
+
48
+ * Get history for a ticket
49
+
50
+ my_ticket.histories #-> Returns an array of history objects
51
+
52
+ * Create a new ticket
53
+
54
+ issue = Ticket.new(:queue => 'some_queue', :subject => 'This is not working for me')
55
+ issue.id #-> 'ticket/new'
56
+ issue.save
57
+ issue.id #-> 23423
58
+
59
+ * Update a ticket
60
+
61
+ ticket = Ticket.find(23452)
62
+ ticket.subject #-> "Some Subject for a ticket."
63
+ ticket.subject #-> "Smoke me a kipper, I'll be back for breakfast."
64
+ ticket.save
65
+ ticket.subject #->"Smoke me a kipper, I'll be back for breakfast."
66
+
67
+ * Comment on a Ticket
68
+ ticket = Ticket.find(23452)
69
+ ticket.comment("This is a lovely Ticket", :time_worked => 45, :cc => 'someone@example.com'))
70
+
71
+
72
+ == REQUIREMENTS:
73
+
74
+ * mechanize
75
+ * A working RT3 install.
76
+
77
+ == INSTALL:
78
+
79
+ $ gem sources -a http://gems.github.com
80
+ $ sudo gem install pjdavis-roart
81
+
82
+ == LICENSE:
83
+
84
+ (C) PJ Davis <pj.davis@gmail.com>
85
+
86
+ This program is free software; you can redistribute it and/or modify it under
87
+ the terms of the WTFPL, Version 2, as
88
+ published by Sam Hocevar. See http://sam.zoy.org/wtfpl/ for more details.
89
+
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+
5
+ begin
6
+ require 'bones'
7
+ Bones.setup
8
+ rescue LoadError
9
+ begin
10
+ load 'tasks/setup.rb'
11
+ rescue LoadError
12
+ raise RuntimeError, '### please install the "bones" gem ###'
13
+ end
14
+ end
15
+
16
+ ensure_in_path 'lib'
17
+ require 'roart'
18
+
19
+ task :default => 'spec:run'
20
+
21
+ PROJ.name = 'roart'
22
+ PROJ.ignore_file = '.gitignore'
23
+ PROJ.authors = 'PJ Davis'
24
+ PROJ.email = 'pj.davis@gmail.com'
25
+ PROJ.url = 'http://github.com/pjdavis/roart'
26
+ PROJ.version = Roart::VERSION
27
+ PROJ.rubyforge.name = 'roart'
28
+ PROJ.exclude = %w(.git pkg coverage)
29
+ PROJ.description = "Interface for working with Request Tracker (RT) tickets inspired by ActiveRecord."
30
+ PROJ.rdoc.main = 'README.rdoc'
31
+ depend_on 'mechanize' #this will go away when a stdlib adapter gets written
32
+
33
+ PROJ.spec.opts << '--color'
34
+
35
+ # EOF
data/lib/roart.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'active_support/core_ext'
4
+
5
+ module Roart
6
+
7
+ # :stopdoc:
8
+ VERSION = '0.1.9'
9
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
10
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
11
+ # :startdoc:
12
+
13
+ # Returns the version string for the library.
14
+ #
15
+ def self.version
16
+ VERSION
17
+ end
18
+
19
+ # Returns the library path for the module. If any arguments are given,
20
+ # they will be joined to the end of the libray path using
21
+ # <tt>File.join</tt>.
22
+ #
23
+ def self.libpath( *args )
24
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
25
+ end
26
+
27
+ # Returns the lpath for the module. If any arguments are given,
28
+ # they will be joined to the end of the path using
29
+ # <tt>File.join</tt>.
30
+ #
31
+ def self.path( *args )
32
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
33
+ end
34
+
35
+ # Utility method used to require all files ending in .rb that lie in the
36
+ # directory below this file that has the same name as the filename passed
37
+ # in. Optionally, a specific _directory_ name can be passed in such that
38
+ # the _filename_ does not have to be equivalent to the directory.
39
+ #
40
+ def self.require_all_libs_relative_to( fname, dir = nil )
41
+ dir ||= ::File.basename(fname, '.*')
42
+ search_me = ::File.expand_path(
43
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
44
+
45
+ Dir.glob(search_me).sort.each {|rb| require rb}
46
+ end
47
+
48
+ end # module Roart
49
+
50
+ Roart.require_all_libs_relative_to(__FILE__)
51
+
52
+ # EOF
@@ -0,0 +1,25 @@
1
+ module Roart
2
+
3
+ # Callbacks are implemented to do a bit of logic either before or after a part of the object life cycle. These can be overridden in your Ticket class and will be called at the approprate times.
4
+ #
5
+ module Callbacks
6
+
7
+ # called just before a ticket that has not been saved to the ticketing system is saved.
8
+ #
9
+ def before_create; end
10
+
11
+ # Called immediately a ticket that has not been saved is saved.
12
+ #
13
+ def before_update; end
14
+
15
+ # called just before a ticket that has been updated is saved to the ticketing system
16
+ #
17
+ def after_create; end
18
+
19
+ # called just after a ticket that has been updated is saved to the ticketing system
20
+ #
21
+ def after_update; end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,65 @@
1
+ require 'yaml'
2
+
3
+ module Roart
4
+
5
+ module Connections
6
+ RequiredConfig = %w(server adapter)
7
+ RequiredToLogin = %w( user pass )
8
+
9
+ end
10
+
11
+ class Connection
12
+
13
+ attr_reader :agent
14
+ attr_reader :conf
15
+
16
+ def initialize(conf)
17
+ if conf.is_a?(String)
18
+ raise RoartError, "Loading Config File not yet implemented"
19
+ elsif conf.class.name == Hash.name #TODO: Figure out why conf.is_a?(Hash) doesn't work
20
+ @conf = conf
21
+ end
22
+ if Roart::check_keys(conf, Roart::Connections::RequiredConfig)
23
+ @agent = @conf[:login]
24
+ add_methods!
25
+ else
26
+ raise RoartError, "Configuration Error"
27
+ end
28
+ end
29
+
30
+ def authenticate(conf)
31
+ if Roart::check_keys(conf, Roart::Connections::RequiredToLogin)
32
+ connection.authenticate(conf)
33
+ self
34
+ end
35
+ end
36
+
37
+ def rest_path
38
+ self.server + '/REST/1.0/'
39
+ end
40
+
41
+ def get(uri)
42
+ connection.get(uri)
43
+ end
44
+
45
+ def post(uri, payload)
46
+ connection.post(uri, payload)
47
+ end
48
+
49
+ protected
50
+
51
+ def connection
52
+ @connection ||= ConnectionAdapter.new(conf)
53
+ end
54
+
55
+ def add_methods!
56
+ @conf.each do |key, value|
57
+ (class << self; self; end).send :define_method, key do
58
+ return value
59
+ end
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,21 @@
1
+ require 'forwardable'
2
+
3
+ module Roart
4
+
5
+ class ConnectionAdapter
6
+ extend Forwardable
7
+
8
+ def initialize(config)
9
+ @adapter = Roart::ConnectionAdapters.const_get(config[:adapter].capitalize + "Adapter").new(config)
10
+ @adapter.login(config) if config[:user] && config[:pass]
11
+ end
12
+
13
+ def authenticate(config)
14
+ @adapter.login(config)
15
+ end
16
+
17
+ def_delegators :@adapter, :get, :post
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,32 @@
1
+ require 'mechanize'
2
+
3
+ module Roart
4
+ module ConnectionAdapters
5
+ class MechanizeAdapter
6
+
7
+ def initialize(config)
8
+ @conf = config
9
+ end
10
+
11
+ def login(config)
12
+ @conf.merge!(config)
13
+ agent = Mechanize.new
14
+ page = agent.get(@conf[:server])
15
+ form = page.form('login')
16
+ form.user = @conf[:user]
17
+ form.pass = @conf[:pass]
18
+ page = agent.submit form
19
+ @agent = agent
20
+ end
21
+
22
+ def get(uri)
23
+ @agent.get(uri).body
24
+ end
25
+
26
+ def post(uri, payload)
27
+ @agent.post(uri, payload).body
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ module Roart
2
+ class ContentFormatter
3
+ # The following is only based on quick trial&error on my part. The RT Api (at least in 3.6) does not have good documentation.
4
+ # Strings in a RT request:
5
+ # - are not allowed to contain '\r'
6
+ # - must use equally padded new-lines to distinguish them from the beginning of a new field (we use 2-space padding)
7
+ def self.format_string(string)
8
+ string.gsub("\r", '').gsub("\n", "\n ")
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,22 @@
1
+ class Hash
2
+ def to_content_format
3
+ fields = self.map do |key,value|
4
+ unless value.nil?
5
+ value = Roart::ContentFormatter.format_string(value.to_s)
6
+ if key.to_s.match(/^cf_.+/)
7
+ "CF-#{key.to_s[3..key.to_s.length].gsub(/_/, " ").camelize.humanize}: #{value}"
8
+ else
9
+ "#{key.to_s.camelize}: #{value}"
10
+ end
11
+ end
12
+ end
13
+ content = fields.compact.sort.join("\n")
14
+ end
15
+
16
+ def with_indifferent_access
17
+ hash = HashWithIndifferentAccess.new(self)
18
+ hash.default = self.default
19
+ hash
20
+ end
21
+
22
+ end
@@ -0,0 +1,13 @@
1
+ module Roart
2
+
3
+ class RoartError < StandardError; end
4
+
5
+ class ArgumentError < RoartError; end
6
+
7
+ class TicketSystemError < RoartError; end
8
+
9
+ class TicketSystemInterfaceError < RoartError; end
10
+
11
+ class TicketNotFoundError < RoartError; end
12
+
13
+ end
@@ -0,0 +1,115 @@
1
+ module Roart
2
+
3
+ module Histories
4
+
5
+ DefaultAttributes = %w(creator type description content created)
6
+ RequiredAttributes = %w(creator type)
7
+
8
+ end
9
+
10
+ class HistoryArray < Array
11
+
12
+ def ticket
13
+ @default_options[:ticket]
14
+ end
15
+
16
+ def all
17
+ self
18
+ end
19
+
20
+ def count
21
+ self.size
22
+ end
23
+
24
+ def last
25
+ self[self.size - 1]
26
+ end
27
+
28
+ end
29
+
30
+ class History
31
+
32
+ #TODO Figure out why i can't include Roart::MethodFunctions
33
+ def add_methods!
34
+ @attributes.each do |key, value|
35
+ (class << self; self; end).send :define_method, key do
36
+ return value
37
+ end
38
+ end
39
+ end
40
+
41
+ class << self
42
+
43
+ def default(options)
44
+ history = self.dup
45
+ history.instance_variable_set("@default_options", options)
46
+ history.all
47
+ end
48
+
49
+ def ticket
50
+ @default_options[:ticket]
51
+ end
52
+
53
+ def all
54
+ @histories ||= get_all
55
+ end
56
+
57
+ def default_options
58
+ @default_options
59
+ end
60
+
61
+ protected
62
+
63
+ def instantiate(attrs)
64
+ object = nil
65
+ if attrs.is_a?(Array)
66
+ array = Array.new
67
+ attrs.each do |attr|
68
+ object = self.allocate
69
+ object.instance_variable_set("@attributes", attr.merge(self.default_options))
70
+ object.send("add_methods!")
71
+ array << object
72
+ end
73
+ return array
74
+ elsif attrs.is_a?(Hash)
75
+ object = self.allocate
76
+ object.instance_variable_set("@attributes", attrs.merge(self.default_options))
77
+ object.send("add_methods!")
78
+ end
79
+ object
80
+ end
81
+
82
+ def get_all
83
+ page = get_page
84
+ raise TicketSystemError, "Can't get history." unless page
85
+ raise TicketSystemInterfaceError, "Error getting history for Ticket: #{ticket.id}." unless page.split("\n")[0].include?("200")
86
+ history_array = get_histories_from_page(page)
87
+ history_array
88
+ end
89
+
90
+ def get_histories_from_page(page)
91
+ full_history = HistoryArray.new
92
+ for history in page.split(/^--$/)
93
+ history = history.split("\n")
94
+ history.extend(Roart::TicketPage)
95
+ full_history << self.instantiate(history.to_history_hash)
96
+ end
97
+ full_history.instance_variable_set("@default_options", @default_options)
98
+ full_history
99
+ end
100
+
101
+ def get_page
102
+ @default_options[:ticket].class.connection.get(uri_for(@default_options[:ticket]))
103
+ end
104
+
105
+ def uri_for(ticket)
106
+ uri = self.default_options[:ticket].class.connection.rest_path + "ticket/#{ticket.id}/history?format=l"
107
+ end
108
+
109
+ end
110
+
111
+ protected
112
+
113
+ end
114
+
115
+ end