├── .circleci └── config.yml ├── .eslintrc.yml ├── .github ├── FUNDING.yml └── workflows │ ├── check-assets-js.yml │ └── greetings.yml ├── .gitignore ├── .rubocop.yml ├── .rubocop_todo.yml ├── .tool-versions ├── CHANGELOG.md ├── Dockerfile ├── Gemfile.local ├── ISSUE_TEMPLATE.md ├── LICENSE.txt ├── README.md ├── RELEASE-NOTES.md ├── app ├── controllers │ ├── concerns │ │ ├── issue_templates_common.rb │ │ └── project_templates_common.rb │ ├── global_issue_templates_controller.rb │ ├── global_note_templates_controller.rb │ ├── issue_templates_controller.rb │ ├── issue_templates_settings_controller.rb │ └── note_templates_controller.rb ├── helpers │ └── issue_templates_helper.rb ├── models │ ├── concerns │ │ ├── attribute_name_mapper.rb │ │ └── issue_template_common.rb │ ├── global_issue_template.rb │ ├── global_note_template.rb │ ├── global_note_template_project.rb │ ├── global_note_visible_role.rb │ ├── issue_template.rb │ ├── issue_template_setting.rb │ ├── note_template.rb │ └── note_visible_role.rb └── views │ ├── common │ ├── _nodata.html.erb │ ├── _orphaned.html.erb │ └── _template_links.html.erb │ ├── global_issue_templates │ ├── _form.html.erb │ ├── index.html.erb │ ├── new.html.erb │ └── show.html.erb │ ├── global_note_templates │ ├── _form.html.erb │ ├── index.html.erb │ ├── new.html.erb │ └── show.html.erb │ ├── issue_templates │ ├── _form.html.erb │ ├── _issue_select_form.html.erb │ ├── _issue_template_link.html.erb │ ├── _list_templates.api.rsb │ ├── _list_templates.html.erb │ ├── _note_form.html.erb │ ├── _template_pulldown.html.erb │ ├── index.api.rsb │ ├── index.html.erb │ ├── new.html.erb │ └── show.html.erb │ ├── issue_templates_settings │ └── index.html.erb │ ├── note_templates │ ├── _form.html.erb │ ├── _list_note_templates.html.erb │ ├── index.api.rsb │ ├── index.html.erb │ ├── new.html.erb │ └── show.html.erb │ └── settings │ └── _redmine_issue_templates.html.erb ├── assets ├── images │ ├── eraser.png │ ├── issue_templates.png │ ├── lamp.png │ ├── preview.png │ └── ticket.png ├── javascripts │ └── issue_templates.js └── stylesheets │ └── issue_templates.css ├── config ├── locales │ ├── bg.yml │ ├── da.yml │ ├── de.yml │ ├── en.yml │ ├── es.yml │ ├── fr.yml │ ├── it.yml │ ├── ja.yml │ ├── ko.yml │ ├── pl.yml │ ├── pt-BR.yml │ ├── pt.yml │ ├── ru.yml │ ├── sr-YU.yml │ ├── zh-TW.yml │ └── zh.yml └── routes.rb ├── db └── migrate │ ├── 0001_create_issue_templates.rb │ ├── 0002_create_issue_template_settings.rb │ ├── 0003_add_issue_title_to_issue_templates.rb │ ├── 0004_add_position_to_issue_templates.rb │ ├── 20121208150810_add_is_default_to_issue_templates.rb │ ├── 20130630141710_add_enabled_sharing_to_issue_templates.rb │ ├── 20130701024625_add_inherit_templates_to_issue_template_settings.rb │ ├── 2014020191500_add_should_replaced_to_issue_template_settings.rb │ ├── 20140307024626_create_global_issue_templates.rb │ ├── 20140312054531_create_global_issue_templates_projects.rb │ ├── 20140330155030_remove_is_default_from_global_issue_templates.rb │ ├── 20160727222420_add_checklist_json_to_issue_templates.rb │ ├── 20160828190000_add_checklist_json_to_global_issue_templates.rb │ ├── 20160829001500_change_issue_template_enabled_column.rb │ ├── 20160829001530_change_global_issue_template_enabled_column.rb │ ├── 20170317082100_add_is_default_to_global_issue_templates.rb │ ├── 20181104065200_add_unique_key_to_global_issue_templates_projects.rb │ ├── 20190303082102_create_note_templates.rb │ ├── 20190714171020_create_note_visible_roles.rb │ ├── 20190714211530_add_visibility_to_note_templates.rb │ ├── 20200101204020_add_related_link_to_issue_templates.rb │ ├── 20200101204220_add_related_link_to_global_issue_templates.rb │ ├── 20200102204815_add_link_title_to_issue_templates.rb │ ├── 20200102205044_add_link_title_to_global_issue_templates.rb │ ├── 20200103213630_add_builtin_fields_json_to_issue_templates.rb │ ├── 20200115073600_add_builtin_fields_json_to_global_issue_templates.rb │ ├── 20200314132500_change_column_note_template_description.rb │ ├── 20200405115700_create_global_note_templates.rb │ ├── 20200405120700_create_global_note_visible_roles.rb │ ├── 20200418114157_create_join_table_global_note_template_project.rb │ └── 20230330055341_change_global_note_template_projects_table_name.rb ├── docker-compose.yml ├── init.rb ├── lib ├── issue_templates │ ├── issues_hook.rb │ └── journals_hook.rb └── tasks │ ├── test.rake │ └── util.rake ├── package-lock.json ├── package.json ├── scripts ├── components │ ├── DisplayArea.vue │ ├── FieldValue.vue │ └── JsonGenerator.vue ├── issue_templates.js ├── plugins │ ├── customFields.js │ └── locales.js └── template_fields.js ├── spec ├── controllers │ ├── concerns │ │ └── issue_templates_common_spec.rb │ ├── global_issue_templates_controller_spec.rb │ ├── issue_templates_controller_spec.rb │ └── settings_controller_spec.rb ├── factories │ ├── enabled_modules.rb │ ├── global_issue_templates.rb │ ├── global_note_templates.rb │ ├── issue_statuses.rb │ ├── issue_template_settings.rb │ ├── issue_templates.rb │ ├── note_templates.rb │ ├── projects.rb │ ├── role.rb │ ├── trackers.rb │ └── users.rb ├── features │ ├── admin_spec.rb │ ├── create_issue_spec.rb │ ├── drag_and_drop_spec.rb │ ├── issue_template_popup_spec.rb │ ├── issue_template_spec.rb │ ├── update_issue_spec.rb │ └── update_template_spec.rb ├── helpers │ └── issue_templates_helper_spec.rb ├── models │ ├── global_issue_template_spec.rb │ ├── global_note_template_spec.rb │ ├── issue_template_setting_spec.rb │ ├── issue_template_spec.rb │ └── note_visible_role_spec.rb ├── rails_helper.rb ├── requests │ ├── global_note_templates_spec.rb │ └── note_templates_spec.rb ├── spec_helper.rb └── support │ ├── controller_helper.rb │ └── login_helper.rb ├── test ├── fixtures │ ├── global_issue_templates.yml │ ├── global_issue_templates_projects.yml │ ├── global_note_templates.yml │ ├── issue_template_settings.yml │ ├── issue_templates.yml │ ├── note_templates.yml │ └── note_visible_roles.yml ├── functional │ ├── global_issue_templates_controller_test.rb │ ├── issue_templates_controller_test.rb │ ├── issue_templates_settings_controller_test.rb │ ├── issues_controller_test.rb │ ├── note_templates_controller_test.rb │ └── projects_controller_test.rb ├── integration │ └── layout_test.rb ├── test_helper.rb └── unit │ ├── global_issue_templates_test.rb │ ├── global_note_template_test.rb │ ├── issue_template_setting_test.rb │ ├── issue_template_test.rb │ └── note_template_test.rb └── vite.config.js /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | es2021: true 4 | extends: 5 | - eslint:recommended 6 | - plugin:vue/vue3-essential 7 | overrides: [] 8 | parserOptions: 9 | ecmaVersion: latest 10 | sourceType: module 11 | plugins: 12 | - vue 13 | rules: 14 | semi: 15 | - error 16 | - always 17 | indent: 18 | - error 19 | - 2 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | ko_fi: akikopusu 3 | 4 | -------------------------------------------------------------------------------- /.github/workflows/check-assets-js.yml: -------------------------------------------------------------------------------- 1 | name: Check assets/javascripts/issue_templates.js is up-to-date 2 | 3 | on: 4 | pull_request: 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | check-asset-js: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set Node.js 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: 18.16.0 20 | 21 | - name: Install dependencies 22 | run: npm ci 23 | 24 | - name: Rebuild the assets/javascripts/issue_templates.js 25 | run: npm run build 26 | 27 | - name: Compare the expected and actual assets/javascripts/issue_templates.js 28 | run: | 29 | if [ "$(git diff --ignore-space-at-eol assets/javascripts/ | wc -l)" -gt "0" ]; then 30 | message="assets/javascripts/issue_templates.js is out of date. Please build and push again according to the \"Build scripts\" section in README.md." 31 | echo $message 32 | 33 | # Also add the message to the GitHub step summary 34 | echo "## :x: assets/javascripts/issue_templates.js is out of date" >> $GITHUB_STEP_SUMMARY 35 | echo $message >> $GITHUB_STEP_SUMMARY 36 | exit 1 37 | fi 38 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | issue-message: 'Thank you for contributing to Redmine Issue Templates plugin!'' first issue' 13 | pr-message: 'Thanks you for contributing to Redmine Issue Templates plugin!'' first pr' 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | require: 3 | - rubocop-rails 4 | AllCops: 5 | TargetRubyVersion: 2.4 6 | Exclude: 7 | - 'db/**/*' 8 | 9 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2016-06-01 07:37:41 +0900 using RuboCop version 0.40.0. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 3 10 | Metrics/AbcSize: 11 | Max: 55 12 | 13 | # Offense count: 1 14 | # Configuration parameters: CountComments. 15 | Metrics/ClassLength: 16 | Max: 200 17 | Exclude: 18 | - 'spec/**/*' 19 | - 'test/**/*' 20 | 21 | # "Line is too long"を無効 22 | Layout/LineLength: 23 | Enabled: false 24 | 25 | # Offense count: 1 26 | Metrics/CyclomaticComplexity: 27 | Max: 10 28 | 29 | # Offense count: 1 30 | Metrics/PerceivedComplexity: 31 | Max: 10 32 | 33 | Metrics/BlockLength: 34 | Max: 30 35 | Exclude: 36 | - 'spec/**/*' 37 | - 'test/**/*' 38 | 39 | # Avoid methods longer than 10 lines of code 40 | MethodLength: 41 | CountComments: true # count full line comments? 42 | Max: 45 43 | 44 | # Aboid Missing top-level module documentation comment. 45 | Documentation: 46 | Enabled: false 47 | 48 | EndOfLine: 49 | Enabled: false 50 | 51 | Metrics/ModuleLength: 52 | Max: 120 53 | 54 | Rails/ApplicationRecord: 55 | Enabled: false 56 | 57 | Rails/UniqueValidationWithoutIndex: 58 | Enabled: false 59 | 60 | Rails/InverseOf: 61 | Enabled: false 62 | 63 | Rails/LexicallyScopedActionFilter: 64 | Enabled: false 65 | 66 | Rails/SkipsModelValidations: 67 | Enabled: false 68 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 18.16.0 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.6 2 | LABEL maintainer="AKIKO TAKANO / (Twitter: @akiko_pusu)" \ 3 | description="Image to run Redmine simply with sqlite to try/review plugin." 4 | 5 | ### get Redmine source 6 | ### Replace shell with bash so we can source files ### 7 | RUN rm /bin/sh && ln -s /bin/bash /bin/sh 8 | 9 | ### install default sys packeges ### 10 | 11 | RUN apt-get update 12 | RUN apt-get install -qq -y \ 13 | git vim \ 14 | sqlite3 default-libmysqlclient-dev 15 | RUN apt-get install -qq -y build-essential libc6-dev 16 | 17 | # for e2e test env 18 | RUN sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' 19 | RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - 20 | RUN apt-get update && apt-get install -y google-chrome-stable 21 | RUN google-chrome --version | perl -pe 's/([^0-9]+)([0-9]+)(\.[0-9]+).+/$2/g' > chrome-version-major 22 | RUN curl https://chromedriver.storage.googleapis.com/LATEST_RELEASE_`cat chrome-version-major` > chrome-version 23 | RUN curl -O -L http://chromedriver.storage.googleapis.com/`cat chrome-version`/chromedriver_linux64.zip && rm chrome-version* 24 | RUN unzip chromedriver_linux64.zip && mv chromedriver /usr/local/bin 25 | 26 | RUN cd /tmp && svn co http://svn.redmine.org/redmine/trunk redmine 27 | WORKDIR /tmp/redmine 28 | 29 | COPY . /tmp/redmine/plugins/redmine_issue_templates/ 30 | 31 | 32 | # add database.yml (for development, development with mysql, test) 33 | RUN echo $'test:\n\ 34 | adapter: sqlite3\n\ 35 | database: /tmp/data/redmine_test.sqlite3\n\ 36 | encoding: utf8mb4\n\ 37 | development:\n\ 38 | adapter: sqlite3\n\ 39 | database: /tmp/data/redmine_development.sqlite3\n\ 40 | encoding: utf8mb4\n\ 41 | development_mysql:\n\ 42 | adapter: mysql2\n\ 43 | host: mysql\n\ 44 | password: pasword\n\ 45 | database: redemine_development\n\ 46 | username: root\n'\ 47 | >> config/database.yml 48 | 49 | RUN gem update bundler 50 | RUN bundle install 51 | RUN bundle exec rake db:migrate 52 | EXPOSE 3000 53 | CMD ["rails", "server", "-b", "0.0.0.0"] 54 | -------------------------------------------------------------------------------- /Gemfile.local: -------------------------------------------------------------------------------- 1 | group :test do 2 | gem 'simplecov-rcov', require: false 3 | gem 'rspec-rails' 4 | if RUBY_VERSION < '3.0' 5 | # factory_bot 6.4.5以上はRuby3.0が必要なため、それ以下のバージョンでは6.4.5未満にする 6 | # https://github.com/thoughtbot/factory_bot/releases/tag/v6.4.5 7 | gem 'factory_bot', '<6.4.5' 8 | end 9 | gem 'factory_bot_rails' 10 | gem 'launchy' 11 | gem 'database_cleaner' 12 | dependencies.reject! { |i| i.name == 'nokogiri' } # Ensure Nokogiri have new version 13 | end 14 | 15 | # for Debug 16 | group :development, :test do 17 | gem 'pry-rails' 18 | gem 'pry-byebug' 19 | end 20 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | This is a template to report bug related to redmine issue templates plugin. 2 | You can fill in any format, free style in case report features or questions. 3 | 4 | ## Summary 5 | 6 | 7 | ## Description 8 | 9 | 10 | ## Environment 11 | 12 | These informations are greatly helpful to support quickly. 13 | You can get these informations from Redmine's information page. 14 | (E.g. http://example.com/admin/info) 15 | 16 | - Redmine version 17 | - Installed plugins 18 | - Ruby version 19 | - OS Platform 20 | - Database (MariaDB, MySQL, PostgreSQL) and its version 21 | - Rails Env 22 | 23 | 24 | ## Visual Proof / Screenshot 25 | 26 | When reporting an application error, post the error stack trace that 27 | you should find in the **log file** (eg. log/production.log). 28 | 29 | Screen shot would be also helpful. 30 | 31 | 32 | ## Expected Results 33 | 34 | ## Actual Results 35 | 36 | ## Workaround 37 | 38 | Please let me know if you have any workaround. 39 | -------------------------------------------------------------------------------- /app/controllers/concerns/project_templates_common.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ProjectTemplatesCommon 4 | extend ActiveSupport::Concern 5 | included do 6 | before_action :find_user, :find_project, :authorize, except: %i[preview load load_selectable_fields] 7 | before_action :find_object, only: %i[show edit update destroy] 8 | accept_api_auth :index, :list_templates, :load 9 | end 10 | 11 | def show 12 | render render_form_params 13 | end 14 | 15 | def destroy 16 | unless template.destroy 17 | flash[:error] = l(:enabled_template_cannot_destroy) 18 | redirect_to action: :show, project_id: @project, id: template 19 | return 20 | end 21 | 22 | flash[:notice] = l(:notice_successful_delete) 23 | redirect_to action: 'index', project_id: @project 24 | end 25 | 26 | def save_and_flash(message, action_on_failure) 27 | unless template.save 28 | render render_form_params.merge(action: action_on_failure) 29 | return 30 | end 31 | 32 | respond_to do |format| 33 | format.html do 34 | flash[:notice] = l(message) 35 | redirect_to action: 'show', id: template.id, project_id: @project 36 | end 37 | format.js { head 200 } 38 | end 39 | rescue NoteTemplate::NoteTemplateError => e 40 | flash[:error] = e.message 41 | render render_form_params.merge(action: action_on_failure) 42 | nil 43 | end 44 | 45 | def plugin_setting 46 | Setting.plugin_redmine_issue_templates 47 | end 48 | 49 | def apply_all_projects? 50 | plugin_setting['apply_global_template_to_all_projects'].to_s == 'true' 51 | end 52 | 53 | private 54 | 55 | def template 56 | raise NotImplementedError, "You must implement #{self.class}##{__method__}" 57 | end 58 | 59 | def find_user 60 | @user = User.current 61 | end 62 | 63 | def find_tracker 64 | @tracker = Tracker.find(params[:issue_tracker_id]) 65 | end 66 | 67 | def find_project 68 | @project = Project.find(params[:project_id]) 69 | rescue ActiveRecord::RecordNotFound 70 | render_404 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /app/controllers/global_note_templates_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # noinspection RubocopInspection 4 | class GlobalNoteTemplatesController < ApplicationController 5 | layout 'base' 6 | helper :issues 7 | helper :issue_templates 8 | menu_item :issues 9 | 10 | before_action :find_object, only: %i[show update destroy] 11 | before_action :find_project, only: %i[update] 12 | before_action :require_admin, only: %i[index new show], excep: [:preview] 13 | 14 | # 15 | # Action for global template : Admin right is required. 16 | # 17 | def index 18 | trackers = Tracker.sorted 19 | template_map = {} 20 | trackers.each do |tracker| 21 | tracker_id = tracker.id 22 | templates = GlobalNoteTemplate.search_by_tracker(tracker_id).sorted 23 | template_map[Tracker.find(tracker_id)] = templates if templates.any? 24 | end 25 | 26 | render layout: !request.xhr?, locals: { template_map: template_map, trackers: trackers } 27 | end 28 | 29 | def new 30 | # create empty instance 31 | @global_note_template = GlobalNoteTemplate.new 32 | render render_form_params 33 | end 34 | 35 | def create 36 | @global_note_template = GlobalNoteTemplate.new(template_params) 37 | @global_note_template.author = User.current 38 | 39 | save_and_flash(:notice_successful_create, :new) && return 40 | end 41 | 42 | def show 43 | render render_form_params 44 | end 45 | 46 | def update 47 | # Workaround in case author id is null 48 | @global_note_template.author = User.current if @global_note_template.author.blank? 49 | @global_note_template.safe_attributes = template_params 50 | 51 | save_and_flash(:notice_successful_update, :show) 52 | end 53 | 54 | def destroy 55 | unless @global_note_template.destroy 56 | flash[:error] = l(:enabled_template_cannot_destroy) 57 | redirect_to action: :show, id: @global_note_template 58 | return 59 | end 60 | 61 | flash[:notice] = l(:notice_successful_delete) 62 | redirect_to action: 'index' 63 | end 64 | 65 | def find_project 66 | @projects = Project.all 67 | end 68 | 69 | def find_object 70 | @global_note_template = GlobalNoteTemplate.find(params[:id]) 71 | rescue ActiveRecord::RecordNotFound 72 | render_404 73 | end 74 | 75 | def save_and_flash(message, action_on_failure) 76 | unless @global_note_template.save 77 | render render_form_params.merge(action: action_on_failure) 78 | return 79 | end 80 | 81 | respond_to do |format| 82 | format.html do 83 | flash[:notice] = l(message) 84 | redirect_to action: 'show', id: @global_note_template.id 85 | end 86 | format.js { head 200 } 87 | end 88 | end 89 | 90 | def template_params 91 | params.require(:global_note_template) 92 | .permit(:global_note_template_id, :tracker_id, :name, :memo, :description, 93 | :enabled, :author_id, :position, :visibility, role_ids: [], project_ids: []) 94 | end 95 | 96 | def render_form_params 97 | trackers = Tracker.sorted 98 | projects = Project.all 99 | 100 | { layout: !request.xhr?, 101 | locals: { trackers: trackers, apply_all_projects: apply_all_projects?, 102 | note_template: @global_note_template, projects: projects } } 103 | end 104 | 105 | def apply_all_projects? 106 | plugin_setting['apply_global_template_to_all_projects'].to_s == 'true' 107 | end 108 | 109 | def plugin_setting 110 | Setting.plugin_redmine_issue_templates 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /app/controllers/issue_templates_settings_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # noinspection RubocopInspection 4 | class IssueTemplatesSettingsController < ApplicationController 5 | before_action :find_project, :find_user 6 | before_action :authorize, :find_issue_templates_setting, except: %i[preview] 7 | 8 | def index; end 9 | 10 | def edit 11 | return if params[:settings].blank? 12 | 13 | update_template_setting 14 | flash[:notice] = l(:notice_successful_update) 15 | redirect_to action: 'index', project_id: @project 16 | end 17 | 18 | def preview 19 | @text = params[:settings][:help_message] 20 | render partial: 'common/preview' 21 | end 22 | 23 | def menu_items 24 | { issue_templates_settings: { default: :issue_templates, actions: {} } } 25 | end 26 | 27 | private 28 | 29 | def find_user 30 | @user = User.current 31 | end 32 | 33 | def find_project 34 | @project = Project.find(params[:project_id]) 35 | rescue ActiveRecord::RecordNotFound 36 | render_404 37 | end 38 | 39 | def find_issue_templates_setting 40 | @issue_templates_setting = IssueTemplateSetting.find_or_create(@project.id) 41 | end 42 | 43 | def update_template_setting 44 | issue_templates_setting = IssueTemplateSetting.find_or_create(@project.id) 45 | attribute = params[:settings] 46 | issue_templates_setting.update(enabled: attribute[:enabled], 47 | help_message: attribute[:help_message], 48 | inherit_templates: attribute[:inherit_templates], 49 | should_replaced: attribute[:should_replaced]) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /app/helpers/issue_templates_helper.rb: -------------------------------------------------------------------------------- 1 | module IssueTemplatesHelper 2 | def project_tracker?(tracker, project) 3 | return false unless tracker.present? 4 | 5 | project.trackers.exists?(tracker.id) 6 | end 7 | 8 | def non_project_tracker_msg(flag) 9 | return '' if flag 10 | 11 | "#{l(:unused_tracker_at_this_project)}".html_safe 12 | end 13 | 14 | def template_target_trackers(project, issue_template) 15 | trackers = project.trackers 16 | trackers |= [issue_template.tracker] unless issue_template.tracker.blank? 17 | trackers.collect { |obj| [obj.name, obj.id] } 18 | end 19 | 20 | def options_for_template_pulldown(options) 21 | options.map do |option| 22 | text = option.try(:name).to_s 23 | tag_builder.content_tag_string(:option, text, option, true) 24 | end.join("\n").html_safe 25 | end 26 | 27 | def localize_to_script 28 | return { 29 | button_add: l(:button_add), 30 | button_apply: l(:button_apply), 31 | button_reset: l(:button_reset), 32 | enter_value: l(:enter_value, default: "Please enter a value"), 33 | field_value: l(:field_value), 34 | help_for_this_field: l(:help_for_this_field), 35 | label_field_information: l(:label_field_information, default: "Field information"), 36 | label_builtin_fields_json: l(:label_builtin_fields_json, default: "JSON for fields"), 37 | label_select_field: l(:label_select_field, default: "Select a field"), 38 | unavailable_fields_for_this_tracker: l(:unavailable_fields_for_this_tracker, default: "Unavailable field for this tracker"), 39 | } 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /app/models/concerns/attribute_name_mapper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # A mixin to display the appropriate field name when displaying a validation error message. 4 | module AttributeNameMapper 5 | extend ActiveSupport::Concern 6 | 7 | module ClassMethods 8 | def attribute_map 9 | {} 10 | end 11 | 12 | def human_attribute_name(attr, options = {}) 13 | map = ActiveSupport::HashWithIndifferentAccess.new(attribute_map) 14 | if map.has_key?(attr) 15 | l(map[attr]) 16 | else 17 | super(attr, options) 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/concerns/issue_template_common.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IssueTemplateCommon 4 | extend ActiveSupport::Concern 5 | 6 | # 7 | # Common scope both global and project scope template. 8 | # 9 | included do 10 | belongs_to :author, class_name: 'User', foreign_key: 'author_id' 11 | belongs_to :tracker 12 | before_save :check_default 13 | 14 | before_destroy :confirm_disabled 15 | 16 | validates :title, presence: true 17 | validates :tracker, presence: true 18 | validates :description, presence: true 19 | validates :related_link, format: { with: URI::DEFAULT_PARSER.make_regexp }, allow_blank: true 20 | 21 | scope :enabled, -> { where(enabled: true) } 22 | scope :sorted, -> { order(:position) } 23 | scope :search_by_tracker, lambda { |tracker_id| 24 | where(tracker_id: tracker_id) if tracker_id.present? 25 | } 26 | 27 | scope :is_default, -> { where(is_default: true) } 28 | scope :not_default, -> { where(is_default: false) } 29 | 30 | scope :orphaned, lambda { |project_id = nil| 31 | condition = all 32 | if project_id.present? && try(:name) == 'IssueTemplate' 33 | condition = condition.where(project_id: project_id) 34 | ids = Tracker.joins(:projects).where(projects: { id: project_id }).pluck(:id) 35 | else 36 | ids = Tracker.pluck(:id) 37 | end 38 | condition.where.not(tracker_id: ids) 39 | } 40 | 41 | after_destroy do |template| 42 | logger.info("[Destroy] #{self.class}: #{template.inspect}") 43 | end 44 | 45 | # ActiveRecord::SerializationTypeMismatch may be thrown if non hash object is assigned. 46 | # 47 | # Passing the class as positional argument has removed in Rails 7.2. 48 | # https://edgeguides.rubyonrails.org/7_2_release_notes.html#active-record-removals 49 | if Rails.gem_version >= Gem::Version.new('7.2') 50 | serialize :builtin_fields_json, type: Hash, coder: YAML 51 | else 52 | serialize :builtin_fields_json, Hash 53 | end 54 | end 55 | 56 | # 57 | # Common methods both global and project scope template. 58 | # 59 | def enabled? 60 | enabled 61 | end 62 | 63 | def <=>(other) 64 | position <=> other.position 65 | end 66 | 67 | # Keep this method for a while, but this will be deprecated. 68 | # Please see: https://github.com/akiko-pusu/redmine_issue_templates/issues/363 69 | def checklist 70 | return [] if checklist_json.blank? 71 | 72 | begin 73 | JSON.parse(checklist_json) 74 | rescue StandardError 75 | [] 76 | end 77 | end 78 | 79 | def template_json(except: nil) 80 | template = {} 81 | template[self.class::Config::JSON_OBJECT_NAME] = generate_json 82 | return template.to_json(root: true) if except.blank? 83 | 84 | template.to_json(root: true, except: [except]) 85 | end 86 | 87 | def builtin_fields 88 | builtin_fields_json.to_json 89 | end 90 | 91 | def generate_json 92 | result = attributes 93 | result[:link_title] = link_title.presence || I18n.t(:issue_template_related_link, default: 'Related Link') 94 | result[:checklist] = checklist 95 | result.except('checklist_json') 96 | end 97 | 98 | def template_struct(option = {}) 99 | Struct.new(:value, :name, :class, :selected).new(id, title, option[:class]) 100 | end 101 | 102 | def log_destroy_action(template) 103 | logger.info "[Destroy] #{self.class}: #{template.inspect}" if logger&.info 104 | end 105 | 106 | def confirm_disabled 107 | return unless enabled? 108 | 109 | errors.add :base, 'enabled_template_cannot_destroy' 110 | throw :abort 111 | end 112 | 113 | def copy_title 114 | "copy_of_#{title}" 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /app/models/global_issue_template.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class GlobalIssueTemplate < (defined?(ApplicationRecord) == 'constant' ? ApplicationRecord : ActiveRecord::Base) 4 | include Redmine::SafeAttributes 5 | include IssueTemplateCommon 6 | include AttributeNameMapper 7 | validates :title, uniqueness: { scope: :tracker_id } 8 | has_and_belongs_to_many :projects 9 | 10 | acts_as_positioned scope: [:tracker_id] 11 | 12 | safe_attributes 'title', 13 | 'description', 14 | 'tracker_id', 15 | 'note', 16 | 'enabled', 17 | 'is_default', 18 | 'issue_title', 19 | 'project_ids', 20 | 'position', 21 | 'author_id', 22 | 'related_link', 23 | 'link_title', 24 | 'builtin_fields_json' 25 | 26 | # for intermediate table assosciations 27 | scope :search_by_project, lambda { |project_id| 28 | joins(:projects).where(projects: { id: project_id }) if project_id.present? 29 | } 30 | 31 | module Config 32 | JSON_OBJECT_NAME = 'global_issue_template' 33 | end 34 | Config.freeze 35 | 36 | # 37 | # In case set is_default and updated, others are also updated. 38 | # 39 | def check_default 40 | return unless is_default? && is_default_changed? 41 | 42 | self.class.search_by_tracker(tracker_id).update_all(is_default: false) 43 | end 44 | 45 | # 46 | # Class method 47 | # 48 | class << self 49 | def get_templates_for_project_tracker(project_id, tracker_id = nil) 50 | GlobalIssueTemplate.search_by_tracker(tracker_id) 51 | .search_by_project(project_id) 52 | .enabled 53 | .sorted 54 | end 55 | 56 | def attribute_map 57 | { 58 | description: :issue_description, 59 | title: :issue_template_name, 60 | } 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /app/models/global_note_template_project.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class GlobalNoteTemplateProject < (defined?(ApplicationRecord) == 'constant' ? ApplicationRecord : ActiveRecord::Base) 4 | belongs_to :project 5 | belongs_to :global_note_template, optional: true 6 | end 7 | -------------------------------------------------------------------------------- /app/models/global_note_visible_role.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class GlobalNoteVisibleRole < (defined?(ApplicationRecord) == 'constant' ? ApplicationRecord : ActiveRecord::Base) 4 | include Redmine::SafeAttributes 5 | 6 | safe_attributes 'global_note_template_id', 'role_id' 7 | belongs_to :role 8 | belongs_to :global_note_template, optional: true 9 | 10 | validates :role_id, presence: true 11 | validates :global_note_template_id, presence: true 12 | 13 | scope :search_by_note_template, lambda { |note_template_id| 14 | where(global_note_template_id: note_template_id) 15 | } 16 | end 17 | -------------------------------------------------------------------------------- /app/models/issue_template.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class IssueTemplate < (defined?(ApplicationRecord) == 'constant' ? ApplicationRecord : ActiveRecord::Base) 4 | include Redmine::SafeAttributes 5 | include IssueTemplateCommon 6 | include AttributeNameMapper 7 | belongs_to :project 8 | validates :project_id, presence: true 9 | validates :title, uniqueness: { scope: :project_id } 10 | acts_as_positioned scope: %i[project_id tracker_id] 11 | 12 | # author and project should be stable. 13 | safe_attributes 'title', 14 | 'description', 15 | 'tracker_id', 16 | 'note', 17 | 'enabled', 18 | 'issue_title', 19 | 'is_default', 20 | 'enabled_sharing', 21 | 'visible_children', 22 | 'position', 23 | 'related_link', 24 | 'link_title', 25 | 'builtin_fields_json' 26 | 27 | scope :enabled_sharing, -> { where(enabled_sharing: true) } 28 | scope :search_by_project, lambda { |prolect_id| 29 | where(project_id: prolect_id) 30 | } 31 | 32 | module Config 33 | JSON_OBJECT_NAME = 'issue_template' 34 | end 35 | Config.freeze 36 | 37 | # 38 | # In case set is_default and updated, others are also updated. 39 | # 40 | def check_default 41 | return unless is_default? && is_default_changed? 42 | 43 | self.class.search_by_project(project_id).search_by_tracker(tracker_id).update_all(is_default: false) 44 | end 45 | 46 | # return projects that use this template 47 | def used_projects 48 | return [] unless enabled_sharing 49 | 50 | projects = project.descendants 51 | .joins(:trackers, :enabled_modules).merge(Tracker.where(id: tracker_id)).merge(EnabledModule.where(name: 'issue_templates')) 52 | IssueTemplateSetting.where(project_id: projects).inherit_templates.select(:project_id) 53 | end 54 | 55 | # 56 | # Class method 57 | # 58 | class << self 59 | def get_inherit_templates(project_ids, tracker_id) 60 | # keep ordering of project tree 61 | IssueTemplate.search_by_project(project_ids) 62 | .search_by_tracker(tracker_id) 63 | .enabled 64 | .enabled_sharing 65 | .sorted 66 | end 67 | 68 | def get_templates_for_project_tracker(project_id, tracker_id = nil) 69 | IssueTemplate.search_by_project(project_id) 70 | .search_by_tracker(tracker_id) 71 | .enabled 72 | .sorted 73 | end 74 | 75 | def attribute_map 76 | { 77 | description: :issue_description, 78 | title: :issue_template_name, 79 | } 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /app/models/issue_template_setting.rb: -------------------------------------------------------------------------------- 1 | class IssueTemplateSetting < (defined?(ApplicationRecord) == 'constant' ? ApplicationRecord : ActiveRecord::Base) 2 | include Redmine::SafeAttributes 3 | belongs_to :project 4 | 5 | validates_uniqueness_of :project_id 6 | validates_presence_of :project_id 7 | 8 | safe_attributes 'help_message', 'enabled', 'inherit_templates', 'should_replaced' 9 | 10 | scope :inherit_templates, -> { where(inherit_templates: true) } 11 | 12 | def self.find_or_create(project_id) 13 | setting = IssueTemplateSetting.where(project_id: project_id).first 14 | unless setting.present? 15 | setting = IssueTemplateSetting.new 16 | setting.project_id = project_id 17 | setting.save! 18 | end 19 | setting 20 | end 21 | 22 | # 23 | # Class method 24 | # 25 | class << self 26 | def apply_template_to_child_projects(project_id) 27 | setting = find_setting(project_id) 28 | setting.apply_template_to_child_projects 29 | end 30 | 31 | def unapply_template_from_child_projects(project_id) 32 | setting = find_setting(project_id) 33 | setting.unapply_template_from_child_projects 34 | end 35 | 36 | private 37 | 38 | def find_setting(project_id) 39 | raise ArgumentError, 'Please specify valid project_id.' if project_id.blank? 40 | 41 | setting = IssueTemplateSetting.where(project_id: project_id).first 42 | raise ActiveRecord::RecordNotFound if setting.blank? 43 | 44 | setting 45 | end 46 | end 47 | 48 | def enable_help? 49 | enabled == true && !help_message.blank? 50 | end 51 | 52 | def enabled_inherit_templates? 53 | inherit_templates 54 | end 55 | 56 | def child_projects 57 | project.descendants 58 | end 59 | 60 | def apply_template_to_child_projects 61 | update_inherit_template_of_child_projects(true) 62 | end 63 | 64 | def unapply_template_from_child_projects 65 | update_inherit_template_of_child_projects(false) 66 | end 67 | 68 | def get_inherit_templates(tracker = nil) 69 | return [] unless enabled_inherit_templates? 70 | 71 | project_ids = project.ancestors.collect(&:id) 72 | tracker = project.trackers.pluck(:tracker_id) if tracker.blank? 73 | 74 | # first: get inherit_templates 75 | IssueTemplate.get_inherit_templates(project_ids, tracker) 76 | end 77 | 78 | private 79 | 80 | def update_inherit_template_of_child_projects(value) 81 | IssueTemplateSetting.where(project_id: child_projects).update_all(inherit_templates: value) 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /app/models/note_template.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class NoteTemplate < (defined?(ApplicationRecord) == 'constant' ? ApplicationRecord : ActiveRecord::Base) 4 | include Redmine::SafeAttributes 5 | include AttributeNameMapper 6 | 7 | class NoteTemplateError < StandardError; end 8 | 9 | # author and project should be stable. 10 | safe_attributes 'name', 'description', 'enabled', 'memo', 'tracker_id', 11 | 'project_id', 'position', 'visibility' 12 | 13 | attr_accessor :role_ids 14 | validates :role_ids, presence: true, if: :roles? 15 | 16 | belongs_to :project 17 | belongs_to :author, class_name: 'User', foreign_key: 'author_id' 18 | belongs_to :tracker 19 | 20 | has_many :note_visible_roles, dependent: :nullify 21 | has_many :roles, through: :note_visible_roles 22 | 23 | validates :project_id, presence: true 24 | validates :name, uniqueness: { scope: :project_id } 25 | validates :name, presence: true 26 | validates :tracker, presence: true 27 | validates :description, presence: true 28 | acts_as_positioned scope: %i[project_id tracker_id] 29 | 30 | enum visibility: { mine: 0, roles: 1, open: 2 } 31 | 32 | scope :mine_condition, lambda { |user_id| 33 | where(author_id: user_id).mine if user_id.present? 34 | } 35 | scope :roles_condition, lambda { |role_ids| 36 | joins(:note_visible_roles).where(note_visible_roles: { role_id: role_ids }) 37 | } 38 | 39 | scope :enabled, -> { where(enabled: true) } 40 | scope :sorted, -> { order(:position) } 41 | scope :search_by_tracker, lambda { |tracker_id| 42 | where(tracker_id: tracker_id) if tracker_id.present? 43 | } 44 | scope :search_by_project, lambda { |prolect_id| 45 | where(project_id: prolect_id) if prolect_id.present? 46 | } 47 | 48 | before_save :check_visible_roles 49 | after_save :note_visible_roles! 50 | before_destroy :confirm_disabled 51 | 52 | def <=>(other) 53 | position <=> other.position 54 | end 55 | 56 | def template_json 57 | template = {} 58 | template['note_template'] = generate_json 59 | template.to_json(root: true) 60 | end 61 | 62 | def generate_json 63 | attributes 64 | end 65 | 66 | def note_visible_roles! 67 | return unless roles? 68 | 69 | if role_ids.blank? 70 | raise NoteTemplateError, l(:please_select_at_least_one_role, 71 | default: 'Please select at least one role.') 72 | end 73 | 74 | ActiveRecord::Base.transaction do 75 | NoteVisibleRole.where(note_template_id: id).delete_all if note_visible_roles.present? 76 | role_ids.each do |role_id| 77 | NoteVisibleRole.create!(note_template_id: id, role_id: role_id) 78 | end 79 | end 80 | end 81 | 82 | def loadable?(user_id:) 83 | user = User.find(user_id) 84 | return true if user.admin? || open? 85 | 86 | if mine? 87 | user_id == author_id 88 | elsif roles? 89 | user_project_roles = user.roles_for_project(project).pluck(:id) 90 | match_roles = user_project_roles & roles.ids 91 | !match_roles.empty? 92 | else 93 | false 94 | end 95 | end 96 | 97 | private 98 | 99 | def check_visible_roles 100 | return if roles? || note_visible_roles.empty? 101 | 102 | # Remove roles in case template visible scope is not "roles". 103 | # This remove action is included the same transaction scope. 104 | NoteVisibleRole.where(note_template_id: id).delete_all 105 | end 106 | 107 | def confirm_disabled 108 | return unless enabled? 109 | 110 | errors.add :base, 'enabled_template_cannot_destroy' 111 | throw :abort 112 | end 113 | 114 | # 115 | # Class method 116 | # 117 | class << self 118 | def visible_note_templates_condition(user_id:, project_id:, tracker_id:) 119 | user = User.find(user_id) 120 | project = Project.find(project_id) 121 | user_project_roles = user.roles_for_project(project).pluck(:id) 122 | 123 | base_condition = NoteTemplate.search_by_project(project_id).search_by_tracker(tracker_id) 124 | 125 | open_ids = base_condition.open.pluck(:id) 126 | mine_ids = base_condition.mine_condition(user_id).pluck(:id) 127 | role_ids = base_condition.roles_condition(user_project_roles).pluck(:id) 128 | 129 | # return uniq ids 130 | ids = open_ids | mine_ids | role_ids 131 | NoteTemplate.where(id: ids).enabled.includes(:note_visible_roles) 132 | end 133 | 134 | def attribute_map 135 | { 136 | description: :label_comment, 137 | name: :issue_template_name, 138 | } 139 | end 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /app/models/note_visible_role.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class NoteVisibleRole < (defined?(ApplicationRecord) == 'constant' ? ApplicationRecord : ActiveRecord::Base) 4 | include Redmine::SafeAttributes 5 | 6 | safe_attributes 'note_template_id', 'role_id' 7 | belongs_to :role 8 | belongs_to :note_template, optional: true 9 | 10 | validates :role_id, presence: true 11 | validates :note_template_id, presence: true 12 | 13 | scope :search_by_note_template, lambda { |note_template_id| 14 | where(note_template_id: note_template_id) 15 | } 16 | end 17 | -------------------------------------------------------------------------------- /app/views/common/_nodata.html.erb: -------------------------------------------------------------------------------- 1 | <% if trackers.blank? %> 2 |
3 | <%= simple_format(l(:text_no_tracker_enabled)) %> 4 |
5 | <% end %> -------------------------------------------------------------------------------- /app/views/common/_orphaned.html.erb: -------------------------------------------------------------------------------- 1 |

