roart 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,17 @@
1
+ ==0.1.4 / 2009-08-19
2
+ Implemented callbacks, you can now hook into the ticket life-cycle with before and after create and update.
3
+ Saving works like ActiveRecord now, with save returning true if successful and false otherwise, and save! raising an error if unsuccessful.
4
+ 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.
5
+ 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.
6
+
7
+ ==0.1.3 / 2009-08-18
8
+ Can now update tickets with #save. Returns true if saved, raises an error if not.
9
+
10
+ ==0.1.2 / 2009-08-18
11
+ Added Create ticket functionality. Ticket.new will create a ticket in the RT system and return that ticket.
12
+
13
+ == 0.1.1 / 2009-08-17
14
+ 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.
15
+
16
+ == 0.1.0 / 2009-08-13
17
+ 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,85 @@
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
+
68
+ == REQUIREMENTS:
69
+
70
+ * mechanize
71
+ * A working RT3 install.
72
+
73
+ == INSTALL:
74
+
75
+ $ gem sources -a http://gems.github.com
76
+ $ sudo gem install pjdavis-roart
77
+
78
+ == LICENSE:
79
+
80
+ (C) PJ Davis <pj.davis@gmail.com>
81
+
82
+ This program is free software; you can redistribute it and/or modify it under
83
+ the terms of the WTFPL, Version 2, as
84
+ published by Sam Hocevar. See http://sam.zoy.org/wtfpl/ for more details.
85
+
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'
32
+
33
+ PROJ.spec.opts << '--color'
34
+
35
+ # EOF
data/lib/roart.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ module Roart
3
+
4
+ # :stopdoc:
5
+ VERSION = '0.1.4'
6
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
7
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
8
+ # :startdoc:
9
+
10
+ # Returns the version string for the library.
11
+ #
12
+ def self.version
13
+ VERSION
14
+ end
15
+
16
+ # Returns the library path for the module. If any arguments are given,
17
+ # they will be joined to the end of the libray path using
18
+ # <tt>File.join</tt>.
19
+ #
20
+ def self.libpath( *args )
21
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
22
+ end
23
+
24
+ # Returns the lpath for the module. If any arguments are given,
25
+ # they will be joined to the end of the path using
26
+ # <tt>File.join</tt>.
27
+ #
28
+ def self.path( *args )
29
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
30
+ end
31
+
32
+ # Utility method used to require all files ending in .rb that lie in the
33
+ # directory below this file that has the same name as the filename passed
34
+ # in. Optionally, a specific _directory_ name can be passed in such that
35
+ # the _filename_ does not have to be equivalent to the directory.
36
+ #
37
+ def self.require_all_libs_relative_to( fname, dir = nil )
38
+ dir ||= ::File.basename(fname, '.*')
39
+ search_me = ::File.expand_path(
40
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
41
+
42
+ Dir.glob(search_me).sort.each {|rb| require rb}
43
+ end
44
+
45
+ end # module Roart
46
+
47
+ Roart.require_all_libs_relative_to(__FILE__)
48
+
49
+ # 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,62 @@
1
+ require 'yaml'
2
+ require 'mechanize'
3
+
4
+ module Roart
5
+
6
+ module Connections
7
+ RequiredConfig = %w(server user pass)
8
+
9
+ end
10
+
11
+ class Connection
12
+
13
+ attr_reader :agent
14
+
15
+ def initialize(conf)
16
+
17
+ if conf.is_a?(String)
18
+ raise "Loading Config File not yet implemented"
19
+ elsif conf.is_a?(Hash)
20
+ Roart::check_keys!(conf, Roart::Connections::RequiredConfig)
21
+ @conf = conf
22
+ end
23
+
24
+ @agent = login
25
+ add_methods!
26
+ end
27
+
28
+ def rest_path
29
+ self.server + '/REST/1.0/'
30
+ end
31
+
32
+ def get(uri)
33
+ @agent.get(uri).body
34
+ end
35
+
36
+ def post(uri, payload)
37
+ @agent.post(uri, payload).body
38
+ end
39
+
40
+ protected
41
+
42
+ def login
43
+ agent = WWW::Mechanize.new
44
+ page = agent.get(@conf[:server])
45
+ form = page.form('login')
46
+ form.user = @conf[:user]
47
+ form.pass = @conf[:pass]
48
+ page = agent.submit form
49
+ agent
50
+ end
51
+
52
+ def add_methods!
53
+ @conf.each do |key, value|
54
+ (class << self; self; end).send :define_method, key do
55
+ return value
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,11 @@
1
+ class Array
2
+
3
+ def to_hash
4
+ h = Hash.new
5
+ self.each do |element|
6
+ h.update(element.to_sym => nil)
7
+ end
8
+ h
9
+ end
10
+
11
+ end
@@ -0,0 +1,15 @@
1
+ class Hash
2
+ def to_content_format
3
+ fields = self.map do |key,value|
4
+ unless value.nil?
5
+ if key.to_s.match(/^cf_.+/)
6
+ "CF-#{key.to_s[3..key.to_s.length].camelize.humanize}: #{value}"
7
+ else
8
+ "#{key.to_s.camelize}: #{value}"
9
+ end
10
+ end
11
+ end
12
+ content = fields.compact.join("\n")
13
+ end
14
+
15
+ end
@@ -0,0 +1,21 @@
1
+ # used from ActiveSupport
2
+ # Copyright (c) 2005-2009 David Heinemeier Hansson
3
+
4
+ class String
5
+
6
+ def underscore
7
+ self.gsub(/::/, '/').
8
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
9
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
10
+ tr("-", "_").
11
+ downcase
12
+ end
13
+
14
+ def camelize
15
+ self.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
16
+ end
17
+
18
+ def humanize
19
+ self.gsub(/_id$/, "").gsub(/_/, " ").capitalize
20
+ end
21
+ end
@@ -0,0 +1,11 @@
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
+ 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