├── .gitignore ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── circle.yml ├── fastlane-plugin-jira_release_notes.gemspec ├── fastlane ├── Fastfile └── Pluginfile ├── lib └── fastlane │ └── plugin │ ├── jira_release_notes.rb │ └── jira_release_notes │ ├── actions │ └── jira_release_notes_action.rb │ ├── helper │ └── jira_release_notes_helper.rb │ └── version.rb └── spec ├── jira_release_notes_action_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | Gemfile.lock 3 | 4 | ## Documentation cache and generated files: 5 | .idea 6 | /.yardoc/ 7 | /_yardoc/ 8 | /doc/ 9 | /rdoc/ 10 | fastlane/README.md 11 | fastlane/report.xml 12 | coverage 13 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --color 3 | --format d 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Style/MultipleComparison: 2 | Enabled: false 3 | 4 | Style/PercentLiteralDelimiters: 5 | Enabled: false 6 | 7 | # kind_of? is a good way to check a type 8 | Style/ClassCheck: 9 | EnforcedStyle: kind_of? 10 | 11 | Style/FrozenStringLiteralComment: 12 | Enabled: false 13 | 14 | # This doesn't work with older versions of Ruby (pre 2.4.0) 15 | Style/SafeNavigation: 16 | Enabled: false 17 | 18 | # This doesn't work with older versions of Ruby (pre 2.4.0) 19 | Performance/RegexpMatch: 20 | Enabled: false 21 | 22 | # This suggests use of `tr` instead of `gsub`. While this might be more performant, 23 | # these methods are not at all interchangable, and behave very differently. This can 24 | # lead to people making the substitution without considering the differences. 25 | Performance/StringReplacement: 26 | Enabled: false 27 | 28 | # .length == 0 is also good, we don't always want .zero? 29 | Style/NumericPredicate: 30 | Enabled: false 31 | 32 | # this would cause errors with long lanes 33 | Metrics/BlockLength: 34 | Enabled: false 35 | 36 | # this is a bit buggy 37 | Metrics/ModuleLength: 38 | Enabled: false 39 | 40 | # certificate_1 is an okay variable name 41 | Style/VariableNumber: 42 | Enabled: false 43 | 44 | # This is used a lot across the fastlane code base for config files 45 | Style/MethodMissing: 46 | Enabled: false 47 | 48 | # 49 | # File.chmod(0777, f) 50 | # 51 | # is easier to read than 52 | # 53 | # File.chmod(0o777, f) 54 | # 55 | Style/NumericLiteralPrefix: 56 | Enabled: false 57 | 58 | # 59 | # command = (!clean_expired.nil? || !clean_pattern.nil?) ? CLEANUP : LIST 60 | # 61 | # is easier to read than 62 | # 63 | # command = !clean_expired.nil? || !clean_pattern.nil? ? CLEANUP : LIST 64 | # 65 | Style/TernaryParentheses: 66 | Enabled: false 67 | 68 | # sometimes it is useful to have those empty methods 69 | Style/EmptyMethod: 70 | Enabled: false 71 | 72 | # It's better to be more explicit about the type 73 | Style/BracesAroundHashParameters: 74 | Enabled: false 75 | 76 | # specs sometimes have useless assignments, which is fine 77 | Lint/UselessAssignment: 78 | Exclude: 79 | - '**/spec/**/*' 80 | 81 | # We could potentially enable the 2 below: 82 | Layout/IndentHash: 83 | Enabled: false 84 | 85 | Layout/AlignHash: 86 | Enabled: false 87 | 88 | # HoundCI doesn't like this rule 89 | Layout/DotPosition: 90 | Enabled: false 91 | 92 | # We allow !! as it's an easy way to convert ot boolean 93 | Style/DoubleNegation: 94 | Enabled: false 95 | 96 | # Prevent to replace [] into %i 97 | Style/SymbolArray: 98 | Enabled: false 99 | 100 | # We still support Ruby 2.0.0 101 | Layout/IndentHeredoc: 102 | Enabled: false 103 | 104 | # This cop would not work fine with rspec 105 | Style/MixinGrouping: 106 | Exclude: 107 | - '**/spec/**/*' 108 | 109 | # Sometimes we allow a rescue block that doesn't contain code 110 | Lint/HandleExceptions: 111 | Enabled: false 112 | 113 | # Cop supports --auto-correct. 114 | Lint/UnusedBlockArgument: 115 | Enabled: false 116 | 117 | Lint/AmbiguousBlockAssociation: 118 | Enabled: false 119 | 120 | # Needed for $verbose 121 | Style/GlobalVars: 122 | Enabled: false 123 | 124 | # We want to allow class Fastlane::Class 125 | Style/ClassAndModuleChildren: 126 | Enabled: false 127 | 128 | # $? Exit 129 | Style/SpecialGlobalVars: 130 | Enabled: false 131 | 132 | Metrics/AbcSize: 133 | Enabled: false 134 | 135 | Metrics/MethodLength: 136 | Enabled: false 137 | 138 | Metrics/CyclomaticComplexity: 139 | Enabled: false 140 | 141 | # The %w might be confusing for new users 142 | Style/WordArray: 143 | MinSize: 19 144 | 145 | # raise and fail are both okay 146 | Style/SignalException: 147 | Enabled: false 148 | 149 | # Better too much 'return' than one missing 150 | Style/RedundantReturn: 151 | Enabled: false 152 | 153 | # Having if in the same line might not always be good 154 | Style/IfUnlessModifier: 155 | Enabled: false 156 | 157 | # and and or is okay 158 | Style/AndOr: 159 | Enabled: false 160 | 161 | # Configuration parameters: CountComments. 162 | Metrics/ClassLength: 163 | Max: 320 164 | 165 | 166 | # Configuration parameters: AllowURI, URISchemes. 167 | Metrics/LineLength: 168 | Max: 370 169 | 170 | # Configuration parameters: CountKeywordArgs. 171 | Metrics/ParameterLists: 172 | Max: 17 173 | 174 | Metrics/PerceivedComplexity: 175 | Max: 18 176 | 177 | # Sometimes it's easier to read without guards 178 | Style/GuardClause: 179 | Enabled: false 180 | 181 | # We allow both " and ' 182 | Style/StringLiterals: 183 | Enabled: false 184 | 185 | # something = if something_else 186 | # that's confusing 187 | Style/ConditionalAssignment: 188 | Enabled: false 189 | 190 | # Better to have too much self than missing a self 191 | Style/RedundantSelf: 192 | Enabled: false 193 | 194 | # e.g. 195 | # def self.is_supported?(platform) 196 | # we may never use `platform` 197 | Lint/UnusedMethodArgument: 198 | Enabled: false 199 | 200 | # the let(:key) { ... } 201 | Lint/ParenthesesAsGroupedExpression: 202 | Exclude: 203 | - '**/spec/**/*' 204 | 205 | # This would reject is_ in front of methods 206 | # We use `is_supported?` everywhere already 207 | Style/PredicateName: 208 | Enabled: false 209 | 210 | # We allow the $ 211 | Style/PerlBackrefs: 212 | Enabled: false 213 | 214 | # Disable '+ should be surrounded with a single space' for xcodebuild_spec.rb 215 | Layout/SpaceAroundOperators: 216 | Exclude: 217 | - '**/spec/actions_specs/xcodebuild_spec.rb' 218 | 219 | AllCops: 220 | TargetRubyVersion: 2.0 221 | Include: 222 | - '**/fastlane/Fastfile' 223 | Exclude: 224 | - '**/lib/assets/custom_action_template.rb' 225 | - './vendor/**/*' 226 | 227 | # They have not to be snake_case 228 | Style/FileName: 229 | Exclude: 230 | - '**/Dangerfile' 231 | - '**/Brewfile' 232 | - '**/Gemfile' 233 | - '**/Podfile' 234 | - '**/Rakefile' 235 | - '**/Fastfile' 236 | - '**/Deliverfile' 237 | - '**/Snapfile' 238 | - '**/*.gemspec' 239 | 240 | # We're not there yet 241 | Style/Documentation: 242 | Enabled: false 243 | 244 | # Added after upgrade to 0.38.0 245 | Style/MutableConstant: 246 | Enabled: false 247 | 248 | # length > 0 is good 249 | Style/ZeroLengthPredicate: 250 | Enabled: false 251 | 252 | # Adds complexity 253 | Style/IfInsideElse: 254 | Enabled: false 255 | 256 | # Sometimes we just want to 'collect' 257 | Style/CollectionMethods: 258 | Enabled: false 259 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # os: osx # enable this if you need macOS support 2 | language: ruby 3 | rvm: 4 | - 2.2.4 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 6 | eval_gemfile(plugins_path) if File.exist?(plugins_path) 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Alexander Ignition 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jira_release_notes plugin 2 | 3 | [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-jira_release_notes) 4 | 5 | ## Getting Started 6 | 7 | This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-jira_release_notes`, add it to your project by running: 8 | 9 | ```bash 10 | fastlane add_plugin jira_release_notes 11 | ``` 12 | 13 | ## About jira_release_notes 14 | 15 | Release notes from JIRA for version 16 | 17 | 18 | ## Example 19 | 20 | Check out the [example `Fastfile`](fastlane/Fastfile) to see how to use this plugin. Try it by cloning the repo, running `fastlane install_plugins` and `bundle exec fastlane test`. 21 | 22 | ```ruby 23 | lane :notes do 24 | text = jira_release_notes( 25 | username: "me", 26 | password: "123", # password or api token 27 | url: "https://jira.example.com", 28 | project: "OX", 29 | version: "0.1", 30 | status: "Testable", 31 | format: "plain" 32 | ) 33 | puts text 34 | end 35 | ``` 36 | 37 | ## Options 38 | 39 | ``` 40 | fastlane action jira_release_notes 41 | ``` 42 | 43 | [How to generate an API Access Token](https://confluence.atlassian.com/cloud/api-tokens-938839638.html) 44 | 45 | Key | Description | Env Var | Default 46 | ----|-------------|---------|-------- 47 | url | URL for Jira instance | FL_JIRA_SITE | 48 | username | Username for Jira instance | FL_JIRA_USERNAME | 49 | password | Password for Jira or api token | FL_JIRA_PASSWORD | 50 | project | Jira project name | FL_JIRA_PROJECT | 51 | version | Jira project version | FL_JIRA_PROJECT_VERSION | 52 | status | Jira issue status | FL_JIRA_STATUS | 53 | components | An array of Jira issue components | FL_JIRA_COMPONENTS | 54 | format | Format text. Plain, html or none | FL_JIRA_RELEASE_NOTES_FORMAT | plain 55 | max_results | Maximum number of issues | FL_JIRA_RELEASE_NOTES_MAX_RESULTS | 50 56 | 57 | 58 | ## Run tests for this plugin 59 | 60 | To run both the tests, and code style validation, run 61 | 62 | ``` 63 | rake 64 | ``` 65 | 66 | To automatically fix many of the styling issues, use 67 | ``` 68 | rubocop -a 69 | ``` 70 | 71 | ## Issues and Feedback 72 | 73 | For any other issues and feedback about this plugin, please submit it to this repository. 74 | 75 | ## Troubleshooting 76 | 77 | If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide. 78 | 79 | ## Using _fastlane_ Plugins 80 | 81 | For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/). 82 | 83 | ## About _fastlane_ 84 | 85 | _fastlane_ is the easiest way to automate beta deployments and releases for your iOS and Android apps. To learn more, check out [fastlane.tools](https://fastlane.tools). 86 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | 3 | require 'rspec/core/rake_task' 4 | RSpec::Core::RakeTask.new 5 | 6 | require 'rubocop/rake_task' 7 | RuboCop::RakeTask.new(:rubocop) 8 | 9 | task default: [:spec, :rubocop] 10 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | test: 2 | override: 3 | - bundle exec rake 4 | machine: 5 | ruby: 6 | version: 2.2.4 7 | # Enable xcode below if you need macOS 8 | # xcode: 9 | # version: "7.3" 10 | -------------------------------------------------------------------------------- /fastlane-plugin-jira_release_notes.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | lib = File.expand_path("../lib", __FILE__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'fastlane/plugin/jira_release_notes/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'fastlane-plugin-jira_release_notes' 9 | spec.version = Fastlane::JiraReleaseNotes::VERSION 10 | spec.author = 'Alexander Ignition' 11 | spec.email = 'izh.sever@gmail.com' 12 | 13 | spec.summary = 'Release notes from JIRA for version' 14 | spec.homepage = "https://github.com/RedMadRobot/fastlane-plugin-jira_release_notes" 15 | spec.license = "MIT" 16 | 17 | spec.files = Dir["lib/**/*"] + %w(README.md LICENSE) 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ['lib'] 20 | 21 | # Don't add a dependency to fastlane or fastlane_re 22 | # since this would cause a circular dependency 23 | 24 | spec.add_dependency 'jira-ruby', '~> 2.2.0' 25 | 26 | spec.add_development_dependency 'pry' 27 | spec.add_development_dependency 'bundler' 28 | spec.add_development_dependency 'rspec' 29 | spec.add_development_dependency 'rake' 30 | spec.add_development_dependency 'rubocop' 31 | spec.add_development_dependency 'simplecov' 32 | spec.add_development_dependency 'fastlane', '>= 2.49.0' 33 | end 34 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | lane :test do 2 | gym 3 | notes = jira_release_notes( 4 | username: "me", 5 | password: "123", 6 | url: "https://jira.example.com", 7 | project: "OX", 8 | version: "0.1", 9 | foramt: "plain" 10 | ) 11 | crashlytics( 12 | notes: notes 13 | ) 14 | message = jira_release_notes( 15 | username: "me", 16 | password: "123", 17 | url: "https://jira.example.com", 18 | project: "OX", 19 | version: "0.1", 20 | foramt: "html" 21 | ) 22 | slack( 23 | message: message 24 | ) 25 | end 26 | -------------------------------------------------------------------------------- /fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | -------------------------------------------------------------------------------- /lib/fastlane/plugin/jira_release_notes.rb: -------------------------------------------------------------------------------- 1 | require 'fastlane/plugin/jira_release_notes/version' 2 | 3 | module Fastlane 4 | module JiraReleaseNotes 5 | # Return all .rb files inside the "actions" and "helper" directory 6 | def self.all_classes 7 | Dir[File.expand_path('**/{actions,helper}/*.rb', File.dirname(__FILE__))] 8 | end 9 | end 10 | end 11 | 12 | # By default we want to import all available actions and helpers 13 | # A plugin can contain any number of actions and plugins 14 | Fastlane::JiraReleaseNotes.all_classes.each do |current| 15 | require current 16 | end 17 | -------------------------------------------------------------------------------- /lib/fastlane/plugin/jira_release_notes/actions/jira_release_notes_action.rb: -------------------------------------------------------------------------------- 1 | module Fastlane 2 | module Actions 3 | class JiraReleaseNotesAction < Action 4 | def self.run(params) 5 | Actions.verify_gem!('jira-ruby') 6 | require 'jira-ruby' 7 | 8 | client = JIRA::Client.new( 9 | username: params[:username], 10 | password: params[:password], 11 | site: params[:url], 12 | context_path: '', 13 | auth_type: :basic 14 | ) 15 | 16 | version = params[:version] 17 | project = params[:project] 18 | status = params[:status] 19 | components = params[:components] 20 | max_results = params[:max_results].to_i 21 | issues = [] 22 | 23 | UI.message("Fetch issues from JIRA project '#{project}', version '#{version}'") 24 | 25 | begin 26 | if version.kind_of?(Regexp) 27 | versions = client.Project.find(project).versions 28 | .select { |v| version.match(v.name) } 29 | .map { |v| "'#{v.name}'" } .join(', ') 30 | jql = "PROJECT = '#{project}' AND fixVersion in (#{versions})" 31 | else 32 | jql = "PROJECT = '#{project}' AND fixVersion = '#{version}'" 33 | end 34 | unless status.nil? or status.empty? 35 | jql += " AND status = '#{status}'" 36 | end 37 | unless components.nil? or components.empty? 38 | jql += " AND component in (#{components.map{|s| "\"#{s}\""}.join(", ")})" 39 | end 40 | UI.message("jql '#{jql}'") 41 | issues = client.Issue.jql(jql,max_results: max_results) 42 | 43 | rescue JIRA::HTTPError => e 44 | fields = [e.code, e.message] 45 | fields << e.response.body if e.response.content_type == "application/json" 46 | UI.user_error!("#{e} #{fields.join(', ')}") 47 | end 48 | 49 | UI.success("📝 #{issues.count} issues from JIRA project '#{project}', version '#{version}', status '#{status}', components '#{components}'") 50 | 51 | case params[:format] 52 | when "plain" 53 | Helper::JiraReleaseNotesHelper.plain_format(issues) 54 | when "html" 55 | Helper::JiraReleaseNotesHelper.html_format(issues, params[:url]) 56 | else 57 | issues 58 | end 59 | end 60 | 61 | ##################################################### 62 | # @!group Documentation 63 | ##################################################### 64 | 65 | def self.description 66 | "Jira release notes" 67 | end 68 | 69 | def self.details 70 | "Fetch release notes for Jira project for version" 71 | end 72 | 73 | def self.return_value 74 | "List of issues from jira. Formatted string or class" 75 | end 76 | 77 | def self.return_type 78 | :string 79 | end 80 | 81 | def self.available_options 82 | [ 83 | FastlaneCore::ConfigItem.new(key: :url, 84 | env_name: "FL_JIRA_SITE", 85 | description: "URL for Jira instance", 86 | verify_block: proc do |value| 87 | UI.user_error!("No url for Jira given") if value.to_s.length == 0 88 | end), 89 | FastlaneCore::ConfigItem.new(key: :username, 90 | env_name: "FL_JIRA_USERNAME", 91 | description: "Username for Jira instance", 92 | verify_block: proc do |value| 93 | UI.user_error!("No username") if value.to_s.length == 0 94 | end), 95 | FastlaneCore::ConfigItem.new(key: :password, 96 | env_name: "FL_JIRA_PASSWORD", 97 | description: "Password or api token for Jira", 98 | sensitive: true, 99 | verify_block: proc do |value| 100 | UI.user_error!("No password") if value.to_s.length == 0 101 | end), 102 | FastlaneCore::ConfigItem.new(key: :project, 103 | env_name: "FL_JIRA_PROJECT", 104 | description: "Jira project name", 105 | sensitive: true, 106 | verify_block: proc do |value| 107 | UI.user_error!("No Jira project name") if value.to_s.length == 0 108 | end), 109 | FastlaneCore::ConfigItem.new(key: :status, 110 | env_name: "FL_JIRA_STATUS", 111 | description: "Jira issue status", 112 | sensitive: true, 113 | default_value: ""), 114 | FastlaneCore::ConfigItem.new(key: :components, 115 | env_name: "FL_JIRA_COMPONENTS", 116 | description: "Jira issue components", 117 | type: Array, 118 | sensitive: true, 119 | default_value: ""), 120 | FastlaneCore::ConfigItem.new(key: :version, 121 | env_name: "FL_JIRA_PROJECT_VERSION", 122 | description: "Jira project version", 123 | sensitive: true, 124 | is_string: false, 125 | verify_block: proc do |value| 126 | UI.user_error!("'version' value must be a String or Regexp! Found #{value.class} instead.") unless value.kind_of?(String) || value.kind_of?(Regexp) 127 | UI.user_error!("No Jira project version") if value.to_s.length == 0 128 | end), 129 | FastlaneCore::ConfigItem.new(key: :format, 130 | env_name: "FL_JIRA_RELEASE_NOTES_FORMAT", 131 | description: "Format text. Plain, html or none", 132 | sensitive: true, 133 | default_value: "plain"), 134 | FastlaneCore::ConfigItem.new(key: :max_results, 135 | env_name: "FL_JIRA_RELEASE_NOTES_MAX_RESULTS", 136 | description: "Maximum number of issues", 137 | default_value: "50") 138 | ] 139 | end 140 | 141 | def self.authors 142 | ["Alexander Ignition"] 143 | end 144 | 145 | def self.is_supported?(platform) 146 | true 147 | end 148 | 149 | def self.example_code 150 | [ 151 | 'notes = jira_release_notes( 152 | url: "https://bugs.yourdomain.com", 153 | username: "Your username", 154 | password: "Your password", 155 | project: "ABC", 156 | version: "1.0" 157 | ) 158 | gym 159 | crashlytics(notes: notes)', 160 | 'notes = jira_release_notes( 161 | project: "ABC", 162 | version: "1.0", 163 | format: "html" 164 | ) 165 | gym 166 | slack(message: notes)' 167 | ] 168 | end 169 | 170 | def self.category 171 | :misc 172 | end 173 | end 174 | end 175 | end 176 | -------------------------------------------------------------------------------- /lib/fastlane/plugin/jira_release_notes/helper/jira_release_notes_helper.rb: -------------------------------------------------------------------------------- 1 | module Fastlane 2 | module Helper 3 | class JiraReleaseNotesHelper 4 | # class methods that you define here become available in your action 5 | # as `Helper::JiraReleaseNotesHelper.your_method` 6 | 7 | def self.plain_format(issues) 8 | issues.map { |i| "[#{i.key}] - #{i.summary}" } .join("\n") 9 | end 10 | 11 | def self.html_format(issues, url) 12 | require "cgi" 13 | issues.map do |i| 14 | summary = CGI.escapeHTML(i.summary) 15 | "[#{i.key}] - #{summary}" 16 | end.join("\n") 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/fastlane/plugin/jira_release_notes/version.rb: -------------------------------------------------------------------------------- 1 | module Fastlane 2 | module JiraReleaseNotes 3 | VERSION = "0.7.0" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/jira_release_notes_action_spec.rb: -------------------------------------------------------------------------------- 1 | describe Fastlane::Actions::JiraReleaseNotesAction do 2 | before(:each) do 3 | @username = 'user_name' 4 | @password = 'pass123' 5 | @url = 'http://mydomain.atlassian.net:443' 6 | @project = 'ABC' 7 | @version = '1.0' 8 | @status = 'Testable' 9 | @components = ["API", "UI"] 10 | end 11 | 12 | describe 'Release Notes' do 13 | before(:each) do 14 | @params = { 15 | username: @username, 16 | password: @password, 17 | url: @url, 18 | project: @project, 19 | version: @version 20 | } 21 | @options = { 22 | username: @username, 23 | password: @password, 24 | site: @url, 25 | context_path: '', 26 | auth_type: :basic 27 | } 28 | @issues = [ 29 | instance_double("Issue", key: "#{@project}-01", summary: 'text 1'), 30 | instance_double("Issue", key: "#{@project}-02", summary: 'text 2') 31 | ] 32 | expect(JIRA::Client).to receive(:new).with(@options).and_call_original 33 | end 34 | 35 | describe 'Using status' do 36 | before(:each) do 37 | @params[:status] = @status 38 | end 39 | 40 | it 'string status' do 41 | expect(JIRA::Resource::Issue).to receive(:jql) do |client, query| 42 | expect(client.options).to include(@options) 43 | expect(query).to eql("PROJECT = '#{@project}' AND fixVersion = '#{@version}' AND status = '#{@status}'") 44 | end.and_return(@issues) 45 | 46 | notes = Fastlane::Actions::JiraReleaseNotesAction.run(@params) 47 | expect(notes).to eql(@issues) 48 | end 49 | end 50 | 51 | describe 'Using components' do 52 | before(:each) do 53 | @params[:components] = @components 54 | end 55 | 56 | it 'array components' do 57 | expect(JIRA::Resource::Issue).to receive(:jql) do |client, query| 58 | expect(client.options).to include(@options) 59 | expect(query).to eql("PROJECT = '#{@project}' AND fixVersion = '#{@version}' AND component in (#{@components.map{|s| "\"#{s}\""}.join(", ")})") 60 | end.and_return(@issues) 61 | 62 | notes = Fastlane::Actions::JiraReleaseNotesAction.run(@params) 63 | expect(notes).to eql(@issues) 64 | end 65 | end 66 | 67 | describe 'Formats of version' do 68 | before(:each) do 69 | @params[:format] = 'none' 70 | end 71 | 72 | it 'string version' do 73 | expect(JIRA::Resource::Issue).to receive(:jql) do |client, query| 74 | expect(client.options).to include(@options) 75 | expect(query).to eql("PROJECT = '#{@project}' AND fixVersion = '#{@version}'") 76 | end.and_return(@issues) 77 | 78 | notes = Fastlane::Actions::JiraReleaseNotesAction.run(@params) 79 | expect(notes).to eql(@issues) 80 | end 81 | 82 | it 'regexp version' do 83 | versions = [ 84 | instance_double('Version', name: @version), 85 | instance_double('Version', name: '1.2') 86 | ] 87 | project = instance_double('Project', versions: versions) 88 | @issues << instance_double('Issue', key: "#{@project}-02", summary: 'text 3') 89 | @params[:version] = /1./ 90 | 91 | expect(JIRA::Resource::Project).to receive(:find) do |client, version| 92 | expect(client.options).to include(@options) 93 | expect(version).to eql(@project) 94 | end.and_return(project) 95 | 96 | expect(JIRA::Resource::Issue).to receive(:jql) do |client, query| 97 | expect(client.options).to include(@options) 98 | expect(query).to eql("PROJECT = '#{@project}' AND fixVersion in ('#{@version}', '1.2')") 99 | end.and_return(@issues) 100 | 101 | notes = Fastlane::Actions::JiraReleaseNotesAction.run(@params) 102 | expect(notes).to eql(@issues) 103 | end 104 | end 105 | 106 | describe 'Formats of issues' do 107 | before(:each) do 108 | expect(JIRA::Resource::Issue).to receive(:jql).and_return(@issues) 109 | end 110 | 111 | it 'plain notes' do 112 | @params[:format] = 'plain' 113 | notes = Fastlane::Actions::JiraReleaseNotesAction.run(@params) 114 | expect(notes).to eql([ 115 | "[#{@issues[0].key}] - #{@issues[0].summary}", 116 | "[#{@issues[1].key}] - #{@issues[1].summary}" 117 | ].join("\n")) 118 | end 119 | 120 | it 'html notes' do 121 | @params[:format] = 'html' 122 | notes = Fastlane::Actions::JiraReleaseNotesAction.run(@params) 123 | expect(notes).to eql([ 124 | "[#{@issues[0].key}] - #{@issues[0].summary}", 125 | "[#{@issues[1].key}] - #{@issues[1].summary}" 126 | ].join("\n")) 127 | end 128 | 129 | it 'raw notes' do 130 | @params[:format] = 'none' 131 | notes = Fastlane::Actions::JiraReleaseNotesAction.run(@params) 132 | expect(notes).to eql(@issues) 133 | end 134 | end 135 | end 136 | 137 | describe 'Invalid Parameters' do 138 | it 'raises an error if no username was given' do 139 | expect do 140 | result = Fastlane::FastFile.new.parse("lane :test do 141 | jira_release_notes({ 142 | password: '#{@password}', 143 | url: '#{@url}', 144 | project: '#{@project}', 145 | version: '#{@version}' 146 | }) 147 | end").runner.execute(:test) 148 | end.to raise_error "No username" 149 | end 150 | 151 | it 'raises an error if no password was given' do 152 | expect do 153 | result = Fastlane::FastFile.new.parse("lane :test do 154 | jira_release_notes({ 155 | username: '#{@username}', 156 | url: '#{@url}', 157 | project: '#{@project}', 158 | version: '#{@version}' 159 | }) 160 | end").runner.execute(:test) 161 | end.to raise_error "No password" 162 | end 163 | 164 | it 'raises an error if no url was given' do 165 | expect do 166 | result = Fastlane::FastFile.new.parse("lane :test do 167 | jira_release_notes({ 168 | username: '#{@username}', 169 | password: '#{@password}', 170 | project: '#{@project}', 171 | version: '#{@version}' 172 | }) 173 | end").runner.execute(:test) 174 | end.to raise_error "No url for Jira given" 175 | end 176 | 177 | it 'raises an error if no project was given' do 178 | expect do 179 | result = Fastlane::FastFile.new.parse("lane :test do 180 | jira_release_notes({ 181 | username: '#{@username}', 182 | password: '#{@password}', 183 | url: '#{@url}', 184 | version: '#{@version}' 185 | }) 186 | end").runner.execute(:test) 187 | end.to raise_error "No Jira project name" 188 | end 189 | 190 | it 'raises an error if no version was given' do 191 | expect do 192 | result = Fastlane::FastFile.new.parse("lane :test do 193 | jira_release_notes({ 194 | username: '#{@username}', 195 | password: '#{@password}', 196 | url: '#{@url}', 197 | project: '#{@project}' 198 | }) 199 | end").runner.execute(:test) 200 | end.to raise_error "No Jira project version" 201 | end 202 | 203 | it 'raises an error if invalid version was given' do 204 | expect do 205 | result = Fastlane::FastFile.new.parse("lane :test do 206 | jira_release_notes({ 207 | username: '#{@username}', 208 | password: '#{@password}', 209 | url: '#{@url}', 210 | project: '#{@project}', 211 | version: 1 212 | }) 213 | end").runner.execute(:test) 214 | end.to raise_error "'version' value must be a String or Regexp! Found #{1.class} instead." 215 | end 216 | end 217 | end 218 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | 3 | require 'simplecov' 4 | 5 | # SimpleCov.minimum_coverage 95 6 | SimpleCov.start 7 | 8 | # This module is only used to check the environment is currently a testing env 9 | module SpecHelper 10 | end 11 | 12 | require 'fastlane' # to import the Action super class 13 | require 'fastlane/plugin/jira_release_notes' # import the actual plugin 14 | require 'jira-ruby' 15 | 16 | Fastlane.load_actions # load other actions (in case your plugin calls other actions or shared values) 17 | --------------------------------------------------------------------------------