<%= l(:orphaned_template) %>

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <% orphaned_templates.each do |issue_template| %> 15 | issue_template issue'> 16 | 23 | 29 | 38 | 39 | 40 | 41 | 42 | 43 | 45 | 46 | <% end %> 47 | 48 |
#<%= l(:issue_template_name) %><%= l(:label_preview) %><%= l(:field_tracker) %><%= l(:field_author) %><%= l(:field_updated_on) %>
17 | <%= link_to h(issue_template.id), 18 | { controller: controller.controller_name, action: 'show', 19 | id: issue_template.id, 20 | }.merge(issue_template.try(:project_id) ? { project_id: issue_template.project } : {}), 21 | { title: issue_template.note } %> 22 | 24 | <%= link_to h(issue_template.title), 25 | { controller: controller.controller_name, 26 | id: issue_template.id, action: 'show' }, 27 | { title: "#{html_escape(issue_template.note) }"} %> 28 | 30 |
31 | 32 |
33 | <%= issue_template.title %> 34 | <%= textilizable(issue_template.description) %> 35 |
36 |
37 |
<%= "ID: #{issue_template.tracker_id}" %><%=h issue_template.author %><%= format_time(issue_template.updated_on) %>
49 | -------------------------------------------------------------------------------- /app/views/common/_template_links.html.erb: -------------------------------------------------------------------------------- 1 |
3 | 13 | 23 | 24 | 30 |
31 | -------------------------------------------------------------------------------- /app/views/global_issue_templates/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%=h "#{l(:global_issue_templates)}" %>

2 | <%= render partial: 'common/nodata', locals: { trackers: trackers } %> 3 |
4 | <%= link_to(l(:label_new_templates), 5 | { controller: 'global_issue_templates', action: 'new' }, class: 'icon icon-add') %> 6 | <%= link_to(l(:global_note_templates, default: 'Global Note Templates'), 7 | { controller: 'global_note_templates', action: 'index' }, 8 | class: 'icon icon-template') %> 9 | <%= link_to(l(:label_settings), 10 | { controller: 'settings', action: 'plugin', id: 'redmine_issue_templates' }, 11 | class: 'issue_template icon plugins') %> 12 |
13 |
14 | 15 | <% if template_map.blank? %> 16 |
17 | <%= l(:no_issue_templates_for_this_redmine) %> 18 |
19 | <% end %> 20 | <% template_map.each_key do |tracker| %> 21 |
22 |

<%= tracker.name %>

23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | <% template_map[tracker].sorted.each do |issue_template| %> 40 | issue_template issue'> 41 | 45 | 50 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 75 | 76 | <% end %> 77 | 78 |
#<%= l(:issue_template_name) %><%= l(:label_preview) %><%= l(:field_tracker) %><%= l(:field_author) %><%= l(:field_updated_on) %><%= l(:field_is_default) %><%= l(:label_enabled) %><%=l(:button_sort)%>
<%= link_to h(issue_template.id), { controller: 'global_issue_templates', 42 | id: issue_template.id, action: 'show' }, 43 | { title: issue_template.title } %> 44 | 46 | <%= link_to h(issue_template.title), { controller: 'global_issue_templates', 47 | id: issue_template.id, action: 'show' }, 48 | { title: "#{html_escape(issue_template.note)}" } %> 49 | 51 |
52 | 53 |
54 | <%= issue_template.title %> 55 | <%= textilizable(issue_template.description) %> 56 | <% if issue_template.related_link.present? %> 57 |
58 | <%= link_to issue_template.link_title.present? ? issue_template.link_title : l(:issue_template_related_link, default: 'Related link'), 59 | issue_template.related_link, target: '_blank', rel: 'nofollow noopener', class: 'external' %> 60 | <% end %> 61 |
62 |
63 | 64 |
<%=h issue_template.tracker.name %><%=h issue_template.author %><%= format_time(issue_template.updated_on)%> <%= checked_image issue_template.is_default? %><%= checked_image issue_template.enabled? %> 72 | <%= reorder_handle(issue_template, :url => url_for({ controller: 'global_issue_templates', 73 | id: issue_template.id, action: 'update' })) %> 74 |
79 |
80 | 81 | <%= javascript_tag do %> 82 | // NOTE: Sortable feature depends on Redmine's sorting jQuery plugin. 83 | $(function() { $('table.table-sortable tbody').positionedItems() }) 84 | <% end %> 85 | <% end %> 86 | 87 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /app/views/global_issue_templates/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= link_to(l(:label_list_templates), 3 | { controller: 'global_issue_templates', action: 'index' }, class: 'icon icon-template') %> 4 |
5 |

<%=h "#{l(:issue_templates)} / #{l(:button_add)}" %>

6 | 7 | <%= labelled_form_for :global_issue_template, issue_template, 8 | url: { controller: 'global_issue_templates', action: 'create' }, 9 | html: { id: 'global_issue_template-form', 10 | class: nil, multipart: false } do |f| %> 11 | 12 | <%= render 'form', { f: f, trackers: trackers, projects: projects, 13 | issue_template: issue_template, apply_all_projects: apply_all_projects, 14 | custom_fields: custom_fields, builtin_fields_enable: builtin_fields_enable } %> 15 | <% end %> 16 | -------------------------------------------------------------------------------- /app/views/global_issue_templates/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= link_to l(:button_delete), 3 | { controller: 'global_issue_templates', action: 'destroy', id: issue_template }, 4 | data: { confirm: l(:text_are_you_sure) }, 5 | title: l(:enabled_template_cannot_destroy, default: 'Only disabled template can be destroyed.'), 6 | disabled: issue_template.enabled?, method: 'delete', class: 'icon icon-del template-disabled-link' %> 7 | <%= link_to(l(:label_list_templates), 8 | { controller: 'global_issue_templates', action: 'index' }, class: 'icon icon-template') %> 9 |
10 | 11 |

12 | <%= l(:global_issue_templates, default: 'Global Template for note') %>: #<%= issue_template.id %> <%= issue_template.title %> 13 | <%= avatar(issue_template.author, size: '24') %> 14 |

15 | 16 | <%= labelled_form_for :global_issue_template, 17 | issue_template, 18 | url: { controller: 'global_issue_templates', action: 'update', id: issue_template }, 19 | html: { id: 'global_issue_template-form', class: nil, 20 | multipart: false } do |f| %> 21 | <%= render 'form', { f: f, trackers: trackers, 22 | issue_template: issue_template, projects: projects, apply_all_projects: apply_all_projects, 23 | custom_fields: custom_fields, builtin_fields_enable: builtin_fields_enable } %> 24 | <% end %> 25 | -------------------------------------------------------------------------------- /app/views/global_note_templates/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%=h "#{l(:global_note_templates, default: 'Global Note Templates')}" %>

2 | <%= render partial: 'common/nodata', locals: { trackers: trackers } %> 3 |
4 | <%= link_to(l(:label_new_templates), 5 | { controller: 'global_note_templates', action: 'new' }, class: 'icon icon-add') %> 6 | <%= link_to(l(:global_issue_templates), 7 | { controller: 'global_issue_templates', action: 'index' }, 8 | class: 'icon icon-template') %> 9 | <%= link_to(l(:label_settings), 10 | { controller: 'settings', action: 'plugin', id: 'redmine_issue_templates' }, 11 | class: 'issue_template icon plugins') %> 12 |
13 |
14 | 15 | <% if template_map.blank? %> 16 |
17 | <%= l(:no_note_templates_for_this_redmine, default: 'No global note templates are defined for this Redmine site.') %> 18 |
19 | <% end %> 20 | 21 | <% template_map.each_key do |tracker| %> 22 |
23 |

<%= tracker.name %>

24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | <% template_map[tracker].sorted.each do |note_template| %> 40 | issue_template issue'> 41 | 45 | 50 | 60 | 61 | 62 | 63 | 64 | 65 | 69 | 70 | <% end %> 71 | 72 |
#<%= l(:issue_template_name) %><%= l(:label_preview) %><%= l(:field_tracker) %><%= l(:field_author) %><%= l(:field_updated_on) %><%= l(:label_enabled) %><%=l(:button_sort)%>
<%= link_to h(note_template.id), { controller: 'global_note_templates', 42 | id: note_template.id, action: 'show' }, 43 | { title: note_template.name } %> 44 | 46 | <%= link_to h(note_template.name), { controller: 'global_note_templates', 47 | id: note_template.id, action: 'show' }, 48 | { title: "#{html_escape(note_template.memo)}" } %> 49 | 51 |
52 | 53 |
54 | <%= note_template.name %> 55 | <%= textilizable(note_template.description) %> 56 |
57 |
58 | 59 |
<%=h note_template.tracker.name %><%=h note_template.author %><%= format_time(note_template.updated_at)%> <%= checked_image note_template.enabled? %> 66 | <%= reorder_handle(note_template, :url => url_for({ controller: 'global_note_templates', 67 | id: note_template.id, action: 'update' })) %> 68 |
73 |
74 | 75 | <%= javascript_tag do %> 76 | // NOTE: Sortable feature depends on Redmine's sorting jQuery plugin. 77 | $(function() { $('table.table-sortable tbody').positionedItems() }) 78 | <% end %> 79 | <% end %> 80 | -------------------------------------------------------------------------------- /app/views/global_note_templates/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= link_to(l(:label_list_templates), 3 | { controller: 'global_note_templates', action: 'index' }, class: 'icon icon-template') %> 4 |
5 |

