├── .travis.yml ├── AUTHORS ├── Gemfile ├── LICENSE ├── README.md ├── app ├── controllers │ ├── jenkins_controller.rb │ ├── jenkins_jobs_controller.rb │ └── jenkins_settings_controller.rb ├── helpers │ └── jenkins_helper.rb ├── models │ ├── changesets_jenkins_build.rb │ ├── jenkins_build.rb │ ├── jenkins_job.rb │ ├── jenkins_query_filter.rb │ ├── jenkins_setting.rb │ └── jenkins_test_result.rb ├── presenters │ └── jenkins_job_presenter.rb ├── services │ ├── build_manager.rb │ └── jenkins_client.rb ├── use_cases │ └── jenkins_jobs │ │ ├── base.rb │ │ ├── create_builds.rb │ │ ├── trigger_build.rb │ │ ├── update_all_builds.rb │ │ └── update_last_build.rb └── views │ ├── common │ ├── _jenkins_js_headers.html.haml │ └── _jenkins_sidebar.html.haml │ ├── jenkins │ ├── _jobs_list.html.haml │ ├── index.html.haml │ ├── jenkins_instructions.html.haml │ └── refresh.js.erb │ ├── jenkins_jobs │ ├── _history.html.haml │ ├── _job_solo.html.haml │ ├── build.js.erb │ ├── console.html.haml │ ├── create.js.erb │ ├── edit.html.haml │ ├── history.html.haml │ ├── history.js.erb │ ├── new.html.haml │ ├── refresh.js.erb │ └── update.js.erb │ ├── jenkins_settings │ └── test_connection.html.haml │ └── projects │ └── settings │ ├── _jobs_list.html.haml │ └── _redmine_jenkins.html.haml ├── assets ├── images │ ├── changeset.png │ ├── health-00to19.png │ ├── health-20to39.png │ ├── health-40to59.png │ ├── health-60to79.png │ ├── health-80plus.png │ ├── running.gif │ └── test_connection.png └── stylesheets │ └── application.css ├── config ├── locales │ ├── de.yml │ ├── en.yml │ ├── fr.yml │ ├── pl.yml │ ├── ru.yml │ └── zh.yml └── routes.rb ├── contrib └── travis │ ├── common.sh │ ├── install_redmine.sh │ ├── plugin.sh │ └── redmine.sh ├── db └── migrate │ ├── 20150316023100_create_jenkins_settings.rb │ ├── 20150316023101_create_jenkins_jobs.rb │ ├── 20150316023102_create_jenkins_builds.rb │ ├── 20150316023103_create_jenkins_test_results.rb │ └── 20150316023104_create_changesets_jenkins_builds.rb ├── init.rb ├── lib ├── redmine_jenkins.rb ├── redmine_jenkins │ ├── error.rb │ ├── extra_loading.rb │ ├── hooks │ │ └── add_activity_icon.rb │ └── patches │ │ ├── changeset_patch.rb │ │ ├── project_patch.rb │ │ ├── projects_controller_patch.rb │ │ ├── projects_helper_patch.rb │ │ └── query_patch.rb └── tasks │ └── redmine_jenkins.rake └── spec ├── database_mysql.yml ├── database_postgres.yml ├── factories ├── jenkins_build.rb ├── jenkins_build_changeset.rb ├── jenkins_job.rb ├── jenkins_setting.rb ├── project.rb ├── repository_git.rb └── user.rb ├── models ├── jenkins_build_spec.rb ├── jenkins_job_spec.rb ├── jenkins_setting_spec.rb └── project_spec.rb ├── root_spec_helper.rb └── spec_helper.rb /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.1.0 4 | - 2.0.0 5 | gemfile: 6 | - Gemfile 7 | branches: 8 | only: 9 | - devel 10 | - /^fix_.*$/ 11 | - /^feat_.*$/ 12 | matrix: 13 | include: 14 | - rvm: 2.2.0 15 | env: REDMINE_VERSION=3.0-stable USE_SVN=true DATABASE_ADAPTER=mysql 16 | gemfile: Gemfile 17 | - rvm: 2.2.0 18 | env: REDMINE_VERSION=3.0-stable USE_SVN=true DATABASE_ADAPTER=postgresql 19 | gemfile: Gemfile 20 | - rvm: 2.2.0 21 | env: REDMINE_VERSION=3.0.1 DATABASE_ADAPTER=mysql 22 | gemfile: Gemfile 23 | - rvm: 2.2.0 24 | env: REDMINE_VERSION=3.0.1 DATABASE_ADAPTER=postgresql 25 | gemfile: Gemfile 26 | env: 27 | global: 28 | - REDMINE_LANG=en 29 | - MYSQL_DATABASE=redmine 30 | - MYSQL_HOST=127.0.0.1 31 | - MYSQL_PORT=3306 32 | - MYSQL_USER=root 33 | - MYSQL_PASSWORD= 34 | - POSTGRES_DATABASE=redmine 35 | - POSTGRES_USER=postgres 36 | matrix: 37 | - REDMINE_VERSION=3.0-stable USE_SVN=true DATABASE_ADAPTER=mysql 38 | - REDMINE_VERSION=3.0-stable USE_SVN=true DATABASE_ADAPTER=postgresql 39 | - REDMINE_VERSION=2.6-stable USE_SVN=true DATABASE_ADAPTER=mysql 40 | - REDMINE_VERSION=2.6-stable USE_SVN=true DATABASE_ADAPTER=postgresql 41 | - REDMINE_VERSION=3.0.1 DATABASE_ADAPTER=mysql 42 | - REDMINE_VERSION=3.0.1 DATABASE_ADAPTER=postgresql 43 | - REDMINE_VERSION=2.6.3 DATABASE_ADAPTER=mysql 44 | - REDMINE_VERSION=2.6.3 DATABASE_ADAPTER=postgresql 45 | # - REDMINE_VERSION=3.0.0 DATABASE_ADAPTER=mysql 46 | # - REDMINE_VERSION=3.0.0 DATABASE_ADAPTER=postgresql 47 | # - REDMINE_VERSION=2.6.2 DATABASE_ADAPTER=mysql 48 | # - REDMINE_VERSION=2.6.2 DATABASE_ADAPTER=postgresql 49 | # - REDMINE_VERSION=2.6.1 DATABASE_ADAPTER=mysql 50 | # - REDMINE_VERSION=2.6.1 DATABASE_ADAPTER=postgresql 51 | # - REDMINE_VERSION=2.6.0 DATABASE_ADAPTER=mysql 52 | # - REDMINE_VERSION=2.6.0 DATABASE_ADAPTER=postgresql 53 | # - REDMINE_VERSION=2.5.3 DATABASE_ADAPTER=mysql 54 | # - REDMINE_VERSION=2.5.3 DATABASE_ADAPTER=postgresql 55 | # - REDMINE_VERSION=2.5.2 DATABASE_ADAPTER=mysql 56 | # - REDMINE_VERSION=2.5.2 DATABASE_ADAPTER=postgresql 57 | # - REDMINE_VERSION=2.5.1 58 | # - REDMINE_VERSION=2.5.0 59 | # - REDMINE_VERSION=2.4.7 DATABASE_ADAPTER=mysql 60 | # - REDMINE_VERSION=2.4.7 DATABASE_ADAPTER=postgresql 61 | # - REDMINE_VERSION=2.4.6 DATABASE_ADAPTER=mysql 62 | # - REDMINE_VERSION=2.4.6 DATABASE_ADAPTER=postgresql 63 | # - REDMINE_VERSION=2.4.5 64 | # - REDMINE_VERSION=2.4.4 65 | # - REDMINE_VERSION=2.4.3 66 | # - REDMINE_VERSION=2.4.2 67 | # - REDMINE_VERSION=2.4.1 68 | # - REDMINE_VERSION=2.4.0 69 | # - REDMINE_VERSION=2.3.4 70 | # - REDMINE_VERSION=2.2.4 71 | before_install: 72 | - cd ../.. 73 | - ./jbox-web/redmine_jenkins/contrib/travis/install_redmine.sh 74 | - cd redmine 75 | - echo $(pwd) 76 | - export BUNDLE_GEMFILE=$PWD/Gemfile 77 | before_script: 78 | - echo $(pwd) 79 | - mysql -e 'create database redmine;' 80 | - psql -c 'create database redmine;' -U postgres 81 | - bundle exec rake generate_secret_token 82 | - bundle exec rake db:migrate 83 | - bundle exec rake redmine:load_default_data 84 | - bundle exec rake redmine:plugins:migrate 85 | - bundle exec rake db:test:prepare 86 | script: 87 | - bundle exec rake redmine_jenkins:ci:all 88 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Nicolas Rodriguez 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'jenkins_api_client', '~> 1.3.0' 4 | 5 | # HAML views 6 | gem 'haml-rails' 7 | 8 | group :development, :test do 9 | gem 'rspec', '~> 3.0.0' 10 | gem 'rspec-rails', '~> 3.0.1' 11 | 12 | gem 'shoulda', '~> 3.5.0' 13 | gem 'shoulda-matchers', '~> 2.7.0' 14 | gem 'shoulda-context' 15 | 16 | gem 'factory_girl' 17 | gem 'factory_girl_rails' 18 | gem 'faker' 19 | gem 'database_cleaner' 20 | 21 | # Code coverage 22 | gem 'simplecov', '~> 0.9.1' 23 | gem 'simplecov-rcov' 24 | 25 | # Junit results 26 | gem 'ci_reporter_rspec', '~> 1.0.0' 27 | end 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ![logo](https://raw.github.com/jbox-web/redmine_jenkins/gh-pages/images/jenkins_logo.png) Redmine Jenkins Plugin 2 | 3 | [![GitHub license](https://img.shields.io/github/license/jbox-web/redmine_jenkins.svg)](https://github.com/jbox-web/redmine_jenkins/blob/devel/LICENSE) 4 | [![GitHub release](https://img.shields.io/github/release/jbox-web/redmine_jenkins.svg)](https://github.com/jbox-web/redmine_jenkins/releases/latest) 5 | [![Code Climate](https://codeclimate.com/github/jbox-web/redmine_jenkins.png)](https://codeclimate.com/github/jbox-web/redmine_jenkins) 6 | [![Build Status](https://travis-ci.org/jbox-web/redmine_jenkins.svg?branch=devel)](https://travis-ci.org/jbox-web/redmine_jenkins) 7 | 8 | ### A Redmine plugin which makes building your Jenkins projects easy ;) 9 | 10 | This plugin allows straightforward management of Jenkins projects within Redmine. 11 | 12 | ## Installation 13 | 14 | Assuming that you have Redmine installed : 15 | 16 | ```sh 17 | ## Before install the plugin, stop Redmine! 18 | 19 | # Switch user 20 | root# su - redmine 21 | 22 | # First git clone Bootstrap Kit 23 | redmine$ cd REDMINE_ROOT/plugins 24 | redmine$ git clone https://github.com/jbox-web/redmine_bootstrap_kit.git 25 | redmine$ cd redmine_bootstrap_kit/ 26 | redmine$ git checkout 0.2.3 27 | 28 | # Then Redmine Jenkins plugin 29 | redmine$ cd REDMINE_ROOT/plugins 30 | redmine$ git clone https://github.com/jbox-web/redmine_jenkins.git 31 | redmine$ cd redmine_jenkins/ 32 | redmine$ git checkout 1.0.1 33 | 34 | # Install gems and migrate database 35 | redmine$ cd REDMINE_ROOT/ 36 | redmine$ bundle install --without development test 37 | redmine$ bundle exec rake redmine:plugins:migrate RAILS_ENV=production NAME=redmine_jenkins 38 | 39 | ## After install the plugin, start Redmine! 40 | ``` 41 | 42 | ## Troubleshooting 43 | 44 | > I got a problem, when using apache2, passenger and git-gems, like this: http://stackoverflow.com/questions/6648870/is-not-checked-out-bundle-install-does-not-fix-help 45 | 46 | ```sh 47 | # in case you are running apache2 with passenger, try this: 48 | redmine$ bundle install --deployment 49 | ``` 50 | 51 | ## Contribute 52 | 53 | You can contribute to this plugin in many ways such as : 54 | * Helping with documentation 55 | * Contributing code (features or bugfixes) 56 | * Reporting a bug 57 | * Submitting translations 58 | -------------------------------------------------------------------------------- /app/controllers/jenkins_controller.rb: -------------------------------------------------------------------------------- 1 | class JenkinsController < ApplicationController 2 | unloadable 3 | 4 | # Redmine ApplicationController method 5 | before_filter :find_project_by_project_id 6 | before_filter :can_view_jenkins_jobs 7 | before_filter :find_jenkins_settings 8 | 9 | require 'will_paginate/array' 10 | 11 | helper :redmine_bootstrap_kit 12 | helper :jenkins 13 | 14 | 15 | def index 16 | @jobs = @project.jenkins_jobs 17 | end 18 | 19 | 20 | def refresh 21 | success = [] 22 | errors = [] 23 | @project.jenkins_jobs.each do |job| 24 | result = BuildManager.update_last_build!(job) 25 | if result.success? 26 | success << result.message_on_success 27 | else 28 | errors << result.message_on_errors 29 | end 30 | end 31 | flash.now[:notice] = success.uniq.join('
').html_safe if !success.empty? 32 | flash.now[:error] = errors.uniq.join('
').html_safe if !errors.empty? 33 | @jobs = @project.jenkins_jobs 34 | render layout: false 35 | end 36 | 37 | 38 | private 39 | 40 | 41 | def find_jenkins_settings 42 | if @project.jenkins_setting.nil? 43 | flash.now[:warning] = l(:error_no_settings, url: settings_project_path(@project, 'jenkins')) 44 | render action: 'jenkins_instructions' 45 | end 46 | end 47 | 48 | 49 | def can_view_jenkins_jobs 50 | render_403 unless User.current.allowed_to?(:view_jenkins_jobs, @project) 51 | end 52 | 53 | end 54 | -------------------------------------------------------------------------------- /app/controllers/jenkins_jobs_controller.rb: -------------------------------------------------------------------------------- 1 | class JenkinsJobsController < ApplicationController 2 | unloadable 3 | 4 | # Redmine ApplicationController method 5 | before_filter :find_project_by_project_id 6 | 7 | before_filter :can_build_jenkins_jobs, only: [:build] 8 | before_filter :find_job, except: [:index, :new, :create] 9 | 10 | layout Proc.new { |controller| controller.request.xhr? ? false : 'base' } 11 | 12 | helper :redmine_bootstrap_kit 13 | helper :jenkins 14 | 15 | 16 | def show 17 | render_404 18 | end 19 | 20 | 21 | def new 22 | @job = @project.jenkins_jobs.new 23 | @jobs = available_jobs 24 | end 25 | 26 | 27 | def create 28 | @job = @project.jenkins_jobs.new(params[:jenkins_jobs]) 29 | if @job.save 30 | flash[:notice] = l(:notice_job_added) 31 | result = BuildManager.create_builds!(@job) 32 | flash[:error] = result.message_on_errors if !result.success? 33 | render_js_redirect 34 | else 35 | @jobs = available_jobs 36 | end 37 | end 38 | 39 | 40 | def edit 41 | @jobs = @project.jenkins_setting.get_jobs_list 42 | end 43 | 44 | 45 | def update 46 | if @job.update_attributes(params[:jenkins_jobs]) 47 | flash[:notice] = l(:notice_job_updated) 48 | result = BuildManager.update_all_builds!(@job) 49 | flash[:error] = result.message_on_errors if !result.success? 50 | render_js_redirect 51 | else 52 | @jobs = available_jobs 53 | end 54 | end 55 | 56 | 57 | def destroy 58 | flash[:notice] = l(:notice_job_deleted) if @job.destroy 59 | render_js_redirect 60 | end 61 | 62 | 63 | def build 64 | result = BuildManager.trigger_build!(@job) 65 | if result.success? 66 | flash.now[:notice] = result.message_on_success 67 | else 68 | flash.now[:error] = result.message_on_errors 69 | end 70 | end 71 | 72 | 73 | def history 74 | @builds = @job.builds.ordered.paginate(page: params[:page], per_page: 5) 75 | end 76 | 77 | 78 | def console 79 | @console_output = @job.console 80 | end 81 | 82 | 83 | def refresh 84 | result = BuildManager.update_last_build!(@job) 85 | flash.now[:error] = result.message_on_errors if !result.success? 86 | end 87 | 88 | 89 | private 90 | 91 | 92 | def can_build_jenkins_jobs 93 | render_403 unless User.current.allowed_to?(:build_jenkins_jobs, @project) 94 | end 95 | 96 | 97 | def find_job 98 | @job = @project.jenkins_jobs.find(params[:id]) 99 | rescue ActiveRecord::RecordNotFound => e 100 | render_404 101 | end 102 | 103 | 104 | def success_url 105 | settings_project_path(@project, 'jenkins') 106 | end 107 | 108 | 109 | def render_js_redirect 110 | respond_to do |format| 111 | format.js { render js: "window.location = #{success_url.to_json};" } 112 | end 113 | end 114 | 115 | 116 | def available_jobs 117 | @project.jenkins_setting.get_jobs_list - @project.jenkins_jobs.map(&:name) 118 | end 119 | 120 | end 121 | -------------------------------------------------------------------------------- /app/controllers/jenkins_settings_controller.rb: -------------------------------------------------------------------------------- 1 | class JenkinsSettingsController < ApplicationController 2 | unloadable 3 | 4 | # Redmine ApplicationController method 5 | before_filter :find_project_by_project_id 6 | before_filter :load_jenkins_settings 7 | 8 | 9 | def save 10 | unless params[:jenkins_setting].nil? 11 | if @jenkins_setting.update_attributes(params[:jenkins_setting]) 12 | flash[:notice] = l(:notice_settings_updated) 13 | else 14 | flash[:error] = @jenkins_setting.errors.full_messages.to_sentence 15 | end 16 | end 17 | redirect_to settings_project_path(@project, 'jenkins') 18 | end 19 | 20 | 21 | def test_connection 22 | @content = @jenkins_setting.test_connection 23 | render layout: false 24 | end 25 | 26 | 27 | private 28 | 29 | 30 | def load_jenkins_settings 31 | if @project.jenkins_setting.nil? 32 | @jenkins_setting = @project.build_jenkins_setting 33 | else 34 | @jenkins_setting = @project.jenkins_setting 35 | end 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /app/helpers/jenkins_helper.rb: -------------------------------------------------------------------------------- 1 | module JenkinsHelper 2 | 3 | def state_to_css_class(state) 4 | case state.downcase 5 | when 'success' 6 | 'success' 7 | when 'failure', 'aborted' 8 | 'important' 9 | when 'unstable', 'invalid' 10 | 'warning' 11 | when 'running' 12 | 'info' 13 | when 'not_run' 14 | '' 15 | else 16 | '' 17 | end 18 | end 19 | 20 | 21 | def weather_icon(icon) 22 | image_tag(plugin_asset_link('redmine_jenkins', icon), alt: icon, style: 'display: inline-block; margin-top: 5px;') 23 | end 24 | 25 | 26 | def state_to_label(state) 27 | state.gsub('_', ' ').capitalize 28 | end 29 | 30 | 31 | def plugin_asset_link(plugin_name, asset_name) 32 | File.join(Redmine::Utils.relative_url_root, 'plugin_assets', plugin_name, 'images', asset_name) 33 | end 34 | 35 | 36 | def link_to_jenkins_job(job) 37 | url = job.latest_build_number == 0 ? 'javascript:void(0);' : job.latest_build_url 38 | target = job.latest_build_number == 0 ? '' : 'about_blank' 39 | link_to "##{job.latest_build_number}", url, target: target 40 | end 41 | 42 | 43 | def render_repo_name(job) 44 | if job.repository.nil? 45 | content_tag(:em, 'deleted') 46 | else 47 | (job.repository.identifier.nil? || job.repository.identifier.empty?) ? 'default' : job.repository.identifier 48 | end 49 | end 50 | 51 | 52 | def render_selected_repo(job) 53 | if job.repository.nil? || job.repository.identifier.blank? 54 | [ 'default' ] 55 | else 56 | [ job.repository.identifier, job.repository.id ] 57 | end 58 | end 59 | 60 | end 61 | -------------------------------------------------------------------------------- /app/models/changesets_jenkins_build.rb: -------------------------------------------------------------------------------- 1 | class ChangesetsJenkinsBuild < ActiveRecord::Base 2 | unloadable 3 | 4 | ## Relations 5 | belongs_to :changeset 6 | belongs_to :jenkins_build 7 | 8 | ## Delegators 9 | delegate :revision, to: :changeset 10 | 11 | end 12 | -------------------------------------------------------------------------------- /app/models/jenkins_build.rb: -------------------------------------------------------------------------------- 1 | class JenkinsBuild < ActiveRecord::Base 2 | unloadable 3 | 4 | ## Relations 5 | belongs_to :jenkins_job 6 | belongs_to :author, class_name: 'User', foreign_key: 'author_id' 7 | has_many :changesets_jenkins_builds 8 | has_many :changesets, through: :changesets_jenkins_builds 9 | 10 | ## Validations 11 | validates :jenkins_job_id, presence: true 12 | validates :author_id, presence: true 13 | validates :number, presence: true, uniqueness: { scope: :jenkins_job_id } 14 | 15 | ## Delegators 16 | delegate :project, to: :jenkins_job 17 | 18 | ## Scopes 19 | scope :ordered, -> { order('number DESC') } 20 | 21 | scope :visible, lambda { |*args| 22 | joins(jenkins_job: :project). 23 | where(Project.allowed_to_condition(args.shift || User.current, :view_build_activity, *args)) 24 | } 25 | 26 | ## Redmine Events 27 | acts_as_event :datetime => :finished_at, 28 | :title => :event_name, 29 | :description => :event_desc, 30 | :author => :author, 31 | :url => :event_url, 32 | :type => 'build_activity' 33 | 34 | # Redmine 2.X 35 | if Rails::VERSION::MAJOR == 3 36 | acts_as_activity_provider :type => 'build_activity', 37 | :permission => :view_build_activity, 38 | :timestamp => "#{table_name}.finished_at", 39 | :author_key => :author_id, 40 | :find_options => {:include => {:jenkins_job => :project}} 41 | else 42 | acts_as_activity_provider :type => 'build_activity', 43 | :timestamp => "#{table_name}.finished_at", 44 | :author_key => :author_id, 45 | :scope => preload({:jenkins_job => :project}) 46 | end 47 | 48 | class << self 49 | 50 | def find_by_changeset(changeset) 51 | retval = JenkinsBuild.find(:all, 52 | :order => "#{JenkinsBuild.table_name}.number", 53 | :conditions => ["#{JenkinsBuildChangeset.table_name}.repository_id = ? and #{JenkinsBuildChangeset.table_name}.revision = ?", changeset.repository_id, changeset.revision], 54 | :joins => "INNER JOIN #{JenkinsBuildChangeset.table_name} ON #{JenkinsBuildChangeset.table_name}.jenkins_build_id = #{JenkinsBuild.table_name}.id") 55 | return retval 56 | end 57 | 58 | end 59 | 60 | 61 | def url 62 | "#{jenkins_job.url}/#{number}" 63 | end 64 | 65 | 66 | def event_url(options = {}) 67 | url 68 | end 69 | 70 | 71 | def event_name 72 | "#{l(:label_build)} #{jenkins_job.name} ##{number} : #{result.capitalize}" 73 | end 74 | 75 | 76 | def event_desc 77 | desc = '' 78 | desc << jenkins_job.health_report.map{ |hr| hr['description'] }.join("\n") if jenkins_job.health_report.any? 79 | desc 80 | end 81 | 82 | end 83 | -------------------------------------------------------------------------------- /app/models/jenkins_job.rb: -------------------------------------------------------------------------------- 1 | class JenkinsJob < ActiveRecord::Base 2 | unloadable 3 | 4 | ## Relations 5 | belongs_to :project 6 | belongs_to :repository 7 | has_many :builds, class_name: 'JenkinsBuild', dependent: :destroy 8 | 9 | attr_accessible :name, :repository_id, :builds_to_keep 10 | 11 | ## Validations 12 | validates :project_id, presence: true 13 | validates :repository_id, presence: true 14 | validates :name, presence: true, uniqueness: { scope: :project_id } 15 | validates :builds_to_keep, presence: true 16 | 17 | ## Serializations 18 | serialize :health_report, Array 19 | 20 | ## Delegators 21 | delegate :jenkins_connection, :wait_for_build_id, :jenkins_url, to: :project 22 | 23 | 24 | def to_s 25 | name 26 | end 27 | 28 | 29 | def job_id 30 | name.underscore.gsub(' ', '_') 31 | end 32 | 33 | 34 | def url 35 | "#{jenkins_url}/job/#{name}" 36 | end 37 | 38 | 39 | def latest_build_url 40 | "#{url}/#{latest_build_number}" 41 | end 42 | 43 | 44 | def console 45 | console_output = 46 | begin 47 | jenkins_connection.job.get_console_output(name, latest_build_number)['output'].gsub('\r\n', '
') 48 | rescue => e 49 | e.message 50 | end 51 | console_output 52 | end 53 | 54 | end 55 | -------------------------------------------------------------------------------- /app/models/jenkins_query_filter.rb: -------------------------------------------------------------------------------- 1 | class JenkinsQueryFilter 2 | unloadable 3 | 4 | attr_accessor :name, :available_values, :db_table, :db_field 5 | 6 | def initialize(name, available_values, db_table, db_field) 7 | @name = name 8 | @available_values = available_values 9 | @db_table = db_table 10 | @db_field = db_field 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/models/jenkins_setting.rb: -------------------------------------------------------------------------------- 1 | class JenkinsSetting < ActiveRecord::Base 2 | unloadable 3 | 4 | ## Relations 5 | belongs_to :project 6 | 7 | attr_accessible :url, :auth_user, :auth_password, :show_compact, :wait_for_build_id 8 | 9 | ## Validations 10 | validates :project_id, presence: true, uniqueness: true 11 | validates :url, presence: true 12 | 13 | 14 | def jenkins_connection 15 | jenkins_client.connection 16 | end 17 | 18 | 19 | def test_connection 20 | jenkins_client.test_connection 21 | end 22 | 23 | 24 | def get_jobs_list 25 | jenkins_client.get_jobs_list 26 | end 27 | 28 | 29 | def number_of_builds_for(job_name) 30 | jenkins_client.number_of_builds_for(job_name) 31 | end 32 | 33 | 34 | private 35 | 36 | 37 | def jenkins_client 38 | @jenkins_client ||= JenkinsClient.new(url, jenkins_options) 39 | end 40 | 41 | 42 | def jenkins_options 43 | options = {} 44 | options[:username] = auth_user if !auth_user.empty? 45 | options[:password] = auth_password if !auth_password.empty? 46 | options 47 | end 48 | 49 | end 50 | -------------------------------------------------------------------------------- /app/models/jenkins_test_result.rb: -------------------------------------------------------------------------------- 1 | class JenkinsTestResult < ActiveRecord::Base 2 | unloadable 3 | 4 | belongs_to :jenkins_build 5 | validates :jenkins_build_id, presence: true, uniqueness: true 6 | 7 | 8 | def description_for_activity 9 | "TestResults: #{fail_count}Failed #{skip_count}Skipped Total-#{total_count}" 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /app/presenters/jenkins_job_presenter.rb: -------------------------------------------------------------------------------- 1 | class JenkinsJobPresenter < SimpleDelegator 2 | 3 | attr_reader :jenkins_job 4 | 5 | def initialize(jenkins_job, template) 6 | super(template) 7 | @jenkins_job = jenkins_job 8 | end 9 | 10 | 11 | def job_id 12 | jenkins_job.job_id 13 | end 14 | 15 | 16 | def job_info 17 | s = '' 18 | s << content_tag(:h3, link_to(jenkins_job.name, jenkins_job.url, target: 'about_blank')) 19 | s << render_job_description unless jenkins_job.project.jenkins_setting.show_compact? 20 | s.html_safe 21 | end 22 | 23 | 24 | def latest_build_infos 25 | content_tag(:span, link_to_jenkins_job(jenkins_job), class: 'label label-info') + 26 | content_tag(:em, latest_build_date) 27 | end 28 | 29 | 30 | def job_state 31 | s = '' 32 | s << content_tag(:span, link_to_console_output, class: "label label-#{state_to_css_class(jenkins_job.state)}") 33 | s << content_tag(:span, '', class: 'icon icon-running') if jenkins_job.state == 'running' 34 | s.html_safe 35 | end 36 | 37 | 38 | def latest_build_duration 39 | Time.at(jenkins_job.latest_build_duration/1000).strftime "%M:%S" rescue "00:00" 40 | end 41 | 42 | 43 | def latest_changesets 44 | changesets = jenkins_job.builds.last.changesets rescue [] 45 | return '' if changesets.empty? 46 | content_tag(:ul, render_changesets_list(changesets), class: 'changesets_list list-unstyled') 47 | end 48 | 49 | 50 | def build_history 51 | link_to_history 52 | end 53 | 54 | 55 | def job_actions 56 | s = '' 57 | s << link_to_build if User.current.allowed_to?(:build_jenkins_jobs, jenkins_job.project) 58 | s << link_to_refresh 59 | s.html_safe 60 | end 61 | 62 | 63 | private 64 | 65 | 66 | def render_job_description 67 | s = '' 68 | s << jenkins_job.description 69 | s << render_health_report if jenkins_job.health_report.any? 70 | s 71 | end 72 | 73 | 74 | def render_health_report 75 | content_tag(:ul, render_report_list, class: 'list-unstyled') 76 | end 77 | 78 | 79 | def render_report_list 80 | s = '' 81 | jenkins_job.health_report.each do |health_report| 82 | s << content_tag(:li, "#{health_report['description']} #{weather_icon(health_report['iconUrl'])}".html_safe) 83 | end 84 | s.html_safe 85 | end 86 | 87 | 88 | def latest_build_date 89 | " (#{format_time(jenkins_job.latest_build_date)})" 90 | end 91 | 92 | 93 | def link_to_console_output 94 | url = jenkins_job.latest_build_number == 0 ? 'javascript:void(0);' : console_jenkins_job_path(jenkins_job.project, jenkins_job) 95 | link_to state_to_label(jenkins_job.state), url, title: l(:label_see_console_output), class: 'modal-box-close-only' 96 | end 97 | 98 | 99 | def link_to_build 100 | link_to fa_icon('fa-gears'), build_jenkins_job_path(jenkins_job.project, jenkins_job), title: l(:label_build_now), remote: true 101 | end 102 | 103 | 104 | def link_to_refresh 105 | link_to fa_icon('fa-refresh'), refresh_jenkins_job_path(jenkins_job.project, jenkins_job), title: l(:label_refresh_builds), remote: true 106 | end 107 | 108 | 109 | def link_to_history 110 | link_to fa_icon('fa-history'), history_jenkins_job_path(jenkins_job.project, jenkins_job), title: l(:label_see_history), class: 'modal-box-close-only' 111 | end 112 | 113 | 114 | def render_changesets_list(changesets) 115 | visible_changesets = changesets.take(5) 116 | invisible_changesets = changesets - visible_changesets 117 | render_visible_changesets(visible_changesets) + render_invisible_changesets(invisible_changesets) 118 | end 119 | 120 | 121 | def render_visible_changesets(changesets) 122 | render_changesets(changesets, visible: true) 123 | end 124 | 125 | 126 | def render_invisible_changesets(changesets) 127 | render_display_more + render_changesets(changesets, visible: false) if !changesets.empty? 128 | end 129 | 130 | 131 | def render_changesets(changesets, opts = {}) 132 | visible = opts.delete(:visible){ true } 133 | css_class = visible ? 'changesets visible' : 'changesets invisible' 134 | id = visible ? "changesets-visible-#{jenkins_job.id}" : "changesets-invisible-#{jenkins_job.id}" 135 | 136 | s = '' 137 | changesets.each do |changeset| 138 | s << content_tag(:li, content_for_changeset(changeset).html_safe) 139 | end 140 | 141 | content_tag(:div, s.html_safe, class: css_class, id: id) 142 | end 143 | 144 | 145 | def render_display_more 146 | link_to l(:label_display_more), '#', onclick: "$('#changesets-invisible-#{jenkins_job.id}').toggle(); return false;" 147 | end 148 | 149 | 150 | def content_for_changeset(changeset) 151 | s = '' 152 | s << content_tag(:p, link_to("##{changeset.revision[0..10]}", changeset_url(changeset)), class: 'revision') 153 | s << textilizable(changeset, :comments) 154 | s 155 | end 156 | 157 | 158 | def changeset_url(changeset) 159 | if !jenkins_job.repository.nil? 160 | { controller: 'repositories', action: 'revision', id: jenkins_job.project, repository_id: jenkins_job.repository.identifier_param, rev: changeset.revision } 161 | else 162 | '#' 163 | end 164 | end 165 | 166 | end 167 | -------------------------------------------------------------------------------- /app/services/build_manager.rb: -------------------------------------------------------------------------------- 1 | module BuildManager 2 | class << self 3 | 4 | def create_builds!(job) 5 | JenkinsJobs::CreateBuilds.new(job).call 6 | end 7 | 8 | 9 | def update_all_builds!(job) 10 | JenkinsJobs::UpdateAllBuilds.new(job).call 11 | end 12 | 13 | 14 | def update_last_build!(job) 15 | JenkinsJobs::UpdateLastBuild.new(job).call 16 | end 17 | 18 | 19 | def trigger_build!(job) 20 | JenkinsJobs::TriggerBuild.new(job).call 21 | end 22 | 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/services/jenkins_client.rb: -------------------------------------------------------------------------------- 1 | require 'jenkins_api_client' 2 | 3 | class JenkinsClient 4 | 5 | def initialize(url, opts = {}) 6 | @url = url 7 | 8 | @options = {} 9 | @options[:server_url] = @url 10 | @options[:http_open_timeout] = opts[:http_open_timeout] || 5 11 | @options[:http_read_timeout] = opts[:http_read_timeout] || 60 12 | @options[:username] = opts[:username] if opts.has_key?(:username) 13 | @options[:password] = opts[:password] if opts.has_key?(:password) 14 | end 15 | 16 | 17 | def connection 18 | JenkinsApi::Client.new(@options) 19 | rescue ArgumentError => e 20 | raise RedmineJenkins::Error::JenkinsConnectionError, e 21 | end 22 | 23 | 24 | def test_connection 25 | test = {} 26 | test[:errors] = [] 27 | 28 | begin 29 | test[:jobs_count] = connection.job.list_all.size 30 | rescue => e 31 | test[:jobs_count] = 0 32 | test[:errors] << e.message 33 | end 34 | 35 | begin 36 | test[:version] = connection.get_jenkins_version 37 | rescue => e 38 | test[:version] = 0 39 | test[:errors] << e.message 40 | end 41 | 42 | return test 43 | end 44 | 45 | 46 | def get_jobs_list 47 | connection.job.list_all rescue [] 48 | end 49 | 50 | 51 | def number_of_builds_for(job_name) 52 | connection.job.list_details(job_name)['builds'].size rescue 0 53 | end 54 | 55 | end 56 | -------------------------------------------------------------------------------- /app/use_cases/jenkins_jobs/base.rb: -------------------------------------------------------------------------------- 1 | module JenkinsJobs 2 | class Base 3 | 4 | include Redmine::I18n 5 | 6 | attr_reader :jenkins_job 7 | attr_reader :job_data 8 | attr_reader :errors 9 | attr_reader :use_case 10 | 11 | 12 | def initialize(jenkins_job) 13 | @jenkins_job = jenkins_job 14 | @job_data = nil 15 | @errors = [] 16 | @use_case = self.class.name.split('::').last.underscore 17 | end 18 | 19 | 20 | def call(*args, &block) 21 | self.send(:execute, *args, &block) 22 | return self 23 | end 24 | 25 | 26 | def jenkins_client 27 | jenkins_job.jenkins_connection 28 | end 29 | 30 | 31 | def success? 32 | errors.empty? 33 | end 34 | 35 | 36 | def errors 37 | @errors.uniq 38 | end 39 | 40 | 41 | def message_on_success 42 | l("use_cases.jenkins_job.#{use_case}.success", jenkins_job: jenkins_job.to_s) 43 | end 44 | 45 | 46 | def message_on_errors 47 | l("use_cases.jenkins_job.#{use_case}.failed", jenkins_job: jenkins_job.to_s, errors: errors.to_sentence) 48 | end 49 | 50 | 51 | private 52 | 53 | 54 | def job_status_updated? 55 | get_jenkins_job_details 56 | return false if job_data.nil? 57 | update_job_status 58 | end 59 | 60 | 61 | def update_job_status 62 | jenkins_job.state = color_to_state(job_data['color']) || jenkins_job.state 63 | jenkins_job.description = job_data['description'] || '' 64 | jenkins_job.health_report = job_data['healthReport'] 65 | jenkins_job.latest_build_number = !job_data['lastBuild'].nil? ? job_data['lastBuild']['number'] : 0 66 | jenkins_job.latest_build_date = jenkins_job.builds.last.finished_at rescue '' 67 | jenkins_job.latest_build_duration = jenkins_job.builds.last.duration rescue '' 68 | jenkins_job.save! 69 | jenkins_job.reload 70 | true 71 | end 72 | 73 | 74 | def do_create_builds(builds, update = false) 75 | builds.reverse.each do |build_data| 76 | ## Find Build in Redmine 77 | jenkins_build = jenkins_job.builds.find_by_number(build_data['number']) 78 | 79 | if jenkins_build.nil? 80 | create_build(build_data['number']) 81 | elsif !jenkins_build.nil? && update 82 | update_build(jenkins_build, build_data['number']) 83 | end 84 | end 85 | 86 | clean_up_builds 87 | end 88 | 89 | 90 | def create_build(build_number) 91 | ## Get BuildDetails from Jenkins 92 | build_details = get_jenkins_build_details(build_number) 93 | 94 | ## Create a new AR object to store data 95 | build = jenkins_job.builds.new 96 | build.number = build_number 97 | build.result = build_details['result'].nil? ? 'running' : build_details['result'] 98 | build.building = build_details['building'] 99 | build.duration = build_details['duration'] 100 | build.finished_at = Time.at(build_details['timestamp'].to_f / 1000) 101 | build.author = User.current 102 | build.save! 103 | 104 | ## Update changesets 105 | create_changeset(build, build_details['changeSet']['items']) 106 | end 107 | 108 | 109 | def update_build(build, build_number) 110 | ## Get BuildDetails from Jenkins 111 | build_details = get_jenkins_build_details(build_number) 112 | 113 | ## Update the AR object with new data 114 | build.result = build_details['result'].nil? ? 'running' : build_details['result'] 115 | build.building = build_details['building'] 116 | build.duration = build_details['duration'] 117 | build.finished_at = Time.at(build_details['timestamp'].to_f / 1000) 118 | build.save! 119 | 120 | ## Update changesets 121 | create_changeset(build, build_details['changeSet']['items']) 122 | end 123 | 124 | 125 | def clean_up_builds 126 | jenkins_job.builds.first(number_of_builds_to_delete).map(&:destroy) if too_much_builds? 127 | end 128 | 129 | 130 | def too_much_builds? 131 | jenkins_job.builds.size > jenkins_job.builds_to_keep 132 | end 133 | 134 | 135 | def number_of_builds_to_delete 136 | jenkins_job.builds.size - jenkins_job.builds_to_keep 137 | end 138 | 139 | 140 | def create_changeset(build, changesets) 141 | changesets.each do |changeset| 142 | build_changeset = jenkins_job.repository.changesets.find_by_revision(changeset['commitId']) 143 | next if build_changeset.nil? 144 | build.changesets << build_changeset unless build.changesets.include?(build_changeset) 145 | end 146 | end 147 | 148 | 149 | def get_jenkins_job_details 150 | begin 151 | data = jenkins_client.job.list_details(jenkins_job.name) 152 | rescue => e 153 | @errors << e.message 154 | else 155 | @job_data = data 156 | end 157 | end 158 | 159 | 160 | def get_jenkins_build_details(build_number) 161 | begin 162 | data = jenkins_client.job.get_build_details(jenkins_job.name, build_number) 163 | rescue => e 164 | @errors << e.message 165 | nil 166 | else 167 | data 168 | end 169 | end 170 | 171 | 172 | def color_to_state(color) 173 | case color 174 | when 'blue' 175 | 'success' 176 | when 'red' 177 | 'failure' 178 | when 'notbuilt' 179 | 'notbuilt' 180 | when 'blue_anime' 181 | 'running' 182 | when 'red_anime' 183 | 'running' 184 | else 185 | '' 186 | end 187 | end 188 | 189 | end 190 | end 191 | -------------------------------------------------------------------------------- /app/use_cases/jenkins_jobs/create_builds.rb: -------------------------------------------------------------------------------- 1 | module JenkinsJobs 2 | class CreateBuilds < Base 3 | 4 | def execute 5 | return if !job_status_updated? 6 | do_create_builds(job_data['builds'].take(jenkins_job.builds_to_keep)) 7 | end 8 | 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/use_cases/jenkins_jobs/trigger_build.rb: -------------------------------------------------------------------------------- 1 | module JenkinsJobs 2 | class TriggerBuild < Base 3 | 4 | def execute 5 | build_number = '' 6 | opts = {} 7 | opts['build_start_timeout'] = 30 if jenkins_job.wait_for_build_id 8 | 9 | begin 10 | build_number = jenkins_client.job.build(jenkins_job.name, {}, opts) 11 | rescue => e 12 | @errors << e.message 13 | else 14 | jenkins_job.latest_build_number = build_number if jenkins_job.wait_for_build_id 15 | jenkins_job.state = 'running' 16 | jenkins_job.save! 17 | end 18 | end 19 | 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/use_cases/jenkins_jobs/update_all_builds.rb: -------------------------------------------------------------------------------- 1 | module JenkinsJobs 2 | class UpdateAllBuilds < Base 3 | 4 | def execute 5 | return if !job_status_updated? 6 | do_create_builds(job_data['builds'].take(jenkins_job.builds_to_keep), true) 7 | end 8 | 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/use_cases/jenkins_jobs/update_last_build.rb: -------------------------------------------------------------------------------- 1 | module JenkinsJobs 2 | class UpdateLastBuild < Base 3 | 4 | def execute 5 | return if !job_status_updated? 6 | last_build = job_data['builds'].any? ? [job_data['builds'].first] : [] 7 | do_create_builds(last_build, true) 8 | end 9 | 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/common/_jenkins_js_headers.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :header_tags do 2 | = bootstrap_load_base 3 | = bootstrap_load_module(:label) 4 | = bootstrap_load_module(:modals) 5 | = bootstrap_load_module(:pagination) 6 | = bootstrap_load_module(:switch) 7 | = bootstrap_load_module(:tables) 8 | = bootstrap_load_module(:font_awesome) 9 | 10 | = stylesheet_link_tag 'application', plugin: 'redmine_jenkins' 11 | 12 | #modal-box 13 | #modal-box-close-only 14 | -------------------------------------------------------------------------------- /app/views/common/_jenkins_sidebar.html.haml: -------------------------------------------------------------------------------- 1 | %h3= l(:label_jenkins) 2 | 3 | - if User.current.allowed_to?(:edit_jenkins_settings, @project) 4 | %p= link_to l(:label_settings), settings_project_path(@project, 'jenkins') 5 | -------------------------------------------------------------------------------- /app/views/jenkins/_jobs_list.html.haml: -------------------------------------------------------------------------------- 1 | - if jobs.empty? 2 | .nodata= l(:label_no_data) 3 | 4 | - else 5 | 6 | .flash-messages 7 | 8 | %table{ class: 'table table-hover' } 9 | %thead 10 | %tr 11 | %th= l(:label_job_name) 12 | %th= l(:label_latest_build) 13 | %th= l(:label_job_state) 14 | %th= l(:label_job_duration) 15 | %th= l(:label_last_changesets) 16 | %th= l(:label_job_history) 17 | %th 18 | 19 | %tbody 20 | - jobs.each do |job| 21 | = render partial: 'jenkins_jobs/job_solo', locals: { job: job } 22 | 23 | :javascript 24 | $(document).ready(function() { initModalBoxes(modals); }); 25 | -------------------------------------------------------------------------------- /app/views/jenkins/index.html.haml: -------------------------------------------------------------------------------- 1 | = render partial: 'common/jenkins_js_headers' 2 | 3 | .contextual= link_to l(:label_refresh_jobs), jenkins_refresh_path(@project), class: 'icon icon-reload', remote: true 4 | 5 | %h2= l(:label_jobs_list) 6 | 7 | #jobs-list= render partial: 'jenkins/jobs_list', locals: { jobs: @jobs } 8 | 9 | - content_for :sidebar do 10 | = render partial: 'common/jenkins_sidebar' 11 | -------------------------------------------------------------------------------- /app/views/jenkins/jenkins_instructions.html.haml: -------------------------------------------------------------------------------- 1 | = render partial: 'common/jenkins_js_headers' 2 | 3 | %h2= l(:label_jobs_list) 4 | 5 | - content_for :sidebar do 6 | = render partial: 'common/jenkins_sidebar' 7 | -------------------------------------------------------------------------------- /app/views/jenkins/refresh.js.erb: -------------------------------------------------------------------------------- 1 | <%= js_render_partial('#jobs-list', 'jenkins/jobs_list', locals: { jobs: @jobs }) %> 2 | <%= render_flash_messages_as_js('.flash-messages') %> 3 | $('#jobs-list table tbody').effect("highlight", {color: '#D9EDF7'}, 1500); 4 | -------------------------------------------------------------------------------- /app/views/jenkins_jobs/_history.html.haml: -------------------------------------------------------------------------------- 1 | %table{ class: 'table table-hover' } 2 | %thead 3 | %tr 4 | %th= l(:label_build_number) 5 | %th= l(:label_build_result) 6 | %th= l(:label_finished_at) 7 | 8 | %tbody 9 | - builds.each do |build| 10 | %tr 11 | %td 12 | %span{ class: 'label label-info' }= link_to "##{build.number}", build.url 13 | 14 | %td 15 | %span{ class: "label label-#{state_to_css_class(build.result)}" }= build.result.capitalize 16 | 17 | - if build.result == 'running' 18 | %span{ class: 'icon icon-running' } 19 | 20 | %td= format_time build.finished_at 21 | 22 | = paginate builds, params: { controller: 'jenkins_jobs', action: 'history' } 23 | -------------------------------------------------------------------------------- /app/views/jenkins_jobs/_job_solo.html.haml: -------------------------------------------------------------------------------- 1 | - present job do |p| 2 | %tr{ id: p.job_id } 3 | %td= p.job_info 4 | %td= p.latest_build_infos 5 | %td= p.job_state 6 | %td= p.latest_build_duration 7 | %td= p.latest_changesets 8 | %td= p.build_history 9 | %td= p.job_actions 10 | -------------------------------------------------------------------------------- /app/views/jenkins_jobs/build.js.erb: -------------------------------------------------------------------------------- 1 | <%= render_flash_messages_as_js('.flash-messages') %> 2 | -------------------------------------------------------------------------------- /app/views/jenkins_jobs/console.html.haml: -------------------------------------------------------------------------------- 1 | %pre{ class: 'jenkins-output' }= @console_output 2 | -------------------------------------------------------------------------------- /app/views/jenkins_jobs/create.js.erb: -------------------------------------------------------------------------------- 1 | <%= js_render_template('#modal-box', 'jenkins_jobs/new') %> 2 | -------------------------------------------------------------------------------- /app/views/jenkins_jobs/edit.html.haml: -------------------------------------------------------------------------------- 1 | = labelled_form_for :jenkins_jobs, @job, 2 | url: jenkins_job_path(@project, @job), 3 | html: { method: :put, class: 'tabular', remote: true } do |f| 4 | 5 | .flash-messages= error_messages_for 'job' 6 | 7 | %p= f.select :name, options_for_select(@jobs.collect {|job| [job, job]}, [@job.name, @job.name]), {}, { disabled: true } 8 | %p= f.select :repository_id, options_for_select(@project.repositories.collect {|r| [ r.identifier.blank? ? 'default' : r.identifier, r.id ]}, render_selected_repo(@job)) 9 | %p= f.text_field :builds_to_keep 10 | -------------------------------------------------------------------------------- /app/views/jenkins_jobs/history.html.haml: -------------------------------------------------------------------------------- 1 | #job_builds_history= render partial: 'history', locals: { builds: @builds } 2 | -------------------------------------------------------------------------------- /app/views/jenkins_jobs/history.js.erb: -------------------------------------------------------------------------------- 1 | <%= js_render_partial('#job_builds_history', 'jenkins_jobs/history', locals: { builds: @builds }) %> 2 | -------------------------------------------------------------------------------- /app/views/jenkins_jobs/new.html.haml: -------------------------------------------------------------------------------- 1 | = labelled_form_for :jenkins_jobs, @job, 2 | url: jenkins_jobs_path(@project), 3 | html: { method: :post, class: 'tabular', remote: true } do |f| 4 | 5 | .flash-messages= error_messages_for 'job' 6 | 7 | %p= f.select :name, options_for_select(@jobs.collect {|job| [job, job]}), label: l(:field_job_name) 8 | %p= f.select :repository_id, options_for_select(@project.repositories.collect {|r| [ r.identifier.blank? ? 'default' : r.identifier, r.id ]}) 9 | %p= f.text_field :builds_to_keep 10 | -------------------------------------------------------------------------------- /app/views/jenkins_jobs/refresh.js.erb: -------------------------------------------------------------------------------- 1 | <%= js_render_partial("##{@job.job_id}", 'jenkins_jobs/job_solo', method: :replace, locals: { job: @job }) %> 2 | $('#<%= @job.job_id %>').effect("highlight", {color: '#D9EDF7'}, 1500); 3 | <%= render_flash_messages_as_js('.flash-messages') %> 4 | initModalBoxes(modals); 5 | -------------------------------------------------------------------------------- /app/views/jenkins_jobs/update.js.erb: -------------------------------------------------------------------------------- 1 | <%= js_render_template('#modal-box', 'jenkins_jobs/edit') %> 2 | -------------------------------------------------------------------------------- /app/views/jenkins_settings/test_connection.html.haml: -------------------------------------------------------------------------------- 1 | %p 2 | %b= l(:label_jenkins_version) + ' : ' + @content[:version].to_s 3 | %p 4 | %b= l(:label_jenkins_jobs_count) + ' : ' + @content[:jobs_count].to_s 5 | 6 | - if @content[:errors].any? 7 | %pre 8 | - @content[:errors].each do |error| 9 | = error 10 | -------------------------------------------------------------------------------- /app/views/projects/settings/_jobs_list.html.haml: -------------------------------------------------------------------------------- 1 | #jobs-list 2 | - if @jobs.length == 0 3 | - if !@error.nil? 4 | = @error 5 | - else 6 | = l(:label_jobs_list_empty) 7 | - else 8 | %table{ class: 'table table-hover' } 9 | %thead 10 | %tr 11 | %th= l(:label_job_name) 12 | %th= l(:label_linked_repository) 13 | %th= l(:label_number_of_stored_builds) 14 | %th= l(:label_number_of_jenkins_builds) 15 | %th= l(:label_number_of_builds_to_keep) 16 | %th 17 | 18 | %tbody 19 | - @jobs.each do |job| 20 | %tr 21 | %td= job.name 22 | %td= render_repo_name(job) 23 | %td= job.builds.size 24 | %td= @jenkins_setting.number_of_builds_for(job.name) 25 | %td= job.builds_to_keep 26 | %td 27 | = link_to l(:button_edit), edit_jenkins_job_path(@project, job), class: 'icon icon-edit modal-box' 28 | = link_to l(:button_delete), jenkins_job_path(@project, job), 29 | remote: true, 30 | method: :delete, 31 | confirm: l(:text_are_you_sure), 32 | class: 'icon icon-del' 33 | -------------------------------------------------------------------------------- /app/views/projects/settings/_redmine_jenkins.html.haml: -------------------------------------------------------------------------------- 1 | = render partial: 'common/jenkins_js_headers' 2 | 3 | = error_messages_for 'jenkins_setting' 4 | 5 | = form_for @jenkins_setting, url: jenkins_settings_save_path(@project), 6 | html: { method: :put } do |f| 7 | 8 | %div{ class: 'box tabular' } 9 | %p 10 | %label= l(:label_jenkins_url) 11 | = f.text_field :url, size: 50 12 | %br 13 | %em= l(:label_jenkins_url_desc) 14 | 15 | %p 16 | %label= l(:field_user) 17 | = f.text_field :auth_user, size: 20 18 | 19 | %p 20 | %label= l(:field_password) 21 | = f.password_field :auth_password, size: 20 22 | 23 | %p 24 | %label= l(:label_show_compact) 25 | = bootstrap_switch_tag do 26 | = f.check_box :show_compact, {}, 'true' 27 | 28 | %p 29 | %label= l(:label_wait_for_build_id) 30 | = bootstrap_switch_tag do 31 | = f.check_box :wait_for_build_id, {}, 'true' 32 | 33 | %br 34 | = submit_tag l(:button_save) 35 | = link_to l(:label_test_connection), jenkins_settings_test_connection_path(@project), class: 'icon icon-test-connection modal-box-close-only' 36 | 37 | 38 | %fieldset{ class: 'box' } 39 | %legend 40 | = l(:label_jobs_list) 41 | - if !@project.jenkins_setting.nil? 42 |  -  43 | = link_to l(:label_add_jenkins_job), new_jenkins_job_path(@project), class: 'icon icon-add modal-box' 44 | 45 | = render partial: 'projects/settings/jobs_list' 46 | 47 | :javascript 48 | $(document).ready(function() { setBootstrapSwitch(); initModalBoxes(modals); }); 49 | -------------------------------------------------------------------------------- /assets/images/changeset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbox-web/redmine_jenkins/6c7290a580f5a9e90a90b1f4224a7abb3849fb59/assets/images/changeset.png -------------------------------------------------------------------------------- /assets/images/health-00to19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbox-web/redmine_jenkins/6c7290a580f5a9e90a90b1f4224a7abb3849fb59/assets/images/health-00to19.png -------------------------------------------------------------------------------- /assets/images/health-20to39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbox-web/redmine_jenkins/6c7290a580f5a9e90a90b1f4224a7abb3849fb59/assets/images/health-20to39.png -------------------------------------------------------------------------------- /assets/images/health-40to59.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbox-web/redmine_jenkins/6c7290a580f5a9e90a90b1f4224a7abb3849fb59/assets/images/health-40to59.png -------------------------------------------------------------------------------- /assets/images/health-60to79.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbox-web/redmine_jenkins/6c7290a580f5a9e90a90b1f4224a7abb3849fb59/assets/images/health-60to79.png -------------------------------------------------------------------------------- /assets/images/health-80plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbox-web/redmine_jenkins/6c7290a580f5a9e90a90b1f4224a7abb3849fb59/assets/images/health-80plus.png -------------------------------------------------------------------------------- /assets/images/running.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbox-web/redmine_jenkins/6c7290a580f5a9e90a90b1f4224a7abb3849fb59/assets/images/running.gif -------------------------------------------------------------------------------- /assets/images/test_connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbox-web/redmine_jenkins/6c7290a580f5a9e90a90b1f4224a7abb3849fb59/assets/images/test_connection.png -------------------------------------------------------------------------------- /assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | .label a { color: #FFFFFF; } 2 | #jobs-list .fa { display: inline-block; margin: 5px; } 3 | .icon-running { background-image: url("../images/running.gif"); margin-left: 4px; } 4 | .icon-test-connection { background-image: url("../images/test_connection.png"); margin-left: 4px; } 5 | dt.build_activity { background-image: url("../images/changeset.png"); } 6 | pre.jenkins-output { 7 | white-space: pre-wrap; /* CSS 3 */ 8 | white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ 9 | white-space: -pre-wrap; /* Opera 4-6 */ 10 | white-space: -o-pre-wrap; /* Opera 7 */ 11 | word-wrap: break-word; /* Internet Explorer 5.5+ */ 12 | } 13 | 14 | ul.changesets_list li p { 15 | display: inline; 16 | } 17 | 18 | ul.changesets_list li p.revision { 19 | padding-right: 7px; 20 | } 21 | 22 | div.changesets.invisible { 23 | display: none; 24 | } 25 | -------------------------------------------------------------------------------- /config/locales/de.yml: -------------------------------------------------------------------------------- 1 | de: 2 | label_jenkins: Jenkins 3 | label_build_activity_plural: Jenkins 4 | 5 | ### SETTINGS 6 | label_jenkins_url: Jenkins URL 7 | label_jenkins_url_desc: 'inclusive http://, https:// ...' 8 | label_show_compact: Kompakte Anzeige 9 | label_wait_for_build_id: Warte auf Build Id 10 | label_test_connection: Verbindung testen 11 | label_jenkins_version: Jenkins' Version 12 | label_jenkins_jobs_count: Anzahl Aufgaben in Jenkins 13 | 14 | notice_settings_created: Einstellungen erfolgreich erstellt 15 | notice_settings_updated: Einstellungen erfolgreich aktualisiert 16 | notice_settings_update_failed: Einstellungen fehlerhaft 17 | 18 | label_jobs_list: Aufgabenliste 19 | label_jobs_list_empty: Leer 20 | label_update_jobs_list: Aufgabenliste aktualisieren 21 | label_number_of_stored_builds: Anzahl Builds in Redmine 22 | label_number_of_jenkins_builds: Anzahl Builds in Jenkins 23 | label_number_of_builds_to_keep: Anzahl in Redmine zu haltender Builds 24 | label_last_changesets: Letzte Änderungen 25 | label_add_jenkins_job: Aufgaben hinzufügen 26 | 27 | error_http_error: "HTTP Error %{code}" 28 | error_invalid_url: URL ungültig 29 | error_no_settings: "Keine Einstellungen gefunden, bitte überprüfen" 30 | error_jenkins_connection: Keine Verbindung mit Jenkins möglich 31 | 32 | ### JOBS 33 | label_job_name: Name 34 | label_latest_build: Letzter Build 35 | label_job_state: Status 36 | label_job_history: Historie 37 | label_see_history: see Historie 38 | label_build_now: Jetzt Bauen 39 | label_refresh_jobs: Liste der Aufgaben neu laden 40 | label_refresh_builds: Liste der Builds neu laden 41 | label_build_number: Auftragsnummer 42 | label_build_result: Resultat 43 | label_finished_at: Fertig um 44 | label_see_console_output: Konsolenausgabe 45 | label_build_accepted: "%{job_name}, Auftrag %{build_id} gestartet" 46 | label_linked_repository: zugehöriges Archiv 47 | label_job_duration: Dauer 48 | label_display_more: Display more 49 | 50 | notice_job_added: Aufgabe hinzugefügt 51 | notice_job_add_failed: Hinzufügen der Aufgabe gescheitert 52 | notice_job_deleted: Aufgabe gelöscht 53 | 54 | field_repository: Archiv 55 | field_job_name: Name 56 | field_builds_to_keep: Anzahl zu behaltender Builds 57 | 58 | ### ACTIVITY 59 | label_build: Build 60 | 61 | ### PERMISSIONS 62 | permission_view_jenkins_jobs: Build anzeigen 63 | permission_build_jenkins_jobs: Build starten 64 | permission_view_build_activity: Build Aktivität 65 | permission_edit_jenkins_settings: Einstellungen bearbeiten 66 | 67 | ### QUERY 68 | field_jenkins_job: Aufgabe 69 | field_jenkins_build: Build 70 | 71 | use_cases: 72 | jenkins_job: 73 | create_builds: 74 | success: "Job '%{jenkins_job}' builds successfully created" 75 | failed: "Errors while creating builds for job '%{jenkins_job}' : %{errors}" 76 | trigger_build: 77 | success: "Job '%{jenkins_job}' successfully started" 78 | failed: "Errors while starting job '%{jenkins_job}' : %{errors}" 79 | update_all_builds: 80 | success: "Job '%{jenkins_job}' builds successfully updated" 81 | failed: "Errors while updating job '%{jenkins_job}' builds : %{errors}" 82 | update_last_build: 83 | success: "Job '%{jenkins_job}' last build successfully updated" 84 | failed: "Errors while updating job '%{jenkins_job}' last build : %{errors}" 85 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | label_jenkins: Jenkins 3 | label_build_activity_plural: Jenkins 4 | 5 | ### SETTINGS 6 | label_jenkins_url: Jenkins URL 7 | label_jenkins_url_desc: 'with http://, https:// ...' 8 | label_show_compact: Show compact view 9 | label_wait_for_build_id: "Wait for build id" 10 | label_test_connection: Test connection 11 | label_jenkins_version: Jenkins version 12 | label_jenkins_jobs_count: Number of jobs in Jenkins 13 | 14 | notice_settings_created: Settings created successfully 15 | notice_settings_updated: Settings updated successfully 16 | notice_settings_update_failed: Settings update failed 17 | 18 | label_jobs_list: List of jobs 19 | label_jobs_list_empty: Empty 20 | label_update_jobs_list: Update Job list 21 | label_number_of_stored_builds: Number of stored builds in Redmine 22 | label_number_of_jenkins_builds: Number of builds in Jenkins 23 | label_number_of_builds_to_keep: Number of builds to keep in Redmine 24 | label_last_changesets: Latest changes 25 | label_add_jenkins_job: Add a Job 26 | 27 | error_http_error: "HTTP Error %{code}" 28 | error_invalid_url: URL invalid 29 | error_no_settings: "No settings for this project. Please confirm settings" 30 | error_jenkins_connection: Cannot connect to Jenkins 31 | 32 | ### JOBS 33 | label_job_name: Name 34 | label_latest_build: Latest build 35 | label_job_state: State 36 | label_job_history: History 37 | label_see_history: see History 38 | label_build_now: Build Now 39 | label_refresh_jobs: Refesh the list of jobs 40 | label_refresh_builds: Refresh the list of builds 41 | label_build_number: Build number 42 | label_build_result: Result 43 | label_finished_at: Finished at 44 | label_see_console_output: See console output 45 | label_build_accepted: "%{job_name}, build %{build_id} started" 46 | label_linked_repository: Linked repository 47 | label_job_duration: Duration 48 | label_display_more: Display more 49 | 50 | notice_job_added: Job added 51 | notice_job_add_failed: Adding job failed 52 | notice_job_deleted: Job deleted 53 | 54 | field_repository: Repository 55 | field_job_name: Job name 56 | field_builds_to_keep: Builds to keep 57 | 58 | ### ACTIVITY 59 | label_build: Build 60 | 61 | ### PERMISSIONS 62 | permission_view_jenkins_jobs: View builds 63 | permission_build_jenkins_jobs: Start build 64 | permission_view_build_activity: See build activity 65 | permission_edit_jenkins_settings: Edit settings 66 | 67 | ### QUERY 68 | field_jenkins_job: Job 69 | field_jenkins_build: Build 70 | 71 | use_cases: 72 | jenkins_job: 73 | create_builds: 74 | success: "Job '%{jenkins_job}' builds successfully created" 75 | failed: "Errors while creating builds for job '%{jenkins_job}' : %{errors}" 76 | trigger_build: 77 | success: "Job '%{jenkins_job}' successfully started" 78 | failed: "Errors while starting job '%{jenkins_job}' : %{errors}" 79 | update_all_builds: 80 | success: "Job '%{jenkins_job}' builds successfully updated" 81 | failed: "Errors while updating job '%{jenkins_job}' builds : %{errors}" 82 | update_last_build: 83 | success: "Job '%{jenkins_job}' last build successfully updated" 84 | failed: "Errors while updating job '%{jenkins_job}' last build : %{errors}" 85 | -------------------------------------------------------------------------------- /config/locales/fr.yml: -------------------------------------------------------------------------------- 1 | fr: 2 | label_jenkins: Jenkins 3 | label_build_activity_plural: Jenkins 4 | 5 | ### SETTINGS 6 | label_jenkins_url: URL Jenkins 7 | label_jenkins_url_desc: 'avec http://, https:// ...' 8 | label_show_compact: Montrer Vue Compacte 9 | label_wait_for_build_id: "Attendre l'ID de la construction" 10 | label_test_connection: Tester la connexion 11 | label_jenkins_version: Version de Jenkins 12 | label_jenkins_jobs_count: Nombre de jobs dans Jenkins 13 | 14 | notice_settings_created: Paramètres créés avec succès 15 | notice_settings_updated: Paramètres mis à jour avec succès 16 | notice_settings_update_failed: Erreur lors de la mise à jour des paramètres 17 | 18 | label_jobs_list: Liste des jobs 19 | label_jobs_list_empty: Vide 20 | label_update_jobs_list: Mettre à jour la liste 21 | label_number_of_stored_builds: Nombre de builds dans Redmine 22 | label_number_of_jenkins_builds: Nombre de builds dans Jenkins 23 | label_number_of_builds_to_keep: Nombre de builds à conserver dans Redmine 24 | label_last_changesets: Derniers changements 25 | label_add_jenkins_job: Ajouter un job 26 | 27 | error_http_error: "HTTP Error %{code}" 28 | error_invalid_url: URL invalide 29 | error_no_settings: "Le plugin Jenkins n'est pas configuré. Veuillez le configurer d'abord." 30 | error_jenkins_connection: Erreur lors de la connexion à Jenkins 31 | 32 | ### JOBS 33 | label_job_name: Nom 34 | label_latest_build: Dernière construction 35 | label_job_state: Etat 36 | label_job_history: Historique 37 | label_see_history: Voir l'historique 38 | label_build_now: Lancer la construction 39 | label_refresh_jobs: Rafraîchir la liste des jobs 40 | label_refresh_builds: Rafraîchir la liste des builds 41 | label_build_number: Numéro de build 42 | label_build_result: Résultat 43 | label_finished_at: Terminé le 44 | label_see_console_output: Voir la sortie console 45 | label_build_accepted: "%{job_name}, construction lancée %{build_id}" 46 | label_linked_repository: Dépôt lié 47 | label_job_duration: Durée 48 | label_display_more: Voir la suite 49 | 50 | notice_job_added: Job ajouté 51 | notice_job_add_failed: Erreur lors de l'ajout du job 52 | notice_job_deleted: Job supprimé 53 | 54 | field_repository: Dépôt 55 | field_job_name: Nom du Job 56 | field_builds_to_keep: Nombre de builds à conserver 57 | 58 | ### ACTIVITY 59 | label_build: Construction 60 | 61 | ### PERMISSIONS 62 | permission_view_jenkins_jobs: Voir les jobs 63 | permission_build_jenkins_jobs: Lancer la construction d'un job 64 | permission_view_build_activity: "Voir l'activité des jobs" 65 | permission_edit_jenkins_settings: Modifier les paramètres 66 | 67 | ### QUERY 68 | field_jenkins_job: Job 69 | field_jenkins_build: Build 70 | 71 | use_cases: 72 | jenkins_job: 73 | create_builds: 74 | success: "Builds du job '%{jenkins_job}' créés avec succès" 75 | failed: "Erreurs lors de la création des builds du job '%{jenkins_job}' : %{errors}" 76 | trigger_build: 77 | success: "Job '%{jenkins_job}' lancé avec succès" 78 | failed: "Erreurs lors du déclenchement du job '%{jenkins_job}' : %{errors}" 79 | update_all_builds: 80 | success: "Mise à jour des builds du job '%{jenkins_job}' réalisée avec succès" 81 | failed: "Erreurs lors la mise à jour des builds du job '%{jenkins_job}' : %{errors}" 82 | update_last_build: 83 | success: "Mise à jour du job '%{jenkins_job}' réalisée avec succès" 84 | failed: "Erreurs lors la mise à jour du job '%{jenkins_job}' : %{errors}" 85 | -------------------------------------------------------------------------------- /config/locales/pl.yml: -------------------------------------------------------------------------------- 1 | pl: 2 | label_jenkins: Jenkins 3 | label_build_activity_plural: Jenkins 4 | 5 | ### SETTINGS 6 | label_jenkins_url: Jenkins URL 7 | label_jenkins_url_desc: 'z http://, https:// ...' 8 | label_show_compact: Pokaż widok kompaktowy 9 | label_wait_for_build_id: "Oczekiwanie na ID testu" 10 | label_test_connection: Test połączenia 11 | label_jenkins_version: Wersja Jenkins 12 | 13 | notice_settings_created: Ustawienia utworzone poprawnie 14 | notice_settings_updated: Ustawienia zaktualizowane poprawnie 15 | notice_settings_update_failed: Nieudana aktualizaja ustawień 16 | 17 | label_jobs_list: Lista zadań 18 | label_jobs_list_empty: Pusta 19 | label_update_jobs_list: Aktualizuj listę zadań 20 | label_number_of_stored_builds: Liczba testów przechowywanych w Redmine 21 | label_number_of_jenkins_builds: Liczba testów w Jenkins 22 | label_last_changesets: Ostatnie zmiany 23 | label_add_jenkins_job: Dodaj zadanie 24 | 25 | error_http_error: "Błąd HTTP %{code}" 26 | error_invalid_url: Niepoprawny URL 27 | error_no_settings: "Brak ustawień dla tego projektu. Proszę potwierdzić ustawienia" 28 | error_jenkins_connection: Nie można połączyć się z Jenkins 29 | 30 | ### JOBS 31 | label_job_name: Nazwa 32 | label_latest_build: Ostatni test 33 | label_job_state: Stan 34 | label_job_history: Historia 35 | label_see_history: Zobacz historię 36 | label_build_now: Testuj teraz 37 | label_refresh_jobs: Odśwież listę zadań 38 | label_refresh_builds: Odśwież listę testów 39 | label_build_number: Numer testu 40 | label_build_result: Rezultat 41 | label_finished_at: Skończone o 42 | label_see_console_output: Zobacz wyjście konsoli 43 | label_build_accepted: "%{job_name}, test %{build_id} rozpoczęty" 44 | label_linked_repository: Przypisane repozytorium 45 | label_job_duration: Czas trwania 46 | label_display_more: Display more 47 | 48 | notice_job_added: Zadanie dodane 49 | notice_job_add_failed: Dodawanie zadania nieudane 50 | notice_job_deleted: Zadanie usunięte 51 | 52 | field_repository: Repozytorium 53 | field_job_name: Nazwa zadania 54 | 55 | ### ACTIVITY 56 | label_build: Test 57 | 58 | ### PERMISSIONS 59 | permission_view_jenkins_jobs: Testy 60 | permission_build_jenkins_jobs: Rozpocznij test 61 | permission_view_build_activity: Zobacz aktywność testu 62 | permission_edit_jenkins_settings: Edytuj ustawienia 63 | 64 | ### QUERY 65 | field_jenkins_job: Zadanie 66 | field_jenkins_build: Test 67 | 68 | use_cases: 69 | jenkins_job: 70 | create_builds: 71 | success: "Job '%{jenkins_job}' builds successfully created" 72 | failed: "Errors while creating builds for job '%{jenkins_job}' : %{errors}" 73 | trigger_build: 74 | success: "Job '%{jenkins_job}' successfully started" 75 | failed: "Errors while starting job '%{jenkins_job}' : %{errors}" 76 | update_all_builds: 77 | success: "Job '%{jenkins_job}' builds successfully updated" 78 | failed: "Errors while updating job '%{jenkins_job}' builds : %{errors}" 79 | update_last_build: 80 | success: "Job '%{jenkins_job}' last build successfully updated" 81 | failed: "Errors while updating job '%{jenkins_job}' last build : %{errors}" 82 | -------------------------------------------------------------------------------- /config/locales/ru.yml: -------------------------------------------------------------------------------- 1 | ru: 2 | label_jenkins: Jenkins 3 | label_build_activity_plural: Jenkins 4 | 5 | ### SETTINGS 6 | label_jenkins_url: Jenkins URL 7 | label_jenkins_url_desc: 'with http://, https:// ...' 8 | label_show_compact: Компактный прорстмотр 9 | label_wait_for_build_id: "Wait for build id" 10 | label_test_connection: Проверить соединение 11 | label_jenkins_version: Версия Jenkins 12 | label_jenkins_jobs_count: Количество задач Jenkins 13 | 14 | notice_settings_created: Настройки успешно созданы 15 | notice_settings_updated: Настройки успешно изменены 16 | notice_settings_update_failed: Ошибка при сохранении настроек 17 | 18 | label_jobs_list: Список задач 19 | label_jobs_list_empty: Пусто 20 | label_update_jobs_list: Обновить список задач 21 | label_number_of_stored_builds: Количество сохраненых сборок в Redmine 22 | label_number_of_jenkins_builds: Количество сборок в Jenkins 23 | label_number_of_builds_to_keep: Количество хранимых сборок в Redmine 24 | label_last_changesets: Последние изменения 25 | label_add_jenkins_job: Добавить задачу 26 | 27 | error_http_error: "HTTP Error %{code}" 28 | error_invalid_url: Некорректный URL 29 | error_no_settings: "Нет настроек для данного проекта. Пожалуйста подтвердите настройки" 30 | error_jenkins_connection: Невозможно соединиться с Jenkins 31 | 32 | ### JOBS 33 | label_job_name: Название 34 | label_latest_build: Последняя сборка 35 | label_job_state: Статус 36 | label_job_history: История 37 | label_see_history: просмотр истории 38 | label_build_now: Собрать сейчас 39 | label_refresh_jobs: Обновить список задач 40 | label_refresh_builds: Обновить список сборок 41 | label_build_number: Номер сборки 42 | label_build_result: Результат 43 | label_finished_at: Завершена в 44 | label_see_console_output: Вывод консоли 45 | label_build_accepted: "%{job_name}, сборка %{build_id} запущена" 46 | label_linked_repository: Присоединенное хранилище 47 | label_job_duration: Продолжительность 48 | label_display_more: Display more 49 | 50 | notice_job_added: Задача добавлена 51 | notice_job_add_failed: Ошибка при добавлении задачи 52 | notice_job_deleted: Задача удалена 53 | 54 | field_repository: Репозиторий 55 | field_job_name: Название задачи 56 | field_builds_to_keep: Количество хранимых сборок 57 | 58 | ### ACTIVITY 59 | label_build: Собрать 60 | 61 | ### PERMISSIONS 62 | permission_view_jenkins_jobs: Просмотр сборок 63 | permission_build_jenkins_jobs: Запуск сборок 64 | permission_view_build_activity: Просмотр активности сборок 65 | permission_edit_jenkins_settings: Изменение настроек 66 | 67 | ### QUERY 68 | field_jenkins_job: Задача 69 | field_jenkins_build: Сборка 70 | 71 | use_cases: 72 | jenkins_job: 73 | create_builds: 74 | success: "Job '%{jenkins_job}' builds successfully created" 75 | failed: "Errors while creating builds for job '%{jenkins_job}' : %{errors}" 76 | trigger_build: 77 | success: "Job '%{jenkins_job}' successfully started" 78 | failed: "Errors while starting job '%{jenkins_job}' : %{errors}" 79 | update_all_builds: 80 | success: "Job '%{jenkins_job}' builds successfully updated" 81 | failed: "Errors while updating job '%{jenkins_job}' builds : %{errors}" 82 | update_last_build: 83 | success: "Job '%{jenkins_job}' last build successfully updated" 84 | failed: "Errors while updating job '%{jenkins_job}' last build : %{errors}" 85 | -------------------------------------------------------------------------------- /config/locales/zh.yml: -------------------------------------------------------------------------------- 1 | zh: 2 | label_jenkins: Jenkins 3 | label_build_activity_plural: Jenkins 4 | 5 | ### SETTINGS 6 | label_jenkins_url: Jenkins URL 7 | label_jenkins_url_desc: '以 http://, https:// ... 开头' 8 | label_show_compact: 显示简洁视图 9 | label_wait_for_build_id: "Wait for build id" 10 | label_test_connection: 测试Jenkins连接配置 11 | label_jenkins_version: Jenkins版本 12 | label_jenkins_jobs_count: Jenkins中的任务数量 13 | 14 | notice_settings_created: 配置已创建。 15 | notice_settings_updated: 配置已更新。 16 | notice_settings_update_failed: 配置无法更新。请检查相关配置项。 17 | 18 | label_jobs_list: 任务列表 19 | label_jobs_list_empty: 暂无Jenkins任务 20 | label_update_jobs_list: 更新任务列表 21 | label_number_of_stored_builds: 在Redmine中保存的构建数量 22 | label_number_of_jenkins_builds: 在Jenkins中的构建数量 23 | label_number_of_builds_to_keep: 在Redmine中的构建上限数量 24 | label_last_changesets: 最近变更 25 | label_add_jenkins_job: 添加Jenkins任务 26 | 27 | error_http_error: "HTTP Error %{code}" 28 | error_invalid_url: URL不可用 29 | error_no_settings: "当前项目未设置。请确认 Jenkins设置。" 30 | error_jenkins_connection: 无法连接到Jenkins! 31 | 32 | ### JOBS 33 | label_job_name: 任务名称 34 | label_latest_build: 最近一次构建 35 | label_job_state: 状态 36 | label_job_history: 历史记录 37 | label_see_history: 查看历史记录 38 | label_build_now: 现在运行 39 | label_refresh_jobs: 刷新任务列表 40 | label_refresh_builds: 刷新构建列表 41 | label_build_number: 构建№ 42 | label_build_result: 结果 43 | label_finished_at: 结束时间 44 | label_see_console_output: 查看控制台输出 45 | label_build_accepted: "%{job_name}, 构建№ %{build_id} 已启动。" 46 | label_linked_repository: 关联版本库 47 | label_job_duration: 执行用时 48 | label_display_more: 显示更多 49 | 50 | notice_job_added: 任务已添加。 51 | notice_job_add_failed: 添加任务失败。 52 | notice_job_deleted: 任务已删除。 53 | 54 | field_repository: 版本库 55 | field_job_name: 任务名称 56 | field_builds_to_keep: 保留的构建数量 57 | 58 | ### ACTIVITY 59 | label_build: 构建 60 | 61 | ### PERMISSIONS 62 | permission_view_jenkins_jobs: 查看Jenkins任务 63 | permission_build_jenkins_jobs: 运行Jenkins任务 64 | permission_view_build_activity: 查看构建活动 65 | permission_edit_jenkins_settings: 编辑Jenkin配置 66 | 67 | ### QUERY 68 | field_jenkins_job: Jenkins任务 69 | field_jenkins_build: Jenkins构建 70 | 71 | use_cases: 72 | jenkins_job: 73 | create_builds: 74 | success: "任务 '%{jenkins_job}' 已创建。" 75 | failed: "创建任务 '%{jenkins_job}' 时报错。错误信息:%{errors}" 76 | trigger_build: 77 | success: "任务 '%{jenkins_job}' 正在运行。" 78 | failed: "运行任务 '%{jenkins_job}' 时报错。错误信息:%{errors}" 79 | update_all_builds: 80 | success: "任务 '%{jenkins_job}' 构建已更新" 81 | failed: "更新任务 '%{jenkins_job}' 的构建时报错。错误信息:%{errors}" 82 | update_last_build: 83 | success: "任务 '%{jenkins_job}' 最近一次构建已更新。" 84 | failed: "当更新任务 '%{jenkins_job}' 最近一次构建时报错。错误信息:%{errors}" 85 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | scope 'projects/:project_id' do 2 | get 'jenkins', to: 'jenkins#index' 3 | get 'jenkins/refresh', to: 'jenkins#refresh' 4 | 5 | put 'jenkins_settings/save' 6 | get 'jenkins_settings/test_connection' 7 | 8 | resources :jenkins_jobs do 9 | member do 10 | get 'build' 11 | get 'history' 12 | get 'console' 13 | get 'refresh' 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /contrib/travis/common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function log_header() { 4 | echo "######################" 5 | echo $1 6 | echo "" 7 | } 8 | 9 | 10 | function log_title() { 11 | echo "#### $1" 12 | } 13 | 14 | 15 | function log_ok() { 16 | echo "Done !" 17 | echo "" 18 | } 19 | 20 | 21 | function git_clone() { 22 | plugin_name=$1 23 | plugin_url=$2 24 | 25 | IFS='#' read url treeish <<< "$plugin_url" 26 | 27 | log_title "INSTALL ${plugin_name} PLUGIN" 28 | 29 | if [[ "$treeish" == "" ]] ; then 30 | git clone "${url}" "redmine/plugins/${plugin_name}" 31 | else 32 | git clone "${url}" "redmine/plugins/${plugin_name}" 33 | pushd "redmine/plugins/${plugin_name}" > /dev/null 34 | git checkout -q "$treeish" 35 | popd > /dev/null 36 | fi 37 | 38 | log_ok 39 | } 40 | -------------------------------------------------------------------------------- /contrib/travis/install_redmine.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | REDMINE_INSTALLER_DIR=$(dirname "$(readlink -f "$0")") 4 | source "$REDMINE_INSTALLER_DIR/common.sh" 5 | source "$REDMINE_INSTALLER_DIR/plugin.sh" 6 | source "$REDMINE_INSTALLER_DIR/redmine.sh" 7 | 8 | CURRENT_DIR=$(pwd) 9 | 10 | echo "" 11 | echo "######################" 12 | echo "REDMINE INSTALLATION SCRIPT" 13 | echo "" 14 | echo "REDMINE_VERSION : ${REDMINE_VERSION}" 15 | echo "REDMINE_URL : ${REDMINE_URL}" 16 | echo "CURRENT_DIR : ${CURRENT_DIR}" 17 | echo "GITHUB_SOURCE : ${GITHUB_SOURCE}" 18 | echo "PLUGIN_PATH : ${PLUGIN_PATH}" 19 | echo "" 20 | 21 | install_plugin_packages 22 | install_redmine 23 | install_plugin 24 | install_rspec 25 | install_plugin_dependencies 26 | finish_install 27 | -------------------------------------------------------------------------------- /contrib/travis/plugin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GITHUB_USER=${GITHUB_USER:-jbox-web} 4 | GITHUB_PROJECT=${GITHUB_PROJECT:-redmine_jenkins} 5 | 6 | function install_plugin_packages() { 7 | log_title "INSTALL ADDITIONAL PACKAGES" 8 | echo "No package to install" 9 | } 10 | 11 | function install_plugin_dependencies() { 12 | git_clone 'redmine_bootstrap_kit' 'https://github.com/jbox-web/redmine_bootstrap_kit.git' 13 | } 14 | -------------------------------------------------------------------------------- /contrib/travis/redmine.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | REDMINE_PACKAGE_URL="http://www.redmine.org/releases" 4 | REDMINE_SVN_URL="https://svn.redmine.org/redmine/branches" 5 | 6 | REDMINE_NAME="redmine-${REDMINE_VERSION}" 7 | REDMINE_PACKAGE="${REDMINE_NAME}.tar.gz" 8 | REDMINE_URL="${REDMINE_PACKAGE_URL}/${REDMINE_PACKAGE}" 9 | 10 | USE_SVN=${USE_SVN:-false} 11 | 12 | version=(${REDMINE_VERSION//./ }) 13 | major=${version[0]} 14 | minor=${version[1]} 15 | patch=${version[2]} 16 | 17 | GITHUB_SOURCE="${GITHUB_USER}/${GITHUB_PROJECT}" 18 | PLUGIN_PATH=${PLUGIN_PATH:-$GITHUB_SOURCE} 19 | PLUGIN_NAME=${PLUGIN_NAME:-$GITHUB_PROJECT} 20 | 21 | 22 | function install_redmine_from_package() { 23 | log_title "GET TARBALL" 24 | wget "${REDMINE_URL}" 25 | log_ok 26 | 27 | log_title "EXTRACT IT" 28 | tar xf "${REDMINE_PACKAGE}" 29 | log_ok 30 | } 31 | 32 | 33 | function install_redmine_from_svn() { 34 | log_title "GET SOURCES FROM SVN" 35 | svn co --non-interactive --trust-server-cert "${REDMINE_SVN_URL}/${REDMINE_VERSION}" "${REDMINE_NAME}" 36 | log_ok 37 | } 38 | 39 | 40 | function install_redmine() { 41 | if [ $USE_SVN == 'true' ] ; then 42 | install_redmine_from_svn 43 | else 44 | install_redmine_from_package 45 | fi 46 | } 47 | 48 | 49 | function install_plugin() { 50 | log_title "MOVE PLUGIN" 51 | # Move GITHUB_USER/GITHUB_PROJECT to redmine/plugins dir 52 | mv "${PLUGIN_PATH}" "${REDMINE_NAME}/plugins" 53 | # Remove parent dir (GITHUB_USER) 54 | rmdir $(dirname ${PLUGIN_PATH}) 55 | log_ok 56 | 57 | log_title "CREATE SYMLINK" 58 | ln -s "${REDMINE_NAME}" "redmine" 59 | ln -s "redmine/plugins/redmine_git_hosting/.git" "${REDMINE_NAME}/.git" 60 | log_ok 61 | 62 | log_title "INSTALL DATABASE FILE" 63 | if [ "$DATABASE_ADAPTER" == "mysql" ] ; then 64 | echo "Type : mysql" 65 | cp "redmine/plugins/${PLUGIN_NAME}/spec/database_mysql.yml" "redmine/config/database.yml" 66 | else 67 | echo "Type : postgres" 68 | cp "redmine/plugins/${PLUGIN_NAME}/spec/database_postgres.yml" "redmine/config/database.yml" 69 | fi 70 | 71 | log_ok 72 | } 73 | 74 | 75 | function install_rspec() { 76 | log_title "INSTALL RSPEC FILE" 77 | mkdir "redmine/spec" 78 | cp "redmine/plugins/${PLUGIN_NAME}/spec/root_spec_helper.rb" "redmine/spec/spec_helper.rb" 79 | log_ok 80 | 81 | if [ "$major" == "3" ] ; then 82 | if [ -f "redmine/plugins/${PLUGIN_NAME}/gemfiles/rails4.gemfile" ] ; then 83 | log_title "RAILS 4 : INSTALL GEMFILE" 84 | cp "redmine/plugins/${PLUGIN_NAME}/gemfiles/rails4.gemfile" "redmine/plugins/${PLUGIN_NAME}/Gemfile" 85 | log_ok 86 | fi 87 | else 88 | 89 | if [ -f "redmine/plugins/${PLUGIN_NAME}/gemfiles/rails3.gemfile" ] ; then 90 | log_title "RAILS 3 : INSTALL GEMFILE" 91 | cp "redmine/plugins/${PLUGIN_NAME}/gemfiles/rails3.gemfile" "redmine/plugins/${PLUGIN_NAME}/Gemfile" 92 | log_ok 93 | fi 94 | 95 | log_title "RAILS 3 : UPDATE REDMINE GEMFILE" 96 | 97 | echo "Update shoulda to 3.5.0" 98 | sed -i 's/gem "shoulda", "~> 3.3.2"/gem "shoulda", "~> 3.5.0"/' "redmine/Gemfile" 99 | log_ok 100 | 101 | echo "Let update shoulda-matchers to 2.7.0" 102 | sed -i 's/gem "shoulda-matchers", "1.4.1"/#gem "shoulda-matchers", "1.4.1"/' "redmine/Gemfile" 103 | log_ok 104 | 105 | echo "Update capybara to 2.2.0" 106 | sed -i 's/gem "capybara", "~> 2.1.0"/gem "capybara", "~> 2.2.0"/' "redmine/Gemfile" 107 | log_ok 108 | fi 109 | } 110 | 111 | 112 | function finish_install() { 113 | log_header "CURRENT DIRECTORY LISTING" 114 | ls -l "${CURRENT_DIR}" 115 | echo "" 116 | 117 | log_header "REDMINE PLUGIN DIRECTORY LISTING" 118 | ls -l "${REDMINE_NAME}/plugins" 119 | echo "" 120 | } 121 | -------------------------------------------------------------------------------- /db/migrate/20150316023100_create_jenkins_settings.rb: -------------------------------------------------------------------------------- 1 | class CreateJenkinsSettings < ActiveRecord::Migration 2 | 3 | def change 4 | create_table :jenkins_settings do |t| 5 | t.integer :project_id 6 | t.string :url 7 | t.string :auth_user, default: '' 8 | t.string :auth_password, default: '' 9 | t.boolean :show_compact, default: false 10 | t.boolean :get_build_details, default: true 11 | t.boolean :wait_for_build_id, default: false 12 | end 13 | 14 | add_index :jenkins_settings, :project_id, unique: true 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20150316023101_create_jenkins_jobs.rb: -------------------------------------------------------------------------------- 1 | class CreateJenkinsJobs < ActiveRecord::Migration 2 | 3 | def change 4 | create_table :jenkins_jobs do |t| 5 | t.integer :project_id 6 | t.integer :repository_id 7 | t.string :name 8 | t.integer :latest_build_number 9 | t.datetime :latest_build_date 10 | t.integer :latest_build_duration 11 | t.string :state 12 | t.text :health_report 13 | t.integer :builds_to_keep, default: 10 14 | t.text :description 15 | t.datetime :created_at 16 | t.datetime :updated_at 17 | end 18 | 19 | add_index :jenkins_jobs, :project_id 20 | add_index :jenkins_jobs, :repository_id 21 | add_index :jenkins_jobs, [:project_id, :name], unique: true 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /db/migrate/20150316023102_create_jenkins_builds.rb: -------------------------------------------------------------------------------- 1 | class CreateJenkinsBuilds < ActiveRecord::Migration 2 | 3 | def change 4 | create_table :jenkins_builds do |t| 5 | t.integer :jenkins_job_id 6 | t.integer :number 7 | t.integer :author_id 8 | t.string :result 9 | t.datetime :finished_at 10 | t.boolean :building 11 | t.integer :duration 12 | end 13 | 14 | add_index :jenkins_builds, :jenkins_job_id 15 | add_index :jenkins_builds, :author_id 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /db/migrate/20150316023103_create_jenkins_test_results.rb: -------------------------------------------------------------------------------- 1 | class CreateJenkinsTestResults < ActiveRecord::Migration 2 | 3 | def change 4 | create_table :jenkins_test_results do |t| 5 | t.integer :jenkins_build_id 6 | t.integer :fail_count 7 | t.integer :skip_count 8 | t.integer :total_count 9 | end 10 | 11 | add_index :jenkins_test_results, :jenkins_build_id 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20150316023104_create_changesets_jenkins_builds.rb: -------------------------------------------------------------------------------- 1 | class CreateChangesetsJenkinsBuilds < ActiveRecord::Migration 2 | 3 | def change 4 | create_table :changesets_jenkins_builds do |t| 5 | t.integer :changeset_id 6 | t.integer :jenkins_build_id 7 | end 8 | 9 | add_index :changesets_jenkins_builds, :changeset_id 10 | add_index :changesets_jenkins_builds, :jenkins_build_id 11 | add_index :changesets_jenkins_builds, [:changeset_id, :jenkins_build_id], unique: true, name: 'unique_index_on_changeset_jenkins_build' 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'redmine' 4 | 5 | require 'redmine_jenkins' 6 | 7 | Redmine::Plugin.register :redmine_jenkins do 8 | name 'Redmine Jenkins plugin' 9 | author 'Toshiyuki Ando r-labs, Nicolas Rodriguez' 10 | description 'This is a Jenkins plugin for Redmine' 11 | version '1.0-devel' 12 | url 'https://github.com/jbox-web/redmine_jenkins' 13 | author_url 'https://github.com/jbox-web' 14 | 15 | project_module :jenkins do 16 | permission :view_jenkins_jobs, {:jenkins => [:index]} 17 | permission :build_jenkins_jobs, {:jenkins => [:start_build]} 18 | permission :view_build_activity, {:activity => [:index]} 19 | permission :edit_jenkins_settings, {:jenkins_settings => [:save_settings]} 20 | end 21 | 22 | Redmine::MenuManager.map :project_menu do |menu| 23 | menu.push :jenkins, { controller: 'jenkins', action: 'index' }, caption: :label_jenkins, after: :repository, param: :project_id 24 | end 25 | 26 | activity_provider :build_activity, default: true, class_name: ['JenkinsBuild'] 27 | 28 | end 29 | -------------------------------------------------------------------------------- /lib/redmine_jenkins.rb: -------------------------------------------------------------------------------- 1 | ## Redmine Views Hooks 2 | require 'redmine_jenkins/hooks/add_activity_icon' 3 | 4 | ## Set up autoload of patches 5 | Rails.configuration.to_prepare do 6 | 7 | ## Redmine Jenkins Libs and Patches 8 | rbfiles = Rails.root.join('plugins', 'redmine_jenkins', 'lib', 'redmine_jenkins', '**', '*.rb') 9 | Dir.glob(rbfiles).each do |file| 10 | # Exclude Redmine Views Hooks from Rails loader to avoid multiple calls to hooks on reload in dev environment. 11 | require_dependency file unless File.dirname(file) == Rails.root.join('plugins', 'redmine_jenkins', 'lib', 'redmine_jenkins', 'hooks').to_s 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /lib/redmine_jenkins/error.rb: -------------------------------------------------------------------------------- 1 | module RedmineJenkins 2 | module Error 3 | 4 | # Used to register errors when pulling and pushing the conf file 5 | class JenkinsException < StandardError; end 6 | class JenkinsConnectionError < JenkinsException; end 7 | 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/redmine_jenkins/extra_loading.rb: -------------------------------------------------------------------------------- 1 | module RedmineJenkins 2 | module ExtraLoading 3 | 4 | # Adds plugin locales 5 | ::I18n.load_path += Dir.glob(Rails.root.join('plugins', 'redmine_jenkins', 'config', 'locales', '**', '*.yml')) 6 | 7 | # Load Forms and Concerns objects 8 | [ 9 | Rails.root.join('plugins', 'redmine_jenkins', 'app', 'presenters'), 10 | Rails.root.join('plugins', 'redmine_jenkins', 'app', 'services'), 11 | Rails.root.join('plugins', 'redmine_jenkins', 'app', 'use_cases') 12 | ].each do |dir| 13 | if Dir.exists?(dir) 14 | ActiveSupport::Dependencies.autoload_paths += [dir] 15 | end 16 | end 17 | 18 | # HAML gem 19 | Haml::Template.options[:attr_wrapper] = '"' 20 | 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/redmine_jenkins/hooks/add_activity_icon.rb: -------------------------------------------------------------------------------- 1 | module RedmineJenkins 2 | module Hooks 3 | class AddActivityIcon < Redmine::Hook::ViewListener 4 | 5 | def view_layouts_base_html_head(context={}) 6 | project = context[:project] 7 | return '' unless project 8 | controller = context[:controller] 9 | return '' unless controller 10 | action_name = controller.action_name 11 | return '' unless action_name 12 | 13 | if (controller.class.name == 'ActivitiesController' and action_name == 'index') 14 | return stylesheet_link_tag(:application, plugin: 'redmine_jenkins') 15 | end 16 | end 17 | 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/redmine_jenkins/patches/changeset_patch.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'changeset' 2 | 3 | module RedmineJenkins 4 | module Patches 5 | module ChangesetPatch 6 | 7 | def self.included(base) 8 | base.class_eval do 9 | unloadable 10 | 11 | has_many :changesets_jenkins_builds 12 | has_many :jenkins_builds, through: :changesets_jenkins_builds 13 | end 14 | end 15 | 16 | end 17 | end 18 | end 19 | 20 | unless Changeset.included_modules.include?(RedmineJenkins::Patches::ChangesetPatch) 21 | Changeset.send(:include, RedmineJenkins::Patches::ChangesetPatch) 22 | end 23 | -------------------------------------------------------------------------------- /lib/redmine_jenkins/patches/project_patch.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'project' 2 | 3 | module RedmineJenkins 4 | module Patches 5 | module ProjectPatch 6 | 7 | def self.included(base) 8 | base.send(:include, InstanceMethods) 9 | base.class_eval do 10 | unloadable 11 | 12 | has_one :jenkins_setting, dependent: :destroy 13 | has_many :jenkins_jobs, dependent: :destroy 14 | end 15 | end 16 | 17 | 18 | module InstanceMethods 19 | 20 | def jenkins_url 21 | jenkins_setting.url 22 | end 23 | 24 | 25 | def jenkins_connection 26 | jenkins_setting.jenkins_connection 27 | end 28 | 29 | 30 | def wait_for_build_id 31 | jenkins_setting.wait_for_build_id 32 | end 33 | 34 | end 35 | 36 | end 37 | end 38 | end 39 | 40 | unless Project.included_modules.include?(RedmineJenkins::Patches::ProjectPatch) 41 | Project.send(:include, RedmineJenkins::Patches::ProjectPatch) 42 | end 43 | -------------------------------------------------------------------------------- /lib/redmine_jenkins/patches/projects_controller_patch.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'projects_controller' 2 | 3 | module RedmineJenkins 4 | module Patches 5 | module ProjectsControllerPatch 6 | 7 | def self.included(base) 8 | base.send(:include, InstanceMethods) 9 | base.class_eval do 10 | unloadable 11 | 12 | helper :jenkins 13 | helper :redmine_bootstrap_kit 14 | 15 | alias_method_chain :settings, :redmine_jenkins 16 | end 17 | end 18 | 19 | 20 | module InstanceMethods 21 | 22 | def settings_with_redmine_jenkins(&block) 23 | settings_without_redmine_jenkins(&block) 24 | if @project.jenkins_setting.nil? 25 | @jenkins_setting = JenkinsSetting.new 26 | @jobs = [] 27 | else 28 | @jenkins_setting = @project.jenkins_setting 29 | @jobs = @project.jenkins_jobs 30 | end 31 | end 32 | 33 | end 34 | 35 | end 36 | end 37 | end 38 | 39 | unless ProjectsController.included_modules.include?(RedmineJenkins::Patches::ProjectsControllerPatch) 40 | ProjectsController.send(:include, RedmineJenkins::Patches::ProjectsControllerPatch) 41 | end 42 | -------------------------------------------------------------------------------- /lib/redmine_jenkins/patches/projects_helper_patch.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'projects_helper' 2 | 3 | module RedmineJenkins 4 | module Patches 5 | module ProjectsHelperPatch 6 | 7 | def self.included(base) 8 | base.send(:include, InstanceMethods) 9 | base.class_eval do 10 | unloadable 11 | 12 | alias_method_chain :project_settings_tabs, :redmine_jenkins 13 | end 14 | end 15 | 16 | 17 | module InstanceMethods 18 | 19 | def project_settings_tabs_with_redmine_jenkins(&block) 20 | tabs = project_settings_tabs_without_redmine_jenkins(&block) 21 | tabs.push({ 22 | :name => 'jenkins', 23 | :action => :edit_jenkins_settings, 24 | :partial => 'projects/settings/redmine_jenkins', 25 | :label => :label_jenkins 26 | }) 27 | tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)} 28 | tabs 29 | end 30 | 31 | end 32 | 33 | end 34 | end 35 | end 36 | 37 | unless ProjectsHelper.included_modules.include?(RedmineJenkins::Patches::ProjectsHelperPatch) 38 | ProjectsHelper.send(:include, RedmineJenkins::Patches::ProjectsHelperPatch) 39 | end 40 | -------------------------------------------------------------------------------- /lib/redmine_jenkins/patches/query_patch.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'query' 2 | 3 | module RedmineJenkins 4 | module Patches 5 | module QueryPatch 6 | 7 | def self.included(base) 8 | base.send(:include, InstanceMethods) 9 | base.class_eval do 10 | unloadable 11 | 12 | alias_method_chain :available_filters, :redmine_jenkins 13 | alias_method_chain :sql_for_field, :redmine_jenkins 14 | end 15 | 16 | end 17 | 18 | 19 | module InstanceMethods 20 | 21 | def available_filters_with_redmine_jenkins 22 | return @available_filters if @available_filters 23 | 24 | available_filters_without_redmine_jenkins 25 | 26 | return @available_filters unless project 27 | 28 | jenkins_filters 29 | 30 | @jenkins_filters.each do |filter| 31 | filter.available_values[:name] = I18n.t("field_#{filter.name}") 32 | 33 | if self.respond_to?(:add_available_filter) 34 | add_available_filter(filter.name, filter.available_values) 35 | else 36 | @available_filters[filter.name] = filter.available_values 37 | end 38 | end 39 | 40 | return @available_filters 41 | end 42 | 43 | 44 | def sql_for_field_with_redmine_jenkins(field, operator, value, db_table, db_field, is_custom_filter=false) 45 | case field 46 | when "jenkins_build" 47 | return sql_for_jenkins_build(field, operator, value) 48 | 49 | when "jenkins_job" 50 | return sql_for_jenkins_job(field, operator, value) 51 | 52 | else 53 | return sql_for_field_without_redmine_jenkins(field, operator, value, db_table, db_field, is_custom_filter) 54 | end 55 | end 56 | 57 | 58 | private 59 | 60 | 61 | def sql_for_jenkins_build(field, operator, value) 62 | return sql_for_always_false unless project 63 | 64 | jenkins_changesets = find_jenkins_changesets 65 | 66 | return sql_for_issues(jenkins_changesets) 67 | end 68 | 69 | 70 | def sql_for_jenkins_job(field, operator, value) 71 | return sql_for_always_false unless project 72 | 73 | if filters.has_key?('jenkins_build') 74 | return sql_for_always_true 75 | end 76 | 77 | jenkins_changesets = find_jenkins_changesets 78 | 79 | return sql_for_issues(jenkins_changesets) 80 | end 81 | 82 | 83 | # conditions always true 84 | def sql_for_always_true 85 | return "#{Issue.table_name}.id > 0" 86 | end 87 | 88 | 89 | # conditions always false 90 | def sql_for_always_false 91 | return "#{Issue.table_name}.id < 0" 92 | end 93 | 94 | 95 | def sql_for_issues(jenkins_changesets) 96 | return sql_for_always_false unless jenkins_changesets 97 | return sql_for_always_false if jenkins_changesets.length == 0 98 | 99 | value_revisions = jenkins_changesets.collect{|target| "#{ActiveRecord::Base.connection.quote(target.revision.to_s)}"}.join(",") 100 | sql = "#{Issue.table_name}.id IN" 101 | sql << "(SELECT changesets_issues.issue_id FROM changesets_issues" 102 | sql << " WHERE changesets_issues.changeset_id IN" 103 | sql << " (SELECT #{Changeset.table_name}.id FROM #{Changeset.table_name}" 104 | sql << " WHERE #{Changeset.table_name}.repository_id = #{project.repository.id}" 105 | sql << " AND #{Changeset.table_name}.revision IN (#{value_revisions})" 106 | sql << " )" 107 | sql << ")" 108 | 109 | return sql 110 | end 111 | 112 | 113 | def find_jenkins_changesets 114 | retval = [] 115 | find_jenkins_jobs.each do |job| 116 | builds = find_jenkins_builds(job) 117 | next if builds.length == 0 118 | cond_builds = builds.collect{|build| "#{ActiveRecord::Base.connection.quote(build.id.to_s)}"}.join(",") 119 | retval += ChangesetsJenkinsBuild.find(:all, :conditions => ["#{ChangesetsJenkinsBuild.table_name}.jenkins_build_id in (#{cond_builds})"], :order => "#{ChangesetsJenkinsBuild.table_name}.id DESC", :limit => 100) 120 | end 121 | 122 | return retval 123 | end 124 | 125 | 126 | def find_jenkins_jobs 127 | return [] unless project 128 | 129 | if filters.has_key?('jenkins_job') 130 | cond_jobs = "#{JenkinsJob.table_name}.project_id = #{project.id} and #{conditions_for('jenkins_job', operator_for('jenkins_job'), values_for('jenkins_job'))}" 131 | else 132 | cond_jobs = "#{JenkinsJob.table_name}.project_id = #{project.id}" 133 | end 134 | return JenkinsJob.find(:all, :conditions => cond_jobs) 135 | end 136 | 137 | 138 | def find_jenkins_builds(job) 139 | return [] unless job 140 | 141 | if filters.has_key?('jenkins_build') 142 | cond_builds = conditions_for('jenkins_build', operator_for('jenkins_build'), values_for('jenkins_build')) 143 | else 144 | cond_builds = "#{JenkinsBuild.table_name}.id > 0" #always true 145 | end 146 | 147 | return JenkinsBuild.find(:all, :conditions => ["#{JenkinsBuild.table_name}.jenkins_job_id = ? and #{cond_builds}", job.id], :order => "#{JenkinsBuild.table_name}.number DESC", :limit => 100) 148 | end 149 | 150 | 151 | def jenkins_filters 152 | @jenkins_filters = [] 153 | return @jenkins_filters unless project 154 | return @jenkins_filters unless @available_filters 155 | 156 | @jenkins_filters << JenkinsQueryFilter.new( 157 | "jenkins_job", 158 | {:type => :list_optional, 159 | :order => @available_filters.size + 1, 160 | :values => project.jenkins_jobs.collect {|job| [job.name, job.id.to_s]} 161 | }, 162 | JenkinsJob.table_name, 163 | "id") 164 | 165 | @jenkins_filters << JenkinsQueryFilter.new( 166 | "jenkins_build", 167 | { :type => :integer, :order => @available_filters.size + 2 }, 168 | JenkinsBuild.table_name, 169 | "number") 170 | 171 | return @jenkins_filters 172 | end 173 | 174 | 175 | def conditions_for(field, operator, value) 176 | retval = "" 177 | 178 | available_filters 179 | return retval unless @jenkins_filters 180 | filter = @jenkins_filters.detect {|hfilter| hfilter.name == field} 181 | return retval unless filter 182 | 183 | db_table = filter.db_table 184 | db_field = filter.db_field 185 | 186 | case operator 187 | when "=" 188 | retval = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{ActiveRecord::Base.connection.quote_string(val)}'"}.join(",") + ")" 189 | when "!" 190 | retval = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{ActiveRecord::Base.connection.quote_string(val)}'"}.join(",") + "))" 191 | when "!*" 192 | retval = "#{db_table}.#{db_field} IS NULL" 193 | retval << " OR #{db_table}.#{db_field} = ''" 194 | when "*" 195 | retval = "#{db_table}.#{db_field} IS NOT NULL" 196 | retval << " AND #{db_table}.#{db_field} <> ''" 197 | when ">=" 198 | retval = "#{db_table}.#{db_field} >= #{value.first.to_i}" 199 | when "<=" 200 | retval = "#{db_table}.#{db_field} <= #{value.first.to_i}" 201 | when "!~" 202 | retval = "#{db_table}.#{db_field} NOT LIKE '%#{ActiveRecord::Base.connection.quote_string(value.first.to_s.downcase)}%'" 203 | end 204 | return retval 205 | end 206 | 207 | end 208 | 209 | end 210 | end 211 | end 212 | 213 | unless Query.included_modules.include?(RedmineJenkins::Patches::QueryPatch) 214 | Query.send(:include, RedmineJenkins::Patches::QueryPatch) 215 | end 216 | -------------------------------------------------------------------------------- /lib/tasks/redmine_jenkins.rake: -------------------------------------------------------------------------------- 1 | namespace :redmine_jenkins do 2 | 3 | namespace :ci do 4 | begin 5 | require 'ci/reporter/rake/rspec' 6 | 7 | RSpec::Core::RakeTask.new do |task| 8 | task.rspec_opts = "plugins/redmine_jenkins/spec --color" 9 | end 10 | rescue Exception => e 11 | else 12 | ENV["CI_REPORTS"] = Rails.root.join('junit').to_s 13 | end 14 | 15 | task :all => ['ci:setup:rspec', 'spec'] 16 | end 17 | 18 | 19 | desc "Show library version" 20 | task :version do 21 | puts "Redmine Jenkins #{version("plugins/redmine_jenkins/init.rb")}" 22 | end 23 | 24 | 25 | def version(path) 26 | line = File.read(Rails.root.join(path))[/^\s*version\s*.*/] 27 | line.match(/.*version\s*['"](.*)['"]/)[1] 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /spec/database_mysql.yml: -------------------------------------------------------------------------------- 1 | ## MySQL configuration example 2 | ## Data come from environment variables so the test suite can be run 3 | ## on Travis or Jenkins (see https://github.com/codevise/jenkins-mysql-job-databases-plugin) 4 | production: 5 | adapter: mysql2 6 | database: <%= ENV['MYSQL_DATABASE'] %> 7 | host: <%= ENV['MYSQL_HOST'] %> 8 | port: <%= ENV['MYSQL_PORT'] %> 9 | username: <%= ENV['MYSQL_USER'] %> 10 | password: <%= ENV['MYSQL_PASSWORD'] %> 11 | encoding: utf8 12 | 13 | development: 14 | adapter: mysql2 15 | database: <%= ENV['MYSQL_DATABASE'] %> 16 | host: <%= ENV['MYSQL_HOST'] %> 17 | port: <%= ENV['MYSQL_PORT'] %> 18 | username: <%= ENV['MYSQL_USER'] %> 19 | password: <%= ENV['MYSQL_PASSWORD'] %> 20 | encoding: utf8 21 | 22 | test: 23 | adapter: mysql2 24 | database: <%= ENV['MYSQL_DATABASE'] %> 25 | host: <%= ENV['MYSQL_HOST'] %> 26 | port: <%= ENV['MYSQL_PORT'] %> 27 | username: <%= ENV['MYSQL_USER'] %> 28 | password: <%= ENV['MYSQL_PASSWORD'] %> 29 | encoding: utf8 30 | -------------------------------------------------------------------------------- /spec/database_postgres.yml: -------------------------------------------------------------------------------- 1 | ## PostgreSQL configuration example 2 | ## Data come from environment variables so the test suite can be run 3 | ## on Travis or Jenkins (see https://github.com/lmlima/jenkins-postgresql-job-databases-plugin) 4 | production: 5 | adapter: postgresql 6 | database: <%= ENV['POSTGRES_DATABASE'] %> 7 | username: <%= ENV['POSTGRES_USER'] %> 8 | encoding: utf8 9 | 10 | development: 11 | adapter: postgresql 12 | database: <%= ENV['POSTGRES_DATABASE'] %> 13 | username: <%= ENV['POSTGRES_USER'] %> 14 | encoding: utf8 15 | 16 | test: 17 | adapter: postgresql 18 | database: <%= ENV['POSTGRES_DATABASE'] %> 19 | username: <%= ENV['POSTGRES_USER'] %> 20 | encoding: utf8 21 | -------------------------------------------------------------------------------- /spec/factories/jenkins_build.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | 3 | factory :jenkins_build do |f| 4 | f.number { Faker::Number.number(3) } 5 | f.association :author, factory: :user 6 | end 7 | 8 | end 9 | -------------------------------------------------------------------------------- /spec/factories/jenkins_build_changeset.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | 3 | factory :jenkins_build_changeset do |f| 4 | f.revision { Faker::Number.number(9) } 5 | end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /spec/factories/jenkins_job.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | 3 | factory :jenkins_job do |f| 4 | f.name { Faker::Name.name } 5 | end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /spec/factories/jenkins_setting.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | 3 | factory :jenkins_setting do |f| 4 | f.url { Faker::Internet.url } 5 | end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /spec/factories/project.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | 3 | factory :project do |f| 4 | f.name { Faker::App.name } 5 | f.identifier { Faker::Internet.user_name(nil, %w(- _)) } 6 | end 7 | 8 | end 9 | -------------------------------------------------------------------------------- /spec/factories/repository_git.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | 3 | factory :repository_git, :class => 'Repository::Git' do |f| 4 | f.is_default false 5 | f.sequence(:url) { |n| "/tmp/git_#{n}" } 6 | end 7 | 8 | end 9 | -------------------------------------------------------------------------------- /spec/factories/user.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | 3 | factory :user do |f| 4 | f.login { Faker::Internet.user_name(nil, %w(-)) } 5 | f.firstname { Faker::Name.first_name } 6 | f.lastname { Faker::Name.last_name } 7 | f.mail { Faker::Internet.free_email } 8 | f.language "fr" 9 | f.hashed_password "66eb4812e268747f89ec309178e2ea50410653fb" 10 | f.salt "5abd4e59ac0d483daf2f68d3b6544ff3" 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /spec/models/jenkins_build_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe JenkinsBuild do 4 | 5 | let(:project){ create(:project) } 6 | let(:repository){ create(:repository_git, project: project) } 7 | let(:jenkins_setting){ create(:jenkins_setting, project: project) } 8 | let(:jenkins_job){ create(:jenkins_job, project: project, repository: repository) } 9 | let(:jenkins_build){ build(:jenkins_build, jenkins_job: jenkins_job) } 10 | 11 | subject { jenkins_build } 12 | 13 | it { should be_valid } 14 | 15 | ## Test relations 16 | it { should belong_to(:jenkins_job) } 17 | it { should belong_to(:author).class_name('User').with_foreign_key('author_id') } 18 | it { should have_many(:changesets_jenkins_builds) } 19 | it { should have_many(:changesets).through(:changesets_jenkins_builds) } 20 | 21 | ## Test validation 22 | it { should validate_presence_of(:jenkins_job_id) } 23 | it { should validate_presence_of(:author_id) } 24 | it { should validate_presence_of(:number) } 25 | 26 | it { should validate_uniqueness_of(:number).scoped_to(:jenkins_job_id) } 27 | 28 | ## Test Delegators 29 | it { should delegate_method(:project).to(:jenkins_job) } 30 | end 31 | -------------------------------------------------------------------------------- /spec/models/jenkins_job_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe JenkinsJob do 4 | 5 | let(:project){ create(:project) } 6 | let(:repository){ create(:repository_git, project: project) } 7 | let(:jenkins_setting){ create(:jenkins_setting, project: project) } 8 | let(:jenkins_job){ build(:jenkins_job, project: project, repository: repository) } 9 | 10 | subject { jenkins_job } 11 | 12 | it { should be_valid } 13 | 14 | ## Test relations 15 | it { should belong_to(:project) } 16 | it { should belong_to(:repository) } 17 | it { should have_many(:builds).class_name('JenkinsBuild').dependent(:destroy) } 18 | 19 | ## Test validation 20 | it { should validate_presence_of(:project_id) } 21 | it { should validate_presence_of(:repository_id) } 22 | it { should validate_presence_of(:name) } 23 | 24 | it { should validate_uniqueness_of(:name).scoped_to(:project_id) } 25 | 26 | ## Test Serializations 27 | it { should serialize(:health_report).as(Array) } 28 | 29 | ## Test Delegators 30 | it { should delegate_method(:jenkins_connection).to(:project) } 31 | it { should delegate_method(:jenkins_url).to(:project) } 32 | it { should delegate_method(:wait_for_build_id).to(:project) } 33 | end 34 | -------------------------------------------------------------------------------- /spec/models/jenkins_setting_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe JenkinsSetting do 4 | 5 | let(:project){ create(:project) } 6 | let(:jenkins_setting){ build(:jenkins_setting, project: project) } 7 | 8 | subject { jenkins_setting } 9 | 10 | it { should be_valid } 11 | 12 | ## Test relations 13 | it { should belong_to(:project) } 14 | 15 | ## Test validation 16 | it { should validate_presence_of(:project_id) } 17 | it { should validate_presence_of(:url) } 18 | 19 | it { should validate_uniqueness_of(:project_id) } 20 | end 21 | -------------------------------------------------------------------------------- /spec/models/project_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe Project do 4 | 5 | let(:project){ build(:project) } 6 | 7 | subject { project } 8 | 9 | it { should be_valid } 10 | 11 | ## Test relations 12 | it { should have_one(:jenkins_setting).dependent(:destroy) } 13 | it { should have_many(:jenkins_jobs).dependent(:destroy) } 14 | 15 | end 16 | -------------------------------------------------------------------------------- /spec/root_spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | require 'simplecov-rcov' 3 | 4 | ## Configure SimpleCov 5 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ 6 | SimpleCov::Formatter::HTMLFormatter, 7 | SimpleCov::Formatter::RcovFormatter 8 | ] 9 | 10 | ## Start Simplecov 11 | SimpleCov.start 'rails' do 12 | add_group 'Redmine Jenkins', 'plugins/redmine_jenkins' 13 | end 14 | 15 | ## Load Redmine App 16 | ENV["RAILS_ENV"] = 'test' 17 | require File.expand_path(File.dirname(__FILE__) + '/../config/environment') 18 | require 'rspec/rails' 19 | 20 | ## Load FactoryGirls factories 21 | Dir[Rails.root.join("plugins/*/spec/factories/**/*.rb")].each {|f| require f} 22 | 23 | ## Configure RSpec 24 | RSpec.configure do |config| 25 | config.include FactoryGirl::Syntax::Methods 26 | 27 | config.infer_spec_type_from_file_location! 28 | 29 | config.color = true 30 | config.fail_fast = false 31 | 32 | config.expect_with :rspec do |c| 33 | c.syntax = :expect 34 | end 35 | 36 | config.before(:suite) do 37 | DatabaseCleaner.clean_with(:truncation) 38 | end 39 | 40 | config.before(:each) do 41 | DatabaseCleaner.strategy = :transaction 42 | end 43 | 44 | config.before(:each) do 45 | DatabaseCleaner.start 46 | end 47 | 48 | config.after(:each) do 49 | DatabaseCleaner.clean 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../spec/spec_helper') 2 | --------------------------------------------------------------------------------