acuforce 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ #gem 'psych'
7
+ gem 'mechanize'
8
+
9
+ # Add dependencies to develop your gem here.
10
+ # Include everything needed to run rake, tests, features, etc.
11
+ group :development do
12
+ gem "rspec", "~> 2.3.0"
13
+ gem "bundler", "~> 1.0.0"
14
+ gem "jeweler", "~> 1.6.4"
15
+ gem "rcov", ">= 0"
16
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Harrison Strowd
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,27 @@
1
+ = AcuForce
2
+
3
+ Gem that handles integration with Acunote. Does not expose all actions at this time.
4
+
5
+ Current actions supported:
6
+ - Logging in and Logging out
7
+ - Creating a sprint
8
+ - Finding a sprint
9
+ - Uploading tasks to a sprint from a CSV file
10
+ - Exporting tasks in a sprint to a CSV file
11
+
12
+ == Contributing to AcuForce
13
+
14
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
15
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
16
+ * Fork the project
17
+ * Start a feature/bugfix branch
18
+ * Commit and push until you are happy with your contribution
19
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
20
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
21
+
22
+ == Copyright
23
+
24
+ Copyright (c) 2011 Harrison Strowd. See LICENSE.txt for
25
+ further details.
26
+
27
+ This work was forked and redesigned from work done by bfeigin in https://github.com/bfeigin/AcuPlan.
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "acuforce"
18
+ gem.homepage = "http://github.com/hstrowd/AcuForce"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Client API for performing actions on an Acunote site.}
21
+ gem.description = %Q{
22
+ Provides an API for the following actions:
23
+ * Logging in and Logging out
24
+ * Creating a sprint
25
+ * Finding a sprint
26
+ * Uploading tasks to a sprint from a CSV file
27
+ * Exporting tasks in a sprint to a CSV file
28
+ }
29
+ gem.email = "hstrowd@gmail.com"
30
+ gem.authors = ["Harrison Strowd"]
31
+ # dependencies defined in Gemfile
32
+ end
33
+ Jeweler::RubygemsDotOrgTasks.new
34
+
35
+ require 'rspec/core'
36
+ require 'rspec/core/rake_task'
37
+ RSpec::Core::RakeTask.new(:spec) do |spec|
38
+ spec.pattern = FileList['spec/**/*_spec.rb']
39
+ end
40
+
41
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
42
+ spec.pattern = 'spec/**/*_spec.rb'
43
+ spec.rcov = true
44
+ end
45
+
46
+ task :default => :spec
47
+
48
+ require 'rake/rdoctask'
49
+ Rake::RDocTask.new do |rdoc|
50
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
51
+
52
+ rdoc.rdoc_dir = 'rdoc'
53
+ rdoc.title = "acuforce #{version}"
54
+ rdoc.rdoc_files.include('README*')
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
56
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,70 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{acuforce}
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Harrison Strowd"]
12
+ s.date = %q{2011-12-24}
13
+ s.description = %q{
14
+ Provides an API for the following actions:
15
+ * Logging in and Logging out
16
+ * Creating a sprint
17
+ * Finding a sprint
18
+ * Uploading tasks to a sprint from a CSV file
19
+ * Exporting tasks in a sprint to a CSV file
20
+ }
21
+ s.email = %q{hstrowd@gmail.com}
22
+ s.extra_rdoc_files = [
23
+ "LICENSE.txt",
24
+ "README.rdoc"
25
+ ]
26
+ s.files = [
27
+ "Gemfile",
28
+ "LICENSE.txt",
29
+ "README.rdoc",
30
+ "Rakefile",
31
+ "VERSION",
32
+ "acuforce.gemspec",
33
+ "lib/acuforce.rb",
34
+ "lib/acunote_connection.rb",
35
+ "lib/acunote_project.rb",
36
+ "lib/acunote_sprint.rb",
37
+ "spec/acuforce_spec.rb",
38
+ "spec/spec_helper.rb"
39
+ ]
40
+ s.homepage = %q{http://github.com/hstrowd/AcuForce}
41
+ s.licenses = ["MIT"]
42
+ s.require_paths = ["lib"]
43
+ s.rubygems_version = %q{1.6.2}
44
+ s.summary = %q{Client API for performing actions on an Acunote site.}
45
+
46
+ if s.respond_to? :specification_version then
47
+ s.specification_version = 3
48
+
49
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
50
+ s.add_runtime_dependency(%q<mechanize>, [">= 0"])
51
+ s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
52
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
53
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
54
+ s.add_development_dependency(%q<rcov>, [">= 0"])
55
+ else
56
+ s.add_dependency(%q<mechanize>, [">= 0"])
57
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
58
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
59
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
60
+ s.add_dependency(%q<rcov>, [">= 0"])
61
+ end
62
+ else
63
+ s.add_dependency(%q<mechanize>, [">= 0"])
64
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
65
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
66
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
67
+ s.add_dependency(%q<rcov>, [">= 0"])
68
+ end
69
+ end
70
+
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ #INITAL DEVELOPMENT
3
+ #askryl
4
+ #https://github.com/skryl/AcuForce.git
5
+
6
+ #bfeigin intense modifications
7
+ #Ripped out most of AcuForce, kept the basic Acunote logic see AcunoteBase
8
+ #https://github.com/bfeigin/AcuForce.git
9
+
10
+ #Major modifications to transition from OmniPlan to Accunote
11
+ #Notes there are a few gems required see directly below :)
12
+
13
+ require 'rubygems'
14
+ require 'acunote_connection'
15
+ require 'acunote_project'
16
+ require 'acunote_sprint'
17
+
18
+ DEBUG = true unless defined? DEBUG
@@ -0,0 +1,113 @@
1
+ require 'mechanize'
2
+ require 'yaml'
3
+ require 'singleton'
4
+
5
+ # A singleton class to contain the acunote session information.
6
+ class AcunoteConnection
7
+ include Singleton
8
+
9
+ attr_accessor :username
10
+ attr_writer :home_url
11
+ attr_reader :logged_in, :mech
12
+
13
+ def initialize()
14
+ @mech ||= Mechanize.new
15
+ end
16
+
17
+ # For lack of a better place, put sessions in tmp
18
+ SESSION_DIR = "/tmp" unless defined? SESSION_DIR
19
+ SESSION_FILE = "#{SESSION_DIR}/acunote.session" unless defined? SESSION_FILE
20
+
21
+ LOGIN_FIELDS = ['login[username]', 'login[password]'] unless defined? LOGIN_FIELDS
22
+ LOGIN_FORM_NAME = "login_form" unless defined? LOGIN_FORM_NAME
23
+
24
+ # The home_url must be set after the instance is first retrieved.
25
+ def home_url
26
+ raise "home_url not set" unless @home_url
27
+ @home_url
28
+ end
29
+
30
+ def login_url
31
+ "#{self.home_url}/login"
32
+ end
33
+
34
+ def logout_url
35
+ "#{self.home_url}/login/logout"
36
+ end
37
+
38
+ def login(username, password, force = false)
39
+ @logged_in = nil if force
40
+ return true if logged_in
41
+
42
+ #Going to assume the session is good to save time here. The session will be
43
+ #discarded and a force login will be performed if get_page fails.
44
+ if !force && File.exists?(SESSION_FILE) && ! File.zero?(SESSION_FILE) && mech.cookie_jar.load(SESSION_FILE)
45
+ STDERR.puts "Loaded session file" if DEBUG
46
+ @username = username
47
+ @logged_in = true
48
+ end
49
+
50
+ unless logged_in
51
+
52
+ #try to log in
53
+ login_page = get_page(login_url)
54
+ STDERR.puts "Navigated to '#{login_page.title}'" if DEBUG
55
+
56
+ form = login_page.forms.first
57
+ form[LOGIN_FIELDS[0]] = username
58
+ form[LOGIN_FIELDS[1]] = password
59
+ dest_page = form.submit(form.buttons.first)
60
+
61
+ STDERR.puts "Navigated to '#{dest_page.title}'" if DEBUG
62
+ if dest_page.uri == login_page.uri
63
+ STDERR.puts "Error: Bad login!"
64
+ return false
65
+ end
66
+
67
+ #serialize session and save for later reuse
68
+ mech.cookie_jar.save_as(SESSION_FILE)
69
+ @username = username
70
+ @logged_in = true
71
+ end
72
+ end
73
+
74
+ def logout()
75
+ if File.exists?(SESSION_FILE)
76
+ File.delete(SESSION_FILE)
77
+ end
78
+ get_page(logout_url)
79
+ @username = nil
80
+ @logged_in = false
81
+ end
82
+
83
+ def set_timeout(timeout = 60)
84
+ mech.keep_alive = false
85
+ mech.open_timeout = timeout
86
+ mech.read_timeout = timeout
87
+ mech.idle_timeout = timeout
88
+ end
89
+
90
+ #Retrieves the requested page and verifies destination url to make sure there
91
+ #was no innapropriate redirect. If redirected, a force login will be performed
92
+ #(assuming credentials are passed in as arguments) and the page will be retrieved
93
+ #again.
94
+ def get_page(url, matcher = /.*/, retry_count = 1)
95
+ begin
96
+ page = mech.get(url)
97
+ if page.uri.to_s =~ matcher
98
+ page
99
+ else
100
+ #try a force login and retry once (in case the session is stale)
101
+ if retry_count > 0 && login(true)
102
+ STDERR.puts "Attn: get_page problem, overwrote stale session, retrying..."
103
+ get_page(url, matcher, retry_count - 1)
104
+ else STDERR.puts "Error: Can't retrieve valid response page for <#{url}>"
105
+ end
106
+ end
107
+ rescue Mechanize::ResponseCodeError => e
108
+ STDERR.puts "ResponseError!"
109
+ puts url if DEBUG
110
+ puts e if DEBUG
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,25 @@
1
+ require 'acunote_connection'
2
+
3
+ # API for accessing Acunote projects.
4
+ class AcunoteProject
5
+ def self.acu_conn
6
+ AcunoteConnection.instance
7
+ end
8
+
9
+ def self.url(id)
10
+ "#{acu_conn.home_url}/projects/#{id}"
11
+ end
12
+
13
+ def self.find_id_by_name(name)
14
+ link = find_by_name(proj_id, sprint_name)
15
+ if(link.uri.to_s =~ /projects\/([0-9]*)\/sprints/)
16
+ $1
17
+ end
18
+ end
19
+
20
+ # NAME can be a literal string or a regex.
21
+ def self.find_by_name(name)
22
+ projects = acu_conn.get_page(url(''))
23
+ projects.links_with(:href => /projects\/([0-9]*)\/sprints$/, :text => name).first if projects
24
+ end
25
+ end
@@ -0,0 +1,71 @@
1
+ require 'acunote_connection'
2
+
3
+ # API for accessing Acunote projects.
4
+ class AcunoteSprint
5
+ ACUNOTE_CSV_HEADER = "Level,Number,Description,Tags,Owner,Status,Resolution,Priority,Severity,Estimate,Remaining,Due Date,QA Owner,Business Owner,Wiki,Watchers,Related,Duplicate,Predecessors,Successors,Version 1\r\n" unless defined? ACUNOTE_CSV_HEADER
6
+
7
+ def self.acu_conn
8
+ AcunoteConnection.instance
9
+ end
10
+
11
+ def self.url(proj_id, sprint_id)
12
+ "#{acu_conn.home_url}/projects/#{proj_id}/sprints/#{sprint_id}"
13
+ end
14
+
15
+ def self.create(proj_id, name, opts = {})
16
+ opts[:type] ||= 'Backlog'
17
+ opts[:start_date] ||= Date.today
18
+ opts[:end_date] ||= opts[:start_date] + 365
19
+
20
+ unless acu_conn.logged_in
21
+ STDERR.puts "Must login before creating a sprint." if DEBUG
22
+ return false
23
+ end
24
+
25
+ sprint_page = acu_conn.get_page(url(proj_id, 'new'))
26
+
27
+ # Check that the project could be found.
28
+ unless sprint_page
29
+ STDERR.puts "Spcified project could not be found." if DEBUG
30
+ return false
31
+ end
32
+
33
+ sprint_form = sprint_page.forms_with({:name => 'sprint_new_dialog'}).first
34
+
35
+ if opts[:type] == 'Backlog'
36
+ (sprint_form.radiobuttons_with(:value => /Backlog/).first &&
37
+ sprint_form.radiobuttons_with(:value => /Backlog/).first.check)
38
+ else
39
+ sprint_form.radiobuttons_with(:value => /Iteration/).first.check
40
+ sprint_form.fields_with(:id => 'sprint_start_date').first.value = opts[:start_date].to_s
41
+ end
42
+ sprint_form.fields_with(:id => 'sprint_name').first.value = name
43
+ sprint_form.fields_with(:id => 'sprint_end_date').first.value = opts[:end_date].to_s
44
+ sprint_form.submit
45
+ end
46
+
47
+ # NAME can be a literal string or a regex.
48
+ def self.find_id_by_name(proj_id, name)
49
+ link = find_by_name(proj_id, name)
50
+ if(link.uri.to_s =~ /\/sprints\/([0-9]*)/)
51
+ $1
52
+ end
53
+ end
54
+
55
+ # NAME can be a literal string or a regex.
56
+ def self.find_by_name(proj_id, name)
57
+ sprints = acu_conn.get_page(url(proj_id, ''))
58
+ sprints.links_with(:text => name).first if sprints
59
+ end
60
+
61
+ def self.upload_csv(raw_data, proj_id, sprint_id)
62
+ import_page = acu_conn.get_page(url(opts[:proj_id],sprint_id)+"/import")
63
+ import_form = import_page.form_with({:name => 'import_form'})
64
+ import_form.field_with(:id => 'data_to_import').value = raw_data.to_s
65
+ import_form.submit
66
+ end
67
+
68
+ def self.export_csv(proj_id, sprint_id)
69
+ acu_conn.get_page(url(proj_id,sprint_id) + '/export').body
70
+ end
71
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Acufore Loading' do
4
+ it 'should be able to load the Connection API' do
5
+ AcunoteConnection.should === AcunoteConnection.instance
6
+ end
7
+ it 'should be able to load the Project API' do
8
+ AcunoteProject.should === AcunoteProject.new
9
+ end
10
+ it 'should be able to load the Sprint API' do
11
+ AcunoteSprint.should === AcunoteSprint.new
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'acuforce'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acuforce
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Harrison Strowd
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-12-24 00:00:00.000000000 -06:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: mechanize
17
+ requirement: &2156230120 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *2156230120
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: &2156229640 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 2.3.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *2156229640
37
+ - !ruby/object:Gem::Dependency
38
+ name: bundler
39
+ requirement: &2156229160 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: 1.0.0
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *2156229160
48
+ - !ruby/object:Gem::Dependency
49
+ name: jeweler
50
+ requirement: &2156228680 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: 1.6.4
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *2156228680
59
+ - !ruby/object:Gem::Dependency
60
+ name: rcov
61
+ requirement: &2156228200 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *2156228200
70
+ description: ! '
71
+
72
+ Provides an API for the following actions:
73
+
74
+ * Logging in and Logging out
75
+
76
+ * Creating a sprint
77
+
78
+ * Finding a sprint
79
+
80
+ * Uploading tasks to a sprint from a CSV file
81
+
82
+ * Exporting tasks in a sprint to a CSV file
83
+
84
+ '
85
+ email: hstrowd@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files:
89
+ - LICENSE.txt
90
+ - README.rdoc
91
+ files:
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.rdoc
95
+ - Rakefile
96
+ - VERSION
97
+ - acuforce.gemspec
98
+ - lib/acuforce.rb
99
+ - lib/acunote_connection.rb
100
+ - lib/acunote_project.rb
101
+ - lib/acunote_sprint.rb
102
+ - spec/acuforce_spec.rb
103
+ - spec/spec_helper.rb
104
+ has_rdoc: true
105
+ homepage: http://github.com/hstrowd/AcuForce
106
+ licenses:
107
+ - MIT
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ segments:
119
+ - 0
120
+ hash: 102055120518766815
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubyforge_project:
129
+ rubygems_version: 1.6.2
130
+ signing_key:
131
+ specification_version: 3
132
+ summary: Client API for performing actions on an Acunote site.
133
+ test_files: []