JIRA-Client-0.43000755004231004231 012463542712 13744 5ustar00gustavogustavo000000000000TODO100644004231004231 50512463542712 14475 0ustar00gustavogustavo000000000000JIRA-Client-0.43- Implement some non-API methods from http://code.google.com/p/jira4r/source/browse/trunk/lib/jira4r/jira_tool.rb. - Implement a CLI tool like http://confluence.atlassian.com/display/JIRAEXT/JIRA+Command+Line+Interface or http://confluence.atlassian.com/display/JIRAEXT/JIRA+CLI as a proof-of-concept usage example. README100644004231004231 113112463542712 14701 0ustar00gustavogustavo000000000000JIRA-Client-0.43JIRA::Client - (DEPRECATED) Extended interface to JIRA's SOAP API DEPRECATION WARNING: Please, before using this module consider using the newer JIRA::REST because JIRA's SOAP API was deprecated on JIRA 6.0 and won't be available anymore on JIRA 8.0. JIRA is a proprietary bug tracking system from Atlassian (http://www.atlassian.com/software/jira/). Copyright (c) 2012-2015 by CPqD (http://www.cpqd.com.br/) This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Home page: http://search.cpan.org/dist/JIRA-Client/ Changes100644004231004231 1603712463542712 15347 0ustar00gustavogustavo000000000000JIRA-Client-0.43Revision history for JIRA-Client. 0.43 2015-02-01 22:33:09-02:00 America/Sao_Paulo [Changes] - Deprecate the distribution, because JIRA's SOAP API is already deprecated and won't be available in future JIRA 8.0. Prospect users are invited to try the JIRA::REST module instead. 0.42 2014-08-09 17:47:44 America/Sao_Paulo [Changes] - Add deprecation warning and reference to JIRA::REST. 0.41 2014-04-01 21:34:37 America/Sao_Paulo [New features] - New method filter_issues_unsorted. 0.40 2012-08-02 08:21:52 America/Sao_Paulo - Fix constructor URL munging by applying a patch from Slaven Rezić. Thanks again! 0.39 2012-07-31 09:25:36 America/Sao_Paulo - Revert the constructor change of 0.38 because it broke invokations when JIRA server's base URL had a path component. - Implemented an alternative invokation style based on named arguments passed via a hash-ref. This was suggested by Slaven Rezić. Thanks! 0.38 2012-07-13 19:50:24 America/Sao_Paulo - The constructor now admits non-base URLs to non-standard WSDL descriptors. This was suggested by Elena Bolshakova . Thanks! - Add examples/jira-filter.pl. - Detects some weird connection errors. 0.37 2012-05-01 18:48:03 America/Sao_Paulo - Use Data::Util to make for cleaner code. 0.36 2012-04-21 20:55:58 America/Sao_Paulo - Moved from Google Code to GitHub. 0.35 2012-04-12 13:14:13 America/Sao_Paulo - Add method filter_issues. 0.34 2012-02-22 - Distribution converted to use Dist::Zilla. Online tests are now driven by a configuration file. 0.33 2012-02-20 - Make create_issue correctly support sub-task issue types. This was suggested by Stephen Marquard via https://rt.cpan.org/Ticket/Display.html?id=75157. 0.32 2011-12-05 - [rt72470] Fix create_issue and progress_workflow_action_safely parameter conversions. - [rt72667] Pass project key to get_security_levels. 0.31 2011-11-19 - Accept both names for JIRA fields that must be named one way to be set and are named otherwise when read. This is due to https://jira.atlassian.com/browse/JRA-12300 and was reported by Dan Baber via https://rt.cpan.org/Ticket/Display.html?id=72470. 0.30 2011-09-26 - Cast duedate's field to a "date" SOAP::Data type. This is required by JIRA 4.4. 0.29 2011-09-13 - Support new methods in the JIRA 4.4 SOAP API. 0.28 2011-05-09 - JIRA::Client->new passes extra arguments to the underlying SOAP::Lite object. This was suggested by Nicholas J Humfrey . 0.27 2011-03-05 - Properly casts RemoteProjectRole objects in several methods. This was suggested by Philippe MARTIN. 0.26 2010-12-21 - Implements some magic to make it easier to specify Cascading field values in the create_issue and update_issue methods. This was suggested by Keith Hackworth. 0.25 2010-09-11 - Implements method get_statuses, courtesy of michael AT riceclan.org. - Fixes a problem in _convert_resolution, courtesy of KBeal AT crosscountry-auto.com. - Fixes a typo in examples/jiraclient.pl, courtesy of cosimo AT cpan.org. 0.24 2009-12-24 - Corrects one typo suggested by Andrey Belous and denies the attachment of empty files, as suggested by Jon Connell. 0.23 2009-12-12 - Corrects the SOAP serialization of arrays to enable the use of the add*AttachmentsToIssue API methods. Thanks to Jon Connell who reported the problem. - Implements the methods attach_files_to_issue and attach_strings_to_issue to make it easier to attach things. - The method create_issue now accepts an optional Security Level argument. Thanks to Lance Selvidge for the suggestion. 0.22 2009-11-30 - Corrects a bug [http://rt.cpan.org/Ticket/Display.html?id=52076] that affected the methods progressWorkflowAction and updateIssue. 0.21 2009-11-21 - Adds update_issue method. - Adds lots of implicit conversions and document them better. - Can set duedate field with a DateTime object. - Adds a examples directory with a single example so far. - Adds resolution conversion from names. 0.20 2009-11-20 - Converts the 'duedate' field from the ISO format (YYYY-MM-DD) into the JIRA required format (d/MMM/yy) in create_issue and progress_workflow_action_safely. This is necessary because while JIRA requires the later, it gives the former in getIssue. Thanks to Andrey Belous for alerting me about this. - Adds a perlcritic test and placates some of its criticisms. 0.19 2009-11-06 - Updates the default conversions for the new JIRA 4.0 methods. Thanks to Mário Moreira for alerting me about this. 0.18 2009-10-24 - Adding kwalitee test. (See http://cpants.perl.org/dist/overview/JIRA-Client) 0.17 2009-10-18 - Implements the method get_issue_custom_field_values, to more easily grok the custom field values from an issue. - Refactors some code in a bunch of helper functions. - Implements many more online tests. There are 39 now. - Makes the POD tests disabled by default. They are meant to be used by the author only. 0.16 2009-10-04 - Implements the method get_favourite_filters, which caches the user's favourite filters. - Casts automatically filter names into filter id in the arguments for getIssueCountForFilter, getIssuesFromFilter, and getIssuesFromFilterWithLimit. - These changes were inspired by Andrew Grangaard's example in http://www.lowlevelmanager.com/2009/09/access-jira-api-from-perl-with.html. Thanks! 0.15 2009-09-28 - The method progress_workflow_action_safely hash argument now accepts the same shortcuts as the create_issue argument does. - Nate Murray and Jon Connell sent me bug reports and suggestions. Thanks. 0.14 2009-09-12 - Supporting versions of JIRA older than 3.14.4 in the method next_issue. - Added an optional argument to the method set_issue_iterator to allow for the specification of the pre-fetching cache size. 0.13 2009-08-04 - Make it easy to set custom fields in create_issue. 0.12 2009-08-02 - Avoids spurious messages during destruction with a kludge. 0.11 2009-07-13 - set_filter_iterator accepts filter ids or filter names. 0.10 2009-07-11 - Implements methods create_issue and progress_workflow_action_safelly using ideas from Jon Connel . - Makes it easier to call some methods by accepting simpler arguments using an idea from Bjørn-Olav Strand . - Implements constructors for some helper objects. - Implements online tests agains a JIRA server. 0.05 2009-05-24 - Implement methods get_priorities, get_versions, get_custom_fields, and set_custom_fields to maintain a cache of this information. - Eliminates the method custom_field_map, which is superseeded by get/set_custom_fields. 0.04 2009-05-05 - Implement coersions for structured arguments. 0.03 2009-04-25 - First version, released on an unsuspecting world. LICENSE100644004231004231 4362512463542712 15064 0ustar00gustavogustavo000000000000JIRA-Client-0.43This software is copyright (c) 2015 by CPqD. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Terms of the Perl programming language system itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" --- The GNU General Public License, Version 1, February 1989 --- This software is Copyright (c) 2015 by CPqD. This is free software, licensed under: The GNU General Public License, Version 1, February 1989 GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Artistic License 1.0 --- This software is Copyright (c) 2015 by CPqD. This is free software, licensed under: The Artistic License 1.0 The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End dist.ini100644004231004231 72712463542712 15457 0ustar00gustavogustavo000000000000JIRA-Client-0.43; See this to understand: http://dzil.org/tutorial/convert-dist.html name = JIRA-Client author = Gustavo L. de M. Chaves license = Perl_5 copyright_holder = CPqD [Deprecated] [GatherDir] exclude_match = ~$ [@Filter] -bundle = @Basic -remove = GatherDir -remove = Readme [NextRelease] [@Git] [AutoPrereqs] [PodWeaver] [Git::NextVersion] [PkgVersion] [PodSyntaxTests] [PodCoverageTests] [Test::Kwalitee] [GitHub::Update] [GitHub::Meta] META.yml100644004231004231 140412463542712 15275 0ustar00gustavogustavo000000000000JIRA-Client-0.43--- abstract: "(DEPRECATED) Extended interface to JIRA's SOAP API" author: - 'Gustavo L. de M. Chaves ' build_requires: Test::More: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'Dist::Zilla version 5.031, CPAN::Meta::Converter version 2.143240' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: JIRA-Client requires: Carp: '0' Data::Util: '0' File::Basename: '0' MIME::Base64: '0' SOAP::Lite: '0' strict: '0' warnings: '0' resources: bugtracker: https://github.com/gnustavo/JIRA-Client/issues homepage: http://search.cpan.org/dist/JIRA-Client/ repository: git://github.com/gnustavo/JIRA-Client.git version: '0.43' x_deprecated: 1 MANIFEST100644004231004231 56012463542712 15137 0ustar00gustavogustavo000000000000JIRA-Client-0.43# This file was automatically generated by Dist::Zilla::Plugin::Manifest v5.031. Changes LICENSE MANIFEST META.yml Makefile.PL README TODO dist.ini examples/jira-dump.pl examples/jira-filter.pl examples/jiraclient.pl lib/JIRA/Client.pm t/00-load.t t/02-demo-user.t t/demo-atlassian.conf t/release-kwalitee.t t/release-pod-coverage.t t/release-pod-syntax.t tidyall.ini tidyall.ini100644004231004231 10612463542712 16145 0ustar00gustavogustavo000000000000JIRA-Client-0.43[PerlCritic] select = lib/**/*.pm [PodChecker] select = lib/**/*.pm t000755004231004231 012463542712 14130 5ustar00gustavogustavo000000000000JIRA-Client-0.4300-load.t100755004231004231 25112463542712 15572 0ustar00gustavogustavo000000000000JIRA-Client-0.43/t# Hey Emacs, this is -*- perl -*- use Test::More tests => 1; BEGIN { use_ok( 'JIRA::Client' ); } diag( "Testing JIRA::Client $JIRA::Client::VERSION, Perl $], $^X" ); Makefile.PL100644004231004231 245612463542712 16006 0ustar00gustavogustavo000000000000JIRA-Client-0.43 # This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v5.031. use strict; use warnings; use ExtUtils::MakeMaker; my %WriteMakefileArgs = ( "ABSTRACT" => "(DEPRECATED) Extended interface to JIRA's SOAP API", "AUTHOR" => "Gustavo L. de M. Chaves ", "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => 0 }, "DISTNAME" => "JIRA-Client", "EXE_FILES" => [], "LICENSE" => "perl", "NAME" => "JIRA::Client", "PREREQ_PM" => { "Carp" => 0, "Data::Util" => 0, "File::Basename" => 0, "MIME::Base64" => 0, "SOAP::Lite" => 0, "strict" => 0, "warnings" => 0 }, "TEST_REQUIRES" => { "Test::More" => 0 }, "VERSION" => "0.43", "test" => { "TESTS" => "t/*.t" } ); my %FallbackPrereqs = ( "Carp" => 0, "Data::Util" => 0, "ExtUtils::MakeMaker" => 0, "File::Basename" => 0, "MIME::Base64" => 0, "SOAP::Lite" => 0, "Test::More" => 0, "strict" => 0, "warnings" => 0 ); unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { delete $WriteMakefileArgs{TEST_REQUIRES}; delete $WriteMakefileArgs{BUILD_REQUIRES}; $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; WriteMakefile(%WriteMakefileArgs); 02-demo-user.t100755004231004231 664212463542712 16607 0ustar00gustavogustavo000000000000JIRA-Client-0.43/t# Hey Emacs, this is -*- perl -*- use strict; use warnings; use Test::More; use JIRA::Client; my $democonf = $ENV{DEMOCONF} || 't/demo-atlassian.conf'; unless (defined $democonf) { plan skip_all => 'Demo tests are disabled.'; } unless (-r $democonf) { plan skip_all => "DEMOCONF ($democonf) does not exist or is unreadable."; } my $conf = do $democonf; unless ($conf) { plan skip_all => "couldn't parse $democonf: $@" if $@; plan skip_all => "couldn't do $democonf: $!" unless defined $conf; plan skip_all => "couldn't run $democonf" unless $conf; } plan skip_all => "$democonf does not return a hash-ref" unless ref $conf and ref $conf eq 'HASH'; plan skip_all => "$democonf does not define a password" unless defined $conf->{pass}; my $jira = eval {JIRA::Client->new({ baseurl => $conf->{url}, user => $conf->{user}, password => $conf->{pass}, })}; ok(defined $jira, 'new returns') and ok(ref $jira, 'new returns an object') and is(ref $jira, 'JIRA::Client', 'new returns a correct object') or BAIL_OUT("Cannot proceed without a JIRA::Client object: $@\n"); my $issue = eval {$jira->create_issue({ project => $conf->{project}, assignee => $conf->{user}, %{$conf->{issue}}, })}; ok(defined $issue, 'create_issue returns') and ok(ref $issue, 'create_issue returns an object') and is(ref $issue, 'RemoteIssue', "create_issue returns a RemoteIssue object (https://jira.atlassian.com/browse/$issue->{key})") or BAIL_OUT("Cannot proceed because I cannot create an issue: $@\n"); my $rissue = eval {$jira->getIssue($issue->{key})}; ok(defined $rissue, 'getIssue returns') and ok(ref $rissue, 'getIssue returns an object') and is(ref $rissue, 'RemoteIssue', 'getIssue returns a RemoteIssue object') and is($rissue->{key}, $issue->{key}, 'getIssue returns the correct issue') or BAIL_OUT("Cannot proceed because I cannot get anything from the server: $@\n"); my $subissue = eval {$jira->create_issue({ project => $conf->{project}, assignee => $conf->{user}, parent => $issue->{key}, %{$conf->{subtask}}, })}; ok(defined $subissue, 'create_issue sub-task returns') and ok(ref $subissue, 'create_issue sub-task returns an object') and is(ref $subissue, 'RemoteIssue', "create_issue sub-task returns a RemoteIssue object (http://sandbox.onjira.com/browse/$subissue->{key})") or BAIL_OUT("Cannot proceed because I cannot create an sub-task issue: $@\n"); foreach my $progress (@{$conf->{subtask_progress}}) { my $pissue = eval {$jira->progress_workflow_action_safely($subissue, @$progress)}; my $prefix = "progress_workflow_action_safely(sub-task, $progress->[0])"; ok(defined $pissue, "$prefix returns") and ok(ref $pissue, "$prefix returns an object") and is(ref $pissue, 'RemoteIssue', "$prefix returns a RemoteIssue object") and isnt($pissue->{status}, $progress->[0], "$prefix progressed the issue"); $subissue = $pissue; } foreach my $progress (@{$conf->{issue_progress}}) { my $pissue = eval {$jira->progress_workflow_action_safely($issue, @$progress)}; my $prefix = "progress_workflow_action_safely(issue, $progress->[0])"; ok(defined $pissue, "$prefix returns") and ok(ref $pissue, "$prefix returns an object") and is(ref $pissue, 'RemoteIssue', "$prefix returns a RemoteIssue object") and isnt($pissue->{status}, $progress->[0], "$prefix progressed the issue"); $issue = $pissue; } done_testing(); JIRA000755004231004231 012463542712 15160 5ustar00gustavogustavo000000000000JIRA-Client-0.43/libClient.pm100644004231004231 14026112463542712 17140 0ustar00gustavogustavo000000000000JIRA-Client-0.43/lib/JIRApackage JIRA::Client; # ABSTRACT: (DEPRECATED) Extended interface to JIRA's SOAP API $JIRA::Client::VERSION = '0.43'; use strict; use warnings; use Carp; use Data::Util qw(:check); use SOAP::Lite; sub new { my ($class, @args) = @_; my $args; if (@args == 1) { $args = shift @args; is_hash_ref($args) or croak "$class::new sole argument must be a hash-ref.\n"; foreach my $arg (qw/baseurl user password/) { exists $args->{$arg} or croak "Missing $arg key to $class::new hash argument.\n"; } $args->{soapargs} = [] unless exists $args->{soapargs}; } elsif (@args >= 3) { my $baseurl = shift @args; my $user = shift @args; my $password = shift @args; $args = { baseurl => $baseurl, user => $user, password => $password, soapargs => \@args, }; } else { croak "Invalid number of arguments to $class::new.\n"; } $args->{wsdl} = '/rpc/soap/jirasoapservice-v2?wsdl' unless exists $args->{wsdl}; my $url = $args->{baseurl}; $url =~ s{/$}{}; # clean trailing slash $url .= $args->{wsdl}; my $soap = SOAP::Lite->proxy($url, @{$args->{soapargs}}); # Make all scalars be encoded as strings by default. $soap->typelookup({default => [0, sub {1}, 'as_string']}); my $auth = $soap->login($args->{user}, $args->{password}); croak $auth->faultcode(), ', ', $auth->faultstring() if defined $auth->fault(); my $auth_result = $auth->result() or croak "Unknown error while connecting to JIRA. Please, check the URL.\n"; my $self = { soap => $soap, auth => $auth_result, iter => undef, cache => { components => {}, # project_key => {name => RemoteComponent} versions => {}, # project_key => {name => RemoteVersion} }, }; return bless $self, $class; } # This empty DESTROY is necessary because we're using AUTOLOAD. # http://www.perlmonks.org/?node_id=93045 sub DESTROY { } # The issue "https://jira.atlassian.com/browse/JRA-12300" explains why # some fields in JIRA have nonintuitive names. Here we map them. my %JRA12300 = ( affectsVersions => 'versions', type => 'issuetype', ); my %JRA12300_backwards = reverse %JRA12300; # These are some helper functions to convert names into ids. sub _convert_type { my ($self, $type) = @_; if ($type =~ /\D/) { my $types = $self->get_issue_types(); return $types->{$type}{id} if exists $types->{$type}; $types = $self->get_subtask_issue_types(); return $types->{$type}{id} if exists $types->{$type}; croak "There is no issue type called '$type'.\n"; } return $type; } sub _convert_priority { my ($self, $prio) = @_; if ($prio =~ /\D/) { my $prios = $self->get_priorities(); croak "There is no priority called '$prio'.\n" unless exists $prios->{$prio}; return $prios->{$prio}{id}; } return $prio; } sub _convert_resolution { my ($self, $resolution) = @_; if ($resolution =~ /\D/) { my $resolutions = $self->get_resolutions(); croak "There is no resolution called '$resolution'.\n" unless exists $resolutions->{$resolution}; return $resolutions->{$resolution}{id}; } return $resolution; } sub _convert_security_level { my ($self, $seclevel, $project) = @_; if ($seclevel =~ /\D/) { my $seclevels = $self->get_security_levels($project); croak "There is no security level called '$seclevel'.\n" unless exists $seclevels->{$seclevel}; return $seclevels->{$seclevel}{id}; } return $seclevel; } # This routine receives an array with a list of $components specified # by RemoteComponent objects, names, and ids. It returns an array of # RemoteComponent objects. sub _convert_components { my ($self, $components, $project) = @_; is_array_ref($components) or croak "The 'components' value must be an ARRAY ref.\n"; my @converted; my $pcomponents; # project components foreach my $component (@{$components}) { if (is_instance($component => 'RemoteComponent')) { push @converted, $component; } elsif (is_integer($component)) { push @converted, RemoteComponent->new($component); } else { # It's a component name. Let us convert it into its id. croak "Cannot convert component names because I don't know for which project.\n" unless $project; $pcomponents = $self->get_components($project) unless defined $pcomponents; croak "There is no component called '$component'.\n" unless exists $pcomponents->{$component}; push @converted, RemoteComponent->new($pcomponents->{$component}{id}); } } return \@converted; } # This routine receives an array with a list of $versions specified by # RemoteVersion objects, names, and ids. It returns an array of # RemoteVersion objects. sub _convert_versions { my ($self, $versions, $project) = @_; is_array_ref($versions) or croak "The '$versions' value must be an ARRAY ref.\n"; my @converted; my $pversions; # project versions foreach my $version (@{$versions}) { if (is_instance($version => 'RemoteVersion')) { push @converted, $version; } elsif (is_integer($version)) { push @converted, RemoteVersion->new($version); } else { # It is a version name. Let us convert it into its id. croak "Cannot convert version names because I don't know for which project.\n" unless $project; $pversions = $self->get_versions($project) unless defined $pversions; croak "There is no version called '$version'.\n" unless exists $pversions->{$version}; push @converted, RemoteVersion->new($pversions->{$version}{id}); } } return \@converted; } # This routine returns a duedate as a SOAP::Data object with type # 'date'. It can generate this from a DateTime object or from a string # in the format YYYY-MM-DD. sub _convert_duedate { my ($self, $duedate) = @_; if (is_instance($duedate => 'DateTime')) { return SOAP::Data->type(date => $duedate->strftime('%F')); } elsif (is_string($duedate)) { if (my ($year, $month, $day) = ($duedate =~ /^(\d{4})-(\d{2})-(\d{2})/)) { $month >= 1 and $month <= 12 or croak "Invalid duedate ($duedate).\n"; return SOAP::Data->type(date => $duedate); } } return $duedate; } # This routine receives a hash mapping custom field's ids to # values. The ids can be specified by their real id or by their id's # numeric suffix (as the 1000 in 'customfield_1000'). Scalar values # are substituted by references to arrays containing the original # value. The routine returns a hash-ref to another hash with converted # keys and values. sub _convert_custom_fields { my ($self, $custom_fields) = @_; is_hash_ref($custom_fields) or croak "The 'custom_fields' value must be a HASH ref.\n"; my %converted; while (my ($id, $values) = each %$custom_fields) { my $realid = $id; unless ($realid =~ /^customfield_\d+$/) { my $cfs = $self->get_custom_fields(); croak "Can't find custom field named '$id'.\n" unless exists $cfs->{$id}; $realid = $cfs->{$id}{id}; } # Custom field values must be specified as ARRAYs but we allow for some short-cuts. if (is_value($values)) { $converted{$realid} = [$values]; } elsif (is_array_ref($values)) { $converted{$realid} = $values; } elsif (is_hash_ref($values)) { # This is a short-cut for a Cascading select field, which # must be specified like this: http://tinyurl.com/2bmthoa # The short-cut requires a HASH where each cascading level # is indexed by its level number, starting at zero. foreach my $level (sort {$a <=> $b} keys %$values) { my $level_values = $values->{$level}; $level_values = [$level_values] unless ref $level_values; if ($level eq '0') { # The first level doesn't have a colon $converted{$realid} = $level_values } elsif ($level =~ /^\d+$/) { $converted{"$realid:$level"} = $level_values; } else { croak "Invalid cascading field values level spec ($level). It must be a natural number.\n"; } } } else { croak "Custom field '$id' got a '", ref($values), "' reference as a value.\nValues can only be specified as scalars, ARRAYs, or HASHes though.\n"; } } return \%converted; } my %_converters = ( affectsVersions => \&_convert_versions, components => \&_convert_components, custom_fields => \&_convert_custom_fields, duedate => \&_convert_duedate, fixVersions => \&_convert_versions, priority => \&_convert_priority, resolution => \&_convert_resolution, type => \&_convert_type, ); # Accept both names for fields with duplicate names. foreach my $field (keys %JRA12300) { $_converters{$JRA12300{$field}} = $_converters{$field}; } # This routine applies all the previous conversions to the $params # hash. It returns a reference another hash with converted keys and # values, which is the base for invoking the methods createIssue, # UpdateIssue, and progressWorkflowAction. sub _convert_params { my ($self, $params, $project) = @_; my %converted; # Convert fields' values while (my ($field, $value) = each %$params) { $converted{$field} = exists $_converters{$field} ? $_converters{$field}->($self, $value, $project) : $value; } return \%converted; } # This routine gets a hash produced by _convert_params and flatens in # place its Component, Version, and custom_fields fields. It also # converts the hash's key according with the %JRA12300 table. It goes # a step further before invoking the methods UpdateIssue and # progressWorkflowAction. sub _flaten_components_and_versions { my ($params) = @_; # Flaten Component and Version fields for my $field (grep {exists $params->{$_}} qw/components affectsVersions fixVersions/) { $params->{$field} = [map {$_->{id}} @{$params->{$field}}]; } # Flaten the customFieldValues field if (my $custom_fields = delete $params->{custom_fields}) { while (my ($id, $values) = each %$custom_fields) { $params->{$id} = $values; } } # Due to a bug in JIRA we have to substitute the names of some fields. foreach my $field (grep {exists $params->{$_}} keys %JRA12300) { $params->{$JRA12300{$field}} = delete $params->{$field}; } return; } sub create_issue { my ($self, $params, $seclevel) = @_; is_hash_ref($params) or croak "create_issue's requires a HASH-ref argument.\n"; for my $field (qw/project summary type/) { croak "create_issue's HASH ref must define a '$field'.\n" unless exists $params->{$field}; } $params = $self->_convert_params($params, $params->{project}); # Substitute customFieldValues array for custom_fields hash if (my $cfs = delete $params->{custom_fields}) { $params->{customFieldValues} = [map {RemoteCustomFieldValue->new($_, $cfs->{$_})} keys %$cfs]; } if (my $parent = delete $params->{parent}) { if (defined $seclevel) { return $self->createIssueWithParentWithSecurityLevel($params, $parent, _convert_security_level($self, $seclevel, $params->{project})); } else { return $self->createIssueWithParent($params, $parent); } } else { if (defined $seclevel) { return $self->createIssueWithSecurityLevel($params, _convert_security_level($self, $seclevel, $params->{project})); } else { return $self->createIssue($params); } } } sub update_issue { my ($self, $issue, $params) = @_; my $key; if (is_instance($issue => 'RemoteIssue')) { $key = $issue->{key}; } else { $key = $issue; $issue = $self->getIssue($key); } is_hash_ref($params) or croak "update_issue second argument must be a HASH ref.\n"; my ($project) = ($key =~ /^([^-]+)/); $params = $self->_convert_params($params, $project); _flaten_components_and_versions($params); return $self->updateIssue($key, $params); } sub get_issue_types { my ($self) = @_; $self->{cache}{issue_types} ||= {map {$_->{name} => $_} @{$self->getIssueTypes()}}; return $self->{cache}{issue_types}; } sub get_subtask_issue_types { my ($self) = @_; $self->{cache}{subtask_issue_types} ||= {map {$_->{name} => $_} @{$self->getSubTaskIssueTypes()}}; return $self->{cache}{subtask_issue_types}; } sub get_statuses { my ($self) = @_; $self->{cache}{statuses} ||= {map {$_->{name} => $_} @{$self->getStatuses()}}; return $self->{cache}{statuses}; } sub get_priorities { my ($self) = @_; $self->{cache}{priorities} ||= {map {$_->{name} => $_} @{$self->getPriorities()}}; return $self->{cache}{priorities}; } sub get_resolutions { my ($self) = @_; $self->{cache}{resolutions} ||= {map {$_->{name} => $_} @{$self->getResolutions()}}; return $self->{cache}{resolutions}; } sub get_security_levels { my ($self, $project_key) = @_; $self->{cache}{seclevels}{$project_key} ||= {map {$_->{name} => $_} @{$self->getSecurityLevels($project_key)}}; return $self->{cache}{seclevels}{$project_key}; } sub get_custom_fields { my ($self) = @_; $self->{cache}{custom_fields} ||= {map {$_->{name} => $_} @{$self->getCustomFields()}}; return $self->{cache}{custom_fields}; } sub set_custom_fields { my ($self, $cfs) = @_; $self->{cache}{custom_fields} = $cfs; return; } sub get_components { my ($self, $project_key) = @_; $self->{cache}{components}{$project_key} ||= {map {$_->{name} => $_} @{$self->getComponents($project_key)}}; return $self->{cache}{components}{$project_key}; } sub get_versions { my ($self, $project_key) = @_; $self->{cache}{versions}{$project_key} ||= {map {$_->{name} => $_} @{$self->getVersions($project_key)}}; return $self->{cache}{versions}{$project_key}; } sub get_favourite_filters { my ($self) = @_; $self->{cache}{filters} ||= {map {$_->{name} => $_} @{$self->getFavouriteFilters()}}; return $self->{cache}{filters}; } sub set_filter_iterator { my ($self, $filter, $cache_size) = @_; if ($filter =~ /\D/) { my $filters = $self->getSavedFilters(); foreach my $f (@$filters) { if ($f->{name} eq $filter) { $filter = $f->{id}; last; } } croak "Can't find filter '$filter'\n" if $filter =~ /\D/; } if ($cache_size) { croak "set_filter_iterator's second arg must be a number ($cache_size).\n" if $cache_size =~ /\D/; } $self->{iter} = { id => $filter, offset => 0, # offset to be used in the next call to getIssuesFromFilterWithLimit issues => [], # issues returned by the last call to getIssuesFromFilterWithLimit size => $cache_size || 128, }; return; } sub next_issue { my ($self) = @_; defined $self->{iter} or croak "You must call setFilterIterator before calling nextIssue\n"; my $iter = $self->{iter}; if (@{$iter->{issues}} == 0) { if ($iter->{id}) { my $issues = eval {$self->getIssuesFromFilterWithLimit($iter->{id}, $iter->{offset}, $iter->{size})}; if ($@) { # The getIssuesFromFilterWithLimit appeared in JIRA # 3.13.4. Before that we had to use the unsafe # getIssuesFromFilter. Here we detect that we're talking # with an old JIRA and resort to the deprecated method # instead. croak $@ unless $@ =~ /No such operation/; $iter->{issues} = $self->getIssuesFromFilter($iter->{id}); $iter->{id} = undef; } elsif (@$issues) { $iter->{offset} += @$issues; $iter->{issues} = $issues; } else { $self->{iter} = undef; return; } } else { return; } } return shift @{$iter->{issues}}; } sub progress_workflow_action_safely { my ($self, $issue, $action, $params) = @_; my $key; if (is_instance($issue => 'RemoteIssue')) { $key = $issue->{key}; } else { $key = $issue; $issue = undef; } $params = {} unless defined $params; is_hash_ref($params) or croak "progress_workflow_action_safely's third arg must be a HASH-ref\n"; # Grok the action id if it's not a number if ($action =~ /\D/) { my @available_actions = @{$self->getAvailableActions($key)}; my @named_actions = grep {$action eq $_->{name}} @available_actions; if (@named_actions) { $action = $named_actions[0]->{id}; } else { croak "Unavailable action ($action).\n"; } } # Make sure $params contains all the fields that are present in # the action screen. my @fields = @{$self->getFieldsForAction($key, $action)}; foreach my $id (map {$_->{id}} @fields) { # Due to a bug in JIRA we have to substitute the names of some fields. $id = $JRA12300_backwards{$id} if $JRA12300_backwards{$id}; next if exists $params->{$id}; $issue = $self->getIssue($key) unless defined $issue; if (exists $issue->{$id}) { $params->{$id} = $issue->{$id} if defined $issue->{$id}; } # NOTE: It's not a problem if we can't find a missing # parameter in the issue. It will simply stay undefined. } my ($project) = ($key =~ /^([^-]+)/); $params = $self->_convert_params($params, $project); _flaten_components_and_versions($params); return $self->progressWorkflowAction($key, $action, $params); } sub get_issue_custom_field_values { my ($self, $issue, @cfs) = @_; my @values; my $cfs; CUSTOM_FIELD: foreach my $cf (@cfs) { unless ($cf =~ /^customfield_\d+$/) { $cfs = $self->get_custom_fields() unless defined $cfs; croak "Can't find custom field named '$cf'.\n" unless exists $cfs->{$cf}; $cf = $cfs->{$cf}{id}; } foreach my $rcfv (@{$issue->{customFieldValues}}) { if ($rcfv->{customfieldId} eq $cf) { push @values, $rcfv->{values}; next CUSTOM_FIELD; } } push @values, undef; # unset custom field } return wantarray ? @values : \@values; } sub attach_files_to_issue { my ($self, $issue, @files) = @_; # First we process the @files specification. Filenames are pushed # in @filenames and @attachments will end up with IO objects from # which the file contents are going to be read later. my (@filenames, @attachments); for my $file (@files) { if (is_string($file)) { require File::Basename; push @filenames, File::Basename::basename($file); open my $fh, '<:raw', $file or croak "Can't open $file: $!\n"; push @attachments, $fh; close $fh; } elsif (is_hash_ref($file)) { while (my ($name, $contents) = each %$file) { push @filenames, $name; if (is_string($contents)) { open my $fh, '<:raw', $contents or croak "Can't open $contents: $!\n"; push @attachments, $fh; close $fh; } elsif (is_glob_ref($contents) || is_instance($contents => 'IO::File') || is_instance($contents => 'FileHandle')) { push @attachments, $contents; } else { croak "Invalid content specification for file $name.\n"; } } } else { croak "Files must be specified by STRINGs or HASHes, not by " . ref($file) . "s\n"; } } # Now we have to read all file contents and encode them to Base64. require MIME::Base64; for my $i (0 .. $#attachments) { my $fh = $attachments[$i]; my $attachment = ''; my $chars_read; while ($chars_read = read $fh, my $buf, 57*72) { $attachment .= MIME::Base64::encode_base64($buf); } defined $chars_read or croak "Error reading '$filenames[$i]': $!\n"; length $attachment or croak "Can't attach empty file '$filenames[$i]'\n"; $attachments[$i] = $attachment; } return $self->addBase64EncodedAttachmentsToIssue($issue, \@filenames, \@attachments); } sub attach_strings_to_issue { my ($self, $issue, $hash) = @_; require MIME::Base64; my (@filenames, @attachments); while (my ($filename, $contents) = each %$hash) { push @filenames, $filename; push @attachments, MIME::Base64::encode_base64($contents); } return $self->addBase64EncodedAttachmentsToIssue($issue, \@filenames, \@attachments); } sub filter_issues_unsorted { my ($self, $filter, $limit) = @_; $filter =~ s/^\s*"?//; $filter =~ s/"?\s*$//; if ($filter =~ /^(?:[A-Z]+-\d+\s+)*[A-Z]+-\d+$/i) { # space separated key list # Let's construct a JQL query in the form "issuekey IN (...)" to # pass to getIssuesFromJqlSearch. my %keys = map {($_ => undef)} split / /, $filter; # discard duplicates my @keys = keys %keys; # If the list is too big we split it up and invoke # getIssuesFromJqlSearch several times. my @issues; while (my @subkeys = splice(@keys, 0, 128)) { my $jql = 'issuekey IN (' . join(',', @subkeys) . ')'; push @issues, @{$self->getIssuesFromJqlSearch($jql, 1000)}; } return @issues; } elsif ($filter =~ /^[\w-]+$/i) { # saved filter return @{$self->getIssuesFromFilterWithLimit($filter, 0, $limit || 1000)}; } else { # JQL filter return @{$self->getIssuesFromJqlSearch($filter, $limit || 1000)}; } } sub filter_issues { my ($self, $filter, $limit) = @_; # Order the issues by project key and then by numeric value using # a Schwartzian transform. return map {$_->[2]} sort {$a->[0] cmp $b->[0] or $a->[1] <=> $b->[1]} map {my ($p, $n) = ($_->{key} =~ /([A-Z]+)-(\d+)/); [$p, $n, $_]} filter_issues_unsorted($self, $filter, $limit); } ## no critic (Modules::ProhibitMultiplePackages) package RemoteFieldValue; $RemoteFieldValue::VERSION = '0.43'; sub new { my ($class, $id, $values) = @_; # Due to a bug in JIRA we have to substitute the names of some fields. $id = $JRA12300{$id} if exists $JRA12300{$id}; $values = [$values] unless ref $values; return bless({id => $id, values => $values}, $class); } package RemoteCustomFieldValue; $RemoteCustomFieldValue::VERSION = '0.43'; sub new { my ($class, $id, $values) = @_; $values = [$values] unless ref $values; return bless({customfieldId => $id, key => undef, values => $values} => $class); } package RemoteComponent; $RemoteComponent::VERSION = '0.43'; sub new { my ($class, $id, $name) = @_; my $o = bless({id => $id}, $class); $o->{name} = $name if $name; return $o; } package RemoteVersion; $RemoteVersion::VERSION = '0.43'; sub new { my ($class, $id, $name) = @_; my $o = bless({id => $id}, $class); $o->{name} = $name if $name; return $o; } package JIRA::Client; # Almost all of the JIRA API parameters are strings. The %typeof hash # specifies the exceptions. It maps a method name to a hash mapping a # parameter position to its type. (The parameter position is # zero-based, after the authentication token. my %typeof = ( addActorsToProjectRole => {1 => \&_cast_remote_project_role}, addAttachmentsToIssue => \&_cast_attachments, addBase64EncodedAttachmentsToIssue => \&_cast_base64encodedattachments, addComment => {0 => \&_cast_issue_key, 1 => \&_cast_remote_comment}, addDefaultActorsToProjectRole => {1 => \&_cast_remote_project_role}, # addPermissionTo # addUserToGroup # addVersion addWorklogAndAutoAdjustRemainingEstimate => {0 => \&_cast_issue_key}, addWorklogAndRetainRemainingEstimate => {0 => \&_cast_issue_key}, addWorklogWithNewRemainingEstimate => {0 => \&_cast_issue_key}, archiveVersion => {2 => 'boolean'}, # createGroup # createIssue createIssueWithParent => {1 => \&_cast_issue_key}, createIssueWithParentWithSecurityLevel => {1 => \&_cast_issue_key, 2 => 'long'}, createIssueWithSecurityLevel => {1 => 'long'}, # createPermissionScheme # createProject # createProjectFromObject createProjectRole => {0 => \&_cast_remote_project_role}, # createUser # deleteGroup deleteIssue => {0 => \&_cast_issue_key}, # deletePermissionFrom # deletePermissionScheme # deleteProject deleteProjectAvatar => {0 => 'long'}, deleteProjectRole => {0 => \&_cast_remote_project_role, 1 => 'boolean'}, # deleteUser # deleteWorklogAndAutoAdjustRemainingEstimate # deleteWorklogAndRetainRemainingEstimate # deleteWorklogWithNewRemainingEstimate # editComment # getAllPermissions getAssociatedNotificationSchemes => {0 => \&_cast_remote_project_role}, getAssociatedPermissionSchemes => {0 => \&_cast_remote_project_role}, getAttachmentsFromIssue => {0 => \&_cast_issue_key}, getAvailableActions => {0 => \&_cast_issue_key}, getComment => {0 => 'long'}, getComments => {0 => \&_cast_issue_key}, # getComponents # getConfiguration # getCustomFields getDefaultRoleActors => {0 => \&_cast_remote_project_role}, # getFavouriteFilters getFieldsForAction => {0 => \&_cast_issue_key}, getFieldsForCreate => {1 => 'long'}, getFieldsForEdit => {0 => \&_cast_issue_key}, # getGroup getIssue => {0 => \&_cast_issue_key}, # getIssueById getIssueCountForFilter => {0 => \&_cast_filter_name_to_id}, getIssuesFromFilter => {0 => \&_cast_filter_name_to_id}, getIssuesFromFilterWithLimit => {0 => \&_cast_filter_name_to_id, 1 => 'int', 2 => 'int'}, getIssuesFromJqlSearch => {1 => 'int'}, # getIssuesFromTextSearch getIssuesFromTextSearchWithLimit => {1 => 'int', 2 => 'int'}, getIssuesFromTextSearchWithProject => {2 => 'int'}, # getIssueTypes # getIssueTypesForProject # getNotificationSchemes # getPermissionSchemes # getPriorities # getProjectAvatar getProjectAvatars => {1 => 'boolean'}, getProjectById => {0 => 'long'}, # getProjectByKey getProjectRole => {0 => 'long'}, getProjectRoleActors => {0 => \&_cast_remote_project_role}, # getProjectRoles # getProjectsNoSchemes getProjectWithSchemesById => {0 => 'long'}, getResolutionDateById => {0 => 'long'}, getResolutionDateByKey => {0 => \&_cast_issue_key}, # getResolutions # getSavedFilters getSecurityLevel => {0 => \&_cast_issue_key}, # getSecurityLevels # getSecuritySchemes # getServerInfo # getStatuses # getSubTaskIssueTypes # getSubTaskIssueTypesForProject # getUser # getVersions getWorklogs => {0 => \&_cast_issue_key}, hasPermissionToCreateWorklog => {0 => \&_cast_issue_key}, # hasPermissionToDeleteWorklog # hasPermissionToEditComment # hasPermissionToUpdateWorklog # isProjectRoleNameUnique # login ##NOT USED## # logout ##NOT USED## progressWorkflowAction => {0 => \&_cast_issue_key, 2 => \&_cast_remote_field_values}, # refreshCustomFields # releaseVersion removeActorsFromProjectRole => {1 => \&_cast_remote_project_role}, # removeAllRoleActorsByNameAndType # removeAllRoleActorsByProject removeDefaultActorsFromProjectRole => {1 => \&_cast_remote_project_role}, # removeUserFromGroup # setNewProjectAvatar setProjectAvatar => {1 => 'long'}, # setUserPassword # updateGroup updateIssue => {0 => \&_cast_issue_key, 1 => \&_cast_remote_field_values}, # updateProject updateProjectRole => {0 => \&_cast_remote_project_role}, # updateUser # updateWorklogAndAutoAdjustRemainingEstimate # updateWorklogAndRetainRemainingEstimate # updateWorklogWithNewRemainingEstimate ); sub _cast_issue_key { my ($self, $issue) = @_; return ref $issue ? $issue->{key} : $issue; } sub _cast_remote_comment { my ($self, $arg) = @_; return ref $arg ? $arg : bless({body => $arg} => 'RemoteComment'); } sub _cast_filter_name_to_id { my ($self, $arg) = @_; is_string($arg) or croak "Filter arg must be a string.\n"; return $arg unless $arg =~ /\D/; my $filters = $self->get_favourite_filters(); exists $filters->{$arg} or croak "Unknown filter: $arg\n"; return $filters->{$arg}{id}; } sub _cast_remote_field_values { my ($self, $arg) = @_; return is_hash_ref($arg) ? [map {RemoteFieldValue->new($_, $arg->{$_})} keys %$arg] : $arg; } sub _cast_remote_project_role { my ($self, $arg) = @_; if (is_instance($arg => 'RemoteProjectRole') && exists $arg->{id} && is_string($arg->{id})) { $arg->{id} = SOAP::Data->type(long => $arg->{id}); } return $arg; } sub _cast_attachments { my ($self, $method, $args) = @_; # The addAttachmentsToIssue method is deprecated and requires too # much overhead to pass the file contents over the wire. Here we # convert the arguments to call the newer # addBase64EncodedAttachmentsToIssue method instead. require MIME::Base64; for my $content (@{$args->[2]}) { $content = MIME::Base64::encode_base64($content); } $$method = 'addBase64EncodedAttachmentsToIssue'; _cast_base64encodedattachments($self, $method, $args); return; } sub _cast_base64encodedattachments { my ($self, $method, $args) = @_; $args->[0] = _cast_issue_key($self, $args->[0]); # We have to set the names of the arrays and of its elements # because the default naming isn't properly understood by JIRA. for my $i (1 .. 2) { $args->[$i] = SOAP::Data->name( "array$i", [map {SOAP::Data->name("elem$i", $_)} @{$args->[$i]}], ); } return; } # All methods follow the same call convention, which makes it easy to # implement them all with an AUTOLOAD. our $AUTOLOAD; sub AUTOLOAD { my ($self, @args) = @_; (my $method = $AUTOLOAD) =~ s/.*:://; # Perform any non-default type coersion if (my $typeof = $typeof{$method}) { if (is_hash_ref($typeof)) { while (my ($i, $type) = each %$typeof) { if (is_code_ref($type)) { $args[$i] = $type->($self, $args[$i]); } elsif (is_value($args[$i])) { $args[$i] = SOAP::Data->type($type => $args[$i]); } elsif (is_array_ref($args[$i])) { foreach (@{$args[$i]}) { $_ = SOAP::Data->type($type => $_); } } elsif (is_hash_ref($args[$i])) { foreach (values %{$args[$i]}) { $_ = SOAP::Data->type($type => $_); } } else { croak "Can't coerse argument $i of method $AUTOLOAD.\n"; } } } elsif (is_code_ref($typeof)) { $typeof->($self, \$method, \@args); } } my $call = $self->{soap}->call($method, $self->{auth}, @args); croak $call->faultcode(), ', ', $call->faultstring() if defined $call->fault(); return $call->result(); } 1; # End of JIRA::Client __END__ =pod =encoding UTF-8 =head1 NAME JIRA::Client - (DEPRECATED) Extended interface to JIRA's SOAP API =head1 VERSION version 0.43 =head1 SYNOPSIS use JIRA::Client; my $jira = JIRA::Client->new('http://jira.example.com/jira', 'user', 'passwd'); my $issue = $jira->create_issue( { project => 'TST', type => 'Bug', summary => 'Summary of the bug', assignee => 'gustavo', components => ['compa', 'compb'], fixVersions => ['1.0.1'], custom_fields => {Language => 'Perl', Architecture => 'Linux'}, } ); $issue = eval { $jira->getIssue('TST-123') }; die "Can't getIssue(): $@" if $@; $jira->set_filter_iterator('my-filter'); while (my $issue = $jira->next_issue()) { # ... } =head1 DESCRIPTION B: Please, before using this module consider using the newer L because JIRA's SOAP API was L on JIRA 6.0 and won't be available anymore on JIRA 8.0. JIRA is a proprietary bug tracking system from Atlassian (L). This module implements an Object Oriented wrapper around JIRA's SOAP API, which is specified in L. (This version is known work against JIRA 4.4.) Moreover, it implements some other methods to make it easier to do some common operations. =head1 API METHODS With the exception of the API C and C methods, which aren't needed, all other methods are available through the JIRA::Client object interface. You must call them with the same name as documented in the specification but you should not pass the C argument, because it is supplied transparently by the JIRA::Client object. All methods fail by throwing exceptions (croaking, actually). You may want to guard against this by invoking them within an eval block, like this: my $issue = eval { $jira->getIssue('TST-123') }; die "Can't getIssue('TST-123'): $@" if $@; Some of the API methods require hard-to-build data structures as arguments. This module tries to make them easier to call by accepting simpler structures and implicitly constructing the more elaborated ones before making the actual SOAP call. Note that this is an option, i.e, you can either pass the elaborate structures by yourself or the simpler ones in the call. The items below are all the implemented implicit conversions. Wherever a parameter of the type specified first is required (as an rvalue, not as an lvalue) by an API method you can safely pass a value of the type specified second. =over 4 =item A B as a string can be specified by a B object. =item A B object can be specified by a string. =item A B as a string can be specified by a B object. =item A B object array can be specified by a hash mapping field names to values. =back =head1 EXTRA METHODS This module implements some extra methods to add useful functionality to the API. They are described below. Note that their names don't follow the CamelCase convention used by the native API methods but the more Perlish underscore_separated_words convention so that you can distinguish them and we can avoid future name clashes. =head2 B BASEURL, USER, PASSWD [, ] C is the JIRA server's base URL (e.g., C or C), to which the default WSDL descriptor path (C) will be appended in order to construct the underlying SOAP::Lite object. C and C are the credentials that will be used to authenticate into JIRA. Any other arguments will be passed to the L object that will be created to talk to JIRA. =head2 B HASH_REF You can invoke the constructor with a single hash-ref argument. The same arguments that are passed as a list above can be passed by name with a hash. This constructor is also more flexible, as it makes room for extra arguments. The valid hash keys are listed below. =over =item baseurl => STRING (Required) The JIRA server's base URL. =item wsdl => STRING (Optional) JIRA's standard WSDL descriptor path is C. If your JIRA instance has a non-standard path to the WSDL service, you may specify it here. =item user => STRING (Required) The username to authenticate into JIRA. =item password => STRING (Required) The password to authenticate into JIRA. =item soapargs => ARRAY_REF (Optional) Extra arguments to be passed to the L object that will be created to talk to JIRA. =back =head2 B HASH_REF [, SECURITYLEVEL] Creates a new issue given a hash containing the initial values for its fields and, optionally, a security-level. The hash must specify at least the fields C, C, and C. This is an easier to use version of the createIssue API method. For once it accepts symbolic values for some of the issue fields that the API method does not. Specifically: =over 4 =item C can be specified by I instead of by I. =item C can be specified by I instead of by I. =item C can be specified by a list of component I or I instead of a list of C objects. =item C and C can be specified by a list of version I or I instead of a list of C objects. =item C can be specified by a DateTime object or by a string in ISO standard format (YYYY-MM-DD...). (Note that up to JIRA 4.3 you could pass a string in the format "d/MMM/yy", which was passed as is to JIRA, which expected a B SOAP type. However, since JIRA 4.4 the server expects a B SOAP type, which must be in the ISO standard format.) =back It accepts a 'magic' field called B, which specifies the issue key from which the created issue must be a sub-task. It accepts another 'magic' field called B to make it easy to set custom fields. It accepts a hash mapping each custom field to its value. The custom field can be specified by its id (in the format B) or by its name, in which case the method will try to convert it to its id. Note that to do that conversion the user needs administrator rights. A simple custom field value can be specified by a scalar, which will be properly placed inside an ARRAY in order to satisfy the B's structure. Cascading select fields are properly specified like this: http://tinyurl.com/2bmthoa. The magic short-cut requires a HASH where each cascading level is indexed by its level number, starting at zero. So, instead of specifying it like this: { id => 'customfield_10011', values => [ SOAP::Data->type(string => '10031' ) ] }, { id => 'customfield_10011:1', values => [ SOAP::Data->type(string => '10188') ], }, You can do it like this: {customfield_10011 => {'0' => 10031, '1' => 10188}}, Note that the original hash keys and values are completely preserved. =head2 B ISSUE_OR_KEY, HASH_REF Update a issue given a hash containing the values for its fields. The first argument may be an issue key or a RemoteIssue object. The second argument must be a hash-ref specifying the fields's values just like documented in the create_issue function above. This is an easier to use version of the updateIssue API method because it accepts the same shortcuts that create_issue does. =head2 B Returns a hash mapping the server's issue type names to the RemoteIssueType objects describing them. =head2 B Returns a hash mapping the server's sub-task issue type names to the RemoteIssueType objects describing them. =head2 B Returns a hash mapping the server's status names to the RemoteStatus objects describing them. =head2 B Returns a hash mapping a server's priorities names to the RemotePriority objects describing them. =head2 B Returns a hash mapping a server's resolution names to the RemoteResolution objects describing them. =head2 B PROJECT-KEY Returns a hash mapping a project's security level names to the RemoteSecurityLevel objects describing them. =head2 B Returns a hash mapping JIRA's custom field names to the RemoteField representing them. It's useful since when you get a RemoteIssue object from this API it doesn't contain the custom field's names, but just their identifiers. From the RemoteField object you can obtain the field's B, which is useful when calling the B method. The method calls the getCustomFields API method the first time and keeps the custom fields information in a cache. =head2 B HASHREF Passes a hash mapping JIRA's custom field names to the RemoteField representing them to populate the custom field's cache. This can be useful if you don't have administrative privileges to the JIRA instance, since only administrators can call the B API method. =head2 B PROJECT_KEY Returns a hash mapping a project's components names to the RemoteComponent objects describing them. =head2 B PROJECT_KEY Returns a hash mapping a project's versions names to the RemoteVersion objects describing them. =head2 B Returns a hash mapping the user's favourite filter names to its filter ids. =head2 B FILTER [, CACHE_SIZE] Sets up an iterator for the filter identified by FILTER. It must be called before calls to B. FILTER can be either a filter I or a filter I, in which case it's converted to a filter id with a call to C. CACHE_SIZE defines the number of issues that will be pre-fetched by B using C. If not specified, a suitable default will be used. =head2 B This must be called after a call to B. Each call returns a reference to the next issue from the filter. When there are no more issues it returns undef. =head2 B ISSUE, ACTION, PARAMS This is a safe and easier to use version of the B API method which is used to progress an issue through a workflow's action while making edits to the fields that are shown in the action screen. The API method is dangerous because if you forget to provide new values to all the fields shown in the screen, then the fields not provided will become undefined in the issue. The problem has a pending issue on Atlassian's JIRA L. This method plays it safe by making sure that all fields shown in the screen that already have a value are given new (or the same) values so that they don't get undefined. It calls the B API method to grok all fields that are shown in the screen. If there is any field not set in the ACTION_PARAMS then it calls B to grok the missing fields current values. As a result it constructs the necessary RemoteFieldAction array that must be passed to progressWorkflowAction. The method is also easier to use because its arguments are more flexible: =over 4 =item C can be either an issue key or a RemoteIssue object returned by a previous call to, e.g., C. =item C can be either an action I or an action I. =item C must be a hash mapping field names to field values. This hash is treated in the same way as the hash passed to the function B above. =back For example, instead of using this: my $action_id = somehow_grok_the_id_of('close'); $jira->progressWorkflowAction('PRJ-5', $action_id, [ RemoteFieldValue->new(2, 'new value'), ..., # all fields must be specified here ]); And risking to forget to pass some field you can do this: $jira->progress_workflow_action_safely('PRJ-5', 'close', {2 => 'new value'}); =head2 B ISSUE, NAME_OR_IDs This method receives a RemoteField object and a list of names or ids of custom fields. It returns a list of references to the ARRAYs containing the values of the ISSUE's custom fields denoted by their NAME_OR_IDs. Returns undef for custom fields not set on the issue. In scalar context it returns a reference to the list. =head2 B ISSUE, FILES... This method attaches one or more files to an issue. The ISSUE argument may be an issue key or a B object. The attachments may be specified in two ways: =over 4 =item STRING A string denotes a filename to be open and read. In this case, the attachment name is the file's basename. =item HASHREF When you want to specify a different name to the attachment or when you already have an IO object (a GLOB, a IO::File, or a FileHandle) you must pass them as values of a hash. The keys of the hash are taken as the attachment name. You can specify more than one attachment in each hash. =back The method retuns the value returned by the B API method. In the example below, we attach three files to the issue TST-1. The first is called C and its contents are read from C. The second is called C and its contents are read from C. the third is called C and its contents are read from the object refered to by C<$fh>. $jira->attach_files_to_issue('TST-1', '/path/to/file1.txt', { 'text.txt' => '/path/to/file2.txt', 'me.jpg' => $fh, }, ); =head2 B ISSUE, HASHREF This method attaches one or more strings to an issue. The ISSUE argument may be an issue key or a B object. The attachments are specified by a HASHREF in which the keys denote the file names and the values their contents. The method retuns the value returned by the B API method. =head2 B FILTER [, LIMIT] This method returns a list of RemoteIssue objects from the specified FILTER, which is a string that is understood in one of these ways (in order): =over =item A space-separated list of issue keys To specify issues explicitly by their keys, which must match /[A-Z]+-\d+/i. The letters in the key are upcased before being passed to getIssue. For example: KEY-123 chave-234 CLAVE-345 Note that the result list doesn't respect the order in which the keys are specified and also that duplicate keys are discarded and the corresponding issue appear only once in the resulting list. =item The name of a saved filter If FILTER is a single word, it is passed to getIssuesFromFilterWithLimit as a filter name. For example: sprint-backlok-filter =item A JQL expression As a last resort, FILTER is passed to getIssuesFromJqlSearch as a JQL expression. For example: project = CDS AND fixVersion = sprint-5 =back The optional LIMIT argument specified the maximum number of issues that can be returned. It has a default limit of 1000, but this can be overridden by the JIRA server configuration. This method is meant to be used as a flexible interface for human beings to request a list of issues. Be warned, however, that you are responsible to de-taint the FILTER argument before passing it to the method. =head2 B FILTER [, LIMIT] This method invokes the B method with the same arguments and returns the list of RemoteIssue objects sorted by issue key. =head1 OTHER CONSTRUCTORS The JIRA SOAP API uses several types of objects (i.e., classes) for which the Perl SOAP interface does not provide the necessary constructors. This module implements some of them. =head2 Bnew> ID, VALUES The RemoteFieldValue object represents the value of a field of an issue. It needs two arguments: =over =item ID The field name, which must be a valid key for the ISSUE hash. =item VALUES A scalar or an array of scalars. =back =head2 Bnew> ID, VALUES The RemoteCustomFieldValue object represents the value of a custom_field of an issue. It needs two arguments: =over =item ID The field name, which must be a valid custom_field key. =item VALUES A scalar or an array of scalars. =back =head2 Bnew> ID, NAME =head2 Bnew> ID, NAME =head1 EXAMPLES Please, see the examples under the C directory in the module distribution. =head1 SEE ALSO =over =item * L =back =head1 REPOSITORY L =head1 AUTHOR Gustavo L. de M. Chaves =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2015 by CPqD. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut release-kwalitee.t100644004231004231 53312463542712 17661 0ustar00gustavogustavo000000000000JIRA-Client-0.43/t BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } # this test was generated with Dist::Zilla::Plugin::Test::Kwalitee 2.11 use strict; use warnings; use Test::More 0.88; use Test::Kwalitee 1.21 'kwalitee_ok'; kwalitee_ok(); done_testing; demo-atlassian.conf100644004231004231 210012463542712 20031 0ustar00gustavogustavo000000000000JIRA-Client-0.43/t# Emacs, this is -*- Perl -*- code. my $conf = { url => 'https://jira.atlassian.com/', user => 'gnustavo', pass => undef, project => 'DEMO', issue => { type => 'Bug', summary => 'JIRA::Client test', description => 'Test of http://search.cpan.org/perldoc?JIRA::Client.', duedate => '2020-01-01', priority => 'Trivial', components => [qw/Fuselage Wings/], affectsVersions => [qw/Design Test/], }, subtask => { type => 'Sub-task', summary => 'Subtest of http://search.cpan.org/perldoc?JIRA::Client.', }, subtask_progress => [ # This is a list of argument lists to # progress_workflow_action_safely. Since I don't have # developer role in the Atlassian JIRA instance I cannot # progress any issues. Thus, I keep the following commented # out as an example of the real thing. # ['Start progress'], # ['Close' => {resolution n=> 'Finished'}], ], issue_progress => [ # The comments above for subtask_progress applies here too. # ['Start progress'], # ['Close' => {resolution => 'Finished'}], ], }; examples000755004231004231 012463542712 15503 5ustar00gustavogustavo000000000000JIRA-Client-0.43jira-dump.pl100755004231004231 514012463542712 20073 0ustar00gustavogustavo000000000000JIRA-Client-0.43/examples#!/usr/bin/env perl # Copyright (C) 2012 by CPqD # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . use 5.014; use utf8; use autodie; use warnings; use open ':utf8'; use JIRA::Client; use Data::Dumper; use Getopt::Long; my $usage = "$0 [--projects] [--verbose] JIRAURL USER PASS\n"; my $Projects; my $Verbose; GetOptions( 'projects+' => \$Projects, 'verbose+' => \$Verbose, ) or die $usage; @ARGV == 3 or die "usage: $usage\n"; my $jira = JIRA::Client->new(@ARGV); sub hash_of { my ($array, $key) = @_; $key //= 'name'; my %hash; foreach my $e (@$array) { $hash{$e->{$key}} = $e; } return \%hash; } my %JIRA = ( Configuration => $jira->getConfiguration(), CustomFields => hash_of($jira->getCustomFields()), FavouriteFilters => hash_of($jira->getFavouriteFilters()), IssueTypes => hash_of($jira->getIssueTypes()), Permissions => hash_of($jira->getAllPermissions()), Priorities => hash_of($jira->getPriorities()), Projects => hash_of($jira->getProjectsNoSchemes(), 'key'), Resolutions => hash_of($jira->getResolutions()), ServerInfo => $jira->getServerInfo(), Statuses => hash_of($jira->getStatuses()), SubTaskIssueTypes => hash_of($jira->getSubTaskIssueTypes()), ); if ($Projects) { warn "Grokking ", scalar(keys %{$JIRA{Projects}}), " projects:\n" if $Verbose; foreach my $key (sort keys %{$JIRA{Projects}}) { warn "Grokking project $key\n" if $Verbose; my $project = $JIRA{Projects}{$key}; $project->{info} = { Components => hash_of($jira->getComponents($key)), # IssueTypes => hash_of(jira->getIssueTypesForProject($key)), # Avatars => hash_of(jira->getProjectAvatars($key, 0)), # SecurityLevels => hash_of(jira->getSecurityLevels($key)), # SubTaskIssueTypes => hash_of(jira->getSubTaskIssueTypesForProject($key)), Versions => hash_of($jira->getVersions($key)), }; } } $Data::Dumper::Indent = 1; $Data::Dumper::Sortkeys = 1; say Dumper(\%JIRA); jiraclient.pl100755004231004231 611512463542712 20332 0ustar00gustavogustavo000000000000JIRA-Client-0.43/examples#!/usr/bin/env perl # Sample Perl client accessing JIRA via SOAP using the CPAN # JIRA::Client module. This is mostly a translation of the Python # client example at # http://confluence.atlassian.com/display/JIRA/Creating+a+SOAP+Client. use strict; use warnings; use Data::Dumper; use DateTime; use JIRA::Client; my $jirauser = 'soaptester'; my $passwd = 'soaptester'; my $jira = JIRA::Client->new('http://jira.atlassian.com/', $jirauser, $passwd); my $issue = $jira->getIssue('TST-3410'); print "Retrieved issue:", Dumper($issue), "\n"; my $baseurl = $jira->getServerInfo()->{baseUrl}; # Note: JIRA::Client's create_issue method encapsulates the API's # createIssue, dealing with several name convertions such as issue # types, versions, components, dates, and custom fields. It's usually # much easier to use than the bare method directly. # # These name conversions are performed with implicit calls to the # get_* API methods. They usually require administrative priviledges # to get called. Be warned! my $newissue = $jira->create_issue({ project => 'TST', type => 'Bug', summary => 'Issue created with Perl!' }); print "Created $baseurl/browse/$newissue->{key}\n"; print "Adding comment..\n"; # Note: JIRA::Client converts transparently addComment's first # argument from a RemoteIssue object into an issue key and its second # argument from a string into a RemoteComment object. This kind of # implicit conversion is performed for several methods, making it # easier to use the API. $jira->addComment($newissue, 'Comment added with SOAP'); print "Updating issue..\n"; # Note: JIRA::Client's update_issue method encapsulates the API's # updateIssue, in much the same way as create_issue encapsulates # createIssue above. Note that duedate's value may be specified with a # DateTime object. Also note how you can specify custom fields by # name. $jira->update_issue( $newissue, { summary => '[Updated] Issue created with Perl', type => 'New feature', fixVersions => '1.0.1', duedate => DateTime->today->add(days => 3), custom_fields => { 'Client' => 'CPqD', 'Location' => 'Campinas', }, }, ); print "Resolving issue..\n"; # Note: JIRA::Client's progress_workflow_action_safely method # encapsulates the API's progressWorkflowAction in much the same way # as create_issue encapsulates createIssue above. It also avoids the # need to specify values for all the screen values, lest the # unspecified ones be undefined as a result. Non-specified fields have # their current values fetched from the Issue and inserted in the # paramenters to progressWorkflowAction. $jira->progress_workflow_action_safely( $newissue, 'Resolve Issue', { assigne => 'jefft', fixVersions => '1.1.0', resolution => "Won't Fix", }, ); # This works if you have the right permissions my $user = $jira->createUser("testuser2", "testuser2", "SOAP-created user", 'newuser@localhost'); print "Created user $user\n"; my $group = $jira->getGroup("jira-developers"); $jira->addUserToGroup($group, $user); $jira->addVersion("TST", {name => 'Version 1'}); print "Done!\n"; release-pod-syntax.t100644004231004231 45612463542712 20166 0ustar00gustavogustavo000000000000JIRA-Client-0.43/t#!perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } # This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests. use Test::More; use Test::Pod 1.41; all_pod_files_ok(); jira-filter.pl100644004231004231 557112463542712 20420 0ustar00gustavogustavo000000000000JIRA-Client-0.43/examples#!/usr/bin/env perl # Copyright (C) 2012 by CPqD # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . use utf8; use strict; use warnings; use open ':utf8'; use Getopt::Long; use JIRA::Client; my $usage = "$0 [--limit=LIMIT] JIRAURL JIRAUSER JIRAPASS FILTER\n"; my $Limit = 1000; GetOptions( 'limit=i' => \$Limit, ) or die $usage; my ($Jiraurl, $Jirauser, $Jirapass, @filter) = @ARGV; @filter or die "$usage\nMissing FILTER!\n"; my $Filter = join ' ', @filter; sub jira_filter_issues { my ($jira, $filter, $limit) = @_; $filter =~ s/^\s*"?//; $filter =~ s/"?\s*$//; my $issues = do { if ($filter =~ /^(?:[A-Z]+-\d+\s+)*[A-Z]+-\d+$/i) { # space separated key list [map {$jira->getIssue(uc $_)} split / /, $filter]; } elsif ($filter =~ /^[\w-]+$/i) { # saved filter $jira->getIssuesFromFilterWithLimit($filter, 0, $limit || 1000); } else { # JQL filter $jira->getIssuesFromJqlSearch($filter, $limit || 1000); } }; # Order the issues by project key and then by numeric value using # a Schwartzian transform. map {$_->[2]} sort {$a->[0] cmp $b->[0] or $a->[1] <=> $b->[1]} map {my ($p, $n) = ($_->{key} =~ /([A-Z]+)-(\d+)/); [$p, $n, $_]} @$issues; } my $jira = JIRA::Client->new($Jiraurl, $Jirauser, $Jirapass); my @issues = jira_filter_issues($jira, $Filter, $Limit); foreach my $issue (@issues) { print "$issue->{key}: $issue->{assignee}: '$issue->{summary}'\n"; } __END__ =head1 NAME jira-filter.pl - Lists the JIRA issues form a filter. =head1 SYNOPSIS jira-filter.pl [--limit=LIMIT] JIRAURL JIRAUSER JIRAPASS FILTER =head1 DESCRIPTION This script prints information about each issue found in JIRA matching the specified filter. FILTER can specify issues in three ways: =over =item KEY KEY KEY... A space-separated list of issue keys. =item JQL Expression A JQL Expression (L). =item Saved search filter A saved search filter name (L). =back =head1 OPTIONS =over =item --limit=LIMIT This option limits the number of issues that the filter should output. (Default is 1000.) =back =head1 SEE ALSO =head1 COPYRIGHT Copyright 2012 CPqD. =head1 AUTHOR Gustavo Chaves release-pod-coverage.t100644004231004231 57212463542712 20432 0ustar00gustavogustavo000000000000JIRA-Client-0.43/t#!perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } # This file was automatically generated by Dist::Zilla::Plugin::PodCoverageTests. use Test::Pod::Coverage 1.08; use Pod::Coverage::TrustPod; all_pod_coverage_ok({ coverage_class => 'Pod::Coverage::TrustPod' });