├── .gitignore ├── LICENSE ├── README.adoc ├── code-style-intellij.xml ├── jamira-cli ├── pom.xml └── src │ └── main │ ├── java │ └── org │ │ └── tomitribe │ │ └── jamira │ │ └── cli │ │ ├── AccountCommand.java │ │ ├── AddCommand.java │ │ ├── BulkCreateCommand.java │ │ ├── CreateCommand.java │ │ ├── DeleteCommand.java │ │ ├── Generate.java │ │ ├── GetCommand.java │ │ ├── InvalidAttachementException.java │ │ ├── ListCommand.java │ │ ├── Loader.java │ │ ├── SearchCommand.java │ │ └── UpdateCommand.java │ └── resources │ └── META-INF │ └── services │ └── org.tomitribe.crest.api.Loader ├── jamira-core ├── pom.xml └── src │ ├── main │ ├── java-templates │ │ └── org │ │ │ └── tomitribe │ │ │ └── jamira │ │ │ └── core │ │ │ └── Version.java │ └── java │ │ └── org │ │ └── tomitribe │ │ └── jamira │ │ └── core │ │ ├── Account.java │ │ ├── AccountExistsException.java │ │ ├── Bar.java │ │ ├── Cache.java │ │ ├── Client.java │ │ ├── Formatting.java │ │ ├── Home.java │ │ ├── Input.java │ │ ├── IssueKey.java │ │ ├── Jamira.java │ │ ├── NoAccountSetupException.java │ │ ├── NoIssueSummariesSuppliedException.java │ │ ├── NoSuchAccountExistsException.java │ │ ├── NoSuchIssueTypeException.java │ │ ├── NoSuchPriorityException.java │ │ ├── ProjectKey.java │ │ ├── cache │ │ ├── CachedIssueType.java │ │ ├── CachedMetadataRestClient.java │ │ ├── CachedPriority.java │ │ ├── CachedResolution.java │ │ ├── CachedStatus.java │ │ ├── CompletedPromise.java │ │ └── JsonbInstances.java │ │ └── http │ │ ├── CustomAsynchronousHttpClientFactory.java │ │ └── CustomAsynchronousJiraRestClientFactory.java │ └── test │ └── java │ └── org │ └── tomitribe │ └── jamira │ └── core │ ├── CreateSubtaskExample.java │ ├── Foo.java │ └── GetIssueExample.java ├── jamira-report └── pom.xml ├── jbang-catalog.json └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | .mvn/timing.properties 10 | *.jar 11 | *.class 12 | .classpath 13 | .project 14 | .settings 15 | .idea 16 | *.ipr 17 | *.iml 18 | *.log 19 | *.vscode 20 | *~ 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | # Atlassian JIRA Command-Line (CLI) 2 | 3 | Jamira is a command-line API for Atlassian JIRA using the Tomitribe CREST library. 4 | 5 | ## Installation 6 | 7 | Jamira can be downloaded as a ready-to-go executable from Maven Central 8 | 9 | ---- 10 | curl https://repo1.maven.org/maven2/org/tomitribe/jamira/jamira-cli/0.5/jamira-cli-0.5.sh > /usr/local/bin/jamira 11 | chmod 755 /usr/local/bin/jamira 12 | ---- 13 | 14 | ## Installation using JBang 15 | 16 | Jamira can be installed and run with jbang - it will even setup java for you if you do not have that available. 17 | 18 | --- 19 | curl -Ls sh.jbang.dev | bash -s app install jamira@tomitribe/jamira 20 | jamira 21 | --- 22 | 23 | ## Completion 24 | 25 | Additionally, if you want command completion you can add the following to your `~/.bash_profile`, `~/.bashrc` or similar environment setup scripts: 26 | 27 | ---- 28 | source "$(jamira _completion -f)" 29 | ---- 30 | 31 | ## Setup 32 | 33 | Jamira has accounts in a similar way git has remotes. An account can be setup using the `account add` command 34 | 35 | ---- 36 | Usage: account add Name Username Password URI 37 | ---- 38 | 39 | The `Name` value should be any short identifier with no spaces and all lowercase. Any other command that has an `--account` flag can use that identifier to locate the login information and URL of your desired JIRA server. 40 | 41 | Example setup: 42 | 43 | ---- 44 | $ jamira account add apache elmerfudd "GetTh3Rabb1t" https://issues.apache.org/jira 45 | 46 | $ jamira account list 47 | name username serverUri 48 | -------- ----------- -------------------------------- 49 | apache elmerfudd https://issues.apache.org/jira 50 | ---- 51 | 52 | ## Commands 53 | 54 | ---- 55 | $ jamira 56 | Commands: 57 | 58 | account 59 | bulk-create 60 | create 61 | help 62 | list 63 | ---- 64 | 65 | ### List 66 | 67 | ---- 68 | $ jamira list 69 | Usage: list [subcommand] [options] 70 | 71 | Sub commands: 72 | 73 | components 74 | favourite-filters 75 | fields 76 | groups 77 | issue-link-types 78 | issue-types 79 | priorities 80 | project-roles 81 | projects 82 | resolutions 83 | statuses 84 | subtasks 85 | users 86 | versions 87 | ---- 88 | 89 | #### List Priorities 90 | 91 | For example to list all of the priorities setup in the JIRA install: 92 | 93 | ---- 94 | $ ./jamira-cli/target/jamira list priorities 95 | id name description 96 | ------- ---------- -------------------------------------------------------------------------------------------------------------- 97 | 1 Blocker Blocks development and/or testing work, production could not run 98 | 10000 Urgent This issue should block release until it is resolved, and trigger immediate release once resolved. 99 | 2 Critical Crashes, loss of data, severe memory leak. 100 | 10001 High 101 | 3 Major Major loss of function. 102 | 10002 Normal 103 | 4 Minor Minor loss of function, or other problem where easy workaround is present. 104 | 10003 Low 105 | 5 Trivial Cosmetic problem like misspelt words or misaligned text. 106 | 10100 P0 Outage blocking development and/or testing work; requires immediate and continuous attention 107 | 10101 P1 Critical bug: data loss, total loss of function, or loss of testing signal due to test failures or flakiness 108 | 10102 P2 Default priority. Will be triaged and planned according to community practices. 109 | 10103 P3 Non-urgent bugs, features, and improvements 110 | 10104 P4 Trivial items, spelling errors, etc. 111 | ---- 112 | 113 | #### List Subtasks 114 | 115 | To list all of the subtasks for a specific issue 116 | 117 | ---- 118 | $ jamira list subtasks OPENEJB-142 119 | issueKey summary status.name 120 | -------------- ------------------------------------------------------ ------------- 121 | OPENEJB-145 iTest: StatelessRemoteJndiTests Closed 122 | OPENEJB-146 iTest: StatelessLocalJndiTests Closed 123 | OPENEJB-147 iTest: StatelessHomeIntfcTests Closed 124 | OPENEJB-148 iTest: StatelessEjbHomeTests Closed 125 | OPENEJB-149 iTest: StatelessEjbObjectTests Closed 126 | OPENEJB-150 iTest: StatelessEjbLocalHomeTests Closed 127 | OPENEJB-151 iTest: StatelessEjbLocalObjectTests Closed 128 | OPENEJB-152 iTest: StatelessRemoteIntfcTests Closed 129 | OPENEJB-153 iTest: StatelessLocalIntfcTests Closed 130 | OPENEJB-154 iTest: StatelessHomeHandleTests Closed 131 | ---- 132 | 133 | ### Create 134 | 135 | Can create issues, versions and components. For common issue types there are convenience commands; bug, improvement, new-feature, task, subtask. 136 | 137 | ---- 138 | $ jamira create 139 | Missing sub-command 140 | Usage: create [subcommand] [options] 141 | 142 | Sub commands: 143 | 144 | bug 145 | component 146 | improvement 147 | issue 148 | new-feature 149 | subtask 150 | task 151 | version 152 | wish 153 | 154 | ---- 155 | 156 | #### Create Sub-Task 157 | 158 | Sub-tasks can be created by simply specifying the parent issue key and supplying a text summary to use for the new issue 159 | 160 | ---- 161 | $ jamira create subtask 162 | Missing argument: IssueKey 163 | 164 | Usage: create subtask [options] IssueKey String 165 | 166 | Options: 167 | --account= Shortname of the JIRA server 168 | (default: default) 169 | --affected-version= The names of the versions affected by the issue 170 | --assignee= Username of the person to which the issue should be assigned 171 | --component= The component names relating to the issue 172 | --description= Long description of the issue 173 | --fix-version= The names of the versions affected by the issue 174 | --priority= The name of the priority for the issue 175 | --reporter= Username of the person who is the reporter of the issue 176 | ---- 177 | 178 | 179 | For example: 180 | 181 | ---- 182 | jamira create subtask TOMEE-3002 "Test caching" 183 | ---- 184 | 185 | ### Bulk-create 186 | 187 | When you have several issues you need to create it's advised to use the bulk API. 188 | 189 | ---- 190 | $ jamira bulk-create 191 | Missing sub-command 192 | Usage: bulk-create [subcommand] [options] 193 | 194 | Sub commands: 195 | 196 | issues 197 | subtasks 198 | 199 | ---- 200 | 201 | 202 | #### Bulk-create Subtasks 203 | 204 | Subtasks can be created in bulk using the following command. The summary for each subtask is read 205 | from the piped input stream. This allows several lines of text to be generated on the command line 206 | and each line becomes a subtask. 207 | 208 | ---- 209 | $ jamira bulk-create subtasks 210 | Missing argument: IssueKey 211 | 212 | Usage: bulk-create subtasks [options] IssueKey 213 | 214 | Options: 215 | --account= The shortname of the jira install configured via the `setup` command 216 | (default: default) 217 | --affected-version= The names of the versions affected by the issue 218 | --assignee= Username of the person to which the issue should be assigned 219 | --component= The component names relating to the issue 220 | --fix-version= The names of the versions affected by the issue 221 | --priority= The name of the priority for the issue 222 | --reporter= Username of the person who is the reporter of the issue 223 | 224 | ---- 225 | 226 | For example: 227 | 228 | ---- 229 | $ echo -e "Bulk insert"{1..10}"\n" 230 | Bulk insert1 231 | Bulk insert2 232 | Bulk insert3 233 | Bulk insert4 234 | Bulk insert5 235 | Bulk insert6 236 | Bulk insert7 237 | Bulk insert8 238 | Bulk insert9 239 | Bulk insert10 240 | 241 | $ echo -e "Bulk insert"{1..10}"\n" | jamira bulk-create subtasks TOMEE-3002 242 | TOMEE-3021 243 | TOMEE-3022 244 | TOMEE-3023 245 | TOMEE-3024 246 | TOMEE-3025 247 | TOMEE-3026 248 | TOMEE-3027 249 | TOMEE-3028 250 | TOMEE-3029 251 | TOMEE-3030 252 | 253 | ---- 254 | -------------------------------------------------------------------------------- /code-style-intellij.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | -------------------------------------------------------------------------------- /jamira-cli/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 4.0.0 20 | 21 | 22 | org.tomitribe.jamira 23 | jamira-parent 24 | 0.6-SNAPSHOT 25 | 26 | 27 | jamira-cli 28 | Jamira :: CLI 29 | 30 | 31 | 32 | org.tomitribe.jamira 33 | jamira-core 34 | 0.6-SNAPSHOT 35 | 36 | 37 | org.tomitribe 38 | tomitribe-crest 39 | 0.15 40 | 41 | 42 | org.apache.geronimo.specs 43 | geronimo-validation_1.1_spec 44 | 1.0 45 | 46 | 47 | org.apache.bval 48 | bval-jsr 49 | 1.1.2 50 | 51 | 52 | org.apache.geronimo.specs 53 | geronimo-json_1.1_spec 54 | 1.0 55 | 56 | 57 | org.apache.geronimo.specs 58 | geronimo-jsonb_1.0_spec 59 | 1.0 60 | 61 | 62 | org.apache.johnzon 63 | johnzon-jsonb 64 | 1.2.8 65 | 66 | 67 | org.apache.tomcat 68 | tomcat-el-api 69 | 10.0.5 70 | 71 | 72 | org.apache.tomcat 73 | tomcat-jasper-el 74 | 10.0.5 75 | 76 | 77 | junit 78 | junit 79 | 4.13.1 80 | test 81 | 82 | 83 | org.projectlombok 84 | lombok 85 | 1.18.12 86 | compile 87 | 88 | 89 | 90 | install 91 | 92 | 93 | maven-shade-plugin 94 | 2.1 95 | 96 | 97 | package 98 | 99 | shade 100 | 101 | 102 | true 103 | 104 | 105 | org.tomitribe.crest.Main 106 | 107 | 108 | 109 | 110 | *:* 111 | 112 | META-INF/*.SF 113 | META-INF/*.DSA 114 | META-INF/*.RSA 115 | META-INF/LICENSE 116 | LICENSE 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | org.skife.maven 127 | really-executable-jar-maven-plugin 128 | 1.4.0 129 | 130 | 131 | package 132 | 133 | really-executable-jar 134 | 135 | 136 | 137 | 138 | -Dcmd="$0" $JAMIRA_OPTS -Xmx1G 139 | jamira 140 | true 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /jamira-cli/src/main/java/org/tomitribe/jamira/cli/AccountCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.cli; 17 | 18 | import lombok.Data; 19 | import org.tomitribe.crest.api.Command; 20 | import org.tomitribe.jamira.core.Account; 21 | import org.tomitribe.jamira.core.AccountExistsException; 22 | import org.tomitribe.jamira.core.Formatting; 23 | import org.tomitribe.jamira.core.Home; 24 | import org.tomitribe.jamira.core.Jamira; 25 | import org.tomitribe.jamira.core.NoSuchAccountExistsException; 26 | 27 | import java.io.IOException; 28 | import java.net.URI; 29 | import java.util.List; 30 | import java.util.stream.Collectors; 31 | 32 | @Command("account") 33 | public class AccountCommand { 34 | 35 | /** 36 | * Save login information for a JIRA install 37 | * 38 | * @param name Short name of the account in the format of `[a-z]+`. 39 | * @param username Username required to login to the server 40 | * @param password Password required to login to the server 41 | * @param uri URI of the server 42 | */ 43 | @Command 44 | public void add(final Name name, final Username username, final Password password, final URI uri) throws IOException { 45 | final Jamira jamira = Home.get().jamira(); 46 | final Account account = jamira.account(name.getName()); 47 | 48 | if (account.exists()) { 49 | throw new AccountExistsException(name.getName()); 50 | } 51 | 52 | account.setUsername(username.getUsername()); 53 | account.setPassword(password.getPassword()); 54 | account.setServerUri(uri); 55 | account.save(); 56 | } 57 | 58 | /** 59 | * Delete login information for a JIRA install 60 | * 61 | * @param name Short name of the account in the format of `[a-z]+`. 62 | */ 63 | @Command 64 | public void remove(final Name name) { 65 | final Jamira jamira = Home.get().jamira(); 66 | final Account account = jamira.account(name.getName()); 67 | 68 | if (!account.exists()) { 69 | throw new NoSuchAccountExistsException(name.getName()); 70 | } 71 | 72 | account.delete(); 73 | } 74 | 75 | /** 76 | * List JIRA installs configured 77 | */ 78 | @Command 79 | public String[][] list() { 80 | final Jamira jamira = Home.get().jamira(); 81 | 82 | final List accounts = jamira.accounts().collect(Collectors.toList()); 83 | return Formatting.asTable(accounts, "name username serverUri"); 84 | } 85 | 86 | @Data 87 | public static class Name { 88 | private final String name; 89 | } 90 | 91 | @Data 92 | public static class Username { 93 | private final String username; 94 | } 95 | 96 | @Data 97 | public static class Password { 98 | private final String password; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /jamira-cli/src/main/java/org/tomitribe/jamira/cli/AddCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.cli; 17 | 18 | import com.atlassian.jira.rest.client.api.IssueRestClient; 19 | import com.atlassian.jira.rest.client.api.domain.Comment; 20 | import com.atlassian.jira.rest.client.api.domain.Issue; 21 | import io.atlassian.util.concurrent.Promise; 22 | import org.joda.time.DateTime; 23 | import org.tomitribe.crest.api.Command; 24 | import org.tomitribe.crest.api.Default; 25 | import org.tomitribe.crest.api.In; 26 | import org.tomitribe.crest.api.Option; 27 | import org.tomitribe.jamira.core.Account; 28 | import org.tomitribe.jamira.core.Client; 29 | import org.tomitribe.jamira.core.IssueKey; 30 | import org.tomitribe.util.IO; 31 | 32 | import java.io.File; 33 | import java.io.IOException; 34 | import java.io.InputStream; 35 | import java.util.concurrent.ExecutionException; 36 | 37 | @Command("add") 38 | public class AddCommand { 39 | 40 | @Command("attachment") 41 | public void attachment(final IssueKey issueKey, 42 | @Option("account") @Default("default") final Account account, 43 | final File[] files) throws ExecutionException, InterruptedException { 44 | for (final File file : files) { 45 | if (!file.exists()) throw new InvalidAttachementException(file, "Does not exist"); 46 | if (!file.canRead()) throw new InvalidAttachementException(file, "Cannot be read"); 47 | } 48 | 49 | final Client client = account.getClient(); 50 | final IssueRestClient issueClient = client.getIssueClient(); 51 | 52 | final Issue issue = issueClient.getIssue(issueKey.getKey()).get(); 53 | 54 | final Promise voidPromise = issueClient.addAttachments(issue.getAttachmentsUri(), files); 55 | voidPromise.get(); 56 | } 57 | 58 | // @Command("comment") 59 | public void comment(final IssueKey issueKey, 60 | final String message, 61 | @Option("account") @Default("default") final Account account) throws ExecutionException, InterruptedException, IOException { 62 | 63 | final Client client = account.getClient(); 64 | final IssueRestClient issueClient = client.getIssueClient(); 65 | 66 | final Issue issue = issueClient.getIssue(issueKey.getKey()).get(); 67 | final DateTime creationDate = null; 68 | final Comment comment = new Comment(null, message, null, null, creationDate, creationDate, null, null); 69 | 70 | issueClient.addComment(issue.getCommentsUri(), comment).get(); 71 | } 72 | 73 | @Command("comment") 74 | public void comment(final IssueKey issueKey, 75 | @In final InputStream pipedInput, 76 | @Option("account") @Default("default") final Account account) throws ExecutionException, InterruptedException, IOException { 77 | 78 | final Client client = account.getClient(); 79 | final IssueRestClient issueClient = client.getIssueClient(); 80 | 81 | final String content = IO.slurp(pipedInput); 82 | 83 | final Issue issue = issueClient.getIssue(issueKey.getKey()).get(); 84 | final DateTime creationDate = null; 85 | final Comment comment = new Comment(null, content, null, null, creationDate, creationDate, null, null); 86 | 87 | issueClient.addComment(issue.getCommentsUri(), comment).get(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /jamira-cli/src/main/java/org/tomitribe/jamira/cli/BulkCreateCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.tomitribe.jamira.cli; 18 | 19 | import com.atlassian.jira.rest.client.api.IssueRestClient; 20 | import com.atlassian.jira.rest.client.api.domain.BasicIssue; 21 | import com.atlassian.jira.rest.client.api.domain.BasicProject; 22 | import com.atlassian.jira.rest.client.api.domain.BulkOperationResult; 23 | import com.atlassian.jira.rest.client.api.domain.Issue; 24 | import com.atlassian.jira.rest.client.api.domain.IssueFieldId; 25 | import com.atlassian.jira.rest.client.api.domain.IssueType; 26 | import com.atlassian.jira.rest.client.api.domain.input.FieldInput; 27 | import com.atlassian.jira.rest.client.api.domain.input.IssueInput; 28 | import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder; 29 | import org.tomitribe.crest.api.Command; 30 | import org.tomitribe.crest.api.Default; 31 | import org.tomitribe.crest.api.In; 32 | import org.tomitribe.crest.api.Option; 33 | import org.tomitribe.crest.api.PrintOutput; 34 | import org.tomitribe.jamira.core.Account; 35 | import org.tomitribe.jamira.core.Client; 36 | import org.tomitribe.jamira.core.IssueKey; 37 | import org.tomitribe.jamira.core.NoIssueSummariesSuppliedException; 38 | import org.tomitribe.jamira.core.ProjectKey; 39 | import org.tomitribe.util.IO; 40 | 41 | import java.io.InputStream; 42 | import java.util.List; 43 | import java.util.stream.Collectors; 44 | import java.util.stream.Stream; 45 | 46 | @Command("bulk-create") 47 | public class BulkCreateCommand { 48 | 49 | /** 50 | * Bulk-creates subtasks for the specified parent issue by reading the 51 | * summaries from the piped input. It is expected each summary is on 52 | * a separate line. 53 | * 54 | * Any flags supplied such as `fix-version` or `component` will be applied 55 | * to all sub-tasks created 56 | * 57 | * @param parentKey The issue key of the parent issue 58 | * @param input Issue summaries will be read via piped input 59 | * @param assignee Username of the person to which the issue should be assigned 60 | * @param reporter Username of the person who is the reporter of the issue 61 | * @param priority The name of the priority for the issue. See the `list priorities` command 62 | * @param affectedVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 63 | * @param fixVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 64 | * @param components The component names relating to the issue. See the `list components` command. Flag may be repeated. 65 | * @param account The shortname of the jira install configured via the `setup` command 66 | */ 67 | @Command("subtasks") 68 | public PrintOutput createSubtask(final IssueKey parentKey, 69 | @In final InputStream input, 70 | @Option("assignee") final String assignee, 71 | @Option("reporter") final String reporter, 72 | @Option("priority") final String priority, 73 | @Option("affected-version") final List affectedVersions, 74 | @Option("fix-version") final List fixVersions, 75 | @Option("component") final List components, 76 | @Option("account") @Default("default") final Account account) throws Exception { 77 | 78 | final Client client = account.getClient(); 79 | final IssueRestClient issueClient = client.getIssueClient(); 80 | 81 | final Issue parent = issueClient.getIssue(parentKey.getKey()).get(); 82 | final IssueType type = client.getIssueType("sub-task"); 83 | 84 | final String slurp = IO.slurp(input); 85 | final List issues = Stream.of(slurp.split(System.lineSeparator())) 86 | .map(String::trim) 87 | .filter(s -> s.length() >= 0) 88 | .map(s -> new IssueInputBuilder(parent.getProject(), type, s)) 89 | .peek(issue -> { 90 | issue.setFieldValue("parent", parent); 91 | if (affectedVersions != null) issue.setAffectedVersionsNames(affectedVersions); 92 | if (fixVersions != null) issue.setFixVersionsNames(fixVersions); 93 | if (components != null) issue.setComponentsNames(components); 94 | if (assignee != null) issue.setAssigneeName(assignee); 95 | if (reporter != null) issue.setReporterName(reporter); 96 | if (priority != null) issue.setPriority(client.getPriority(priority)); 97 | }) 98 | .map(IssueInputBuilder::build) 99 | .collect(Collectors.toList()); 100 | 101 | if (issues.size() == 0) { 102 | throw new NoIssueSummariesSuppliedException(); 103 | } 104 | 105 | final BulkOperationResult result = issueClient.createIssues(issues).get(); 106 | 107 | return out -> { 108 | 109 | result.getErrors().forEach(errorResult -> { 110 | final int number = errorResult.getFailedElementNumber(); 111 | final IssueInput failedIssue = issues.get(number); 112 | final FieldInput field = failedIssue.getField(IssueFieldId.SUMMARY_FIELD.id); 113 | out.printf("FAILED: %s%n", field.getValue()); 114 | errorResult.getElementErrors().getErrorMessages().forEach(s -> { 115 | out.printf(" ERROR: %s%n", s); 116 | }); 117 | }); 118 | 119 | result.getIssues().forEach(basicIssue -> { 120 | out.printf("%s%n", basicIssue.getKey()); 121 | }); 122 | }; 123 | } 124 | 125 | /** 126 | * Bulk-creates issues for the specified project by reading the 127 | * summaries from the piped input. It is expected each summary is on 128 | * a separate line. 129 | * 130 | * Any flags supplied such as `fix-version` or `component` will be applied 131 | * to all issues created 132 | * 133 | * @param projectKey Text key for the JIRA project. Example `TOMEE` 134 | * @param input Issue summaries will be read via piped input 135 | * @param typeName The name of the issue type. See the `list issue-types` command 136 | * @param assignee Username of the person to which the issue should be assigned 137 | * @param reporter Username of the person who is the reporter of the issue 138 | * @param priority The name of the priority for the issue. See the `list priorities` command 139 | * @param affectedVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 140 | * @param fixVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 141 | * @param components The component names relating to the issue. See the `list components` command. Flag may be repeated. 142 | * @param account The shortname of the jira install configured via the `setup` command 143 | */ 144 | @Command("issues") 145 | public PrintOutput issues(final ProjectKey projectKey, 146 | @In final InputStream input, 147 | @Option("type") @Default("bug") final String typeName, 148 | @Option("assignee") final String assignee, 149 | @Option("reporter") final String reporter, 150 | @Option("priority") final String priority, 151 | @Option("affected-version") final List affectedVersions, 152 | @Option("fix-version") final List fixVersions, 153 | @Option("component") final List components, 154 | @Option("account") @Default("default") final Account account) throws Exception { 155 | 156 | final Client client = account.getClient(); 157 | final IssueRestClient issueClient = client.getIssueClient(); 158 | final IssueType type = client.getIssueType(typeName); 159 | 160 | final String slurp = IO.slurp(input); 161 | final List issues = Stream.of(slurp.split(System.lineSeparator())) 162 | .map(String::trim) 163 | .filter(s -> s.length() >= 0) 164 | .map(s -> { 165 | final BasicProject project = new BasicProject(null, projectKey.getKey(), null, null); 166 | return new IssueInputBuilder(project, type, s); 167 | }) 168 | .peek(issue -> { 169 | if (affectedVersions != null) issue.setAffectedVersionsNames(affectedVersions); 170 | if (fixVersions != null) issue.setFixVersionsNames(fixVersions); 171 | if (components != null) issue.setComponentsNames(components); 172 | if (assignee != null) issue.setAssigneeName(assignee); 173 | if (reporter != null) issue.setReporterName(reporter); 174 | if (priority != null) issue.setPriority(client.getPriority(priority)); 175 | }) 176 | .map(IssueInputBuilder::build) 177 | .collect(Collectors.toList()); 178 | 179 | if (issues.size() == 0) { 180 | throw new NoIssueSummariesSuppliedException(); 181 | } 182 | 183 | final BulkOperationResult result = issueClient.createIssues(issues).get(); 184 | 185 | return out -> { 186 | 187 | result.getErrors().forEach(errorResult -> { 188 | final int number = errorResult.getFailedElementNumber(); 189 | final IssueInput failedIssue = issues.get(number); 190 | final FieldInput field = failedIssue.getField(IssueFieldId.SUMMARY_FIELD.id); 191 | out.printf("FAILED: %s%n", field.getValue()); 192 | errorResult.getElementErrors().getErrorMessages().forEach(s -> { 193 | out.printf(" ERROR: %s%n", s); 194 | }); 195 | }); 196 | 197 | result.getIssues().forEach(basicIssue -> { 198 | out.printf("%s%n", basicIssue.getKey()); 199 | }); 200 | }; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /jamira-cli/src/main/java/org/tomitribe/jamira/cli/CreateCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.tomitribe.jamira.cli; 18 | 19 | import com.atlassian.jira.rest.client.api.ComponentRestClient; 20 | import com.atlassian.jira.rest.client.api.IssueRestClient; 21 | import com.atlassian.jira.rest.client.api.VersionRestClient; 22 | import com.atlassian.jira.rest.client.api.domain.BasicIssue; 23 | import com.atlassian.jira.rest.client.api.domain.Component; 24 | import com.atlassian.jira.rest.client.api.domain.Issue; 25 | import com.atlassian.jira.rest.client.api.domain.IssueType; 26 | import com.atlassian.jira.rest.client.api.domain.Project; 27 | import com.atlassian.jira.rest.client.api.domain.Version; 28 | import com.atlassian.jira.rest.client.api.domain.input.ComponentInput; 29 | import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder; 30 | import com.atlassian.jira.rest.client.api.domain.input.VersionInputBuilder; 31 | import org.tomitribe.crest.api.Command; 32 | import org.tomitribe.crest.api.Default; 33 | import org.tomitribe.crest.api.In; 34 | import org.tomitribe.crest.api.Option; 35 | import org.tomitribe.jamira.core.Account; 36 | import org.tomitribe.jamira.core.Client; 37 | import org.tomitribe.jamira.core.Input; 38 | import org.tomitribe.jamira.core.IssueKey; 39 | import org.tomitribe.jamira.core.ProjectKey; 40 | 41 | import java.io.IOException; 42 | import java.io.InputStream; 43 | import java.util.List; 44 | import java.util.concurrent.ExecutionException; 45 | 46 | @Command("create") 47 | public class CreateCommand { 48 | 49 | /** 50 | * Creates a new version (which logically belongs to a project) 51 | * 52 | * @param projectKey Text key for the JIRA project. Example `TOMEE` 53 | * @param versionName Version string. Example `9.0.0-M7` 54 | * @param description Long description of the version 55 | * @param released True if this version has been released 56 | * @param archived True if this version is archived 57 | * @param account Shortname of the JIRA server 58 | */ 59 | @Command 60 | public String version(final ProjectKey projectKey, final String versionName, 61 | @Option("description") final String description, 62 | @Option("released") final Boolean released, 63 | @Option("archived") final Boolean archived, 64 | @Option("account") @Default("default") final Account account) throws ExecutionException, InterruptedException { 65 | final Client client = account.getClient(); 66 | final VersionRestClient versionRestClient = client.getVersionRestClient(); 67 | final VersionInputBuilder versionInputBuilder = new VersionInputBuilder(projectKey.getKey()); 68 | versionInputBuilder.setName(versionName); 69 | if (description != null) versionInputBuilder.setDescription(description); 70 | if (released != null) versionInputBuilder.setReleased(released); 71 | if (archived != null) versionInputBuilder.setArchived(archived); 72 | final Version version = versionRestClient.createVersion(versionInputBuilder.build()).get(); 73 | return String.format("%s %s", version.getId(), version.getName()); 74 | } 75 | 76 | /** 77 | * Create a component in the respective JIRA project 78 | * 79 | * @param projectKey Text key for the JIRA project. Example `TOMEE` 80 | * @param componentName Display name of the component 81 | * @param description Long description of the component 82 | * @param leadUsername Username of the component lead 83 | * @param account Shortname of the JIRA server 84 | */ 85 | @Command 86 | public String component(final ProjectKey projectKey, final String componentName, 87 | @Option("description") final String description, 88 | @Option("lead-username") final String leadUsername, 89 | @Option("account") @Default("default") final Account account) throws ExecutionException, InterruptedException { 90 | 91 | final Client client = account.getClient(); 92 | final ComponentRestClient componentClient = client.getComponentClient(); 93 | final ComponentInput componentInput = new ComponentInput(componentName, description, leadUsername, null); 94 | final Component component = componentClient.createComponent(projectKey.getKey(), componentInput).get(); 95 | return String.format("%s %s", component.getId(), component.getName()); 96 | } 97 | 98 | //CHECKSTYLE:OFF 99 | 100 | /** 101 | * Create a subtask for the specified JIRA issue 102 | * 103 | * @param parentKey The issue key for the parent issue. Example TOMEE-123 104 | * @param summary The title of the subtask 105 | * @param descriptionArg Long description of the issue. Can also be piped to the command via stdin. 106 | * @param assignee Username of the person to which the issue should be assigned 107 | * @param reporter Username of the person who is the reporter of the issue 108 | * @param priority The name of the priority for the issue. See the `list priorities` command 109 | * @param affectedVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 110 | * @param fixVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 111 | * @param components The component names relating to the issue. See the `list components` command. Flag may be repeated. 112 | * @param account Shortname of the JIRA server 113 | */ 114 | @Command("subtask") 115 | public String subtask(final IssueKey parentKey, 116 | final String summary, 117 | @Option("description") String descriptionArg, 118 | @Option("assignee") final String assignee, 119 | @Option("reporter") final String reporter, 120 | @Option("priority") final String priority, 121 | @Option("affected-version") final List affectedVersions, 122 | @Option("fix-version") final List fixVersions, 123 | @Option("component") final List components, 124 | @In final InputStream pipedInput, 125 | @Option("account") @Default("default") final Account account) throws Exception { 126 | 127 | final Client client = account.getClient(); 128 | 129 | final Issue parent = client.getIssueClient().getIssue(parentKey.getKey()).get(); 130 | final IssueType type = client.getIssueType("sub-task"); 131 | 132 | final String description = Input.read(pipedInput, descriptionArg); 133 | 134 | final IssueInputBuilder issue = new IssueInputBuilder(parent.getProject(), type, summary); 135 | if (affectedVersions != null) issue.setAffectedVersionsNames(affectedVersions); 136 | if (fixVersions != null) issue.setFixVersionsNames(fixVersions); 137 | if (components != null) issue.setComponentsNames(components); 138 | if (assignee != null) issue.setAssigneeName(assignee); 139 | if (reporter != null) issue.setReporterName(reporter); 140 | if (description != null) issue.setDescription(description); 141 | if (priority != null) issue.setPriority(client.getPriority(priority)); 142 | 143 | issue.setFieldValue("parent", parent); 144 | 145 | final BasicIssue createdIssue = client.getIssueClient().createIssue(issue.build()).get(); 146 | 147 | return createdIssue.getKey().trim(); 148 | } 149 | 150 | 151 | /** 152 | * Create a JIRA issue of type task 153 | * @param projectKey Text key for the JIRA project. Example `TOMEE` 154 | * @param summary Text to use as the title of the issue 155 | * @param description Long description of the issue. Can also be piped to the command via stdin. 156 | * @param assignee Username of the person to which the issue should be assigned 157 | * @param reporter Username of the person who is the reporter of the issue 158 | * @param priority The name of the priority for the issue. See the `list priorities` command 159 | * @param affectedVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 160 | * @param fixVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 161 | * @param components The component names relating to the issue. See the `list components` command. Flag may be repeated. 162 | * @param account Shortname of the JIRA server 163 | */ 164 | @Command("task") 165 | public String task(final ProjectKey projectKey, 166 | final String summary, 167 | @Option("description") final String description, 168 | @Option("assignee") final String assignee, 169 | @Option("reporter") final String reporter, 170 | @Option("priority") final String priority, 171 | @Option("affected-version") final List affectedVersions, 172 | @Option("fix-version") final List fixVersions, 173 | @Option("component") final List components, 174 | @In final InputStream pipedInput, 175 | @Option("account") @Default("default") final Account account) throws Exception { 176 | 177 | return createIssue("task", projectKey, summary, description, assignee, 178 | reporter, priority, affectedVersions, fixVersions, 179 | components, pipedInput, account); 180 | } 181 | 182 | /** 183 | * Create a JIRA issue of type improvement 184 | * @param projectKey Text key for the JIRA project. Example `TOMEE` 185 | * @param summary Text to use as the title of the issue 186 | * @param description Long description of the issue. Can also be piped to the command via stdin. 187 | * @param assignee Username of the person to which the issue should be assigned 188 | * @param reporter Username of the person who is the reporter of the issue 189 | * @param priority The name of the priority for the issue. See the `list priorities` command 190 | * @param affectedVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 191 | * @param fixVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 192 | * @param components The component names relating to the issue. See the `list components` command. Flag may be repeated. 193 | * @param account Shortname of the JIRA server 194 | */ 195 | @Command("improvement") 196 | public String improvement(final ProjectKey projectKey, 197 | final String summary, 198 | @Option("description") final String description, 199 | @Option("assignee") final String assignee, 200 | @Option("reporter") final String reporter, 201 | @Option("priority") final String priority, 202 | @Option("affected-version") final List affectedVersions, 203 | @Option("fix-version") final List fixVersions, 204 | @Option("component") final List components, 205 | @In final InputStream pipedInput, 206 | @Option("account") @Default("default") final Account account) throws Exception { 207 | 208 | return createIssue("improvement", projectKey, summary, description, assignee, 209 | reporter, priority, affectedVersions, fixVersions, 210 | components, pipedInput, account); 211 | } 212 | 213 | /** 214 | * Create a JIRA issue of type wish 215 | * @param projectKey Text key for the JIRA project. Example `TOMEE` 216 | * @param summary Text to use as the title of the issue 217 | * @param description Long description of the issue. Can also be piped to the command via stdin. 218 | * @param assignee Username of the person to which the issue should be assigned 219 | * @param reporter Username of the person who is the reporter of the issue 220 | * @param priority The name of the priority for the issue. See the `list priorities` command 221 | * @param affectedVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 222 | * @param fixVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 223 | * @param components The component names relating to the issue. See the `list components` command. Flag may be repeated. 224 | * @param account Shortname of the JIRA server 225 | */ 226 | @Command("wish") 227 | public String wish(final ProjectKey projectKey, 228 | final String summary, 229 | @Option("description") final String description, 230 | @Option("assignee") final String assignee, 231 | @Option("reporter") final String reporter, 232 | @Option("priority") final String priority, 233 | @Option("affected-version") final List affectedVersions, 234 | @Option("fix-version") final List fixVersions, 235 | @Option("component") final List components, 236 | @In final InputStream pipedInput, 237 | @Option("account") @Default("default") final Account account) throws Exception { 238 | 239 | return createIssue("wish", projectKey, summary, description, assignee, 240 | reporter, priority, affectedVersions, fixVersions, 241 | components, pipedInput, account); 242 | } 243 | 244 | /** 245 | * Create a JIRA issue of type bug 246 | * @param projectKey Text key for the JIRA project. Example `TOMEE` 247 | * @param summary Text to use as the title of the issue 248 | * @param description Long description of the issue. Can also be piped to the command via stdin. 249 | * @param assignee Username of the person to which the issue should be assigned 250 | * @param reporter Username of the person who is the reporter of the issue 251 | * @param priority The name of the priority for the issue. See the `list priorities` command 252 | * @param affectedVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 253 | * @param fixVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 254 | * @param components The component names relating to the issue. See the `list components` command. Flag may be repeated. 255 | * @param account Shortname of the JIRA server 256 | */ 257 | @Command("bug") 258 | public String bug(final ProjectKey projectKey, 259 | final String summary, 260 | @Option("description") final String description, 261 | @Option("assignee") final String assignee, 262 | @Option("reporter") final String reporter, 263 | @Option("priority") final String priority, 264 | @Option("affected-version") final List affectedVersions, 265 | @Option("fix-version") final List fixVersions, 266 | @Option("component") final List components, 267 | @In final InputStream pipedInput, 268 | @Option("account") @Default("default") final Account account) throws Exception { 269 | 270 | return createIssue("bug", projectKey, summary, description, assignee, 271 | reporter, priority, affectedVersions, fixVersions, 272 | components, pipedInput, account); 273 | } 274 | 275 | /** 276 | * Create a JIRA issue of the specified type 277 | * @param projectKey Text key for the JIRA project. Example `TOMEE` 278 | * @param summary Text to use as the title of the issue 279 | * @param type The name of the issue type. See the `list issue-types` command 280 | * @param description Long description of the issue. Can also be piped to the command via stdin. 281 | * @param assignee Username of the person to which the issue should be assigned 282 | * @param reporter Username of the person who is the reporter of the issue 283 | * @param priority The name of the priority for the issue. See the `list priorities` command 284 | * @param affectedVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 285 | * @param fixVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 286 | * @param components The component names relating to the issue. See the `list components` command. Flag may be repeated. 287 | * @param account Shortname of the JIRA server 288 | */ 289 | @Command("issue") 290 | public String issue(final ProjectKey projectKey, 291 | final String summary, 292 | @Option("type") @Default("bug") final String type, 293 | @Option("description") final String description, 294 | @Option("assignee") final String assignee, 295 | @Option("reporter") final String reporter, 296 | @Option("priority") final String priority, 297 | @Option("affected-version") final List affectedVersions, 298 | @Option("fix-version") final List fixVersions, 299 | @Option("component") final List components, 300 | @In final InputStream pipedInput, 301 | @Option("account") @Default("default") final Account account) throws Exception { 302 | 303 | return createIssue(type, projectKey, summary, description, assignee, 304 | reporter, priority, affectedVersions, fixVersions, 305 | components, pipedInput, account); 306 | } 307 | 308 | /** 309 | * Create a JIRA issue of type bug 310 | * @param projectKey Text key for the JIRA project. Example `TOMEE` 311 | * @param summary Text to use as the title of the issue 312 | * @param description Long description of the issue. Can also be piped to the command via stdin. 313 | * @param assignee Username of the person to which the issue should be assigned 314 | * @param reporter Username of the person who is the reporter of the issue 315 | * @param priority The name of the priority for the issue. See the `list priorities` command 316 | * @param affectedVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 317 | * @param fixVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 318 | * @param components The component names relating to the issue. See the `list components` command. Flag may be repeated. 319 | * @param account Shortname of the JIRA server 320 | */ 321 | @Command("new-feature") 322 | public String newFeature(final ProjectKey projectKey, 323 | final String summary, 324 | @Option("description") final String description, 325 | @Option("assignee") final String assignee, 326 | @Option("reporter") final String reporter, 327 | @Option("priority") final String priority, 328 | @Option("affected-version") final List affectedVersions, 329 | @Option("fix-version") final List fixVersions, 330 | @Option("component") final List components, 331 | @In final InputStream pipedInput, 332 | @Option("account") @Default("default") final Account account) throws Exception { 333 | 334 | return createIssue("new feature", projectKey, summary, description, assignee, 335 | reporter, priority, affectedVersions, fixVersions, 336 | components, pipedInput, account); 337 | } 338 | 339 | private static String createIssue(final String typeName, final ProjectKey projectKey, 340 | final String summary, final String descriptionArg, 341 | final String assignee, final String reporter, 342 | final String priority, final List affectedVersions, 343 | final List fixVersions, final List components, 344 | final InputStream pipedInput, 345 | final Account account) throws InterruptedException, java.util.concurrent.ExecutionException, IOException { 346 | final Client client = account.getClient(); 347 | final Project project = client.getProjectClient().getProject(projectKey.getKey()).get(); 348 | final IssueType type = client.getIssueType(typeName); 349 | 350 | final String description = Input.read(pipedInput, descriptionArg); 351 | 352 | final IssueInputBuilder issue = new IssueInputBuilder(project, type, summary); 353 | 354 | if (affectedVersions != null) issue.setAffectedVersionsNames(affectedVersions); 355 | if (fixVersions != null) issue.setFixVersionsNames(fixVersions); 356 | if (components != null) issue.setComponentsNames(components); 357 | if (assignee != null) issue.setAssigneeName(assignee); 358 | if (reporter != null) issue.setReporterName(reporter); 359 | if (description != null) issue.setDescription(description); 360 | if (priority != null) issue.setPriority(client.getPriority(priority)); 361 | 362 | final IssueRestClient issueClient = client.getIssueClient(); 363 | final BasicIssue createdIssue = issueClient.createIssue(issue.build()).get(); 364 | 365 | return createdIssue.getKey(); 366 | } 367 | //CHECKSTYLE:ON 368 | 369 | } 370 | -------------------------------------------------------------------------------- /jamira-cli/src/main/java/org/tomitribe/jamira/cli/DeleteCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.cli; 17 | 18 | public class DeleteCommand { 19 | } 20 | -------------------------------------------------------------------------------- /jamira-cli/src/main/java/org/tomitribe/jamira/cli/Generate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.cli; 17 | 18 | import org.tomitribe.util.StringTemplate; 19 | import org.tomitribe.util.Strings; 20 | 21 | import java.util.Arrays; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | 25 | public class Generate { 26 | 27 | private final List objects = Arrays.asList( 28 | "Audit", 29 | "Component", 30 | "Group", 31 | "Issue", 32 | "Metadata", 33 | "MyPermissions", 34 | "Project", 35 | "ProjectRoles", 36 | "Search", 37 | "Session", 38 | "User", 39 | "Version" 40 | ); 41 | 42 | private final List issueTypes = Arrays.asList(); 43 | 44 | public static void main(String[] args) { 45 | new Generate().generateListCommands(); 46 | } 47 | 48 | private void generateListCommands() { 49 | for (final String className : objects) { 50 | String variableName = Strings.lcfirst(className); 51 | 52 | final StringTemplate template = new StringTemplate("" + 53 | " @Command(\"{variableName}s\")\n" + 54 | " public String[][] {variableName}s(final ProjectKey projectKey,\n" + 55 | " @Option(\"fields\") @Default(\"name released releaseDate\") final String fields) throws Exception {\n" + 56 | " final {className}Client {variableName}Client = client.get{className}Client();\n" + 57 | " return asTable({variableName}Client.getVersions(), fields.split(\"[ ,]+\"));\n" + 58 | " }\n"); 59 | 60 | final HashMap map = new HashMap<>(); 61 | map.put("variableName", variableName); 62 | map.put("className", className); 63 | System.out.println(template.apply(map)); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /jamira-cli/src/main/java/org/tomitribe/jamira/cli/GetCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.cli; 17 | 18 | public class GetCommand { 19 | } 20 | -------------------------------------------------------------------------------- /jamira-cli/src/main/java/org/tomitribe/jamira/cli/InvalidAttachementException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.cli; 17 | 18 | import org.tomitribe.crest.api.Exit; 19 | 20 | import java.io.File; 21 | 22 | @Exit(8) 23 | public class InvalidAttachementException extends IllegalArgumentException { 24 | public InvalidAttachementException(final File file, final String s) { 25 | super(String.format("%s %s", s, file.getAbsolutePath())); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /jamira-cli/src/main/java/org/tomitribe/jamira/cli/ListCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.tomitribe.jamira.cli; 18 | 19 | import com.atlassian.jira.rest.client.api.GroupRestClient; 20 | import com.atlassian.jira.rest.client.api.MetadataRestClient; 21 | import com.atlassian.jira.rest.client.api.SearchRestClient; 22 | import com.atlassian.jira.rest.client.api.domain.Issue; 23 | import com.atlassian.jira.rest.client.api.domain.Project; 24 | import org.tomitribe.crest.api.Command; 25 | import org.tomitribe.crest.api.Default; 26 | import org.tomitribe.crest.api.Option; 27 | import org.tomitribe.jamira.core.Account; 28 | import org.tomitribe.jamira.core.Client; 29 | import org.tomitribe.jamira.core.Formatting; 30 | import org.tomitribe.jamira.core.IssueKey; 31 | import org.tomitribe.jamira.core.ProjectKey; 32 | 33 | @Command("list") 34 | public class ListCommand { 35 | 36 | /** 37 | * List the subtasks for the specified parent task 38 | * @param parent The issue key of the parent issue 39 | * @param account The shortname of the jira install configured via the `setup` command 40 | * @param fields Select the fields desired in the final output. Use `--fields=all` to see all possible fields. 41 | * @param sort Sort the results by the specified field or fields. 42 | */ 43 | @Command("subtasks") 44 | public String[][] subtasks(final IssueKey parent, 45 | @Option("account") @Default("default") final Account account, 46 | @Option("fields") @Default("issueKey summary status.name") final String fields, 47 | @Option("sort") @Default("issueType.name priority.name status.name") final String sort 48 | ) throws Exception { 49 | final Client client = account.getClient(); 50 | final Issue issue = client.getIssueClient().getIssue(parent.getKey()).get(); 51 | return Formatting.asTable(issue.getSubtasks(), fields, sort); 52 | } 53 | 54 | /** 55 | * List the projects available in the JIRA instance 56 | * @param account The shortname of the jira install configured via the `setup` command 57 | * @param fields Select the fields desired in the final output. Use `--fields=all` to see all possible fields. 58 | * @param sort Sort the results by the specified field or fields. 59 | */ 60 | @Command("projects") 61 | public String[][] projects( 62 | @Option("account") @Default("default") final Account account, 63 | @Option("fields") @Default("id key name") final String fields, 64 | @Option("sort") @Default("issueType.name priority.name status.name") final String sort) throws Exception { 65 | final Client client = account.getClient(); 66 | return Formatting.asTable(client.getProjectClient().getAllProjects().get(), fields, sort); 67 | } 68 | 69 | /** 70 | * List the versions for the specified project 71 | * @param account The shortname of the jira install configured via the `setup` command 72 | * @param fields Select the fields desired in the final output. Use `--fields=all` to see all possible fields. 73 | * @param sort Sort the results by the specified field or fields. 74 | */ 75 | @Command("versions") 76 | public String[][] versions(final ProjectKey projectKey, 77 | @Option("account") @Default("default") final Account account, 78 | @Option("fields") @Default("name released releaseDate") final String fields, 79 | @Option("sort") @Default("issueType.name priority.name status.name") final String sort) throws Exception { 80 | final Client client = account.getClient(); 81 | final Project project = client.getProjectClient().getProject(projectKey.getKey()).get(); 82 | return Formatting.asTable(project.getVersions(), fields, sort); 83 | } 84 | 85 | /** 86 | * List the components for the specified project 87 | * @param account The shortname of the jira install configured via the `setup` command 88 | * @param fields Select the fields desired in the final output. Use `--fields=all` to see all possible fields. 89 | * @param sort Sort the results by the specified field or fields. 90 | */ 91 | @Command("components") 92 | public String[][] components(final ProjectKey projectKey, 93 | @Option("account") @Default("default") final Account account, 94 | @Option("fields") @Default("id name description") final String fields, 95 | @Option("sort") @Default("issueType.name priority.name status.name") final String sort) throws Exception { 96 | 97 | final Client client = account.getClient(); 98 | final Project project = client.getProjectClient().getProject(projectKey.getKey()).get(); 99 | return Formatting.asTable(project.getComponents(), fields, sort); 100 | } 101 | 102 | /** 103 | * Returns a list of users that match the search string. 104 | * This resource cannot be accessed anonymously. 105 | * 106 | * @param username A query string used to search username, name or e-mail address 107 | * @param startAt The index of the first user to return (0-based) 108 | * @param maxResults The maximum number of users to return (defaults to 50). The maximum allowed value is 1000. 109 | * If you specify a value that is higher than this number, your search results will be truncated. 110 | * @param includeActive If true, then active users are included in the results (default true) 111 | * @param includeInactive If true, then inactive users are included in the results (default false) 112 | * @return list of users that match the search string 113 | */ 114 | @Command("users") 115 | public String[][] users( 116 | final String username, 117 | @Option("account") @Default("default") final Account account, 118 | @Option("start-at") final Integer startAt, 119 | @Option("maxResults") final Integer maxResults, 120 | @Option("include-active") final Boolean includeActive, 121 | @Option("include-inactive") final Boolean includeInactive, 122 | @Option("fields") @Default("name displayName timezone active") final String fields, 123 | @Option("sort") @Default("displayName") final String sort) throws Exception { 124 | final Client client = account.getClient(); 125 | return Formatting.asTable(client.getUserClient().findUsers( 126 | username, 127 | startAt, 128 | maxResults, 129 | includeActive, 130 | includeInactive 131 | ).get(), fields, sort); 132 | } 133 | 134 | /** 135 | * Returns groups with substrings matching a given query. 136 | * This is mainly for use with the group picker, so the returned groups contain html to be used as picker suggestions. 137 | * The groups are also wrapped in a single response object that also contains a header for use in the picker, 138 | * specifically showing X of Y matching groups. 139 | * 140 | * The number of groups returned is limited by the system property "jira.ajax.autocomplete.limit" 141 | * 142 | * The groups will be unique and sorted. 143 | * 144 | * @param query A string to match groups against 145 | * @param exclude Exclude groups 146 | * @param maxResults The maximum number of groups to return 147 | * @param userName A user name 148 | * @return list of groups that match the search string 149 | */ 150 | @Command("groups") 151 | public String[][] groups(@Option("query") final String query, 152 | @Option("account") @Default("default") final Account account, 153 | @Option("exclude") final String exclude, 154 | @Option("maxResults") final Integer maxResults, 155 | @Option("username") final String userName) throws Exception { 156 | final Client client = account.getClient(); 157 | final GroupRestClient groupClient = client.getGroupClient(); 158 | return Formatting.asTable(groupClient.findGroups(query, exclude, maxResults, userName).get()); 159 | } 160 | 161 | /** 162 | * List the project roles for the specified project 163 | * @param account The shortname of the jira install configured via the `setup` command 164 | * @param fields Select the fields desired in the final output. Use `--fields=all` to see all possible fields. 165 | * @param sort Sort the results by the specified field or fields. 166 | */ 167 | @Command("project-roles") 168 | public String[][] projectRoles(final ProjectKey projectKey, 169 | @Option("account") @Default("default") final Account account, 170 | @Option("fields") @Default("name") final String fields, 171 | @Option("sort") @Default("name") final String sort) throws Exception { 172 | final Client client = account.getClient(); 173 | final Project project = client.getProjectClient().getProject(projectKey.getKey()).get(); 174 | return Formatting.asTable(project.getProjectRoles(), fields, sort); 175 | } 176 | 177 | /** 178 | * List the favorite filters for the current account 179 | * @param account The shortname of the jira install configured via the `setup` command 180 | * @param fields Select the fields desired in the final output. Use `--fields=all` to see all possible fields. 181 | * @param sort Sort the results by the specified field or fields. 182 | */ 183 | @Command("favourite-filters") 184 | public String[][] favouriteFilters(@Option("account") @Default("default") final Account account, 185 | @Option("fields") @Default("id name jql") final String fields, 186 | @Option("sort") @Default("name") final String sort) throws Exception { 187 | final Client client = account.getClient(); 188 | final SearchRestClient searchClient = client.getSearchClient(); 189 | return Formatting.asTable(searchClient.getFavouriteFilters().get(), fields, sort); 190 | } 191 | 192 | /** 193 | * List the issue types for the JIRA instance 194 | * @param account The shortname of the jira install configured via the `setup` command 195 | * @param fields Select the fields desired in the final output. Use `--fields=all` to see all possible fields. 196 | * @param sort Sort the results by the specified field or fields. 197 | */ 198 | @Command("issue-types") 199 | public String[][] issueTypes(@Option("account") @Default("default") final Account account, 200 | @Option("fields") @Default("id name description") final String fields, 201 | @Option("sort") @Default("name") final String sort) throws Exception { 202 | final Client client = account.getClient(); 203 | final MetadataRestClient metadataClient = client.getMetadataClient(); 204 | return Formatting.asTable(metadataClient.getIssueTypes().get(), fields, sort); 205 | } 206 | 207 | /** 208 | * List the issue types for the specified project 209 | * @param account The shortname of the jira install configured via the `setup` command 210 | * @param fields Select the fields desired in the final output. Use `--fields=all` to see all possible fields. 211 | * @param sort Sort the results by the specified field or fields. 212 | */ 213 | @Command("issue-types") 214 | public String[][] issueTypes(final ProjectKey projectKey, 215 | @Option("account") @Default("default") final Account account, 216 | @Option("fields") @Default("id name description") final String fields, 217 | @Option("sort") @Default("name") final String sort) throws Exception { 218 | final Client client = account.getClient(); 219 | final Project project = client.getProjectClient().getProject(projectKey.getKey()).get(); 220 | return Formatting.asTable(project.getIssueTypes(), fields, sort); 221 | } 222 | 223 | /** 224 | * List the issue link types for the JIRA instance 225 | * @param account The shortname of the jira install configured via the `setup` command 226 | * @param fields Select the fields desired in the final output. Use `--fields=all` to see all possible fields. 227 | * @param sort Sort the results by the specified field or fields. 228 | */ 229 | @Command("issue-link-types") 230 | public String[][] issueLinkTypes(@Option("account") @Default("default") final Account account, 231 | @Option("fields") @Default("id name inward outward") final String fields, 232 | @Option("sort") @Default("name") final String sort) throws Exception { 233 | final Client client = account.getClient(); 234 | final MetadataRestClient metadataClient = client.getMetadataClient(); 235 | return Formatting.asTable(metadataClient.getIssueLinkTypes().get(), fields, sort); 236 | } 237 | 238 | /** 239 | * List the statuses configured in the JIRA instance 240 | * @param account The shortname of the jira install configured via the `setup` command 241 | * @param fields Select the fields desired in the final output. Use `--fields=all` to see all possible fields. 242 | * @param sort Sort the results by the specified field or fields. 243 | */ 244 | @Command("statuses") 245 | public String[][] statuses(@Option("account") @Default("default") final Account account, 246 | @Option("fields") @Default("id name statusCategory.key description") final String fields, 247 | @Option("sort") @Default("name") final String sort) throws Exception { 248 | final Client client = account.getClient(); 249 | final MetadataRestClient metadataClient = client.getMetadataClient(); 250 | return Formatting.asTable(metadataClient.getStatuses().get(), fields, sort); 251 | } 252 | 253 | /** 254 | * List the priorities configured in the JIRA instance 255 | * @param account The shortname of the jira install configured via the `setup` command 256 | * @param fields Select the fields desired in the final output. Use `--fields=all` to see all possible fields. 257 | * @param sort Sort the results by the specified field or fields. 258 | */ 259 | @Command("priorities") 260 | public String[][] priorities(@Option("account") @Default("default") final Account account, 261 | @Option("fields") @Default("id name description") final String fields, 262 | @Option("sort") @Default("name") final String sort) throws Exception { 263 | final Client client = account.getClient(); 264 | final MetadataRestClient metadataClient = client.getMetadataClient(); 265 | return Formatting.asTable(metadataClient.getPriorities().get(), fields, sort); 266 | } 267 | 268 | /** 269 | * List the resolutions configured in the JIRA instance 270 | * @param account The shortname of the jira install configured via the `setup` command 271 | * @param fields Select the fields desired in the final output. Use `--fields=all` to see all possible fields. 272 | * @param sort Sort the results by the specified field or fields. 273 | */ 274 | @Command("resolutions") 275 | public String[][] resolutions(@Option("account") @Default("default") final Account account, 276 | @Option("fields") @Default("id name description") final String fields, 277 | @Option("sort") @Default("name") final String sort) throws Exception { 278 | final Client client = account.getClient(); 279 | final MetadataRestClient metadataClient = client.getMetadataClient(); 280 | return Formatting.asTable(metadataClient.getResolutions().get(), fields, sort); 281 | } 282 | 283 | /** 284 | * List the custom fields configured in the JIRA instance 285 | * @param account The shortname of the jira install configured via the `setup` command 286 | * @param fields Select the fields desired in the final output. Use `--fields=all` to see all possible fields. 287 | * @param sort Sort the results by the specified field or fields. 288 | */ 289 | @Command("fields") 290 | public String[][] fields(@Option("account") @Default("default") final Account account, 291 | @Option("fields") @Default("fieldType id name") final String fields, 292 | @Option("sort") @Default("name") final String sort) throws Exception { 293 | final Client client = account.getClient(); 294 | final MetadataRestClient metadataClient = client.getMetadataClient(); 295 | return Formatting.asTable(metadataClient.getFields().get(), fields, sort); 296 | } 297 | 298 | // @Command("subtasks") 299 | // public Stream subtasks2(final IssueKey parent) throws Exception { 300 | // final Issue issue = client.getIssueClient().getIssue(parent.getKey()).get(); 301 | // 302 | // return Client.stream(issue.getSubtasks()) 303 | // .map(subtask -> String.format("%s %s", subtask.getIssueKey(), subtask.getSummary())); 304 | // } 305 | 306 | 307 | } 308 | -------------------------------------------------------------------------------- /jamira-cli/src/main/java/org/tomitribe/jamira/cli/Loader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.tomitribe.jamira.cli; 18 | 19 | import java.util.Arrays; 20 | import java.util.Iterator; 21 | 22 | public class Loader implements org.tomitribe.crest.api.Loader { 23 | 24 | @Override 25 | public Iterator> iterator() { 26 | return Arrays.asList( 27 | BulkCreateCommand.class, 28 | UpdateCommand.class, 29 | AccountCommand.class, 30 | ListCommand.class, 31 | AddCommand.class, 32 | SearchCommand.class, 33 | CreateCommand.class 34 | ).iterator(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /jamira-cli/src/main/java/org/tomitribe/jamira/cli/SearchCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.cli; 17 | 18 | import com.atlassian.jira.rest.client.api.SearchRestClient; 19 | import com.atlassian.jira.rest.client.api.domain.SearchResult; 20 | import org.tomitribe.crest.api.Command; 21 | import org.tomitribe.crest.api.Default; 22 | import org.tomitribe.crest.api.Option; 23 | import org.tomitribe.jamira.core.Account; 24 | import org.tomitribe.jamira.core.Client; 25 | import org.tomitribe.jamira.core.Formatting; 26 | 27 | import java.util.concurrent.ExecutionException; 28 | 29 | @Command("search") 30 | public class SearchCommand { 31 | 32 | @Command 33 | public String[][] jql(final String query, 34 | @Option("fields") @Default("key issueType.name priority.name status.name summary") final String fields, 35 | @Option("sort") @Default("issueType.name priority.name status.name") final String sort, 36 | @Option("account") @Default("default") final Account account) throws ExecutionException, InterruptedException { 37 | final Client client = account.getClient(); 38 | final SearchRestClient searchClient = client.getSearchClient(); 39 | final SearchResult result = searchClient.searchJql(query).get(); 40 | return Formatting.asTable(result.getIssues(), fields, sort); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /jamira-cli/src/main/java/org/tomitribe/jamira/cli/UpdateCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.cli; 17 | 18 | import com.atlassian.jira.rest.client.api.IssueRestClient; 19 | import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder; 20 | import org.tomitribe.crest.api.Command; 21 | import org.tomitribe.crest.api.Default; 22 | import org.tomitribe.crest.api.In; 23 | import org.tomitribe.crest.api.Option; 24 | import org.tomitribe.jamira.core.Account; 25 | import org.tomitribe.jamira.core.Client; 26 | import org.tomitribe.jamira.core.IssueKey; 27 | import org.tomitribe.util.IO; 28 | 29 | import java.io.InputStream; 30 | import java.util.List; 31 | 32 | @Command("update") 33 | public class UpdateCommand { 34 | 35 | //CHECKSTYLE:OFF 36 | 37 | /** 38 | * Update the specified JIRA issue. The issue description can be supplied as a 39 | * flag or alternatively by reading it from piped input to the command. 40 | * 41 | * echo "This is the new description" | jamira update issue TOMEE-3141 --summary="New title" 42 | * 43 | * If both the description flag and piped input is used, the resulting description will 44 | * be the contents of the flag, a new line, then the contents of the piped input. 45 | * For example the following would set the description to "Hello\nWorld": 46 | * 47 | * echo "World" | jamira update issue TOMEE-3141 --description="Hello" 48 | * 49 | * 50 | * @param issueKey Text key for the issue. For example TOMEE-123 51 | * @param summary Text to use as the title of the issue 52 | * @param type The name of the issue type. See the `list issue-types` command 53 | * @param description Long description of the issue 54 | * @param assignee Username of the person to which the issue should be assigned 55 | * @param reporter Username of the person who is the reporter of the issue 56 | * @param priority The name of the priority for the issue. See the `list priorities` command 57 | * @param affectedVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 58 | * @param fixVersions The names of the versions affected by the issue. See the `list versions` command. Flag may be repeated. 59 | * @param components The component names relating to the issue. See the `list components` command. Flag may be repeated. 60 | * @param account Shortname of the JIRA server 61 | */ 62 | @Command("issue") 63 | public void issue(final IssueKey issueKey, 64 | @Option("summary") final String summary, 65 | @Option("type") final String type, 66 | @Option("description") String description, 67 | @Option("assignee") final String assignee, 68 | @Option("reporter") final String reporter, 69 | @Option("priority") final String priority, 70 | @Option("affected-version") final List affectedVersions, 71 | @Option("fix-version") final List fixVersions, 72 | @Option("component") final List components, 73 | @In final InputStream descriptionInput, 74 | @Option("account") @Default("default") final Account account) throws Exception { 75 | 76 | final Client client = account.getClient(); 77 | final IssueRestClient issueClient = client.getIssueClient(); 78 | 79 | { // Read the description from System input 80 | final String content = IO.slurp(descriptionInput); 81 | if (content != null && content.length() > 0) { 82 | if (description == null) { 83 | description = content; 84 | } else { 85 | description += System.lineSeparator() + content; 86 | } 87 | } 88 | } 89 | 90 | final IssueInputBuilder issue = new IssueInputBuilder(); 91 | 92 | if (summary != null) issue.setSummary(summary); 93 | if (type != null) issue.setIssueType(client.getIssueType(type)); 94 | if (description != null) issue.setDescription(description); 95 | if (assignee != null) issue.setAssigneeName(assignee); 96 | if (reporter != null) issue.setReporterName(reporter); 97 | if (priority != null) issue.setPriority(client.getPriority(priority)); 98 | if (affectedVersions != null) issue.setAffectedVersionsNames(affectedVersions); 99 | if (fixVersions != null) issue.setFixVersionsNames(fixVersions); 100 | if (components != null) issue.setComponentsNames(components); 101 | 102 | issueClient.updateIssue(issueKey.getKey(), issue.build()).get(); 103 | } 104 | //CHECKSTYLE:ON 105 | 106 | } 107 | -------------------------------------------------------------------------------- /jamira-cli/src/main/resources/META-INF/services/org.tomitribe.crest.api.Loader: -------------------------------------------------------------------------------- 1 | org.tomitribe.jamira.cli.Loader 2 | -------------------------------------------------------------------------------- /jamira-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 4.0.0 20 | 21 | 22 | org.tomitribe.jamira 23 | jamira-parent 24 | 0.6-SNAPSHOT 25 | 26 | 27 | jamira-core 28 | Jamira :: Core 29 | 30 | 31 | 32 | org.tomitribe 33 | tomitribe-util 34 | 1.3.16 35 | 36 | 37 | org.tomitribe 38 | tomitribe-crest-api 39 | 0.15 40 | 41 | 42 | com.atlassian.jira 43 | jira-rest-java-client-app 44 | 5.2.2 45 | 46 | 47 | org.apache.geronimo.specs 48 | geronimo-validation_1.1_spec 49 | 1.0 50 | 51 | 52 | org.apache.bval 53 | bval-jsr 54 | 1.1.2 55 | 56 | 57 | org.apache.geronimo.specs 58 | geronimo-json_1.1_spec 59 | 1.0 60 | 61 | 62 | org.apache.geronimo.specs 63 | geronimo-jsonb_1.0_spec 64 | 1.0 65 | 66 | 67 | org.apache.johnzon 68 | johnzon-jsonb 69 | 1.2.8 70 | 71 | 72 | junit 73 | junit 74 | 4.13.1 75 | test 76 | 77 | 78 | org.projectlombok 79 | lombok 80 | 1.18.12 81 | compile 82 | 83 | 84 | 85 | install 86 | 87 | 88 | org.codehaus.mojo 89 | templating-maven-plugin 90 | 1.0.0 91 | 92 | 93 | filtering-java-templates 94 | 95 | filter-sources 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /jamira-core/src/main/java-templates/org/tomitribe/jamira/core/Version.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.tomitribe.jamira.core; 18 | 19 | public interface Version { 20 | String VERSION = "${project.version}"; 21 | } 22 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/Account.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import lombok.Data; 19 | import org.tomitribe.util.Files; 20 | import org.tomitribe.util.IO; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.io.OutputStream; 26 | import java.io.UncheckedIOException; 27 | import java.net.URI; 28 | import java.util.Properties; 29 | 30 | @Data 31 | public class Account { 32 | 33 | private final String name; 34 | private final File file; 35 | private final Properties properties; 36 | 37 | public boolean exists() { 38 | return file.exists(); 39 | } 40 | 41 | public Client getClient() { 42 | return new Client(this); 43 | } 44 | 45 | public String getUsername() { 46 | return properties.getProperty("username"); 47 | } 48 | 49 | public void setUsername(final String username) { 50 | properties.setProperty("username", username); 51 | } 52 | 53 | public String getPassword() { 54 | return properties.getProperty("password"); 55 | } 56 | 57 | public void setPassword(final String password) { 58 | properties.setProperty("password", password); 59 | } 60 | 61 | public URI getServerUri() { 62 | final String uri = properties.getProperty("serverUri"); 63 | return URI.create(uri); 64 | } 65 | 66 | public void setServerUri(final String uri) { 67 | setServerUri(URI.create(uri)); 68 | } 69 | 70 | public void setServerUri(final URI uri) { 71 | properties.setProperty("serverUri", uri.toASCIIString()); 72 | } 73 | 74 | public void save() throws IOException { 75 | try (final OutputStream out = IO.write(file)) { 76 | properties.store(out, name); 77 | } 78 | } 79 | 80 | public void delete() { 81 | Files.remove(file); 82 | } 83 | 84 | 85 | public static Account name(final String name) { 86 | final Jamira jamira = Home.get().jamira(); 87 | 88 | if ("default".equalsIgnoreCase(name)) { 89 | return jamira.accounts().findFirst().orElseThrow(NoAccountSetupException::new); 90 | } 91 | 92 | final Account account = jamira.account(name); 93 | if (!account.exists()) { 94 | throw new NoSuchAccountExistsException(name); 95 | } 96 | return account; 97 | } 98 | 99 | static Account load(final File file) { 100 | final String name = file.getName().replace(".properties", ""); 101 | 102 | if (!file.exists()) { 103 | return new Account(name, file, new Properties()); 104 | } 105 | 106 | try { 107 | final Properties properties = new Properties(); 108 | try (final InputStream in = IO.read(file)) { 109 | properties.load(in); 110 | return new Account(name, file, properties); 111 | } 112 | } catch (IOException e) { 113 | throw new UncheckedIOException(e); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/AccountExistsException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import org.tomitribe.crest.api.Exit; 19 | 20 | @Exit(2) 21 | public class AccountExistsException extends IllegalStateException { 22 | public AccountExistsException(final String name) { 23 | super("Account exists: " + name); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/Bar.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | public class Bar { 19 | } 20 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/Cache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import org.tomitribe.jamira.core.cache.JsonbInstances; 19 | import org.tomitribe.util.IO; 20 | import org.tomitribe.util.dir.Dir; 21 | import org.tomitribe.util.dir.Name; 22 | 23 | import javax.json.bind.Jsonb; 24 | import java.io.File; 25 | import java.io.IOException; 26 | import java.io.OutputStream; 27 | import java.io.UncheckedIOException; 28 | import java.util.concurrent.TimeUnit; 29 | 30 | public interface Cache extends Dir { 31 | 32 | @Name("issue-types.json") 33 | File issueTypesJson(); 34 | 35 | @Name("statuses.json") 36 | File statusesJson(); 37 | 38 | @Name("priorities.json") 39 | File prioritiesJson(); 40 | 41 | @Name("resolutions.json") 42 | File resolutionsJson(); 43 | 44 | class Entry { 45 | 46 | private final File file; 47 | private final Class type; 48 | 49 | public Entry(final File file, final Class type) { 50 | this.file = file; 51 | this.type = type; 52 | final Bar bar = new Bar(); 53 | } 54 | 55 | public static long age(final File file) { 56 | return System.currentTimeMillis() - file.lastModified(); 57 | } 58 | 59 | public File getFile() { 60 | return file; 61 | } 62 | 63 | public boolean isFresh() { 64 | return file.exists() && Cache.Entry.age(file) < TimeUnit.DAYS.toMillis(30); 65 | } 66 | 67 | public void write(final Object object) { 68 | final Jsonb jsonb = JsonbInstances.get(); 69 | try { 70 | final String json = jsonb.toJson(object); 71 | try (final OutputStream out = IO.write(file)) { 72 | out.write(json.getBytes()); 73 | } 74 | } catch (IOException e) { 75 | throw new UncheckedIOException(e); 76 | } 77 | } 78 | 79 | public T read() { 80 | final Jsonb jsonb = JsonbInstances.get(); 81 | try { 82 | final String json = IO.slurp(file); 83 | return jsonb.fromJson(json, type); 84 | } catch (IOException e) { 85 | throw new UncheckedIOException(e); 86 | } 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/Client.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import com.atlassian.httpclient.api.factory.HttpClientOptions; 19 | import com.atlassian.jira.rest.client.api.AuditRestClient; 20 | import com.atlassian.jira.rest.client.api.ComponentRestClient; 21 | import com.atlassian.jira.rest.client.api.GroupRestClient; 22 | import com.atlassian.jira.rest.client.api.IssueRestClient; 23 | import com.atlassian.jira.rest.client.api.JiraRestClient; 24 | import com.atlassian.jira.rest.client.api.MetadataRestClient; 25 | import com.atlassian.jira.rest.client.api.MyPermissionsRestClient; 26 | import com.atlassian.jira.rest.client.api.ProjectRestClient; 27 | import com.atlassian.jira.rest.client.api.ProjectRolesRestClient; 28 | import com.atlassian.jira.rest.client.api.SearchRestClient; 29 | import com.atlassian.jira.rest.client.api.SessionRestClient; 30 | import com.atlassian.jira.rest.client.api.UserRestClient; 31 | import com.atlassian.jira.rest.client.api.VersionRestClient; 32 | import com.atlassian.jira.rest.client.api.domain.IssueType; 33 | import com.atlassian.jira.rest.client.api.domain.Priority; 34 | import org.tomitribe.jamira.core.http.CustomAsynchronousJiraRestClientFactory; 35 | import io.atlassian.util.concurrent.Promise; 36 | import lombok.Data; 37 | import org.tomitribe.jamira.core.cache.CachedMetadataRestClient; 38 | 39 | import java.net.URI; 40 | import java.util.Spliterator; 41 | import java.util.Spliterators; 42 | import java.util.concurrent.TimeUnit; 43 | import java.util.stream.Stream; 44 | import java.util.stream.StreamSupport; 45 | 46 | @Data 47 | public class Client { 48 | 49 | private final JiraRestClient restClient; 50 | private final Account account; 51 | 52 | public Client(final Account account) { 53 | this.account = account; 54 | final CustomAsynchronousJiraRestClientFactory factory = new CustomAsynchronousJiraRestClientFactory(); 55 | final URI jiraServerUri = account.getServerUri(); 56 | final HttpClientOptions options = new HttpClientOptions(); 57 | //XXX set it to 60 minutes here but for future versions could allow the user to configure this timeout 58 | options.setSocketTimeout(60, TimeUnit.MINUTES); 59 | restClient = factory.createWithBasicHttpAuthentication(jiraServerUri, account.getUsername(), account.getPassword(), options); 60 | } 61 | 62 | public IssueType getIssueType(final String name) { 63 | return stream(getMetadataClient().getIssueTypes()) 64 | .filter(issueType -> name.equalsIgnoreCase(issueType.getName())) 65 | .findFirst() 66 | .orElseThrow(() -> new NoSuchIssueTypeException(name)); 67 | } 68 | 69 | public Priority getPriority(final String name) { 70 | return stream(getMetadataClient().getPriorities()) 71 | .filter(priority -> name.equalsIgnoreCase(priority.getName())) 72 | .findFirst() 73 | .orElseThrow(() -> new NoSuchPriorityException(name)); 74 | } 75 | 76 | public static Stream stream(final Promise> promise) { 77 | try { 78 | return stream(promise.get()); 79 | } catch (final RuntimeException e) { 80 | throw e; 81 | } catch (final Exception e) { 82 | throw new IllegalStateException(e); 83 | } 84 | } 85 | 86 | public IssueRestClient getIssueClient() { 87 | return restClient.getIssueClient(); 88 | } 89 | 90 | public SessionRestClient getSessionClient() { 91 | return restClient.getSessionClient(); 92 | } 93 | 94 | public UserRestClient getUserClient() { 95 | return restClient.getUserClient(); 96 | } 97 | 98 | public GroupRestClient getGroupClient() { 99 | return restClient.getGroupClient(); 100 | } 101 | 102 | public ProjectRestClient getProjectClient() { 103 | return restClient.getProjectClient(); 104 | } 105 | 106 | public ComponentRestClient getComponentClient() { 107 | return restClient.getComponentClient(); 108 | } 109 | 110 | public MetadataRestClient getMetadataClient() { 111 | return new CachedMetadataRestClient(restClient.getMetadataClient(), Home.get().jamira().cache(account)); 112 | } 113 | 114 | public SearchRestClient getSearchClient() { 115 | return restClient.getSearchClient(); 116 | } 117 | 118 | public VersionRestClient getVersionRestClient() { 119 | return restClient.getVersionRestClient(); 120 | } 121 | 122 | public ProjectRolesRestClient getProjectRolesRestClient() { 123 | return restClient.getProjectRolesRestClient(); 124 | } 125 | 126 | public AuditRestClient getAuditRestClient() { 127 | return restClient.getAuditRestClient(); 128 | } 129 | 130 | public MyPermissionsRestClient getMyPermissionsRestClient() { 131 | return restClient.getMyPermissionsRestClient(); 132 | } 133 | 134 | public static Stream stream(final Iterable iterable) { 135 | return StreamSupport.stream(Spliterators.spliteratorUnknownSize( 136 | iterable.iterator(), 137 | Spliterator.ORDERED) 138 | , false); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/Formatting.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import org.tomitribe.util.Join; 19 | import org.tomitribe.util.collect.ObjectMap; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Arrays; 23 | import java.util.Collections; 24 | import java.util.Comparator; 25 | import java.util.List; 26 | 27 | public class Formatting { 28 | 29 | private Formatting() { 30 | } 31 | 32 | public static String[][] asTable(final Iterable items, final String fields) { 33 | return asTable(items, fields, ""); 34 | } 35 | 36 | public static String[][] asTable(final Iterable items, final String fields, final String sort) { 37 | if ("all".equalsIgnoreCase(fields)) { 38 | return asTable(items); 39 | } 40 | 41 | return asTable(items, fields.split("[ ,]+"), sort.split("[ ,]+")); 42 | } 43 | 44 | public static String[][] asTable(final Iterable items, final String[] fields, final String[] sort) { 45 | final List> rows = new ArrayList<>(); 46 | 47 | for (final T item : items) { 48 | final List row = new ArrayList<>(); 49 | 50 | final ObjectMap map = new ObjectMap(item); 51 | for (final String field : fields) { 52 | row.add(resolve(map, field)); 53 | } 54 | rows.add(row); 55 | } 56 | 57 | rows.sort(compareFields(fields, sort)); 58 | 59 | // sort the rows 60 | // Collections.sort(rows,(a, b) -> { 61 | // a. 62 | // }); 63 | 64 | final String[][] data = new String[rows.size() + 1][fields.length]; 65 | int rowCount = 0; 66 | 67 | // Add the headers 68 | data[rowCount++] = fields; 69 | 70 | for (final List row : rows) { 71 | data[rowCount++] = row.toArray(new String[fields.length]); 72 | } 73 | 74 | 75 | return data; 76 | } 77 | 78 | public static Comparator> compareFields(final String[] fields, final String[] sort) { 79 | return compareFields(Arrays.asList(fields), Arrays.asList(sort)); 80 | } 81 | 82 | public static Comparator> compareFields(final List fields, final List sort) { 83 | 84 | Comparator> comparator = (o1, o2) -> 0; 85 | 86 | for (final String field : sort) { 87 | final int i = fields.indexOf(field); 88 | if (i < 0) continue; 89 | comparator = comparator.thenComparing(strings -> strings.get(i)); 90 | } 91 | 92 | return comparator; 93 | } 94 | 95 | public static String[][] asTable(final Iterable items) { 96 | final List> rows = new ArrayList<>(); 97 | 98 | int columns = 0; 99 | List keys = null; 100 | for (final T item : items) { 101 | final List row = new ArrayList<>(); 102 | 103 | final ObjectMap map = new ObjectMap(item); 104 | 105 | keys = new ArrayList<>(map.keySet()); 106 | Collections.sort(keys); 107 | 108 | columns = Math.max(columns, keys.size()); 109 | 110 | for (final String field : keys) { 111 | row.add(resolve(map, field)); 112 | } 113 | 114 | rows.add(row); 115 | } 116 | 117 | // sort the rows 118 | // Collections.sort(rows,(a, b) -> { 119 | // a. 120 | // }); 121 | 122 | final String[][] data = new String[rows.size() + 1][columns]; 123 | int rowCount = 0; 124 | 125 | // Add the headers 126 | data[rowCount++] = keys.toArray(new String[columns]); 127 | 128 | for (final List row : rows) { 129 | data[rowCount++] = row.toArray(new String[columns]); 130 | } 131 | 132 | return data; 133 | } 134 | 135 | private static String resolve(final ObjectMap map, final String field) { 136 | final List parts = new ArrayList<>(Arrays.asList(field.split("\\."))); 137 | 138 | if (parts.size() > 1) { 139 | final String part = parts.remove(0); 140 | final Object object = map.get(part); 141 | return resolve(new ObjectMap(object), Join.join(".", parts)); 142 | } 143 | 144 | final Object o = map.get(field); 145 | return o != null ? o.toString() : "null"; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/Home.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import org.tomitribe.util.dir.Mkdir; 19 | import org.tomitribe.util.dir.Name; 20 | 21 | import java.io.File; 22 | 23 | public interface Home { 24 | 25 | @Mkdir 26 | @Name(".jamira") 27 | Jamira jamira(); 28 | 29 | static Home get() { 30 | return from(System.getProperty("user.home")); 31 | } 32 | 33 | static Home from(final String path) { 34 | return from(new File(path).getAbsoluteFile()); 35 | } 36 | 37 | static Home from(final File file) { 38 | return org.tomitribe.util.dir.Dir.of(Home.class, file); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/Input.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import java.io.BufferedReader; 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.io.InputStreamReader; 22 | 23 | public class Input { 24 | private Input() { 25 | } 26 | 27 | public static String read(final InputStream stdin, final String arg) throws IOException { 28 | final StringBuilder content = new StringBuilder(); 29 | if (arg != null) content.append(arg); 30 | 31 | final BufferedReader reader = new BufferedReader(new InputStreamReader(stdin)); 32 | if (reader.ready()) { 33 | String line = null; 34 | while ((line = reader.readLine()) != null) { 35 | if (content.length() > 0) content.append(System.lineSeparator()); 36 | content.append(line); 37 | } 38 | } 39 | return content.length() > 0 ? content.toString() : null; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/IssueKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import lombok.AllArgsConstructor; 19 | import lombok.Data; 20 | 21 | @Data 22 | @AllArgsConstructor 23 | public class IssueKey { 24 | private final String key; 25 | 26 | public ProjectKey getProjectKey() { 27 | final String projectKey = key.replaceAll("-.*", ""); 28 | return new ProjectKey(projectKey); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/Jamira.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import org.tomitribe.util.dir.Mkdir; 19 | 20 | import java.io.File; 21 | import java.util.stream.Stream; 22 | 23 | public interface Jamira extends org.tomitribe.util.dir.Dir { 24 | 25 | @Mkdir 26 | Cache cache(final String name); 27 | 28 | default Cache cache(final Account account) { 29 | return cache(account.getName()); 30 | } 31 | 32 | default Account account() { 33 | return account("default"); 34 | } 35 | 36 | default Account account(final String name) { 37 | final File file = file(name + ".properties"); 38 | return Account.load(file); 39 | } 40 | 41 | default Stream accounts() { 42 | return files() 43 | .filter(file -> file.getName().endsWith(".properties")) 44 | .map(Account::load); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/NoAccountSetupException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import org.tomitribe.crest.api.Exit; 19 | 20 | @Exit(1) 21 | public class NoAccountSetupException extends IllegalArgumentException { 22 | public NoAccountSetupException() { 23 | super("No jira accounts configured. Run `account add` to configure a jira account"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/NoIssueSummariesSuppliedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import org.tomitribe.crest.api.Exit; 19 | 20 | @Exit(7) 21 | public class NoIssueSummariesSuppliedException extends RuntimeException { 22 | public NoIssueSummariesSuppliedException() { 23 | super("Expected issue summaries supplied via piped input"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/NoSuchAccountExistsException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import org.tomitribe.crest.api.Exit; 19 | 20 | @Exit(3) 21 | public class NoSuchAccountExistsException extends IllegalStateException { 22 | public NoSuchAccountExistsException(final String name) { 23 | super("No such account exists: " + name); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/NoSuchIssueTypeException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import org.tomitribe.crest.api.Exit; 19 | 20 | @Exit(5) 21 | public class NoSuchIssueTypeException extends RuntimeException { 22 | public NoSuchIssueTypeException(final String name) { 23 | super("No such issue type: " + name); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/NoSuchPriorityException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import org.tomitribe.crest.api.Exit; 19 | 20 | @Exit(6) 21 | public class NoSuchPriorityException extends RuntimeException { 22 | public NoSuchPriorityException(final String name) { 23 | super("No such priority: " + name); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/ProjectKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import com.atlassian.jira.rest.client.api.domain.BasicProject; 19 | import lombok.AllArgsConstructor; 20 | import lombok.Data; 21 | 22 | @Data 23 | @AllArgsConstructor 24 | public class ProjectKey { 25 | private final String key; 26 | 27 | public BasicProject asBasicProject() { 28 | return new BasicProject(null, key, null, null); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/cache/CachedIssueType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core.cache; 17 | 18 | import com.atlassian.jira.rest.client.api.domain.IssueType; 19 | import lombok.AllArgsConstructor; 20 | import lombok.Builder; 21 | import lombok.Data; 22 | import lombok.NoArgsConstructor; 23 | 24 | import java.net.URI; 25 | 26 | @Data 27 | @Builder(builderClassName = "Builder") 28 | @NoArgsConstructor 29 | @AllArgsConstructor 30 | public class CachedIssueType { 31 | 32 | private URI self; 33 | private Long id; 34 | private String name; 35 | private boolean isSubtask; 36 | private String description; 37 | private URI iconUri; 38 | 39 | public IssueType toIssueType() { 40 | return new IssueType(self, id, name, isSubtask, description, iconUri); 41 | } 42 | 43 | public static CachedIssueType fromIssueType(final IssueType issueType) { 44 | return CachedIssueType.builder() 45 | .self(issueType.getSelf()) 46 | .id(issueType.getId()) 47 | .name(issueType.getName()) 48 | .isSubtask(issueType.isSubtask()) 49 | .description(issueType.getDescription()) 50 | .iconUri(issueType.getIconUri()) 51 | .build(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/cache/CachedMetadataRestClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core.cache; 17 | 18 | import com.atlassian.jira.rest.client.api.MetadataRestClient; 19 | import com.atlassian.jira.rest.client.api.domain.Field; 20 | import com.atlassian.jira.rest.client.api.domain.IssueType; 21 | import com.atlassian.jira.rest.client.api.domain.IssuelinksType; 22 | import com.atlassian.jira.rest.client.api.domain.Priority; 23 | import com.atlassian.jira.rest.client.api.domain.Resolution; 24 | import com.atlassian.jira.rest.client.api.domain.ServerInfo; 25 | import com.atlassian.jira.rest.client.api.domain.Status; 26 | import io.atlassian.util.concurrent.Promise; 27 | import org.tomitribe.jamira.core.Cache; 28 | 29 | import java.net.URI; 30 | import java.util.List; 31 | import java.util.Optional; 32 | import java.util.stream.Collectors; 33 | import java.util.stream.Stream; 34 | 35 | import static org.tomitribe.jamira.core.Client.stream; 36 | 37 | public class CachedMetadataRestClient implements MetadataRestClient { 38 | 39 | private final MetadataRestClient client; 40 | private final Cache cache; 41 | 42 | public CachedMetadataRestClient(final MetadataRestClient client, final Cache cache) { 43 | this.client = client; 44 | this.cache = cache; 45 | } 46 | 47 | @Override 48 | public Promise getIssueType(final URI uri) { 49 | final Optional match = stream(getIssueTypes()) 50 | .filter(issueType -> issueType.getSelf().equals(uri)) 51 | .findFirst(); 52 | 53 | if (match.isPresent()) { 54 | return new CompletedPromise<>(match.get()); 55 | } 56 | 57 | return client.getIssueType(uri); 58 | } 59 | 60 | @Override 61 | public Promise> getIssueTypes() { 62 | final Cache.Entry entry = new Cache.Entry<>(cache.issueTypesJson(), CachedIssueType[].class); 63 | 64 | if (entry.isFresh()) { 65 | final CachedIssueType[] cachedIssueTypes = entry.read(); 66 | final List issueTypes = Stream.of(cachedIssueTypes) 67 | .map(CachedIssueType::toIssueType) 68 | .collect(Collectors.toList()); 69 | return new CompletedPromise<>(issueTypes); 70 | } 71 | 72 | final Iterable issueTypes = get(client.getIssueTypes()); 73 | { // Cache the values 74 | final List cachedIssueTypes = stream(issueTypes) 75 | .map(CachedIssueType::fromIssueType) 76 | .collect(Collectors.toList()); 77 | 78 | entry.write(cachedIssueTypes); 79 | } 80 | 81 | return new CompletedPromise<>(issueTypes); 82 | } 83 | 84 | private Iterable get(final Promise> issueTypes) { 85 | try { 86 | return issueTypes.get(); 87 | } catch (final Exception e) { 88 | throw new IllegalStateException(e); 89 | } 90 | } 91 | 92 | @Override 93 | public Promise> getIssueLinkTypes() { 94 | return client.getIssueLinkTypes(); 95 | } 96 | 97 | @Override 98 | public Promise getStatus(final URI uri) { 99 | return client.getStatus(uri); 100 | } 101 | 102 | @Override 103 | public Promise> getStatuses() { 104 | final Cache.Entry entry = new Cache.Entry<>(cache.statusesJson(), CachedStatus[].class); 105 | 106 | if (entry.isFresh()) { 107 | final CachedStatus[] cachedStatuses = entry.read(); 108 | final List statuses = Stream.of(cachedStatuses) 109 | .map(CachedStatus::toStatus) 110 | .collect(Collectors.toList()); 111 | return new CompletedPromise<>(statuses); 112 | } 113 | 114 | final Iterable statuses = get(client.getStatuses()); 115 | { // Cache the values 116 | final List cachedStatuses = stream(statuses) 117 | .map(CachedStatus::fromStatus) 118 | .collect(Collectors.toList()); 119 | 120 | entry.write(cachedStatuses); 121 | } 122 | 123 | return new CompletedPromise<>(statuses); 124 | } 125 | 126 | @Override 127 | public Promise getPriority(final URI uri) { 128 | return client.getPriority(uri); 129 | } 130 | 131 | @Override 132 | public Promise> getPriorities() { 133 | final Cache.Entry entry = new Cache.Entry<>(cache.prioritiesJson(), CachedPriority[].class); 134 | 135 | if (entry.isFresh()) { 136 | final CachedPriority[] cachedPriorities = entry.read(); 137 | final List priorities = Stream.of(cachedPriorities) 138 | .map(CachedPriority::toPriority) 139 | .collect(Collectors.toList()); 140 | return new CompletedPromise<>(priorities); 141 | } 142 | 143 | final Iterable priorities = get(client.getPriorities()); 144 | { // Cache the values 145 | final List cachedPriorities = stream(priorities) 146 | .map(CachedPriority::fromPriority) 147 | .collect(Collectors.toList()); 148 | 149 | entry.write(cachedPriorities); 150 | } 151 | 152 | return new CompletedPromise<>(priorities); 153 | } 154 | 155 | @Override 156 | public Promise getResolution(final URI uri) { 157 | return client.getResolution(uri); 158 | } 159 | 160 | @Override 161 | public Promise> getResolutions() { 162 | final Cache.Entry entry = new Cache.Entry<>(cache.resolutionsJson(), CachedResolution[].class); 163 | 164 | if (entry.isFresh()) { 165 | final CachedResolution[] cachedResolutions = entry.read(); 166 | final List resolutions = Stream.of(cachedResolutions) 167 | .map(CachedResolution::toResolution) 168 | .collect(Collectors.toList()); 169 | return new CompletedPromise<>(resolutions); 170 | } 171 | 172 | final Iterable resolutions = get(client.getResolutions()); 173 | { // Cache the values 174 | final List cachedResolutions = stream(resolutions) 175 | .map(CachedResolution::fromResolution) 176 | .collect(Collectors.toList()); 177 | 178 | entry.write(cachedResolutions); 179 | } 180 | 181 | return new CompletedPromise<>(resolutions); 182 | } 183 | 184 | @Override 185 | public Promise getServerInfo() { 186 | return client.getServerInfo(); 187 | } 188 | 189 | @Override 190 | public Promise> getFields() { 191 | return client.getFields(); 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/cache/CachedPriority.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core.cache; 17 | 18 | import com.atlassian.jira.rest.client.api.domain.Priority; 19 | import lombok.AllArgsConstructor; 20 | import lombok.Builder; 21 | import lombok.Data; 22 | import lombok.NoArgsConstructor; 23 | 24 | import java.net.URI; 25 | 26 | @Data 27 | @Builder(builderClassName = "Builder") 28 | @NoArgsConstructor 29 | @AllArgsConstructor 30 | public class CachedPriority { 31 | 32 | private URI self; 33 | private String name; 34 | private Long id; 35 | private String statusColor; 36 | private String description; 37 | private URI iconUrl; 38 | 39 | public Priority toPriority() { 40 | return new Priority(self, id, name, statusColor, description, iconUrl); 41 | } 42 | 43 | public static CachedPriority fromPriority(final Priority priority) { 44 | return CachedPriority.builder() 45 | .self(priority.getSelf()) 46 | .id(priority.getId()) 47 | .name(priority.getName()) 48 | .description(priority.getDescription()) 49 | .iconUrl(priority.getIconUri()) 50 | .statusColor(priority.getStatusColor()) 51 | .build(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/cache/CachedResolution.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core.cache; 17 | 18 | import com.atlassian.jira.rest.client.api.domain.Resolution; 19 | import lombok.AllArgsConstructor; 20 | import lombok.Builder; 21 | import lombok.Data; 22 | import lombok.NoArgsConstructor; 23 | 24 | import java.net.URI; 25 | 26 | @Data 27 | @Builder(builderClassName = "Builder") 28 | @NoArgsConstructor 29 | @AllArgsConstructor 30 | public class CachedResolution { 31 | 32 | private URI self; 33 | private String name; 34 | private Long id; 35 | private String description; 36 | 37 | public Resolution toResolution() { 38 | return new Resolution(self, id, name, description); 39 | } 40 | 41 | public static CachedResolution fromResolution(final Resolution resolution) { 42 | return CachedResolution.builder() 43 | .self(resolution.getSelf()) 44 | .id(resolution.getId()) 45 | .name(resolution.getName()) 46 | .description(resolution.getDescription()) 47 | .build(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/cache/CachedStatus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core.cache; 17 | 18 | import com.atlassian.jira.rest.client.api.StatusCategory; 19 | import com.atlassian.jira.rest.client.api.domain.Status; 20 | import lombok.AllArgsConstructor; 21 | import lombok.Builder; 22 | import lombok.Data; 23 | import lombok.NoArgsConstructor; 24 | 25 | import java.net.URI; 26 | 27 | @Data 28 | @Builder(builderClassName = "Builder") 29 | @NoArgsConstructor 30 | @AllArgsConstructor 31 | public class CachedStatus { 32 | 33 | private URI self; 34 | private String name; 35 | private Long id; 36 | private String description; 37 | private URI iconUrl; 38 | private StatusCategory statusCategory; 39 | 40 | public Status toStatus() { 41 | return new Status(self, id, name, description, iconUrl, statusCategory); 42 | } 43 | 44 | public static CachedStatus fromStatus(final Status status) { 45 | return CachedStatus.builder() 46 | .self(status.getSelf()) 47 | .id(status.getId()) 48 | .name(status.getName()) 49 | .description(status.getDescription()) 50 | .iconUrl(status.getIconUrl()) 51 | .statusCategory(status.getStatusCategory()) 52 | .build(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/cache/CompletedPromise.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core.cache; 17 | 18 | import io.atlassian.util.concurrent.Promise; 19 | 20 | import java.util.concurrent.ExecutionException; 21 | import java.util.concurrent.TimeUnit; 22 | import java.util.concurrent.TimeoutException; 23 | import java.util.function.Consumer; 24 | import java.util.function.Function; 25 | 26 | class CompletedPromise implements Promise { 27 | private final T object; 28 | 29 | public CompletedPromise(final T object) { 30 | this.object = object; 31 | } 32 | 33 | @Override 34 | public T claim() { 35 | return object; 36 | } 37 | 38 | @Override 39 | public Promise done(final Consumer c) { 40 | throw new UnsupportedOperationException(); 41 | } 42 | 43 | @Override 44 | public Promise fail(final Consumer c) { 45 | throw new UnsupportedOperationException(); 46 | } 47 | 48 | @Override 49 | public Promise then(final TryConsumer callback) { 50 | throw new UnsupportedOperationException(); 51 | } 52 | 53 | @Override 54 | public Promise map(final Function function) { 55 | throw new UnsupportedOperationException(); 56 | } 57 | 58 | @Override 59 | public Promise flatMap(final Function> function) { 60 | throw new UnsupportedOperationException(); 61 | } 62 | 63 | @Override 64 | public Promise recover(final Function handleThrowable) { 65 | throw new UnsupportedOperationException(); 66 | } 67 | 68 | @Override 69 | public Promise fold(final Function handleThrowable, final Function function) { 70 | throw new UnsupportedOperationException(); 71 | } 72 | 73 | @Override 74 | public boolean cancel(final boolean mayInterruptIfRunning) { 75 | return false; 76 | } 77 | 78 | @Override 79 | public boolean isCancelled() { 80 | return false; 81 | } 82 | 83 | @Override 84 | public boolean isDone() { 85 | return true; 86 | } 87 | 88 | @Override 89 | public T get() throws InterruptedException, ExecutionException { 90 | return object; 91 | } 92 | 93 | @Override 94 | public T get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 95 | return object; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/cache/JsonbInstances.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core.cache; 17 | 18 | import javax.json.bind.Jsonb; 19 | import javax.json.bind.JsonbBuilder; 20 | import javax.json.bind.JsonbConfig; 21 | 22 | public class JsonbInstances { 23 | 24 | private JsonbInstances() { 25 | } 26 | 27 | private static final ThreadLocal INSTANCES = ThreadLocal.withInitial(() -> { 28 | final JsonbConfig config = new JsonbConfig() 29 | .setProperty("johnzon.failOnMissingCreatorValues", false) 30 | .withFormatting(true); 31 | return JsonbBuilder.create(config); 32 | }); 33 | 34 | public static Jsonb get() { 35 | return INSTANCES.get(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/http/CustomAsynchronousHttpClientFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core.http; 17 | 18 | import com.atlassian.event.api.EventPublisher; 19 | import com.atlassian.httpclient.apache.httpcomponents.DefaultHttpClientFactory; 20 | import com.atlassian.httpclient.api.HttpClient; 21 | import com.atlassian.httpclient.api.factory.HttpClientOptions; 22 | import com.atlassian.jira.rest.client.api.AuthenticationHandler; 23 | import com.atlassian.jira.rest.client.internal.async.AsynchronousHttpClientFactory; 24 | import com.atlassian.jira.rest.client.internal.async.AtlassianHttpClientDecorator; 25 | import com.atlassian.jira.rest.client.internal.async.DisposableHttpClient; 26 | import com.atlassian.sal.api.ApplicationProperties; 27 | import com.atlassian.sal.api.UrlMode; 28 | import com.atlassian.sal.api.executor.ThreadLocalContextManager; 29 | 30 | import javax.annotation.Nonnull; 31 | import java.io.File; 32 | import java.net.URI; 33 | import java.util.Date; 34 | 35 | /* 36 | * Allows the specification of custom http client options. 37 | */ 38 | public class CustomAsynchronousHttpClientFactory extends AsynchronousHttpClientFactory { 39 | 40 | @SuppressWarnings("unchecked") 41 | public DisposableHttpClient createClient(final URI serverUri, final AuthenticationHandler authenticationHandler, final HttpClientOptions options) { 42 | final DefaultHttpClientFactory defaultHttpClientFactory = new DefaultHttpClientFactory( 43 | new EventPublisher() { 44 | @Override 45 | public void publish(Object o) { 46 | 47 | } 48 | 49 | @Override 50 | public void register(Object o) { 51 | 52 | } 53 | 54 | @Override 55 | public void unregister(Object o) { 56 | 57 | } 58 | 59 | @Override 60 | public void unregisterAll() { 61 | 62 | } 63 | }, 64 | new CustomAsynchronousHttpClientFactory.RestClientApplicationProperties(serverUri), 65 | new ThreadLocalContextManager() { 66 | @Override 67 | public Object getThreadLocalContext() { 68 | return null; 69 | } 70 | 71 | @Override 72 | public void setThreadLocalContext(Object context) { 73 | } 74 | 75 | @Override 76 | public void clearThreadLocalContext() { 77 | } 78 | }); 79 | 80 | final HttpClient httpClient = defaultHttpClientFactory.create(options); 81 | 82 | return new AtlassianHttpClientDecorator(httpClient, authenticationHandler) { 83 | @Override 84 | public void destroy() throws Exception { 85 | defaultHttpClientFactory.dispose(httpClient); 86 | } 87 | }; 88 | } 89 | 90 | private static class NoOpEventPublisher implements EventPublisher { 91 | @Override 92 | public void publish(Object o) { 93 | } 94 | 95 | @Override 96 | public void register(Object o) { 97 | } 98 | 99 | @Override 100 | public void unregister(Object o) { 101 | } 102 | 103 | @Override 104 | public void unregisterAll() { 105 | } 106 | } 107 | 108 | private static class RestClientApplicationProperties implements ApplicationProperties { 109 | 110 | private final String baseUrl; 111 | 112 | private RestClientApplicationProperties(URI jiraURI) { 113 | this.baseUrl = jiraURI.getPath(); 114 | } 115 | 116 | @Override 117 | public String getBaseUrl() { 118 | return baseUrl; 119 | } 120 | 121 | @Nonnull 122 | @Override 123 | public String getBaseUrl(UrlMode urlMode) { 124 | return baseUrl; 125 | } 126 | 127 | @Nonnull 128 | @Override 129 | public String getDisplayName() { 130 | return "Jamira Jira Client"; 131 | } 132 | 133 | @Nonnull 134 | @Override 135 | public String getPlatformId() { 136 | return ApplicationProperties.PLATFORM_JIRA; 137 | } 138 | 139 | @Nonnull 140 | @Override 141 | public String getVersion() { 142 | return "unknown"; 143 | } 144 | 145 | @Nonnull 146 | @Override 147 | public Date getBuildDate() { 148 | throw new UnsupportedOperationException(); 149 | } 150 | 151 | @Nonnull 152 | @Override 153 | public String getBuildNumber() { 154 | return String.valueOf(0); 155 | } 156 | 157 | @Override 158 | public File getHomeDirectory() { 159 | return new File("."); 160 | } 161 | 162 | @Override 163 | public String getPropertyValue(final String s) { 164 | throw new UnsupportedOperationException("Not implemented"); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /jamira-core/src/main/java/org/tomitribe/jamira/core/http/CustomAsynchronousJiraRestClientFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core.http; 17 | 18 | import com.atlassian.httpclient.api.factory.HttpClientOptions; 19 | import com.atlassian.jira.rest.client.api.AuthenticationHandler; 20 | import com.atlassian.jira.rest.client.api.JiraRestClient; 21 | import com.atlassian.jira.rest.client.auth.BasicHttpAuthenticationHandler; 22 | import com.atlassian.jira.rest.client.internal.async.AsynchronousHttpClientFactory; 23 | import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClient; 24 | import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory; 25 | import com.atlassian.jira.rest.client.internal.async.DisposableHttpClient; 26 | 27 | import java.net.URI; 28 | 29 | /* 30 | * Allows the specification of custom http client options. 31 | */ 32 | public class CustomAsynchronousJiraRestClientFactory extends AsynchronousJiraRestClientFactory { 33 | 34 | public JiraRestClient create(final URI serverURI, final AuthenticationHandler authenticationHandler, final HttpClientOptions clientOptions) { 35 | DisposableHttpClient httpClient = (new AsynchronousHttpClientFactory()).createClient(serverURI, authenticationHandler); 36 | return new AsynchronousJiraRestClient(serverURI, httpClient); 37 | } 38 | 39 | public JiraRestClient createWithBasicHttpAuthentication(URI serverUri, String username, String password, HttpClientOptions clientOptions) { 40 | return this.create(serverUri, new BasicHttpAuthenticationHandler(username, password), clientOptions); 41 | } 42 | 43 | public JiraRestClient createWithAuthenticationHandler(URI serverUri, AuthenticationHandler authenticationHandler, HttpClientOptions clientOptions) { 44 | return this.create(serverUri, authenticationHandler, clientOptions); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /jamira-core/src/test/java/org/tomitribe/jamira/core/CreateSubtaskExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import com.atlassian.jira.rest.client.api.IssueRestClient; 19 | import com.atlassian.jira.rest.client.api.JiraRestClient; 20 | import com.atlassian.jira.rest.client.api.JiraRestClientFactory; 21 | import com.atlassian.jira.rest.client.api.ProjectRestClient; 22 | import com.atlassian.jira.rest.client.api.domain.BasicIssue; 23 | import com.atlassian.jira.rest.client.api.domain.Issue; 24 | import com.atlassian.jira.rest.client.api.domain.IssueType; 25 | import com.atlassian.jira.rest.client.api.domain.Project; 26 | import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder; 27 | import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory; 28 | 29 | import java.net.URI; 30 | import java.util.HashMap; 31 | import java.util.Map; 32 | import java.util.Spliterator; 33 | import java.util.Spliterators; 34 | import java.util.stream.Stream; 35 | import java.util.stream.StreamSupport; 36 | 37 | public class CreateSubtaskExample { 38 | public static void main(String[] args) throws Exception { 39 | final JiraRestClientFactory factory = new AsynchronousJiraRestClientFactory(); 40 | final URI jiraServerUri = new URI("https://issues.apache.org/jira"); 41 | final JiraRestClient restClient = factory.createWithBasicHttpAuthentication(jiraServerUri, "foo", "bar"); 42 | final IssueRestClient issueClient = restClient.getIssueClient(); 43 | final Issue parent = issueClient.getIssue("OPENEJB-142").get(); 44 | final ProjectRestClient projectClient = restClient.getProjectClient(); 45 | 46 | final Project project = projectClient.getProject("OPENEJB").get(); 47 | 48 | final String name = "sub-task"; 49 | final IssueType type = stream(project.getIssueTypes()) 50 | .filter(issueType -> name.equalsIgnoreCase(issueType.getName())) 51 | .findFirst() 52 | .get(); 53 | 54 | final IssueInputBuilder issue = new IssueInputBuilder(project, type, "Testing JIRA Client"); 55 | final Map parentMap = new HashMap(); 56 | parentMap.put("key", parent.getKey()); 57 | issue.setFieldValue("parent", parent); 58 | 59 | final BasicIssue createdIssue = issueClient.createIssue(issue.build()).get(); 60 | System.out.println(createdIssue.getKey()); 61 | // IssueType. 62 | // final IssueInputBuilder builder = new IssueInputBuilder("TOMEE", 0l, "Testing Jira Client"); 63 | // 64 | // issueClient.createIssue(issue); 65 | // 66 | } 67 | 68 | public static Stream stream(final Iterable iterable) { 69 | return StreamSupport.stream(Spliterators.spliteratorUnknownSize( 70 | iterable.iterator(), 71 | Spliterator.ORDERED) 72 | , false); 73 | } 74 | } -------------------------------------------------------------------------------- /jamira-core/src/test/java/org/tomitribe/jamira/core/Foo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright ${YEAR} Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | public class Foo { 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /jamira-core/src/test/java/org/tomitribe/jamira/core/GetIssueExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Tomitribe and community 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.tomitribe.jamira.core; 17 | 18 | import com.atlassian.jira.rest.client.api.IssueRestClient; 19 | import com.atlassian.jira.rest.client.api.JiraRestClient; 20 | import com.atlassian.jira.rest.client.api.JiraRestClientFactory; 21 | import com.atlassian.jira.rest.client.api.domain.Issue; 22 | import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory; 23 | import io.atlassian.util.concurrent.Promise; 24 | 25 | import java.net.URI; 26 | 27 | public class GetIssueExample { 28 | public static void main(String[] args) throws Exception { 29 | final JiraRestClientFactory factory = new AsynchronousJiraRestClientFactory(); 30 | final URI jiraServerUri = new URI("https://issues.apache.org/jira"); 31 | final JiraRestClient restClient = factory.createWithBasicHttpAuthentication(jiraServerUri, "foo", "bar"); 32 | final IssueRestClient issueClient = restClient.getIssueClient(); 33 | 34 | final Promise issuePromise = issueClient.getIssue("TOMEE-2865"); 35 | final Issue issue = issuePromise.get(); 36 | 37 | System.out.printf("[%s] %s%n", issue.getKey(), issue.getSummary()); 38 | } 39 | } -------------------------------------------------------------------------------- /jamira-report/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 4.0.0 20 | 21 | 22 | org.tomitribe.jamira 23 | jamira-parent 24 | 0.6-SNAPSHOT 25 | 26 | 27 | jamira-report 28 | Jamira :: Report 29 | 30 | 31 | -------------------------------------------------------------------------------- /jbang-catalog.json: -------------------------------------------------------------------------------- 1 | { 2 | "catalogs": {}, 3 | "aliases": { 4 | "jamira": { 5 | "script-ref": "org.tomitribe.jamira:jamira-cli:LATEST:shaded" 6 | } 7 | }, 8 | "templates": {} 9 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 4.0.0 20 | 21 | 22 | org.tomitribe 23 | oss-parent 24 | 5 25 | 26 | 27 | org.tomitribe.jamira 28 | jamira-parent 29 | 0.6-SNAPSHOT 30 | pom 31 | 32 | 33 | scm:git:git@github.com:tomitribe/jamira.git 34 | scm:git:git@github.com:tomitribe/jamira.git 35 | scm:git:git@github.com:tomitribe/jamira.git 36 | HEAD 37 | 38 | 39 | 40 | jamira-core 41 | jamira-cli 42 | jamira-report 43 | 44 | 45 | 46 | install 47 | 48 | 49 | org.apache.rat 50 | apache-rat-plugin 51 | 52 | 53 | **/*.tsv 54 | **/*.json 55 | src/test/resources/** 56 | src/main/resources/META-INF/services/** 57 | 58 | 59 | 60 | 61 | org.apache.maven.plugins 62 | maven-compiler-plugin 63 | 3.8.1 64 | 65 | 1.8 66 | 1.8 67 | 68 | 69 | 70 | 71 | 72 | 73 | central 74 | 75 | false 76 | 77 | 78 | 79 | 80 | org.apache.maven.plugins 81 | maven-surefire-plugin 82 | 83 | true 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | atlassian 94 | Atlassian Public Repository 95 | https://maven.atlassian.com/content/repositories/atlassian-public/ 96 | 97 | 98 | atlassian-external 99 | Atlassian External Repository 100 | https://packages.atlassian.com/maven-external/ 101 | 102 | 103 | 104 | --------------------------------------------------------------------------------