├── .gitignore ├── .yardopts ├── ChangeLog ├── Gemfile ├── LICENSE.txt ├── README.markdown ├── Rakefile ├── docs ├── Examples.markdown ├── GettingStarted.markdown └── SwitchingHTTPDriver.markdown ├── jiraSOAP.gemspec ├── lib ├── jiraSOAP.rb └── jiraSOAP │ ├── api.rb │ ├── api │ ├── additions.rb │ ├── attachments.rb │ ├── avatars.rb │ ├── comments.rb │ ├── components.rb │ ├── filters.rb │ ├── issue_data_types.rb │ ├── issues.rb │ ├── project_roles.rb │ ├── projects.rb │ ├── schemes.rb │ ├── server_info.rb │ ├── users.rb │ ├── versions.rb │ └── worklog.rb │ ├── core_extensions.rb │ ├── entities.rb │ ├── entities │ ├── attachment.rb │ ├── avatar.rb │ ├── comment.rb │ ├── component.rb │ ├── custom_field_value.rb │ ├── described_entity.rb │ ├── dynamic_entity.rb │ ├── entity.rb │ ├── field.rb │ ├── field_value.rb │ ├── filter.rb │ ├── issue.rb │ ├── issue_property.rb │ ├── issue_security_scheme.rb │ ├── issue_type.rb │ ├── named_entity.rb │ ├── notification_scheme.rb │ ├── permission.rb │ ├── permission_mapping.rb │ ├── permission_scheme.rb │ ├── priority.rb │ ├── project.rb │ ├── project_role.rb │ ├── resolution.rb │ ├── scheme.rb │ ├── server_configuration.rb │ ├── server_info.rb │ ├── status.rb │ ├── time_info.rb │ ├── user.rb │ ├── usergroup.rb │ ├── username.rb │ ├── version.rb │ └── worklog.rb │ ├── handsoap_extensions.rb │ ├── jira_service.rb │ ├── macruby_extensions.rb │ ├── nokogiri_extensions.rb │ ├── url.rb │ └── version.rb └── test ├── attachments_test.rb ├── custom_field_values_test.rb ├── helper.rb ├── login_test.rb ├── logout_test.rb ├── service_test.rb └── worklog_test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | .DS_Store 3 | coverage 4 | pkg 5 | .yardoc 6 | doc 7 | Gemfile.lock 8 | *.rbo 9 | *.gem 10 | pkg/ 11 | .rvmrc 12 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --protected 2 | --no-cache 3 | --markup markdown 4 | --markup-provider redcarpet 5 | --readme README.markdown 6 | lib/**/*.rb 7 | - 8 | docs/GettingStarted.markdown 9 | docs/Examples.markdown 10 | docs/SwitchingHTTPDriver.markdown 11 | ChangeLog 12 | LICENSE.txt 13 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | Version 0.10.9 2 | 3 | * Fix including nil comment fields in new comments [GH-19] (thanks @idris) 4 | 5 | Version 0.10.8 6 | 7 | * Fix RemoteAPI#add_worklog_and_auto_adjust_remaining_estimate (thanks @justfalter) 8 | * Make JIRA::Worklog#start_date return a Time object instead of a DateTime object (thanks @justfalter) 9 | 10 | Version 0.10.7 11 | 12 | * Add RemoteAPI#issue_with_parent (thanks @softwarealliesadmin) 13 | 14 | Version 0.10.6 15 | 16 | * Add JIRAService.instance_with_token 17 | 18 | Version 0.10.5 19 | 20 | * Make jiraSOAP compatible with Ruby 1.8.7+ (thanks @wapcaplet) 21 | 22 | Version 0.10.4 23 | 24 | * Fix JIRA::Worklog#start_date typo (was start_data) (thanks @FDj) 25 | 26 | Version 0.10.3 27 | 28 | * Fix RemoteAPI#create_issue problem with setting a non-default reporter (thanks @toddtomkinson) 29 | 30 | Version 0.10.2 31 | 32 | * Fix RemoteAPIAdditions#custom_field_with_name (thanks @mpaclark) 33 | 34 | Version 0.10.1 35 | 36 | * Fix a documentation error with the new attachments API (thanks Vincent Beau) 37 | 38 | Version 0.10.0 39 | 40 | * Add RemoteAPI#add_attachments_to_issue_with_key (thanks @rjharmon) 41 | * Add RemoteAPI#components_for_project_with_key (thanks @rjharmon) 42 | 43 | * Fix a typo in RemoteAPI#progress_workflow_action (thanks Vincent Beau) 44 | 45 | * Deprecate RemoteAPI#add_base64_encoded_attachments_to_issue_with_key (thanks @rjharmon) 46 | * Remove deprecated methods from previous release 47 | * Begin making the test suite public 48 | 49 | Version 0.9.2 50 | 51 | * Remove unneeded httpclient dependency 52 | 53 | Version 0.9.1 54 | 55 | * Fix a typo in the deprecation warning 56 | 57 | Version 0.9.0 58 | 59 | * Deprecate RemoteAPI#get_projects_without_schemes in favour of #projects 60 | * Properly deprecate #get_ methods 61 | * Add RemoteAPI#progress_workflow_action (thanks Lucas Jourdes) 62 | * Add RemoteAPI#available_action (thanks Lucas Jourdes) 63 | * Add extra documentation about updating fields (thanks Vincent Beau) 64 | 65 | * Various small documentation updates and tweaks 66 | 67 | Version 0.8.6 68 | 69 | * Add extra documentation about updating cascading fields (thanks Lucas Jourdes) 70 | 71 | Version 0.8.5 72 | 73 | * Add missing alias for a predicate attribute (IssueProperty#sub_task?) 74 | 75 | Version 0.8.4 76 | 77 | * Fix indentation of example code in documentation 78 | 79 | Version 0.8.3 80 | 81 | * Fix a documentation link 82 | 83 | Version 0.8.2 84 | 85 | * Added Issue#custom_field for getting custom field values 86 | * Added a file of examples, ported from jira4r 87 | * Add setters to attribute aliases 88 | * Added a forgotten deprecation 89 | * Updated ChangeLog with changes from 0.8.1 90 | * Updated ChangeLog with changes from 0.8.0 (sorry!) 91 | 92 | Version 0.8.1 93 | 94 | * Updated README and GettingStarted guide 95 | 96 | Version 0.8.0 97 | 98 | * Deprecated the #get_ from methods that start with #get_ 99 | * Alias #login to #log_in and #logout to #log_out 100 | * Added RemoteAPI#permission_to_edit_comment? 101 | * Added UserGroup and related RemoteAPI methods 102 | * Added RemoteAPI#add_worklog (thanks @FDj) 103 | * Added ability to initialize JIRA::FieldValue without an array (thanks @knut) 104 | 105 | * Various documentation updates and tweaks 106 | 107 | * Removed handsoap parsing abstraction (roflscale for parsing) 108 | * Removed the YARD plugin in favour of YARD 0.7 DSL documenting stuff 109 | 110 | * Fix setting the assignee during Issue creation (thanks @ssmiech) 111 | 112 | Version 0.7.1 113 | 114 | * Remove AOT compiled files from gem 115 | 116 | Version 0.7 117 | 118 | * Added YARD extension for documentation 119 | * Removed URL class in favour of directly using underlying objects 120 | * Added AOT compilation for MacRuby 121 | * Moved RemoteAPI into the JIRA namespace 122 | * Alias #get_favourite_filter to #get_favorite_filter 123 | 124 | * Tweaked parsing logic more in preparation for the builder generalization 125 | * Various documentation updates 126 | 127 | * Removed jeweler development dependency 128 | * Removed yardstick development dependency 129 | * General tweaking of the rakefile, gemspec, gemfile, and .yardopts 130 | 131 | Version 0.6.1 132 | 133 | * Added RemoteAPIAdditions module for conveniences 134 | * Fix duplicate dependancy listings 135 | 136 | Version 0.6 137 | 138 | * Added RemoteAPI#get_projects_without_schemes 139 | * Added RemoteAPI#delete_project_avatar_with_id 140 | * Added RemoteAPI#delete_project_with_key 141 | * Added RemoteAPI#set_project_avatar_for_project_with_key 142 | * Added RemoteAPI#set_new_project_avatar_for_project_with_key 143 | * Added RemoteAPI#get_resolution_date_for_issue_with_id 144 | * Added RemoteAPI#get_resolution_date_for_issue_with_key 145 | * Added RemoteAPI#get_project_roles and friends 146 | 147 | * Changed parsing logic (1.5-2x faster) 148 | * Changed Scheme#type to return a Class constant 149 | * Changed #color in JIRA::Priority to be formatted as an array 150 | triple 151 | * Alias #colour to #color in JIRA::Priority 152 | * Changed instances of #lead to #lead_username 153 | * Changed instances of #create_date to #create_time 154 | * Changed instances of #last_updated to #last_updated_time 155 | * Changed instances of #filename to #file_name 156 | * Changed instances of #original_author to #author 157 | * Changed instances of #reporter_name to #reporter_username 158 | * Changed instances of #assignee_name to #assignee_username 159 | * Consolidated Handsoap parsing extensions into NokogiriDriver 160 | * Various documentation updates 161 | 162 | Version 0.5 163 | 164 | * Begin summarizing changes in a changelog 165 | * Begin abstracting parts of JIRA model 166 | 167 | * Fixed RemoteAPI#add_version_to_project_with_key 168 | 169 | * Added RemoteAPI#get_server_configuration 170 | 171 | * Changed use of factories to constructors in the model 172 | * Changed FieldValue#id to FieldValue#field_name 173 | * Changed CustomField to CustomFieldValue 174 | * Changed User#name to User#username 175 | * Changed Scheme#type to return the class name 176 | * Changed some RemoteAPI method names to be more descriptive 177 | * Changed Avatar#content_type to Avatar#mime_type 178 | * Various documentation updates 179 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2012 Marketcircle Inc. 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. 21 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # jiraSOAP - Ruby interface to the JIRA SOAP API 2 | 3 | Uses [handsoap](http://wiki.github.com/unwire/handsoap/) to build a 4 | client for the JIRA SOAP API that works on Ruby 1.9 as well as MacRuby. 5 | 6 | You can read the documentation for the 7 | [latest release](http://rubydoc.info/gems/jiraSOAP/) or 8 | the 9 | [HEAD commit](http://rdoc.info/github/Marketcircle/jiraSOAP/master/frames). 10 | The meat of the service is in the `RemoteAPI` module. 11 | 12 | 13 | ## Motivation 14 | 15 | The `jira4r` gem already exists, and works well on Ruby 1.8, but is 16 | not compatible with Ruby 1.9 or MacRuby due to its dependance on 17 | `soap4r`. 18 | 19 | 20 | ## Goals 21 | 22 | Pick up where `jira4r` left off: 23 | 24 | - Implement the current API; `jira4r` does not implement APIs from JIRA 4.x 25 | * not including APIs that have been deprecated in JIRA 4.x 26 | - More natural interface; not adhering to the API when the API is weird 27 | - Speed; network latency is bad enough (it would be cool to roflscale) 28 | - Excellent documentation 29 | 30 | 31 | ## Getting Started 32 | 33 | See the {file:docs/GettingStarted.markdown Getting Started} guide. 34 | 35 | 36 | ## TODO 37 | 38 | 39 | - Finish implementing all of the JIRA API 40 | - Performance optimizations 41 | + Using GCD/Threads for parsing arrays of results; a significant 42 | speed up for large types and large arrays (ie. creating issues from 43 | JQL searches) 44 | + Parsing might be doable with array indexing instead of hash lookups 45 | + Use a different web driver backend (net/http is slow under load) 46 | - ActiveRecord inspired conveniences 47 | + ProjectRole.new( 'test role' ).unique? # => check uniqueness 48 | + Issue.new( args ).create! # => creates a new issue 49 | + Issue.with_key( 'JIRA-123' ) # => returns result of issue lookup 50 | + Issue.new( args ).project # => returns a JIRA::Project 51 | 52 | 53 | ## Test Suite 54 | 55 | The test suite relies on a specific JIRA server being available. Every 56 | thing that might need to be configured has been abstracted to its own 57 | method so that the values can easily be changed, but I will try to 58 | provide a database backup in the near future if the licensing works out. 59 | 60 | ## Note on Patches/Pull Requests 61 | 62 | * Fork the project. 63 | * Make your feature addition or bug fix. 64 | * Add tests for it. This is important so I don't break it in a 65 | future version unintentionally. If it is difficult for you to run 66 | the tests then let me know. 67 | * Commit, do not mess with rakefile, version, or history. 68 | (if you want to have your own version, that is fine but 69 | bump version in a commit by itself I can ignore when I pull) 70 | * Send me a pull request. Bonus points for topic branches. 71 | 72 | 73 | ## License 74 | 75 | Copyright: [Marketcircle Inc.](http://www.marketcircle.com/), 2010-2012 76 | 77 | See LICENSE.txt for details. 78 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | task :default => :test 4 | 5 | 6 | ### MACRUBY BONUSES 7 | 8 | if defined? RUBY_ENGINE && RUBY_ENGINE == 'macruby' 9 | require 'rake/compiletask' 10 | Rake::CompileTask.new 11 | end 12 | 13 | 14 | ### GEM STUFF 15 | 16 | require 'rubygems/package_task' 17 | spec = Gem::Specification.load 'jiraSOAP.gemspec' 18 | Gem::PackageTask.new(spec) { } 19 | 20 | desc 'Build the gem and install it' 21 | task :install => :gem do 22 | require 'rubygems/dependency_installer' 23 | Gem::DependencyInstaller.new.install "pkg/#{spec.file_name}" 24 | end 25 | 26 | 27 | ### TESTING 28 | 29 | require 'rake/testtask' 30 | Rake::TestTask.new(:test) do |t| 31 | t.libs << 'test' 32 | t.ruby_opts = ['-rhelper.rb'] 33 | t.verbose = true 34 | end 35 | 36 | desc 'Startup irb with jiraSOAP loaded' 37 | task :console do 38 | sh 'irb -Ilib -rubygems -rjiraSOAP' 39 | end 40 | 41 | 42 | ### DOCUMENTATION 43 | 44 | begin 45 | require 'yard' 46 | YARD::Rake::YardocTask.new 47 | rescue LoadError => e 48 | warn 'yard not available. Install it with: gem install yard' 49 | end 50 | -------------------------------------------------------------------------------- /docs/Examples.markdown: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | These snippets showcase ways to interact with a JIRA server via JIRA's 4 | SOAP interface. The script was originally adapted from an example 5 | script for jira4r. 6 | 7 | ## The first thing you need to do 8 | 9 | Always start by creating a new {JIRA::JIRAService}, with the argument 10 | being the base URL to your JIRA server 11 | (e.g. http://confluence.atlassian.com): 12 | 13 | jira = JIRA::JIRAService.new 'http://jira.atlassian.com:8080' 14 | 15 | Then you need to log in: 16 | 17 | jira.login 'soaptester', 'password' 18 | 19 | Now the rest of the API is available to you. 20 | 21 | ## Server Information 22 | 23 | You can get information about the server, such as its version, and 24 | also information about its configuration, such as whether or not 25 | voting on issues is allowed: 26 | 27 | baseurl = jira.server_info.base_url 28 | puts "Base URL: #{baseurl} \n" 29 | 30 | if jira.server_configuration.voting_allowed? 31 | puts 'Voting is allowed on this server' 32 | else 33 | puts 'Voting is not allowed on this server' 34 | end 35 | 36 | ## Project Information 37 | 38 | Getting information about projects is also very easy, except for 39 | information about schemes (i.e. permissions, etc.). 40 | 41 | # get every project 42 | projects = jira.projects.each { |project| puts project.inspect } 43 | 44 | # get a specific project 45 | project_key = 'DEMO' 46 | project = jira.project_with_key project_key 47 | puts "Details for project #{project_key}: #{project.inspect}" 48 | 49 | ### Project Versions 50 | 51 | version = JIRA::Version.new 52 | version.name = 'version 99' 53 | new_version = jira.add_version_to_project_with_key project.key, version 54 | puts "Added version #{new_version.name} to #{project.name}\n" 55 | 56 | ## Issues 57 | 58 | There are many ways to get and issue from the server, I think the 59 | easiest is to use the issue key. 60 | 61 | issue_key = 'TST-10392' 62 | issue = jira.issue_with_key issue_key 63 | puts "Retrieved issue: #{issue.key}: #{issue.summary}\n" 64 | 65 | ### Get the value of a custom field 66 | 67 | I find that I often need to get information from an issue's custom fields. 68 | 69 | custom_field_id = jira.custom_field_with_name('Reported on behalf of').id 70 | custom_field_value = issue.custom_field(custom_field_id) 71 | puts "Value of issue #{issue.key}'s custom field with ID #{custom_field_id}: " + 72 | "#{custom_field_value}\n" 73 | 74 | ### Comment on an issue 75 | 76 | It is also pretty easy to add a comment to an issue. 77 | 78 | comment = JIRA::Comment.new 79 | comment.body = 'commented from jiraSOAP' 80 | jira.add_comment_to_issue_with_key issue.key, comment 81 | 82 | ### Update a field for an issue 83 | 84 | Updating an issue is easy, but has some caveats, so you should see the 85 | documentation as well ({JIRA::RemoteAPI#update_issue}). 86 | 87 | summary = JIRA::FieldValue.new('summary', 'new summary info from jiraSOAP') 88 | jira.update_issue issue.key, summary 89 | puts "Updated issue #{issue.key}'s field #{summary.field_name}\n" 90 | 91 | One caveat is that some fields cannot be updated without also 92 | progressing the issue to a new status. 93 | 94 | action = jira.available_actions('TST-14').find do |action| 95 | action.name == 'Send To Test' 96 | end 97 | 98 | jira.progress_workflow_action 'TST-14', action.id 99 | 100 | You can also update fields, when progressing an issue to a new 101 | workflow state. 102 | 103 | assignee = JIRA::FieldValue.new 'assignee', 'mrada' 104 | jira.progress_workflow_action 'TST-14', action.id, assignee 105 | 106 | ## User Information 107 | 108 | CRUD operations for user information are also available. 109 | 110 | begin 111 | password = rand(1000000000000).to_s(16) 112 | user = jira.create_user('jon', password, 'Jon Happy', 'jon@usa.com') 113 | puts "Created user #{user.username} with password: #{password}\n" 114 | rescue 115 | end 116 | 117 | ### User groups 118 | 119 | # get details about a user group 120 | group = jira.group_with_name 'jira-users' 121 | puts "Retrieved details for group jira-users: #{group.inspect}\n" 122 | 123 | # add a user to a group 124 | newuser = JIRA::User.new 125 | newuser.name = user.name 126 | jira.add_user_to_group group, user 127 | puts "Added user #{user.name} in group #{group.name}\n" 128 | 129 | ## Don't forget to log out 130 | 131 | Your session will timeout, but for security purposes it is always a 132 | good idea to log out when you are done. 133 | 134 | jira.logout 135 | -------------------------------------------------------------------------------- /docs/GettingStarted.markdown: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | `jiraSOAP` should run on Ruby 1.9.2+ and MacRuby 0.8+. It is available through rubygems or you can build from source: 4 | 5 | # Using rubygems 6 | gem install jiraSOAP 7 | 8 | # Building from source 9 | git clone git://github.com/Marketcircle/jiraSOAP.git 10 | rake install 11 | 12 | Once installed, you can run a quick demo (making appropriate substitutions): 13 | 14 | require 'jiraSOAP' 15 | 16 | The first thing that you need to do is create a JIRAService object: 17 | 18 | db = JIRA::JIRAService.new 'http://jira.yourSite.com:8080' 19 | 20 | Then you need to log in (a failed login will raise an exception): 21 | 22 | db.login 'mrada', 'secret' 23 | 24 | Once you are logged in, you can start querying the server for information: 25 | 26 | issues = db.issues_from_jql_search 'reporter = currentUser()', 100 27 | issues.each { |issue| 28 | #do something... 29 | puts issue.summary 30 | } 31 | 32 | Don't forget to log out when you are done: 33 | 34 | db.logout 35 | 36 | See {file:docs/Examples.markdown} for more examples. 37 | 38 | To find out what APIs are available, check out the {JIRA::RemoteAPI} 39 | module, as well as the {JIRA::RemoteAPIAdditions} module for 40 | conveniences that `jiraSOAP` has added. 41 | 42 | Some of the odder behaviours of the JIRA SOAP API are not documented 43 | here, though an answer might be available from the 44 | [JIRA FAQ](http://confluence.atlassian.com/display/JIRA/Frequently+Asked+RPC+Questions+and+Known+Issues). 45 | -------------------------------------------------------------------------------- /docs/SwitchingHTTPDriver.markdown: -------------------------------------------------------------------------------- 1 | # Switch HTTP Drivers 2 | 3 | By default, `jiraSOAP` tells `handsoap` to use the `net/http` driver 4 | for all HTTP work. This is done so that `jiraSOAP` will work 5 | out-of-the-box with other Ruby implementations, namely 6 | MacRuby. However, `net/http` is slow compared to the other 7 | available HTTP drivers. 8 | 9 | Switching to another, more performant, HTTP driver is advisable to get 10 | the maximum roflscale performance from `jiraSOAP`. The HTTP driver is 11 | handled entirely by `handsoap`, and is very easy change, you just need 12 | to make sure that you have loaded `jiraSOAP` first and then tell 13 | `handsoap` to use a different driver. An example would look like this: 14 | 15 | require 'rubygems' 16 | require 'jiraSOAP' 17 | Handsoap.http_driver = :curb 18 | 19 | Which would change the driver to the `curb` gem. There are other 20 | drivers available, and an up to date list is maintained in the 21 | `handsoap` [README](https://github.com/unwire/handsoap). 22 | 23 | __Note__: I only run the full test suite using `net/http`, but other 24 | drivers should be drop-in replacements that require no changes to 25 | `jiraSOAP` itself (with the exception of `eventmachine`). 26 | -------------------------------------------------------------------------------- /jiraSOAP.gemspec: -------------------------------------------------------------------------------- 1 | require './lib/jiraSOAP/version' 2 | 3 | Gem::Specification.new do |s| 4 | s.name = 'jiraSOAP' 5 | s.version = JIRA::VERSION 6 | 7 | s.summary = 'A Ruby client for the JIRA SOAP API' 8 | s.description = 'Written to run fast and work on Ruby 1.9 as well as MacRuby' 9 | s.authors = ['Mark Rada'] 10 | s.email = ['markrada26@gmail.com'] 11 | s.homepage = 'http://github.com/Marketcircle/jiraSOAP' 12 | s.license = 'MIT' 13 | 14 | s.files = Dir.glob('lib/**/*.rb') + ['.yardopts', 'Rakefile'] 15 | s.test_files = Dir.glob('test/**/*') 16 | s.extra_rdoc_files = [ 17 | 'README.markdown', 18 | 'ChangeLog', 19 | 'LICENSE.txt', 20 | 'docs/GettingStarted.markdown', 21 | 'docs/Examples.markdown' 22 | ] 23 | 24 | s.add_runtime_dependency 'nokogiri', '~> 1.5.0' 25 | s.add_runtime_dependency 'handsoap', '~> 1.1.8' 26 | # s.add_runtime_dependency 'httpclient', '~> 2.2.1' 27 | 28 | s.add_development_dependency 'yard', '~> 0.8.2' 29 | s.add_development_dependency 'redcarpet', '~> 1.17' 30 | s.add_development_dependency 'minitest', '~> 3.1' 31 | end 32 | -------------------------------------------------------------------------------- /lib/jiraSOAP.rb: -------------------------------------------------------------------------------- 1 | require 'time' 2 | require 'uri' 3 | 4 | require 'nokogiri' 5 | require 'jiraSOAP/nokogiri_extensions' 6 | 7 | require 'handsoap' 8 | #Handsoap.http_driver = :http_client 9 | Handsoap.http_driver = :net_http 10 | require 'jiraSOAP/handsoap_extensions' 11 | 12 | require 'jiraSOAP/url' 13 | 14 | ## 15 | # All the remote entities as well as the SOAP service client. 16 | module JIRA; end 17 | 18 | require 'jiraSOAP/version' 19 | require 'jiraSOAP/core_extensions' 20 | require 'jiraSOAP/entities' 21 | require 'jiraSOAP/api' 22 | require 'jiraSOAP/jira_service' 23 | 24 | if defined? RUBY_ENGINE && RUBY_ENGINE == 'macruby' 25 | require 'jiraSOAP/macruby_extensions' 26 | end 27 | -------------------------------------------------------------------------------- /lib/jiraSOAP/api.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Contains the API defined by Atlassian for the [JIRA SOAP service](http://docs.atlassian.com/software/jira/docs/api/rpc-jira-plugin/latest/com/atlassian/jira/rpc/soap/JiraSoapService.html). 3 | # 4 | # There are several cases where this API diverges from the one defined by 5 | # Atlassian; most notably, this API tries to be more idomatically Ruby by using 6 | # snake case for method names, default values, varargs, etc.. 7 | module JIRA::RemoteAPI 8 | 9 | # @group Logging in/out 10 | 11 | ## 12 | # The first method to call; other methods will fail until you are logged in. 13 | # 14 | # @param [String] username JIRA user name to login with 15 | # @param [String] password 16 | # @return [String] auth_token if successful, otherwise raises an exception 17 | def login username, password 18 | response = soap_call 'login', username, password 19 | @user = username 20 | @auth_token = response.first.content 21 | end 22 | alias_method :log_in, :login 23 | 24 | ## 25 | # You only need to call this to make an explicit logout; normally, a session 26 | # will automatically expire after a set time (configured on the server). 27 | # @return [Boolean] true if successful, otherwise false 28 | def logout 29 | jira_call( 'logout' ).to_boolean.tap do |_| 30 | @user = nil 31 | @auth_token = nil 32 | end 33 | end 34 | alias_method :log_out, :logout 35 | 36 | # @endgroup 37 | 38 | 39 | private 40 | 41 | # XPath constant to get a node containing a response data. 42 | RESPONSE_XPATH = '/node()[1]/node()[1]/node()[1]/node()[2]'.freeze 43 | 44 | ## 45 | # @todo make this method less ugly 46 | # 47 | # A generic method for calling a SOAP method and soapifying all 48 | # the arguments. 49 | # 50 | # @param [String] method name of the JIRA SOAP API method 51 | # @param [Object] args the arguments for the method, excluding the 52 | # authentication token 53 | # @return [Handsoap::Response] 54 | def build method, *args 55 | invoke "soap:#{method}" do |msg| 56 | for i in 0...args.size 57 | case arg = args.shift 58 | when JIRA::Entity, Array 59 | msg.add "soap:in#{i}" do |submsg| arg.soapify_for submsg end 60 | else 61 | msg.add "soap:in#{i}", arg 62 | end 63 | end 64 | end 65 | end 66 | 67 | # @return [Nokogiri::XML::NodeSet] 68 | def soap_call method, *args 69 | response = build method, *args 70 | response .document.element/RESPONSE_XPATH 71 | end 72 | 73 | ## 74 | # A simple call, for methods that will return a single object. 75 | # 76 | # @param [String] method 77 | # @param [Object] args 78 | # @return [Nokogiri::XML::Element] 79 | def jira_call method, *args 80 | response = soap_call method, self.auth_token, *args 81 | response.first 82 | end 83 | 84 | ## 85 | # A more complex form of {#jira_call} that does a little more work for 86 | # you when you need to build an array of return values. 87 | # 88 | # @param [#new_with_xml] type a {JIRA::Entity} subclass 89 | # @param [String] method name of the JIRA SOAP API method 90 | # @param [Object] args the arguments for the method, excluding the 91 | # authentication token 92 | # @return [Nokogiri::XML::NodeSet] 93 | def array_jira_call type, method, *args 94 | response = soap_call method, self.auth_token, *args 95 | response.xpath('node()').map { |frag| type.new_with_xml(frag) } 96 | end 97 | 98 | end 99 | 100 | 101 | require 'jiraSOAP/api/additions' 102 | require 'jiraSOAP/api/attachments' 103 | require 'jiraSOAP/api/avatars' 104 | require 'jiraSOAP/api/comments' 105 | require 'jiraSOAP/api/components' 106 | require 'jiraSOAP/api/filters' 107 | require 'jiraSOAP/api/issue_data_types' 108 | require 'jiraSOAP/api/issues' 109 | require 'jiraSOAP/api/project_roles' 110 | require 'jiraSOAP/api/projects' 111 | require 'jiraSOAP/api/schemes' 112 | require 'jiraSOAP/api/server_info' 113 | require 'jiraSOAP/api/users' 114 | require 'jiraSOAP/api/versions' 115 | require 'jiraSOAP/api/worklog' 116 | -------------------------------------------------------------------------------- /lib/jiraSOAP/api/additions.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Methods declared here do not directly map to methods defined in JIRA's 3 | # SOAP API javadoc. They are generally close to something from the javadoc 4 | # but with some extra conveniences. 5 | module JIRA::RemoteAPIAdditions 6 | 7 | ## 8 | # Returns the first field that exactly matches the given 9 | # name, otherwise returns nil. 10 | # 11 | # @param [String] name 12 | # @return [JIRA::Field,nil] 13 | def custom_field_with_name name 14 | custom_fields.find { |cf| cf.name == name } 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lib/jiraSOAP/api/attachments.rb: -------------------------------------------------------------------------------- 1 | module JIRA::RemoteAPI 2 | 3 | ## 4 | # @note The JIRA SOAP API only works with the metadata for attachments, 5 | # you will have to look at your servers configuration to figure 6 | # out the proper URL to call to get attachment data. 7 | # 8 | # This module has implemented all relevant APIs as of JIRA 4.2. 9 | 10 | # @group Attachments 11 | 12 | ## 13 | # @todo change method name to reflect that you only get metadata 14 | # 15 | # @param [String] issue_key 16 | # @return [Array] 17 | def attachments_for_issue_with_key issue_key 18 | array_jira_call JIRA::Attachment, 'getAttachmentsFromIssue', issue_key 19 | end 20 | 21 | ## 22 | # @note Expect this method to be slow. 23 | # 24 | # Uploads attachments to an issue using the `addBase64EncodedAttachmentsToIssue` 25 | # SOAP method. 26 | # 27 | # The metadata is not automatically refreshed by this method. To get the 28 | # updated metadata (e.g., `file_size` and `content_type`), call 29 | # {RemoteAPI#attachments_for_issue_with_key}. 30 | # 31 | # @param [String] issue_key 32 | # @param [JIRA::Attachment] attachments files to be uploaded; 33 | # their `content` attributes should populated with the data 34 | # @return [Boolean] true if successful 35 | def add_attachments_to_issue_with_key issue_key, *attachments 36 | invoke('soap:addBase64EncodedAttachmentsToIssue') { |msg| 37 | msg.add 'soap:in0', self.auth_token 38 | msg.add 'soap:in1', issue_key 39 | msg.add 'soap:in2' do |submsg| 40 | attachments.each { |attachment| submsg.add 'filenames', attachment.filename } 41 | end 42 | msg.add 'soap:in3' do |submsg| 43 | attachments.each { |attachment| submsg.add 'base64EncodedData', [attachment.content].pack('m0') } 44 | end 45 | } 46 | true 47 | end 48 | 49 | ## 50 | # @deprecated This will be removed in the next release (either 0.11 or 1.0) 51 | # 52 | # (see #add_attachments_to_issue_with_key) 53 | # 54 | # @param [String] issue_key 55 | # @param [Array] filenames array of names for attachments 56 | # @param [Array] data base64 encoded data for upload 57 | # @return [Boolean] true if successful, otherwise an exception is raised 58 | def add_base64_encoded_attachments_to_issue_with_key issue_key, filenames, data 59 | $stderr.puts <<-EOM 60 | RemoteAPI#add_base64_encoded_attachments_to_issue_with_key is deprecated and will be removed in the next release. 61 | Please use RemoteAPI#add_attachments_to_issue_with_key instead. 62 | EOM 63 | 64 | invoke('soap:addBase64EncodedAttachmentsToIssue') { |msg| 65 | msg.add 'soap:in0', self.auth_token 66 | msg.add 'soap:in1', issue_key 67 | msg.add 'soap:in2' do |submsg| 68 | filenames.each { |filename| submsg.add 'filenames', filename } 69 | end 70 | msg.add 'soap:in3' do |submsg| 71 | data.each { |datum| submsg.add 'base64EncodedData', datum } 72 | end 73 | } 74 | true 75 | end 76 | 77 | end 78 | -------------------------------------------------------------------------------- /lib/jiraSOAP/api/avatars.rb: -------------------------------------------------------------------------------- 1 | module JIRA::RemoteAPI 2 | 3 | # @group Avatars 4 | 5 | ## 6 | # Gets you the default avatar image for a project; if you want all 7 | # the avatars for a project, use {#project_avatars_for_key}. 8 | # 9 | # @param [String] project_key 10 | # @return [JIRA::Avatar] 11 | def project_avatar_for_key project_key 12 | JIRA::Avatar.new_with_xml jira_call('getProjectAvatar', project_key) 13 | end 14 | 15 | ## 16 | # Gets ALL avatars for a given project with this method; if you 17 | # just want the project avatar, use {#project_avatar_for_key}. 18 | # 19 | # @param [String] project_key 20 | # @param [Boolean] include_default_avatars 21 | # @return [Array] 22 | def project_avatars_for_key project_key, include_default_avatars = false 23 | array_jira_call JIRA::Avatar, 'getProjectAvatars', project_key, include_default_avatars 24 | end 25 | 26 | ## 27 | # @note You cannot delete system avatars, and you need project 28 | # administration permissions to delete other avatars. 29 | # 30 | # @param [String] avatar_id 31 | # @return [Boolean] true if successful 32 | def delete_project_avatar_with_id avatar_id 33 | jira_call 'deleteProjectAvatar', avatar_id 34 | true 35 | end 36 | 37 | ## 38 | # @note You need project administration permissions to edit an avatar. 39 | # @note JIRA does not care if the avatar_id is valid. 40 | # 41 | # Change the project avatar to another existing avatar. If you want to 42 | # upload a new avatar and set it to be the new project avatar use 43 | # {#set_new_project_avatar_for_project_with_key} instead. 44 | # 45 | # @return [Boolean] true if successful 46 | def set_project_avatar_for_project_with_key project_key, avatar_id 47 | jira_call 'setProjectAvatar', project_key, avatar_id 48 | true 49 | end 50 | 51 | ## 52 | # @note You need project administration permissions to edit an avatar. 53 | # 54 | # Use this method to create a new custom avatar for a project and set it 55 | # to be current avatar for the project. 56 | # 57 | # The image, provided as base64 encoded data, should be a 48x48 pixel square. 58 | # If the image is larger, the top left 48 pixels are taken, if it is smaller 59 | # then it will be upscaled to 48 pixels. 60 | # The small version of the avatar image (16 pixels) is generated 61 | # automatically. 62 | # 63 | # If you want to switch a project avatar to an avatar that already exists on 64 | # the system then use {#set_project_avatar_for_project_with_key} instead. 65 | # 66 | # @param [String] project_key 67 | # @param [String] mime_type 68 | # @param [String] base64_image 69 | # @return [Boolean] true if successful 70 | def set_new_project_avatar_for_project_with_key project_key, mime_type, base64_image 71 | jira_call 'setNewProjectAvatar', project_key, mime_type, base64_image 72 | true 73 | end 74 | 75 | end 76 | -------------------------------------------------------------------------------- /lib/jiraSOAP/api/comments.rb: -------------------------------------------------------------------------------- 1 | module JIRA::RemoteAPI 2 | 3 | # @group Comments 4 | 5 | # @param [String] issue_key 6 | # @param [JIRA::Comment] comment 7 | # @return [Boolean] true if successful 8 | def add_comment_to_issue_with_key issue_key, comment 9 | jira_call 'addComment', issue_key, comment 10 | true 11 | end 12 | 13 | # @param [String] id 14 | # @return [JIRA::Comment] 15 | def comment_with_id id 16 | JIRA::Comment.new_with_xml jira_call( 'getComment', id ) 17 | end 18 | 19 | # @param [String] issue_key 20 | # @return [Array] 21 | def comments_for_issue_with_key issue_key 22 | array_jira_call JIRA::Comment, 'getComments', issue_key 23 | end 24 | 25 | # @param [JIRA::Comment] comment 26 | # @return [JIRA::Comment] 27 | def update_comment comment 28 | JIRA::Comment.new_with_xml jira_call( 'editComment', comment ) 29 | end 30 | 31 | # @param [JIRA::Comment] comment 32 | def permission_to_edit_comment? comment 33 | jira_call( 'hasPermissionToEditComment', comment ).to_boolean 34 | end 35 | 36 | end 37 | -------------------------------------------------------------------------------- /lib/jiraSOAP/api/components.rb: -------------------------------------------------------------------------------- 1 | module JIRA::RemoteAPI 2 | # @group Components 3 | 4 | ## 5 | # Lists a project's components 6 | # 7 | # @param [String] project_key 8 | # @return [Array] 9 | def components_for_project_with_key project_key 10 | array_jira_call JIRA::Component, 'getComponents', project_key 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /lib/jiraSOAP/api/filters.rb: -------------------------------------------------------------------------------- 1 | module JIRA::RemoteAPI 2 | 3 | # @group Filters 4 | 5 | ## 6 | # Retrieves favourite filters for the currently logged in user. 7 | # 8 | # @return [Array] 9 | def favourite_filters 10 | array_jira_call JIRA::Filter, 'getFavouriteFilters' 11 | end 12 | alias_method :favorite_filters, :favourite_filters 13 | 14 | # @param [String] id 15 | # @return [Fixnum] 16 | def issue_count_for_filter_with_id id 17 | jira_call( 'getIssueCountForFilter', id ).to_i 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /lib/jiraSOAP/api/issue_data_types.rb: -------------------------------------------------------------------------------- 1 | module JIRA::RemoteAPI 2 | 3 | # @group Issue attributes 4 | 5 | # @return [Array] 6 | def priorities 7 | array_jira_call JIRA::Priority, 'getPriorities' 8 | end 9 | 10 | # @return [Array] 11 | def resolutions 12 | array_jira_call JIRA::Resolution, 'getResolutions' 13 | end 14 | 15 | # @return [Array] 16 | def custom_fields 17 | array_jira_call JIRA::Field, 'getCustomFields' 18 | end 19 | 20 | # @return [Array] 21 | def issue_types 22 | array_jira_call JIRA::IssueType, 'getIssueTypes' 23 | end 24 | 25 | # @param [String] project_id 26 | # @return [Array] 27 | def issue_types_for_project_with_id project_id 28 | array_jira_call JIRA::IssueType, 'getIssueTypesForProject', project_id 29 | end 30 | 31 | # @return [Array] 32 | def statuses 33 | array_jira_call JIRA::Status, 'getStatuses' 34 | end 35 | 36 | # @return [Array] 37 | def subtask_issue_types 38 | array_jira_call JIRA::IssueType, 'getSubTaskIssueTypes' 39 | end 40 | 41 | # @param [String] project_id 42 | # @return [Array] 43 | def subtask_issue_types_for_project_with_id project_id 44 | array_jira_call JIRA::IssueType, 'getSubTaskIssueTypesForProject', project_id 45 | end 46 | 47 | ## 48 | # @todo find out what this method does 49 | # 50 | # I have no idea what this method does. 51 | # 52 | # @return [Boolean] true if no exceptions were raised 53 | def refresh_custom_fields 54 | jira_call 'refreshCustomFields' 55 | true 56 | end 57 | 58 | end 59 | -------------------------------------------------------------------------------- /lib/jiraSOAP/api/issues.rb: -------------------------------------------------------------------------------- 1 | module JIRA::RemoteAPI 2 | 3 | # @group Issues 4 | 5 | ## 6 | # @note During my own testing, I found that HTTP requests could timeout 7 | # for really large requests (~2500 results) if you are not on the 8 | # same network. So I set a more reasonable upper limit; feel free 9 | # to override it, but be aware of the potential issues. 10 | # 11 | # This method is the equivalent of making an advanced search from the web 12 | # interface. 13 | # 14 | # The {JIRA::Issue} structure does not include any comments or attachments. 15 | # 16 | # @param [String] jql_query JQL query as a string 17 | # @param [Fixnum] max_results limit on number of returned results; 18 | # the value may be overridden by the server if max_results is too large 19 | # @return [Array] 20 | def issues_from_jql_search jql_query, max_results = 2000 21 | array_jira_call JIRA::Issue, 'getIssuesFromJqlSearch', jql_query, max_results 22 | end 23 | 24 | ## 25 | # This method can update most, but not all, issue fields. Some limitations 26 | # are because of how the API is designed, and some are because I have not 27 | # yet implemented the ability to update fields made of custom objects 28 | # (things in the JIRA module). 29 | # 30 | # Fields known to not update via this method: 31 | # 32 | # - status - use {#progress_workflow_action} 33 | # - attachments - use {#add_base64_encoded_attachments_to_issue_with_key} 34 | # 35 | # Though {JIRA::FieldValue} objects have an id field, they do not expect 36 | # to be given id values. You must use the camel cased name of the field you 37 | # wish to update, such as `'fixVersions'` to update the `fix_versions` for 38 | # an issue. 39 | # 40 | # However, there is at least one exception to the rule; when you wish to 41 | # update the affected versions for an issue, you should use `'versions'` 42 | # instead of `'affectsVersions'`. You need to be careful about these 43 | # cases as it has been reported that JIRA will silently fail to update 44 | # fields it does not know about. 45 | # 46 | # Updating nested fields can be tricky, see the example on cascading 47 | # select field's to see how it would be done. 48 | # 49 | # A final note, some fields only should be passed the id in order to 50 | # update them, as shown in the version updating example. 51 | # 52 | # @example Usage With A Normal Field 53 | # 54 | # summary = JIRA::FieldValue.new 'summary', 'My new summary' 55 | # 56 | # @example Usage With A Custom Field 57 | # 58 | # custom_field = JIRA::FieldValue.new 'customfield_10060', '123456' 59 | # 60 | # @example Setting a field to be blank/nil 61 | # 62 | # description = JIRA::FieldValue.new 'description' 63 | # 64 | # @example Calling the method to update an issue 65 | # 66 | # jira.update_issue 'PROJECT-1', description, custom_field 67 | # 68 | # @example Setting the value of a cascading select field 69 | # 70 | # part1 = JIRA::FieldValue.new 'customfield_10285', 'Main Detail' 71 | # part2 = JIRA::FieldValue.new 'customfield_10285:1', 'First Subdetail' 72 | # jira.update_issue 'PROJECT-1', part1, part2 73 | # 74 | # @example Changing the affected versions for an issue 75 | # 76 | # version = jira.versions_for_project.find { |x| x.name = 'v1.0beta' } 77 | # field = JIRA::FieldValue.new 'versions', version.id 78 | # jira.update_issue 'PROJECT-1', field 79 | # 80 | # @param [String] issue_key 81 | # @param [JIRA::FieldValue] field_values 82 | # @return [JIRA::Issue] 83 | def update_issue issue_key, *field_values 84 | JIRA::Issue.new_with_xml jira_call('updateIssue', issue_key, field_values) 85 | end 86 | 87 | ## 88 | # Some fields will be ignored when an issue is created. 89 | # 90 | # - reporter - you cannot override this value at creation 91 | # - resolution 92 | # - attachments 93 | # - votes 94 | # - status 95 | # - due date - I think this is a bug in jiraSOAP or JIRA 96 | # - environment - I think this is a bug in jiraSOAP or JIRA 97 | # 98 | # @param [JIRA::Issue] issue 99 | # @return [JIRA::Issue] 100 | def create_issue_with_issue issue 101 | JIRA::Issue.new_with_xml jira_call( 'createIssue', issue ) 102 | end 103 | 104 | # @param [JIRA::Issue] issue 105 | # @param [String] parent_id 106 | # @return [JIRA:Issue] 107 | def create_issue_with_issue_and_parent issue, parent_id 108 | JIRA::Issue.new_with_xml jira_call('createIssueWithParent', issue, parent_id) 109 | end 110 | alias_method :create_issue_with_parent, :create_issue_with_issue_and_parent 111 | 112 | # @param [String] issue_key 113 | # @return [JIRA::Issue] 114 | def issue_with_key issue_key 115 | JIRA::Issue.new_with_xml jira_call( 'getIssue', issue_key ) 116 | end 117 | 118 | # @param [String] issue_id 119 | # @return [JIRA::Issue] 120 | def issue_with_id issue_id 121 | JIRA::Issue.new_with_xml jira_call( 'getIssueById', issue_id ) 122 | end 123 | 124 | # @param [String] id 125 | # @param [Fixnum] max_results 126 | # @param [Fixnum] offset 127 | # @return [Array] 128 | def issues_from_filter_with_id id, max_results = 500, offset = 0 129 | array_jira_call JIRA::Issue, 'getIssuesFromFilterWithLimit', id, offset, max_results 130 | end 131 | 132 | # @param [String] issue_id 133 | # @return [Time] 134 | def resolution_date_for_issue_with_id issue_id 135 | jira_call( 'getResolutionDateById', issue_id ).to_iso_date 136 | end 137 | 138 | # @param [String] issue_key 139 | # @return [Time] 140 | def resolution_date_for_issue_with_key issue_key 141 | jira_call( 'getResolutionDateByKey', issue_key ).to_iso_date 142 | end 143 | 144 | ## 145 | # This method acts like {#update_issue} except that it also updates 146 | # the status of the issue. 147 | # 148 | # The `action_id` parameter comes from the `id` of an available action. 149 | # Normally you will use this method in conjunction with 150 | # {#available_actions} to decide which action to take. 151 | # 152 | # @param [String] issue_key 153 | # @param [String] action_id this is the id of workflow action 154 | # @param [JIRA::FieldValue] field_values 155 | # @return [JIRA::Issue] 156 | def progress_workflow_action issue_key, action_id, *field_values 157 | JIRA::Issue.new_with_xml jira_call('progressWorkflowAction', 158 | issue_key, action_id, field_values) 159 | end 160 | 161 | ## 162 | # Returns workflow actions available for an issue. 163 | # 164 | # @param [String] issue_key 165 | # @return [Array] 166 | def available_actions issue_key 167 | array_jira_call JIRA::NamedEntity, 'getAvailableActions', issue_key 168 | end 169 | 170 | end 171 | -------------------------------------------------------------------------------- /lib/jiraSOAP/api/project_roles.rb: -------------------------------------------------------------------------------- 1 | module JIRA::RemoteAPI 2 | 3 | # @group Project Roles 4 | 5 | # @return [Array] 6 | def project_roles 7 | array_jira_call JIRA::ProjectRole, 'getProjectRoles' 8 | end 9 | 10 | # @param [String] role_id 11 | # @return [JIRA::ProjectRole] 12 | def project_role_with_id role_id 13 | JIRA::ProjectRole.new_with_xml jira_call( 'getProjectRole', role_id ) 14 | end 15 | 16 | # @param [JIRA::ProjectRole] project_role 17 | # @return [JIRA::ProjectRole] the role that was created 18 | def create_project_role_with_role project_role 19 | JIRA::ProjectRole.new_with_xml jira_call( 'createProjectRole', project_role ) 20 | end 21 | 22 | ## 23 | # @note JIRA 4.0 and 4.2 returns an exception if the name already exists 24 | # 25 | # Returns true if the name does not exist. 26 | # 27 | # @param [String] project_role_name 28 | # @return [Boolean] true if successful 29 | def project_role_name_unique? project_role_name 30 | jira_call( 'isProjectRoleNameUnique', project_role_name ).to_boolean 31 | end 32 | 33 | ## 34 | # @note The confirm argument appears to do nothing (at least on JIRA 4.0) 35 | # 36 | # @param [JIRA::ProjectRole] project_role 37 | # @param [Boolean] confirm 38 | # @return [Boolean] true if successful 39 | def delete_project_role project_role, confirm = true 40 | jira_call 'deleteProjectRole', project_role, confirm 41 | true 42 | end 43 | 44 | ## 45 | # @note JIRA 4.0 will not update project roles, it will instead throw 46 | # an exception telling you that the project role already exists 47 | # 48 | # @param [JIRA::ProjectRole] project_role 49 | # @return [JIRA::ProjectRole] the role after the update 50 | def update_project_role_with_role project_role 51 | JIRA::ProjectRole.new_with_xml jira_call( 'updateProjectRole', project_role ) 52 | end 53 | 54 | end 55 | -------------------------------------------------------------------------------- /lib/jiraSOAP/api/projects.rb: -------------------------------------------------------------------------------- 1 | module JIRA::RemoteAPI 2 | 3 | # @group Projects 4 | 5 | ## 6 | # You need to explicitly ask for schemes in order to get them. By 7 | # default, most project fetching methods purposely leave out all 8 | # the scheme information as permission schemes can be very large. 9 | # 10 | # @param [String] project_key 11 | # @return [JIRA::Project] 12 | def project_with_key project_key 13 | JIRA::Project.new_with_xml jira_call( 'getProjectByKey', project_key ) 14 | end 15 | 16 | # @param [String] project_id 17 | # @return [JIRA::Project] 18 | def project_with_id project_id 19 | JIRA::Project.new_with_xml jira_call( 'getProjectById', project_id ) 20 | end 21 | 22 | ## 23 | # @todo Parse the permission scheme 24 | # @note This method does not yet include the permission scheme. 25 | # 26 | # @param [String] project_id 27 | # @return [JIRA::Project] 28 | def project_including_schemes_with_id project_id 29 | JIRA::Project.new_with_xml jira_call( 'getProjectWithSchemesById', project_id ) 30 | end 31 | 32 | ## 33 | # @note This will not fill in {JIRA::Scheme} data for the projects. 34 | # 35 | # @return [Array] 36 | def projects 37 | array_jira_call JIRA::Project, 'getProjectsNoSchemes' 38 | end 39 | alias_method :projects_without_schemes, :projects 40 | 41 | ## 42 | # Requires you to set at least a project name, key, and lead. 43 | # However, it is also a good idea to set other project properties, such as 44 | # the permission scheme as the default permission scheme can be too 45 | # restrictive in most cases. 46 | # 47 | # @param [JIRA::Project] project 48 | # @return [JIRA::Project] 49 | def create_project_with_project project 50 | JIRA::Project.new_with_xml jira_call( 'createProjectFromObject', project ) 51 | end 52 | 53 | ## 54 | # The id of the project is the only field that you cannot update. Or, at 55 | # least the only field I know that you cannot update. 56 | # 57 | # @param [JIRA::Project] project 58 | # @return [JIRA::Project] 59 | def update_project_with_project project 60 | JIRA::Project.new_with_xml jira_call( 'updateProject', project ) 61 | end 62 | 63 | # @param [String] project_key 64 | # @return [Boolean] true if successful 65 | def delete_project_with_key project_key 66 | jira_call 'deleteProject', project_key 67 | true 68 | end 69 | 70 | end 71 | -------------------------------------------------------------------------------- /lib/jiraSOAP/api/schemes.rb: -------------------------------------------------------------------------------- 1 | module JIRA::RemoteAPI 2 | 3 | # @group Schemes 4 | 5 | # @return [Array] 6 | def notification_schemes 7 | array_jira_call JIRA::NotificationScheme, 'getNotificationSchemes' 8 | end 9 | 10 | # @return [Array] 11 | def permission_schemes 12 | array_jira_call JIRA::PermissionScheme, 'getPermissionSchemes' 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /lib/jiraSOAP/api/server_info.rb: -------------------------------------------------------------------------------- 1 | module JIRA::RemoteAPI 2 | 3 | # @group Server Information 4 | 5 | ## 6 | # The @build_date attribute is a Time value, but does not include a time. 7 | # 8 | # @return [JIRA::ServerInfo] 9 | def server_info 10 | JIRA::ServerInfo.new_with_xml jira_call( 'getServerInfo' ) 11 | end 12 | 13 | # @return [JIRA::ServerConfiguration] 14 | def server_configuration 15 | JIRA::ServerConfiguration.new_with_xml jira_call( 'getConfiguration' ) 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /lib/jiraSOAP/api/users.rb: -------------------------------------------------------------------------------- 1 | module JIRA::RemoteAPI 2 | 3 | # @group Users 4 | 5 | # @param [String] user_name 6 | # @return [JIRA::User] 7 | def user_with_name user_name 8 | JIRA::User.new_with_xml jira_call( 'getUser', user_name ) 9 | end 10 | 11 | ## 12 | # It seems that creating a user without any permission groups will trigger 13 | # an exception on some versions of JIRA. The irony is that this method provides 14 | # no way to add groups. The good news though, is that the creation will still 15 | # happen; but the user will have no permissions. 16 | # 17 | # @param [String] username 18 | # @param [String] password 19 | # @param [String] full_name 20 | # @param [String] email 21 | # @return [JIRA::User,nil] depending on your JIRA version, this method may 22 | # always raise an exception instead of actually returning anything 23 | def create_user username, password, full_name, email 24 | fragment = jira_call( 'createUser', username, password, full_name, email ) 25 | JIRA::User.new_with_xml fragment 26 | end 27 | 28 | # @param [String] username 29 | # @return [Boolean] true if successful 30 | def delete_user_with_name username 31 | jira_call 'deleteUser', username 32 | true 33 | end 34 | 35 | # @param [String] group_name 36 | # @return [JIRA::UserGroup] 37 | def group_with_name group_name 38 | frag = jira_call 'getGroup', group_name 39 | JIRA::UserGroup.new_with_xml frag 40 | end 41 | 42 | # @param [JIRA::UserGroup] group 43 | # @param [JIRA::User] user 44 | # @return [Boolean] true if successful 45 | def add_user_to_group group, user 46 | jira_call 'addUserToGroup', group, user 47 | true 48 | end 49 | 50 | # @param [JIRA::UserGroup] group 51 | # @param [JIRA::User] user 52 | # @return [Boolean] 53 | def remove_user_from_group group, user 54 | jira_call 'removeUserFromGroup', group, user 55 | true 56 | end 57 | 58 | ## 59 | # Create a new user group. You can initialize the group 60 | # with a user if you wish. 61 | # 62 | # @param [String] group_name 63 | # @param [JIRA::User] user 64 | # @return [JIRA::UserGroup] 65 | def create_user_group group_name, user = nil 66 | frag = jira_call 'createGroup', group_name, user 67 | JIRA::UserGroup.new_with_xml frag 68 | end 69 | 70 | ## 71 | # @todo Find out the semantics of swap_group 72 | # 73 | # @param [String] group_name 74 | # @param [String] swap_group 75 | # @return [Boolean] true if successful 76 | def delete_user_group group_name, swap_group 77 | jira_call 'deleteGroup', group_name, swap_group 78 | true 79 | end 80 | 81 | end 82 | -------------------------------------------------------------------------------- /lib/jiraSOAP/api/versions.rb: -------------------------------------------------------------------------------- 1 | module JIRA::RemoteAPI 2 | 3 | # @group Versions 4 | 5 | # @param [String] project_key 6 | # @return [Array] 7 | def versions_for_project project_key 8 | array_jira_call JIRA::Version, 'getVersions', project_key 9 | end 10 | 11 | ## 12 | # New versions cannot have the archived bit set and the release date 13 | # field will ignore the time of day you give it and instead insert 14 | # the time zone offset as the time of day. 15 | # 16 | # Remember that the @release_date field is the tentative release date, 17 | # so its value is independant of the @released flag. 18 | # 19 | # Descriptions do not appear to be included with JIRA::Version objects 20 | # that SOAP API provides. 21 | # 22 | # @param [String] project_key 23 | # @param [JIRA::Version] version 24 | # @return [JIRA::Version] 25 | def add_version_to_project_with_key project_key, version 26 | JIRA::Version.new_with_xml jira_call( 'addVersion', project_key, version ) 27 | end 28 | 29 | ## 30 | # The archive state can only be set to true for versions that have not been 31 | # released. However, this is not reflected by the return value of this method. 32 | # 33 | # @param [String] project_key 34 | # @param [String] version_name 35 | # @param [Boolean] state 36 | # @return [Boolean] true if successful 37 | def set_archive_state_for_version_for_project project_key, version_name, state 38 | jira_call 'archiveVersion', project_key, version_name, state 39 | true 40 | end 41 | 42 | ## 43 | # You can set the release state for a project with this method. 44 | # 45 | # @param [String] project_name 46 | # @param [JIRA::Version] version 47 | # @return [Boolean] true if successful 48 | def release_state_for_version_for_project project_name, version 49 | jira_call 'releaseVersion', project_name, version 50 | true 51 | end 52 | 53 | end 54 | -------------------------------------------------------------------------------- /lib/jiraSOAP/api/worklog.rb: -------------------------------------------------------------------------------- 1 | module JIRA::RemoteAPI 2 | 3 | # @group Worklogs 4 | 5 | ## 6 | # Adds a worklog to the given issue. 7 | # 8 | # @param [String] issue_key 9 | # @param [JIRA::Worklog] worklog 10 | def add_worklog_and_auto_adjust_remaining_estimate issue_key, worklog 11 | JIRA::Worklog.new_with_xml jira_call( 'addWorklogAndAutoAdjustRemainingEstimate', issue_key, worklog ) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/jiraSOAP/core_extensions.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Monkey patches to allow the SOAP building method to not need 3 | # a special case for dealing with arrays. 4 | class Array 5 | 6 | # @param [Handsoap::XmlMason::Node] msg message the node to add the object to 7 | def soapify_for msg 8 | each { |item| item.soapify_for msg } 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities.rb: -------------------------------------------------------------------------------- 1 | require 'jiraSOAP/entities/entity' 2 | require 'jiraSOAP/entities/dynamic_entity' 3 | require 'jiraSOAP/entities/named_entity' 4 | require 'jiraSOAP/entities/described_entity' 5 | 6 | require 'jiraSOAP/entities/component' 7 | require 'jiraSOAP/entities/field' 8 | require 'jiraSOAP/entities/filter' 9 | require 'jiraSOAP/entities/project_role' 10 | 11 | require 'jiraSOAP/entities/username' 12 | require 'jiraSOAP/entities/user' 13 | require 'jiraSOAP/entities/usergroup' 14 | 15 | require 'jiraSOAP/entities/field_value' 16 | require 'jiraSOAP/entities/custom_field_value' 17 | 18 | require 'jiraSOAP/entities/avatar' 19 | require 'jiraSOAP/entities/comment' 20 | require 'jiraSOAP/entities/version' 21 | require 'jiraSOAP/entities/attachment' 22 | 23 | require 'jiraSOAP/entities/scheme' 24 | require 'jiraSOAP/entities/notification_scheme' 25 | require 'jiraSOAP/entities/issue_security_scheme' 26 | require 'jiraSOAP/entities/permission' 27 | require 'jiraSOAP/entities/permission_mapping' 28 | require 'jiraSOAP/entities/permission_scheme' 29 | 30 | require 'jiraSOAP/entities/issue_property' 31 | require 'jiraSOAP/entities/resolution' 32 | require 'jiraSOAP/entities/status' 33 | require 'jiraSOAP/entities/priority' 34 | require 'jiraSOAP/entities/issue_type' 35 | 36 | require 'jiraSOAP/entities/project' 37 | require 'jiraSOAP/entities/issue' 38 | 39 | require 'jiraSOAP/entities/time_info' 40 | require 'jiraSOAP/entities/server_info' 41 | require 'jiraSOAP/entities/server_configuration' 42 | 43 | require 'jiraSOAP/entities/worklog' 44 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/attachment.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Only contains the metadata for an attachment. The URI for an attachment 3 | # appears to be of the form 4 | # "{JIRA::JIRAService.endpoint_url}/secure/attachment/{#id}/{#file_name}". 5 | class JIRA::Attachment < JIRA::NamedEntity 6 | 7 | # @return [String] 8 | add_attribute :author, 'author', :content 9 | 10 | # @return [String] 11 | add_attribute :file_name, 'filename', :content 12 | alias_method :filename, :file_name 13 | alias_method :filename=, :file_name= 14 | 15 | # @return [String] 16 | add_attribute :file_name, 'filename', :content 17 | 18 | ## 19 | # @note This method does not allow you to read the content of an existing 20 | # attachment on the issue; only the metadata for the attachment may 21 | # be read at this time. 22 | # 23 | # Content to be used for adding attachments, using 24 | # {RemoteAPI#add_attachments_to_issue_with_key}. Do _not_ base64 encode 25 | # the data yourself, it will be done for you when the attachment is 26 | # uploaded. 27 | # 28 | # However, attachment data coming from the server will come down in base64 29 | # encoded format... 30 | # 31 | # @return [String] 32 | attr_accessor :content 33 | 34 | # @return [String] 35 | add_attribute :mime_type, 'mimetype', :content 36 | alias_method :content_type, :mime_type 37 | alias_method :content_type=, :mime_type= 38 | 39 | ## 40 | # Measured in bytes 41 | # 42 | # @return [Number] 43 | add_attribute :file_size, 'filesize', :to_i 44 | 45 | # @return [Time] 46 | add_attribute :create_time, 'created', :to_iso_date 47 | 48 | ## 49 | # Fetch the attachment from the server. 50 | def attachment 51 | raise NotImplementedError, 'Please implement me. :(' 52 | end 53 | end 54 | 55 | ## 56 | # @deprecated This is just an alias, please use {JIRA::Attachment} instead 57 | # 58 | # Just an alias for {JIRA::Attachment}. 59 | JIRA::AttachmentMetadata = JIRA::Attachment 60 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/avatar.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # @todo find out what the id value of @owner relates to 3 | # 4 | # Contains a base64 encoded avatar image and metadata about the avatar. 5 | class JIRA::Avatar < JIRA::DynamicEntity 6 | 7 | ## 8 | # This seems to be an id ref to some other object 9 | # 10 | # @return [String] 11 | add_attribute :owner, 'owner', :content 12 | 13 | ## 14 | # The place where the avatar is used (e.g. 'project') 15 | # 16 | # @return [String] 17 | add_attribute :type, 'type', :content 18 | 19 | # @return [String] 20 | add_attribute :mime_type, 'contentType', :content 21 | alias_method :content_type, :mime_type 22 | alias_method :content_type=, :mime_type= 23 | 24 | # @return [String] 25 | add_attribute :base64_data, 'base64Data', :content 26 | alias_method :data, :base64_data 27 | alias_method :data=, :base64_data= 28 | 29 | ## 30 | # Indicates if the image is the system default 31 | # 32 | # @return [Boolean] 33 | add_attribute :system, 'system', :to_boolean 34 | 35 | end 36 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/comment.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Contains a comment's body and metadata. 3 | class JIRA::Comment < JIRA::DynamicEntity 4 | 5 | ## 6 | # A username 7 | # 8 | # @return [String] 9 | add_attribute :author, 'author', :content 10 | 11 | # @return [String] 12 | add_attribute :body, 'body', :content 13 | 14 | # @return [String] 15 | add_attribute :group_level, 'groupLevel', :content 16 | 17 | # @return [String] 18 | add_attribute :role_level, 'roleLevel', :content 19 | 20 | ## 21 | # A username 22 | # 23 | # @return [String] 24 | add_attribute :update_author, 'updateAuthor', :content 25 | 26 | # @return [Time] 27 | add_attribute :create_time, 'created', :to_iso_date 28 | 29 | # @return [Time] 30 | add_attribute :last_updated_time, 'updated', :to_iso_date 31 | 32 | ## 33 | # Add a created comment to an issue 34 | def add_to issue_key 35 | raise NotImplementedError, 'Please implement me. :(' 36 | end 37 | 38 | ## 39 | # @todo make this method shorter 40 | # 41 | # @param [Handsoap::XmlMason::Node] msg 42 | # @return [Handsoap::XmlMason::Node] 43 | def soapify_for msg 44 | msg.add 'id', @id 45 | msg.add 'author', @author 46 | msg.add 'body', @body 47 | msg.add 'groupLevel', @group_level if @group_level 48 | msg.add 'roleLevel', @role_level if @role_level 49 | msg.add 'updateAuthor', @update_author if @update_author 50 | end 51 | 52 | end 53 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/component.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Represents a component name and id only. It does not include the component 3 | # lead. 4 | class JIRA::Component < JIRA::NamedEntity 5 | end 6 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/custom_field_value.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # @note There are no API methods that directly create objects of this class, 3 | # they are only created as an attribute of {JIRA::Issue} objects. 4 | # @todo See if @key is always nil from the server, maybe we can remove it 5 | # 6 | # Represents an instance of a custom field (with values). 7 | class JIRA::CustomFieldValue < JIRA::DynamicEntity 8 | 9 | # @return [String] 10 | add_attribute :id, 'customfieldId', :content 11 | 12 | # @return [String] Used with cascading select fields 13 | add_attribute :key, 'key', :content 14 | 15 | # @return [Array] 16 | add_attribute :values, 'values', :contents_of_children 17 | 18 | ## 19 | # You can optionally initialize a custom field value with new 20 | # 21 | # @param [String] custom_field_id 22 | def initialize custom_field_id = nil, *custom_values 23 | @id = custom_field_id 24 | @values = custom_values 25 | end 26 | 27 | ## 28 | # Generate a SOAP message fragment for the object. 29 | # 30 | # @param [Handsoap::XmlMason::Node] msg SOAP message to add the object to 31 | # @param [String] label tag name used in wrapping tags 32 | # @return [Handsoap::XmlMason::Element] 33 | def soapify_for msg, label = 'customFieldValues' 34 | msg.add label do |submsg| 35 | submsg.add 'customfieldId', @id 36 | submsg.add 'key', @key 37 | submsg.add_simple_array 'values', @values 38 | end 39 | end 40 | 41 | end 42 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/described_entity.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # @abstract A named entity that also has a short description 3 | class JIRA::DescribedEntity < JIRA::NamedEntity 4 | 5 | ## 6 | # A short blurb 7 | # 8 | # @return [String] 9 | add_attribute :description, 'description', :content 10 | 11 | end 12 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/dynamic_entity.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # @abstract Anything that can be configured has an id field. 3 | class JIRA::DynamicEntity < JIRA::Entity 4 | 5 | ## 6 | # Usually a numerical value, but sometimes prefixed with a string 7 | # 8 | # @example 9 | # '12450' 10 | # 'customfield_10000' 11 | # 12 | # @return [String] 13 | add_attribute :id, 'id', :content 14 | 15 | end 16 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/entity.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # @abstract The base class for all JIRA objects that can given by the server. 3 | class JIRA::Entity 4 | 5 | class << self 6 | 7 | ## 8 | # Used for parsing XML 9 | # 10 | # @return [Hash{String=>Array(Symbol,Symbol,Class*)}] 11 | attr_accessor :parse 12 | 13 | ## 14 | # Define the callback to automatically initialize the build and parse 15 | # tables when any subclass is defined. 16 | def inherited subclass 17 | subclass.parse = @parse.dup 18 | end 19 | 20 | ## 21 | # @todo Add a way to signify if an attribute should not be used in 22 | # message building, as some attributes should never be included 23 | # in a SOAP message. 24 | # 25 | # Define a single instance attribute on the class including the 26 | # specification on how to parse the XML output and how to build 27 | # SOAP messages. 28 | # 29 | # Predicate methods will automatically be created if the transformer 30 | # method is `:to_boolean`. 31 | # 32 | # @param [Symbol] name name of the attribute to create 33 | # @param [String] jira_name name of the XML tag to look for when 34 | # parsing responses from the server 35 | # @param [Symbol,Array(Symbol,Class)] transformer either the method 36 | # name to transform some XML contents, or the method name and the 37 | # class to build from the attribute 38 | # @return [nil] 39 | def add_attribute name, jira_name, transformer 40 | attr_accessor name 41 | alias_method "#{name}?", name if transformer == :to_boolean 42 | @parse[jira_name] = [:"#{name}=", *transformer] 43 | end 44 | 45 | end 46 | 47 | @parse = {} # needs to be initialized 48 | 49 | # @param [Handsoap::XmlQueryFront::NokogiriDriver] frag 50 | # @return [JIRA::Entity] 51 | def self.new_with_xml frag 52 | entity = allocate 53 | entity.initialize_with_xml frag 54 | entity 55 | end 56 | 57 | # @param [Nokogiri::XML::Element] element 58 | def initialize_with_xml element 59 | attributes = self.class.parse 60 | element.children.each { |node| 61 | action = attributes[node.name] 62 | self.send action[0], (node.send *action[1..-1]) if action 63 | #puts "Action is #{action.inspect} for #{node.node_name}" 64 | } 65 | end 66 | 67 | end 68 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/field.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Represents a field mapping. 3 | class JIRA::Field < JIRA::NamedEntity 4 | end 5 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/field_value.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # This class is a bit of a hack; it is really just a key-value pair and only 3 | # used with {RemoteAPI#update_issue}. 4 | class JIRA::FieldValue 5 | 6 | ## 7 | # The name for regular fields, and the id for custom fields 8 | # 9 | # @return [String] 10 | attr_accessor :field_name 11 | 12 | ## 13 | # An array for the values, usually only has one object 14 | # 15 | # @return [Array<#to_s>] 16 | attr_accessor :values 17 | 18 | # @param [String] field_name 19 | # @param [Array] values 20 | def initialize field_name = nil, values = nil 21 | @field_name = field_name 22 | if values 23 | @values = values.is_a?( ::Array ) ? values : [values] 24 | end 25 | end 26 | 27 | ## 28 | # @todo soapify properly for custom objects (JIRA module). 29 | # 30 | # @param [Handsoap::XmlMason::Node] message the node to add the object to 31 | # @param [String] label name for the tags that wrap the message 32 | # @return [Handsoap::XmlMason::Element] 33 | def soapify_for message, label = 'fieldValue' 34 | message.add label do |message| 35 | message.add 'id', @field_name 36 | message.add_simple_array 'values', @values unless @values.nil? 37 | end 38 | end 39 | 40 | end 41 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/filter.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # @note You can only read filters from the server, there are no API methods 3 | # for creating, updating, or deleting filters from the server. 4 | # 5 | # Represents a filter, but does not seem to include the filters JQL query. 6 | class JIRA::Filter < JIRA::DescribedEntity 7 | 8 | # @return [String] 9 | add_attribute :author, 'author', :content 10 | 11 | # @return [String] 12 | add_attribute :project_name, 'project', :content 13 | 14 | ## 15 | # @todo Find out what this is for, perhaps it is the XML form of 16 | # equivalent JQL query? 17 | # 18 | # @return [String] 19 | add_attribute :xml, 'xml', :content 20 | 21 | end 22 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/issue.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # @note Issues with an UNRESOLVED status will have nil for the value for 3 | # {#resolution_id}. 4 | # @todo Add attributes for the comments and the attachment metadata 5 | # 6 | # Contains most of the data and metadata for a JIRA issue, but does 7 | # not contain the {JIRA::Comment}s or {JIRA::Attachment}s. 8 | # 9 | # This class is easily the most convoluted structure in the API, and will 10 | # likely be the greatest source of bugs. The irony of the situation is that 11 | # this structure is also the most critical to have in working order. 12 | class JIRA::Issue < JIRA::DynamicEntity 13 | 14 | # @return [String] 15 | add_attribute :key, 'key', :content 16 | 17 | # @return [String] 18 | add_attribute :summary, 'summary', :content 19 | 20 | # @return [String] 21 | add_attribute :description, 'description', :content 22 | 23 | # @return [String] 24 | add_attribute :type_id, 'type', :content 25 | 26 | # @return [String] 27 | add_attribute :status_id, 'status', :content 28 | 29 | # @return [String] 30 | add_attribute :assignee_username, 'assignee', :content 31 | 32 | # @return [String] 33 | add_attribute :reporter_username, 'reporter', :content 34 | 35 | # @return [String] 36 | add_attribute :priority_id, 'priority', :content 37 | 38 | # @return [String] 39 | add_attribute :project_name, 'project', :content 40 | 41 | # @return [String] 42 | add_attribute :resolution_id, 'resolution', :content 43 | 44 | # @return [String] 45 | add_attribute :environment, 'environment', :content 46 | 47 | # @return [Number] 48 | add_attribute :votes, 'votes', :to_i 49 | 50 | # @return [Time] 51 | add_attribute :last_updated_time, 'updated', :to_iso_date 52 | 53 | # @return [Time] 54 | add_attribute :create_time, 'created', :to_iso_date 55 | 56 | ## 57 | # This is actually a Time object with no time resolution. 58 | # 59 | # @return [Time] 60 | add_attribute :due_date, 'duedate', :to_iso_date 61 | 62 | # @return [Array] 63 | add_attribute :affects_versions, 'affectsVersions', [:children_as_objects, JIRA::Version] 64 | 65 | # @return [Array] 66 | add_attribute :fix_versions, 'fixVersions', [:children_as_objects, JIRA::Version] 67 | 68 | # @return [Array] 69 | add_attribute :components, 'components', [:children_as_objects, JIRA::Component] 70 | 71 | # @return [Array] 72 | add_attribute :custom_field_values, 'customFieldValues', [:children_as_objects, JIRA::CustomFieldValue] 73 | 74 | # @return [Array] 75 | add_attribute :attachment_names, 'attachmentNames', :contents_of_children 76 | 77 | ## 78 | # Get the value of a custom field given the custom field id, returns 79 | # nil if the issue does not have a value for that field. 80 | # 81 | # @param [String] id the custom field id (e.g. 'customfield_10150') 82 | # @return [JIRA::CustomFieldValue,nil] 83 | def custom_field id 84 | custom_field_values.find { |field_value| field_value.id == id } 85 | end 86 | 87 | ## 88 | # @todo see if we can use the simple and complex array builders 89 | # 90 | # Generate the SOAP message fragment for an issue. Can you spot the oddities 91 | # and inconsistencies? (hint: there are many). 92 | # 93 | # We don't bother including fields that are ignored. I tried to only 94 | # ignore fields that will never be needed at creation time, but I may have 95 | # messed up. 96 | # 97 | # We don't wrap the whole thing in 'issue' tags for 98 | # {#RemoteAPI#create_issue_with_issue} calls; this is an inconsistency in the 99 | # way jiraSOAP works and may need to be worked around for other {RemoteAPI} 100 | # methods. 101 | # 102 | # Servers only seem to accept issues if components/versions are just ids 103 | # and do not contain the rest of the {JIRA::Component}/{JIRA::Version} 104 | # structure. 105 | # 106 | # To get the automatic assignee we pass `'-1'` as the value for @assignee. 107 | # 108 | # Passing an environment/due date field with a value of nil causes the 109 | # server to complain about the formatting of the message. 110 | # 111 | # @param [Handsoap::XmlMason::Node] msg message the node to add the object to 112 | def soapify_for msg 113 | msg.add 'priority', @priority_id 114 | msg.add 'type', @type_id 115 | msg.add 'project', @project_name 116 | 117 | msg.add 'summary', @summary 118 | msg.add 'description', @description 119 | 120 | msg.add 'components' do |submsg| 121 | (@components || []).each { |component| 122 | submsg.add 'components' do |component_msg| 123 | component_msg.add 'id', component.id 124 | end 125 | } 126 | end 127 | msg.add 'affectsVersions' do |submsg| 128 | (@affects_versions || []).each { |version| 129 | submsg.add 'affectsVersions' do |version_msg| 130 | version_msg.add 'id', version.id 131 | end 132 | } 133 | end 134 | msg.add 'fixVersions' do |submsg| 135 | (@fix_versions || []).each { |version| 136 | submsg.add 'fixVersions' do |version_msg| 137 | version_msg.add 'id', version.id end 138 | } 139 | end 140 | 141 | msg.add 'reporter', @reporter_username unless @reporter_username.nil? 142 | msg.add 'assignee', (@assignee_username || '-1') 143 | msg.add_complex_array 'customFieldValues', (@custom_field_values || []) 144 | 145 | msg.add 'environment', @environment unless @environment.nil? 146 | msg.add 'duedate', @due_date.xmlschema unless @due_date.nil? 147 | end 148 | 149 | end 150 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/issue_property.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # @abstract A common base for most issue properties; core issue properties 3 | # also have an icon. 4 | class JIRA::IssueProperty < JIRA::DescribedEntity 5 | 6 | ## 7 | # NSURL on MacRuby, URI::HTTP on CRuby 8 | # 9 | # @return [URI::HTTP,NSURL] 10 | add_attribute :icon, 'icon', :to_url 11 | 12 | end 13 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/issue_security_scheme.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Basic metadata about a project's issue security scheme. 3 | class JIRA::IssueSecurityScheme < JIRA::Scheme 4 | end 5 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/issue_type.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Contains all the metadata for an issue type. 3 | class JIRA::IssueType < JIRA::IssueProperty 4 | 5 | ## 6 | # True if the issue type is also a subtask 7 | # 8 | # @return [Boolean] 9 | add_attribute :subtask, 'subTask', :to_boolean 10 | alias_method :sub_task, :subtask 11 | alias_method :sub_task=, :subtask= 12 | alias_method :sub_task?, :subtask 13 | 14 | end 15 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/named_entity.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # @abstract Most dynamic entities also have a name 3 | class JIRA::NamedEntity < JIRA::DynamicEntity 4 | 5 | ## 6 | # A plain language name 7 | # 8 | # @return [String] 9 | add_attribute :name, 'name', :content 10 | 11 | end 12 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/notification_scheme.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Basic metadata about a project's notification scheme. 3 | class JIRA::NotificationScheme < JIRA::Scheme 4 | end 5 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/permission.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # A permission id and the username that it is tied to. 3 | class JIRA::Permission < JIRA::Entity 4 | 5 | ## 6 | # The permission type 7 | # 8 | # @return [String] 9 | add_attribute :name, 'name', :content 10 | 11 | ## 12 | # A unique id number 13 | # 14 | # @return [Number] 15 | add_attribute :permission, 'permission', :to_i 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/permission_mapping.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # @todo Make sure the xml names are correct (check the XML dump) 3 | class JIRA::PermissionMapping < JIRA::Entity 4 | 5 | # @return [Array] 6 | add_attribute :permission, 'permission', [:children_as_object, JIRA::Permission] 7 | 8 | # @return [Array] 9 | add_attribute :users, 'entities', [:children_as_objects, JIRA::UserName] 10 | 11 | end 12 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/permission_scheme.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # @todo Complete this class 3 | # 4 | # Includes a mapping of project specific permission settings. 5 | class JIRA::PermissionScheme < JIRA::Scheme 6 | 7 | # @return [Array] 8 | add_attribute :permission_mappings, 'permissionMappings', [:children_as_objects, JIRA::PermissionMapping] 9 | 10 | end 11 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/priority.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Contains all the metadata for a priority level. 3 | class JIRA::Priority < JIRA::IssueProperty 4 | 5 | ## 6 | # The RGB components as a triple 7 | # 8 | # @return [Array(String,String,String)] 9 | add_attribute :color, 'color', :to_colour_triple 10 | alias_method :colour, :color 11 | alias_method :colour=, :color= 12 | 13 | end 14 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/project.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Contains the data and metadata about a project and its configuration. 3 | class JIRA::Project < JIRA::DescribedEntity 4 | 5 | # @return [String] 6 | add_attribute :key, 'key', :content 7 | 8 | # @return [String] 9 | add_attribute :lead_username, 'lead', :content 10 | 11 | # @return [String] 12 | add_attribute :issue_security_scheme, 'issueSecurityScheme', [:children_as_object, JIRA::IssueSecurityScheme] 13 | 14 | # @return [String] 15 | add_attribute :notification_scheme, 'notificationScheme', [:children_as_object, JIRA::NotificationScheme] 16 | 17 | # @return [String] 18 | add_attribute :permission_scheme, 'permissionScheme', [:children_as_object, JIRA::PermissionScheme] 19 | 20 | # @return [NSURL,URI::HTTP] 21 | add_attribute :jira_url, 'url', :to_url 22 | 23 | # @return [NSURL,URI::HTTP] 24 | add_attribute :project_url, 'projectUrl', :to_url 25 | 26 | 27 | ## 28 | # @todo Encode the schemes 29 | # 30 | # @param [Handsoap::XmlMason::Node] msg 31 | # @return [Handsoap::XmlMason::Node] 32 | def soapify_for msg 33 | msg.add 'id', @id 34 | msg.add 'name', @name 35 | msg.add 'key', @key 36 | msg.add 'url', @jira_url 37 | msg.add 'projectUrl', @project_url 38 | msg.add 'lead', @lead_username 39 | msg.add 'description', @description 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/project_role.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Only a name, description, and id. 3 | class JIRA::ProjectRole < JIRA::DescribedEntity 4 | 5 | # @param [Handsoap::XmlMason::Node] msg the node where to add self 6 | def soapify_for msg 7 | msg.add 'id', @id 8 | msg.add 'name', @name 9 | msg.add 'description', @description 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/resolution.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Contains all the metadata for a resolution. 3 | class JIRA::Resolution < JIRA::IssueProperty 4 | end 5 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/scheme.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # @abstract 3 | # 4 | # Schemes used by the server. 5 | class JIRA::Scheme < JIRA::DescribedEntity 6 | 7 | ## 8 | # Child classes need to be careful when encoding the scheme type to XML. 9 | # 10 | # @return [Class] 11 | alias_method :type, :class 12 | 13 | end 14 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/server_configuration.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # A simple structure that is used by {RemoteAPI#server_configuration}. 3 | class JIRA::ServerConfiguration < JIRA::Entity 4 | 5 | # @return [Boolean] 6 | add_attribute :external_user_management_allowed, 'allowExternalUserManagement', :to_boolean 7 | 8 | # @return [Boolean] 9 | add_attribute :attachments_allowed, 'allowAttachments', :to_boolean 10 | 11 | # @return [Boolean] 12 | add_attribute :issue_linking_allowed, 'allowIssueLinking', :to_boolean 13 | 14 | # @return [Boolean] 15 | add_attribute :subtasks_allowed, 'allowSubTasks', :to_boolean 16 | 17 | # @return [Boolean] 18 | add_attribute :time_tracking_allowed, 'allowTimeTracking', :to_boolean 19 | 20 | # @return [Boolean] 21 | add_attribute :unassigned_issues_allowed, 'allowUnassignedIssues', :to_boolean 22 | 23 | # @return [Boolean] 24 | add_attribute :voting_allowed, 'allowVoting', :to_boolean 25 | 26 | # @return [Boolean] 27 | add_attribute :watching_allowed, 'allowWatching', :to_boolean 28 | 29 | # @return [Number] 30 | add_attribute :time_tracking_days_per_week, 'timeTrackingDaysPerWeek', :to_i 31 | 32 | # @return [Number] 33 | add_attribute :time_tracking_hours_per_day, 'timeTrackingHoursPerDay', :to_i 34 | 35 | ## 36 | # @note JIRA misspells 'management', so we define this attribute twice 37 | # to cover both cases. 38 | # 39 | # @return [Boolean] 40 | add_attribute :external_user_management_allowed, 'allowExternalUserManagment', :to_boolean 41 | 42 | end 43 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/server_info.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Only contains basic information about the endpoint server and only used 3 | # by {RemoteAPI#server_info}. 4 | class JIRA::ServerInfo < JIRA::Entity 5 | 6 | # @return [NSURL,URI::HTTP] 7 | add_attribute :base_url, 'baseUrl', :to_url 8 | 9 | # @return [Time] 10 | add_attribute :build_date, 'buildDate', :to_iso_date 11 | 12 | # @return [Number] 13 | add_attribute :build_number, 'buildNumber', :to_i 14 | 15 | # @return [String] 16 | add_attribute :edition, 'edition', :content 17 | 18 | # @return [String] 19 | add_attribute :version, 'version', :content 20 | 21 | # @return [Array] 22 | add_attribute :server_time, 'serverTime', [:children_as_object, JIRA::TimeInfo] 23 | 24 | end 25 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/status.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Contains all the metadata for an issue's status. 3 | class JIRA::Status < JIRA::IssueProperty 4 | end 5 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/time_info.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Simple structure for a time and time zone; only used by JIRA::ServerInfo 3 | # objects, which themselves are only created when {RemoteAPI#server_info} 4 | # is called. 5 | class JIRA::TimeInfo < JIRA::Entity 6 | 7 | # @return [Time] 8 | add_attribute :server_time, 'serverTime', :to_natural_date 9 | 10 | ## 11 | # @example 12 | # 'America/Toronto' 13 | # 14 | # @return [String] 15 | add_attribute :timezone, 'timeZoneId', :content 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/user.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Contains only the basic information about a user. The only things missing here 3 | # are the permissions and login statistics, but these are not given in the API. 4 | class JIRA::User < JIRA::UserName 5 | 6 | # @return [String] 7 | add_attribute :full_name, 'fullname', :content 8 | 9 | # @return [String] 10 | add_attribute :email_address, 'email', :content 11 | 12 | end 13 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/usergroup.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Though this class contains a name, it is not the same name that 3 | # you get from a {JIRA::NamedEntity}. 4 | class JIRA::UserGroup < JIRA::Entity 5 | 6 | # @return [String] 7 | add_attribute :name, 'name', :content 8 | 9 | ## 10 | # @todo I suspect that I will have to delete users from SOAPifying 11 | # 12 | # @return [Array] 13 | add_attribute :users, 'users', [:children_as_objects, JIRA::User] 14 | 15 | end 16 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/username.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # @todo Find out if this is an abstract class. 3 | # 4 | # This is just a @name, JIRA::User should inherit from this class 5 | class JIRA::UserName < JIRA::Entity 6 | 7 | # @return [String] 8 | add_attribute :username, 'name', :content 9 | alias_method :name, :username 10 | alias_method :name=, :username= 11 | 12 | end 13 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/version.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # @todo Find out why we don't get a description for this object 3 | # 4 | # Represents a version for a project. The description field is never 5 | # included when you retrieve versions from the server. 6 | class JIRA::Version < JIRA::NamedEntity 7 | 8 | # @return [Number] 9 | add_attribute :sequence, 'sequence', :to_i 10 | 11 | # @return [Boolean] 12 | add_attribute :released, 'released', :to_boolean 13 | 14 | # @return [Boolean] 15 | add_attribute :archived, 'archived', :to_boolean 16 | 17 | # @return [Time] 18 | add_attribute :release_date, 'releaseDate', :to_iso_date 19 | 20 | # @param [Handsoap::XmlMason::Node] msg 21 | # @return [Handsoap::XmlMason::Node] 22 | def soapify_for msg 23 | msg.add 'name', @name 24 | msg.add 'archived', @archived if @archived 25 | msg.add 'sequence', @sequence if @sequence 26 | msg.add 'releaseDate', @release_date.xmlschema if @release_date 27 | msg.add 'released', @released if @released 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /lib/jiraSOAP/entities/worklog.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Contains the data and metadata for a remote worklog. 3 | class JIRA::Worklog < JIRA::DescribedEntity 4 | 5 | # @return [String] 6 | add_attribute :comment, 'comment', :content 7 | 8 | # @return [Time] 9 | add_attribute :start_date, 'startDate', :to_iso_date 10 | 11 | # @return [String] 12 | add_attribute :time_spent, 'timeSpent', :content 13 | 14 | # @param [Handsoap::XmlMason::Node] msg 15 | # @return [Handsoap::XmlMason::Node] 16 | def soapify_for msg 17 | msg.add 'comment', @comment 18 | msg.add 'startDate', @start_date 19 | msg.add 'timeSpent', @time_spent 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /lib/jiraSOAP/handsoap_extensions.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # @todo Push these upstream? 3 | # 4 | # Some simple extensions to Handsoap to make building SOAP messages easier. 5 | class Handsoap::XmlMason::Node 6 | ## 7 | # @todo Make this method recursive 8 | # 9 | # @param [String] node_name 10 | # @param [Array] array 11 | # @param [Hash] options 12 | def add_simple_array node_name, array = [], options = {} 13 | prefix, name = parse_ns(node_name) 14 | node = append_child Handsoap::XmlMason::Element.new(self, prefix, name, nil, options) 15 | array.each { |element| node.add node_name, element } 16 | end 17 | 18 | ## 19 | # @todo Make this method recursive 20 | # 21 | # @param [String] node_name 22 | # @param [Array] array 23 | # @param [Hash] options 24 | def add_complex_array node_name, array = [], options = {} 25 | prefix, name = parse_ns(node_name) 26 | node = append_child Handsoap::XmlMason::Element.new(self, prefix, name, nil, options) 27 | array.each { |element| element.soapify_for node, name } 28 | end 29 | end 30 | 31 | ## 32 | # Monkey patch to expose the underlying Nokogiri object as jiraSOAP 33 | # can parse much faster without the Handsoap layer in between. 34 | class Handsoap::XmlQueryFront::NokogiriDriver 35 | # @return [Nokogiri::XML::Element] 36 | attr_reader :element 37 | end 38 | -------------------------------------------------------------------------------- /lib/jiraSOAP/jira_service.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # @note HTTPS is not supported out of the box in this version. 3 | # 4 | # Interface to the JIRA endpoint server. 5 | # 6 | # Due to limitations in Handsoap::Service, there can only be one endpoint. 7 | # You can have multiple instances of that one endpoint if you would 8 | # like (different users); but if you try to set a differnt endpoint for a 9 | # new instance you will end up messing up any other instances currently 10 | # being used. 11 | class JIRA::JIRAService < Handsoap::Service 12 | include JIRA::RemoteAPI 13 | include JIRA::RemoteAPIAdditions 14 | 15 | # @return [String] 16 | attr_reader :auth_token 17 | 18 | # @return [String] 19 | attr_reader :user 20 | 21 | # @return [String] 22 | attr_reader :endpoint_url 23 | 24 | class << self 25 | ## 26 | # Expose endpoint URL 27 | # 28 | # @return [String] 29 | def endpoint_url 30 | @@endpoint_url 31 | end 32 | end 33 | 34 | ## 35 | # Initialize _and_ log in. Fancy. 36 | # 37 | # @param [String,URI::HTTP,NSURL] url URL for the JIRA server 38 | # @param [String] user JIRA user name to login with 39 | # @param [String] password 40 | # @return [JIRA::JIRAService] 41 | def self.instance_with_endpoint url, user, password 42 | jira = JIRA::JIRAService.new url 43 | jira.login user, password 44 | jira 45 | end 46 | 47 | ## 48 | # Create an instance with an existing authorization token. In cases 49 | # where you have cached the auth token elsewhere, you can avoid having 50 | # to login again by initializing the instance with the token. 51 | # 52 | # @param [String,URI::HTTP,NSURL] url 53 | # @param [String] user 54 | # @param [String] token 55 | # @return [JIRA::JIRAService] 56 | def self.instance_with_token url, user, token 57 | obj = allocate 58 | obj.initialize_with_token url, user, token 59 | obj 60 | end 61 | 62 | # @param [String,URI::HTTP,NSURL] endpoint for the JIRA server 63 | def initialize endpoint 64 | @@endpoint_url = @endpoint_url = endpoint.to_s 65 | self.class.endpoint({ 66 | :uri => "#{endpoint_url}/rpc/soap/jirasoapservice-v2", 67 | :version => 2 68 | }) 69 | end 70 | 71 | ## 72 | # Special constructor meant for 73 | # 74 | # @param [String,URI::HTTP,NSURL] endpoint 75 | # @param [String] user 76 | # @param [String] token 77 | def initialize_with_token endpoint, user, token 78 | initialize endpoint 79 | @user = user 80 | @auth_token = token 81 | end 82 | 83 | 84 | protected 85 | 86 | ## 87 | # Makes sure the correct namespace is set 88 | def on_create_document doc 89 | doc.alias 'soap', 'http://soap.rpc.jira.atlassian.com' 90 | end 91 | 92 | ## 93 | # Make sure that the required namespace is added 94 | def on_response_document doc 95 | doc.add_namespace 'jir', @endpoint_url 96 | end 97 | 98 | end 99 | -------------------------------------------------------------------------------- /lib/jiraSOAP/macruby_extensions.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # In the case of MacRuby, we extend NSURL to behave enough like a 3 | # URI::HTTP object that they can be interchanged. 4 | class NSURL 5 | alias_method :to_s, :absoluteString 6 | alias_method :inspect, :absoluteString 7 | end 8 | 9 | module JIRA 10 | @url_class = NSURL 11 | @url_init_method = :'URLWithString:' 12 | end 13 | 14 | # @todo get a parallel map method for collections 15 | -------------------------------------------------------------------------------- /lib/jiraSOAP/nokogiri_extensions.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Monkey (Freedom) patches to Nokogiri 3 | class Nokogiri::XML::Element 4 | 5 | # @return [Fixnum] 6 | def to_i 7 | content.to_i 8 | end 9 | 10 | # @return [Time,nil] 11 | def to_iso_date 12 | return if (temp = content).empty? 13 | Time.iso8601 temp 14 | end 15 | 16 | ## 17 | # Parses non-strict date strings into Time objects. 18 | # 19 | # @return [Time,nil] 20 | def to_natural_date 21 | return if (temp = content).empty? 22 | Time.new temp 23 | end 24 | 25 | ## 26 | # We assume that the boolean is encoded as as string, because that 27 | # how JIRA is doing right now, but the XML schema allows a boolean 28 | # to be encoded as 0/1 numbers instead. 29 | # 30 | # @return [Boolean] 31 | def to_boolean 32 | content == 'true' # || content == 1 33 | end 34 | 35 | # @return [URI::HTTP,NSURL,nil] 36 | def to_url 37 | return if (temp = content).empty? 38 | JIRA.url_class.send JIRA.url_init_method, temp 39 | end 40 | 41 | ## 42 | # This is a bit naive, but should be sufficient for its purpose. 43 | # 44 | # @return [Array(String,String,String),nil] 45 | def to_color_triple 46 | return if (temp = content).empty? 47 | temp.match(/#(..)(..)(..)/).captures 48 | end 49 | alias_method :to_colour_triple, :to_color_triple 50 | 51 | ## 52 | # Ideally this method will return an array of strings, but this 53 | # may not always be the case. 54 | def contents_of_children 55 | children.map { |val| val.content } 56 | end 57 | 58 | # @param [Class] klass the JIRA object you want to make 59 | def children_as_object klass 60 | klass.new_with_xml self 61 | end 62 | 63 | # @param [Class] klass the object you want an array of 64 | # @return [Array] an array of klass objects 65 | def children_as_objects klass 66 | children.map { |node| klass.new_with_xml node } 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/jiraSOAP/url.rb: -------------------------------------------------------------------------------- 1 | module JIRA 2 | 3 | class << self 4 | 5 | ## 6 | # When running on MacRuby, a URL will be wrapped into an NSURL object; 7 | # but on all other Ruby implementations you will get a URI::HTTP object. 8 | # The NSURL class is monkey patched just enough to make NSURL and 9 | # URI::HTTP interchangeable. If you really want to, you can override 10 | # the wrapper by changing {JIRA.url_class} and {JIRA.url_init_method} 11 | # so that: 12 | # 13 | # JIRA.url_class.send JIRA.url_init_method, 'http://marketcircle.com' 14 | # 15 | # will be working code. 16 | # 17 | # @return [Class,Module] 18 | attr_accessor :url_class 19 | 20 | ## 21 | # We also need a variable for the init method for a URL object 22 | # 23 | # @return [Symbol] 24 | attr_accessor :url_init_method 25 | 26 | end 27 | 28 | @url_class = URI 29 | @url_init_method = :parse 30 | 31 | end 32 | -------------------------------------------------------------------------------- /lib/jiraSOAP/version.rb: -------------------------------------------------------------------------------- 1 | module JIRA 2 | # @return [String] 3 | VERSION = '0.10.9' 4 | end 5 | -------------------------------------------------------------------------------- /test/attachments_test.rb: -------------------------------------------------------------------------------- 1 | require 'base64' 2 | 3 | # @note These tests will bloat up your server after a while 4 | class TestAddAttachmentsToIssueWithKey < MiniTest::Unit::TestCase 5 | setup_usual 6 | 7 | def key 8 | 'JIRA-1' 9 | end 10 | 11 | def image1 12 | @image1 ||= "switcheroo-#{Time.now.to_f}.rb" 13 | end 14 | 15 | def image2 16 | @image2 ||= "colemak-#{Time.now.to_f}.png" 17 | end 18 | 19 | def attachment1 20 | @attachment ||= JIRA::Attachment.new.tap do |x| 21 | x.content = File.read(File.join(File.dirname(__FILE__), '..', 'Rakefile')) 22 | x.file_name = "Rakefile-#{Time.now.to_f}" 23 | end 24 | end 25 | 26 | def attachment2 27 | @attachment ||= JIRA::Attachment.new.tap do |x| 28 | x.content = File.read(File.join(File.dirname(__FILE__), '..', 'jiraSOAP.gemspec')) 29 | x.file_name = "Rakefile-#{Time.now.to_f}" 30 | end 31 | end 32 | 33 | def test_returns_true 34 | assert_equal true, 35 | db.add_attachments_to_issue_with_key(key, attachment1) 36 | end 37 | 38 | def test_attachment_data_is_unaltered # for some definition of unaltered 39 | skip 'Need to implement the method for getting attachments first' 40 | end 41 | 42 | def test_add_one_attachment 43 | db.add_attachments_to_issue_with_key key, attachment1 44 | issue = db.issue_with_key key 45 | refute_nil issue.attachment_names.find { |x| x == attachment1.file_name } 46 | end 47 | 48 | def test_add_two_attachments 49 | db.add_attachments_to_issue_with_key key, attachment1, attachment2 50 | issue = db.issue_with_key key 51 | refute_nil issue.attachment_names.find { |x| x == attachment1.file_name } 52 | refute_nil issue.attachment_names.find { |x| x == attachment2.file_name } 53 | end 54 | 55 | def test_returns_true_base64 56 | assert_equal true, 57 | db.add_base64_encoded_attachments_to_issue_with_key(key, 58 | [image1], 59 | [switcheroo]) 60 | end 61 | 62 | def test_add_one_attachment_base64 63 | db.add_base64_encoded_attachments_to_issue_with_key(key, 64 | [image1], 65 | [switcheroo]) 66 | issue = db.issue_with_key key 67 | refute_nil issue.attachment_names.find { |x| x == image1 } 68 | end 69 | 70 | def test_add_two_attachments_base64 71 | db.add_base64_encoded_attachments_to_issue_with_key(key, 72 | [image1, image2], 73 | [switcheroo, colemak]) 74 | issue = db.issue_with_key key 75 | refute_nil issue.attachment_names.find { |x| x == image1 } 76 | refute_nil issue.attachment_names.find { |x| x == image2 } 77 | end 78 | 79 | def switcheroo 80 | <<-EOF 81 | IyEvdXNyL2Jpbi9lbnYgbWFjcnVieQoKZmlsZSA9IElPLnJlYWQoJ0xvY2FsaXphYmxlLnR4dCcp 82 | LnNwbGl0KC9cbi8pLnNlbGVjdCB7IHxsaW5lfCAhbGluZS5tYXRjaCgvOyQvKS5uaWw/IH0uZWFj 83 | aCB7IHxsaW5lfAogIG1hZ2ljID0gbGluZS5tYXRjaCAvIiguKykiXHMrPVxzKyIoLispIjsvdQoK 84 | ICBwdXRzIDw8RU9GCiAgICAgICA8ZGljdD4KICAgICAgICAgICAgICAgPGtleT5DcmVhdGVEYXRl 85 | PC9rZXk+CiAgICAgICAgICAgICAgIDxyZWFsPjEuMDwvcmVhbD4KICAgICAgICAgICAgICAgPGtl 86 | eT5LZXk8L2tleT4KICAgICAgICAgICAgICAgPHN0cmluZz4jeyQxfTwvc3RyaW5nPgogICAgICAg 87 | ICAgICAgICA8a2V5PlZhbHVlPC9rZXk+CiAgICAgICAgICAgICAgIDxzdHJpbmc+I3skMn08L3N0 88 | cmluZz4KICAgICAgIDwvZGljdD4KRU9GCn0K 89 | EOF 90 | end 91 | 92 | def colemak 93 | <<-EOF 94 | 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 199 | QyL2DYZVAAAAAElFTkSuQmCC 200 | EOF 201 | end 202 | 203 | end 204 | -------------------------------------------------------------------------------- /test/custom_field_values_test.rb: -------------------------------------------------------------------------------- 1 | class TestCustomFieldValues < MiniTest::Unit::TestCase 2 | 3 | def test_can_initialize_empty 4 | cv = JIRA::CustomFieldValue.new 5 | assert_nil cv.id 6 | assert_empty cv.values 7 | end 8 | 9 | def test_can_initailize_with_an_id 10 | id = 'thingamajig' 11 | cv = JIRA::CustomFieldValue.new id 12 | assert_equal id, cv.id 13 | assert_empty cv.values 14 | end 15 | 16 | def test_can_initialize_with_everthing 17 | id = 42 18 | values = 45 19 | cv = JIRA::CustomFieldValue.new id, values 20 | assert_equal id, cv.id 21 | assert_empty Array(values), cv.values 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'jiraSOAP' 2 | 3 | require 'rubygems' 4 | gem 'minitest', '~> 2.5' 5 | require 'minitest/autorun' 6 | require 'minitest/pride' 7 | 8 | class MiniTest::Unit::TestCase 9 | 10 | def user 11 | 'marada' 12 | end 13 | 14 | def password 15 | 'test' 16 | end 17 | 18 | def host 19 | 'http://169.254.199.62:8080' 20 | end 21 | 22 | def db 23 | @db ||= JIRA::JIRAService.new host 24 | end 25 | 26 | def self.setup_usual 27 | define_method :setup do 28 | db.login user, password 29 | end 30 | end 31 | 32 | def teardown 33 | db.logout 34 | end 35 | 36 | def assert_instance_of_boolean value, msg = nil 37 | if value == true || value == false 38 | assert true 39 | else 40 | msg = "Expected #{mu_pp(value)} to be a boolean" unless msg 41 | assert false, msg 42 | end 43 | end 44 | 45 | end 46 | 47 | 48 | offline = ['worklog_test', 'service_test'] 49 | basic = ['login_test', 'logout_test', 'custom_field_values_test', 'worklog_test'] 50 | create = ['attachments_test'] 51 | read = basic + [] 52 | update = [] 53 | delete = [] 54 | all = basic + create + read + update + delete + offline 55 | 56 | # Look at environment variables to decide which tests to run 57 | tests = case ENV['JIRASOAP'] 58 | when 'all' then all 59 | when 'basic' then basic 60 | when 'create' then create 61 | when 'read' then read 62 | when 'update' then update 63 | when 'delete' then delete 64 | when 'offline' then offline 65 | else read # hmm, for safeties sake, but maybe it should be all... 66 | end 67 | tests.each do |test| require test end 68 | -------------------------------------------------------------------------------- /test/login_test.rb: -------------------------------------------------------------------------------- 1 | class TestLogin < MiniTest::Unit::TestCase 2 | 3 | def test_returns_token 4 | assert_match /^[a-zA-Z0-9]+$/, db.login(user, password) 5 | end 6 | 7 | def test_caches_the_token 8 | db.login(user, password) 9 | assert_match /^[a-zA-Z0-9]+$/, db.auth_token 10 | end 11 | 12 | def test_caches_the_user 13 | db.login(user, password) 14 | assert_equal user, db.instance_variable_get(:@user) 15 | end 16 | 17 | def test_aliased 18 | assert_equal db.method(:login), db.method(:log_in) 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /test/logout_test.rb: -------------------------------------------------------------------------------- 1 | class TestLogout < MiniTest::Unit::TestCase 2 | setup_usual 3 | 4 | def test_normal 5 | db.login user, password 6 | assert db.logout 7 | end 8 | 9 | def test_no_session 10 | # strongly assert is false, not nil 11 | db.logout 12 | assert_equal false, db.logout 13 | end 14 | 15 | def test_unsets_cached_attributes 16 | db.login user, password 17 | db.logout 18 | assert_nil db.instance_variable_get(:@user) 19 | assert_nil db.auth_token 20 | end 21 | 22 | def test_aliased 23 | assert_equal db.method(:logout), db.method(:log_out) 24 | end 25 | 26 | # override since we logout during the test 27 | def teardown 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /test/service_test.rb: -------------------------------------------------------------------------------- 1 | class TestJIRAService < MiniTest::Unit::TestCase 2 | 3 | def setup; end 4 | def teardown; end 5 | 6 | def test_token_constructor 7 | inst = JIRA::JIRAService.instance_with_token 'url', 'user', 'token' 8 | assert_equal 'url', inst.endpoint_url 9 | assert_equal 'user', inst.user 10 | assert_equal 'token', inst.auth_token 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /test/worklog_test.rb: -------------------------------------------------------------------------------- 1 | class TestWorklog < MiniTest::Unit::TestCase 2 | 3 | def setup; end 4 | def teardown; end 5 | 6 | def test_no_typos 7 | w = JIRA::Worklog.new 8 | 9 | assert_respond_to w, :comment 10 | assert_respond_to w, :start_date 11 | assert_respond_to w, :time_spent 12 | end 13 | 14 | end 15 | --------------------------------------------------------------------------------