<%=h "#{l(:global_note_templates, default: 'Global Template for note') } / #{l(:button_add)}" %>

6 | 7 | <%= labelled_form_for :global_note_template, note_template, 8 | url: { controller: 'global_note_templates', action: 'create' }, 9 | html: { id: 'global_note_template-form', class: nil, multipart: false } do |f| %> 10 | 11 | <%= render 'form', { f: f, trackers: trackers, 12 | note_template: note_template, projects: projects, apply_all_projects: apply_all_projects } %> 13 |
14 | <%= submit_tag l(:button_create) %> 15 | <%= link_to l(:button_cancel), { action: 'index'}, data: { confirm: l(:text_are_you_sure) } %> 16 | <% end %> 17 | -------------------------------------------------------------------------------- /app/views/global_note_templates/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= link_to l(:button_delete), 3 | { controller: 'global_note_templates', action: 'destroy', id: note_template }, 4 | data: { confirm: l(:text_are_you_sure) }, 5 | name: l(:enabled_template_cannot_destroy, default: 'Only disabled template can be destroyed.'), 6 | disabled: note_template.enabled?, method: 'delete', class: 'icon icon-del template-disabled-link' %> 7 | <%= link_to(l(:label_list_templates), 8 | { controller: 'global_note_templates', action: 'index' }, class: 'icon icon-template') %> 9 |
10 | 11 |

12 | <%= l(:global_note_templates) %>: #<%= note_template.id %> <%= note_template.name %> 13 | <%= avatar(note_template.author, size: '24') %> 14 |

15 | 16 | <%= labelled_form_for :global_note_template, 17 | note_template, 18 | url: { controller: 'global_note_templates', action: 'update', id: note_template }, 19 | html: { id: 'global_note_template-form', class: nil, 20 | multipart: false } do |f| %> 21 | <%= render 'form', { f: f, trackers: trackers, 22 | note_template: note_template, projects: projects, apply_all_projects: apply_all_projects } %> 23 |
24 | <%= submit_tag l(:button_save) %> 25 | <%= link_to l(:button_cancel), { action: 'index' }, 26 | onclick: 'Element.hide("edit-note_template"); return false' %> 27 | <% end %> 28 | -------------------------------------------------------------------------------- /app/views/issue_templates/_issue_template_link.html.erb: -------------------------------------------------------------------------------- 1 | <% if authorize_for('issue_templates', 'show') %> 2 |

<%= l(:issue_template) %>

3 | 15 | <%- end -%> 16 | <% if authorize_for('note_templates', 'show') %> 17 |

<%= l(:note_template) %>

18 | 30 | <%- end -%> -------------------------------------------------------------------------------- /app/views/issue_templates/_list_templates.api.rsb: -------------------------------------------------------------------------------- 1 | api.array :global_issue_templates do 2 | global_issue_templates.each do |template| 3 | api.template do 4 | api.id template.id 5 | api.tracker_id template.tracker_id 6 | api.tracker_name template.tracker.id 7 | api.title template.title 8 | api.issue_title template.issue_title 9 | api.description template.description 10 | api.note template.note 11 | api.enabled template.enabled 12 | api.updated_on template.updated_on 13 | api.created_on template.created_on 14 | api.updated_on template.updated_on 15 | end 16 | end 17 | end 18 | api.array :inherit_templates do 19 | inherit_templates.each do |template| 20 | api.template do 21 | api.id template.id 22 | api.tracker_id template.tracker_id 23 | api.tracker_name template.tracker.name 24 | api.title template.title 25 | api.issue_title template.issue_title 26 | api.description template.description 27 | api.note template.note 28 | api.enabled template.enabled 29 | api.is_default template.id == default_template 30 | api.enabled_sharing template.enabled_sharing 31 | api.position template.position 32 | api.updated_on template.updated_on 33 | api.created_on template.created_on 34 | api.updated_on template.updated_on 35 | end 36 | end 37 | end 38 | api.array :issue_templates do 39 | issue_templates.each do |template| 40 | api.template do 41 | api.id template.id 42 | api.tracker_id template.tracker_id 43 | api.tracker_name template.tracker.name 44 | api.title template.title 45 | api.issue_title template.issue_title 46 | api.description template.description 47 | api.note template.note 48 | api.enabled template.enabled 49 | api.is_default template.is_default 50 | api.enabled_sharing template.enabled_sharing 51 | api.position template.position 52 | api.updated_on template.updated_on 53 | api.created_on template.created_on 54 | api.updated_on template.updated_on 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /app/views/issue_templates/_list_templates.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <% issue_templates.each do |template| %> 12 | template_data'> 13 | 16 | 19 | 33 | 34 | 38 | 39 | <% end %> 40 | <% inherit_templates.each do |template| %> 41 | 42 | 45 | 48 | 62 | 63 | 67 | 68 | <% end %> 69 | <% global_issue_templates.each do |template| %> 70 | template_data'> 71 | 74 | 77 | 91 | 92 | 96 | 97 | <% end %> 98 |
<%=h l(:issue_template_name) %><%=h l(:issue_title) %><%=h l(:issue_description) %><%= l(:field_is_default) %><%=h l(:button_apply, default: 'Apply') %>
14 | <%= template.title %> 15 | 17 | <%= template.issue_title %> 18 | 20 |
21 | 22 |
23 | <%= template.title %> 24 | <%= textilizable(template.description) %> 25 | <% if template.related_link.present? %> 26 |
27 | <%= link_to template.link_title.present? ? template.link_title : l(:issue_template_related_link, default: 'Related link'), 28 | template.related_link, target: '_blank', rel: 'nofollow noopener', class: 'external' %> 29 | <% end %> 30 |
31 |
32 |
<%= checked_image template.is_default? %> 35 | 37 |
43 | <%= template.title %> 44 | 46 | <%= template.issue_title %> 47 | 49 |
50 | 51 |
52 | <%= template.title %> 53 | <%= textilizable(template.description) %> 54 | <% if template.related_link.present? %> 55 |
56 | <%= link_to template.link_title.present? ? template.link_title : l(:issue_template_related_link, default: 'Related link'), 57 | template.related_link, target: '_blank', rel: 'nofollow noopener', class: 'external' %> 58 | <% end %> 59 |
60 |
61 |
<%= checked_image template == default_template %> 64 | 66 |
72 | <%= template.title %> 73 | 75 | <%= template.issue_title %> 76 | 78 |
79 | 80 |
81 | <%= template.issue_title %> 82 | <%= textilizable(template.description) %> 83 | <% if template.related_link.present? %> 84 |
85 | <%= link_to template.link_title.present? ? template.link_title : l(:issue_template_related_link, default: 'Related link'), 86 | template.related_link, target: '_blank', rel: 'nofollow noopener', class: 'external' %> 87 | <% end %> 88 |
89 |
90 |
<%= checked_image template == default_template %> 93 | 95 |
99 | 100 | 109 | -------------------------------------------------------------------------------- /app/views/issue_templates/_note_form.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | element_id = type 3 | if type == 'template_edit_journal' 4 | element_id = "template_journal_#{@journal.id}_notes" 5 | end 6 | project_id = issue&.project_id 7 | tracker_id = issue&.tracker_id 8 | %> 9 |
10 | 13 | 14 | <%=h l(:display_and_filter_issue_templates_in_dialog, default: 'Filter Templates') %> 15 | 16 | 17 |
18 | 28 |
29 |
30 | 31 | 53 | -------------------------------------------------------------------------------- /app/views/issue_templates/_template_pulldown.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= options_for_template_pulldown(grouped_options) %> 4 | 5 | -------------------------------------------------------------------------------- /app/views/issue_templates/index.api.rsb: -------------------------------------------------------------------------------- 1 | api.array :global_issue_templates do 2 | @global_issue_templates.each do |template| 3 | api.template do 4 | api.id template.id 5 | api.tracker_id template.tracker_id 6 | api.tracker_name template.tracker.try(:name) || nil 7 | api.title template.title 8 | api.issue_title template.issue_title 9 | api.description template.description 10 | api.note template.note 11 | api.enabled template.enabled 12 | api.updated_on template.updated_on 13 | api.created_on template.created_on 14 | api.updated_on template.updated_on 15 | end 16 | end 17 | end 18 | api.array :inherit_templates do 19 | @inherit_templates.each do |template| 20 | api.template do 21 | api.id template.id 22 | api.tracker_id template.tracker_id 23 | api.tracker_name template.tracker.tracker.try(:name) || nil 24 | api.title template.title 25 | api.issue_title template.issue_title 26 | api.description template.description 27 | api.note template.note 28 | api.enabled template.enabled 29 | api.is_default template.is_default 30 | api.enabled_sharing template.enabled_sharing 31 | api.position template.position 32 | api.updated_on template.updated_on 33 | api.created_on template.created_on 34 | api.updated_on template.updated_on 35 | end 36 | end 37 | end 38 | api.array :issue_templates do 39 | project_templates.each do |template| 40 | api.template do 41 | api.id template.id 42 | api.tracker_id template.tracker_id 43 | api.tracker_name template.tracker.try(:name) || nil 44 | api.title template.title 45 | api.issue_title template.issue_title 46 | api.description template.description 47 | api.note template.note 48 | api.enabled template.enabled 49 | api.is_default template.is_default 50 | api.enabled_sharing template.enabled_sharing 51 | api.position template.position 52 | api.updated_on template.updated_on 53 | api.created_on template.created_on 54 | api.updated_on template.updated_on 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /app/views/issue_templates/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= link_to(l(:label_list_templates), 3 | { controller: 'issue_templates', 4 | action: 'index', project_id: project }, class: 'icon icon-template') %> 5 |
6 |

<%=h "#{l(:issue_templates)} / #{l(:button_add)}" %>

7 | <%= render partial: 'common/nodata', locals: { trackers: project.trackers } %> 8 | <% if project.trackers.any? %> 9 | <%= labelled_form_for :issue_template, @issue_template, 10 | url: { controller: 'issue_templates', action: 'create', project_id: project }, 11 | html: { id: 'issue_template-form', class: nil, multipart: false } do |f| %> 12 | 13 | <%= render 'form', { f: f, issue_template: issue_template, project: project, custom_fields: custom_fields, 14 | builtin_fields_enable: builtin_fields_enable } %> 15 |
16 | <%= submit_tag l(:button_create) %> 17 | <%= link_to l(:button_cancel), { action: 'index'}, data: { confirm: l(:text_are_you_sure) } %> 18 | <% end %> 19 | <% end %> 20 | 21 |
<%= render partial: 'common/template_links' %>
-------------------------------------------------------------------------------- /app/views/issue_templates_settings/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= render partial: 'common/nodata', locals: { trackers: @project.trackers } %> 3 | 4 |

<%= l(:issue_templates_settings, default: 'Issue Templates Setting') %>

5 |

6 |
7 |

<%= l(:issue_templates_optional_settings, default: 'Templates Optional Settings') %>

8 |

<%= l(:about_help_message) %>

9 | <%= labelled_form_for :settings, @issue_templates_setting, 10 | url: { controller: 'issue_templates_settings', 11 | action: 'edit', project_id: @project, 12 | setting_id: @issue_templates_setting.id }, 13 | html: { id: 'issue_templates_settings' } do |f| %> 14 | <%= error_messages_for 'issue_templates_setting' %> 15 |
16 | 17 |

<%= f.check_box :inherit_templates, label: l(:label_inherit_templates) %> 18 | 21 | <%= l(:help_for_this_field) %> 22 | 23 | 24 |

25 | 26 |

27 | <%= f.check_box :should_replaced, label: l(:label_should_replaced) %> 28 | 32 | <%= l(:help_for_this_field) %> 33 | 34 | 35 |

36 | 37 |

<%= f.check_box :enabled, label: l(:label_show_help_message) %>

38 |

39 | 40 | <%=content_tag(:label, l(:label_help_message)) %> 41 | <%=text_area_tag 'settings[help_message]', @issue_templates_setting['help_message'], size: '50x5', 42 | class: 'wiki-edit' %> 43 |
44 |

45 | <%= wikitoolbar_for 'settings_help_message' %> 46 | 47 |
48 | 49 | <%= submit_tag l(:button_save) %> 50 | <% end %> 51 |
52 | 53 | 54 | 57 | 60 | 61 |
62 | 63 |
<%= render partial: 'common/template_links' %>
64 | -------------------------------------------------------------------------------- /app/views/note_templates/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= error_messages_for 'note_template' %> 2 |
3 |

<%= f.text_field :name, required: true, size: 80, label: l(:issue_template_name) %>

4 | 5 |
6 | <%= l(:label_applied_for_issue) %> 7 |

8 | <% if note_template.tracker.blank? %> 9 | <%= f.select :tracker_id, template_target_trackers(project, note_template), 10 | required: true, label: l(:label_tracker), include_blank: true %> 11 | 12 | <%= h note_template.tracker.present? ? note_template.tracker.name : 13 | l(:orphaned_template, default: 'Orphaned template from tracker') %> 14 | <% else %> 15 | <%= f.select :tracker_id, template_target_trackers(project, note_template), 16 | required: true, label: l(:label_tracker), selected: note_template.tracker.id %> 17 | <% unless project_tracker?(note_template.tracker, project) %>
18 | <%= non_project_tracker_msg(project_tracker?(note_template.tracker, project)) %> 19 | <% end %> 20 | <% end %> 21 |

22 |

23 | <%= f.text_area :description, cols: 78, rows: 12, 24 | required: true, 25 | label: l(:label_comment), class: 'wiki-edit' %> 26 |

27 | 28 |

29 | <%= f.select :visibility, NoteTemplate.visibilities.map { |k, v| [t("note_templates.visibility.#{k}"), k] }, 30 | selected: note_template.visibility, 31 | required: true, label: l(:field_template_visibility) %> 32 |

33 |

'> 35 | 36 | <% Role.givable.each do |role| %> 37 | <%= check_box_tag('note_template[role_ids][]', 38 | role.id, 39 | note_template.note_visible_roles.pluck(:role_id).to_a.include?(role.id)) %> 40 | <%= role.name %> 41 | <% end %> 42 |

43 |
44 |

45 | <%= f.text_area :memo, cols: 70, rows: 3, 46 | required: false, 47 | label: l(:issue_template_note) %> 48 | 51 | <%= l(:help_for_this_field) %> 52 | 53 | 54 |

55 | 56 |

57 | <%= f.check_box :enabled, label: l(:label_enabled) %> 58 | 61 | <%= l(:help_for_this_field) %> 62 | 63 | 64 |

65 |
66 | 67 | <%= wikitoolbar_for 'note_template_description' %> 68 | 69 | 70 | 73 | 74 | 77 | 78 | 81 | 82 | 83 | 95 | -------------------------------------------------------------------------------- /app/views/note_templates/_list_note_templates.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <% note_templates.each do |template| %> 11 | 12 | 15 | 27 | 32 | 33 | <% end %> 34 | <% global_note_templates.each do |template| %> 35 | 36 | 39 | 51 | 56 | 57 | <% end %> 58 | 59 |
<%=h l(:note_template_name, default: "Name") %><%=h l(:note_description, default: "Comment Body") %><%=h l(:button_apply, default: 'Apply') %>
13 | <%= template.name %> 14 | 16 |
17 | 18 |
19 | <%= template.name %> 20 | <%= textilizable(template.description) %> 21 |
22 | <%= l(:issue_template_note) %> 23 | <%= textilizable(template.try(:memo)) %> 24 |
25 |
26 |
28 | 31 |
37 | <%= template.name %> 38 | 40 |
41 | 42 |
43 | <%= template.name %> 44 | <%= textilizable(template.description) %> 45 |
46 | <%= l(:issue_template_note) %> 47 | <%= textilizable(template.try(:memo)) %> 48 |
49 |
50 |
52 | 55 |
60 | -------------------------------------------------------------------------------- /app/views/note_templates/index.api.rsb: -------------------------------------------------------------------------------- 1 | api.array :note_templates do 2 | note_templates.each do |template| 3 | api.template do 4 | api.id template.id 5 | api.tracker_id template.tracker_id 6 | api.tracker_name template.tracker.name 7 | api.title template.title 8 | api.description template.description 9 | api.enabled template.enabled 10 | api.created_on template.created_on 11 | api.updated_on template.updated_on 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/views/note_templates/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= link_to(l(:label_list_templates), 3 | { controller: 'note_templates', 4 | action: 'index', project_id: project }, class: 'icon icon-template') %> 5 |
6 |

<%=h "#{l(:note_template)} / #{l(:button_add)}" %>

7 | <% if project.trackers.any? %> 8 | <%= labelled_form_for :note_template, @note_template, 9 | url: { controller: 'note_templates', action: 'create', project_id: project }, 10 | html: { id: 'issue_template-form', class: nil, multipart: false } do |f| %> 11 | 12 | <%= render 'form', { f: f, note_template: note_template, project: project } %> 13 |
14 | <%= submit_tag l(:button_create) %> 15 | <%= link_to l(:button_cancel), { action: 'index'}, data: { confirm: l(:text_are_you_sure) } %> 16 | <% end %> 17 | <% end %> 18 | 19 | <%= render partial: 'common/template_links' %> -------------------------------------------------------------------------------- /app/views/note_templates/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= link_to_if_authorized l(:button_edit), 3 | { controller: 'note_templates', action: 'update', id: note_template, 4 | project_id: project }, 5 | class: 'icon icon-edit', accesskey: accesskey(:edit), 6 | onclick: "document.getElementById('edit-note_template').style.display = 'inline'; return false;" %> 7 | <%= link_to_if_authorized l(:button_delete), 8 | { controller: 'note_templates', action: 'destroy', 9 | id: note_template, project_id: project }, 10 | data: { confirm: l(:template_remove_confirm, 11 | default: "Are you sure to remove this template? %{count} subprojects use this template.") }, 12 | title: l(:enabled_template_cannot_destroy, default: 'Only disabled template can be destroyed.'), 13 | method: 'delete', class: 'icon icon-del template-disabled-link', disabled: note_template.enabled? %> 14 | <%= link_to(l(:label_list_templates), 15 | { controller: 'note_templates', 16 | action: 'index', 17 | project_id: project }, class: 'icon icon-template') %> 18 | <%= link_to_if_authorized(l(:label_new_templates), 19 | { controller: 'note_templates', action: 'new', project_id: @project }, 20 | class: 'icon icon-add') %> 21 |
22 | 23 |

24 | <%= l(:note_template) %>: #<%= note_template.id %> <%= note_template.name %> 25 | <%= avatar(note_template.author, size: '24') %> 26 |

27 | 28 | <%= render partial: 'common/nodata', locals: { trackers: project.trackers } %> 29 | <% if authorize_for('note_templates', 'update') %> 30 |
31 | <%= labelled_form_for :note_template, note_template, 32 | url: { controller: 'note_templates', action: 'update', 33 | project_id: project, id: note_template }, 34 | html: { id: 'note_template-form', class: nil, multipart: false } do |f| %> 35 | 36 | <%= render 'form', { f: f, note_template: note_template, project: project } %> 37 |
38 | <%= submit_tag l(:button_save) %> 39 | <%= link_to l(:button_cancel), { action: 'index' }, 40 | onclick: 'Element.hide("edit-note_template"); return false' %> 41 | <% end %> 42 |
43 |
44 | <% else %> 45 | 46 | 47 |
48 |

49 | 50 | <%= h note_template.name %> 51 |

52 |
53 | <%= l(:label_applied_for_issue) %> 54 |

55 | 56 | <%= h note_template.tracker.name %> 57 |

58 | 59 |

60 | 61 | <%= h note_template.name %> 62 |

63 | 64 |

65 | <%= l(:issue_description) %> 66 |

67 | 68 |
<%= textilizable(note_template.description) %>
69 | 70 |
71 | 72 |

73 | <%= note_template.memo.blank? ? l(:label_none) : note_template.memo %> 74 |

75 | 76 |

77 | <%= checked_image note_template.enabled? %> 78 |

79 | 80 |

81 | <%= authoring note_template.created_at, note_template.author %> 82 | <% if note_template.created_at != note_template.updated_at %> 83 | (<%= l(:label_updated_time, time_tag(note_template.updated_at)).html_safe %>) 84 | <% end %> 85 |

86 | 87 |
88 | <% end %> 89 | 90 | <%= render partial: 'common/template_links' %> 91 | -------------------------------------------------------------------------------- /app/views/settings/_redmine_issue_templates.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | <%= hidden_field_tag("settings[apply_global_template_to_all_projects]", 0, :id => nil).html_safe %> 5 | <%= check_box_tag 'settings[apply_global_template_to_all_projects]', true, @settings['apply_global_template_to_all_projects'] == "true" %> 6 | 9 | <%= l(:help_for_this_field) %> 10 | 11 | 12 |

13 |

14 | 15 | <%= hidden_field_tag('settings[apply_template_when_edit_issue]', 0, :id => nil).html_safe %> 16 | <%= check_box_tag 'settings[apply_template_when_edit_issue]', true, @settings['apply_template_when_edit_issue'] == 'true' %> 17 |

18 |

19 | <%= link_to(l(:link_to_index_edit_template), 20 | { controller: 'global_issue_templates', action: 'index' }, 21 | class: 'icon icon-template pull-right') %> 22 |

23 |

24 | <%= link_to(l(:link_to_index_edit_note_template, default: 'Note templates list / edit templates'), 25 | { controller: 'global_note_templates', action: 'index' }, 26 | class: 'icon icon-template pull-right') %> 27 |

28 | 29 |

30 | 33 | <%= hidden_field_tag('settings[enable_builtin_fields]', 0, :id => nil).html_safe %> 34 | <%= check_box_tag 'settings[enable_builtin_fields]', true, @settings['enable_builtin_fields'] == 'true' %> 35 | 38 | <%= l(:help_for_this_field) %> 39 | 40 | 41 |

42 |
43 | 44 | 50 | 51 | 57 | -------------------------------------------------------------------------------- /assets/images/eraser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agileware-jp/redmine_issue_templates/24756d64c22a2ba771315ee11afdda843287b5c9/assets/images/eraser.png -------------------------------------------------------------------------------- /assets/images/issue_templates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agileware-jp/redmine_issue_templates/24756d64c22a2ba771315ee11afdda843287b5c9/assets/images/issue_templates.png -------------------------------------------------------------------------------- /assets/images/lamp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agileware-jp/redmine_issue_templates/24756d64c22a2ba771315ee11afdda843287b5c9/assets/images/lamp.png -------------------------------------------------------------------------------- /assets/images/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agileware-jp/redmine_issue_templates/24756d64c22a2ba771315ee11afdda843287b5c9/assets/images/preview.png -------------------------------------------------------------------------------- /assets/images/ticket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agileware-jp/redmine_issue_templates/24756d64c22a2ba771315ee11afdda843287b5c9/assets/images/ticket.png -------------------------------------------------------------------------------- /config/locales/da.yml: -------------------------------------------------------------------------------- 1 | # Danish strings go here for Rails i18n 2 | da: 3 | issue_templates: Sagsskabeloner 4 | issue_template: Sagsskabelon 5 | issue_template_note: Note 6 | label_enabled: Aktiveret 7 | label_help_message: Skabelon-hjælpetekst 8 | label_show_help_message: Vis hjælpetekst ved oprettelse eller redigering af sag. 9 | about_help_message: Hvert projekt kan have sin egen hjælpetekst. 10 | close_help: Luk hjælpetekst. 11 | about_template_help_message: Du kan se instruktioner om sagsskabeloner på dette projekt. 12 | label_enabled_help_message: Tjekboks til aktivering af denne skabelon. Fjern afkrydsning for at gemme som kladde. 13 | label_list_templates: "Skabeloner" 14 | label_new_templates: "Tilføj skabelon" 15 | issue_template_name: "Skabelonnavn" 16 | issue_description: "Sagsbeskrivelse" 17 | issue_title: "Sagsemne" 18 | label_applied_for_issue: "Felter: " 19 | help_for_issue_title: "Hvis sat, vil sagens emnefelt blive udfyldt når skabelonen vælges." 20 | help_for_this_field: "Forklaring til dette felt." 21 | permission_manage_issue_templates: "Administrér skabeloner" 22 | permission_edit_issue_templates: "Ret skabeloner" 23 | permission_show_issue_templates: "Vis skabeloner" 24 | project_module_issue_templates: "Sagsskabeloner" 25 | label_isdefault_help_message: "Anvend som den forudvalgte skabelon på sagstypen." 26 | defaulf_template_loaded: "Indlæste forudvalgt skabelon. (Tracker: %{tracker})" 27 | text_no_tracker_enabled: "Der er ikke defineret sagstyper for dette projekt.\nSæt disse inden der defineres sagsskabeloner.. " 28 | label_enabled_sharing: "Del med projekttræet." 29 | label_inherit_templates: "Arv sagsskabeloner" 30 | label_inherit_templates_help_message: "Arv sagsskabeloner fra hovedprojekt. Hovedprojektets sagsskabeloner skal være delt med projekttræet." 31 | label_inherited_templates: "Nedarvede sagsskabeloner" 32 | no_issue_templates_for_this_project: "Ingen sagsskabeloner defineret for dette projekt." 33 | link_to_index_edit_template: "Sagsskabeloner / ret skabeloner" 34 | erase_issue_subject_and_description: "Tøm emne og beskrivelse." 35 | unused_tracker_at_this_project: "NB: Sagstypen anvendes ikke med projektet." 36 | label_enabledshaing_help_message: "Hvis valgt deles skabelonen med underliggende projekter. (Arv sagsskabeloner skal dog være valgt til på underprojekter.)" 37 | label_should_replaced: "Erstat emne og beskrivelse" 38 | label_should_replaced_help_message: "Hvis valgt erstattes emne og beskrivelse med skabelonen. Ellers tilføjes skabelonens indhold den eksisterende tekst." 39 | global_issue_templates: "Fælles sagsskabeloner" 40 | no_issue_templates_for_this_redmine: "Ingen fælles sagsskabeloner defineret." 41 | only_admin_can_associate_global_template: "Kun administratorer kan sammenkoble fælles sagsskabeloner med projekter." 42 | text_no_tracker_enabled_for_global: "Der er ikke defineret nogen sagstyper.\nSæt disse inden der defineres sagsskabeloner." 43 | display_and_filter_issue_templates_in_dialog: "Filtrér Skabeloner" 44 | label_filter_template: "Filtrér Skabeloner" 45 | -------------------------------------------------------------------------------- /config/locales/es.yml: -------------------------------------------------------------------------------- 1 | # Spanish strings go here for Rails i18n 2 | # Translation by Andres Arias https://github.com/mrlocke 3 | es: 4 | issue_templates: "Plantillas de peticiones" 5 | issue_template: "Plantilla de peticiones" 6 | issue_template_note: "Nota" 7 | label_enabled: "Habilitado" 8 | label_help_message: "Acerca de plantillas" 9 | label_show_help_message: "Mostrar el mensaje de ayuda cunado se crean/modifican las peticiones." 10 | about_help_message: "Cada proyecto puede personalizar el mensaje de ayuda para las plantillas." 11 | close_help: "Cerrar mensaje de ayuda." 12 | about_template_help_message: "Puede ver las instrucciones para las plantillas de peticiones en este proyecto." 13 | label_enabled_help_message: "Marque para activar (habilitar) esta plantilla. Si desea guardarla como borrador, desmarque esta casilla." 14 | label_list_templates: "Lista plantillas" 15 | label_new_templates: "Añadir plantilla" 16 | issue_template_name: "Nombre de la plantilla" 17 | issue_description: "Descripción" 18 | issue_title: "Asunto" 19 | label_applied_for_issue: "Campos que rellenará la plantilla: " 20 | help_for_issue_title: "Si el Asunto ya existe, será reemplazado por el de la plantilla." 21 | help_for_this_field: "Ayuda para este campo." 22 | permission_manage_issue_templates: "Administrar Plantillas" 23 | permission_edit_issue_templates: "Editar Plantillas" 24 | permission_show_issue_templates: "Ver Plantillas" 25 | project_module_issue_templates: "plantillas de Peticiones" 26 | label_isdefault_help_message: "Marque la casilla para seleccionar como plantilla por defecto para este tipo de peticiones." 27 | defaulf_template_loaded: "Cargada la plantilla por defecto. (Tipo de Petición: %{tracker})" 28 | text_no_tracker_enabled: "Todavía no se han configurado Tipos de Peticiones para este proyecto.\nPor favor, configure los primero, ya que las plantillas son asignadas a Tipos de Peticiones." 29 | label_enabled_sharing: "Compartir con el árbol de proyectos." 30 | label_inherit_templates: "Heredar plantillas" 31 | label_inherit_templates_help_message: "Heredar plantillas desde el proyecto padre. (Sólo se comparten las plantillas marcadas como habilitadas.)" 32 | label_inherited_templates: "Plantillas heredadas" 33 | no_issue_templates_for_this_project: "No se han definido plantillas de peticiones para este proyecto." 34 | link_to_index_edit_template: "Ver/Editar plantillas de peticiones" 35 | erase_issue_subject_and_description: "Borrar el Asunto y la Descripción." 36 | unused_tracker_at_this_project: "NOTA: Este Tipo de Petición no está definido para este proyecto. Por favor, cambie la configuración de la plantilla, si es necesario." 37 | label_enabledshaing_help_message: "Si está marcada, la plantilla podrá ser compartida con los sub-proyectos descendientes. (También debe ser activada la opción de heredar plantillas en los proyectos hijos.)" 38 | label_should_replaced: "Reemplazar Asunto y Descripción" 39 | label_should_replaced_help_message: "Si está habilitado, el Asunto y Descripción serán borrados y reemplazados con el texto de la plantilla. De lo contrario, serán añadidos. (Opción por defecto.)" 40 | global_issue_templates: "Plantillas de Peticiones Globales" 41 | no_issue_templates_for_this_redmine: "No hay plantillas globales definidas en este redmine." 42 | only_admin_can_associate_global_template: "Solamente el Administrador de Redmine puede asociar plantillas globales con esté proyecto." 43 | text_no_tracker_enabled_for_global: "No se han definido todavía Tipos de Peticiones. Por favor, configurelos primero, ya que las plantillas son asignadas a Tipos de Peticiones." 44 | -------------------------------------------------------------------------------- /config/locales/fr.yml: -------------------------------------------------------------------------------- 1 | # Les chaines en français sont définies ici pour Rails i18n 2 | fr: 3 | issue_templates: "Gabarits" 4 | issue_template: "Gabarit des demandes" 5 | issue_template_note: "Note" 6 | label_enabled: "Activer" 7 | label_help_message: "À propos des gabarits" 8 | label_show_help_message: "Afficher le message d'aide lors de la création ou mise à jour d'une demande." 9 | about_help_message: "Le message d'aide peut-être personnalisé pour chaque projet." 10 | close_help: "Fermer le messsage d'aide" 11 | about_template_help_message: "Aide au sujet des gabarits de ce projet." 12 | label_enabled_help_message: "Case à cocher pour activer ce gabarit. Si vous prévoyez sauvegarder ce gabarit en brouillon, décochez cette case." 13 | label_list_templates: "Liste des gabarits" 14 | label_new_templates: "Ajouter un gabarit" 15 | issue_template_name: "Nom du gabarit" 16 | issue_description: "Description de la demande" 17 | issue_title: "Titre de la demande" 18 | label_applied_for_issue: "Champs spécifiques de la demande: " 19 | help_for_issue_title: "Si le titre est défini, il sera appliqué aux demandes en choisissant un gabarit." 20 | help_for_this_field: "Aide pour ce champ." 21 | permission_manage_issue_templates: "Gérer les gabarits" 22 | permission_edit_issue_templates: "Modifier le gabarit" 23 | permission_show_issue_templates: "Afficher les gabarits" 24 | project_module_issue_templates: "Gabarit de demande" 25 | label_isdefault_help_message: "Cochez cette case pour établir ce gabarit par défaut pour ce type de demande." 26 | defaulf_template_loaded: " Le gabarit par défaut est chargé dans la zone de description (Type de demande: %{tracker})" 27 | text_no_tracker_enabled: " Aucun type de demande n'a été configuré encore.\nDéfinissez au moins un type car les gabarits sont définis en fonction des types de demandes." 28 | label_enabled_sharing: "Activer le partage avec l'arborescence du projet." 29 | label_inherit_templates: "Hériter gabarit" 30 | label_inherit_templates_help_message: "Hériter des gabarits du projet parent. (Seuls les gabarits parents dont l'option partage avec l'arborescence du projet est cochée sont listés.)" 31 | label_inherited_templates: "Gabarit hérité" 32 | no_issue_templates_for_this_project: "Aucun gabarit de demande n'est défini pour ce projet." 33 | link_to_index_edit_template: "Liste de gabarits de demandes / éditer gabarit" 34 | erase_issue_subject_and_description: "Enlever le sujet et le texte descriptif" 35 | unused_tracker_at_this_project: "NOTE: Ce tracker n'est pas défini pour ce projet. Veuillez redéfinir les réglages du gabarit si nécessaire." 36 | label_enabledshaing_help_message: "Si vrai, ce gabarit peut être partagé avec les sous-projets. (Vous devez également activer l'option Gabarit hérité dans le projet enfant.)" 37 | label_should_replaced: "Remplacer le sujet et la description" 38 | label_should_replaced_help_message: "Si vrai, les champs sujet et description sont effacés et remplacés par le texte du gabarit. (Par défaut à Faux et le texte en annexe.)" 39 | global_issue_templates: "Modèle de gabarit de demande" 40 | no_issue_templates_for_this_redmine: "Aucun modèle de gabarit de demande n'est défini pour ce redmine." 41 | only_admin_can_associate_global_template: "Seul l'administrateur peut associer un modèle de gabarit avec ce projet." 42 | text_no_tracker_enabled_for_global: "Aucun tracker n'est encore défini.\nVeuillez les définir en premier car les gabarits sont assignés à des trackers." 43 | -------------------------------------------------------------------------------- /config/locales/it.yml: -------------------------------------------------------------------------------- 1 | # Italian strings go here for Rails i18n 2 | it: 3 | issue_templates: Modelli segnalazioni 4 | issue_template: Modello segnalazione 5 | issue_template_note: Note 6 | label_enabled: Attivato 7 | label_help_message: Info sui modelli 8 | label_show_help_message: Mostra messaggio d'aiuto durante la creazione o modifica delle segnalazioni. 9 | about_help_message: Ogni progetto può avere messaggi d'aiuto personalizzati per i modelli. 10 | close_help: Chiudi messaggio d'aiuto. 11 | about_template_help_message: Puoi vedere le istruzioni per i modelli di segnalazione di questo progetto. 12 | label_enabled_help_message: Abilita questo modello. Se vuoi salvarlo come bozza, disattiva questa opzione. 13 | label_list_templates: "Elenco modelli" 14 | label_new_templates: "Nuovo modello" 15 | issue_template_name: "Nome modello" 16 | issue_description: "Testo della segnalazione" 17 | issue_title: "Titolo segnalazione" 18 | label_applied_for_issue: "Campi applicati per la segnalazione: " 19 | help_for_issue_title: "Se il titolo della Segnalazione è definito, sarà applicato all'Oggetto della segnalazione quando si applica questo modello." 20 | help_for_this_field: "Aiuto per questo campo." 21 | permission_manage_issue_templates: "Gestisci Modelli" 22 | permission_edit_issue_templates: "Modifica Modelli" 23 | permission_show_issue_templates: "Mostra Modelli" 24 | project_module_issue_templates: "Modelli Segnalazioni" 25 | label_isdefault_help_message: "Usa questo come modello predefinito per le segnalazioni del tracker." 26 | defaulf_template_loaded: "Caricato modello predefinito nella descrizione. (Tracker: %{tracker})" 27 | text_no_tracker_enabled: "Nessun tracker è stato configurato per questo progetto.\nPer favore configurali prima perché i modelli segnalazione sono assegnati ai tracker. " 28 | label_enabled_sharing: "Abilita condivisione con gerarchia progetto." 29 | label_inherit_templates: "Eredita modelli segnalazioni" 30 | label_inherit_templates_help_message: "Eredita modelli da progetto padre. (Only parent's templates are listed which marked as enabled sharing with project tree.)" 31 | label_inherited_templates: "Modelli ereditati" 32 | no_issue_templates_for_this_project: "Nessun modello di segnalazione è definito per questo progetto." 33 | link_to_index_edit_template: "Elenco modelli segnalazione / modifica modelli" 34 | erase_issue_subject_and_description: "Pulisci oggetto e descrizione." 35 | unused_tracker_at_this_project: "NOTA: Questo tracker non è abilitato per questo progetto. Per favore modifica le impostazioni del modello se necessario." 36 | label_enabledshaing_help_message: "Se abilitato, questo modello può essere condiviso con i sottoprogetti. (Devi anche abilitare nel sottoprogetto l'opzione Eredita modelli segnalazioni.)" 37 | label_should_replaced: "Sostituisci oggetto e descrizione" 38 | label_should_replaced_help_message: "Se abilitato, l'oggetto e la descrizione esistenti saranno sostituiti con il testo del modello.(Normalmente disattivato, i testi saranno accodati.)" 39 | global_issue_templates: "Modelli Segnalazioni Globali" 40 | no_issue_templates_for_this_redmine: "Nessun modello globale di segnalazione è definito per questo sito Redmine." 41 | only_admin_can_associate_global_template: "Solo un amministratore di Redmine può associare modelli globali a questo progetto." 42 | text_no_tracker_enabled_for_global: "Nessun tracker definito\nÈ necessario configurarli prima perché i Modelli segnalazione sono assegnati ai tracker." 43 | display_and_filter_issue_templates_in_dialog: "Filtra modelli" 44 | label_filter_template: "Filtra modelli" 45 | -------------------------------------------------------------------------------- /config/locales/pl.yml: -------------------------------------------------------------------------------- 1 | # Polish strings go here for Rails i18n 2 | pl: 3 | issue_templates: Szablony zagadnień 4 | issue_template: Szablon zagadnienia 5 | issue_template_note: info 6 | label_enabled: Włącz 7 | label_help_message: O szablonach 8 | label_show_help_message: Pokazuj pomoc przy tworzeniu / edycji zagadnień. 9 | about_help_message: Każdy projekt może mieć swoje wiadomości pomocy dla szablonów. 10 | close_help: Zamknij pomoc. 11 | about_template_help_message: Możesz zobaczyć instrukcję dla szablonów zagadnień w tym projekcie. 12 | label_enabled_help_message: Powoduje możliwość korzystania z tego szablonu. Jeśli chcesz, aby szablon był zapisany jako szkic, odznacz to pole. 13 | label_list_templates: "Lista szablonów" 14 | label_new_templates: "Dodaj szablon" 15 | issue_template_name: "Nazwa szablonu" 16 | issue_description: "Treść zagadnienia" 17 | issue_title: "Tytuł zagadnienia" 18 | label_applied_for_issue: "Pola wpisywane do zagadnienia: " 19 | help_for_issue_title: "Jeśli wpisany jest tytuł zagadnienia, to także będzie wpisywane przy wyborze szablonu." 20 | help_for_this_field: "Pomoc dla tego pola." 21 | permission_manage_issue_templates: "Zarządzaj Szablonami" 22 | permission_edit_issue_templates: "Edycja Szablonów" 23 | permission_show_issue_templates: "Pokaż Szablony" 24 | project_module_issue_templates: "Szablony Zagadnień" 25 | label_isdefault_help_message: "Zaznacz aby móc używać tego szablonu jako domyślnego dla danego typu zagadnień." 26 | defaulf_template_loaded: "Wczytano domyślny szablon do opisu zagadnienia. (Typ: %{tracker})" 27 | text_no_tracker_enabled: "Nie skonfigurowano typów zagadnień dla tego projektu.\nProszę je najpierw ustawić, ponieważ szablony są przypisane do typów zagadnień. " 28 | label_enabled_sharing: "Włączone współdzielenie z drzewem podprojektów." 29 | label_inherit_templates: "Dziedzicz szablony" 30 | label_inherit_templates_help_message: "Dziedzicz szablony z projektu nadrzędnego. (Tylko szablony projektu nadrzędnego, które mają włączoną funkcję współdzielenia z drzewem podprojektów są pokazane.)" 31 | label_inherited_templates: "Dziedziczone szablony" 32 | no_issue_templates_for_this_project: "Brak zdefiniowanych szablonów zagadnień dla tego projektu." 33 | link_to_index_edit_template: "Lista szablonów zagadnień / edycja szablonów" 34 | erase_issue_subject_and_description: "Czyść tytuł i tekst w opisie zagadnienia." 35 | unused_tracker_at_this_project: "INFO: Ten typ zagadnienia nie jest włączony dla tego projektu. Jeśli to konieczne proszę ponownie zdefiniować ustawienia projektu." 36 | label_enabledshaing_help_message: "Jeśli włączone, możliwe jest współdzielenie szablonu z podprojektami. (Musisz także włączyć szablony dziedziczone w podprojekcie.)" 37 | label_should_replaced: "Nadpisuj tytuł i opis zagadnienia" 38 | label_should_replaced_help_message: "Jeśli włączone, wpisany wcześniej tytuł i opis zagadnienia będą czyszczone i zastępowane tekstem z szablonu. (Domyślnie wyłączone, tekst jest dopisywany.)" 39 | global_issue_templates: "Globalne Szablony Zagadnień" 40 | no_issue_templates_for_this_redmine: "Brak zdefiniowanych globalnych szablonów zagadnień." 41 | only_admin_can_associate_global_template: "Tylko administrator może przypisywać globalne szablony do projektów." 42 | text_no_tracker_enabled_for_global: "Brak zdefiniowanych typów zagadnień.\nProszę je najpierw ustawić, ponieważ szablony są przypisane do typów zagadnień." 43 | -------------------------------------------------------------------------------- /config/locales/pt.yml: -------------------------------------------------------------------------------- 1 | # Brazilian strings go here for Rails i18n 2 | pt: 3 | issue_templates: Modelos de tarefas 4 | issue_template: Modelo de tarefas 5 | issue_template_note: notas 6 | label_enabled: Habilitado 7 | label_help_message: Sobre modelos 8 | label_show_help_message: Visualizar mensagem de ajuda ao Criar/Atualizar tarefa. 9 | about_help_message: Cada projeto pode customizar mensagem de ajuda para os modelos. 10 | close_help: Fechar mensagem de ajuda. 11 | about_template_help_message: Poder visualizar instruções sobre modelos de tarefas neste projeto. 12 | label_enabled_help_message: Opção para ativar este modelo. Se deseja salvar este modelo como rascunho, desative esta opção. 13 | label_list_templates: "Lista de modelos" 14 | label_new_templates: "Incluir modelo" 15 | issue_template_name: "Nome do modelo" 16 | issue_description: "Corpo da tarefa" 17 | issue_title: "Título da tarefa" 18 | label_applied_for_issue: "Campos aplicados para tarefa: " 19 | help_for_issue_title: "Se definido o título da tarefa, este será aplicado à tarefa ao selecionar o modelo." 20 | help_for_this_field: "Ajuda sobre este campo." 21 | permission_manage_issue_templates: "Gerenciar Modelos" 22 | permission_edit_issue_templates: "Editar Modelos" 23 | permission_show_issue_templates: "Visualizar Modelos" 24 | project_module_issue_templates: "Modelos de Tarefas" 25 | label_isdefault_help_message: "Se esta opção estiver ativa este será o modelo padrão para este tipo de tarefa." 26 | defaulf_template_loaded: "Modelo padrão carregado na área da descrição. (Tipo: %{tracker})" 27 | text_no_tracker_enabled: "Não há tipos configurados para este projeto.\nPor favor configure os tipos pois os modelos são associados a eles. " 28 | label_enabled_sharing: "Habilitar compartilhamento com a árvore de projetos." 29 | label_inherit_templates: "Herdar modelos" 30 | label_inherit_templates_help_message: "Herda modelos do projeto pai. (Apenas os modelos do projeto pai listados são os marcados com compartilhamento habilitado com a árvore de projetos.)" 31 | label_inherited_templates: "Modelos herdados" 32 | no_issue_templates_for_this_project: "Não há modelos de tarefa definidos para este projeto." 33 | link_to_index_edit_template: "Lista de modelos de tarefa / editar modelos" 34 | erase_issue_subject_and_description: "Limpar assunto e descrição." 35 | unused_tracker_at_this_project: "NOTA: Este tipo não está habilitado para uso neste projeto. Por favor redefina as configurações do modelo se necessário." 36 | label_enabledshaing_help_message: "Se habilitado, este modelo pode ser compartilhado com projetos descendentes. (Você também deve ativar a opção de herdar modelos no projeto filho.)" 37 | label_should_replaced: "Substituir assunto e descrição" 38 | label_should_replaced_help_message: "Se habilitado assunto e descrição são excluídos e substituídos pelos textos do modelo. (Por padrão os textos são adicionados.)" 39 | global_issue_templates: "Modelos de Tarefa Globais" 40 | no_issue_templates_for_this_redmine: "Não há modelos de tarefa globais definidos neste redmine." 41 | only_admin_can_associate_global_template: "Somente administradores podem associar modelos globais a este projeto." 42 | text_no_tracker_enabled_for_global: "Nenhum tipo foi definido ainda.\nPor favor defina os tipos, pois os modelos são atribuídos a estes." 43 | -------------------------------------------------------------------------------- /config/locales/sr-YU.yml: -------------------------------------------------------------------------------- 1 | # English strings go here for Rails i18n 2 | sr-YU: 3 | issue_templates: Šabloni problema 4 | issue_template: Šablon problema 5 | issue_template_note: nota 6 | label_enabled: Aktivno 7 | label_help_message: O šablonu 8 | label_show_help_message: Prikaži poruku za pomoć prilikom kreiranja ili ažuriranja problema. 9 | about_help_message: Za svaki projekat se može prilagoditi poruka za pomoć za upotrebu šablona. 10 | close_help: Zatvori poruku za pomoć. 11 | about_template_help_message: Možete videti instrukcije o korišćenju šablona na ovom projektu. 12 | label_enabled_help_message: Aktivira šablon. Ukoliko i dalje razvijate ovaj šablon, ostavite ga deaktiviranim dok nije završen. 13 | label_list_templates: "Lista šablona" 14 | label_new_templates: "Dodaj šablon" 15 | issue_template_name: "Ime šablona" 16 | issue_description: "Opis" 17 | issue_title: "Predmet" 18 | label_applied_for_issue: "Polja problema za koja se šablon odnosi" 19 | help_for_issue_title: "Ukoliko je definisan predmet problema on će takođe biti primenjen prilikom selekcije šablona." 20 | help_for_this_field: "Pomoć za ovo polje" 21 | permission_manage_issue_templates: "Upravljanje šablonima" 22 | permission_edit_issue_templates: "Ažuriraj šablon" 23 | permission_show_issue_templates: "Prikaži šablon" 24 | project_module_issue_templates: "Šabloni problema" 25 | label_isdefault_help_message: "Ukoliko je aktivirano, ovaj šablon će biti podrazumevani (default) za dato praćenje (tracker) problema." 26 | defaulf_template_loaded: "Učitan je podrazumevani šablon za ovo praćenje problema u polje za opis. (Praćenje: %{tracker})" 27 | text_no_tracker_enabled: "Još uvek nisu konfigurisani pratioci (tracker) za ovaj projekat." 28 | label_enabled_sharing: "Omogući deljenje sa stablom projekta" 29 | label_enabledshaing_help_message: "Aktiviraj da bi subprojekti nasledili šablone." 30 | label_inherit_templates: "Nasledi šablon" 31 | label_inherit_templates_help_message: "Nasledi šablon od majke projekta" 32 | label_inherited_templates: "Nasleđeni šabloni" 33 | no_issue_templates_for_this_project: "Nema definisanih šablona za ovaj projekat." 34 | link_to_index_edit_template: "Lista šablona problema / ažuriraj šablone." 35 | erase_issue_subject_and_description: "Obriši naslov i opis" 36 | unused_tracker_at_this_project: "NOTA: Ovo praćenje problema (tracker) se ne koristi u ovom projektu." -------------------------------------------------------------------------------- /config/locales/zh-TW.yml: -------------------------------------------------------------------------------- 1 | # Tranditional Chinese strings go here for Rails i18n 2 | zh-TW: 3 | issue_templates: "問題樣板" 4 | issue_template: "問題樣板" 5 | issue_template_note: "註釋" 6 | label_enabled: "啟用" 7 | label_help_message: "關於樣板" 8 | label_show_help_message: "建立/修改問題時顯示提示訊息。" 9 | about_help_message: "每個專案都可以為這個樣板定義提示訊息。" 10 | close_help: "關閉提示訊息。" 11 | about_template_help_message: "你可以看到當前問題的樣板幫助訊息。" 12 | label_enabled_help_message: "該確認框用以啟用當前的樣板。若你只想把目前的樣板先當成草稿使用,請不要勾選。" 13 | label_list_templates: "樣板列表" 14 | label_new_templates: "新建樣板" 15 | issue_template_name: "樣板名稱" 16 | issue_description: "樣板内容" 17 | issue_title: "問題標題" 18 | label_applied_for_issue: "該問題所使用的欄位內容" 19 | help_for_issue_title: "如果在這邊有設定值,則使用該樣板時回自動帶入。" 20 | help_for_this_field: "本欄位說明。" 21 | permission_manage_issue_templates: "管理樣板" 22 | permission_edit_issue_templates: "編輯樣板" 23 | permission_show_issue_templates: "顯示樣板" 24 | project_module_issue_templates: "問題樣板" 25 | label_isdefault_help_message: "勾選此選項則該問題類型會使用此樣板當作預設值" 26 | defaulf_template_loaded: "載入樣板資訊至概述欄位中(追蹤標籤: %{tracker})" 27 | text_no_tracker_enabled: "本專案的追蹤標簽中沒有任何一個設定了問題樣板。\n 請在使用前先設定他們" 28 | label_enabled_sharing: "在專案樹狀結構中分享" 29 | label_inherit_templates: "繼承樣板" 30 | label_inherit_templates_help_message: " 從父專案繼承問題樣板(父專案中只有那些有勾選分享的才會出現在此處)。" 31 | label_inherited_templates: "繼承樣板" 32 | no_issue_templates_for_this_project: "此專案沒有定義任何的問題樣板" 33 | link_to_index_edit_template: "問題樣板列表/ 編輯樣板" 34 | erase_issue_subject_and_description: "清除主旨以及概述。" 35 | unused_tracker_at_this_project: "備註:這個追蹤標簽並沒有被指派給這個專案使用。必要的話請重新設定樣板" 36 | label_enabledshaing_help_message: "如果設定值為 True,這個樣板就可以分享給子專案使用(你還是得在子專案中把繼承而來的問題樣板啟用)。" 37 | label_should_replaced: "取代標題以及描述" 38 | label_should_replaced_help_message: "如果設定值為 True,當前的標題以及描述會被清除,且被樣板的文字所取代(預設是 False, 把文字附加在後面)。" 39 | global_issue_templates: "全域問題樣板" 40 | no_issue_templates_for_this_redmine: "網站目前沒有全域的問題樣板!" 41 | only_admin_can_associate_global_template: "只有 Redmine 管理員可以在這個專案中關聯全域樣板。" 42 | text_no_tracker_enabled_for_global: "尚未定義追蹤標籤。\n請先分配模板適用的追蹤標籤。" 43 | display_and_filter_issue_templates_in_dialog: "預覽樣板內容" 44 | label_filter_template: "篩選樣板" 45 | label_msg_confirm_to_replace: "確定要取代主旨和說明嗎?" 46 | label_apply_global_template_to_all_projects: "套用全域問題樣板到所有專案。" 47 | note_apply_global_template_to_all_projects_setting_enabled: "基於外掛設定,這個全域問題樣板被套用到所有專案。" 48 | project_list_associated_this_template: "專案列表: (已套用 %{all} 個中的 %{applied} 個)" 49 | note_project_local_template_override_global_template: "如果專案有自己的樣板,覆蓋全域樣板且新增問題時不顯示全域樣板選項。" 50 | help_project_local_template_override_global_template: "當啟用這個選項時,所有的全域問題樣板會被套用到所有專案。如果特定專案有針對每個追蹤標籤設定樣板,它將會覆蓋全域樣板,且新增問題時不會顯示全域樣板。" 51 | warning_project_local_template_override_global_template: "註冊包含機密內容的全域問題樣板時,請特別注意:一旦啟用了這個選項,所有的全域樣板會被套用到所有專案。" 52 | revert_before_applying_template: "還原" 53 | template_remove_confirm: "確定要刪除這個樣板嗎?目前有 %{count} 個子問題使用這個樣板。" 54 | label_number_of_subprojects_use_this_template: "%{count} 個子專案使用這個樣板。" 55 | enabled_template_cannot_destroy: "只有已停用的樣板可以被刪除。請先確認其他子問題是否有使用這個樣板,如果沒有則可以先停用它再來刪除。" 56 | orphaned_templates: "追蹤標籤中被孤立的樣板" 57 | orphaned_template: "追蹤標籤中被孤立的樣板" 58 | label_template_applied: "問題樣板已套用。你可以按「還原」連結來還原。" 59 | label_hide_confirm_dialog_in_the_future: "未來直接覆寫,不再詢問。" 60 | label_template_for_note: "筆記樣板" 61 | label_use_template_when_edit: "編輯問題時使用樣板" 62 | note_template: "筆記樣板" 63 | no_note_templates_for_this_project: "這個專案沒有任何回應樣板。" 64 | label_memo_help_message: "請建立一個筆記來說明為何套用這個樣板。" 65 | note_template_name: "筆記樣板名稱" 66 | note_description: "回應內容" 67 | field_template_visibility: 樣板可見度 68 | note_templates: 69 | visibility: 70 | mine: 只有我 71 | roles: 只有這些角色 72 | open: 任何人 73 | please_select_at_least_one_role: "請至少選擇一個角色。" 74 | -------------------------------------------------------------------------------- /config/locales/zh.yml: -------------------------------------------------------------------------------- 1 | # Simplified Chinese strings go here for Rails i18n, based on file ja.yml 2 | zh: 3 | issue_templates: ISSUE模板管理 4 | issue_template: ISSUE模板管理 5 | issue_template_note: 批注 6 | label_enabled: 启用 7 | label_help_message: 关于ISSUE模板 8 | label_show_help_message: 当创建/修改问题时显示对应的帮助信息。 9 | about_help_message: 每个项目都可以自定义ISSUE模板帮助信息。 10 | close_help: 关闭帮助信息。 11 | about_template_help_message: 您可以查看当前项目ISSUE模板的帮助指南。 12 | label_enabled_help_message: 该复选框用于激活当前ISSUE模板。若您只想将ISSUE模板保存为草稿,请取消该复选框的勾选。 13 | label_list_templates: "ISSUE模板列表" 14 | label_new_templates: "新建ISSUE模板" 15 | issue_template_name: "ISSUE模板名称" 16 | issue_description: "ISSUE模板内容" 17 | issue_title: "ISSUE主题" 18 | label_applied_for_issue: "应用于该ISSUE的字段:" 19 | help_for_issue_title: "若在ISSUE模板中已定义了主题,则在使用该ISSUE模板时,将自动替换为对应的主题信息。" 20 | help_for_this_field: "当前字段帮助信息" 21 | permission_manage_issue_templates: "管理ISSUE模板" 22 | permission_edit_issue_templates: "编辑ISSUE模板" 23 | permission_show_issue_templates: "显示ISSUE模板" 24 | project_module_issue_templates: "ISSUE模板管理" 25 | label_isdefault_help_message: "勾选后,设置当前ISSUE模板为指定跟踪类型对应的默认模板。" 26 | defaulf_template_loaded: "在描述区域内加载模板默认信息。 (跟踪类型:%{tracker})" 27 | text_no_tracker_enabled: "当前项目中尚未配置跟踪类型。\n请在使用ISSUE模板前,先配置项目跟踪类型。" 28 | label_enabled_sharing: "启用向下继承(在当前项目的子项目中使用该ISSUE模板)" 29 | label_inherit_templates: "继承ISSUE模板" 30 | label_inherit_templates_help_message: "从父项目中继承ISSUE模板。(仅限在父项目中使用,且标记为向下继承的ISSUE模板。)" 31 | label_inherited_templates: "继承ISSUE模板" 32 | no_issue_templates_for_this_project: "当前项目中未定义ISSUE模板信息。" 33 | link_to_index_edit_template: "ISSUE模板列表 / 编辑ISSUE模板" 34 | erase_issue_subject_and_description: "点击清除主题及描述信息。" 35 | unused_tracker_at_this_project: "注意:当前项目中未定义可用的跟踪类型。请根据实际需要,重新配置ISSUE模板。" 36 | label_enabledshaing_help_message: "若选中,则当前ISSUE模板可以向下继承,与子项目共享使用。(您需在子项目中激活ISSUE模板继承选项便可使用。)" 37 | label_should_replaced: "替换主题及描述信息" 38 | label_should_replaced_help_message: "若选中,现有主题及描述信息将被自动清除,并替换为ISSUE模板中定义的内容。(默认不选中,则在现有内容后扩展模板内定义的信息。)" 39 | global_issue_templates: "全局ISSUE模板管理" 40 | no_issue_templates_for_this_redmine: "当前未定义全局ISSUE模板。请联系Redmine系统管理员。" 41 | only_admin_can_associate_global_template: "只有Redmine系统管理员才能在当前项目中关联全局ISSUE模板。" 42 | text_no_tracker_enabled_for_global: "未指定跟踪类型。\n在使用ISSUE模板前,请先指定模板适用的跟踪类型。" 43 | display_and_filter_issue_templates_in_dialog: "模板过滤器" 44 | label_filter_template: "模板过滤器" 45 | label_msg_confirm_to_replace: "确认替换主题及描述信息?" 46 | label_apply_global_template_to_all_projects: "将全局问题模板应用于全部项目。" 47 | note_apply_global_template_to_all_projects_setting_enabled: "该全局问题模板通过插件设置配置后,应用于所有项目。" 48 | project_list_associated_this_template: "项目列表: (已应用于全部 %{all} 个项目中的 %{applied} 个)" 49 | note_project_local_template_override_global_template: "如果存在项目本地模板,则覆盖全局模板,且在该项目内创建问题时,不显示任何全局模板。" 50 | help_project_local_template_override_global_template: "如果启用此选项,则问题的全局模板将应用于所有项目。 如果设置了每个跟踪器的项目特定模板,则会覆盖对应的全局模板,并在创建新问题时,隐藏全局模板。" 51 | warning_project_local_template_override_global_template: "注册全局问题模板时,务必注意模板信息中的特定项目的机密信息。 一旦启用此选项,则所有全局模板将应用于所有项目。" 52 | revert_before_applying_template: "回退" 53 | template_remove_confirm: "您确定要删除此模板吗? 当前有 %{count} 个项目正在使用此模板。" 54 | label_number_of_subprojects_use_this_template: "%{count} 个项目正在使用此模板 " 55 | enabled_template_cannot_destroy: "已启用的模板不能删除。检查您正在使用的模板是否在其他项目中存在,并在删除之前将其禁用。" 56 | orphaned_templates: "基于跟踪的个性化模板" 57 | orphaned_template: "基于跟踪的个性化模板" 58 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | # 2 | # TODO: Clean up routing. 3 | # 4 | Rails.application.routes.draw do 5 | concern :tamplate_common do 6 | get 'orphaned_templates', on: :collection 7 | end 8 | 9 | concern :previewable do 10 | post 'preview', on: :collection 11 | end 12 | 13 | resources :global_issue_templates, except: [:edit], concerns: %i[tamplate_common previewable] 14 | 15 | # for project issue template 16 | resources :projects, only: [] do 17 | resources :issue_templates, except: [:edit], concerns: [:tamplate_common] do 18 | post 'set_pulldown', on: :collection 19 | get 'list_templates', on: :collection 20 | end 21 | 22 | resources :issue_templates_settings, only: [:edit], concerns: [:previewable] do 23 | patch 'edit', on: :collection 24 | end 25 | 26 | get 'issue_templates_settings', to: 'issue_templates_settings#index' 27 | 28 | resources :note_templates, except: [:edit] 29 | end 30 | 31 | resources :issue_templates, only: %i[load preview load_selectable_fields], concerns: [:previewable] do 32 | post 'load', on: :collection 33 | get 'load_selectable_fields', on: :collection 34 | end 35 | 36 | # for note temlate 37 | resources :note_templates, only: %i[load preview list_templates] do 38 | post 'load', on: :collection 39 | get 'list_templates', on: :collection 40 | end 41 | 42 | # for global note temlate 43 | resources :global_note_templates, except: [:edit], concerns: %i[previewable] 44 | end 45 | -------------------------------------------------------------------------------- /db/migrate/0001_create_issue_templates.rb: -------------------------------------------------------------------------------- 1 | class CreateIssueTemplates < ActiveRecord::Migration[4.2] 2 | def self.up 3 | create_table :issue_templates do |t| 4 | t.column :title, :string, null: false 5 | t.column :project_id, :integer 6 | t.column :tracker_id, :integer, null: false 7 | t.column :author_id, :integer, null: false 8 | t.column :note, :string 9 | t.column :description, :text 10 | t.column :enabled, :boolean 11 | t.column :created_on, :timestamp 12 | t.column :updated_on, :timestamp 13 | end 14 | add_index :issue_templates, :author_id 15 | add_index :issue_templates, :project_id 16 | add_index :issue_templates, :tracker_id 17 | end 18 | 19 | def self.down 20 | remove_index :issue_templates, :author_id 21 | remove_index :issue_templates, :project_id 22 | remove_index :issue_templates, :tracker_id 23 | drop_table :issue_templates 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /db/migrate/0002_create_issue_template_settings.rb: -------------------------------------------------------------------------------- 1 | class CreateIssueTemplateSettings < ActiveRecord::Migration[4.2] 2 | def self.up 3 | create_table :issue_template_settings do |t| 4 | t.column :project_id, :integer 5 | 6 | t.column :help_message, :text 7 | 8 | t.column :enabled, :boolean 9 | end 10 | end 11 | 12 | def self.down 13 | drop_table :issue_template_settings 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/0003_add_issue_title_to_issue_templates.rb: -------------------------------------------------------------------------------- 1 | class AddIssueTitleToIssueTemplates < ActiveRecord::Migration[4.2] 2 | def self.up 3 | add_column :issue_templates, :issue_title, :string 4 | 5 | IssueTemplate.reset_column_information 6 | issue_templates = IssueTemplate.all 7 | issue_templates.each do |t| 8 | t.issue_title = t.title 9 | t.save 10 | end 11 | end 12 | 13 | def self.down 14 | remove_column :issue_templates, :issue_title 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/0004_add_position_to_issue_templates.rb: -------------------------------------------------------------------------------- 1 | class AddPositionToIssueTemplates < ActiveRecord::Migration[4.2] 2 | def self.up 3 | add_column :issue_templates, :position, :integer, default: 1 4 | 5 | IssueTemplate.reset_column_information 6 | 7 | issue_templates = IssueTemplate.all 8 | say_with_time('Update each template to set default position.') do 9 | issue_templates.each_with_index { |t, i| t.update_attribute(:position, i + 1) } 10 | end 11 | end 12 | 13 | def self.down 14 | remove_column :issue_templates, :position 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20121208150810_add_is_default_to_issue_templates.rb: -------------------------------------------------------------------------------- 1 | class AddIsDefaultToIssueTemplates < ActiveRecord::Migration[4.2] 2 | def self.up 3 | add_column :issue_templates, :is_default, :boolean, default: false 4 | end 5 | 6 | def self.down 7 | remove_column :issue_templates, :is_default 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20130630141710_add_enabled_sharing_to_issue_templates.rb: -------------------------------------------------------------------------------- 1 | class AddEnabledSharingToIssueTemplates < ActiveRecord::Migration[4.2] 2 | def self.up 3 | add_column :issue_templates, :enabled_sharing, :boolean, default: false 4 | end 5 | 6 | def self.down 7 | remove_column :issue_templates, :enabled_sharing 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20130701024625_add_inherit_templates_to_issue_template_settings.rb: -------------------------------------------------------------------------------- 1 | class AddInheritTemplatesToIssueTemplateSettings < ActiveRecord::Migration[4.2] 2 | def self.up 3 | add_column :issue_template_settings, :inherit_templates, :boolean, default: false, null: false 4 | end 5 | 6 | def self.down 7 | remove_column :issue_template_settings, :inherit_templates 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/2014020191500_add_should_replaced_to_issue_template_settings.rb: -------------------------------------------------------------------------------- 1 | class AddShouldReplacedToIssueTemplateSettings < ActiveRecord::Migration[4.2] 2 | def self.up 3 | add_column :issue_template_settings, :should_replaced, :boolean, default: false 4 | end 5 | 6 | def self.down 7 | remove_column :issue_template_settings, :should_replaced 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20140307024626_create_global_issue_templates.rb: -------------------------------------------------------------------------------- 1 | class CreateGlobalIssueTemplates < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :global_issue_templates do |t| 4 | t.string :title 5 | t.string :issue_title 6 | t.integer :tracker_id 7 | t.integer :author_id 8 | t.string :note 9 | t.text :description 10 | t.boolean :enabled 11 | t.integer :position 12 | t.boolean :is_default 13 | t.timestamp :created_on 14 | t.timestamp :updated_on 15 | end 16 | add_index :global_issue_templates, :author_id 17 | add_index :global_issue_templates, :tracker_id 18 | end 19 | 20 | def self.down 21 | remove_index :global_issue_templates, :author_id 22 | remove_index :global_issue_templates, :tracker_id 23 | drop_table :global_issue_templates 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /db/migrate/20140312054531_create_global_issue_templates_projects.rb: -------------------------------------------------------------------------------- 1 | class CreateGlobalIssueTemplatesProjects < ActiveRecord::Migration[4.2] 2 | def self.up 3 | create_table :global_issue_templates_projects, id: false do |t| 4 | t.integer :project_id 5 | t.integer :global_issue_template_id 6 | end 7 | end 8 | 9 | def self.down 10 | drop_table :global_issue_templates_projects 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20140330155030_remove_is_default_from_global_issue_templates.rb: -------------------------------------------------------------------------------- 1 | class RemoveIsDefaultFromGlobalIssueTemplates < ActiveRecord::Migration[4.2] 2 | def self.up 3 | remove_column :global_issue_templates, :is_default 4 | end 5 | 6 | def self.down 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20160727222420_add_checklist_json_to_issue_templates.rb: -------------------------------------------------------------------------------- 1 | class AddChecklistJsonToIssueTemplates < ActiveRecord::Migration[4.2] 2 | def self.up 3 | add_column :issue_templates, :checklist_json, :text 4 | end 5 | 6 | def self.down 7 | remove_column :issue_templates, :checklist_json 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160828190000_add_checklist_json_to_global_issue_templates.rb: -------------------------------------------------------------------------------- 1 | class AddChecklistJsonToGlobalIssueTemplates < ActiveRecord::Migration[4.2] 2 | def self.up 3 | add_column :global_issue_templates, :checklist_json, :text 4 | end 5 | 6 | def self.down 7 | remove_column :global_issue_templates, :checklist_json 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160829001500_change_issue_template_enabled_column.rb: -------------------------------------------------------------------------------- 1 | class ChangeIssueTemplateEnabledColumn < ActiveRecord::Migration[4.2] 2 | def self.up 3 | change_column :issue_templates, :enabled, :boolean, default: false, null: false 4 | end 5 | 6 | def self.down 7 | change_column :issue_templates, :enabled, :boolean 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160829001530_change_global_issue_template_enabled_column.rb: -------------------------------------------------------------------------------- 1 | class ChangeGlobalIssueTemplateEnabledColumn < ActiveRecord::Migration[4.2] 2 | def self.up 3 | change_column :global_issue_templates, :enabled, :boolean, default: false, null: false 4 | end 5 | 6 | def self.down 7 | change_column :global_issue_templates, :enabled, :boolean 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20170317082100_add_is_default_to_global_issue_templates.rb: -------------------------------------------------------------------------------- 1 | class AddIsDefaultToGlobalIssueTemplates < ActiveRecord::Migration[4.2] 2 | def self.up 3 | add_column :global_issue_templates, :is_default, :boolean, default: false, null: false 4 | end 5 | 6 | def self.down 7 | remove_column :global_issue_templates, :is_default 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20181104065200_add_unique_key_to_global_issue_templates_projects.rb: -------------------------------------------------------------------------------- 1 | class AddUniqueKeyToGlobalIssueTemplatesProjects < ActiveRecord::Migration[4.2] 2 | def self.up 3 | add_index :global_issue_templates_projects, 4 | [:project_id, :global_issue_template_id], unique: true, 5 | name: 'projects_global_issue_templates' 6 | end 7 | 8 | def self.down 9 | remove_index :global_issue_templates_projects, name: 'projects_global_issue_templates' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20190303082102_create_note_templates.rb: -------------------------------------------------------------------------------- 1 | class CreateNoteTemplates < ActiveRecord::Migration[5.1] 2 | def up 3 | create_table :note_templates do |t| 4 | t.string :name 5 | t.string :description 6 | t.string :memo 7 | t.integer :project_id 8 | t.integer :tracker_id 9 | t.integer :author_id 10 | t.boolean :enabled 11 | t.integer :position 12 | t.timestamps 13 | end 14 | add_index :note_templates, :author_id 15 | add_index :note_templates, :project_id 16 | add_index :note_templates, :tracker_id 17 | add_index :note_templates, :enabled 18 | end 19 | 20 | def down 21 | remove_index :note_templates, :author_id 22 | remove_index :note_templates, :project_id 23 | remove_index :note_templates, :tracker_id 24 | remove_index :note_templates, :enabled 25 | drop_table :note_templates 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /db/migrate/20190714171020_create_note_visible_roles.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateNoteVisibleRoles < ActiveRecord::Migration[5.1] 4 | def up 5 | create_table :note_visible_roles do |t| 6 | t.integer :note_template_id 7 | t.integer :role_id 8 | t.timestamps 9 | end 10 | add_index :note_visible_roles, :note_template_id 11 | add_index :note_visible_roles, :role_id 12 | end 13 | 14 | def down 15 | remove_index :note_visible_roles, :role_id 16 | remove_index :note_visible_roles, :note_template_id 17 | drop_table :note_visible_roles 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /db/migrate/20190714211530_add_visibility_to_note_templates.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddVisibilityToNoteTemplates < ActiveRecord::Migration[5.1] 4 | def self.up 5 | add_column :note_templates, :visibility, :integer, default: 2 6 | end 7 | 8 | def self.down 9 | remove_column :note_templates, :visibility 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200101204020_add_related_link_to_issue_templates.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddRelatedLinkToIssueTemplates < ActiveRecord::Migration[5.2] 4 | def self.up 5 | add_column :issue_templates, :related_link, :text 6 | end 7 | 8 | def self.down 9 | remove_column :issue_templates, :related_link 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200101204220_add_related_link_to_global_issue_templates.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddRelatedLinkToGlobalIssueTemplates < ActiveRecord::Migration[5.2] 4 | def self.up 5 | add_column :global_issue_templates, :related_link, :text 6 | end 7 | 8 | def self.down 9 | remove_column :global_issue_templates, :related_link 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200102204815_add_link_title_to_issue_templates.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddLinkTitleToIssueTemplates < ActiveRecord::Migration[5.2] 4 | def self.up 5 | add_column :issue_templates, :link_title, :text 6 | end 7 | 8 | def self.down 9 | remove_column :issue_templates, :link_title 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200102205044_add_link_title_to_global_issue_templates.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddLinkTitleToGlobalIssueTemplates < ActiveRecord::Migration[5.2] 4 | def self.up 5 | add_column :global_issue_templates, :link_title, :text 6 | end 7 | 8 | def self.down 9 | remove_column :global_issue_templates, :link_title 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200103213630_add_builtin_fields_json_to_issue_templates.rb: -------------------------------------------------------------------------------- 1 | class AddBuiltinFieldsJsonToIssueTemplates < ActiveRecord::Migration[5.2] 2 | def self.up 3 | add_column :issue_templates, :builtin_fields_json, :text 4 | end 5 | 6 | def self.down 7 | remove_column :issue_templates, :builtin_fields_json 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20200115073600_add_builtin_fields_json_to_global_issue_templates.rb: -------------------------------------------------------------------------------- 1 | class AddBuiltinFieldsJsonToGlobalIssueTemplates < ActiveRecord::Migration[5.2] 2 | def self.up 3 | add_column :global_issue_templates, :builtin_fields_json, :text 4 | end 5 | 6 | def self.down 7 | remove_column :global_issue_templates, :builtin_fields_json 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20200314132500_change_column_note_template_description.rb: -------------------------------------------------------------------------------- 1 | class ChangeColumnNoteTemplateDescription < ActiveRecord::Migration[5.2] 2 | def self.up 3 | change_column :note_templates, :description, :text 4 | end 5 | 6 | def down 7 | change_column :note_templates, :description, :string 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20200405115700_create_global_note_templates.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateGlobalNoteTemplates < ActiveRecord::Migration[5.1] 4 | def up 5 | create_table :global_note_templates do |t| 6 | t.string :name 7 | t.text :description 8 | t.string :memo 9 | t.integer :tracker_id 10 | t.integer :author_id 11 | t.boolean :enabled 12 | t.integer :position 13 | t.integer :visibility, default: 2 14 | t.timestamps 15 | end 16 | add_index :global_note_templates, :author_id 17 | add_index :global_note_templates, :tracker_id 18 | add_index :global_note_templates, :enabled 19 | end 20 | 21 | def down 22 | remove_index :global_note_templates, :author_id 23 | remove_index :global_note_templates, :tracker_id 24 | remove_index :global_note_templates, :enabled 25 | drop_table :global_note_templates 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /db/migrate/20200405120700_create_global_note_visible_roles.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateGlobalNoteVisibleRoles < ActiveRecord::Migration[5.1] 4 | def up 5 | create_table :global_note_visible_roles do |t| 6 | t.integer :global_note_template_id 7 | t.integer :role_id 8 | t.timestamps 9 | end 10 | add_index :global_note_visible_roles, :global_note_template_id 11 | add_index :global_note_visible_roles, :role_id 12 | end 13 | 14 | def down 15 | remove_index :global_note_visible_roles, :role_id 16 | remove_index :global_note_visible_roles, :global_note_template_id 17 | drop_table :global_note_visible_roles 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /db/migrate/20200418114157_create_join_table_global_note_template_project.rb: -------------------------------------------------------------------------------- 1 | class CreateJoinTableGlobalNoteTemplateProject < ActiveRecord::Migration[5.2] 2 | def change 3 | create_join_table :global_note_templates, :projects, table_name: :global_note_template_projects do |t| 4 | # t.index [:global_note_template_id, :project_id] 5 | # t.index [:project_id, :global_note_template_id] 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20230330055341_change_global_note_template_projects_table_name.rb: -------------------------------------------------------------------------------- 1 | class ChangeGlobalNoteTemplateProjectsTableName < ActiveRecord::Migration[5.2] 2 | def up 3 | rename_table :global_note_template_projects, :global_note_templates_projects 4 | end 5 | 6 | def down 7 | rename_table :global_note_templates_projects, :global_note_template_projects 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.2' 2 | services: 3 | # start service for redmine with plugin 4 | # 1. $ docker-compose build --force-rm --no-cache 5 | # 2. $ docker-compose up -d web 6 | # 7 | # 8 | web: 9 | build: 10 | context: . 11 | image: redmine_sqlite3 12 | container_name: redmine_sqlite3 13 | command: > 14 | bash -c "bundle && 15 | bundle exec rake db:migrate && 16 | bundle exec rake redmine:plugins:migrate && 17 | bundle exec rake generate_secret_token && 18 | bundle exec rails s -p 3000 -b '0.0.0.0'" 19 | environment: 20 | RAILS_ENV: development 21 | volumes: 22 | - .:/tmp/redmine/plugins/redmine_issue_templates 23 | - ./.data:/tmp/data 24 | ports: 25 | - "3000:3000" 26 | mysql: 27 | image: mysql 28 | environment: 29 | MYSQL_ROOT_PASSWORD: pasword 30 | ports: 31 | - "3306:3306" 32 | -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Redmine Issue Template Plugin 4 | # 5 | # This is a plugin for Redmine to generate and use issue templates 6 | # for each project to assist issue creation. 7 | # Created by Akiko Takano. 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the GNU General Public License 11 | # as published by the Free Software Foundation; either version 2 12 | # of the License, or (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 | require File.expand_path('lib/redmine') 23 | 24 | # NOTE: Keep error message for a while to support Redmine3.x users. 25 | def issue_template_version_message(original_message = nil) 26 | <<-"USAGE" 27 | 28 | ========================== 29 | #{original_message} 30 | 31 | If you use Redmine3.x, please use Redmine Issue Templates version 0.2.x or clone via 32 | 'v0.2.x-support-Redmine3' branch. 33 | You can download older version from here: https://github.com/akiko-pusu/redmine_issue_templates/releases 34 | ========================== 35 | USAGE 36 | end 37 | 38 | def template_menu_allowed? 39 | proc { |p| User.current.allowed_to?({ controller: 'issue_templates', action: 'show' }, p) } 40 | end 41 | 42 | Redmine::Plugin.register :redmine_issue_templates do 43 | begin 44 | name 'Redmine Issue Templates plugin' 45 | author 'Agileware Inc.' 46 | description 'Plugin to generate and use issue templates for each project to assist issue creation.' 47 | version '1.2.1' 48 | author_url 'https://agileware.jp/' 49 | requires_redmine version_or_higher: '4.0' 50 | url 'https://github.com/agileware-jp/redmine_issue_templates' 51 | 52 | settings partial: 'settings/redmine_issue_templates', 53 | default: { 54 | apply_global_template_to_all_projects: 'false', 55 | apply_template_when_edit_issue: 'false', 56 | enable_builtin_fields: 'false' 57 | } 58 | 59 | menu :admin_menu, :redmine_issue_templates, { controller: 'global_issue_templates', action: 'index' }, 60 | caption: :global_issue_templates, html: { class: 'icon icon-global_issue_templates' } 61 | 62 | menu :project_menu, :issue_templates, { controller: 'issue_templates', action: 'index' }, 63 | caption: :issue_templates, param: :project_id, 64 | after: :settings, if: template_menu_allowed? 65 | 66 | project_module :issue_templates do 67 | permission :edit_issue_templates, issue_templates: %i[new create edit update destroy move], note_templates: %i[new create edit update destroy move] 68 | permission :show_issue_templates, issue_templates: %i[index show load set_pulldown list_templates orphaned_templates], 69 | note_templates: %i[index show load list_templates] 70 | permission :manage_issue_templates, { issue_templates_settings: %i[index edit] }, require: :member 71 | end 72 | rescue ::Redmine::PluginRequirementError => e 73 | raise ::Redmine::PluginRequirementError.new(issue_template_version_message(e.message)) # rubocop:disable Style/RaiseArgs 74 | end 75 | end 76 | 77 | Rails.application.config.after_initialize do 78 | require File.expand_path('../lib/issue_templates/issues_hook', __FILE__) 79 | require File.expand_path('../lib/issue_templates/journals_hook', __FILE__) 80 | end 81 | -------------------------------------------------------------------------------- /lib/issue_templates/issues_hook.rb: -------------------------------------------------------------------------------- 1 | # To change this template, choose Tools | Templates 2 | # and open the template in the editor. 3 | module IssueTemplates 4 | class IssuesHook < Redmine::Hook::ViewListener 5 | include IssuesHelper 6 | 7 | CONTROLLERS = %( 8 | 'IssuesController' 'IssueTemplatesController' 'ProjectsController' 'IssueTemplatesSettingsController' 9 | 'GlobalIssueTemplatesController' 'SettingsController' 'NoteTemplatesController' 10 | 'GlobalNoteTemplatesController' 11 | ).freeze 12 | 13 | ACTIONS = %('new' 'update_form' 'create', 'show').freeze 14 | 15 | def view_layouts_base_html_head(context = {}) 16 | o = stylesheet_link_tag('issue_templates', plugin: 'redmine_issue_templates') 17 | o << redmine_issue_template_javascript_include_tag if need_template_js?(context[:controller]) 18 | o 19 | end 20 | 21 | def view_issues_form_details_top(context = {}) 22 | issue = context[:issue] 23 | parameters = context[:request].parameters 24 | return if existing_issue?(issue) 25 | return if copied_issue?(parameters) 26 | 27 | project = context[:project] 28 | project_id = issue.project_id.present? ? issue.project_id : project.id 29 | return unless create_action?(parameters[:action]) && project_id.present? 30 | 31 | context[:controller].send( 32 | :render_to_string, 33 | partial: 'issue_templates/issue_select_form', 34 | locals: locals_params(issue, project_id, parameters[:form_update_triggered_by]) 35 | ) 36 | end 37 | 38 | render_on :view_issues_sidebar_planning_bottom, partial: 'issue_templates/issue_template_link' 39 | 40 | private 41 | 42 | def existing_issue?(issue) 43 | return false if apply_template_when_edit_issue? 44 | 45 | issue.id.present? || issue.tracker_id.blank? 46 | end 47 | 48 | def copied_issue?(parameters) 49 | return false if apply_template_when_edit_issue? 50 | 51 | copy_from = parameters[:copy_from] 52 | copy_from.present? 53 | end 54 | 55 | def create_action?(action) 56 | return true if apply_template_when_edit_issue? 57 | 58 | ACTIONS.include?(action) 59 | end 60 | 61 | def setting(project_id) 62 | IssueTemplateSetting.find_or_create(project_id) 63 | end 64 | 65 | def need_template_js?(controller) 66 | CONTROLLERS.include?(controller.class.name) 67 | end 68 | 69 | def plugin_setting 70 | Setting.plugin_redmine_issue_templates 71 | end 72 | 73 | def apply_all_projects? 74 | plugin_setting['apply_global_template_to_all_projects'].to_s == 'true' 75 | end 76 | 77 | def apply_template_when_edit_issue? 78 | plugin_setting['apply_template_when_edit_issue'].to_s == 'true' 79 | end 80 | 81 | def locals_params(issue, project_id, is_triggered_by) 82 | { setting: setting(project_id), 83 | issue: issue, 84 | is_triggered_by: is_triggered_by, 85 | project_id: project_id, 86 | pulldown_url: pulldown_url(issue, project_id, is_triggered_by) } 87 | end 88 | 89 | def pulldown_url(issue, project_id, is_triggered_by) 90 | pulldown_url = if issue.try(:id).present? 91 | url_for(controller: 'issue_templates', 92 | action: 'set_pulldown', project_id: project_id, is_triggered_by: is_triggered_by, 93 | is_update_issue: issue.try(:id).present?) 94 | else 95 | url_for(controller: 'issue_templates', 96 | action: 'set_pulldown', project_id: project_id, is_triggered_by: is_triggered_by) 97 | end 98 | pulldown_url 99 | end 100 | 101 | def redmine_issue_template_javascript_include_tag 102 | if ENV['REDMINE_ISSUE_TEMPLATE_VITE_SERVE_URL'].present? 103 | source = "#{ENV['REDMINE_ISSUE_TEMPLATE_VITE_SERVE_URL']}/scripts/issue_templates.js" 104 | javascript_include_tag(source, type: :module) 105 | else 106 | javascript_include_tag(:issue_templates, plugin: :redmine_issue_templates, type: :module) 107 | end 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /lib/issue_templates/journals_hook.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # To change this template, choose Tools | Templates 4 | # and open the template in the editor. 5 | module IssueTemplates 6 | class JournalsHook < Redmine::Hook::ViewListener 7 | def view_journals_notes_form_after_notes(context = {}) 8 | journal = context[:journal] 9 | issue = journal.issue 10 | tracker_id = issue.try(:tracker_id) 11 | templates = target_templates(context, tracker_id) 12 | global_templates = global_note_templates(context, tracker_id) 13 | return if templates.empty? && global_templates.empty? 14 | 15 | context[:controller].send( 16 | :render_to_string, 17 | partial: 'issue_templates/note_form', locals: { type: 'template_edit_journal', templates: templates, issue: issue } 18 | ) 19 | end 20 | 21 | # Add journal with edit issue 22 | def view_issues_edit_notes_bottom(context = {}) 23 | issue = context[:issue] 24 | tracker_id = issue.try(:tracker_id) 25 | templates = target_templates(context, tracker_id) 26 | global_templates = global_note_templates(context, tracker_id) 27 | return if templates.empty? && global_templates.empty? 28 | 29 | context[:controller].send( 30 | :render_to_string, 31 | partial: 'issue_templates/note_form', locals: { type: 'template_issue_notes', templates: templates, issue: issue } 32 | ) 33 | end 34 | 35 | def target_templates(context, tracker_id) 36 | (tracker_id, project_id) = tracker_project_ids(context, tracker_id) 37 | NoteTemplate.visible_note_templates_condition( 38 | user_id: User.current.id, project_id: project_id, tracker_id: tracker_id 39 | ).sorted 40 | end 41 | 42 | def global_note_templates(context, tracker_id) 43 | (tracker_id, project_id) = tracker_project_ids(context, tracker_id) 44 | GlobalNoteTemplate.visible_note_templates_condition( 45 | user_id: User.current.id, project_id: project_id, tracker_id: tracker_id 46 | ).sorted 47 | end 48 | 49 | def tracker_project_ids(context, tracker_id) 50 | project = context[:project] 51 | project_id = project.present? ? project.id : issue.try(:project_id) 52 | [tracker_id, project_id] 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/tasks/test.rake: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | 3 | namespace :redmine_issue_templates do 4 | desc 'Run test for redmine_issue_template plugin.' 5 | task :test do |task_name| 6 | next unless ENV['RAILS_ENV'] == 'test' && task_name.name == 'redmine_issue_templates:test' 7 | end 8 | 9 | Rake::TestTask.new(:test) do |t| 10 | t.libs << 'lib' 11 | t.pattern = 'plugins/redmine_issue_templates/test/**/*_test.rb' 12 | t.verbose = false 13 | t.warning = false 14 | end 15 | 16 | desc 'Run spec for redmine_issue_template plugin' 17 | task :spec do |task_name| 18 | next unless ENV['RAILS_ENV'] == 'test' && task_name.name == 'redmine_issue_templates:spec' 19 | 20 | begin 21 | require 'rspec/core' 22 | path = 'plugins/redmine_issue_templates/spec/' 23 | options = ['-I plugins/redmine_issue_templates/spec'] 24 | options << '--format' 25 | options << 'documentation' 26 | options << path 27 | RSpec::Core::Runner.run(options) 28 | rescue LoadError => ex 29 | puts "This task should be called only for redmine issue template spec. #{ex.message}" 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/tasks/util.rake: -------------------------------------------------------------------------------- 1 | namespace :redmine_issue_templates do 2 | desc 'Apply inhelit template setting to child projects.' 3 | task :apply_inhelit_template_to_child_projects, 'project_id' 4 | task apply_inhelit_template_to_child_projects: :environment do |_t, args| 5 | project_id = args.project_id 6 | begin 7 | IssueTemplateSetting.apply_template_to_child_projects(project_id) 8 | rescue ActiveRecord::RecordNotFound 9 | puts "IssueTemplateSetting to project specified by #{project_id} does not exist." 10 | end 11 | end 12 | 13 | desc 'Unapply inhelit template setting from child projects.' 14 | task :unapply_inhelit_template_from_child_projects, 'project_id' 15 | task unapply_inhelit_template_from_child_projects: :environment do |_t, args| 16 | project_id = args.project_id 17 | begin 18 | IssueTemplateSetting.unapply_template_from_child_projects(project_id) 19 | rescue ActiveRecord::RecordNotFound 20 | puts "IssueTemplateSetting to project specified by #{project_id} does not exist." 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redmine_issue_templates", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "preview": "vite preview --port 4173", 8 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore" 9 | }, 10 | "dependencies": { 11 | "axios": "^1.3.6", 12 | "vue": "^2.7.7" 13 | }, 14 | "devDependencies": { 15 | "@rushstack/eslint-patch": "^1.1.0", 16 | "@vitejs/plugin-legacy": "^2.0.0", 17 | "@vitejs/plugin-vue2": "^1.1.2", 18 | "@vue/eslint-config-prettier": "^7.0.0", 19 | "eslint": "^8.5.0", 20 | "eslint-plugin-vue": "^9.11.0", 21 | "prettier": "^2.5.1", 22 | "terser": "^5.14.2", 23 | "vite": "^3.0.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /scripts/components/DisplayArea.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 33 | -------------------------------------------------------------------------------- /scripts/components/FieldValue.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 79 | -------------------------------------------------------------------------------- /scripts/plugins/customFields.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export const CustomFieldPlugin = { 4 | install(Vue, options = {}) { 5 | const { 6 | baseUrl, 7 | templateId, 8 | projectId, 9 | } = options; 10 | Vue.prototype.getCustomFields = async (trackerId) => { 11 | const params = { 12 | tracker_id: trackerId, 13 | template_id: templateId, 14 | project_id: projectId, 15 | }; 16 | const { data } = await axios.get(baseUrl, { params }); 17 | const { custom_fields } = data; 18 | return custom_fields; 19 | }; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /scripts/plugins/locales.js: -------------------------------------------------------------------------------- 1 | export const LocalePlugin = { 2 | install(Vue, locale) { 3 | Vue.prototype.l = (key) => { 4 | return locale[key]; 5 | }; 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /scripts/template_fields.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import JsonGenerator from './components/JsonGenerator.vue'; 3 | import { CustomFieldPlugin } from './plugins/customFields'; 4 | import { LocalePlugin } from './plugins/locales'; 5 | 6 | const TEMPLATE_FIELDS = function (props) { 7 | const { 8 | loadSelectableFieldsPath, 9 | templateId, 10 | projectId, 11 | locales, 12 | } = props; 13 | Vue.use(LocalePlugin, locales); 14 | Vue.use(CustomFieldPlugin, { 15 | baseUrl: loadSelectableFieldsPath, 16 | templateId, 17 | projectId, 18 | }); 19 | 20 | new Vue({ 21 | render: (h) => h(JsonGenerator, { props }) 22 | }).$mount('#json_generator'); 23 | }; 24 | 25 | window.TEMPLATE_FIELDS = TEMPLATE_FIELDS; 26 | -------------------------------------------------------------------------------- /spec/controllers/concerns/issue_templates_common_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../spec_helper' 4 | 5 | describe 'IssueTemplatesCommon' do 6 | before do 7 | class FakesController < ApplicationController 8 | include IssueTemplatesCommon 9 | end 10 | allow_any_instance_of(FakesController).to receive(:action_name).and_return('fake_action') 11 | User.current = FactoryBot.build(:user) 12 | end 13 | let(:mock_controller) { FakesController.new } 14 | 15 | describe '#log_action' do 16 | subject { mock_controller.log_action } 17 | 18 | it do 19 | expect(Rails.logger).to receive(:info).with("[FakesController] fake_action called by #{User.current.name}").once 20 | subject 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/controllers/settings_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../spec_helper' 4 | require File.expand_path(File.dirname(__FILE__) + '/../support/controller_helper') 5 | 6 | describe SettingsController, type: :controller do 7 | include_context 'As admin' 8 | before do 9 | Redmine::SudoMode.disable! 10 | Redmine::Plugin.register(:redmine_issue_templates) do 11 | settings partial: 'settings/redmine_issue_templates', 12 | default: { 'apply_global_template_to_all_projects' => 'false' } 13 | end 14 | @request.session[:user_id] = user.id 15 | end 16 | 17 | after(:all) do 18 | Redmine::Plugin.unregister(:redmine_issue_templates) 19 | end 20 | 21 | describe '#GET plugin' do 22 | render_views 23 | before do 24 | Setting.send 'plugin_redmine_issue_templates=', 'apply_global_template_to_all_projects' => 'false' 25 | get :plugin, params: { id: 'redmine_issue_templates' } 26 | end 27 | include_examples 'Right response', 200 28 | it 'Contains right plugin setting content' do 29 | expect(response.body).to match(/id="settings_apply_global_template_to_all_projects"/im) 30 | end 31 | end 32 | 33 | describe '#POST plugin' do 34 | render_views 35 | before do 36 | post :plugin, params: { id: 'redmine_issue_templates', 37 | settings: { apply_global_template_to_all_projects: true } } 38 | end 39 | include_examples 'Right response', 302 40 | it 'Setting value is changed true' do 41 | settings = Setting.plugin_redmine_issue_templates 42 | expect(settings['apply_global_template_to_all_projects']).to be_truthy 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/factories/enabled_modules.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :enabled_module do 3 | project_id { 1 } 4 | name { 'issue_templates' } 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/factories/global_issue_templates.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :global_issue_template do |t| 3 | association :tracker 4 | t.sequence(:title) { |n| "global_template-title: #{n}" } 5 | t.sequence(:description) { |n| "global_template-description: #{n}" } 6 | t.sequence(:note) { |n| "global_template-note: #{n}" } 7 | t.sequence(:position) { |n| n } 8 | t.enabled { true } 9 | t.is_default { false } 10 | t.author_id { 1 } 11 | 12 | factory :global_issue_template_with_projects do 13 | transient do 14 | projects_count { 5 } 15 | end 16 | 17 | after(:create) do |global_issue_template, evaluator| 18 | global_issue_template.projects = create_list(:project, evaluator.projects_count) 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/factories/global_note_templates.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :global_note_template do |t| 3 | association :tracker 4 | t.sequence(:name) { |n| "global_template-title: #{n}" } 5 | t.sequence(:description) { |n| "global_template-description: #{n}" } 6 | t.sequence(:memo) { |n| "global_template-note: #{n}" } 7 | t.sequence(:position) { |n| n } 8 | t.enabled { true } 9 | t.author_id { 1 } 10 | t.visibility { 2 } # open 11 | 12 | factory :global_note_template_with_projects do 13 | transient do 14 | projects_count { 5 } 15 | end 16 | 17 | after(:create) do |global_note_template, evaluator| 18 | global_note_template.projects = create_list(:project, evaluator.projects_count) 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/factories/issue_statuses.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :issue_status do 3 | sequence(:name) { |n| "status-name: #{n}" } 4 | sequence(:position) { |n| n } 5 | is_closed { false } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/factories/issue_template_settings.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :issue_template_setting do |t| 3 | association :project 4 | t.sequence(:help_message) { |n| "Project-#{n}: temlpate help" } 5 | t.enabled { true } 6 | t.inherit_templates { false } 7 | t.should_replaced { false } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/factories/issue_templates.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :issue_template do |t| 3 | association :project 4 | association :tracker 5 | t.sequence(:title) { |n| "template-title: #{n}" } 6 | t.sequence(:issue_title) { |n| "template-issue_title: #{n}" } 7 | t.sequence(:description) { |n| "template-description: #{n}" } 8 | t.sequence(:note) { |n| "template-note: #{n}" } 9 | t.sequence(:position) { |n| n } 10 | t.enabled { true } 11 | t.enabled_sharing { true } 12 | t.author_id { 1 } 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/factories/note_templates.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :note_template do 3 | association :project 4 | association :tracker 5 | author_id { 1 } 6 | sequence(:name) { |n| "note-template-name: #{n}" } 7 | sequence(:description) { |n| "note-template-description: #{n}" } 8 | sequence(:memo) { |n| "note-template-memo: #{n}" } 9 | enabled { true } 10 | sequence(:position) { |n| n } 11 | visibility { NoteTemplate.visibilities[:open] } 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/factories/projects.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :project do 3 | sequence(:name) { |n| "project-name: #{n}" } 4 | sequence(:description) { |n| "project-description: #{n}" } 5 | sequence(:identifier) { |n| "project-#{n}" } 6 | homepage { 'http://ecookbook.somenet.foo/' } 7 | is_public { true } 8 | 9 | trait :with_enabled_modules do 10 | after(:build) do |tracker| 11 | status = FactoryBot.create(:issue_status) 12 | tracker.default_status_id = status.id 13 | end 14 | end 15 | 16 | factory :project_with_enabled_modules do 17 | after(:create) do |project, _evaluator| 18 | FactoryBot.create(:enabled_module, project_id: project.id) 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/factories/role.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :role do 5 | sequence(:name) { |n| "Developer: #{n}" } 6 | builtin { 0 } 7 | issues_visibility { 'default' } 8 | users_visibility { 'all' } 9 | position { 1 } 10 | permissions { %i[ 11 | edit_project 12 | manage_members 13 | manage_versions 14 | manage_categories 15 | view_issues 16 | add_issues 17 | edit_issues 18 | copy_issues 19 | manage_issue_relations 20 | manage_subtasks 21 | add_issue_notes 22 | delete_issues 23 | view_issue_watchers 24 | save_queries 25 | view_gantt 26 | view_calendar 27 | log_time 28 | view_time_entries 29 | edit_own_time_entries 30 | manage_news 31 | comment_news 32 | view_documents 33 | add_documents 34 | edit_documents 35 | delete_documents 36 | view_wiki_pages 37 | view_wiki_edits 38 | edit_wiki_pages 39 | protect_wiki_pages 40 | delete_wiki_pages 41 | add_messages 42 | edit_own_messages 43 | delete_own_messages 44 | manage_boards 45 | view_files 46 | manage_files 47 | browse_repository 48 | view_changesets 49 | ] } 50 | 51 | trait :manager_role do 52 | name { 'Manager' } 53 | issues_visibility { 'all' } 54 | users_visibility { 'all' } 55 | permissions { %i[ 56 | add_project 57 | edit_project 58 | close_project 59 | select_project_modules 60 | manage_members 61 | manage_versions 62 | manage_categories 63 | view_issues 64 | add_issues 65 | edit_issues 66 | manage_issue_relations 67 | manage_subtasks 68 | add_issue_notes 69 | delete_issues 70 | view_issue_watchers 71 | set_issues_private 72 | set_notes_private 73 | view_private_notes 74 | delete_issue_watchers 75 | manage_public_queries 76 | save_queries 77 | view_gantt 78 | view_calendar 79 | log_time 80 | view_time_entries 81 | edit_own_time_entries 82 | delete_time_entries 83 | manage_news 84 | comment_news 85 | view_documents 86 | add_documents 87 | edit_documents 88 | delete_documents 89 | view_wiki_pages 90 | view_wiki_edits 91 | edit_wiki_pages 92 | delete_wiki_pages_attachments 93 | protect_wiki_pages 94 | delete_wiki_pages 95 | rename_wiki_pages 96 | add_messages 97 | edit_own_messages 98 | delete_own_messages 99 | manage_boards 100 | view_files 101 | manage_files 102 | browse_repository 103 | manage_repository 104 | view_changesets 105 | manage_related_issues 106 | manage_project_activities 107 | ] } 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /spec/factories/trackers.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :tracker do 3 | sequence(:name) { |n| "tracker-name: #{n}" } 4 | sequence(:position) { |n| n } 5 | default_status_id { 1 } 6 | trait :with_default_status do 7 | after(:build) do |tracker| 8 | status = FactoryBot.create(:issue_status) 9 | tracker.default_status_id = status.id 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/factories/users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :user do |u| 5 | # sequence -> exp. :login -> user1, user2..... 6 | u.sequence(:login) { |n| "user#{n}" } 7 | u.sequence(:firstname) { |n| "User#{n}" } 8 | u.sequence(:lastname) { |n| "Test#{n}" } 9 | u.sequence(:mail) { |n| "user#{n}@badge.example.com" } 10 | u.language { 'en' } 11 | # password = foo 12 | u.hashed_password { '8f659c8d7c072f189374edacfa90d6abbc26d8ed' } 13 | u.salt { '7599f9963ec07b5a3b55b354407120c0' } 14 | 15 | # login and password is the same. (Note: login length should be longer than 7.) 16 | trait :password_same_login do 17 | after(:create) do |user| 18 | user.password = user.login 19 | user.auth_source_id = nil 20 | user.save 21 | end 22 | end 23 | 24 | trait :as_group do 25 | type { 'Group' } 26 | lastname { "Group#{n}" } 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/features/admin_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../spec_helper' 2 | require_relative '../rails_helper' 3 | require_relative '../support/login_helper' 4 | 5 | RSpec.configure do |c| 6 | c.include LoginHelper 7 | end 8 | 9 | feature 'PluginSetting to apply Global issue templates to all the projects', js: true do 10 | given(:user) { FactoryBot.create(:user, :password_same_login, login: 'admin', language: 'en') } 11 | 12 | background(:all) do 13 | Redmine::Plugin.register(:redmine_issue_templates) do 14 | settings partial: 'settings/redmine_issue_templates', 15 | default: { 'apply_global_template_to_all_projects' => 'false' } 16 | end 17 | end 18 | 19 | background do 20 | # Prevent to call User.deliver_security_notification when user is created. 21 | allow_any_instance_of(User).to receive(:deliver_security_notification).and_return(true) 22 | 23 | Setting.send 'plugin_redmine_issue_templates=', 'apply_global_template_to_all_projects' => 'false' 24 | user.update_attribute(:admin, true) 25 | log_user(user.login, user.login) 26 | visit '/settings/plugin/redmine_issue_templates' 27 | end 28 | 29 | scenario 'Settings "apply_global_template_to_all_projects" is displayed.' do 30 | expect(page).to have_content('Apply Global issue templates to all the projects.') 31 | expect(page).to have_selector('#settings_apply_global_template_to_all_projects') 32 | end 33 | 34 | scenario 'Activate "apply_global_template_to_all_projects".' do 35 | expect(page).to have_unchecked_field('settings_apply_global_template_to_all_projects') 36 | check 'settings_apply_global_template_to_all_projects' 37 | click_on 'Apply' 38 | expect(page).to have_selector('#settings_apply_global_template_to_all_projects') 39 | expect(page).to have_checked_field('settings_apply_global_template_to_all_projects') 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/features/create_issue_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../spec_helper' 4 | require_relative '../rails_helper' 5 | require_relative '../support/login_helper' 6 | 7 | RSpec.configure do |c| 8 | c.include LoginHelper 9 | end 10 | 11 | feature 'Create issue', js: true do 12 | given(:user) { FactoryBot.create(:user, :password_same_login, login: 'test-manager', language: 'en', admin: false) } 13 | given(:project) { FactoryBot.create(:project_with_enabled_modules) } 14 | given(:tracker) { FactoryBot.create(:tracker, :with_default_status) } 15 | given(:role) { FactoryBot.create(:role, :manager_role) } 16 | given(:status) { IssueStatus.create(name: 'open', is_closed: false) } 17 | given(:issue_note) { page.find('textarea#issue_notes') } 18 | 19 | background(:all) do 20 | Redmine::Plugin.register(:redmine_issue_templates) do 21 | settings partial: 'settings/redmine_issue_templates', 22 | default: { 'apply_global_template_to_all_projects' => 'false', 'apply_template_when_edit_issue' => 'true' } 23 | end 24 | end 25 | 26 | background do 27 | project.trackers << tracker 28 | assign_template_priv(role, add_permission: :show_issue_templates) 29 | member = Member.new(project: project, user_id: user.id) 30 | member.member_roles << MemberRole.new(role: role) 31 | member.save 32 | end 33 | 34 | describe 'apply default issue template' do 35 | background do 36 | FactoryBot.create( 37 | :issue_template, 38 | project_id: project.id, 39 | tracker_id: tracker.id, 40 | issue_title: 'default issue title', 41 | description: 'default issue description', 42 | is_default: is_default, 43 | ) 44 | end 45 | 46 | context 'is_default: true' do 47 | let(:is_default) { true } 48 | scenario 'Select tracker and apply default template' do 49 | log_user(user.login, user.login) 50 | visit "/projects/#{project.identifier}/issues/new" 51 | select tracker.name, from: 'issue[tracker_id]' 52 | expect(find('#issue_subject').value).to eq('default issue title') 53 | expect(find('#issue_description').value).to eq('default issue description') 54 | end 55 | end 56 | 57 | context 'is_default: false' do 58 | let(:is_default) { false } 59 | scenario 'Select tracker and apply default template' do 60 | log_user(user.login, user.login) 61 | visit "/projects/#{project.identifier}/issues/new" 62 | select tracker.name, from: 'issue[tracker_id]' 63 | expect(find('#issue_subject').value).to eq('') 64 | expect(find('#issue_description').value).to eq('') 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/features/update_template_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../spec_helper' 4 | require_relative '../rails_helper' 5 | require_relative '../support/login_helper' 6 | 7 | RSpec.configure do |c| 8 | c.include LoginHelper 9 | end 10 | 11 | feature 'Update template', js: true do 12 | given(:user) { FactoryBot.create(:user, :password_same_login, login: 'test-manager', language: 'en', admin: false) } 13 | given(:project) { FactoryBot.create(:project_with_enabled_modules) } 14 | given(:tracker) { FactoryBot.create(:tracker, :with_default_status) } 15 | given(:role) { FactoryBot.create(:role, :manager_role) } 16 | given(:status) { IssueStatus.create(name: 'open', is_closed: false) } 17 | given(:expected_note_description) { 'Note Template desctiption' } 18 | given!(:template) { 19 | NoteTemplate.create(project_id: project.id, tracker_id: tracker.id, 20 | name: 'Note Template name', description: expected_note_description, enabled: true) 21 | } 22 | 23 | background(:all) do 24 | Redmine::Plugin.register(:redmine_issue_templates) do 25 | settings partial: 'settings/redmine_issue_templates', 26 | default: { 'apply_global_template_to_all_projects' => 'false', 'apply_template_when_edit_issue' => 'true' } 27 | end 28 | end 29 | 30 | background do 31 | project.trackers << tracker 32 | 33 | priority = IssuePriority.create( 34 | name: 'Low', 35 | position: 1, is_default: false, type: 'IssuePriority', active: true, project_id: nil, parent_id: nil, 36 | position_name: 'lowest' 37 | ) 38 | 39 | member = Member.new(project: project, user_id: user.id) 40 | member.member_roles << MemberRole.new(role: role) 41 | member.save 42 | 43 | Issue.create(project_id: project.id, tracker_id: tracker.id, 44 | author_id: user.id, 45 | priority: priority, 46 | subject: 'test_create', 47 | status_id: status.id, 48 | description: 'IssueTest#test_create') 49 | end 50 | 51 | context 'Have show_issue_template permission' do 52 | 53 | background do 54 | assign_template_priv(role, add_permission: :show_issue_templates) 55 | end 56 | 57 | scenario 'Cannot edit the template, only view it' do 58 | visit_log_user(user) 59 | visit "/projects/#{project.identifier}/note_templates/#{template.id}" 60 | sleep(0.2) 61 | expect(page).to have_no_selector('div#edit-note_template') 62 | expect(page).to have_selector('div#view-note_template') 63 | end 64 | end 65 | 66 | context 'Have edit_issue_template permission' do 67 | 68 | background do 69 | assign_template_priv(role, add_permission: :edit_issue_templates) 70 | assign_template_priv(role, add_permission: :show_issue_templates) 71 | end 72 | 73 | scenario 'Can edit the template, and view it' do 74 | visit_log_user(user) 75 | visit "/projects/#{project.identifier}/note_templates/#{template.id}" 76 | sleep(0.2) 77 | expect(page).to have_selector('div#edit-note_template') 78 | expect(page).to have_no_selector('div#view-note_template') 79 | end 80 | end 81 | 82 | private 83 | 84 | def visit_log_user(user) 85 | user.update_attribute(:admin, false) 86 | log_user(user.login, user.login) 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /spec/helpers/issue_templates_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../spec_helper' 2 | 3 | describe IssueTemplatesHelper do 4 | describe '#project_tracker?' do 5 | let(:trackers) { FactoryBot.create_list(:tracker, 2, :with_default_status) } 6 | let(:project) { FactoryBot.create(:project) } 7 | let(:tracker) { trackers.first } 8 | subject { helper.project_tracker?(tracker, project) } 9 | 10 | context 'Tracker is associated' do 11 | before do 12 | project.trackers << tracker 13 | end 14 | it { is_expected.to be_truthy } 15 | end 16 | context 'Tracker is not associated' do 17 | before do 18 | project.trackers << trackers.last 19 | end 20 | it { is_expected.to be_falsey } 21 | end 22 | end 23 | 24 | describe '#non_project_tracker_msg' do 25 | it { expect(helper.non_project_tracker_msg(true)).to eq '' } 26 | it { expect(helper.non_project_tracker_msg(false)).to match('') } 27 | end 28 | 29 | describe '#template_target_trackers' do 30 | let(:trackers) { FactoryBot.create_list(:tracker, 2, :with_default_status) } 31 | let(:project) { FactoryBot.create(:project) } 32 | let(:tracker) { trackers.last } 33 | let(:template) do 34 | FactoryBot.create(:issue_template, tracker_id: tracker.id, project_id: project.id) 35 | end 36 | subject { helper.template_target_trackers(project, template) } 37 | before do 38 | project.trackers << trackers.first 39 | end 40 | it { expect(subject.include?([tracker.name, tracker.id])).to be_truthy } 41 | it { expect(subject.length).to eq 2 } 42 | end 43 | 44 | describe '#options_for_template_pulldown' do 45 | let(:options) do 46 | option = Struct.new(:id, :name) 47 | [].tap do |options| 48 | (0..2).each do |id| 49 | options << option.new(id, "name-#{id}") 50 | end 51 | end 52 | end 53 | subject { helper.options_for_template_pulldown(options) } 54 | it { expect(subject).to match('') } 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/models/global_issue_template_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../spec_helper' 4 | 5 | describe GlobalIssueTemplate do 6 | describe '#valid?' do 7 | let(:instance) { GlobalIssueTemplate.new(tracker_id: tracker.id, title: 'sample', description: 'description1') } 8 | let(:tracker) { FactoryBot.create(:tracker, :with_default_status) } 9 | subject { instance.valid? } 10 | 11 | it 'related_link in invalid format' do 12 | instance.related_link = 'non url format string' 13 | is_expected.to be_falsey 14 | expect(instance.errors.messages.key?(:related_link)).to be_truthy 15 | end 16 | 17 | it 'related_link in valid format' do 18 | instance.related_link = 'https://valid.example.com/links.html' 19 | is_expected.to be_truthy 20 | end 21 | end 22 | 23 | describe '#builtin_fields_json' do 24 | let(:tracker) { FactoryBot.create(:tracker, :with_default_status) } 25 | let(:global_issue_template) do 26 | create(:global_issue_template, tracker_id: tracker.id) 27 | end 28 | subject { global_issue_template.update(builtin_fields_json: object) } 29 | 30 | context 'Data is a valid hash' do 31 | let(:object) { { 'key': 'value', 'foo': 'bar' } } 32 | it { is_expected.to be_truthy } 33 | end 34 | 35 | context 'Data is not a valid hash' do 36 | let(:object) { [1, 2, 3] } 37 | it { expect { subject }.to raise_error(ActiveRecord::SerializationTypeMismatch) } 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/models/global_note_template_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../spec_helper' 4 | 5 | describe GlobalNoteTemplate do 6 | let(:tracker) { FactoryBot.create(:tracker, :with_default_status) } 7 | let!(:note_template) { FactoryBot.create(:global_note_template, tracker_id: tracker.id, position: 1) } 8 | let!(:note_template2) { FactoryBot.create(:global_note_template, tracker_id: tracker.id, position: 2) } 9 | let!(:note_template3) { FactoryBot.create(:global_note_template, tracker_id: tracker.id, position: 3) } 10 | 11 | it 'Instance of GlobalNoteTemplate' do 12 | expect(note_template).to be_an_instance_of(GlobalNoteTemplate) 13 | end 14 | 15 | describe 'scope: .sorted' do 16 | it 'do sort by position correctly' do 17 | expect([note_template, note_template2, note_template3]).to eq GlobalNoteTemplate.sorted 18 | expect(GlobalNoteTemplate.sorted.first).to eq note_template 19 | end 20 | 21 | it 'do sort by position correctly after update' do 22 | note_template.update(position: GlobalNoteTemplate.count) 23 | expect(GlobalNoteTemplate.sorted).to eq [note_template2, note_template3, note_template] 24 | end 25 | end 26 | 27 | it 'can be deleted even though some projects bound' do 28 | note_template_with_project = create(:global_note_template, tracker_id: tracker.id, position: 4, enabled: false) 29 | note_template_with_project.projects << create(:project) << create(:project) << create(:project) 30 | 31 | expect(GlobalNoteTemplate.where(id: note_template_with_project.id)).not_to be_empty 32 | note_template_with_project.destroy 33 | expect(GlobalNoteTemplate.where(id: note_template_with_project.id)).to be_empty 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/models/issue_template_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../spec_helper' 4 | 5 | describe IssueTemplate do 6 | let(:tracker) { FactoryBot.create(:tracker, :with_default_status) } 7 | let(:project) { FactoryBot.create(:project) } 8 | let(:issue_template) { FactoryBot.create(:issue_template, tracker_id: tracker.id, project_id: project.id) } 9 | let(:issue_template2) { FactoryBot.create(:issue_template, tracker_id: tracker.id, project_id: project.id) } 10 | it 'Instance of IssueTemplate' do 11 | expect(issue_template).to be_an_instance_of(IssueTemplate) 12 | end 13 | 14 | describe 'scope .orphaned' do 15 | subject { IssueTemplate.orphaned.count } 16 | before do 17 | # Remove related tracker 18 | issue_template.tracker.delete 19 | end 20 | it { is_expected.to eq 1 } 21 | end 22 | 23 | describe 'scope: .sorted' do 24 | it 'do sort by position correctly' do 25 | expect([issue_template, issue_template2]).to eq [issue_template2, issue_template].sort 26 | expect(IssueTemplate.sorted.first).to eq issue_template 27 | end 28 | 29 | it 'do sort by position correctly after update' do 30 | issue_template.update(position: issue_template2.position + 100) 31 | expect(IssueTemplate.sorted.first).to eq issue_template2 32 | end 33 | end 34 | 35 | describe '#destroy' do 36 | subject { issue_template.destroy } 37 | context 'Template is enabled' do 38 | before do 39 | issue_template.enabled = true 40 | issue_template.save 41 | end 42 | it 'Failed to remove with invalid message' do 43 | expect(Rails.logger).to receive(:info).with(/\[Destroy\] IssueTemplate: /).never 44 | subject 45 | expect(issue_template.errors.present?).to be_truthy 46 | end 47 | end 48 | context 'Template is disabled' do 49 | before { issue_template.enabled = false } 50 | it 'Removed and log message is generated' do 51 | expect(Rails.logger).to receive(:info).with(/\[Destroy\] IssueTemplate: /).once 52 | subject 53 | expect(issue_template.errors.present?).to be_falsey 54 | end 55 | end 56 | end 57 | 58 | describe '#valid?' do 59 | let(:instance) { described_class.new(tracker_id: tracker.id, project_id: project.id, title: 'sample', description: 'description1') } 60 | subject { instance.valid? } 61 | 62 | it 'related_link in invalid format' do 63 | instance.related_link = 'non url format string' 64 | is_expected.to be_falsey 65 | expect(instance.errors.messages.key?(:related_link)).to be_truthy 66 | end 67 | 68 | it 'related_link in valid format' do 69 | instance.related_link = 'https://valid.example.com/links.html' 70 | is_expected.to be_truthy 71 | end 72 | end 73 | 74 | describe '#builtin_fields_json' do 75 | subject { issue_template.update(builtin_fields_json: object) } 76 | 77 | context 'Data is a valid hash' do 78 | let(:object) { { 'key': 'value', 'foo': 'bar' } } 79 | it { is_expected.to be_truthy } 80 | end 81 | 82 | context 'Data is not a valid hash' do 83 | let(:object) { [1, 2, 3] } 84 | it { expect { subject }.to raise_error(ActiveRecord::SerializationTypeMismatch) } 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /spec/models/note_visible_role_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../spec_helper' 4 | 5 | describe NoteVisibleRole do 6 | let(:instance) { described_class.new } 7 | it 'Instance of NoteVisibleRole' do 8 | expect(instance).to be_an_instance_of(NoteVisibleRole) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is copied to spec/ when you run 'rails generate rspec:install' 4 | ENV['RAILS_ENV'] ||= 'test' 5 | require File.expand_path('../../../config/environment', __dir__) 6 | 7 | abort('The Rails environment is running in production mode!') if Rails.env.production? 8 | require File.expand_path('./spec_helper', __dir__) 9 | require 'selenium-webdriver' 10 | 11 | ActiveRecord::Migration.maintain_test_schema! 12 | 13 | RSpec.configure do |config| 14 | config.fixture_path = "#{::Rails.root}/test/fixtures" 15 | config.include FactoryBot::Syntax::Methods 16 | 17 | config.before :suite do 18 | Capybara.register_driver :headless_chrome do |app| 19 | options = Selenium::WebDriver::Chrome::Options.new 20 | # 21 | # NOTE: When using Chrome headless, default window size is 800x600. 22 | # In case window size is not specified, Redmine renderes its contents with responsive mode. 23 | # 24 | options.add_argument('window-size=1280,800') 25 | unless ENV['HEADLESS'] == '0' 26 | options.add_argument('headless') 27 | options.add_argument('disable-gpu') 28 | end 29 | Capybara::Selenium::Driver.new(app, browser: :chrome, options: options) 30 | end 31 | end 32 | 33 | config.before :each, type: :feature do 34 | Capybara.javascript_driver = :headless_chrome 35 | Capybara.current_driver = :headless_chrome 36 | Capybara.default_max_wait_time = 30 37 | end 38 | 39 | config.include Capybara::DSL 40 | config.use_transactional_fixtures = false 41 | config.infer_spec_type_from_file_location! 42 | 43 | config.filter_rails_from_backtrace! 44 | 45 | config.before(:suite) do 46 | DatabaseCleaner.strategy = :truncation 47 | DatabaseCleaner.clean_with(:truncation) 48 | end 49 | 50 | config.before(:each) do 51 | DatabaseCleaner.start 52 | end 53 | 54 | config.after(:each) do 55 | DatabaseCleaner.clean 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/requests/global_note_templates_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../spec_helper' 4 | require File.expand_path(File.dirname(__FILE__) + '/../support/controller_helper') 5 | 6 | RSpec.configure do |c| 7 | c.include ControllerHelper 8 | end 9 | 10 | RSpec.describe 'Global Note Template', type: :request do 11 | let(:user) { FactoryBot.create(:user, :password_same_login, login: 'test-manager', language: 'en', admin: true) } 12 | let(:tracker) { FactoryBot.create(:tracker, :with_default_status) } 13 | let(:target_template_name) { 'Global Note template name' } 14 | let(:target_template) { GlobalNoteTemplate.last } 15 | 16 | before do 17 | # do nothing 18 | end 19 | 20 | it 'show global note template list' do 21 | login_request(user.login, user.login) 22 | get '/global_note_templates' 23 | expect(response.status).to eq 200 24 | 25 | get '/global_note_templates/new' 26 | expect(response.status).to eq 200 27 | end 28 | 29 | it 'create global note template and load' do 30 | login_request(user.login, user.login) 31 | post '/global_note_templates', 32 | params: { global_note_template: 33 | { tracker_id: tracker.id, name: target_template_name, 34 | description: 'Global Note template description', memo: 'Test memo', enabled: 1 } } 35 | expect(response).to have_http_status(302) 36 | 37 | post '/note_templates/load', params: { note_template: { note_template_id: target_template.id, template_type: 'global' } } 38 | json = JSON.parse(response.body) 39 | expect(target_template.name).to eq(json['note_template']['name']) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/requests/note_templates_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../spec_helper' 4 | require File.expand_path(File.dirname(__FILE__) + '/../support/controller_helper') 5 | 6 | RSpec.configure do |c| 7 | c.include ControllerHelper 8 | end 9 | 10 | RSpec.describe 'Note Template', type: :request do 11 | let(:user) { FactoryBot.create(:user, :password_same_login, login: 'test-manager', language: 'en', admin: false) } 12 | let(:project) { FactoryBot.create(:project_with_enabled_modules) } 13 | let(:tracker) { FactoryBot.create(:tracker, :with_default_status) } 14 | let(:role) { FactoryBot.create(:role, :manager_role) } 15 | let(:target_template) { NoteTemplate.last } 16 | 17 | before do 18 | project.trackers << tracker 19 | assign_template_priv(role, add_permission: :show_issue_templates) 20 | assign_template_priv(role, add_permission: :edit_issue_templates) 21 | member = Member.new(project: project, user_id: user.id) 22 | member.member_roles << MemberRole.new(role: role) 23 | member.save 24 | end 25 | 26 | it 'show note template list' do 27 | login_request(user.login, user.login) 28 | get "/projects/#{project.identifier}/note_templates" 29 | expect(response.status).to eq 200 30 | 31 | get "/projects/#{project.identifier}/note_templates/new" 32 | expect(response.status).to eq 200 33 | end 34 | 35 | it 'create note template and load' do 36 | login_request(user.login, user.login) 37 | post "/projects/#{project.identifier}/note_templates", 38 | params: { note_template: 39 | { tracker_id: tracker.id, name: 'Note template name', 40 | description: 'Note template description', memo: 'Test memo', enabled: 1 } } 41 | expect(response).to have_http_status(302) 42 | 43 | post '/note_templates/load', params: { note_template: { note_template_id: target_template.id } } 44 | json = JSON.parse(response.body) 45 | expect(target_template.name).to eq(json['note_template']['name']) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../../config/environment', __FILE__) 2 | require 'rspec/rails' 3 | require 'simplecov' 4 | require 'factory_bot_rails' 5 | require 'database_cleaner' 6 | 7 | SimpleCov.coverage_dir('coverage/redmine_issue_templates_spec') 8 | SimpleCov.start 'rails' 9 | 10 | RSpec.configure do |config| 11 | config.fixture_path = "#{::Rails.root}/test/fixtures" 12 | config.use_transactional_fixtures = false 13 | config.infer_spec_type_from_file_location! 14 | config.include FactoryBot::Syntax::Methods 15 | FactoryBot.definition_file_paths = [File.expand_path('../factories', __FILE__)] 16 | FactoryBot.find_definitions 17 | config.before(:all) do 18 | FactoryBot.reload 19 | end 20 | 21 | config.before(:suite) do 22 | DatabaseCleaner.strategy = :truncation 23 | DatabaseCleaner.clean_with(:truncation) 24 | end 25 | 26 | config.before(:each) do 27 | DatabaseCleaner.start 28 | end 29 | 30 | config.after(:each) do 31 | DatabaseCleaner.clean 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/support/controller_helper.rb: -------------------------------------------------------------------------------- 1 | module ControllerHelper 2 | # AuthHeader with api key (Ref.http://www.redmine.org/projects/redmine/wiki/Rest_api) 3 | def auth_with_user(user) 4 | request.headers['X-Redmine-API-Key'] = user.api_key.to_s 5 | end 6 | 7 | def clear_token 8 | request.headers['X-Redmine-API-Key'] = nil 9 | end 10 | 11 | def login_request(login, password) 12 | post '/login', params: { username: login, password: password } 13 | end 14 | 15 | def assign_template_priv(role, add_permission: nil, remove_permission: nil) 16 | return if add_permission.blank? && remove_permission.blank? 17 | 18 | role.add_permission! add_permission if add_permission.present? 19 | role.remove_permission! remove_permission if remove_permission.present? 20 | end 21 | 22 | shared_context 'As admin' do 23 | let(:user) { FactoryBot.create(:user, status: 1, admin: is_admin) } 24 | let(:is_admin) { true } 25 | end 26 | 27 | shared_context 'Project and Tracler exists' do 28 | let(:count) { 4 } 29 | let(:trackers) { FactoryBot.create_list(:tracker, 2, :with_default_status) } 30 | let(:projects) { FactoryBot.create_list(:project, count) } 31 | end 32 | 33 | shared_examples 'Right response' do |status_code| 34 | it { expect(response.status).to eq status_code } 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/support/login_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module LoginHelper 4 | def log_user(login, password) 5 | visit '/login' 6 | 7 | within('#login-form form') do 8 | fill_in 'username', with: login 9 | fill_in 'password', with: password 10 | find('input[name=login]').click 11 | end 12 | end 13 | 14 | def assign_template_priv(role, add_permission: nil, remove_permission: nil) 15 | return if add_permission.blank? && remove_permission.blank? 16 | 17 | role.add_permission! add_permission if add_permission.present? 18 | role.remove_permission! remove_permission if remove_permission.present? 19 | end 20 | 21 | def wait_for_ajax 22 | Timeout.timeout(Capybara.default_max_wait_time) do 23 | loop until finished_all_ajax_requests? 24 | end 25 | end 26 | 27 | def finished_all_ajax_requests? 28 | page.evaluate_script('jQuery.active').zero? 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/fixtures/global_issue_templates.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html 2 | one: 3 | id: 1 4 | 5 | tracker_id: 1 6 | 7 | author_id: 1 8 | 9 | title: global_title1 10 | 11 | description: "global description1" 12 | 13 | note: note1 14 | 15 | enabled: true 16 | 17 | position: 1 18 | 19 | two: 20 | id: 2 21 | 22 | tracker_id: 3 23 | 24 | author_id: 1 25 | 26 | title: titel2 27 | 28 | description: "global description2" 29 | 30 | note: note2 31 | 32 | enabled: false 33 | 34 | position: 1 35 | 36 | three: 37 | id: 3 38 | 39 | tracker_id: 2 40 | 41 | author_id: 1 42 | 43 | title: titel3 44 | 45 | description: "global description3" 46 | 47 | note: note3 48 | 49 | enabled: true 50 | 51 | position: 1 52 | 53 | four: 54 | id: 4 55 | 56 | tracker_id: 1 57 | 58 | author_id: 1 59 | 60 | title: titel4 61 | 62 | description: "global description4" 63 | 64 | note: note4 65 | 66 | enabled: true 67 | 68 | position: 2 69 | 70 | five: 71 | id: 5 72 | 73 | tracker_id: 1 74 | 75 | author_id: 1 76 | 77 | title: titel5 78 | 79 | description: "global description5" 80 | 81 | note: note5 82 | 83 | enabled: true 84 | 85 | position: 3 86 | -------------------------------------------------------------------------------- /test/fixtures/global_issue_templates_projects.yml: -------------------------------------------------------------------------------- 1 | global_issue_templates_projects_001: 2 | project_id: 1 3 | global_issue_template_id: 1 4 | global_issue_templates_projects_002: 5 | project_id: 3 6 | global_issue_template_id: 1 -------------------------------------------------------------------------------- /test/fixtures/global_note_templates.yml: -------------------------------------------------------------------------------- 1 | --- 2 | global_note_templates_001: 3 | id: 1 4 | name: global note template 1 5 | description: |- 6 | global description 1-1 7 | global description 1-2 8 | tracker_id: 1 9 | author_id: 2 10 | enabled: true 11 | position: 1 12 | visibility: open 13 | -------------------------------------------------------------------------------- /test/fixtures/issue_template_settings.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html 2 | one: 3 | id: 1 4 | 5 | project_id: 1 6 | 7 | help_message: MyText 8 | 9 | enabled: true 10 | 11 | inherit_templates: false 12 | 13 | two: 14 | id: 2 15 | 16 | project_id: 2 17 | 18 | help_message: MyText 19 | 20 | enabled: false 21 | 22 | inherit_templates: false 23 | 24 | three: 25 | id: 3 26 | 27 | project_id: 3 28 | 29 | help_message: Project3 help 30 | 31 | enabled: true 32 | 33 | inherit_templates: false 34 | 35 | -------------------------------------------------------------------------------- /test/fixtures/issue_templates.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html 2 | one: 3 | id: 1 4 | 5 | project_id: 1 6 | 7 | tracker_id: 1 8 | 9 | author_id: 1 10 | 11 | title: title1 12 | 13 | description: description1 14 | 15 | note: note1 16 | 17 | enabled: true 18 | 19 | position: 1 20 | 21 | enabled_sharing: true 22 | 23 | two: 24 | id: 2 25 | 26 | project_id: 1 27 | 28 | tracker_id: 3 29 | 30 | author_id: 1 31 | 32 | title: titel2 33 | 34 | description: description2 35 | 36 | note: note2 37 | 38 | enabled: false 39 | 40 | position: 1 41 | 42 | enabled_sharing: false 43 | 44 | three: 45 | id: 3 46 | 47 | project_id: 1 48 | 49 | tracker_id: 2 50 | 51 | author_id: 1 52 | 53 | title: titel3 54 | 55 | description: description3 56 | 57 | note: note3 58 | 59 | enabled: true 60 | 61 | position: 1 62 | 63 | four: 64 | id: 4 65 | 66 | project_id: 1 67 | 68 | tracker_id: 1 69 | 70 | author_id: 2 71 | 72 | title: titel4 73 | 74 | description: description4 75 | 76 | note: note4 77 | 78 | enabled: true 79 | 80 | position: 2 81 | 82 | five: 83 | id: 5 84 | 85 | project_id: 1 86 | 87 | tracker_id: 1 88 | 89 | author_id: 1 90 | 91 | title: title5 92 | 93 | description: description5 94 | 95 | note: note5 96 | 97 | enabled: false 98 | 99 | position: 3 100 | 101 | six: 102 | id: 6 103 | 104 | project_id: 3 105 | 106 | tracker_id: 1 107 | 108 | author_id: 1 109 | 110 | title: title6 111 | 112 | description: description6 113 | 114 | note: note5 115 | 116 | enabled: true 117 | 118 | position: 3 119 | -------------------------------------------------------------------------------- /test/fixtures/note_templates.yml: -------------------------------------------------------------------------------- 1 | --- 2 | note_templates_001: 3 | id: 1 4 | name: note template 1 5 | description: |- 6 | comment 1-1 7 | comment 1-2 8 | project_id: 1 9 | tracker_id: 1 10 | author_id: 2 11 | enabled: true 12 | position: 1 13 | visibility: mine 14 | note_templates_002: 15 | id: 2 16 | name: note template 2 17 | description: |- 18 | comment 2-1 19 | comment 2-2 20 | project_id: 1 21 | tracker_id: 1 22 | author_id: 2 23 | enabled: true 24 | position: 2 25 | visibility: roles 26 | note_templates_003: 27 | id: 3 28 | name: note template 3 29 | description: |- 30 | comment 3-1 31 | comment 3-2 32 | project_id: 1 33 | tracker_id: 1 34 | author_id: 2 35 | enabled: true 36 | position: 3 37 | visibility: roles 38 | note_templates_004: 39 | id: 4 40 | name: note template 4 41 | description: |- 42 | comment 4-1 43 | comment 4-2 44 | project_id: 1 45 | tracker_id: 1 46 | author_id: 2 47 | enabled: true 48 | position: 4 49 | visibility: open 50 | note_templates_005: 51 | id: 5 52 | name: note template 5 53 | description: |- 54 | comment 5-1 55 | comment 5-2 56 | project_id: 1 57 | tracker_id: 1 58 | author_id: 3 59 | enabled: true 60 | position: 5 61 | visibility: mine 62 | -------------------------------------------------------------------------------- /test/fixtures/note_visible_roles.yml: -------------------------------------------------------------------------------- 1 | --- 2 | note_visible_roles_001: 3 | id: 1 4 | note_template_id: 2 5 | role_id: 1 6 | note_visible_roles_002: 7 | id: 2 8 | note_template_id: 2 9 | role_id: 2 10 | note_visible_roles_003: 11 | id: 3 12 | note_template_id: 3 13 | role_id: 2 14 | -------------------------------------------------------------------------------- /test/functional/issue_templates_settings_controller_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path('../test_helper', __dir__) 4 | 5 | class IssueTemplatesSettingsControllerTest < Redmine::ControllerTest 6 | fixtures :projects, 7 | :users, 8 | :roles, 9 | :members, 10 | :member_roles, 11 | :enabled_modules, 12 | :issue_templates 13 | 14 | def setup 15 | # Enabled Template module 16 | enabled_module = EnabledModule.new 17 | enabled_module.project_id = 1 18 | enabled_module.name = 'issue_templates' 19 | enabled_module.save 20 | 21 | # set default user to 2 (as member) 22 | @request.session[:user_id] = 2 23 | Role.find(1).add_permission! :manage_issue_templates 24 | 25 | @project = Project.find(1) 26 | end 27 | 28 | def test_update_without_permission 29 | Role.find(1).remove_permission! :manage_issue_templates 30 | post :edit, params: { project_id: @project, 31 | settings: { enabled: '1', help_message: 'Hoo', inherit_templates: true }, 32 | setting_id: 1, tab: 'issue_templates' } 33 | assert_response 403 34 | end 35 | 36 | def test_update_with_permission_and_non_project 37 | post :edit, params: { project_id: 'dummy', 38 | settings: { enabled: '1', help_message: 'Hoo', inherit_templates: true }, 39 | setting_id: 1 } 40 | assert_response 404 41 | end 42 | 43 | def test_update_with_permission_and_redirect 44 | post :edit, params: { project_id: @project, 45 | settings: { enabled: '1', help_message: 'Hoo', inherit_templates: true }, 46 | setting_id: 1 } 47 | assert_response :redirect 48 | assert_redirected_to controller: 'issue_templates_settings', 49 | action: 'index', project_id: @project 50 | end 51 | 52 | def test_preview_template_setting 53 | post :preview, params: { settings: { help_message: 'h1. Preview test.', 54 | enabled: '1' }, 55 | project_id: @project } 56 | assert_select 'h1', /Preview test\./, @response.body.to_s 57 | end 58 | 59 | def test_create_template_setting 60 | IssueTemplateSetting.delete_all 61 | 62 | post :edit, params: { project_id: @project, 63 | settings: { enabled: '1', help_message: 'Hoo', inherit_templates: true }, 64 | setting_id: 1 } 65 | assert_response :redirect 66 | assert_redirected_to controller: 'issue_templates_settings', 67 | action: 'index', project_id: @project 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /test/functional/issues_controller_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../test_helper') 2 | require 'issues_controller' 3 | 4 | # Test for view hooks. 5 | class IssuesControllerTest < Redmine::ControllerTest 6 | fixtures :projects, 7 | :users, 8 | :roles, 9 | :members, 10 | :member_roles, 11 | :issues, 12 | :issue_statuses, 13 | :versions, 14 | :trackers, 15 | :projects_trackers, 16 | :issue_categories, 17 | :enabled_modules, 18 | :enumerations, 19 | :attachments, 20 | :workflows, 21 | :custom_fields, 22 | :custom_values, 23 | :custom_fields_trackers, 24 | :time_entries, 25 | :journals, 26 | :journal_details, 27 | :issue_templates 28 | 29 | def setup 30 | User.current = nil 31 | enabled_module = EnabledModule.new 32 | enabled_module.project_id = 1 33 | enabled_module.name = 'issue_templates' 34 | enabled_module.save 35 | roles = Role.all 36 | roles.each do |role| 37 | role.permissions << :show_issue_templates 38 | role.remove_permission! :edit_issue_templates 39 | role.save 40 | end 41 | @request.session[:user_id] = 2 42 | @project = Project.find(1) 43 | end 44 | 45 | def test_index_without_project 46 | get :index 47 | assert_response :success 48 | assert_select 'h3', count: 0, text: I18n.t('issue_template') 49 | end 50 | 51 | def test_index 52 | get :index, params: { project_id: @project.id } 53 | assert_response :success 54 | assert_select 'div#template_area select#issue_template', false, 55 | 'Action index should not contain template select pulldown.' 56 | assert_select 'h3', text: I18n.t('issue_template') 57 | assert_select 'a', { href: "/projects/#{@project}/issue_templates/new" }, false 58 | end 59 | 60 | def test_index_with_edit_permission 61 | Role.find(1).add_permission! :edit_issue_templates 62 | get :index, params: { project_id: @project.id } 63 | assert_select 'h3', text: I18n.t('issue_template') 64 | assert_select 'a', href: "/projects/#{@project}/issue_templates/new" 65 | end 66 | 67 | def test_new 68 | get :new, params: { project_id: 1 } 69 | assert_response :success 70 | assert_select 'div#template_area select#issue_template' 71 | end 72 | 73 | # NOTE: When copy, template area should not be displayed. 74 | def test_copy 75 | get :new, params: { project_id: 1, copy_from: 1 } 76 | assert_response :success 77 | assert_select 'div#template_area', false 78 | end 79 | 80 | def test_new_without_project 81 | get :new 82 | assert_response :success 83 | assert_select 'div#template_area select#issue_template', true 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /test/functional/note_templates_controller_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../test_helper', __dir__) 2 | require 'minitest/autorun' 3 | 4 | class NoteTemplatesControllerTest < Redmine::ControllerTest 5 | fixtures :projects, :enabled_modules, 6 | :users, :roles, 7 | :members, :member_roles, 8 | :trackers, :projects_trackers, 9 | :note_templates, :note_visible_roles 10 | 11 | def setup 12 | @request.session[:user_id] = 2 # jsmith 13 | @request.env['HTTP_REFERER'] = '/' 14 | # Enabled Template module 15 | @project = Project.find(1) 16 | @project.enabled_modules << EnabledModule.new(name: 'issue_templates') 17 | @project.save! 18 | 19 | # Set default permission: show template 20 | Role.find(1).add_permission! :show_issue_templates 21 | end 22 | 23 | def test_index_with_non_existing_project_should_be_not_found 24 | # set non existing project 25 | get :index, params: { project_id: 100 } 26 | assert_response :not_found 27 | end 28 | 29 | def test_index_without_show_permission_should_be_forbidden 30 | Role.find(1).remove_permission! :show_issue_templates 31 | get :index, params: { project_id: 1 } 32 | assert_response :forbidden 33 | end 34 | 35 | def test_index_with_normal_should_be_success 36 | get :index, params: { project_id: 1 } 37 | assert_response :success 38 | end 39 | 40 | def test_index_with_admin_logged_in_should_appear_all_note_templates 41 | @request.session[:user_id] = 1 # admin 42 | 43 | ids = NoteTemplate.reorder(id: :asc).where(project_id: 1).pluck(:id) 44 | assert_equal [1, 2, 3, 4, 5], ids 45 | 46 | get :index, params: { project_id: 1 } 47 | assert_response :success 48 | 49 | assert_select 'table.template_list tbody tr.note_template' do 50 | ids.each do |id| 51 | assert_select 'td a[href=?]', "/projects/ecookbook/note_templates/#{id}", count: 1 52 | end 53 | end 54 | end 55 | 56 | def test_index_should_appear_note_templates_with_open_visibility 57 | ids = NoteTemplate.reorder(id: :asc).where(project_id: 1).open.pluck(:id) 58 | assert_equal [4], ids 59 | 60 | get :index, params: { project_id: 1 } 61 | assert_response :success 62 | 63 | assert_select 'table.template_list tbody tr.note_template' do 64 | ids.each do |id| 65 | assert_select 'td a[href=?]', "/projects/ecookbook/note_templates/#{id}", count: 1 66 | end 67 | end 68 | end 69 | 70 | def test_index_with_author_logged_in_should_appear_note_templates_with_mine_visibility 71 | user_id = 3 # dlopper 72 | @request.session[:user_id] = user_id 73 | Role.find(2).add_permission! :show_issue_templates 74 | 75 | ids = NoteTemplate.reorder(id: :asc).where(project_id: 1).mine_condition(user_id).pluck(:id) 76 | assert_equal [5], ids 77 | 78 | get :index, params: { project_id: 1 } 79 | assert_response :success 80 | 81 | assert_select 'table.template_list tbody tr.note_template' do 82 | ids.each do |id| 83 | assert_select 'td a[href=?]', "/projects/ecookbook/note_templates/#{id}", count: 1 84 | end 85 | end 86 | end 87 | 88 | def test_index_should_appear_note_templates_with_roles_visibility 89 | ids = NoteTemplate.reorder(id: :asc).where(project_id: 1).where(visibility: :roles).pluck(:id) 90 | assert_equal [2, 3], ids 91 | 92 | @request.session[:user_id] = 2 # jsmith 93 | Role.find(1).add_permission! :show_issue_templates 94 | 95 | get :index, params: { project_id: 1 } 96 | assert_response :success 97 | 98 | assert_select 'table.template_list tbody tr.note_template' do 99 | assert_select 'td a[href=?]', "/projects/ecookbook/note_templates/2", count: 1 100 | assert_select 'td a[href=?]', "/projects/ecookbook/note_templates/3", count: 0 101 | end 102 | 103 | @request.session[:user_id] = 3 # dlopper 104 | Role.find(2).add_permission! :show_issue_templates 105 | 106 | get :index, params: { project_id: 1 } 107 | assert_response :success 108 | 109 | assert_select 'table.template_list tbody tr.note_template' do 110 | assert_select 'td a[href=?]', "/projects/ecookbook/note_templates/2", count: 1 111 | assert_select 'td a[href=?]', "/projects/ecookbook/note_templates/3", count: 1 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /test/functional/projects_controller_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../test_helper') 2 | require 'projects_controller' 3 | 4 | class ProjectsControllerTest < Redmine::ControllerTest 5 | fixtures :projects, :users, :roles, :members, :member_roles, 6 | :trackers, :projects_trackers, :enabled_modules 7 | def setup 8 | # as project admin 9 | @request.session[:user_id] = 2 10 | Role.find(1).add_permission! :show_issue_templates 11 | # Enabled Template module 12 | @project = Project.find(1) 13 | @project.enabled_modules << EnabledModule.new(name: 'issue_templates') 14 | @project.save! 15 | end 16 | 17 | def test_settings 18 | get :show, params: { id: 1 } 19 | assert_response :success 20 | assert_select '#main-menu > ul > li > a.issue-templates' 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/integration/layout_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../test_helper') 2 | 3 | class LayoutTest < Redmine::IntegrationTest 4 | fixtures :projects, :trackers, :issue_statuses, :issues, 5 | :enumerations, :users, 6 | :projects_trackers, 7 | :roles, 8 | :member_roles, 9 | :members, 10 | :enabled_modules, 11 | :workflows, 12 | :issue_templates 13 | 14 | def test_issue_template_not_visible_when_module_off 15 | # module -> disabled 16 | log_user('admin', 'admin') 17 | post '/projects/ecookbook/modules', 18 | params: { enabled_module_names: ['issue_tracking'], commit: 'Save', id: 'ecookbook' } 19 | 20 | get '/projects/ecookbook/issues' 21 | assert_response :success 22 | assert_select 'h3', count: 0, text: I18n.t('issue_template') 23 | 24 | get '/projects/ecookbook/issues/new' 25 | assert_select 'div#template_area select#issue_template', 0 26 | end 27 | 28 | def test_issue_template_visible_when_module_on 29 | # module -> enabled 30 | log_user('admin', 'admin') 31 | post '/projects/ecookbook/modules', 32 | params: { enabled_module_names: %w[issue_tracking issue_templates], 33 | commit: 'Save', id: 'ecookbook' } 34 | 35 | get '/projects/ecookbook/issues' 36 | assert_response :success 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'simplecov' 3 | require 'simplecov-rcov' 4 | rescue LoadError => ex 5 | puts <<-"EOS" 6 | This test should be called only for redmine issue template test. 7 | Test exit with LoadError -- #{ex.message} 8 | Please move redmine_issue_templates/Gemfile.local to redmine_issue_templates/Gemfile 9 | and run bundle install if you want to to run tests. 10 | EOS 11 | exit 12 | end 13 | 14 | if ENV['JENKINS'] == 'true' 15 | SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter 16 | true 17 | else 18 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([SimpleCov::Formatter::HTMLFormatter]) 19 | end 20 | 21 | SimpleCov.coverage_dir('coverage/redmine_issue_templates_test') 22 | SimpleCov.start do 23 | add_filter do |source_file| 24 | # report this plugin only. 25 | !source_file.filename.include?('plugins/redmine_issue_templates') || !source_file.filename.end_with?('.rb') 26 | end 27 | end 28 | 29 | require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper') 30 | ActiveRecord::FixtureSet.create_fixtures(File.dirname(__FILE__) + '/fixtures/', 31 | %i[issue_templates issue_template_settings 32 | global_issue_templates global_issue_templates_projects 33 | note_templates note_visible_roles 34 | global_note_templates]) 35 | 36 | module Redmine 37 | class ControllerTest 38 | setup do 39 | Setting.text_formatting = 'textile' 40 | end 41 | 42 | teardown do 43 | Setting.delete_all 44 | Setting.clear_cache 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/unit/global_issue_templates_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../test_helper', __dir__) 2 | 3 | class GlobalIssueTemplatesTest < ActiveSupport::TestCase 4 | fixtures :global_issue_templates, :users, :trackers 5 | 6 | def setup 7 | @global_issue_template = GlobalIssueTemplate.find(1) 8 | end 9 | 10 | def test_truth 11 | assert_kind_of GlobalIssueTemplate, @global_issue_template 12 | end 13 | 14 | def test_template_enabled 15 | enabled = @global_issue_template.enabled? 16 | assert_equal true, enabled, @global_issue_template.enabled? 17 | 18 | @global_issue_template.enabled = false 19 | @global_issue_template.save! 20 | enabled = @global_issue_template.enabled? 21 | assert_equal false, enabled, @global_issue_template.enabled? 22 | end 23 | 24 | def test_sort_by_position 25 | a = GlobalIssueTemplate.new(title: 'Template4', position: 2, tracker_id: 1) 26 | b = GlobalIssueTemplate.new(title: 'Template5', position: 1, tracker_id: 1) 27 | assert_equal [b, a], [a, b].sort 28 | end 29 | 30 | def test_required_attributes_should_be_validated 31 | { 32 | title: ' ', 33 | tracker: nil, 34 | description: " \n\n ", 35 | }.each do |attr, val| 36 | @global_issue_template.reload 37 | @global_issue_template.__send__("#{attr}=", val) 38 | 39 | assert_raises ActiveRecord::RecordInvalid do 40 | @global_issue_template.save! 41 | end 42 | 43 | assert_includes @global_issue_template.errors[attr], 'cannot be blank' 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /test/unit/global_note_template_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../test_helper') 4 | 5 | class GlobalNoteTemplateTest < ActiveSupport::TestCase 6 | fixtures :projects, :users, :trackers, :roles, :global_note_templates 7 | 8 | def setup; end 9 | def teardown; end 10 | 11 | def test_create_should_require_tracker 12 | template = GlobalNoteTemplate.new(name: 'GlobalNoteTemplate1', visibility: 'open', description: 'description1') 13 | assert_no_difference 'GlobalNoteTemplate.count' do 14 | assert_raises ActiveRecord::RecordInvalid do 15 | template.save! 16 | end 17 | end 18 | assert_equal ['Tracker cannot be blank'], template.errors.full_messages 19 | end 20 | 21 | def test_required_attributes_should_be_validated 22 | template = GlobalNoteTemplate.find(1) 23 | { 24 | name: ' ', 25 | tracker: nil, 26 | description: " \n\n ", 27 | }.each do |attr, val| 28 | template.reload 29 | template.__send__("#{attr}=", val) 30 | 31 | assert_raises ActiveRecord::RecordInvalid do 32 | template.save! 33 | end 34 | 35 | assert_includes template.errors[attr], 'cannot be blank' 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/unit/issue_template_setting_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../test_helper') 2 | 3 | class IssueTemplateSettingTest < ActiveSupport::TestCase 4 | fixtures :issue_template_settings, :projects 5 | 6 | def setup 7 | @issue_template_setting = IssueTemplateSetting.find(1) 8 | end 9 | 10 | def test_truth 11 | assert_kind_of IssueTemplateSetting, @issue_template_setting 12 | end 13 | 14 | def test_help_message_enabled 15 | enable_help = @issue_template_setting.enable_help? 16 | assert_equal(true, enable_help) 17 | assert_equal(false, !enable_help) 18 | end 19 | 20 | def test_duplicate_project_setting 21 | templ = IssueTemplateSetting.find_or_create(3) 22 | templ.attributes = { enabled: true, help_message: 'Help!' } 23 | assert templ.save!, 'Failed to save.' 24 | 25 | # test which has the same proect id 26 | templ2 = IssueTemplateSetting.new 27 | templ2.attributes = { project_id: 1, enabled: true, help_message: 'Help!' } 28 | assert !templ2.save, 'Dupricate project should be denied.' 29 | end 30 | 31 | def test_help_message_disabled 32 | # load disabled template setting 33 | issue_template_setting = IssueTemplateSetting.find(2) 34 | enable_help = issue_template_setting.enable_help? 35 | assert_equal(false, enable_help) 36 | end 37 | 38 | def test_find_template_setting 39 | # for Project 6 40 | issue_template_setting = IssueTemplateSetting.find_or_create(6) 41 | assert_kind_of IssueTemplateSetting, issue_template_setting 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/unit/issue_template_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../test_helper') 2 | 3 | class IssueTemplateTest < ActiveSupport::TestCase 4 | fixtures :issue_templates, :projects, :users, :trackers 5 | 6 | def setup 7 | @issue_template = IssueTemplate.find(1) 8 | end 9 | 10 | def test_truth 11 | assert_kind_of IssueTemplate, @issue_template 12 | end 13 | 14 | def test_template_enabled 15 | enabled = @issue_template.enabled? 16 | assert_equal true, enabled, @issue_template.enabled? 17 | 18 | @issue_template.enabled = false 19 | @issue_template.save! 20 | enabled = @issue_template.enabled? 21 | assert_equal false, enabled, @issue_template.enabled? 22 | end 23 | 24 | def test_sort_by_position 25 | a = IssueTemplate.new(title: 'Template1', position: 2, project_id: 1, tracker_id: 1) 26 | b = IssueTemplate.new(title: 'Template2', position: 1, project_id: 1, tracker_id: 1) 27 | assert_equal [b, a], [a, b].sort 28 | end 29 | 30 | def test_is_default 31 | # Reset default data 32 | IssueTemplate.update_all(is_default: false) 33 | assert !@issue_template.is_default? 34 | 35 | @issue_template.is_default = true 36 | @issue_template.save! 37 | assert @issue_template.is_default? 38 | 39 | templates = IssueTemplate.search_by_project(1).search_by_tracker(1).not_default 40 | templates.each do |template| 41 | assert !template.is_default? 42 | end 43 | end 44 | 45 | def test_required_attributes_should_be_validated 46 | { 47 | project_id: nil, 48 | title: ' ', 49 | tracker: nil, 50 | description: " \n\n ", 51 | }.each do |attr, val| 52 | @issue_template.reload 53 | @issue_template.__send__("#{attr}=", val) 54 | 55 | assert_raises ActiveRecord::RecordInvalid do 56 | @issue_template.save! 57 | end 58 | 59 | assert_includes @issue_template.errors[attr], 'cannot be blank' 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url'; 2 | 3 | import { defineConfig } from 'vite'; 4 | import vue2 from '@vitejs/plugin-vue2'; 5 | 6 | export default defineConfig({ 7 | plugins: [ 8 | vue2(), 9 | ], 10 | build: { 11 | rollupOptions: { 12 | input: { 13 | issue_templates: '/scripts/issue_templates.js', 14 | }, 15 | output: { 16 | entryFileNames: '[name].js', 17 | }, 18 | }, 19 | outDir: 'assets/javascripts', 20 | }, 21 | server: { 22 | port: 5244, 23 | }, 24 | resolve: { 25 | alias: { 26 | '^': fileURLToPath(new URL('./scripts', import.meta.url)) 27 | } 28 | } 29 | }); 30 | --------------------------------------------------------------------------------