Repository: akiko-pusu/redmine_issue_templates Branch: master Commit: 86fbfc17eb92 Files: 166 Total size: 423.0 KB Directory structure: gitextract_qmbduvvp/ ├── .circleci/ │ └── config.yml ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ └── greetings.yml ├── .gitignore ├── .rubocop.yml ├── .rubocop_todo.yml ├── Dockerfile ├── Gemfile.local ├── ISSUE_TEMPLATE.md ├── LICENSE.txt ├── README.md ├── RELEASE-NOTES.md ├── _config.yml ├── 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/ │ │ │ └── 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/ │ ├── javascripts/ │ │ ├── issue_templates.js │ │ └── template_fields.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 ├── docker-compose.yml ├── goodcheck.yml ├── init.rb ├── lang/ │ └── en.yml ├── lib/ │ ├── issue_templates/ │ │ ├── issues_hook.rb │ │ └── journals_hook.rb │ └── tasks/ │ ├── test.rake │ └── util.rake ├── script/ │ └── circleci-setup.sh ├── 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 │ │ ├── projects.rb │ │ ├── role.rb │ │ ├── trackers.rb │ │ └── users.rb │ ├── features/ │ │ ├── admin_spec.rb │ │ ├── drag_and_drop_spec.rb │ │ ├── issue_template_popup_spec.rb │ │ ├── issue_template_spec.rb │ │ └── update_issue_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 │ ├── issue_template_settings.yml │ └── issue_templates.yml ├── functional/ │ ├── global_issue_templates_controller_test.rb │ ├── issue_templates_controller_test.rb │ ├── issue_templates_settings_controller_test.rb │ ├── issues_controller_test.rb │ └── projects_controller_test.rb ├── integration/ │ └── layout_test.rb ├── test_helper.rb └── unit/ ├── global_issue_templates_test.rb ├── issue_template_setting_test.rb ├── issue_template_test.rb └── note_template_test.rb ================================================ FILE CONTENTS ================================================ ================================================ FILE: .circleci/config.yml ================================================ # Ruby CircleCI 2.0 configuration file # # Check https://circleci.com/docs/2.0/language-ruby/ for more details # version: 2 general: artifacts: - coverage/${CIRCLE_PROJECT_REPONAME}_test/index.html jobs: build: docker: # specify the version you desire here (ruby 2.6.x) - image: circleci/ruby:2.6-browsers-legacy steps: - checkout test: docker: # specify the version you desire here - image: circleci/ruby:2.6-browsers-legacy environment: RAILS_ENV: test DB_HOST: 127.0.0.1 DRIVER: headless TZ: /usr/share/zoneinfo/Asia/Tokyo - image: mysql:5.7 command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --innodb-large-prefix=true --innodb-file-format=Barracuda --sql-mode="" environment: MYSQL_USER: root MYSQL_ALLOW_EMPTY_PASSWORD: yes working_directory: ~/repo steps: - checkout - run: name: ready for redmine command: | REDMINE_BRANCH=master sh script/circleci-setup.sh - run: perl -pi -e "s/gem \"capybara\".*$/gem \"capybara\"/g" Gemfile - run: bundle install --path vendor/bundle --without postgresql rmagick && bundle update - run: bundle exec rails g rspec:install - run: bundle exec rake db:create RAILS_ENV=test - run: bundle exec rake db:migrate RAILS_ENV=test - run: bundle exec rake redmine:plugins:migrate RAILS_ENV=test - run: command: | bundle exec rake ${CIRCLE_PROJECT_REPONAME}:test RAILS_ENV=test - run: command: | bundle exec rspec -I plugins/redmine_issue_templates/spec --format documentation plugins/redmine_issue_templates/spec/models/ - run: command: | bundle exec rspec -I plugins/redmine_issue_templates/spec --format documentation plugins/redmine_issue_templates/spec/helpers/ - run: command: | bundle exec rspec -I plugins/redmine_issue_templates/spec --format documentation plugins/redmine_issue_templates/spec/controllers/ - run: command: | bundle exec rspec -I plugins/redmine_issue_templates/spec --format documentation plugins/redmine_issue_templates/spec/requests/ - run: command: | bundle exec rspec -I plugins/redmine_issue_templates/spec --format documentation plugins/redmine_issue_templates/spec/features/ - run: command: | bundle exec rake redmine:plugins:migrate NAME=${CIRCLE_PROJECT_REPONAME} \ VERSION=0 RAILS_ENV=test - run: command: | mkdir -p /tmp/coverage cp -r coverage/${CIRCLE_PROJECT_REPONAME}_test /tmp/coverage/ cp -r coverage/${CIRCLE_PROJECT_REPONAME}_spec /tmp/coverage/ - store_artifacts: path: /tmp/coverage workflows: version: 2 build_and_test: jobs: - build - test: requires: - build filters: branches: ignore: - /v0.2.x-support-Redmine3.*/ ================================================ FILE: .gitattributes ================================================ .gitattributes export-ignore .gitignore export-ignore wercker.yml export-ignore script/ export-ignore .rubocop* export-ignore Gemfile.local export-ignore .circleci/ export-ignore docker-compose.yml export-ignore Dockerfile export-ignore _config.yml export-ignore ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms ko_fi: akikopusu ================================================ FILE: .github/workflows/greetings.yml ================================================ name: Greetings on: [issues] jobs: greeting: runs-on: ubuntu-latest steps: - uses: actions/first-interaction@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} issue-message: 'Thank you for contributing to Redmine Issue Templates plugin!'' first issue' pr-message: 'Thanks you for contributing to Redmine Issue Templates plugin!'' first pr' ================================================ FILE: .gitignore ================================================ coverage/ doc/ .yardoc/ Gemfile.lock Gemfile .history .wercker ================================================ FILE: .rubocop.yml ================================================ inherit_from: .rubocop_todo.yml require: - rubocop-rails AllCops: TargetRubyVersion: 2.4 Exclude: - 'db/**/*' ================================================ FILE: .rubocop_todo.yml ================================================ # This configuration was generated by # `rubocop --auto-gen-config` # on 2016-06-01 07:37:41 +0900 using RuboCop version 0.40.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # Offense count: 3 Metrics/AbcSize: Max: 55 # Offense count: 1 # Configuration parameters: CountComments. Metrics/ClassLength: Max: 200 Exclude: - 'spec/**/*' - 'test/**/*' # "Line is too long"を無効 Layout/LineLength: Enabled: false # Offense count: 1 Metrics/CyclomaticComplexity: Max: 10 # Offense count: 1 Metrics/PerceivedComplexity: Max: 10 Metrics/BlockLength: Max: 30 Exclude: - 'spec/**/*' - 'test/**/*' # Avoid methods longer than 10 lines of code MethodLength: CountComments: true # count full line comments? Max: 45 # Aboid Missing top-level module documentation comment. Documentation: Enabled: false EndOfLine: Enabled: false Metrics/ModuleLength: Max: 120 Rails/ApplicationRecord: Enabled: false Rails/UniqueValidationWithoutIndex: Enabled: false Rails/InverseOf: Enabled: false Rails/LexicallyScopedActionFilter: Enabled: false Rails/SkipsModelValidations: Enabled: false ================================================ FILE: Dockerfile ================================================ FROM ruby:2.6 LABEL maintainer="AKIKO TAKANO / (Twitter: @akiko_pusu)" \ description="Image to run Redmine simply with sqlite to try/review plugin." ### get Redmine source ### Replace shell with bash so we can source files ### RUN rm /bin/sh && ln -s /bin/bash /bin/sh ### install default sys packeges ### RUN apt-get update RUN apt-get install -qq -y \ git vim \ sqlite3 default-libmysqlclient-dev RUN apt-get install -qq -y build-essential libc6-dev # for e2e test env RUN sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - RUN apt-get update && apt-get install -y google-chrome-stable RUN google-chrome --version | perl -pe 's/([^0-9]+)([0-9]+)(\.[0-9]+).+/$2/g' > chrome-version-major RUN curl https://chromedriver.storage.googleapis.com/LATEST_RELEASE_`cat chrome-version-major` > chrome-version RUN curl -O -L http://chromedriver.storage.googleapis.com/`cat chrome-version`/chromedriver_linux64.zip && rm chrome-version* RUN unzip chromedriver_linux64.zip && mv chromedriver /usr/local/bin RUN cd /tmp && svn co http://svn.redmine.org/redmine/trunk redmine WORKDIR /tmp/redmine COPY . /tmp/redmine/plugins/redmine_issue_templates/ # add database.yml (for development, development with mysql, test) RUN echo $'test:\n\ adapter: sqlite3\n\ database: /tmp/data/redmine_test.sqlite3\n\ encoding: utf8mb4\n\ development:\n\ adapter: sqlite3\n\ database: /tmp/data/redmine_development.sqlite3\n\ encoding: utf8mb4\n\ development_mysql:\n\ adapter: mysql2\n\ host: mysql\n\ password: pasword\n\ database: redemine_development\n\ username: root\n'\ >> config/database.yml RUN gem update bundler RUN bundle install RUN bundle exec rake db:migrate EXPOSE 3000 CMD ["rails", "server", "-b", "0.0.0.0"] ================================================ FILE: Gemfile.local ================================================ group :test do gem 'simplecov-rcov', require: false gem 'rspec-rails' gem 'factory_bot_rails' gem 'launchy' gem 'database_cleaner' end # for Debug group :development, :test do gem 'pry-rails' gem 'pry-byebug' end ================================================ FILE: ISSUE_TEMPLATE.md ================================================ This is a template to report bug related to redmine issue templates plugin. You can fill in any format, free style in case report features or questions. ## Summary ## Description ## Environment These informations are greatly helpful to support quickly. You can get these informations from Redmine's information page. (E.g. http://example.com/admin/info) - Redmine version - Installed plugins - Ruby version - OS Platform - Database (MariaDB, MySQL, PostgreSQL) and its version - Rails Env ## Visual Proof / Screenshot When reporting an application error, post the error stack trace that you should find in the **log file** (eg. log/production.log). Screen shot would be also helpful. ## Expected Results ## Actual Results ## Workaround Please let me know if you have any workaround. ================================================ FILE: LICENSE.txt ================================================ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ================================================ FILE: README.md ================================================ # Redmine Issue Templates Plugin [![Plugin info at redmine.org](https://img.shields.io/badge/Redmine-plugin-green.svg?)](http://www.redmine.org/plugins/redmine_issue_templates) [![CircleCI](https://circleci.com/gh/akiko-pusu/redmine_issue_templates/tree/master.svg?style=shield)](https://circleci.com/gh/akiko-pusu/redmine_issue_templates/tree/master) [![Sider](https://img.shields.io/badge/Special%20Thanks!-Sider-blue.svg?)](https://sider.review/features) Plugin to generate and use issue templates for each project to assist issue creation. The latest version 1.0.x **is not compatible with IE11**. (Related: #310) Please use version 0.3.8 or **[0.3-stable](https://github.com/akiko-pusu/redmine_issue_templates/tree/0.3-stable) branch** (uing jQuery version) as a stable release for Redmine4.x. ## Repository * ## Plugin installation 1. Copy the plugin directory into the $REDMINE_ROOT/plugins directory. Please note that plugin's folder name should be "redmine_issue_templates". If changed, some migration task will be failed. 2. Do migration task. e.g. rails redmine:plugins:migrate RAILS_ENV=production 3. (Re)Start Redmine. ## Uninstall Try this: * rails redmine:plugins:migrate NAME=redmine_issue_templates VERSION=0 RAILS_ENV=production ### When migration error If the migration is cancelled with the error like following message for the first time you try to install this plugin: > Caused by: Mysql2::Error: Table 'DATABASE_FOR_REDMINE.issue_templates' doesn't exist You can fix this error to remove migration records related to this plugin from shema_migrations table. If you can access and select database for Redmine, try this command: ```sql select * from schema_migrations where version like '%redmine_issue_templates%'; ``` If there are any records shown like this and there is no table named 'issue_templates', your installation has been incomplete state. ```sql 1-redmine_issue_templates 2-redmine_issue_templates ``` So, you should better to uninstall task first, and retry the migration. If you have not created any template records yet, and hope to uninstall and re-install this plugin, please see README. **Uninstall:** ```ruby rails db:migrate_plugins NAME=redmine_issue_templates VERSION=0 RAILS_ENV=production ``` After that, records of migration are removed from schema_migrations table. **Re-install:** ```ruby rails db:migrate_plugins NAME=redmine_issue_templates RAILS_ENV=production (for Redmine4.x) ``` **Related issue:** * * * ## Required Settings 1. Login to your Redmine install as an Administrator 2. Enable the permissions for your Roles: * Show issue templates: User can show issue templates and use templates when creating/updating issues. * Edit issue templates: User can create/update/activate templates for each project. * Manage issue templates: User can edit help message of templates for each project. 3. Enable the module "Issue Template" on the project setting page. 4. The link to the plugin should appear on that project's navigation. ## Note This plugin aims to assist contributor's feedback by using template if the project has some format for issues. ## Troubleshoot for bundle intall and startup problem This plugin repository includes some test code and gem settings. If you have some trouble related "bundle install", please try --without option. > Exp. bundle install --without test ## WebPage * (Redmine Plugin List) * (Repository & Issues) If you have any requests, bug reports, please use GitHub issues. ## Description and usage info * ## Changelog ### 1.1.0 Drop off the feature to integrate with Checklist plugin, for maintenance reason. Please see for more details: ### 1.0.5 Bugfix and final release to support Checklist integration. Please see: * Bugfix: template_type is not defined error (GitHub: #364 / Thanks for reporting issue, @toku463ne) ### 1.0.4 Release to implemented some additional built-in/custom fields support. * Feature: Add preselected watchers to templates. (GitHub: #302) * Feature: Enabled to define assignees and categories. (GitHub: #362) * Bugfix: Template duplicates when changing Status or Category fields. (GitHub: #354) * Bugfix: Template body not loaded into issue answer (v.1.0.3 only) (GitHub: #356) * Update JavaScript and Spec. Thank you for the valuable information and feedback, @ChrisUHZ! RESTRICTION: This version **is still not compatible with IE11**. (Related: #310) ### 1.0.3 NOTE: Mainly, maintenance, bugfix and refactoring only. There is no additional feature. * Refactor JavaScript to work properly under jQuery 3.x (for Redmine trunk). * Add some feature specs to test Builtin-fields support. RESTRICTION: This version **is still not compatible with IE11**. (Related: #310) ### 1.0.2 Release to implememted Global note templates feature. NOTE: **Migration is required** to use global note template. * Feature: Implement Global Note Template. (GitHub: #268, #336) * Feature: Improve the input form for built-In / custom fields setting. (GitHub: #345) * Bugfix: Selecting note template browser "jumps" to top of page. (GitHub: #338) * Bugfix: Change to make the selector more specific. Thanks, @sandratatarevicova (GitHub: #332, #333) * Apply Bulgarian translation. Thanks, @jwalkerbg (GitHub: #330) * Update README: `--without` argument for `bundle` is no longer necessary. (GitHub: #335 / by @vividtone) * Update German Translation (by Christian Friebel). RESTRICTION: This version **is still not compatible with IE11**. (Related: #310) ### 1.0.1 This is bugfix release against v1.0.0. Updating to 1.0.1 is highly recommended, if you're using 1.0.0. Migration is also required. * Bugfix: Can't create a new templates optional settings. (GitHub: #322) * Migration: Change the column type to text. (GitHub: #323) * Update JavaScript. Thank you for the valuable information and feedback, @AlUser71! ### 1.0.0 RESTRICTION: This version **is not compatible with IE11**. (Related: #310) Please use version **0.3.8** or **[0.3-stable](https://github.com/akiko-pusu/redmine_issue_templates/tree/0.3-stable) branch** (uing jQuery version) if you need to support IE11. NOTE: **Migration is required**. Since ``Support Built-In / Custom Fields`` is an experimental feature, please **be careful** if you hope to try it. * Feature: Add feature to show template usage / example (#303) * Using Vue.js v2.6.11 * Feature: Support Built-In / Custom Fields (#304) * Rewrite JavaSctipt code from jQuery into plain JavaScript. And some browsers may not work fine because Support Built-In / Custom Fields feature uses Vue.js for frontend. So feedback, issue report, suggestion highly appreciate! ### 0.3.8 This is bugfix release. * Bugfix: Fix that Issue Templates plugin changes the cursor icon for "Information" menu on Redmine's administration page (by vividtone, GitHub #316) * Bugfix: Orphaned template list is not displayed (GitHub #337) * Update Russian translation (GitHub #340) * Update Bulgarian translation (GitHub #329) * Update Korean translation (update Korean translation) * Bugfix: enabled to create a new issue template setting. (GitHub #322) ### 0.3.7 This is bugfix release to prevent the conflict with other plugins. * Bugfix: Tooltip for template body preview is hidden. (GitHub PR #300) * Refactor: Change to use project menu to prevent the project setting tab's conflict. (GitHub PR #299) Thank you for the valuable information and feedback, @ChrisUHZ! ### 0.3.6 This is bugfix release against v0.3.5. Updating to 0.3.6 is highly recommended! * Update zh-TW locale. #281 (by Vongola) * Refactor: Update test code / Change Validation check. * Add troubleshooting for migration error and uninstall. * Add workaround to prevent other plugin's conflict. (#282) * Add workaround to load right templates if the project has subproject and subproject selected. (#289) * Apply the patch by @dmakurin to prevent the error when the user can't edit tracker id. (#288) * Only wipe issue subject and description if replace flag. (#284, Applied Pull Request by @mattgill) ### 0.3.5 NOTE: This version requires migration command to enhance note template's feature. ``Note Template visibility per role`` feature is still a prototype, so feedback highly appreciate! * Design: PR / Mrliptontea theme compatibility #266 (by mrliptontea) * Bugfix: #270 / Apply polyfill code for IE11. (reported by yui-har) * Feature: Note Template visibility per role. #267 * Bugfix: Fix the request URL for accessing note_templates/load #261 (by ishikawa999) * Bugfix: Note Template does not work on CKEDitor. #275 * Update README for contribution #273 ### 0.3.4 This is bugfix release against v0.3.3. * Add navigation link between issue template and note template. * Refactor: Change to use let / const instead of var. * Update test environment, especially E2E. (Follow up Redmine4.1) * Bugfix #256 / Related to checklists. ### 0.3.3 This is bugfix release against v0.3.2. Updating to 0.3.3 is highly recommended! * Revert and Bugfix #230 * Merge pull request #252 from ishikawa999/fix/248 by @ishikawa999 * Bugfix: #234 / Enable to save checklists when updating a template. ### 0.3.2 * Bugfix: Adding issue templates with checklists occurs internal error.(#243) * Merge PR commit: bca2fe481 by @two-pack, restored missing newline. (Related: #242) * Feature: Add clear subject/body option when tracker changed which has no template. (#230) * Code refactoring. ### 0.3.1 * Basic feature implemented of note template. * Enabled to use issue templates when updating issue. * Go to global template admin setting, and turn on "apply_template_when_edit_issue" flag. * Bugfix: Prevent conflict against issue controller helper. (#217) * Update readme: Merged PR #219. Thanks Arnaud Venturi! NOTE: This version requires migration command to use note template feature. ```bash rails redmine:plugins:migrate RAILS_ENV=production ``` ### 0.3.0 * Support Redmine 4.x. * Now master branch unsupports Redmine 3.x. * Please use ver **0.2.x** or ``v0.2.x-support-Redmine3`` branch in case using Redmine3.x. * Follow Redmine's preview option to the wiki toolbar. * Show additional navigation message when plugin is applied to Redmine 3.x. NOTE: Mainly, maintenance, bugfix and refactoring only. There is no additional feature, translation in this release. Thank you for creating patch, Mizuki Ishikawa! ### 0.2.1 Mainly, bugfix and refactoring release. Updating to 0.2.1 is highly recommended in case using CKEditor or MySQL replication. NOTE: Migration is required, especially using MySQL replication. * Bugfix: Fix "Page not found" error when try to create project template from project setting. (GitHub: #192, #199) * Bugfix: Add composite unique index to support MySQL group replication. (GitHub: #197) * Workaround: Wait fot 200 msec until CKE Editor's ajax callback done. (GitHub: #193) * Add feature to hide confirmation dialog when overwritten issue subject and description, with using user cookie. (GitHub: #190) * Refactoring: Minitest and so on. A cookie named "issue_template_confirm_to_replace_hide_dialog" is stored from this release. (Related: #190) ### 0.2.0 Bugfix and refactoring release. Updating from v0.1.9 to 0.2.0 is highly recommended. In this release, some methods which implemented on Redmine v3.3 are ported for plugin's compatibility. (To support Redmine 3.0 - 3.4) * Bugfix: Prevent to call unimplemened methods prior to Redmine3.2. (GitHub: #180) * Refactoring: Code format. (JS, CSS) / Update config for E2E test. * Updated Simplified Chinese translation, thanks Steven.W. (GitHub PR: #179) * Applied responsive layout against template list (index) page. Thank you for reviewing, Tatsuya Saito! For release notes before v0.2.0, please see: [RELEASE-NOTES.md](RELEASE-NOTES.md) ### Contributing Pull requests, reporting issues, stars are always welcome! I'm always thrilled to receive pull requests, and do my best to process them as fast as possible. Not sure if that typo is worth a pull request? Do it! I will appreciate it. * Fork it! * Create your feature branch: git checkout -b my-new-feature * Commit your changes: git commit -am 'Add some feature' * Push to the branch: git push origin my-new-feature * Submit a pull request :D ### Language and I18n contributors * Brazilian: Adriano Ceccarelli / Pedro Moritz de Carvalho Neto * Korean: Jaebok Oh * Chinese: Steven Wong, vongola12324 (zh-TW) * Bulgarian: Ivan Cenov * Russian: Denny Brain, danaivehr * German: Terence Miller, Christian Friebel and anonymous contributor * French: Anonymous one * Serbian: Miodrag Milic * Polish: Paweł Budikom and Krzysztof Wosinski * Spanish: Andres Arias * Italian: Luca Lesinigo * Danish: AThomsen ### Rake Tasks You can see rake task, with (bundle exec) rake -T, related to this plugin. Exp. ```bash # Apply inhelit template setting to child projects $ rake redmine_issue_templates:apply_inhelit_template_to_child_projects[project_id] # Run test for redmine_issue_template plugin $ rake redmine_issue_templates:default # Run spec for redmine_issue_template plugin $ rake redmine_issue_templates:spec # Run tests $ rake redmine_issue_templates:test # Unapply inhelit template setting from child projects $ rake redmine_issue_templates:unapply_inhelit_template_from_child_projects[project_id] # Generate YARD Documentation for redmine_issue_template plugin $ rake redmine_issue_templates:yardoc ``` You can apply/unapply inherit templates for all the hild projects. ```bash rake redmine_issue_templates:apply_inhelit_template_to_child_projects[project_id] # Apply inhelit template setting to child projects rake redmine_issue_templates:unapply_inhelit_template_from_child_projects[project_id] # Unapply inhelit template setting from child projects ``` If you want to apply inherit templates setting all the child project of project_id: 1 (as parent project), please run rake command like this: rake redmine_issue_templates:apply_inhelit_template_to_child_projects[1] ### Run test Please see .circleci/config.yml for more details. ```bash % cd REDMINE_ROOT_DIR % cp plugins/redmine_issue_templates/Gemfile.local plugins/redmine_issue_templates/Gemfile % bundle install --with test % export RAILS_ENV=test % bundle exec ruby -I"lib:test" -I plugins/redmine_issue_templates/test plugins/redmine_issue_templates/test/functional/issue_templates_controller_test.rb ``` or ```bash % bundle exec rails redmine_issue_templates:test ``` #### Run spec Please see .circleci/config.yml for more details. ```bash % cd REDMINE_ROOT_DIR % cp plugins/redmine_issue_templates/Gemfile.local plugins/redmine_issue_templates/Gemfile % bundle install --with test % export RAILS_ENV=test % bundle exec rspec -I plugins/redmine_issue_templates/spec --format documentation plugins/redmine_issue_templates/spec/ ``` By default, use chrome as a webdriver. If you set environment variable 'DRIVER' to 'headless', headless_chrome is used. ```bash % DRIVER='headless' bundle exec rspec -I plugins/redmine_issue_templates/spec --format documentation plugins/redmine_issue_templates/spec/ ``` ### License This software is licensed under the GNU GPL v2. ================================================ FILE: RELEASE-NOTES.md ================================================ # Releas notes before v0.2.0 ### 0.1.9 Bugfix and refactoring release. * Bugfix: Fix wrong template sort ordering. (GitHub: #174) * Change UI to reorder templates with using drag and drop. * Add feature to copy template (Now project scope template only.) * Code refactoring. Use Headless Chrome for feature spec. Change to use CircleCI for build and test. * PR: Update Bulgarian translation. Thank you so much, Ivan Cenov! (GitHub: #171) * PR: Update Update pt-BR.yml Thank you so much, Adriano Baptistella! (GitHub: #173) * Bugfix: Wrong column label in "Preview Template Contents" modal dialog. (GitHub: #154) * PR: Updates to German language file. Thank you so much, Tobias Fischer! (GitHub: #164) ### 0.1.8 Bugfix release. * Bugfix: Prevent "undefined local variable or method" error when listing project orphaned templates. (GitHub: #150) * PR: Add Portuguese translation. Thank you so much, Adriano Baptistella! (GitHub: #149) * Change url of Redmine Plugin Directory. (Changed identifier from issue_templates to redmine_issue_templates.) ### 0.1.7 Bugfix release, and some code refactorings. #### Bugfix * After related tracker is removed, index (list) templates failed with exception. (#139) * Checklist not loading from Template. (#141) #### Refactoring * Remove all unlodable statement. * Remove unused rake task. * Rename modules. ### 0.1.6 Maintenance release to follow Redmine's update, and some refactoring related to test, namespace. Other additional updates are following: * Change support Redmine version to 3.0 or higher. * Stop to use jbuilder for rendering json. (#124) * Now any gemfile is not used. * PR: UI improvement / Correct CSS. Thanks taqueci! (#120, #123) * Bugfix: Add exception handler and not to work rake task if rake task name is not specified. (#130) * Logging if template is deleted. (#118) * Change Template UI related to delete action. (#117) * Prevent unexpected deletion of template. * PR: Add to confirm before replacing description and subject. Thanks, Tatsuya Saito. (#111) * PR: Fix CSS setting. Thanks, Tatsuya Saito. (#110) * Updated Simplified Chinese translation, thanks Steven.W. (#105, #113) ### 0.1.5 NOTE: Please run "rake redmine:plugins:migrate" task because new column is added. #### Feature for Global issue templates * Add feature enabled to mark global issue template as "default". * Add plugin setting option to apply global issue templates to all the project. * This option is on the plugin configuration screen. Please read help content before activate this option! #### Other updates * Update Russian translation. Thanks danaivehr! (GitHub: #95) * Prevent to locate template pulldown above "tracker" field and soon after jump below "tracker" field. (GitHub: #96) * Unselect projects on global issue template edit screen does not work correctly. (GitHub: #99) * Feature: Add “Revert” Icon to revert applied template. (Github: #98) * Change the place of message to notice "default template loaded" now to bottom of the page. * Change the place of "Check all | Uncheck all” link in global issue template create / edit screen, to above the project list. (GitHub: #90) * In case the list of projects is very long and would be much more comfortable to have the option on top. * Project select checkbox area is collapsed by default. * Also, in case the list of projects is very long, administrator has to scroll to submit "Save" button. ### 0.1.4.1 Bugfix version for #83, #92. Correct some methods not to use named parameters, because ruby 1.9x does not support named parameters. * Bugfix: GitHub: #83, #92 * Bug: Italian translation should be start with "it". (GitHub: #87) ### 0.1.4 Maintenance release to follow Redmine's update, and some refactoring related to test, namespace. Other additional updates are following: * Change css and default width setting for template filter modal dialog. (GitHub: #78) * Add Italian translation. Thank you so much, Luca Lesinigo! (GitHub: #75) * Add Danish translation. Thank you so much, AThomsen! (GitHub: #68) * Do not append template contents if the content hasn't been edited. (GitHub: #62) * Add rake task to apply/unapply inherit templates for all the hild projects. (GitHub: #61) ### 0.1.3 NOTE: Please run "rake redmine:plugins:migrate" task because new column is added. * Code refactoring. (Thank you so much for SideCI!) * Enabled to use template in case no project parameter passed. (GitHub: #43) * Updated the German locale. Thanks, jwciss! * Template is loaded after the error when required fields are not filled. (GitHub: #50) * First implement to integrate Checklist plugin. (GitHub: #39) ### 0.1.2 * Move repository from Bitbucket to Github. * Add Spanish translation. Thanks, Andres Arias (r-labs #1413) * Add popup to preview template description and filter template. (Featured: r-labs 1410) * Bugfix: Prevent to load default template in case updated issue form triggered by states change event. (Related: bitbucket#36, Template loads every time Status is changed.) * Update Simplified Chinese Localization. Thanks, Steven Wong. * Support REST API with json format. (prototype. r-labs #1324) * Code refactoring. ### 0.1.1 Bugfix release. * Update Brazilian translation file. (Bitbucket Pull Request: #4) * Removed deprecation warnings and adjusted gemfile to redmine 3.1.x (Bitbucket Pull Request: #5) * Bug fix, prevent load template and overwrite description when status changed. (Bitbucket Issue: #36) * Hide templates Element on Trackers without Issue Template. (Bitbucket Issue: #57) * Bug fix, when issue create issue from copy, template should not overwrite description. (Bitbucket Issue: #70) Special thanks all contributors, and Mattani-san, to this release. ### 0.1.0 NOTE: Please run "rake redmine:plugins:migrate" task because new column is added. * Support Redmine 3.0. (r-labs: #1366) * Add Sort to Global Templates. (r-labs: #1364) * Add Polish translation file. (r-labs: #1354) ### 0.0.9 Bug fix release. * Fix bug on ruby 1.8. (#52) * Remove feature to use JQuery tooltip to preview description, because useless. (#50) * Change css definition to avoide conflict with Redmine's base style. (#45) * Correct migration file to prevent uninstall error. (Related: #54) ### 0.0.8 NOTE: Please run "rake redmine:plugins:migrate" task because new column is added. * Fix some bugs. * Support global issue templates. * Try to use JQueryUI's tooltip. * Add Chinese / zh-TW translation file. Thank you so much, Chinese Spporter! Known issue: * Template loads every time Status is changed * * Only happned in case using default template. ### 0.0.7 NOTE: Please run "rake redmine:plugins:migrate" task because new column is added. * Fix some bugs. * Compatible with CKEditor. (#1280) * Add feature to show warning message for orphaned templates. (#1278) * Inherited templates only should be listed which tracker is the same to child project use. (#1278) * Add French translation file. (Bitbucket IssueID:33) * Add Serbian translation. Thank you so much, Miodrag Milic. (Bitbucket IssueID:34) * Add option to change append or replace with template. (#1176) ### 0.0.6 * Inherited templates from parent project. (#1267) * Add links to template list/edit at project setting tab. (#1269) * Add link to erase issue subject and description text. * Replace :rubygems in Gemfile with 'https://rubygems.org'. Thanks for JohnArcher. * Fixed invalid encoding. Thank you so much, Christoph. (#1178) * Fixed append "null" string to issue title field. (IssueID: #1268) * Prevent to load template when update and redirect with validation error. (#1151, #1254) ### 0.0.5 * Load default template. (#1088) * Show warning message in case no project trackers are assigned. * Change CSS style when showing template. (#1141) ### 0.0.4 * Support Redmine 2.1.x (Now unsupport Redmine 2.0.x. Please use ver 0.0.3 for Redmine2.0.x) Thak you so much, Viktor Muth, that gave me some feedback. * Now insert template text just after the text that is already in the description field. (#1115) ================================================ FILE: _config.yml ================================================ theme: jekyll-theme-slate ================================================ FILE: app/controllers/concerns/issue_templates_common.rb ================================================ # frozen_string_literal: true module Concerns module IssueTemplatesCommon extend ActiveSupport::Concern class InvalidTemplateFormatError < StandardError; end included do before_action :log_action, only: [:destroy] # logging action def log_action logger&.info "[#{self.class}] #{action_name} called by #{User.current.name}" end def plugin_setting Setting.plugin_redmine_issue_templates end def apply_all_projects? plugin_setting['apply_global_template_to_all_projects'].to_s == 'true' end def apply_template_when_edit_issue? plugin_setting['apply_template_when_edit_issue'].to_s == 'true' end def builtin_fields_enabled? plugin_setting['enable_builtin_fields'].to_s == 'true' end end def load_selectable_fields tracker_id = params[:tracker_id] project_id = params[:project_id] render plain: {} && return if tracker_id.blank? custom_fields = core_fields_map_by_tracker_id(tracker_id: tracker_id, project_id: project_id).merge(custom_fields_map_by_tracker_id(tracker_id)) render plain: { custom_fields: custom_fields }.to_json end def orphaned_templates render partial: 'common/orphaned', locals: { orphaned_templates: orphaned } end def apply_all_projects? plugin_setting['apply_global_template_to_all_projects'].to_s == 'true' end def builtin_fields_json value = template_params[:builtin_fields].blank? ? {} : JSON.parse(template_params[:builtin_fields]) return value if value.is_a?(Hash) raise InvalidTemplateFormatError end def valid_params attributes = template_params.except(:builtin_fields) attributes[:builtin_fields_json] = builtin_fields_json if builtin_fields_enabled? attributes end def destroy raise NotImplementedError, "You must implement #{self.class}##{__method__}" end # # TODO: Code should be refactored # def core_fields_map_by_tracker_id(tracker_id: nil, project_id: nil) return {} unless builtin_fields_enabled? fields = %w[status_id priority_id] fields << 'watcher_user_ids' if project_id.present? # exclude "description" tracker = Tracker.find_by(id: tracker_id) fields += tracker.core_fields.reject { |field| field == 'description' } if tracker.present? fields.reject! { |field| %w[category_id fixed_version_id assigned_to_id].include?(field) } if project_id.blank? map = {} fields.each do |field| id = "issue_#{field}" name = I18n.t('field_' + field.gsub(/_id$/, '')) value = { name: name, core_field_id: id } if field == 'priority_id' value[:possible_values] = IssuePriority.active.pluck(:name) value[:field_format] = 'list' end if field == 'status_id' && tracker.present? value[:possible_values] = tracker.issue_statuses.pluck(:name) value[:field_format] = 'list' end if field == 'category_id' && project_id.present? categories = IssueCategory.where(project_id: project_id) value[:possible_values] = categories.pluck(:name) value[:field_format] = 'list' end if field == 'assigned_to_id' && project_id.present? project = Project.find(project_id) assignable_users = (project.assignable_users(tracker).to_a + [project.default_assigned_to]).uniq.compact value[:possible_values] = assignable_users.map { |user| user.name } value[:field_format] = 'list' end if field == 'watcher_user_ids' && project_id.present? issue = Issue.new(tracker_id: tracker_id, project_id: project_id) watchers = helpers.users_for_new_issue_watchers(issue) value[:field_format] = 'list' value[:possible_values] = watchers.map { |user| "#{user.name} :#{user.id}" } value[:name] = I18n.t('field_watcher') value[:multiple] = true end value[:field_format] = 'date' if %(start_date due_date).include?(field) value[:field_format] = 'ratio' if field == 'done_ratio' map[id] = value end map rescue StandardError => e logger&.info "core_fields_map_by_tracker_id failed due to this error: #{e.message}" {} end def custom_fields_map_by_tracker_id(tracker_id = nil) return {} unless builtin_fields_enabled? return {} if tracker_id.blank? tracker = Tracker.find_by(id: tracker_id) ids = tracker&.custom_field_ids || [] fields = IssueCustomField.where(id: ids) map = {} fields.each do |field| id = "issue_custom_field_values_#{field.id}" attributes = field.attributes attributes = attributes.merge(possible_values: field.possible_values_options.map { |value| value[0] }) if field.format.name == 'bool' map[id] = attributes end map rescue StandardError => e logger&.info "core_fields_map_by_tracker_id failed due to this error: #{e.message}" {} end end end ================================================ FILE: app/controllers/concerns/project_templates_common.rb ================================================ # frozen_string_literal: true module Concerns module ProjectTemplatesCommon extend ActiveSupport::Concern included do before_action :find_user, :find_project, :authorize, except: %i[preview load load_selectable_fields] before_action :find_object, only: %i[show edit update destroy] accept_api_auth :index, :list_templates, :load end def show render render_form_params end def destroy unless template.destroy flash[:error] = l(:enabled_template_cannot_destroy) redirect_to action: :show, project_id: @project, id: template return end flash[:notice] = l(:notice_successful_delete) redirect_to action: 'index', project_id: @project end def save_and_flash(message, action_on_failure) unless template.save render render_form_params.merge(action: action_on_failure) return end respond_to do |format| format.html do flash[:notice] = l(message) redirect_to action: 'show', id: template.id, project_id: @project end format.js { head 200 } end rescue NoteTemplate::NoteTemplateError => e flash[:error] = e.message render render_form_params.merge(action: action_on_failure) nil end def plugin_setting Setting.plugin_redmine_issue_templates end def apply_all_projects? plugin_setting['apply_global_template_to_all_projects'].to_s == 'true' end private def template raise NotImplementedError, "You must implement #{self.class}##{__method__}" end def find_user @user = User.current end def find_tracker @tracker = Tracker.find(params[:issue_tracker_id]) end def find_project @project = Project.find(params[:project_id]) rescue ActiveRecord::RecordNotFound render_404 end end end ================================================ FILE: app/controllers/global_issue_templates_controller.rb ================================================ # frozen_string_literal: true # noinspection RubocopInspection class GlobalIssueTemplatesController < ApplicationController layout 'base' helper :issues include IssueTemplatesHelper include Concerns::IssueTemplatesCommon menu_item :issues before_action :find_object, only: %i[show edit update destroy] before_action :find_project, only: %i[edit update] before_action :require_admin, only: %i[index new show], excep: [:preview] # # Action for global template : Admin right is required. # def index trackers = Tracker.all template_map = {} trackers.each do |tracker| tracker_id = tracker.id templates = GlobalIssueTemplate.search_by_tracker(tracker_id).sorted template_map[Tracker.find(tracker_id)] = templates if templates.any? end render layout: !request.xhr?, locals: { template_map: template_map, trackers: trackers } end def new # create empty instance @global_issue_template = GlobalIssueTemplate.new render render_form_params end def create @global_issue_template = GlobalIssueTemplate.new @global_issue_template.author = User.current begin @global_issue_template.safe_attributes = valid_params rescue ActiveRecord::SerializationTypeMismatch, Concerns::IssueTemplatesCommon::InvalidTemplateFormatError flash[:error] = I18n.t(:builtin_fields_should_be_valid_json, default: 'Please enter a valid JSON fotmat string.') render render_form_params.merge(action: :new) return end save_and_flash(:notice_successful_create, :new) && return end def show render render_form_params end def update begin @global_issue_template.safe_attributes = valid_params rescue ActiveRecord::SerializationTypeMismatch, Concerns::IssueTemplatesCommon::InvalidTemplateFormatError flash[:error] = I18n.t(:builtin_fields_should_be_valid_json, default: 'Please enter a valid JSON fotmat string.') render render_form_params.merge(action: :show) return end save_and_flash(:notice_successful_update, :show) end def edit # Change from request.post to request.patch for Rails4. return unless request.patch? || request.put? begin @global_issue_template.safe_attributes = valid_params rescue ActiveRecord::SerializationTypeMismatch flash[:error] = I18n.t(:builtin_fields_should_be_valid_json, default: 'Please enter a valid JSON fotmat string.') render render_form_params.merge(action: :show) return end save_and_flash(:notice_successful_update, :show) end def destroy unless @global_issue_template.destroy flash[:error] = l(:enabled_template_cannot_destroy) redirect_to action: :show, id: @global_issue_template return end flash[:notice] = l(:notice_successful_delete) redirect_to action: 'index' end # preview def preview global_issue_template = params[:global_issue_template] id = params[:id] @text = (global_issue_template ? global_issue_template[:description] : nil) @global_issue_template = GlobalIssueTemplate.find(id) if id render partial: 'common/preview' end private def orphaned GlobalIssueTemplate.orphaned end def find_project @projects = Project.all end def find_object @global_issue_template = GlobalIssueTemplate.find(params[:id]) rescue ActiveRecord::RecordNotFound render_404 end def save_and_flash(message, action_on_failure) unless @global_issue_template.save render render_form_params.merge(action: action_on_failure) return end respond_to do |format| format.html do flash[:notice] = l(message) redirect_to action: 'show', id: @global_issue_template.id end format.js { head 200 } end end def template_params params.require(:global_issue_template) .permit(:title, :tracker_id, :issue_title, :description, :note, :is_default, :enabled, :author_id, :position, :related_link, :link_title, :builtin_fields, project_ids: []) end def render_form_params trackers = Tracker.all projects = Project.all tracker_id = @global_issue_template.tracker_id custom_fields = core_fields_map_by_tracker_id(tracker_id: tracker_id) .merge(custom_fields_map_by_tracker_id(tracker_id)).to_json { layout: !request.xhr?, locals: { trackers: trackers, apply_all_projects: apply_all_projects?, issue_template: @global_issue_template, projects: projects, custom_fields: custom_fields.to_s, builtin_fields_enable: builtin_fields_enabled? } } end end ================================================ FILE: app/controllers/global_note_templates_controller.rb ================================================ # frozen_string_literal: true # noinspection RubocopInspection class GlobalNoteTemplatesController < ApplicationController layout 'base' helper :issues helper :issue_templates menu_item :issues before_action :find_object, only: %i[show update destroy] before_action :find_project, only: %i[update] before_action :require_admin, only: %i[index new show], excep: [:preview] # # Action for global template : Admin right is required. # def index trackers = Tracker.all template_map = {} trackers.each do |tracker| tracker_id = tracker.id templates = GlobalNoteTemplate.search_by_tracker(tracker_id).sorted template_map[Tracker.find(tracker_id)] = templates if templates.any? end render layout: !request.xhr?, locals: { template_map: template_map, trackers: trackers } end def new # create empty instance @global_note_template = GlobalNoteTemplate.new render render_form_params end def create @global_note_template = GlobalNoteTemplate.new(template_params) @global_note_template.author = User.current save_and_flash(:notice_successful_create, :new) && return end def show render render_form_params end def update # Workaround in case author id is null @global_note_template.author = User.current if @global_note_template.author.blank? @global_note_template.safe_attributes = template_params save_and_flash(:notice_successful_update, :show) end def destroy unless @global_note_template.destroy flash[:error] = l(:enabled_template_cannot_destroy) redirect_to action: :show, id: @global_note_template return end flash[:notice] = l(:notice_successful_delete) redirect_to action: 'index' end def find_project @projects = Project.all end def find_object @global_note_template = GlobalNoteTemplate.find(params[:id]) rescue ActiveRecord::RecordNotFound render_404 end def save_and_flash(message, action_on_failure) unless @global_note_template.save render render_form_params.merge(action: action_on_failure) return end respond_to do |format| format.html do flash[:notice] = l(message) redirect_to action: 'show', id: @global_note_template.id end format.js { head 200 } end end def template_params params.require(:global_note_template) .permit(:global_note_template_id, :tracker_id, :name, :memo, :description, :enabled, :author_id, :position, :visibility, role_ids: [], project_ids: []) end def render_form_params trackers = Tracker.all projects = Project.all { layout: !request.xhr?, locals: { trackers: trackers, apply_all_projects: apply_all_projects?, note_template: @global_note_template, projects: projects } } end def apply_all_projects? plugin_setting['apply_global_template_to_all_projects'].to_s == 'true' end def plugin_setting Setting.plugin_redmine_issue_templates end end ================================================ FILE: app/controllers/issue_templates_controller.rb ================================================ # frozen_string_literal: true # noinspection ALL class IssueTemplatesController < ApplicationController layout 'base' helper :issues include Concerns::IssueTemplatesCommon include Concerns::ProjectTemplatesCommon menu_item :issues before_action :find_tracker, :find_templates, only: %i[set_pulldown list_templates] def index project_id = @project.id project_templates = IssueTemplate.search_by_project(project_id) # pick up used tracker ids tracker_ids = @project.trackers.pluck(:id) @template_map = {} tracker_ids.each do |tracker_id| templates = project_templates.search_by_tracker(tracker_id).sorted @template_map[Tracker.find(tracker_id)] = templates if templates.any? end setting = IssueTemplateSetting.find_or_create(project_id) @inherit_templates = setting.get_inherit_templates @global_issue_templates = global_templates(tracker_ids) respond_to do |format| format.html do render layout: !request.xhr?, locals: { apply_all_projects: apply_all_projects?, tracker_ids: tracker_ids } end format.api do render formats: :json, locals: { project_templates: project_templates } end end end def new if params[:copy_from].present? @issue_template = IssueTemplate.find(params[:copy_from]).dup @issue_template.title = @issue_template.copy_title else # create empty instance @issue_template ||= IssueTemplate.new(author: @user, project: @project) end render render_form_params end def create @issue_template = IssueTemplate.new @issue_template.author = User.current @issue_template.project = @project begin @issue_template.safe_attributes = valid_params rescue ActiveRecord::SerializationTypeMismatch, Concerns::IssueTemplatesCommon::InvalidTemplateFormatError flash[:error] = I18n.t(:builtin_fields_should_be_valid_json, default: 'Please enter a valid JSON fotmat string.') render render_form_params.merge(action: :new) return end # TODO: Should return validation error in case mandatory fields are blank. save_and_flash(:notice_successful_create, :new) && return end def update begin @issue_template.safe_attributes = valid_params rescue ActiveRecord::SerializationTypeMismatch, Concerns::IssueTemplatesCommon::InvalidTemplateFormatError flash[:error] = I18n.t(:builtin_fields_should_be_valid_json, default: 'Please enter a valid JSON fotmat string.') render render_form_params.merge(action: :show) return end save_and_flash(:notice_successful_update, :show) end # load template description def load issue_template_id = params[:template_id] template_type = params[:template_type] issue_template = if template_type.present? && template_type == 'global' GlobalIssueTemplate.find(issue_template_id) else IssueTemplate.find(issue_template_id) end rendered_json = builtin_fields_enabled? ? issue_template.template_json : issue_template.template_json(except: 'builtin_fields_json') render plain: rendered_json end # update pulldown def set_pulldown @group = [] @default_template = nil add_templates_to_group(@issue_templates) add_templates_to_group(@inherit_templates, class: 'inherited') add_templates_to_group(@global_templates, class: 'global') if loadable_trigger? @group[@default_template].selected = 'selected' end render action: '_template_pulldown', layout: false, locals: { is_triggered_by: request.parameters[:is_triggered_by], grouped_options: @group, should_replaced: setting.should_replaced, default_template: @default_template } end # # List templates associated with tracker and project. # TODO: refactor here. Duplicate with set_pulldown.... # def list_templates (default_global, default_inherit, default_project) = default_templates default_template = default_inherit.presence || default_global default_template = default_project.presence || default_template respond_to do |format| format.html do render action: '_list_templates', layout: false, locals: { default_template: default_template, issue_templates: @issue_templates, inherit_templates: @inherit_templates, global_issue_templates: @global_templates } end format.api do render action: '_list_templates', locals: { default_template: default_template, issue_templates: @issue_templates, inherit_templates: @inherit_templates, global_issue_templates: @global_templates } end end end def menu_items { issue_templates: { default: :issue_templates, actions: {} } } end # preview def preview issue_template = params[:issue_template] @text = (issue_template ? issue_template[:description] : nil) render partial: 'common/preview' end private def orphaned IssueTemplate.orphaned(@project.id) end def find_object @issue_template = IssueTemplate.find(params[:id]) @project = @issue_template.project rescue ActiveRecord::RecordNotFound render_404 end def find_templates @issue_templates = issue_templates @inherit_templates = inherit_templates @global_templates = global_templates(@tracker.id) end def template @issue_template end def setting IssueTemplateSetting.find_or_create(@project.id) end def global_templates(tracker_id) return [] if apply_all_projects? && templates_exist? project_id = apply_all_projects? ? nil : @project.id GlobalIssueTemplate.get_templates_for_project_tracker(project_id, tracker_id) end def default_templates [@global_templates, @inherit_templates, @issue_templates].map do |templates| templates.try(:is_default).try(:first) end end def default_template_index @default_template.blank? ? @group.length - 1 : @default_template end def add_templates_to_group(templates, option = {}) templates.each do |template| @group << template.template_struct(option) next unless template.is_default == true @default_template = default_template_index end end def issue_templates if params[:issue_project_id] @project = Project.find(params[:issue_project_id]) end IssueTemplate.get_templates_for_project_tracker(@project.id, @tracker.id) end def inherit_templates setting.get_inherit_templates(@tracker) end def template_params params.require(:issue_template).permit(:tracker_id, :title, :note, :issue_title, :description, :is_default, :enabled, :author_id, :position, :enabled_sharing, :related_link, :link_title, :builtin_fields) end def templates_exist? @inherit_templates.present? || @issue_templates.present? end def render_form_params child_project_used_count = template&.used_projects&.count custom_fields = core_fields_map_by_tracker_id(tracker_id: template&.tracker_id, project_id: @project.id) .merge(custom_fields_map_by_tracker_id(template&.tracker_id)).to_json { layout: !request.xhr?, locals: { issue_template: template, project: @project, child_project_used_count: child_project_used_count, custom_fields: custom_fields.to_s, builtin_fields_enable: builtin_fields_enabled? } } end def loadable_trigger? is_triggered_by = request.parameters[:is_triggered_by] is_update_issue = request.parameters[:is_update_issue] return false if is_triggered_by.present? && is_triggered_by != 'is_update_issue' return @default_template.present? && (is_update_issue.blank? || is_update_issue != 'true') end end ================================================ FILE: app/controllers/issue_templates_settings_controller.rb ================================================ # frozen_string_literal: true # noinspection RubocopInspection class IssueTemplatesSettingsController < ApplicationController before_action :find_project, :find_user before_action :authorize, :find_issue_templates_setting, except: %i[preview] def index; end def edit return if params[:settings].blank? update_template_setting flash[:notice] = l(:notice_successful_update) redirect_to action: 'index', project_id: @project end def preview @text = params[:settings][:help_message] render partial: 'common/preview' end def menu_items { issue_templates_settings: { default: :issue_templates, actions: {} } } end private def find_user @user = User.current end def find_project @project = Project.find(params[:project_id]) rescue ActiveRecord::RecordNotFound render_404 end def find_issue_templates_setting @issue_templates_setting = IssueTemplateSetting.find_or_create(@project.id) end def update_template_setting issue_templates_setting = IssueTemplateSetting.find_or_create(@project.id) attribute = params[:settings] issue_templates_setting.update(enabled: attribute[:enabled], help_message: attribute[:help_message], inherit_templates: attribute[:inherit_templates], should_replaced: attribute[:should_replaced]) end end ================================================ FILE: app/controllers/note_templates_controller.rb ================================================ # frozen_string_literal: true class NoteTemplatesController < ApplicationController include Concerns::ProjectTemplatesCommon layout 'base' helper :issue_templates menu_item :issues def index project_id = @project.id note_templates = NoteTemplate.search_by_project(project_id).sorted # pick up used tracker ids tracker_ids = @project.trackers.pluck(:id) @template_map = {} tracker_ids.each do |tracker_id| templates = note_templates.search_by_tracker(tracker_id) @template_map[Tracker.find(tracker_id)] = templates if templates.any? end @global_note_templates = global_templates(tracker_ids) respond_to do |format| format.html do render layout: !request.xhr?, locals: { apply_all_projects: apply_all_projects?, tracker_ids: tracker_ids } end format.api do render formats: :json, locals: { note_templates: note_templates } end end end def new @note_template ||= NoteTemplate.new(author: @user, project: @project) render render_form_params end def create @note_template = NoteTemplate.new(template_params) @note_template.author = User.current @note_template.project = @project save_and_flash(:notice_successful_create, :new) && return end def update # Workaround in case author id is null @note_template.author = User.current if @note_template.author.blank? @note_template.safe_attributes = template_params @note_template.role_ids = template_params[:role_ids] save_and_flash(:notice_successful_update, :show) end # load template description def load note_template_id = template_params[:note_template_id] template_type = template_params[:template_type] if template_type.present? && template_type == 'global' project_id = template_params[:project_id] note_template = GlobalNoteTemplate.find(note_template_id) # prevent to load if the template visibility does not match. raise ActiveRecord::RecordNotFound unless note_template.loadable?(user_id: User.current.id, project_id: project_id) else note_template = NoteTemplate.find(note_template_id) # prevent to load if the template visibility does not match. raise ActiveRecord::RecordNotFound unless note_template.loadable?(user_id: User.current.id) end render plain: note_template.template_json rescue ActiveRecord::RecordNotFound render_404 end def list_templates tracker_id = params[:tracker_id] project_id = params[:project_id] note_templates = NoteTemplate.visible_note_templates_condition( user_id: User.current.id, project_id: project_id, tracker_id: tracker_id ) global_note_templates = GlobalNoteTemplate.visible_note_templates_condition( user_id: User.current.id, project_id: project_id, tracker_id: tracker_id ) respond_to do |format| format.html do render action: '_list_note_templates', layout: false, locals: { note_templates: note_templates, global_note_templates: global_note_templates } end end end def destroy unless @note_template.destroy flash[:error] = l(:enabled_template_cannot_destroy) redirect_to action: :show, project_id: @project, id: @note_template return end flash[:notice] = l(:notice_successful_delete) redirect_to action: 'index', project_id: @project end def menu_items { note_templates: { default: :issue_templates, actions: {} } } end private def find_object @note_template = NoteTemplate.find(params[:id]) @project = @note_template.project rescue ActiveRecord::RecordNotFound render_404 end def template_params params.require(:note_template) .permit(:note_template_id, :project_id, :template_type, :tracker_id, :name, :memo, :description, :enabled, :author_id, :position, :visibility, role_ids: []) end def template @note_template end def render_form_params { layout: !request.xhr?, locals: { note_template: template, project: @project } } end def templates_exist? @note_templates.present? end def global_templates(tracker_id) return [] if apply_all_projects? && templates_exist? project_id = apply_all_projects? ? nil : @project.id GlobalNoteTemplate.get_templates_for_project_tracker(project_id, tracker_id) end end ================================================ FILE: app/helpers/issue_templates_helper.rb ================================================ module IssueTemplatesHelper def project_tracker?(tracker, project) return false unless tracker.present? project.trackers.exists?(tracker.id) end def non_project_tracker_msg(flag) return '' if flag "#{l(:unused_tracker_at_this_project)}".html_safe end def template_target_trackers(project, issue_template) trackers = project.trackers trackers |= [issue_template.tracker] unless issue_template.tracker.blank? trackers.collect { |obj| [obj.name, obj.id] } end def options_for_template_pulldown(options) options.map do |option| text = option.try(:name).to_s tag_builder.content_tag_string(:option, text, option, true) end.join("\n").html_safe end end ================================================ FILE: app/models/concerns/issue_template/common.rb ================================================ # frozen_string_literal: true module Concerns module IssueTemplate module Common extend ActiveSupport::Concern # # Common scope both global and project scope template. # included do belongs_to :author, class_name: 'User', foreign_key: 'author_id' belongs_to :tracker before_save :check_default before_destroy :confirm_disabled validates :title, presence: true validates :tracker, presence: true validates :related_link, format: { with: URI::DEFAULT_PARSER.make_regexp }, allow_blank: true scope :enabled, -> { where(enabled: true) } scope :sorted, -> { order(:position) } scope :search_by_tracker, lambda { |tracker_id| where(tracker_id: tracker_id) if tracker_id.present? } scope :is_default, -> { where(is_default: true) } scope :not_default, -> { where(is_default: false) } scope :orphaned, lambda { |project_id = nil| condition = all if project_id.present? && try(:name) == 'IssueTemplate' condition = condition.where(project_id: project_id) ids = Tracker.joins(:projects).where(projects: { id: project_id }).pluck(:id) else ids = Tracker.pluck(:id) end condition.where.not(tracker_id: ids) } after_destroy do |template| logger.info("[Destroy] #{self.class}: #{template.inspect}") end # ActiveRecord::SerializationTypeMismatch may be thrown if non hash object is assigned. serialize :builtin_fields_json, Hash end # # Common methods both global and project scope template. # def enabled? enabled end def <=>(other) position <=> other.position end # Keep this method for a while, but this will be deprecated. # Please see: https://github.com/akiko-pusu/redmine_issue_templates/issues/363 def checklist return [] if checklist_json.blank? begin JSON.parse(checklist_json) rescue StandardError [] end end def template_json(except: nil) template = {} template[self.class::Config::JSON_OBJECT_NAME] = generate_json return template.to_json(root: true) if except.blank? template.to_json(root: true, except: [except]) end def builtin_fields builtin_fields_json.to_json end def generate_json result = attributes result[:link_title] = link_title.presence || I18n.t(:issue_template_related_link, default: 'Related Link') result[:checklist] = checklist result.except('checklist_json') end def template_struct(option = {}) Struct.new(:value, :name, :class, :selected).new(id, title, option[:class]) end def log_destroy_action(template) logger.info "[Destroy] #{self.class}: #{template.inspect}" if logger&.info end def confirm_disabled return unless enabled? errors.add :base, 'enabled_template_cannot_destroy' throw :abort end def copy_title "copy_of_#{title}" end end end end ================================================ FILE: app/models/global_issue_template.rb ================================================ # frozen_string_literal: true class GlobalIssueTemplate < ActiveRecord::Base include Redmine::SafeAttributes include Concerns::IssueTemplate::Common validates :title, uniqueness: { scope: :tracker_id } has_and_belongs_to_many :projects acts_as_positioned scope: [:tracker_id] safe_attributes 'title', 'description', 'tracker_id', 'note', 'enabled', 'is_default', 'issue_title', 'project_ids', 'position', 'author_id', 'related_link', 'link_title', 'builtin_fields_json' # for intermediate table assosciations scope :search_by_project, lambda { |project_id| joins(:projects).where(projects: { id: project_id }) if project_id.present? } module Config JSON_OBJECT_NAME = 'global_issue_template' end Config.freeze # # In case set is_default and updated, others are also updated. # def check_default return unless is_default? && is_default_changed? self.class.search_by_tracker(tracker_id).update_all(is_default: false) end # # Class method # class << self def get_templates_for_project_tracker(project_id, tracker_id = nil) GlobalIssueTemplate.search_by_tracker(tracker_id) .search_by_project(project_id) .enabled .sorted end end end ================================================ FILE: app/models/global_note_template.rb ================================================ # frozen_string_literal: true class GlobalNoteTemplate < ActiveRecord::Base include Redmine::SafeAttributes include ActiveModel::Validations # author and project should be stable. safe_attributes 'name', 'description', 'enabled', 'memo', 'tracker_id', 'position', 'visibility', 'role_ids', 'project_ids' validates :role_ids, presence: true, if: :roles? belongs_to :author, class_name: 'User', inverse_of: false, foreign_key: 'author_id' belongs_to :tracker has_many :global_note_template_projects, dependent: :nullify has_many :projects, through: :global_note_template_projects has_many :global_note_visible_roles, dependent: :nullify has_many :roles, through: :global_note_visible_roles validates :name, presence: true acts_as_positioned scope: %i[tracker_id] enum visibility: { roles: 1, open: 2 } scope :mine_condition, lambda { |user_id| where(author_id: user_id).mine if user_id.present? } scope :roles_condition, lambda { |role_ids| joins(:global_note_visible_roles).where(global_note_visible_roles: { role_id: role_ids }) } scope :enabled, -> { where(enabled: true) } scope :sorted, -> { order(:position) } scope :search_by_tracker, lambda { |tracker_id| where(tracker_id: tracker_id) if tracker_id.present? } # for intermediate table assosciations scope :search_by_project, lambda { |project_id| joins(:projects).where(projects: { id: project_id }) if project_id.present? } before_save :check_visible_roles after_save :note_visible_roles! before_destroy :confirm_disabled def <=>(other) position <=> other.position end def template_json template = {} template['note_template'] = generate_json template.to_json(root: true) end def generate_json attributes end def note_visible_roles! return unless roles? if role_ids.blank? raise NoteTemplateError, l(:please_select_at_least_one_role, default: 'Please select at least one role.') end ActiveRecord::Base.transaction do GlobalNoteVisibleRole.where(global_note_template_id: id).delete_all if global_note_visible_roles.present? role_ids.each do |role_id| GlobalNoteVisibleRole.create!(global_note_template_id: id, role_id: role_id) end end end def loadable?(user_id:, project_id:) return true if open? project = Project.find(project_id) user_project_roles = User.find(user_id).roles_for_project(project).pluck(:id) match_roles = user_project_roles & roles.ids return true if roles? && match_roles.present? false end private def check_visible_roles return if roles? || global_note_visible_roles.empty? # Remove roles in case template visible scope is not "roles". # This remove action is included the same transaction scope. GlobalNoteVisibleRole.where(global_note_template_id: id).delete_all end def confirm_disabled return unless enabled? errors.add :base, 'enabled_template_cannot_destroy' throw :abort end # # Class method # class << self def visible_note_templates_condition(user_id:, project_id:, tracker_id:) user = User.find(user_id) project = Project.find(project_id) user_project_roles = user.roles_for_project(project).pluck(:id) base_condition = GlobalNoteTemplate.search_by_tracker(tracker_id) base_condition = base_condition.search_by_project(project_id) unless apply_all_projects? open_ids = base_condition.open.pluck(:id) role_ids = base_condition.roles_condition(user_project_roles).pluck(:id) # return uniq ids ids = open_ids | role_ids GlobalNoteTemplate.where(id: ids).includes(:global_note_visible_roles) end def get_templates_for_project_tracker(project_id, tracker_id = nil) GlobalNoteTemplate.search_by_tracker(tracker_id) .search_by_project(project_id) .enabled .sorted end def plugin_setting Setting.plugin_redmine_issue_templates end def apply_all_projects? plugin_setting['apply_global_template_to_all_projects'].to_s == 'true' end end end ================================================ FILE: app/models/global_note_template_project.rb ================================================ # frozen_string_literal: true class GlobalNoteTemplateProject < ActiveRecord::Base belongs_to :project belongs_to :global_note_template, optional: true end ================================================ FILE: app/models/global_note_visible_role.rb ================================================ # frozen_string_literal: true class GlobalNoteVisibleRole < ActiveRecord::Base include Redmine::SafeAttributes safe_attributes 'global_note_template_id', 'role_id' belongs_to :role belongs_to :global_note_template, optional: true validates :role_id, presence: true validates :global_note_template_id, presence: true scope :search_by_note_template, lambda { |note_template_id| where(global_note_template_id: note_template_id) } end ================================================ FILE: app/models/issue_template.rb ================================================ # frozen_string_literal: true class IssueTemplate < ActiveRecord::Base include Redmine::SafeAttributes include Concerns::IssueTemplate::Common belongs_to :project validates :project_id, presence: true validates :title, uniqueness: { scope: :project_id } acts_as_positioned scope: %i[project_id tracker_id] # author and project should be stable. safe_attributes 'title', 'description', 'tracker_id', 'note', 'enabled', 'issue_title', 'is_default', 'enabled_sharing', 'visible_children', 'position', 'related_link', 'link_title', 'builtin_fields_json' scope :enabled_sharing, -> { where(enabled_sharing: true) } scope :search_by_project, lambda { |prolect_id| where(project_id: prolect_id) } module Config JSON_OBJECT_NAME = 'issue_template' end Config.freeze # # In case set is_default and updated, others are also updated. # def check_default return unless is_default? && is_default_changed? self.class.search_by_project(project_id).search_by_tracker(tracker_id).update_all(is_default: false) end # return projects that use this template def used_projects return [] unless enabled_sharing projects = project.descendants .joins(:trackers, :enabled_modules).merge(Tracker.where(id: tracker_id)).merge(EnabledModule.where(name: 'issue_templates')) IssueTemplateSetting.where(project_id: projects).inherit_templates.select(:project_id) end # # Class method # class << self def get_inherit_templates(project_ids, tracker_id) # keep ordering of project tree IssueTemplate.search_by_project(project_ids) .search_by_tracker(tracker_id) .enabled .enabled_sharing .sorted end def get_templates_for_project_tracker(project_id, tracker_id = nil) IssueTemplate.search_by_project(project_id) .search_by_tracker(tracker_id) .enabled .sorted end end end ================================================ FILE: app/models/issue_template_setting.rb ================================================ class IssueTemplateSetting < ActiveRecord::Base include Redmine::SafeAttributes belongs_to :project validates_uniqueness_of :project_id validates_presence_of :project_id safe_attributes 'help_message', 'enabled', 'inherit_templates', 'should_replaced' scope :inherit_templates, -> { where(inherit_templates: true) } def self.find_or_create(project_id) setting = IssueTemplateSetting.where(project_id: project_id).first unless setting.present? setting = IssueTemplateSetting.new setting.project_id = project_id setting.save! end setting end # # Class method # class << self def apply_template_to_child_projects(project_id) setting = find_setting(project_id) setting.apply_template_to_child_projects end def unapply_template_from_child_projects(project_id) setting = find_setting(project_id) setting.unapply_template_from_child_projects end private def find_setting(project_id) raise ArgumentError, 'Please specify valid project_id.' if project_id.blank? setting = IssueTemplateSetting.where(project_id: project_id).first raise ActiveRecord::RecordNotFound if setting.blank? setting end end def enable_help? enabled == true && !help_message.blank? end def enabled_inherit_templates? inherit_templates end def child_projects project.descendants end def apply_template_to_child_projects update_inherit_template_of_child_projects(true) end def unapply_template_from_child_projects update_inherit_template_of_child_projects(false) end def get_inherit_templates(tracker = nil) return [] unless enabled_inherit_templates? project_ids = project.ancestors.collect(&:id) tracker = project.trackers.pluck(:tracker_id) if tracker.blank? # first: get inherit_templates IssueTemplate.get_inherit_templates(project_ids, tracker) end private def update_inherit_template_of_child_projects(value) IssueTemplateSetting.where(project_id: child_projects).update_all(inherit_templates: value) end end ================================================ FILE: app/models/note_template.rb ================================================ # frozen_string_literal: true class NoteTemplate < ActiveRecord::Base include Redmine::SafeAttributes include ActiveModel::Validations class NoteTemplateError < StandardError; end # author and project should be stable. safe_attributes 'name', 'description', 'enabled', 'memo', 'tracker_id', 'project_id', 'position', 'visibility' attr_accessor :role_ids validates :role_ids, presence: true, if: :roles? belongs_to :project belongs_to :author, class_name: 'User', foreign_key: 'author_id' belongs_to :tracker has_many :note_visible_roles, dependent: :nullify has_many :roles, through: :note_visible_roles validates :project_id, presence: true validates :name, uniqueness: { scope: :project_id } validates :name, presence: true acts_as_positioned scope: %i[project_id tracker_id] enum visibility: { mine: 0, roles: 1, open: 2 } scope :mine_condition, lambda { |user_id| where(author_id: user_id).mine if user_id.present? } scope :roles_condition, lambda { |role_ids| joins(:note_visible_roles).where(note_visible_roles: { role_id: role_ids }) } scope :enabled, -> { where(enabled: true) } scope :sorted, -> { order(:position) } scope :search_by_tracker, lambda { |tracker_id| where(tracker_id: tracker_id) if tracker_id.present? } scope :search_by_project, lambda { |prolect_id| where(project_id: prolect_id) if prolect_id.present? } before_save :check_visible_roles after_save :note_visible_roles! before_destroy :confirm_disabled def <=>(other) position <=> other.position end def template_json template = {} template['note_template'] = generate_json template.to_json(root: true) end def generate_json attributes end def note_visible_roles! return unless roles? if role_ids.blank? raise NoteTemplateError, l(:please_select_at_least_one_role, default: 'Please select at least one role.') end ActiveRecord::Base.transaction do NoteVisibleRole.where(note_template_id: id).delete_all if note_visible_roles.present? role_ids.each do |role_id| NoteVisibleRole.create!(note_template_id: id, role_id: role_id) end end end def loadable?(user_id:) return true if open? return true if mine? && user_id == author_id user_project_roles = User.find(user_id).roles_for_project(project).pluck(:id) match_roles = user_project_roles & roles.ids return true if roles? && !match_roles.empty? false end private def check_visible_roles return if roles? || note_visible_roles.empty? # Remove roles in case template visible scope is not "roles". # This remove action is included the same transaction scope. NoteVisibleRole.where(note_template_id: id).delete_all end def confirm_disabled return unless enabled? errors.add :base, 'enabled_template_cannot_destroy' throw :abort end # # Class method # class << self def visible_note_templates_condition(user_id:, project_id:, tracker_id:) user = User.find(user_id) project = Project.find(project_id) user_project_roles = user.roles_for_project(project).pluck(:id) base_condition = NoteTemplate.search_by_project(project_id).search_by_tracker(tracker_id) open_ids = base_condition.open.pluck(:id) mine_ids = base_condition.mine_condition(user_id).pluck(:id) role_ids = base_condition.roles_condition(user_project_roles).pluck(:id) # return uniq ids ids = open_ids | mine_ids | role_ids NoteTemplate.where(id: ids).includes(:note_visible_roles) end end end ================================================ FILE: app/models/note_visible_role.rb ================================================ # frozen_string_literal: true class NoteVisibleRole < ActiveRecord::Base include Redmine::SafeAttributes safe_attributes 'note_template_id', 'role_id' belongs_to :role belongs_to :note_template, optional: true validates :role_id, presence: true validates :note_template_id, presence: true scope :search_by_note_template, lambda { |note_template_id| where(note_template_id: note_template_id) } end ================================================ FILE: app/views/common/_nodata.html.erb ================================================ <% if trackers.blank? %>
<%= simple_format(l(:text_no_tracker_enabled)) %>
<% end %> ================================================ FILE: app/views/common/_orphaned.html.erb ================================================

<%= l(:orphaned_template) %>

<% orphaned_templates.each do |issue_template| %> issue_template issue'> <% end %>
# <%= l(:issue_template_name) %> <%= l(:label_preview) %> <%= l(:field_tracker) %> <%= l(:field_author) %> <%= l(:field_updated_on) %>
<%= link_to h(issue_template.id), { controller: controller.controller_name, action: 'show', id: issue_template.id, }.merge(issue_template.try(:project_id) ? { project_id: issue_template.project } : {}), { title: issue_template.note } %> <%= link_to h(issue_template.title), { controller: controller.controller_name, id: issue_template.id, action: 'show' }, { title: "#{html_escape(issue_template.note) }"} %>
<%= issue_template.title %> <%= textilizable(issue_template.description) %>
<%= "ID: #{issue_template.tracker_id}" %> <%=h issue_template.author %> <%= format_time(issue_template.updated_on) %>
================================================ FILE: app/views/common/_template_links.html.erb ================================================
================================================ FILE: app/views/global_issue_templates/_form.html.erb ================================================ <%= error_messages_for 'global_issue_template' %>

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

<%= l(:label_applied_for_issue) %>

<% if issue_template.tracker.blank? %> <%= f.select :tracker_id, trackers.collect { |t| [t.name, t.id] }, { required: true }, required: true, label: l(:label_tracker) %> <%= h issue_template.tracker.present? ? issue_template.tracker.name : l(:orphaned_template, default: 'Orphaned template from tracker') %> <% else %> <%= f.select :tracker_id, trackers.collect { |t| [t.name, t.id] }, { required: true }, required: true, label: l(:label_tracker), selected: issue_template.tracker.id %> <% end %>

<%= f.text_field :issue_title, required: false, size: 80, label: l(:issue_title) %> <%= l(:help_for_this_field) %>

<%= f.text_area :description, cols: 78, rows: 12, required: true, label: l(:issue_description), class: 'wiki-edit', style: 'overflow: auto;' %>

<% if builtin_fields_enable %>

<%= l(:help_for_this_field) %>

' v-model='newItemValue'> ' v-model='newItemValue'> <%= l(:button_add) %>

<%= l(:label_field_information, default: 'Field information') %> <%= l(:label_custom_field, default: 'Custom field') %>: {{ customFields[newItemTitle].name }}
{{ customFields[newItemTitle] }}
  • {{ customFields[item.title].name }}: {{ item.value }} / {{ item.title }} <%= l(:unavailable_fields_for_this_tracker, default: 'Unavailable field for this tarcker') %> : {{ item.value }} / {{ item.title }}
<%= l(:button_reset) %> <%= l(:button_apply) %>

<%= f.text_area :builtin_fields, required: false, cols: 60, rows: 4, label: l(:label_builtin_fields_json, default: 'JSON for fields') %>

<% end %>

<%= f.text_area :note, cols: 70, rows: 3, required: false, label: l(:issue_template_note), style: 'overflow:auto;' %>

<%= f.text_field :related_link, type: 'url', size: 70, label: l(:issue_template_related_link, default: 'Related link') %> <%= l(:help_for_this_field) %>

<%= f.check_box :is_default, label: l(:field_is_default) %> <%= l(:help_for_this_field) %>

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

<%= wikitoolbar_for 'global_issue_template_description' %>
<% if apply_all_projects %>

<%= l(:note_apply_global_template_to_all_projects_setting_enabled) %> ( <%= link_to(l(:label_settings), { controller: 'settings', action: 'plugin', id: 'redmine_issue_templates' }, class: 'issue_template icon plugins') %> )

<% end %> <% if projects.any? %>
'>
<% end %> <%= hidden_field_tag 'global_issue_template[project_ids][]', '' %>
<%= submit_tag l(issue_template.new_record? ? :button_create : :button_save) %> <%= link_to l(:button_cancel), { action: 'index' }, data: { confirm: l(:text_are_you_sure) } %> <% if builtin_fields_enable %> <%= javascript_include_tag('vue.min', plugin: 'redmine_issue_templates') %> <%= javascript_include_tag('template_fields', plugin: 'redmine_issue_templates') %> <% end %> ================================================ FILE: app/views/global_issue_templates/index.html.erb ================================================

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

<%= render partial: 'common/nodata', locals: { trackers: trackers } %>
<%= link_to(l(:label_new_templates), { controller: 'global_issue_templates', action: 'new' }, class: 'icon icon-add') %> <%= link_to(l(:global_note_templates, default: 'Global Note Templates'), { controller: 'global_note_templates', action: 'index' }, class: 'icon icon-template') %> <%= link_to(l(:label_settings), { controller: 'settings', action: 'plugin', id: 'redmine_issue_templates' }, class: 'issue_template icon plugins') %>
<% if template_map.blank? %>
<%= l(:no_issue_templates_for_this_redmine) %>
<% end %> <% template_map.each_key do |tracker| %>

<%= tracker.name %>

<% template_map[tracker].sorted.each do |issue_template| %> issue_template issue'> <% end %>
# <%= 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', id: issue_template.id, action: 'show' }, { title: issue_template.title } %> <%= link_to h(issue_template.title), { controller: 'global_issue_templates', id: issue_template.id, action: 'show' }, { title: "#{html_escape(issue_template.note)}" } %>
<%= issue_template.title %> <%= textilizable(issue_template.description) %> <% if issue_template.related_link.present? %>
<%= link_to issue_template.link_title.present? ? issue_template.link_title : l(:issue_template_related_link, default: 'Related link'), issue_template.related_link, target: '_blank', rel: 'nofollow noopener', class: 'external' %> <% end %>
<%=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? %> <%= reorder_handle(issue_template, :url => url_for({ controller: 'global_issue_templates', id: issue_template.id, action: 'update' })) %>
<%= javascript_tag do %> // NOTE: Sortable feature depends on Redmine's sorting jQuery plugin. $(function() { $('table.table-sortable tbody').positionedItems() }) <% end %> <% end %> ================================================ FILE: app/views/global_issue_templates/new.html.erb ================================================
<%= link_to(l(:label_list_templates), { controller: 'global_issue_templates', action: 'index' }, class: 'icon icon-template') %>

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

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

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

<%= labelled_form_for :global_issue_template, issue_template, url: { controller: 'global_issue_templates', action: 'update', id: issue_template }, html: { id: 'global_issue_template-form', class: nil, multipart: false } do |f| %> <%= render 'form', { f: f, trackers: trackers, issue_template: issue_template, projects: projects, apply_all_projects: apply_all_projects, custom_fields: custom_fields, builtin_fields_enable: builtin_fields_enable } %> <% end %> ================================================ FILE: app/views/global_note_templates/_form.html.erb ================================================ <%= error_messages_for 'global_note_template' %>

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

<%= l(:label_applied_for_issue) %>

<% if note_template.tracker.blank? %> <%= f.select :tracker_id, trackers.collect { |t| [t.name, t.id] }, { required: true }, required: true, label: l(:label_tracker) %> <%= h note_template.tracker.present? ? note_template.tracker.name : l(:orphaned_template, default: 'Orphaned template from tracker') %> <% else %> <%= f.select :tracker_id, trackers.collect { |t| [t.name, t.id] }, { required: true }, required: true, label: l(:label_tracker), selected: note_template.tracker.id %> <% end %>

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

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

'> <% Role.givable.each do |role| %> <%= check_box_tag('global_note_template[role_ids][]', role.id, note_template.global_note_visible_roles.pluck(:role_id).to_a.include?(role.id)) %> <%= role.name %> <% end %>

<%= f.text_area :memo, cols: 70, rows: 3, required: false, label: l(:issue_template_note) %> <%= l(:help_for_this_field) %>

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

<%= wikitoolbar_for 'global_note_template_description' %>
<% if apply_all_projects %>

<%= l(:note_apply_global_template_to_all_projects_setting_enabled) %> ( <%= link_to(l(:label_settings), { controller: 'settings', action: 'plugin', id: 'redmine_issue_templates' }, class: 'issue_template icon plugins') %> )

<% end %> <% if projects.any? %>
'>
<% end %> <%= hidden_field_tag 'global_note_template[project_ids][]', '' %>
================================================ FILE: app/views/global_note_templates/index.html.erb ================================================

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

<%= render partial: 'common/nodata', locals: { trackers: trackers } %>
<%= link_to(l(:label_new_templates), { controller: 'global_note_templates', action: 'new' }, class: 'icon icon-add') %> <%= link_to(l(:global_issue_templates), { controller: 'global_issue_templates', action: 'index' }, class: 'icon icon-template') %> <%= link_to(l(:label_settings), { controller: 'settings', action: 'plugin', id: 'redmine_issue_templates' }, class: 'issue_template icon plugins') %>
<% if template_map.blank? %>
<%= l(:no_note_templates_for_this_redmine, default: 'No global note templates are defined for this Redmine site.') %>
<% end %> <% template_map.each_key do |tracker| %>

<%= tracker.name %>

<% template_map[tracker].sorted.each do |note_template| %> issue_template issue'> <% end %>
# <%= l(:issue_template_name) %> <%= l(:label_preview) %> <%= l(:field_tracker) %> <%= l(:field_author) %> <%= l(:field_updated_at) %> <%= l(:label_enabled) %> <%=l(:button_sort)%>
<%= link_to h(note_template.id), { controller: 'global_note_templates', id: note_template.id, action: 'show' }, { title: note_template.name } %> <%= link_to h(note_template.name), { controller: 'global_note_templates', id: note_template.id, action: 'show' }, { title: "#{html_escape(note_template.memo)}" } %>
<%= note_template.name %> <%= textilizable(note_template.description) %>
<%=h note_template.tracker.name %> <%=h note_template.author %> <%= format_time(note_template.updated_at)%> <%= checked_image note_template.enabled? %> <%= reorder_handle(note_template, :url => url_for({ controller: 'global_note_templates', id: note_template.id, action: 'update' })) %>
<%= javascript_tag do %> // NOTE: Sortable feature depends on Redmine's sorting jQuery plugin. $(function() { $('table.table-sortable tbody').positionedItems() }) <% end %> <% end %> ================================================ FILE: app/views/global_note_templates/new.html.erb ================================================
<%= link_to(l(:label_list_templates), { controller: 'global_note_templates', action: 'index' }, class: 'icon icon-template') %>

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

<%= labelled_form_for :global_note_template, note_template, url: { controller: 'global_note_templates', action: 'create' }, html: { id: 'global_note_template-form', class: nil, multipart: false } do |f| %> <%= render 'form', { f: f, trackers: trackers, note_template: note_template, projects: projects, apply_all_projects: apply_all_projects } %>
<%= submit_tag l(:button_create) %> <%= link_to l(:button_cancel), { action: 'index'}, data: { confirm: l(:text_are_you_sure) } %> <% end %> ================================================ FILE: app/views/global_note_templates/show.html.erb ================================================
<%= link_to l(:button_delete), { controller: 'global_note_templates', action: 'destroy', id: note_template }, data: { confirm: l(:text_are_you_sure) }, name: l(:enabled_template_cannot_destroy, default: 'Only disabled template can be destroyed.'), disabled: note_template.enabled?, method: 'delete', class: 'icon icon-del template-disabled-link' %> <%= link_to(l(:label_list_templates), { controller: 'global_note_templates', action: 'index' }, class: 'icon icon-template') %>

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

<%= labelled_form_for :global_note_template, note_template, url: { controller: 'global_note_templates', action: 'update', id: note_template }, html: { id: 'global_note_template-form', class: nil, multipart: false } do |f| %> <%= render 'form', { f: f, trackers: trackers, note_template: note_template, projects: projects, apply_all_projects: apply_all_projects } %>
<%= submit_tag l(:button_save) %> <%= link_to l(:button_cancel), { action: 'index' }, onclick: 'Element.hide("edit-note_template"); return false' %> <% end %> ================================================ FILE: app/views/issue_templates/_form.html.erb ================================================ <%= error_messages_for 'issue_template' %>

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

<%= l(:label_applied_for_issue) %>

<% if issue_template.tracker.blank? %> <%= f.select :tracker_id, template_target_trackers(project, issue_template), required: true, label: l(:label_tracker), include_blank: true %> <%= h issue_template.tracker.present? ? issue_template.tracker.name : l(:orphaned_template, default: 'Orphaned template from tracker') %> <% else %> <%= f.select :tracker_id, template_target_trackers(project, issue_template), required: true, label: l(:label_tracker), selected: issue_template.tracker.id %> <% unless project_tracker?(issue_template.tracker, project) %>
<%= non_project_tracker_msg(project_tracker?(issue_template.tracker, project)) %> <% end %> <% end %>

<%= f.text_field :issue_title, required: false, size: 80, label: l(:issue_title) %> <%= l(:help_for_this_field) %>

<%= f.text_area :description, cols: 78, rows: 12, required: true, label: l(:issue_description), class: 'wiki-edit', style: 'overflow: auto;' %>

<% if builtin_fields_enable %>

<%= l(:help_for_this_field) %>

' v-model='newItemValue'> ' v-model='newItemValue'> <%= l(:button_add) %>

<%= l(:label_field_information, default: 'Field information') %>
{{ customFields[newItemTitle] }}
  • {{ customFields[item.title].name }}: {{ item.value }} / {{ item.title }} <%= l(:unavailable_fields_for_this_tracker, default: 'Unavailable field for this tarcker') %> : {{ item.value }} / {{ item.title }}
<%= l(:button_reset) %> <%= l(:button_apply) %>

<%= f.text_area :builtin_fields, required: false, cols: 60, rows: 4, label: l(:label_builtin_fields_json, default: 'JSON for fields') %>

<% end %>

<%= f.text_area :note, cols: 70, rows: 3, required: false, label: l(:issue_template_note), style: 'overflow:auto;' %>

<%= f.text_field :related_link, type: 'url', size: 70, label: l(:issue_template_related_link, default: 'Related link') %> <%= l(:help_for_this_field) %>

<%= f.check_box :is_default, label: l(:field_is_default) %> <%= l(:help_for_this_field) %>

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

<%= f.check_box :enabled_sharing, label: l(:label_enabled_sharing) %> <%= l(:help_for_this_field) %>

<%= wikitoolbar_for 'issue_template_description' %> <% if builtin_fields_enable %> <%= javascript_include_tag('vue.min', plugin: 'redmine_issue_templates') %> <%= javascript_include_tag('template_fields', plugin: 'redmine_issue_templates') %> <% end %> ================================================ FILE: app/views/issue_templates/_issue_select_form.html.erb ================================================ <% return '' unless @issue.project.module_enabled? :issue_templates %> <% return '' unless User.current.allowed_to?(:show_issue_templates, @issue.project) %> ================================================ FILE: app/views/issue_templates/_issue_template_link.html.erb ================================================ <% if authorize_for('issue_templates', 'show') %>

<%= l(:issue_template) %>

  • <%= link_to(l(:label_list_templates), controller: 'issue_templates', action: 'index', project_id: @project) %>
  • <%= link_to_if_authorized(l(:label_new_templates), { controller: 'issue_templates', action: 'new', project_id: @project }) %>
<%- end -%> <% if authorize_for('note_templates', 'show') %>

<%= l(:note_template) %>

  • <%= link_to(l(:label_list_templates), controller: 'note_templates', action: 'index', project_id: @project) %>
  • <%= link_to_if_authorized(l(:label_new_templates), { controller: 'note_templates', action: 'new', project_id: @project }) %>
<%- end -%> ================================================ FILE: app/views/issue_templates/_list_templates.api.rsb ================================================ api.array :global_issue_templates do global_issue_templates.each do |template| api.template do api.id template.id api.tracker_id template.tracker_id api.tracker_name template.tracker.id api.title template.title api.issue_title template.issue_title api.description template.description api.note template.note api.enabled template.enabled api.updated_on template.updated_on api.created_on template.created_on api.updated_on template.updated_on end end end api.array :inherit_templates do inherit_templates.each do |template| api.template do api.id template.id api.tracker_id template.tracker_id api.tracker_name template.tracker.name api.title template.title api.issue_title template.issue_title api.description template.description api.note template.note api.enabled template.enabled api.is_default template.id == default_template api.enabled_sharing template.enabled_sharing api.position template.position api.updated_on template.updated_on api.created_on template.created_on api.updated_on template.updated_on end end end api.array :issue_templates do issue_templates.each do |template| api.template do api.id template.id api.tracker_id template.tracker_id api.tracker_name template.tracker.name api.title template.title api.issue_title template.issue_title api.description template.description api.note template.note api.enabled template.enabled api.is_default template.is_default api.enabled_sharing template.enabled_sharing api.position template.position api.updated_on template.updated_on api.created_on template.created_on api.updated_on template.updated_on end end end ================================================ FILE: app/views/issue_templates/_list_templates.html.erb ================================================ <% issue_templates.each do |template| %> template_data'> <% end %> <% inherit_templates.each do |template| %> <% end %> <% global_issue_templates.each do |template| %> template_data'> <% end %>
<%=h l(:issue_template_name) %> <%=h l(:issue_title) %> <%=h l(:issue_description) %> <%= l(:field_is_default) %> <%=h l(:button_apply, default: 'Apply') %>
<%= template.title %> <%= template.issue_title %>
<%= template.title %> <%= textilizable(template.description) %> <% if template.related_link.present? %>
<%= link_to template.link_title.present? ? template.link_title : l(:issue_template_related_link, default: 'Related link'), template.related_link, target: '_blank', rel: 'nofollow noopener', class: 'external' %> <% end %>
<%= checked_image template.is_default? %>
<%= template.title %> <%= template.issue_title %>
<%= template.title %> <%= textilizable(template.description) %> <% if template.related_link.present? %>
<%= link_to template.link_title.present? ? template.link_title : l(:issue_template_related_link, default: 'Related link'), template.related_link, target: '_blank', rel: 'nofollow noopener', class: 'external' %> <% end %>
<%= checked_image template == default_template %>
<%= template.title %> <%= template.issue_title %>
<%= template.issue_title %> <%= textilizable(template.description) %> <% if template.related_link.present? %>
<%= link_to template.link_title.present? ? template.link_title : l(:issue_template_related_link, default: 'Related link'), template.related_link, target: '_blank', rel: 'nofollow noopener', class: 'external' %> <% end %>
<%= checked_image template == default_template %>
================================================ FILE: app/views/issue_templates/_note_form.html.erb ================================================ <% element_id = type if type == 'template_edit_journal' element_id = "template_journal_#{@journal.id}_notes" end project_id = issue&.project_id tracker_id = issue&.tracker_id %>
<%=h l(:display_and_filter_issue_templates_in_dialog, default: 'Filter Templates') %>
================================================ FILE: app/views/issue_templates/_template_pulldown.html.erb ================================================ <%= options_for_template_pulldown(grouped_options) %> ================================================ FILE: app/views/issue_templates/index.api.rsb ================================================ api.array :global_issue_templates do @global_issue_templates.each do |template| api.template do api.id template.id api.tracker_id template.tracker_id api.tracker_name template.tracker.try(:name) || nil api.title template.title api.issue_title template.issue_title api.description template.description api.note template.note api.enabled template.enabled api.updated_on template.updated_on api.created_on template.created_on api.updated_on template.updated_on end end end api.array :inherit_templates do @inherit_templates.each do |template| api.template do api.id template.id api.tracker_id template.tracker_id api.tracker_name template.tracker.tracker.try(:name) || nil api.title template.title api.issue_title template.issue_title api.description template.description api.note template.note api.enabled template.enabled api.is_default template.is_default api.enabled_sharing template.enabled_sharing api.position template.position api.updated_on template.updated_on api.created_on template.created_on api.updated_on template.updated_on end end end api.array :issue_templates do project_templates.each do |template| api.template do api.id template.id api.tracker_id template.tracker_id api.tracker_name template.tracker.try(:name) || nil api.title template.title api.issue_title template.issue_title api.description template.description api.note template.note api.enabled template.enabled api.is_default template.is_default api.enabled_sharing template.enabled_sharing api.position template.position api.updated_on template.updated_on api.created_on template.created_on api.updated_on template.updated_on end end end ================================================ FILE: app/views/issue_templates/index.html.erb ================================================

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

<%= render partial: 'common/nodata', locals: { trackers: tracker_ids } %>
<%= link_to_if_authorized(l(:label_new_templates), { controller: 'issue_templates', action: 'new', project_id: @project }, class: 'icon icon-add') %>
<% if @notice -%>
<%= @notice -%>
<% end -%> <% if @template_map.empty? %>
<%= l(:no_issue_templates_for_this_project) %>
<% end %> <% @template_map.each_key do |tracker| %>

<%= tracker.name %>

<%= non_project_tracker_msg(project_tracker?(tracker, @project)) %> <% if authorize_for('issue_templates', 'edit') %> <% end %> <% @template_map[tracker].each do |issue_template| %> issue_template issue'> <% if authorize_for('issue_templates', 'edit') %> <% end %> <% end %>
# <%= 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: :issue_templates, action: 'show', id: issue_template.id, project_id: issue_template.project }, { title: issue_template.note } %> <%= link_to h(issue_template.title), { controller: 'issue_templates', id: issue_template.id, action: 'show' }, { title: "#{html_escape(issue_template.note) }"} %>
<%= issue_template.title %> <%= textilizable(issue_template.description) %> <% if issue_template.related_link.present? %>
<%= link_to issue_template.link_title.present? ? issue_template.link_title : l(:issue_template_related_link, default: 'Related link'), issue_template.related_link, target: '_blank', rel: 'nofollow noopener', class: 'external' %> <% end %>
<%=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? %> <%= reorder_handle(issue_template, url: url_for(:controller => 'issue_templates', :action => 'update', :id => issue_template.id, :project_id => issue_template.project)) %>
<%= javascript_tag do %> // NOTE: Sortable feature depends on Redmine's sorting jQuery plugin. $(function() { $('table.table-sortable tbody').positionedItems() }) <% end %> <% end %> <% unless @inherit_templates.blank? %>

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

<% @inherit_templates.each do |issue_template| %> issue_template issue'> <% end %>
# <%= l(:issue_template_name) %> <%= l(:label_preview) %> <%= l(:field_tracker) %> <%= l(:field_author) %> <%= l(:field_updated_on) %> <%= l(:field_is_default) %> <%= l(:label_enabled) %>
<%= link_to h(issue_template.id), { controller: 'issue_templates', action: 'show', id: issue_template.id, project_id: issue_template.project_id }, { title: issue_template.note } %> <%= link_to h(issue_template.title), { controller: 'issue_templates', project_id: issue_template.project_id, id: issue_template.id, action: 'show' }, { title: "#{html_escape(issue_template.note)}" } %>
<%=h issue_template.title %> <%=h textilizable(issue_template.description) %> <% if issue_template.related_link.present? %>
<%= link_to issue_template.link_title.present? ? issue_template.link_title : l(:issue_template_related_link, default: 'Related link'), issue_template.related_link, target: '_blank', rel: 'nofollow noopener', class: 'external' %> <% end %>
<%=h issue_template.tracker.name %> <%=h issue_template.author %> <%=h format_time(issue_template.updated_on)%> <%= checked_image issue_template.is_default? %> <%= checked_image issue_template.enabled? %>
<% end %> <% unless @global_issue_templates.blank? %>

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

<% if apply_all_projects %>

<%= l(:note_apply_global_template_to_all_projects_setting_enabled) %>
<%= l(:note_project_local_template_override_global_template) %>

<% end %> <%= l(:only_admin_can_associate_global_template) %> <% @global_issue_templates.each do |issue_template| %> issue_template issue'> <% end %>
# <%= l(:issue_template_name) %> <%= l(:label_preview) %> <%= l(:field_tracker) %> <%= l(:field_author) %> <%= l(:field_updated_on) %> <%= l(:field_is_default) %>
<% if User.current.admin? %> <%= link_to h(issue_template.id), { controller: 'global_issue_templates', action: 'show', id: issue_template.id, }, { title: issue_template.note } %> <% else %> <%=h issue_template.id %> <% end %> <%= link_to h(issue_template.title), { controller: 'global_issue_templates', id: issue_template.id, action: 'show' }, { title: "#{html_escape(issue_template.note)}" } %>
<%=h issue_template.title %> <%=h textilizable(issue_template.description) %> <% if issue_template.related_link.present? %>
<%= link_to issue_template.link_title.present? ? issue_template.link_title : l(:issue_template_related_link, default: 'Related link'), issue_template.related_link, target: '_blank', rel: 'nofollow noopener', class: 'external' %> <% end %>
<% if issue_template.tracker.present? %> <%= h issue_template.tracker.try(:name) || "ID: #{issue_template.tracker_id}" %> <% else %> <%= "ID: #{issue_template.tracker_id}" %> <% end %> <%=h issue_template.author %> <%=h format_time(issue_template.updated_on)%> <%= checked_image issue_template.is_default? %>
<% end %> <%= render partial: 'common/template_links' %> ================================================ FILE: app/views/issue_templates/new.html.erb ================================================
<%= link_to(l(:label_list_templates), { controller: 'issue_templates', action: 'index', project_id: project }, class: 'icon icon-template') %>

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

<%= render partial: 'common/nodata', locals: { trackers: project.trackers } %> <% if project.trackers.any? %> <%= labelled_form_for :issue_template, @issue_template, url: { controller: 'issue_templates', action: 'create', project_id: project }, html: { id: 'issue_template-form', class: nil, multipart: false } do |f| %> <%= render 'form', { f: f, issue_template: issue_template, project: project, custom_fields: custom_fields, builtin_fields_enable: builtin_fields_enable } %>
<%= submit_tag l(:button_create) %> <%= link_to l(:button_cancel), { action: 'index'}, data: { confirm: l(:text_are_you_sure) } %> <% end %> <% end %>
<%= render partial: 'common/template_links' %>
================================================ FILE: app/views/issue_templates/show.html.erb ================================================
<%= link_to_if_authorized l(:button_edit), { controller: 'issue_templates', action: 'update', id: issue_template, project_id: project }, title: l(:button_edit), class: 'icon icon-edit', accesskey: accesskey(:edit), onclick: "document.getElementById('edit-issue_template').style.display = 'block'; return false;" %> <%= link_to l(:button_copy), { controller: 'issue_templates', action: 'new', project_id: project, copy_from: issue_template.id }, class: 'icon icon-copy', title: 'copy!' %> <%= link_to_if_authorized l(:button_delete), { controller: 'issue_templates', action: 'destroy', id: issue_template, project_id: project }, title: l(:enabled_template_cannot_destroy, default: 'Only disabled template can be destroyed.'), data: { confirm: l(:template_remove_confirm, { count: child_project_used_count, default: 'Are you sure to remove this template? %{count} subprojects use this template.' }) }, method: 'delete', class: 'icon icon-del template-disabled-link', disabled: issue_template.enabled? %> <%= link_to(l(:label_list_templates), { controller: 'issue_templates', action: 'index', project_id: project }, class: 'icon icon-template') %>

<%= l(:issue_templates) %>: #<%= issue_template.id %> <%= issue_template.title %> <%= avatar(issue_template.author, size: '24') %>

<%= render partial: 'common/nodata', locals: { trackers: project.trackers } %> <% if authorize_for('issue_templates', 'update') %>
<%= labelled_form_for :issue_template, issue_template, url: { controller: 'issue_templates', action: 'update', project_id: project, id: issue_template }, html: { id: 'issue_template-form', class: nil, multipart: false } do |f| %> <%= render 'form', { f: f, issue_template: issue_template, project: project, custom_fields: custom_fields, builtin_fields_enable: builtin_fields_enable } %>
<%= submit_tag l(:button_save) %> <%= link_to l(:button_cancel), { action: 'index' }, onclick: 'Element.hide("edit-issue_template"); return false;' %> <% end %>
<% else %>

<%= h issue_template.title %>

<%= l(:label_applied_for_issue) %>

<%= h issue_template.tracker.present? ? issue_template.tracker.name : l(:orphaned_template, default: 'Orphaned template from tracker') %>
<%= non_project_tracker_msg(project_tracker?(issue_template.tracker, project)) %>

<%= h issue_template.issue_title %>

<%= l(:issue_description) %>

<%= textilizable(issue_template.description) %>

<%= issue_template.note.blank? ? l(:label_none) : issue_template.note %>

<%= issue_template.related_link.blank? ? l(:label_none) : issue_template.related_link %>

<%= checked_image issue_template.is_default? %>

<%= checked_image issue_template.enabled? %>

<%= checked_image issue_template.enabled_sharing? %> <%= l(:label_number_of_subprojects_use_this_template, { count: child_project_used_count, default: '%{count} subprojects use this template.....' }) %>

<%= authoring issue_template.created_on, issue_template.author %> <% if issue_template.created_on != issue_template.updated_on %> (<%= l(:label_updated_time, time_tag(issue_template.updated_on)).html_safe %>) <% end %>

<% end %> <%= render partial: 'common/template_links' %> ================================================ FILE: app/views/issue_templates_settings/index.html.erb ================================================
<%= render partial: 'common/nodata', locals: { trackers: @project.trackers } %>

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

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

<%= l(:about_help_message) %>

<%= labelled_form_for :settings, @issue_templates_setting, url: { controller: 'issue_templates_settings', action: 'edit', project_id: @project, setting_id: @issue_templates_setting.id }, html: { id: 'issue_templates_settings' } do |f| %> <%= error_messages_for 'issue_templates_setting' %>

<%= f.check_box :inherit_templates, label: l(:label_inherit_templates) %> <%= l(:help_for_this_field) %>

<%= f.check_box :should_replaced, label: l(:label_should_replaced) %> <%= l(:help_for_this_field) %>

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

<%=content_tag(:label, l(:label_help_message)) %> <%=text_area_tag 'settings[help_message]', @issue_templates_setting['help_message'], size: '50x5', class: 'wiki-edit' %>

<%= wikitoolbar_for 'settings_help_message' %>
<%= submit_tag l(:button_save) %> <% end %>
<%= render partial: 'common/template_links' %>
================================================ FILE: app/views/note_templates/_form.html.erb ================================================ <%= error_messages_for 'note_template' %>

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

<%= l(:label_applied_for_issue) %>

<% if note_template.tracker.blank? %> <%= f.select :tracker_id, template_target_trackers(project, note_template), required: true, label: l(:label_tracker), include_blank: true %> <%= h note_template.tracker.present? ? note_template.tracker.name : l(:orphaned_template, default: 'Orphaned template from tracker') %> <% else %> <%= f.select :tracker_id, template_target_trackers(project, note_template), required: true, label: l(:label_tracker), selected: note_template.tracker.id %> <% unless project_tracker?(note_template.tracker, project) %>
<%= non_project_tracker_msg(project_tracker?(note_template.tracker, project)) %> <% end %> <% end %>

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

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

'> <% Role.givable.each do |role| %> <%= check_box_tag('note_template[role_ids][]', role.id, note_template.note_visible_roles.pluck(:role_id).to_a.include?(role.id)) %> <%= role.name %> <% end %>

<%= f.text_area :memo, cols: 70, rows: 3, required: false, label: l(:issue_template_note) %> <%= l(:help_for_this_field) %>

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

<%= wikitoolbar_for 'note_template_description' %> ================================================ FILE: app/views/note_templates/_list_note_templates.html.erb ================================================ <% note_templates.each do |template| %> <% end %> <% global_note_templates.each do |template| %> <% end %>
<%=h l(:note_template_name, default: "Name") %> <%=h l(:note_description, default: "Comment Body") %> <%=h l(:button_apply, default: 'Apply') %>
<%= template.name %>
<%= template.name %> <%= textilizable(template.description) %>
<%= l(:issue_template_note) %> <%= textilizable(template.try(:memo)) %>
<%= template.name %>
<%= template.name %> <%= textilizable(template.description) %>
<%= l(:issue_template_note) %> <%= textilizable(template.try(:memo)) %>
================================================ FILE: app/views/note_templates/index.api.rsb ================================================ api.array :note_templates do note_templates.each do |template| api.template do api.id template.id api.tracker_id template.tracker_id api.tracker_name template.tracker.name api.title template.title api.description template.description api.enabled template.enabled api.created_on template.created_on api.updated_on template.updated_on end end end ================================================ FILE: app/views/note_templates/index.html.erb ================================================

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

<%= render partial: 'common/nodata', locals: { trackers: tracker_ids } %>
<%= link_to_if_authorized(l(:label_new_templates), { controller: 'note_templates', action: 'new', project_id: @project }, class: 'icon icon-add') %>
<% if @notice -%>
<%= @notice -%>
<% end -%> <% if @template_map.empty? %>
<%= l(:no_note_templates_for_this_project) %>
<% end %> <% @template_map.each_key do |tracker| %>

<%= tracker.name %>

<% if authorize_for('note_templates', 'edit') %> <% end %> <% @template_map[tracker].each do |note_template| %> note_template issue'> <% if authorize_for('note_templates', 'edit') %> <% end %> <% end %>
# <%= 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: :note_templates, action: 'show', id: note_template.id, project_id: note_template.project }, { title: note_template.memo } %> <%= link_to h(note_template.name), { controller: 'note_templates', id: note_template.id, action: 'show' }, { title: "#{html_escape(note_template.memo)}" } %>
<%= note_template.name %> <%= textilizable(note_template.description) %>
<%=h note_template.tracker.name %> <%=h note_template.author %> <%= format_time(note_template.updated_at)%> <%= checked_image note_template.enabled? %> <%= reorder_handle(note_template, url: url_for(:controller => 'note_templates', :action => 'update', :id => note_template.id, :project_id => note_template.project)) %>
<%= javascript_tag do %> // NOTE: Sortable feature depends on Redmine's sorting jQuery plugin. $(function() { $('table.table-sortable tbody').positionedItems() }) <% end %> <% end %> <% if @global_note_templates.present? %>

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

<% if apply_all_projects %>

<%= l(:note_apply_global_template_to_all_projects_setting_enabled) %>
<%= l(:note_project_local_template_override_global_template) %>

<% end %> <%= l(:only_admin_can_associate_global_template) %> <% @global_note_templates.each do |note_template| %> issue_template issue'> <% end %>
# <%= l(:issue_template_name) %> <%= l(:label_preview) %> <%= l(:field_tracker) %> <%= l(:field_author) %> <%= l(:field_updated_on) %> <%= l(:label_enabled) %>
<% if User.current.admin? %> <%= link_to h(note_template.id), { controller: 'global_note_templates', action: 'show', id: note_template.id, }, { title: note_template.memo } %> <% else %> <%=h note_template.id %> <% end %> <%= link_to h(note_template.name), { controller: 'global_note_templates', id: note_template.id, action: 'show' }, { title: "#{html_escape(note_template.memo)}" } %>
<%= note_template.name %> <%= textilizable(note_template.description) %>
<%=h note_template.tracker.name %> <%=h note_template.author %> <%= format_time(note_template.updated_at)%> <%= checked_image note_template.enabled? %>
<% end %> <%= render partial: 'common/template_links' %> ================================================ FILE: app/views/note_templates/new.html.erb ================================================
<%= link_to(l(:label_list_templates), { controller: 'note_templates', action: 'index', project_id: project }, class: 'icon icon-template') %>

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

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

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

<%= render partial: 'common/nodata', locals: { trackers: project.trackers } %> <% if authorize_for('note_templates', 'update') %>
<%= labelled_form_for :note_template, note_template, url: { controller: 'note_templates', action: 'update', project_id: project, id: note_template }, html: { id: 'note_template-form', class: nil, multipart: false } do |f| %> <%= render 'form', { f: f, note_template: note_template, project: project } %>
<%= submit_tag l(:button_save) %> <%= link_to l(:button_cancel), { action: 'index' }, onclick: 'Element.hide("edit-note_template"); return false' %> <% end %>
<% end %>

<%= h note_template.name %>

<%= l(:label_applied_for_issue) %>

<%= h note_template.tracker.name %>

<%= h note_template.name %>

<%= l(:issue_description) %>

<%= textilizable(note_template.description) %>

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

<%= checked_image note_template.enabled? %>

<%= authoring note_template.created_at, note_template.author %> <% if note_template.created_at != note_template.updated_at %> (<%= l(:label_updated_time, time_tag(note_template.updated_at)).html_safe %>) <% end %>

<%= render partial: 'common/template_links' %> ================================================ FILE: app/views/settings/_redmine_issue_templates.html.erb ================================================

<%= hidden_field_tag("settings[apply_global_template_to_all_projects]", 0, :id => nil).html_safe %> <%= check_box_tag 'settings[apply_global_template_to_all_projects]', true, @settings['apply_global_template_to_all_projects'] == "true" %> <%= l(:help_for_this_field) %>

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

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

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

<%= hidden_field_tag('settings[enable_builtin_fields]', 0, :id => nil).html_safe %> <%= check_box_tag 'settings[enable_builtin_fields]', true, @settings['enable_builtin_fields'] == 'true' %> <%= l(:help_for_this_field) %>

================================================ FILE: assets/javascripts/issue_templates.js ================================================ /* * To change this template, choose Tools | Templates * and open the template in the editor. * * Use '==' operator to evaluate null or undefined. */ /* global CKEDITOR, Element, Event */ 'use strict' function ISSUE_TEMPLATE (config) { this.pulldownUrl = config.pulldownUrl this.loadUrl = config.loadUrl this.confirmMsg = config.confirmMessage this.shouldReplaced = config.shouldReplaced this.generalTextYes = config.generalTextYes this.generalTextNo = config.generalTextNo this.isTriggeredBy = config.isTriggeredBy } ISSUE_TEMPLATE.prototype = { clearValue: (id) => { let target = document.getElementById(id) if (target == null) { return } target.value = '' }, eraseSubjectAndDescription: function () { this.clearValue('issue_description') this.clearValue('issue_subject') try { if (CKEDITOR.instances.issue_description) { CKEDITOR.instances.issue_description.setData('') } } catch (e) { // do nothing. } }, openDialog: function (url, title) { // Open dialog (modal window) to display selectable templates list. window.fetch(url) .then((response) => { return response.text() }) .then((data) => { document.getElementById('filtered_templates_list').innerHTML = data let titleElement = document.getElementById('issue_template_dialog_title') titleElement.textContent = title const templateElements = document.querySelectorAll('i.template-update-link') Array.from(templateElements).forEach(el => { el.addEventListener('click', (event) => { this.updateTemplateSelect(event) }) }) }) }, revertAppliedTemplate: function () { let issueSubject = document.getElementById('issue_subject') let oldSubject = document.getElementById('original_subject') let issueDescription = document.getElementById('issue_description') let oldDescription = document.getElementById('original_description') let ns = this issueSubject.value = ns.escapeHTML(oldSubject.textContent) if (issueDescription != null) { issueDescription.value = ns.escapeHTML(oldDescription.textContent) } try { if (CKEDITOR.instances.issue_description) { CKEDITOR.instances.issue_description.setData(ns.escapeHTML(oldDescription.text())) } } catch (e) { // do nothing. } oldDescription.textContent = '' oldDescription.textContent = '' document.getElementById('revert_template').classList.add('disabled') }, loadTemplate: function () { let selectedTemplate = document.getElementById('issue_template') let ns = this if (selectedTemplate.value === '') return let templateType = '' let selectedOption = selectedTemplate.options[selectedTemplate.selectedIndex] if (selectedOption.classList.contains('global')) { templateType = 'global' } window.fetch(ns.loadUrl, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': ns.getCsrfToken() }, body: JSON.stringify({ template_id: selectedTemplate.value, template_type: templateType }) }) .then((response) => { return response.text() }) .then((data) => { // NOTE: Workaround for GiHub Issue, to prevent overwrite with default template // when operator submits new issue form without required field and returns // with error message. If flash message #errorExplanation exists, not overwrited. // (https://github.com/akiko-pusu/redmine_issue_templates/issues/50) if (document.querySelector('#errorExplanation') && document.querySelector('#errorExplanation')[0]) { document.querySelector('#errorExplanation') return } // Returned JSON may have the key named 'global_template' or 'issue_template' let parsedData = JSON.parse(data) let templateKey = Object.keys(parsedData)[0] let obj = parsedData[templateKey] obj.description = (obj.description == null) ? '' : obj.description obj.issue_title = (obj.issue_title == null) ? '' : obj.issue_title let issueSubject = document.getElementById('issue_subject') let issueDescription = document.getElementById('issue_description') this.loadedTemplate = obj if (ns.shouldReplaced === 'true' && (issueDescription.value !== '' || issueSubject.value !== '')) { if (obj.description !== '' || obj.issue_title !== '') { let hideConfirmFlag = ns.hideOverwiteConfirm() if (hideConfirmFlag === false) { return ns.confirmToReplaceContent(obj) } } } ns.replaceTemplateValue(obj) }) }, replaceTemplateValue: function (obj) { let ns = this let oldSubj = '' let oldVal = '' let issueSubject = document.getElementById('issue_subject') let issueDescription = document.getElementById('issue_description') if (issueDescription != null) { let originalDescription = document.getElementById('original_description') if (issueDescription.value !== '' && ns.shouldReplaced === 'false') { oldVal = issueDescription.value + '\n\n' } originalDescription.textContent = issueDescription.value issueDescription.getAttribute('original_description', issueDescription.value) if (oldVal.replace(/(?:\r\n|\r|\n)/g, '').trim() !== obj.description.replace(/(?:\r\n|\r|\n)/g, '').trim()) { issueDescription.value = oldVal + obj.description } } let originalSubject = document.getElementById('original_subject') if (issueSubject.value !== '' && ns.shouldReplaced === 'false') { oldSubj = issueSubject.value + ' ' } originalSubject.textContent = issueSubject.value issueSubject.setAttribute('original_title', issueSubject.value) if (oldSubj.trim() !== obj.issue_title.trim()) { issueSubject.value = oldSubj + obj.issue_title } try { if (CKEDITOR.instances.issue_description) { CKEDITOR.instances.issue_description.setData(oldVal + obj.description) } } catch (e) { // do nothing. } // show message just after default template loaded. if (ns.confirmMsg && ns.shouldReplaced) { ns.showLoadedMessage(issueDescription) } if (originalSubject.textContent.length > 0) { document.getElementById('revert_template').classList.remove('disabled') } ns.setRelatedLink(obj) ns.builtinFields(obj) ns.confirmToReplace = true }, confirmToReplaceContent: function (obj) { let ns = this let dialog = document.getElementById('issue_template_confirm_to_replace_dialog') dialog.style.visibility = 'visible' dialog.classList.add('active') document.getElementById('overwrite_yes').addEventListener('click', () => { if (document.getElementById('issue_template_confirm_to_replace_hide_dialog').checked) { // NOTE: Use document.cookie because Redmine itself does not use jquery.cookie.js. document.cookie = 'issue_template_confirm_to_replace_hide_dialog=1' } else { document.cookie = 'issue_template_confirm_to_replace_hide_dialog=0' } dialog.classList.remove('active') ns.replaceTemplateValue(obj) }) document.getElementById('overwrite_no').addEventListener('click', () => { if (document.getElementById('issue_template_confirm_to_replace_hide_dialog').checked) { // NOTE: Use document.cookie because Redmine itself does not use jquery.cookie.js. document.cookie = 'issue_template_confirm_to_replace_hide_dialog=1' } else { document.cookie = 'issue_template_confirm_to_replace_hide_dialog=0' } dialog.classList.remove('active') }) document.getElementById('issue_template_confirm_to_replace_dialog_cancel') .addEventListener('click', () => { dialog.classList.remove('active') }) }, showLoadedMessage: function (target) { let ns = this // in app/views/issue_templates/_issue_select_form.html.erb let templateStatusArea = document.getElementById('template_status-area') if (templateStatusArea == null) return false if (document.querySelector('div.flash_message')) { document.querySelector('div.flash_message').remove() } let messageElement = document.createElement('div') messageElement.innerHTML = ns.confirmMsg messageElement.classList.add('flash_message') messageElement.classList.add('fadeout') templateStatusArea.appendChild(messageElement) }, getCsrfToken: function () { const metas = document.getElementsByTagName('meta') for (let meta of metas) { if (meta.getAttribute('name') === 'csrf-token') { return meta.getAttribute('content') } } return '' }, setPulldown: function (tracker) { let ns = this let params = { issue_tracker_id: tracker, is_triggered_by: ns.isTriggeredBy } let pullDownProject = document.getElementById('issue_project_id') if (pullDownProject) { params.issue_project_id = pullDownProject.value } window.fetch(ns.pulldownUrl, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': ns.getCsrfToken() }, body: JSON.stringify(params) }) .then((response) => { return response.text() }) .then((data) => { document.getElementById('issue_template').innerHTML = data let length = document.querySelectorAll('#issue_template > optgroup > option').length if (length < 1) { document.getElementById('template_area').style.display = 'none' if (ns.isTriggeredBy != null && this.isTriggeredBy === 'issue_tracker_id') { if (document.querySelectorAll('#issue-form.new_issue').length > 0 && ns.should_replaced === true) { if (typeof ns !== 'undefined') { ns.eraseSubjectAndDescription() } } } } else { document.getElementById('template_area').style.display = 'inline' } let changeEvent = new Event('change') document.getElementById('issue_template').dispatchEvent(changeEvent) }) }, setRelatedLink: function (obj) { let relatedLink = document.getElementById('issue_template_related_link') if (obj.related_link != null && obj.related_link !== '') { relatedLink.setAttribute('href', obj.related_link) relatedLink.style.display = 'inline' relatedLink.textContent = obj.link_title } else { relatedLink.style.display = 'none' } }, escapeHTML: function (val) { const div = document.createElement('div') div.textContent = val return div.textContent }, unescapeHTML: function (val) { const div = document.createElement('div') div.innerHTML = val return div.innerHTML }, replaceCkeContent: function () { let element = document.getElementById('issue_description') return CKEDITOR.instances.issue_description.setData(element.value) }, hideOverwiteConfirm: function () { let cookieArray = [] if (document.cookie !== '') { let tmp = document.cookie.split('; ') for (let i = 0; i < tmp.length; i++) { let data = tmp[i].split('=') cookieArray[data[0]] = decodeURIComponent(data[1]) } } let confirmationCookie = cookieArray['issue_template_confirm_to_replace_hide_dialog'] if (confirmationCookie == null || parseInt(confirmationCookie) === 0) { return false } return true }, // support built-in field update builtinFields: function (template) { let ns = this let builtinFieldsJson = template.builtin_fields_json if (builtinFieldsJson == null) return false try { Object.keys(builtinFieldsJson).forEach(function (key) { let value = builtinFieldsJson[key] let element = document.getElementById(key) if (/issue_custom_field_values/.test(key)) { let name = key.replace(/(issue)_(\w+)_(\d+)/, '$1[$2][$3]') let elements = document.querySelectorAll('[name^="' + name + '"]') if (elements.length === 1) { element = elements[0] } else { return ns.updateFieldValues(elements, value) } } if (/issue_watcher_user_ids/.test(key)) { return ns.checkSelectedWatchers(value) } if (element == null) { return } ns.updateFieldValue(element, value) }) } catch (e) { console.log(`NOTE: Builtin / custom fields could not be applied due to this error. ${e.message} : ${e.message}`) } }, updateFieldValue: function (element, value) { // In case field is a select element, scans its option values and marked 'selected'. if (element.tagName.toLowerCase() === 'select') { let values = [] if (Array.isArray(value) === false) { values[0] = value } else { values = value } for (let i = 0; i < values.length; i++) { let options = document.querySelectorAll('#' + element.id + ' option') let filteredOptions = Array.from(options).filter(option => option.text === values[i]) if (filteredOptions.length > 0) { filteredOptions[0].selected = true } } } else { element.value = value } }, updateFieldValues: function (elements, value) { let ns = this for (let i = 0; i < elements.length; i++) { let element = elements[i] if (element.tagName.toLowerCase() === 'select') { return ns.updateFieldValue(element, value) } if (element.value === value) { if (element.tagName.toLowerCase() === 'input') { element.checked = true } else { element.selected = true } } // in case multiple value if (Array.isArray(value)) { if (element.tagName.toLowerCase() === 'input' && value.includes(element.value)) { element.checked = true } } } }, updateTemplateSelect: function (event) { let link = event.target let optionId = link.getAttribute('data-issue-template-id') let optionSelector = '#issue_template > optgroup > option[value="' + optionId + '"]' if (link.classList.contains('template-global')) { optionSelector = optionSelector + '[class="global"]' } let targetOption = document.querySelector(optionSelector) targetOption['selected'] = true let changeEvent = new Event('change') document.getElementById('issue_template').dispatchEvent(changeEvent) }, checkSelectedWatchers: function (values) { let targets = document.querySelectorAll('input[name="issue[watcher_user_ids][]"]') for (let i = 0; i < targets.length; i++) { let target = targets[i] if (values.includes(target.value)) { target.checked = true } } }, filterTemplate: function (event) { let cols = document.getElementsByClassName('template_data') let searchWord = event.target.value let reg = new RegExp(searchWord, 'gi') for (let i = 0; i < cols.length; i++) { let val = cols[i] if (val.textContent.match(reg)) { val.style.display = 'table-row' } else { val.style.display = 'none' } } }, changeTemplatePlace: function () { if (document.querySelector('div.flash_message')) { document.querySelector('div.flash_message').remove() } const subjectParentNode = document.getElementById('issue_subject').parentNode subjectParentNode.parentNode.insertBefore(document.getElementById('template_area'), subjectParentNode) } } // for IE11 compatibility (IE11 does not support native Element.closest) // Ref. https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill // Ref. https://github.com/akiko-pusu/redmine_issue_templates/issues/270 if (!Element.prototype.matches) { Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector } if (!Element.prototype.closest) { Element.prototype.closest = function (s) { let el = this do { if (el.matches(s)) return el el = el.parentElement || el.parentNode } while (el != null && el.nodeType === 1) return null } } // --------- Add event listeners -------------- // document.onreadystatechange = () => { if (document.readyState === 'complete') { let templateDisabledLink = document.querySelector('a.template-disabled-link') if (templateDisabledLink) { templateDisabledLink.addEventListener('click', (event) => { let title = event.target.title if (title.length && event.target.hasAttribute('disabled')) { event.preventDefault() window.alert(title) event.stopPropagation() return false } }) } let templateHelps = document.querySelectorAll('a.template-help') for (let i = 0; i < templateHelps.length; i++) { let element = templateHelps[i] element.addEventListener('mouseenter', (event) => { let contentId = event.target.getAttribute('data-tooltip-content') if (contentId == null) return let target = event.target.getAttribute('data-tooltip-area') let obj = document.getElementById(target) if (obj) { obj.innerHTML = document.getElementById(contentId).innerHTML obj.style.display = 'inline' } }) element.addEventListener('mouseleave', (event) => { let contentId = event.target.getAttribute('data-tooltip-content') if (contentId == null) return let target = event.target.getAttribute('data-tooltip-area') let obj = document.getElementById(target) if (obj) { obj.style.display = 'none' } }) } let orphanedTemplateLink = document.getElementById('orphaned_template_link') if (orphanedTemplateLink) { orphanedTemplateLink.addEventListener('click', (event) => { const url = orphanedTemplateLink.getAttribute('data-url') window.fetch(url) .then((response) => { return response.text() }) .then((data) => { let orphanedTemplate = document.getElementById('orphaned_templates') if (orphanedTemplate) { orphanedTemplate.innerHTML = data } }) }) } let collapsibleHelps = document.querySelectorAll('a.template-help.collapsible') if (collapsibleHelps) { for (let i = 0; i < collapsibleHelps.length; i++) { let element = collapsibleHelps[i] element.addEventListener('click', (event) => { let targetName = event.target.getAttribute('data-template-help-target') let target = document.getElementById(targetName) if (target) { let style = target.style.display target.style.display = (style === 'none' ? 'inline' : 'none') } }) } } } } // ------- fot NoteTemplate function NOTE_TEMPLATE (config) { this.baseElementId = config.baseElementId this.baseTemplateListUrl = config.baseTemplateListUrl this.baseTrackerId = config.baseTrackerId this.baseProjectId = config.baseProjectId this.loadNoteTemplateUrl = config.loadNoteTemplateUrl } NOTE_TEMPLATE.prototype = { setNoteDescription: function (target, value, container) { let element = document.getElementById(target) if (element.value.length === 0) { element.value = value } else { element.value += '\n\n' + value } element.focus() container.style.display = 'none' try { if (CKEDITOR.instances.issue_notes) { CKEDITOR.instances.issue_notes.setData(value) CKEDITOR.instances.issue_notes.focus() } } catch (e) { // do nothing. } }, applyNoteTemplate: function (targetElement) { let ns = this let templateId = targetElement.dataset.noteTemplateId let projectId = document.getElementById('issue_project_id') let loadUrl = ns.loadNoteTemplateUrl let JSONdata = { note_template: { note_template_id: templateId } } if (targetElement.classList.contains('template-global')) { JSONdata.note_template.template_type = 'global' JSONdata.note_template.project_id = ns.baseProjectId if (projectId && projectId.value) { JSONdata.note_template.project_id = projectId.value } } let token = document.querySelector('#issue-form input[name="authenticity_token"]') let req = new window.XMLHttpRequest() req.onreadystatechange = function () { let container = targetElement.closest('div.overlay') let target = container.id.replace('template_', '') target = target.replace('_dialog', '') if (req.readyState === 4) { if (req.status === 200 || req.status === 304) { let value = JSON.parse(req.responseText) ns.setNoteDescription(target, value.note_template.description, container) } } } req.open('POST', loadUrl, true) if (token) { req.setRequestHeader('X-CSRF-Token', token.value) } req.setRequestHeader('Content-Type', 'application/json') req.send(JSON.stringify(JSONdata)) }, changeNoteTemplateList: function (elementId) { let ns = this let token = document.querySelectorAll('#issue-form input[name="authenticity_token"]') let projectId = document.getElementById('issue_project_id') let trackerId = document.getElementById('issue_tracker_id') let templateListUrl = ns.baseTemplateListUrl if (trackerId != null && projectId != null) { templateListUrl += '?tracker_id=' + trackerId.value templateListUrl += '&project_id=' + projectId.value } else { templateListUrl += '?tracker_id=' + ns.baseTrackerId + '&project_id=' + ns.baseProjectId } let req = new window.XMLHttpRequest() req.onreadystatechange = function () { if (req.readyState === 4) { if (req.status === 200 || req.status === 304) { let value = req.responseText // replace here! let dialog = document.getElementById(`${elementId}_dialog`) let target = document.querySelector(`#${elementId}_dialog .popup .filtered_templates_list`) target.innerHTML = value dialog.style = 'display: block;' } } } req.open('GET', templateListUrl, true) if (token) { req.setRequestHeader('X-CSRF-Token', token.value) } req.send() } } ================================================ FILE: assets/javascripts/template_fields.js ================================================ // This JS is used only when create / edit template. (Using Vue.js) 'use strict' const vm = new Vue({ el: '#json_generator', data: { items: [], customFields: {}, newItemTitle: '', newItemValue: '', api_builtin_fields: {}, api_custom_fields: {}, customFieldUrl: '' }, methods: { addField: function (newFieldName, newFieldValue) { if (newFieldName === '' || newFieldValue === '') { return } this.items.push({ title: newFieldName, value: newFieldValue }) this.newFieldName = '' this.newFieldValue = '' }, deleteField: function (target) { this.items = this.items.filter(function (item) { return item !== target }) }, loadField: function () { this.api_builtin_fields = base_builtin_fields this.api_custom_fields = base_custom_fields this.items = [] if (this.api_builtin_fields) { for (const [key, value] of Object.entries(this.api_builtin_fields)) { this.items.push({ title: key, value: value }) } } // { "issue_priority_id":"Priority", "issue_start_date":"Start date" } if (this.api_custom_fields) { for (const [key, value] of Object.entries(this.api_custom_fields)) { this.customFields[key] = value } } }, updateSelectableField: function () { let tmpFields = {} if (this.api_custom_fields) { for (const [key, value] of Object.entries(this.api_custom_fields)) { tmpFields[key] = value } } this.customFields = tmpFields }, fieldFormat: function () { const fields = this.customFields const title = this.newItemTitle if (fields[title] && fields[title].field_format) { const format = fields[title].field_format if (format === 'int' || format === 'date' || format === 'ratio' || format === 'list' || format === 'bool' || format === 'string') { return fields[title].field_format } } return 'text' }, possibleValues: function () { const fields = this.customFields const title = this.newItemTitle return fields[title].possible_values } }, mounted: function () { const trackerPulldown = document.getElementById(trackerPulldownId) if (trackerPulldown) { if (trackerPulldown.value === '') { this.$el.style.display = 'none' } trackerPulldown.addEventListener('change', event => { if (event.target.value === '') { this.$el.style.display = 'none' return } this.$el.style.display = 'block' const trackerId = event.target.value let url = baseUrl + '?tracker_id=' + trackerId + '&template_id=' + templateId if (typeof projectId !== 'undefined') { url += '&project_id=' + projectId } window.fetch(url) .then((response) => { return response.text() }) .then((data) => { let obj = JSON.parse(data) this.api_custom_fields = obj.custom_fields this.updateSelectableField() }) }) } this.loadField() }, computed: { // not yet }, watch: { newItemTitle: function (val) { if (typeof relativeUrlRoot === 'undefined') { this.customFieldUrl = '' return } let field = this.customFields[val] if (field == null || field.type != 'IssueCustomField') { this.customFieldUrl = '' return } this.customFieldUrl = relativeUrlRoot + '/custom_fields/' + field.id + '/edit' } } }) // Apply post data. const copyJson = document.getElementById('paste-json') if (copyJson) { copyJson.addEventListener('click', (event) => { const data = document.getElementById('builtin_fields_data_via_vue') if (data) { const text = data.innerText let jsonObj = JSON.parse(text) let convertObj = {} jsonObj.forEach(item => { let value = item.value if (item.title === 'issue_watcher_user_ids') { value = item.value.map(user => { let idx = user.lastIndexOf(':') return user.substring(idx + 1) }) } convertObj[item.title] = value }) document.getElementById(templateType + '_builtin_fields').value = JSON.stringify(convertObj) } }) } ================================================ FILE: assets/stylesheets/issue_templates.css ================================================ fieldset.issue { padding-top: 5px; padding-bottom: 5px; background-color: #F6F6F6; margin-top: 10px; margin-bottom: 10px; } div.issue_template { margin-left: 100pt; border: .1pt solid #E4E4E4; padding: 4pt; background-color: white; } .issue_template.help_content { color: #8b0000; padding: 6px; } #issue_template_builtin_fields, #issue_template_json_setting_field { width: 60%; } #template_status-area .flash_message { position: fixed; bottom: 0; right: 10px; margin-left: 10px; margin-top: 5pt; margin-bottom: 20pt; display: block; font-style: italic; padding: 8px 10px 8px 20px; color: #31708f; background: #eaf5fb url("../images/lamp.png") no-repeat 3px center; border: solid #a7e4fd 1px; } #field_information { margin-left: 240px; margin-right: 300px; color: grey; } option.inherited { background-color: #FCFD8D; font-style: oblique; } option.global { background-color: #FCFD8D; color: darkred; font-style: oblique; } .non_project_tracker { background: url("../images/lamp.png") no-repeat 3px center; color: #dd8888; font-style: italic; font-weight: lighter; padding-top: 3px; padding-left: 20px; } .template_tracker { background: url("../images/ticket.png") no-repeat 3px center; padding-top: 3px; padding-left: 18px; } .template_box { padding: 6px; margin-bottom: 10px; color: #505050; line-height: 1.5em; border: 1px solid #E4E4E4; } .icon-erase { background-image: url("../images/eraser.png"); } .icon-global_issue_templates { background-image: url("../images/issue_templates.png"); } .icon-template { background: url("../images/issue_templates.png") no-repeat 3px center; } .icon-hint { background: url("../images/ticket.png") no-repeat 3px center; } a.template_tooltip { background-image: url("../images/preview.png"); background-repeat: no-repeat; padding-left: 20px; } #issue_template-form textarea { overflow: auto; } .box-white { background-color: white; } /*--- Tooltip: Use to display template description -----*/ .template_tooltip_wrapper { color: #555; display: inline-block; } /* Hide tooltip body */ .template_tooltip_wrapper .template_tooltip_body { display: none; } /* Mouse over action */ .template_tooltip_wrapper:hover { position: relative; color: #333; } /* tooltip body */ .template_tooltip_wrapper:hover .template_tooltip_body { text-align: left; display: block; position: absolute; top: 12px; left: 0; font-size: 90%; background-color: #ffffff; min-width: 300px; padding: 8px 10px 12px; border: 1px solid #CCCCCC; z-index: 20000; word-break: break-all; word-wrap: break-word; white-space: normal; } .template_tooltip_wrapper:hover .template_tooltip_body .title { color: #979797; padding-bottom: 10px; font-weight: bold; display: inline-block; font-style: italic; } table.list.template_list { margin: 8px 0; border: 2px solid #e4e4e4; overflow: visible; } td.template_title { max-width: 200px; overflow: hidden; text-overflow: ellipsis; } .list.template_list th { text-align: left; } .list.template_list td { text-align: left; padding: 5px; } .overflow_dialog { overflow: visible; width: auto; } .filtered_templates_list { padding-top: 2px; } select.issue_template { width: 240px; } .issue_template.icon.settings { background: url("../images/lamp.png") no-repeat 3px center; } .issue_template.icon.plugins { background: url("../../../images/plugin.png") no-repeat 3px center; } .contextual.issue_templates { margin-bottom: 5px; } .popup p { margin-left: 0; padding-left: 0; } #revert_template { -webkit-transition: .3s ease-in-out; transition: .3s ease-in-out; } #revert_template:not(.disabled):hover { -webkit-animation: scale .4s ease-in-out; animation: scale .4s ease-in-out; } @-webkit-keyframes scale { 50% { -webkit-transform: scale(1.04); } } @keyframes scale { 50% { transform: scale(1.04); } } #revert_template.disabled { filter: alpha(opacity=20); -moz-opacity: .2; opacity: .2; } a.icon.icon-del[disabled=disabled] { opacity: .5; } #fields_setting_display_area { margin-left: 180px; } #template-help-message-area > p { padding-left: 4px; } /*------------ for responsive -----------------*/ @media (max-width: 899px) { #orphaned_templates>table>thead>tr>th.hideable { display: none; } #orphaned_templates>table>tbody>tr>td.hideable { display: none; } #content>div.template_box>table>thead>tr>th.hideable { display: none; } #content>div.template_box>table>tbody>tr>td.hideable { display: none; } #fields_setting_display_area { margin-left: 0; } #field_information { margin-left: 0; margin-right: 0; } } /*---- help tooltip --*/ a.icon-help.template-help { cursor: help; } a.icon-help .tooltip-area { display: none; position: absolute; width: 300px; margin-left: 8px; padding: 8px; border: 1px solid #cccccc; background-color: #FFFFFF; color: #000000; font-size: 12px; line-height: 1.6; } a.icon-help:hover .tooltip-area { display: inline; } /*--------- Note Template / Issue Template popup --------*/ .overlay { position: fixed; bottom: 0; left: 0; right: 0; background: rgba(87, 87, 87, 0.311); transition: opacity 500ms; visibility: hidden; opacity: 0; overflow: visible; width:100%; height:100%; z-index: -1; } .overlay:target, .overlay.active { visibility: visible; opacity: 1; z-index: 10; } .popup { margin: 100px auto; padding: 20px; background: #fff; border-radius: 5px; width: 60%; max-width: 860px; min-width: 520px; position: relative; transition: all 5s ease-in-out; } .popup h2 { margin-top: 0; margin-bottom: 0; color: #333; background-color: #c6d9ec; font-family: Tahoma, Arial, sans-serif; } .popup .close { position: absolute; width: 20px; height: 20px; top: 20px; right: 20px; opacity: 0.8; transition: all 200ms; font-size: 24px; font-weight: bold; text-decoration: none; color: #666; line-height: initial; } .popup .close:hover { color: #06D85F; opacity: 1; } .popup .content { max-height:50%; overflow: auto; } .popup.small { width: 500px; text-align: center; } #note_template_memo { width: 50%; } .light .popup { border-color: #aaa; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25); } .popup label { margin-left: 0; text-align: left; } .popup_header a.close { margin-right: 8px; } .popup_header h2 { font-size: 12pt; } .overlay .header_wrapper { width: auto; background-color: #c6d9ec; padding: 6px; margin-bottom: 2px; } .overlay .cancel { position: absolute; width: 100%; height: 100%; cursor: default; } .overlay .template_search_filter_wrapper { margin-top: 8px; margin-bottom: 8px; } /*--------- for Flash message --------*/ .fadeout { animation : fadeOut 5s; animation-fill-mode: both; } @keyframes fadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } /*--------- for Flash message --------*/ /*--------- for Template related Link --------*/ div.template_link_area { margin-right: 10px; border: rgb(202, 200, 197); border-width: 1px; border-style: solid; padding-left: 10px; padding-right: 40px; padding-top: 4px; padding-bottom: 4px; } div.template_link_area label { font-weight: bold; } .template-update-link { cursor: pointer; } /*--------- for Template related Link --------*/ .overwrite_confirm_btn { margin-top: 10px; display: inline-block; position: relative; width: 100px; padding: 0.8em; text-align: center; text-decoration: none; color: #1B1B1B; background: #fff; border:1px solid rgb(165, 164, 164); border-radius: 6px; } /*--------- for Overwrite Confirmation --------*/ .overwrite_confirm_btn:hover { background: rgb(190, 187, 187); color: #fff; cursor: pointer; text-decoration: none; } #template_area > p a { display: inline-block; margin-right: .85em; } #template_help_content { position: relative; text-align: left; margin-left: .5em; } #template_help_content p { padding-left: 5px; } #template-help-wiki > p { padding-left: 5px; } #filtered_templates_list > table div.wiki.template_tooltip_body p { padding-left: 0; } ul.json-list { width: 70%; padding: 10px; background-color: white; } ul.json-list li { list-style: none; line-height: 1.6em; padding: 0.5em 0; } form#global_issue_template-form select[multiple=multiple] { min-width: 120px; } form#issue_template-form select[multiple=multiple] { min-width: 120px; } ================================================ FILE: config/locales/bg.yml ================================================ # Bulgarian translation bg: issue_templates: Шаблони issue_template: Шаблони за задачи issue_template_note: Описание на шаблона, което не се показва при създаване на задачите label_enabled: "Активиране" label_disabled: "Забранено" label_help_message: "Кратко описание на работата с шаблоните в този проект" label_show_help_message: "Показване на помощ при създаване на задачи" about_help_message: "Всеки проект може да има свое помощно съобщение за шаблоните." close_help: "Затваряне" about_template_help_message: "Можете да видите инструкция за шаблоните на този проект" label_enabled_help_message: "Активиране на шаблона. Ако този шаблон е все още недовършен, изключете това поле." label_list_templates: "Списък" label_new_templates: "Нов шаблон" issue_template_name: "Наименование" issue_description: "Описание на задачата" issue_title: "Заглавие на задачата (поле \"Относно:\")" label_applied_for_issue: "Полета, приложени към задачата: " help_for_issue_title: "Ако дефинирате заглавие, то ще бъде приложено към полето \"Относно:\" при избиране на шаблона." help_for_this_field: "Помощ за това поле" permission_manage_issue_templates: "Управление на шаблони" permission_edit_issue_templates: "Редактиране на шаблони" permission_show_issue_templates: "Използване на шаблони" project_module_issue_templates: "Шаблони за задачи" label_isdefault_help_message: "Подразбиращ се шаблон за този тракер." defaulf_template_loaded: "Подразбиращият се шаблон е зареден в описанието на задачата. (Тракер: %{tracker})" text_no_tracker_enabled: "Не са избрани тракери за този проект.\nПърво изберете тракери, понеже шаблоните се свързват с проектните тракери. " label_enabled_sharing: "Разрешено споделянето с дървото на проектите." label_inherit_templates: "Наследяване на шаблоните" label_inherit_templates_help_message: "Наследяване на шаблоните от родителския проект. (Показват се само шаблоните, които са споделени с дървото на проектите.)" label_inherited_templates: "Наследени шаблони" no_issue_templates_for_this_project: "За този проект не са дефинирани шаблони." link_to_index_edit_template: "Списък на шаблоните / редактиране на шаблони" erase_issue_subject_and_description: "Изчистване на заглавието и описанието." unused_tracker_at_this_project: "Забележка: Този тракер не е дефиниран за използване в този проект. Моля проверете и редактрирайте конфигурацията на шаблона, ако е необходимо." label_enabledshaing_help_message: "Ако е избрано, този шаблон може да се използва във вложените (child) проекти. (Също така, вие трябва да активирате опцията за наследяване във вложените проекти.)" label_should_replaced: "Замяна на заглавието и описанието" label_should_replaced_help_message: "Ако е избрано, съществуващото заглавие и описание се премахват и заменят с текстовете от шаблона. (По подразбиране не е избрано, и текстовете се добавят.)" global_issue_templates: "Глобални шаблони за задачи" no_issue_templates_for_this_redmine: "Глобални шаблони не са дефинирани." only_admin_can_associate_global_template: "Само администраторите на Redmine могат да асоциират глобални шаблони с с тожи проект." text_no_tracker_enabled_for_global: "Не са дефинирани тракери.\nПърво дефинирайте тракери, понеже шаблоните се асоциират с тракери." display_and_filter_issue_templates_in_dialog: "Преглед" label_filter_template: "Филтриране на шаблоните" label_msg_confirm_to_replace: "Желаете да замените заглавието и описанието?" label_apply_global_template_to_all_projects: "Прилагане на глобалните шаблони към всички проекти." note_apply_global_template_to_all_projects_setting_enabled: "Този глобален шаблон е приложен към всички проекти в конфигурацията на плъгина." project_list_associated_this_template: "Проекти: (приложени: %{applied} от %{all})" note_project_local_template_override_global_template: "Ако съществува локален шаблон в проекта, тпой ще има приоритет пред глобални шаблони." help_project_local_template_override_global_template: "Ако тази опция е активна, всеки глобален шаблон ще бъде приложен към всички проекти. Ако в даден проект има шаблон за същия тракер, то той има приоритет пред глобалния шаблон. Глобалният шаблон не се показва в списъка с шаблони при създаване на задача." warning_project_local_template_override_global_template: "Моля бъдете внимателни, когато създавате глобални шаблони, които съдържат конфиденциална информация. Ако тази опция е активирана, всички глобални шаблони ще бъдат приложени към всички проекти." revert_before_applying_template: "Отмяна" template_remove_confirm: "Да бъде ли премахнат шаблонът? %{count} подпроекта използват този шаблон." label_number_of_subprojects_use_this_template: "%{count} подпроекта използват този шаблон." enabled_template_cannot_destroy: "Само забранени шаблони могат да бъдат премахвани. Проверете дали други подпроекти използват този шаблон или не и го забранете преди да го изтриете." orphaned_templates: "Шаблони без тракер" orphaned_template: "Шаблон без тракер" label_template_applied: "Шаблонът беше приложен. Можете да възстановите старото състояние, като натиснете връзката 'Отмяна'." label_hide_confirm_dialog_in_the_future: "Без потвърждение за в бъдеще, направо прилагане на шаблона." label_template_for_note: "Шаблон за коментар" label_use_template_when_edit: "Използване на шаблони когато се редактират задачи" note_template: "Шаблон за коментар" no_note_templates_for_this_project: "Шаблони за коментари не са дефинирани за този проект." label_memo_help_message: "Моля запишете тук обяснителна бележка, която се появява при прилагане на този шаблон." note_template_name: "Име на шаблона за коментар" note_description: "Коментар" field_template_visibility: Видимост на шаблоните note_templates: visibility: mine: само за мен roles: само за тези роли open: за всички потребители please_select_at_least_one_role: "Моля изберете поне една роля." issue_templates_settings: Конфигурация за шаблони за задачи issue_templates_optional_settings: Допълнителна конфигурация, по избор issue_template_related_link: Връзка issue_template_link_title: Заглавие на връзката label_related_link_help_message: "Ако имате примерни страници или задачи, които използват шаблона, моля запишете тук връзка към тях. Така потребителят може да ги види за примери." label_link_title_help_message: "Можете да зададете заглавите на връзката (По подразбиране : Връзка)" enter_value: Моля въведете стойност. label_select_field: Изберете полета label_builtin_fields_help_message: Въведете потребителски полета и стойности за тях. След това натиснете бутон "Потвърждение" за генериране на данни в JSON формат. label_builtin_fields_json: JSON за полета label_enable_builtin_fields: Разрешаване на поддръжка за потребителски полета help_enable_builtin_fields: Ако тази поддръжка е позволена, ще се покаже форма, в която можете да установите шаблони за потребителски полета. Моля записвайте данните в JSON формат. warning_enable_builtin_fields: Моля бъдете внимателни, ако активирате тази поддръжка, защото тя все още е в експериментален стадий. Все още не е предвиден достатъчно UI и функции за поддръжка на всички типове полета. Възможна е несигурна работа в зависимост от браузъра. Бъдете внимателни. label_field_information: Информация за полето unavailable_fields_for_this_tracker: Недостъпно поле за този тракер global_note_templates: Глобален шаблон за коментар link_to_index_edit_note_template: Шаблони за коментари / редактиране на шаблони ================================================ FILE: config/locales/da.yml ================================================ # Danish strings go here for Rails i18n da: issue_templates: Sagsskabeloner issue_template: Sagsskabelon issue_template_note: Note label_enabled: Aktiveret label_disabled: Deaktiveret label_help_message: Skabelon-hjælpetekst label_show_help_message: Vis hjælpetekst ved oprettelse eller redigering af sag. about_help_message: Hvert projekt kan have sin egen hjælpetekst. close_help: Luk hjælpetekst. about_template_help_message: Du kan se instruktioner om sagsskabeloner på dette projekt. label_enabled_help_message: Tjekboks til aktivering af denne skabelon. Fjern afkrydsning for at gemme som kladde. label_list_templates: "Skabeloner" label_new_templates: "Tilføj skabelon" issue_template_name: "Skabelonnavn" issue_description: "Sagsbeskrivelse" issue_title: "Sagsemne" label_applied_for_issue: "Felter: " help_for_issue_title: "Hvis sat, vil sagens emnefelt blive udfyldt når skabelonen vælges." help_for_this_field: "Forklaring til dette felt." permission_manage_issue_templates: "Administrér skabeloner" permission_edit_issue_templates: "Ret skabeloner" permission_show_issue_templates: "Vis skabeloner" project_module_issue_templates: "Sagsskabeloner" label_isdefault_help_message: "Anvend som den forudvalgte skabelon på sagstypen." defaulf_template_loaded: "Indlæste forudvalgt skabelon. (Tracker: %{tracker})" text_no_tracker_enabled: "Der er ikke defineret sagstyper for dette projekt.\nSæt disse inden der defineres sagsskabeloner.. " label_enabled_sharing: "Del med projekttræet." label_inherit_templates: "Arv sagsskabeloner" label_inherit_templates_help_message: "Arv sagsskabeloner fra hovedprojekt. Hovedprojektets sagsskabeloner skal være delt med projekttræet." label_inherited_templates: "Nedarvede sagsskabeloner" no_issue_templates_for_this_project: "Ingen sagsskabeloner defineret for dette projekt." link_to_index_edit_template: "Sagsskabeloner / ret skabeloner" erase_issue_subject_and_description: "Tøm emne og beskrivelse." unused_tracker_at_this_project: "NB: Sagstypen anvendes ikke med projektet." label_enabledshaing_help_message: "Hvis valgt deles skabelonen med underliggende projekter. (Arv sagsskabeloner skal dog være valgt til på underprojekter.)" label_should_replaced: "Erstat emne og beskrivelse" label_should_replaced_help_message: "Hvis valgt erstattes emne og beskrivelse med skabelonen. Ellers tilføjes skabelonens indhold den eksisterende tekst." global_issue_templates: "Fælles sagsskabeloner" no_issue_templates_for_this_redmine: "Ingen fælles sagsskabeloner defineret." only_admin_can_associate_global_template: "Kun administratorer kan sammenkoble fælles sagsskabeloner med projekter." text_no_tracker_enabled_for_global: "Der er ikke defineret nogen sagstyper.\nSæt disse inden der defineres sagsskabeloner." display_and_filter_issue_templates_in_dialog: "Filtrér Skabeloner" label_filter_template: "Filtrér Skabeloner" ================================================ FILE: config/locales/de.yml ================================================ # German strings go here for Rails i18n de: issue_templates: Vorlagen issue_template: Ticketvorlage issue_template_note: Notiz label_enabled: aktivieren label_disabled: deaktivieren label_help_message: Hilfe label_show_help_message: Hilfe-Dialog im Vorlagenformular anzeigen about_help_message: Jedes Projekt kann die Onlinehilfe für ihre Vorlagen definieren. close_help: Hilfedialog schließen about_template_help_message: Dies ist die Onlinehilfe für Ticketvorlagen in diesem Projekt. label_enabled_help_message: Diese Vorlage aktivieren. Deaktvieren Sie diese Checkbox, um die Vorlage als Entwurf abzulegen. label_list_templates: Vorlagenliste label_new_templates: Vorlage hinzufügen issue_template_name: Vorlagenname issue_description: Beschreibung issue_title: Thema label_applied_for_issue: "Vorausgefüllte Felder:" help_for_issue_title: "Der hier angegebene Text wird als Thema im neuen Ticket übernommen, falls die Vorlage ausgewählt wird." help_for_this_field: Hilfe permission_manage_issue_templates: Vorlagen verwalten permission_edit_issue_templates: Vorlagen ändern permission_show_issue_templates: Vorlagen anzeigen project_module_issue_templates: Ticketvorlagen label_isdefault_help_message: Aktivieren Sie diese Option um die Vorlage als Standard für den Standardtracker zu verwenden. defaulf_template_loaded: "Die Standardvorlage für den Tracker '%{tracker}' wurde in das Beschreibungsfeld übernommen." text_no_tracker_enabled: Bisher wurde keine Konfiguration der Tracker und Ticketvorlagen für dieses Projekt vorgenommen.\nBitte konfigurieren Sie diese notwendige Einstellung, um die Funktion zu nutzen. label_enabled_sharing: Vererbung mit Projektbaum erlauben. label_inherit_templates: Vorlagen vererben label_inherit_templates_help_message: Vorlage von Elternprojekt erben (nur falls Vererbung erlaubt) label_inherited_templates: Geerbte Vorlagen no_issue_templates_for_this_project: Für dieses Projekt sind keine Vorlagen definiert. link_to_index_edit_template: Vorlagenliste / Vorlagen bearbeiten erase_issue_subject_and_description: Lösche Thema und Beschreibung unused_tracker_at_this_project: "ACHTUNG: Dieser Tracker ist für dieses Projekt nicht aktiviert. Bitte Vorlage anpassen." label_enabledshaing_help_message: Falls aktiv kann diese Vorlage an Unterprojekte vererbt werden. label_should_replaced: Ersetze Thema und Beschreibung label_should_replaced_help_message: Falls aktiv werden Thema und Beschreibung gelöscht und durch den Vorlagentext ersetzt. Ansonsten wird der Vorlagentext am Ende angehangen. global_issue_templates: Globale Ticketvorlagen no_issue_templates_for_this_redmine: Es sind keine globalen Ticketvorlagen definiert. only_admin_can_associate_global_template: Nur Administratoren können globale Vorlagen diesem Projekt zuweisen. text_no_tracker_enabled_for_global: Es sind keine Tracker aktiviert.\nVor der Definition von Vorlagen muss mindestens ein Tracker im Projekt freigegeben sein. display_and_filter_issue_templates_in_dialog: Vorlagen ansehen und filtern label_filter_template: Vorlagen filtern label_msg_confirm_to_replace: "Soll das aktuelle Thema und die Beschreibung durch die Vorlage ersetzt werden?" label_apply_global_template_to_all_projects: "Wende Globale Ticketvorlagen auf alle Projekte an." note_apply_global_template_to_all_projects_setting_enabled: "Diese Globale Ticketvorlage wird auf alle Projekte angewendet weil das Vorlagen-Plugin entsprechend konfiguriert ist." project_list_associated_this_template: "Projekte: (angewendet auf: %{applied} von %{all})" note_project_local_template_override_global_template: "Wenn eine lokale Projektvorlage vorhanden ist, überschreiben Sie globale Vorlagen, und globale Vorlagen werden auf dem Bildschirm zum Erstellen von Problemen nicht angezeigt." help_project_local_template_override_global_template: "Wenn diese Option aktiviert ist, werden alle globalen Ticketvorlagen auf alle Projekte angewendet. Wenn eine projektspezifische Vorlage pro Tracker festgelegt ist, wird diese überschrieben und die globale Vorlage wird auf dem Bildschirm zur Erstellung neuer Tickets ausgeblendet." warning_project_local_template_override_global_template: "Bitte seien Sie vorsichtig globale Problemvorlagen mit vertraulichen Inhalten zu hinterlegen. Wenn diese Option aktiviert ist, werden alle globalen Vorlagen auf alle Projekte angewendet." revert_before_applying_template: "Rückgängig" template_remove_confirm: "Möchten Sie dieses Template wirklich löschen? %{count} Unterprojekte nutzen diese Vorlage." label_number_of_subprojects_use_this_template: "%{count} Unterprojekte nutzen diese Vorlage." enabled_template_cannot_destroy: "Nur deaktivierte Vorlagen können gelöscht werden! Prüfe ob andere (Unter-)Projekte diese Vorlage verwenden und deaktiviere sie dort." orphaned_templates: "Verwaiste Vorlage vom Tracker" orphaned_template: "Verwaiste Vorlage vom Tracker" label_template_for_note: "Notizvorlage" label_use_template_when_edit: "Verwenden Sie Vorlagen, wenn Sie das Problem bearbeiten" note_template: "Notizvorlage" note_template_name: Name für Notizvorlage label_template_applied: Die Ausgabevorlage wird angewendet. Sie können mit dem Link "Zurücksetzen" rückgängig gemacht werden. label_hide_confirm_dialog_in_the_future: "Diese Bestätigung in Zukunft ausblenden, einfach überschreiben." no_note_templates_for_this_project: "Für dieses Projekt sind keine Kommentarvorlagen definiert." label_memo_help_message: "Bitte richten Sie einen Hinweis ein, der erklärt, wann Sie diese Vorlage anwenden." note_description: "Comment body" field_template_visibility: Sichtbarkeit von Vorlagen note_templates: visibility: mine: nur für mich roles: nur für diese Rolle open: für alle please_select_at_least_one_role: "Bitte zunächst eine Rolle auswählen" issue_templates_settings: Template Einstellungen issue_templates_optional_settings: Optionale Template Einstellungen issue_template_related_link: Verwandter Link issue_template_link_title: Zugehöriger Link-Titel label_related_link_help_message: "Bitte bei Beispielseiten oder Beispieltickets die Vorlagen nutzen den Link angeben. So können Benutzer diese Beispiele ansehen." label_link_title_help_message: "Sie können den Titel des zugehörigen Links anpassen. (Standard: Verwandter Link)" enter_value: Bitte einen Wert eingeben. label_select_field: Wählen Sie ein Feld aus label_builtin_fields_help_message: Geben Sie eigene Felder oder benutzerdefinierte Felder als Standardwerte im JSON-Format ein. Bitte Felder auswählen und Werte einstellen. Klicken Sie dann auf "Übernehmen" und Sie können Daten im JSON-Format generieren. label_builtin_fields_json: JSON für Felder label_enable_builtin_fields: Aktivieren Sie die integrierte Feldunterstützung help_enable_builtin_fields: Wenn dieses Flag aktiviert ist, wird ein Formular angezeigt in dem Sie Vorlagen für integrierte oder benutzerdefinierte Felder festlegen können. Bitte registrieren Sie die Einstellungen im JSON-Format. warning_enable_builtin_fields: Bitte mit Vorsicht dieses Flag zu aktivieren. Diese Funktion ist nur experimentell. Da ich noch nicht genügend UI und Funktionen zur Verfügung gestellt habe, um alle Feldtypen zu unterstützen. Je nach Browser funktioniert dies möglicherweise nicht. Nutzen Sie diese Funktion in einer Produktionsumgebung mit Vorsicht. label_field_information: Feld Information unavailable_fields_for_this_tracker: Feld für diesen Tracker nicht verfügbar ================================================ FILE: config/locales/en.yml ================================================ # English strings go here for Rails i18n en: issue_templates: Issue templates issue_template: Issue template issue_template_note: Note label_enabled: Enable label_disabled: Disable label_help_message: About templates label_show_help_message: Show help message when creating/updating issues. about_help_message: Each project can have custom help messages for templates. close_help: Close help message. about_template_help_message: You can see the instruction of issue templates on this project. label_enabled_help_message: Checkbox to activate this template. If you would like to save this template as a draft, un-check this box. label_list_templates: "Template list" label_new_templates: "Add template" issue_template_name: "Template name" issue_description: "Issue body" issue_title: "Issue title" label_applied_for_issue: "Fields applied for issue: " help_for_issue_title: "If the Issue title is defined, it will be applied to the Subject of an issue when selecting this template." help_for_this_field: "Help for this field." permission_manage_issue_templates: "Manage Templates" permission_edit_issue_templates: "Edit Templates" permission_show_issue_templates: "Show Templates" project_module_issue_templates: "Issue Templates" label_isdefault_help_message: "Check and you can use this as a default template for the target tracker." defaulf_template_loaded: "Loaded default template into description textarea. (Tracker: %{tracker})" text_no_tracker_enabled: "Any trackers for this project have not been configured yet.\nPlease set them first because templates are assigned to project trackers. " label_enabled_sharing: "Enabled sharing with project tree." label_inherit_templates: "Inherit templates" label_inherit_templates_help_message: "Inherit templates from parent project. (Only parent's templates are listed which marked as enabled sharing with project tree.)" label_inherited_templates: "Inherited templates" no_issue_templates_for_this_project: "No issue templates are defined for this project." link_to_index_edit_template: "Issue templates list / edit templates" erase_issue_subject_and_description: "Clear subject and description text." unused_tracker_at_this_project: "NOTE: This tracker is not defined to use for this project. Please re-define template setting if necessary." label_enabledshaing_help_message: "If true, this template can be shared with descendant projects. (You also have to activate Inherited template option at child project.)" label_should_replaced: "Replace subject and description" label_should_replaced_help_message: "If true, existing subject and description are cleared and replaced with template text.(Default is false, and appended text.)" global_issue_templates: "Global Issue Templates" no_issue_templates_for_this_redmine: "No global issue templates are defined for this Redmine site." only_admin_can_associate_global_template: "Only Redmine administrator can associate global templates with this project." text_no_tracker_enabled_for_global: "Trackers are not defined yet.\nPlease set them first because templates are assigned to trackers." display_and_filter_issue_templates_in_dialog: "Preview Template Contents" label_filter_template: "Filter templates" label_msg_confirm_to_replace: "Do you replace subject and description?" label_apply_global_template_to_all_projects: "Apply Global issue templates to all the projects." note_apply_global_template_to_all_projects_setting_enabled: "This global issue template is applied to all the project by plugin setting." project_list_associated_this_template: "Project List: (applied: %{applied} of %{all})" note_project_local_template_override_global_template: "If project local template exists, override global templates and any global templates are not shown on create issue screen." help_project_local_template_override_global_template: "If this option is activated, every global ticket templates will be applied to all projects. If a project-specific template per tracker is set, it takes override and the global template is hidden on the new ticket creation screen." warning_project_local_template_override_global_template: "Please be careful when registering global issue template, that contain confidential content you want to pay attention to. In case this option is activated, all the global templates will be applied to all projects." revert_before_applying_template: "Revert" template_remove_confirm: "Are you sure to remove this template? %{count} subprojects use this template." label_number_of_subprojects_use_this_template: "%{count} subprojects use this template." enabled_template_cannot_destroy: "Only disabled template can be destroyed. Check if other subprojects use this template or not, and disable this before deleting." orphaned_templates: "Orphaned templates from tracker" orphaned_template: "Orphaned template from tracker" label_template_applied: "Issue template is applied. You can revert with click 'Revert' link." label_hide_confirm_dialog_in_the_future: "Hide this confirmation in the future, just overwrite." label_template_for_note: "Template for note" label_use_template_when_edit: "Use templates when edit issue" note_template: "Template for note" no_note_templates_for_this_project: "No comment templates are defined for this project." label_memo_help_message: "Please set up a note explaining when applying this template." note_template_name: "Template name for note" note_description: "Comment body" field_template_visibility: Templates visibility note_templates: visibility: mine: to me only roles: to these roles only open: to any users please_select_at_least_one_role: "Please select at least one role." issue_templates_settings: Issue Templates Setting issue_templates_optional_settings: Templates Optional Settings issue_template_related_link: Related Link issue_template_link_title: Related Link Title label_related_link_help_message: "If there are some example pages or sample issues which using issue template, please specify the link. So operator can see them as an usage or example for template." label_link_title_help_message: "You can customize the title of the related link. (Default: Related Link)" enter_value: Please enter a value. label_select_field: Select a field label_builtin_fields_help_message: Enter builtin fields or custom fields default values with JSON format. Please select fields and set values. Then click "Apply" and you can generate JSON format data. label_builtin_fields_json: JSON for fields label_enable_builtin_fields: Enable Builtin Field support help_enable_builtin_fields: If this flag is enabled, a form will be displayed that allows you to set templates for built-in or custom fields. Please register the settings in JSON format. warning_enable_builtin_fields: Please be careful to activate this flag. Now this feature is an experimental one. Since I have not provided enough UI and functions yet to support all the field types. It may not work depending on the browser. Use caution when applying in a production environment. label_field_information: Field information unavailable_fields_for_this_tracker: Unavailable field for this tarcker global_note_templates: Global Template for note link_to_index_edit_note_template: Note templates list / edit templates ================================================ FILE: config/locales/es.yml ================================================ # Spanish strings go here for Rails i18n # Translation by Andres Arias https://github.com/mrlocke es: issue_templates: "Plantillas de peticiones" issue_template: "Plantilla de peticiones" issue_template_note: "Nota" label_enabled: "Habilitado" label_disabled: "Deshabilitado" label_help_message: "Acerca de plantillas" label_show_help_message: "Mostrar el mensaje de ayuda cunado se crean/modifican las peticiones." about_help_message: "Cada proyecto puede personalizar el mensaje de ayuda para las plantillas." close_help: "Cerrar mensaje de ayuda." about_template_help_message: "Puede ver las instrucciones para las plantillas de peticiones en este proyecto." label_enabled_help_message: "Marque para activar (habilitar) esta plantilla. Si desea guardarla como borrador, desmarque esta casilla." label_list_templates: "Lista plantillas" label_new_templates: "Añadir plantilla" issue_template_name: "Nombre de la plantilla" issue_description: "Descripción" issue_title: "Asunto" label_applied_for_issue: "Campos que rellenará la plantilla: " help_for_issue_title: "Si el Asunto ya existe, será reemplazado por el de la plantilla." help_for_this_field: "Ayuda para este campo." permission_manage_issue_templates: "Administrar Plantillas" permission_edit_issue_templates: "Editar Plantillas" permission_show_issue_templates: "Ver Plantillas" project_module_issue_templates: "plantillas de Peticiones" label_isdefault_help_message: "Marque la casilla para seleccionar como plantilla por defecto para este tipo de peticiones." defaulf_template_loaded: "Cargada la plantilla por defecto. (Tipo de Petición: %{tracker})" 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." label_enabled_sharing: "Compartir con el árbol de proyectos." label_inherit_templates: "Heredar plantillas" label_inherit_templates_help_message: "Heredar plantillas desde el proyecto padre. (Sólo se comparten las plantillas marcadas como habilitadas.)" label_inherited_templates: "Plantillas heredadas" no_issue_templates_for_this_project: "No se han definido plantillas de peticiones para este proyecto." link_to_index_edit_template: "Ver/Editar plantillas de peticiones" erase_issue_subject_and_description: "Borrar el Asunto y la Descripción." 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." 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.)" label_should_replaced: "Reemplazar Asunto y Descripción" 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.)" global_issue_templates: "Plantillas de Peticiones Globales" no_issue_templates_for_this_redmine: "No hay plantillas globales definidas en este redmine." only_admin_can_associate_global_template: "Solamente el Administrador de Redmine puede asociar plantillas globales con esté proyecto." 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." ================================================ FILE: config/locales/fr.yml ================================================ # Les chaines en français sont définies ici pour Rails i18n fr: issue_templates: "Gabarits" issue_template: "Gabarit des demandes" issue_template_note: "Note" label_enabled: "Activer" label_disabled: "Désactiver" label_help_message: "À propos des gabarits" label_show_help_message: "Afficher le message d'aide lors de la création ou mise à jour d'une demande." about_help_message: "Le message d'aide peut-être personnalisé pour chaque projet." close_help: "Fermer le messsage d'aide" about_template_help_message: "Aide au sujet des gabarits de ce projet." label_enabled_help_message: "Case à cocher pour activer ce gabarit. Si vous prévoyez sauvegarder ce gabarit en brouillon, décochez cette case." label_list_templates: "Liste des gabarits" label_new_templates: "Ajouter un gabarit" issue_template_name: "Nom du gabarit" issue_description: "Description de la demande" issue_title: "Titre de la demande" label_applied_for_issue: "Champs spécifiques de la demande: " help_for_issue_title: "Si le titre est défini, il sera appliqué aux demandes en choisissant un gabarit." help_for_this_field: "Aide pour ce champ." permission_manage_issue_templates: "Gérer les gabarits" permission_edit_issue_templates: "Modifier le gabarit" permission_show_issue_templates: "Afficher les gabarits" project_module_issue_templates: "Gabarit de demande" label_isdefault_help_message: "Cochez cette case pour établir ce gabarit par défaut pour ce type de demande." defaulf_template_loaded: " Le gabarit par défaut est chargé dans la zone de description (Type de demande: %{tracker})" 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." label_enabled_sharing: "Activer le partage avec l'arborescence du projet." label_inherit_templates: "Hériter gabarit" 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.)" label_inherited_templates: "Gabarit hérité" no_issue_templates_for_this_project: "Aucun gabarit de demande n'est défini pour ce projet." link_to_index_edit_template: "Liste de gabarits de demandes / éditer gabarit" erase_issue_subject_and_description: "Enlever le sujet et le texte descriptif" 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." 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.)" label_should_replaced: "Remplacer le sujet et la description" 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.)" global_issue_templates: "Modèle de gabarit de demande" no_issue_templates_for_this_redmine: "Aucun modèle de gabarit de demande n'est défini pour ce redmine." only_admin_can_associate_global_template: "Seul l'administrateur peut associer un modèle de gabarit avec ce projet." 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." ================================================ FILE: config/locales/it.yml ================================================ # Italian strings go here for Rails i18n it: issue_templates: Modelli segnalazioni issue_template: Modello segnalazione issue_template_note: Note label_enabled: Attivato label_disabled: Disattivato label_help_message: Info sui modelli label_show_help_message: Mostra messaggio d'aiuto durante la creazione o modifica delle segnalazioni. about_help_message: Ogni progetto può avere messaggi d'aiuto personalizzati per i modelli. close_help: Chiudi messaggio d'aiuto. about_template_help_message: Puoi vedere le istruzioni per i modelli di segnalazione di questo progetto. label_enabled_help_message: Abilita questo modello. Se vuoi salvarlo come bozza, disattiva questa opzione. label_list_templates: "Elenco modelli" label_new_templates: "Nuovo modello" issue_template_name: "Nome modello" issue_description: "Testo della segnalazione" issue_title: "Titolo segnalazione" label_applied_for_issue: "Campi applicati per la segnalazione: " help_for_issue_title: "Se il titolo della Segnalazione è definito, sarà applicato all'Oggetto della segnalazione quando si applica questo modello." help_for_this_field: "Aiuto per questo campo." permission_manage_issue_templates: "Gestisci Modelli" permission_edit_issue_templates: "Modifica Modelli" permission_show_issue_templates: "Mostra Modelli" project_module_issue_templates: "Modelli Segnalazioni" label_isdefault_help_message: "Usa questo come modello predefinito per le segnalazioni del tracker." defaulf_template_loaded: "Caricato modello predefinito nella descrizione. (Tracker: %{tracker})" text_no_tracker_enabled: "Nessun tracker è stato configurato per questo progetto.\nPer favore configurali prima perché i modelli segnalazione sono assegnati ai tracker. " label_enabled_sharing: "Abilita condivisione con gerarchia progetto." label_inherit_templates: "Eredita modelli segnalazioni" label_inherit_templates_help_message: "Eredita modelli da progetto padre. (Only parent's templates are listed which marked as enabled sharing with project tree.)" label_inherited_templates: "Modelli ereditati" no_issue_templates_for_this_project: "Nessun modello di segnalazione è definito per questo progetto." link_to_index_edit_template: "Elenco modelli segnalazione / modifica modelli" erase_issue_subject_and_description: "Pulisci oggetto e descrizione." unused_tracker_at_this_project: "NOTA: Questo tracker non è abilitato per questo progetto. Per favore modifica le impostazioni del modello se necessario." label_enabledshaing_help_message: "Se abilitato, questo modello può essere condiviso con i sottoprogetti. (Devi anche abilitare nel sottoprogetto l'opzione Eredita modelli segnalazioni.)" label_should_replaced: "Sostituisci oggetto e descrizione" 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.)" global_issue_templates: "Modelli Segnalazioni Globali" no_issue_templates_for_this_redmine: "Nessun modello globale di segnalazione è definito per questo sito Redmine." only_admin_can_associate_global_template: "Solo un amministratore di Redmine può associare modelli globali a questo progetto." text_no_tracker_enabled_for_global: "Nessun tracker definito\nÈ necessario configurarli prima perché i Modelli segnalazione sono assegnati ai tracker." display_and_filter_issue_templates_in_dialog: "Filtra modelli" label_filter_template: "Filtra modelli" ================================================ FILE: config/locales/ja.yml ================================================ # Japanese translation ja: issue_templates: チケットテンプレート issue_template: チケットテンプレート issue_template_note: メモ label_enabled: "有効" label_disabled: "無効" label_help_message: "テンプレートについて" label_show_help_message: "ヘルプメッセージを有効にする" about_help_message: "プロジェクト毎にテンプレートに関するヘルプを設定できます。" close_help: "ヘルプメッセージを閉じます。" about_template_help_message: "プロジェクト毎のテンプレートに関するヘルプを表示します。" label_enabled_help_message: "有効にチェックを入れると、選択できるようになります。ドラフト状態の場合は、チェックを外して下さい。" label_list_templates: "一覧" label_new_templates: "新規作成" issue_template_name: "テンプレート名" issue_description: "チケット本文" issue_title: "チケットタイトル" label_applied_for_issue: "チケットへの適用項目: " help_for_issue_title: "チケットタイトルを設定すると、テンプレート選択時に、タイトルもテンプレート化されたものをセットできます。" help_for_this_field: "この項目のヘルプ" permission_manage_issue_templates: "テンプレートの管理" permission_edit_issue_templates: "テンプレートの編集" permission_show_issue_templates: "テンプレートの表示" project_module_issue_templates: "チケットテンプレート" label_isdefault_help_message: "デフォルト値にチェックすると、チケットの新規登録時に、該当するトラッカーのテンプレートとして読み込まれます。" defaulf_template_loaded: "デフォルトテンプレートが「詳細」テキストエリアにロードされました。(トラッカー: %{tracker})" text_no_tracker_enabled: "テンプレートはトラッカー毎に設定します。\nプロジェクトが利用するトラッカーの設定が必要です。" label_enabled_sharing: "プロジェクトツリーでの共有を許可" label_inherit_templates: "親プロジェクトのテンプレートを継承" label_inherit_templates_help_message: "親プロジェクトのテンプレートを継承します。ただし、親プロジェクトのテンプレートで、プロジェクトツリーでの利用を許可しているものに限られます。" label_inherited_templates: "継承されたテンプレート" no_issue_templates_for_this_project: "このプロジェクト用のテンプレートは未だ登録されていません。" link_to_index_edit_template: "テンプレート一覧 / 編集" erase_issue_subject_and_description: "タイトルと詳細をクリア" unused_tracker_at_this_project: "このプロジェクトでは利用されていないトラッカーです。必要に応じて、トラッカーとテンプレートの関連付けを修正して下さい。" label_enabledshaing_help_message: "オプションをチェックすると、子プロジェクトでの利用を許可します。(子プロジェクト側での『テンプレートの継承』を設定した場合)" label_should_replaced: "テンプレート読み込みの際に本文とタイトルを上書き" label_should_replaced_help_message: "オプションをチェックすると、テンプレートを適用する際に、入力済みの本文とタイトルを上書きします。(デフォルトはOFFです)" global_issue_templates: "グローバル チケットテンプレート" no_issue_templates_for_this_redmine: "このRedmineにはグローバルチケットテンプレートは定義されていません。" only_admin_can_associate_global_template: "このプロジェクトでグローバルチケットテンプレートを利用するには、Redmine管理者権限が必要です。" text_no_tracker_enabled_for_global: "テンプレートはトラッカー毎に設定します。\nトラッカーの設定をお願いします。" display_and_filter_issue_templates_in_dialog: "テンプレートの内容を確認" label_filter_template: "テンプレートの絞り込み" label_msg_confirm_to_replace: "説明と題名を上書きしてもよろしいですか?" label_apply_global_template_to_all_projects: "グローバルチケットテンプレートを全てのプロジェクトに適用" note_apply_global_template_to_all_projects_setting_enabled: "プラグイン設定によって、グローバルチケットテンプレートは全てのプロジェクトに適用されます" project_list_associated_this_template: "このテンプレートが利用可能なプロジェクト (全 %{all}プロジェクト中: %{applied}プロジェクトが有効) " note_project_local_template_override_global_template: "プロジェクト固有のテンプレートが設定されている場合は、プロジェクト固有のテンプレートが優先されます。また、グローバルテンプレートはチケットの新規作成画面では非表示になります。" help_project_local_template_override_global_template: "このオプションが設定されている場合、グローバルチケットテンプレートは全てのプロジェクトに適用されます。 プロジェクト固有のテンプレートが設定されている場合は、プロジェクト固有のテンプレートが優先され、グローバルテンプレートはチケットの新規作成画面では非表示になります。" warning_project_local_template_override_global_template: "このオプションを設定すると、全てのプロジェクトにグローバルテンプレートが適用されます。取り扱いに注意したい内容を含むグローバルテンプレートを登録する場合は、十分注意してください。" revert_before_applying_template: "テンプレート適用前に戻す" template_remove_confirm: "このテンプレートを削除してよろしいですか? %{count}個のサブプロジェクトがこのテンプレートを利用しています。" label_number_of_subprojects_use_this_template: "%{count}個のサブプロジェクトがこのテンプレートを利用しています " enabled_template_cannot_destroy: "有効になっているテンプレートは削除できません。他に利用しているサブプロジェクトの有無を確認して、無効化してから削除を行なってください" orphaned_templates: "トラッカーから孤立したテンプレート" orphaned_template: "トラッカーから孤立したテンプレート" label_template_applied: "テンプレートが適用されました。(元に戻す場合:「テンプレート適用前に戻す」をクリック)" label_hide_confirm_dialog_in_the_future: "次回からは、この上書きの確認メッセージを表示しない" label_template_for_note: "コメント用テンプレート" label_use_template_when_edit: "チケットの編集時にもテンプレートを利用する" note_template: "コメント用テンプレート" no_note_templates_for_this_project: "このプロジェクトのコメント用テンプレートは未だ登録されていません。" label_memo_help_message: "このテンプレートは、どのような場合に利用すればいいか説明を入力してください。" note_template_name: "コメント用テンプレート名" note_description: "コメント本文" field_template_visibility: 表示できるユーザ note_templates: visibility: mine: 自分のみ roles: 次のロールのみ open: すべてのユーザー please_select_at_least_one_role: 1つ以上のロールを指定してください。 issue_templates_settings: チケットテンプレート設定 issue_templates_optional_settings: テンプレートオプション設定 issue_template_related_link: 関連リンク issue_template_link_title: 関連リンクのタイトル label_related_link_help_message: "テンプレートの利用例や、説明のページがあれば、関連リンクを設定できます。" label_link_title_help_message: "関連リンクのタイトルは任意で設定できます(デフォルト: 関連リンク)" enter_value: 値を入力してください label_select_field: フィールドの選択 label_builtin_fields_help_message: フィールドの設定はJSONフォーマットで登録します。フィールドを指定し、値を設定してください。「適用」を押すとJSONフォーマットのデータが作成されます。 label_builtin_fields_json: フィールド設定用JSON label_enable_builtin_fields: 標準/カスタムフィールドの利用を可能にする help_enable_builtin_fields: 設定を有効にすると、標準/カスタムフィールドのテンプレートを設定するためのフォームが表示されます。設定は「フィールドのID & 値」のJSON形式で登録してください。 warning_enable_builtin_fields: 設定の有効化には十分注意してください。この機能は実験的なものです。入力のUIや値の適用のための十分な機能が提供できていません。また、ブラウザの互換性は保証されていません。本番環境への適用には十分注意してください。 label_field_information: このフィールドの情報 unusable_fields_for_this_tracker: このトラッカーでは利用できません global_note_templates: "グローバル コメント用テンプレート" link_to_index_edit_note_template: "コメント用テンプレート一覧 / 編集" ================================================ FILE: config/locales/ko.yml ================================================ # Korean translation ko: issue_templates: 템플릿 issue_template: 일감 템플릿 issue_template_note: 메모 label_enabled: 활성화 label_disabled: 비활성화 label_help_message: 템플릿에 대하여 label_show_help_message: 일감을 생성/수정할 때 도움말을 표시합니다. about_help_message: 프로젝트마다 템플릿에 대한 사용자 정의 도움말을 설정할 수 있습니다. close_help: 도움말을 닫습니다. about_template_help_message: 이 프로젝트에서 일감 템플릿에 대한 지침을 볼 수 있습니다. label_enabled_help_message: 이 템플릿을 활성화하기 위한 체크 상자. 아직 이 템플릿을 완성하지 않았다면 이 상자를 체크하지 마세요. label_list_templates: "템플릿 목록" label_new_templates: "템플릿 추가" issue_template_name: "템플릿 이름" issue_description: "일감 본문" issue_title: "일감 제목" label_applied_for_issue: "일감에 적용될 항목들: " help_for_issue_title: "일감 제목이 정의되면 이 템플릿을 선택할 때 자동으로 일감 제목에 적용될 것입니다." help_for_this_field: "이 항목에 대한 도움말" permission_manage_issue_templates: "템플릿 관리" permission_edit_issue_templates: "템플릿 편집" permission_show_issue_templates: "템플릿 표시" project_module_issue_templates: "일감 템플릿" label_isdefault_help_message: "대상 유형에 대해 기본 템플릿으로 사용합니다." defaulf_template_loaded: "본문 영역에 기본 템플릿을 불러왔습니다. (유형: %{tracker})" text_no_tracker_enabled: "이 프로젝트에서 사용되는 유형이 없습니다.\n템플릿은 유형에 할당되기 때문에 이를 먼저 설정해주세요." label_enabled_sharing: "프로젝트 트리에서 공유할 수 있습니다." label_inherit_templates: "템플릿 상속" label_inherit_templates_help_message: "상위 프로젝트로부터 템플릿들을 상속합니다. (상위 프로젝트에서 프로젝트 트리에서 상속하도록 설정한 템플릿들만 나열됩니다.)" label_inherited_templates: "상속된 템플릿" no_issue_templates_for_this_project: "이 프로젝트를 위해 정의된 일감 템플릿이 없습니다." link_to_index_edit_template: "일감 템플릿 나열/편집" erase_issue_subject_and_description: "제목과 설명을 비움" unused_tracker_at_this_project: "참고: 이 프로젝트에서 사용하는 유형이 아닙니다. 필요한 경우 템플릿 설정을 다시 해주세요." label_enabledshaing_help_message: "이 템플릿은 하위 프로젝트와 공유될 수 있게 됩니다. (하위 프로젝트에서 템플릿 상속을 활성화해야 합니다.)" label_should_replaced: "제목과 설명을 대체" label_should_replaced_help_message: "제목과 본문이 템플릿의 내용으로 대체됩니다. (기본값은 사용 안함)" global_issue_templates: "전역 일감 템플릿" no_issue_templates_for_this_redmine: "이 레드마인을 위해 정의된 전역 일감 템플릿이 없습니다." only_admin_can_associate_global_template: "레드마인 관리자만 전역 템플릿과 이 프로젝트를 연결할 수 있습니다." text_no_tracker_enabled_for_global: "유형이 정의되지 않았습니다.\n템플릿은 유형에 할당되기 때문에 이를 먼저 설정해주세요." display_and_filter_issue_templates_in_dialog: "템플릿 내용 미리보기" label_filter_template: "Filter templates" label_msg_confirm_to_replace: "제목과 설명을 대체하시겠습니까?" label_apply_global_template_to_all_projects: "전역 일감 텔플릿을 모든 프로젝트에 적용." note_apply_global_template_to_all_projects_setting_enabled: "이 전역 일감 템플릿은 플러그인 설정에 따라 모든 프로젝트에 적용됩니다." project_list_associated_this_template: "프로젝트 목록: (적용됨: %{all}개 중 %{applied}개)" note_project_local_template_override_global_template: "프로젝트 한정 템플릿이 있다면 전역 템플릿보다 우선되고, 일감 생성 화면에서 어떤 전역 템플릿도 보여지지 않게 됩니다." help_project_local_template_override_global_template: "이 옵션이 활성화되면 모든 전역 티켓 템플릿이 모든 프로젝트에 적용됩니다. 유형 별로 프로젝트 한정 템플릿이 있다면 전역 템플릿보다 우선되고, 일감 생성 화면에서 전역 템플릿이 보여지지 않게 됩니다." warning_project_local_template_override_global_template: "전역 일감 템플릿을 등록할 때에는 기밀 내용이 포함되어 있는지 주의가 필요합니다. 이 옵션이 활성화되면 모든 전역 템플릿이 모든 프로젝트에 적용됩니다." revert_before_applying_template: "되돌리기" template_remove_confirm: "이 템플릿을 삭제하시겠습니까? %{count}개의 하위 프로젝트들이 이 템플릿을 사용하고 있습니다." label_number_of_subprojects_use_this_template: "%{count}개의 하위 프로젝트들이 이 템플릿을 사용하고 있습니다." enabled_template_cannot_destroy: "비활성화된 템플릿만 삭제될 수 있습니다. 다른 하위 프로젝트들이 이 템플릿을 사용하는지 확인하고 삭제하기 전에 비활성화하세요." orphaned_templates: "유형에 독립적인 템플릿" orphaned_template: "유형에 독립적인 템플릿" label_template_applied: "일감 템플릿이 적용되었습니다. '되돌리기'를 통해 적용하기 전으로 되돌릴 수 있습니다." label_hide_confirm_dialog_in_the_future: "앞으로 이 확인은 나타나지 않으며, 바로 덮어쓰여집니다." label_template_for_note: "댓글 템플릿" label_use_template_when_edit: "일감을 편집할 때 템플릿을 사용" note_template: "댓글 템플릿" no_note_templates_for_this_project: "이 프로젝트에는 댓글 템플릿이 없습니다." label_memo_help_message: "이 템플릿을 적용할 때 설명을 하는 노트를 작성해주세요." note_template_name: "댓글 템플릿 이름" note_description: "댓글 본문" field_template_visibility: 템플릿 공개 범위 note_templates: visibility: mine: 나에게만 roles: 특정 역할에게만 open: 모든 사용자에게 please_select_at_least_one_role: "하나 이상의 역할을 선택해주세요." issue_templates_settings: 일감 템플릿 설정 issue_templates_optional_settings: 템플릿 옵션 설정 issue_template_related_link: 참고 링크 issue_template_link_title: 참고 링크 제목 label_related_link_help_message: "일감 템플릿을 사용하는 예시 페이지나 일감들이 있다면 링크를 지정함으로써 템플릿에 대한 사용법이나 예제를 확인할 수 있습니다." label_link_title_help_message: "참고 링크의 제목을 변경할 수 있습니다. (기본값: 참고 링크)" enter_value: 값을 입력해주세요. label_select_field: 필드 선택 label_builtin_fields_help_message: 내장 필드 혹은 사용자 정의 필드의 기본값을 JSON 포맷으로 입력하세요. 필드들을 선택하고 값을 입력한 뒤에 "적용" 버튼을 누리면 JSON 포맷의 데이터를 만들 수 있습니다. label_builtin_fields_json: 필드들에 대한 JSON 값 label_enable_builtin_fields: 내장된 필드 활성화 help_enable_builtin_fields: 이 항목이 활성화되면 내장 혹은 사용자 정의된 필드들에 대해 템플릿을 설정할 수 있는 폼이 보여질 것입니다. JSON 포맷으로 이 설정을 등록해주세요. warning_enable_builtin_fields: 이 항목을 활성화하는 것을 주의해주세요. 아직 모든 필드 유형을 지원하기에 충분한 UI와 기능을 제공하지 않기 때문에 실험 중인 기능입니다. 사용 중인 웹 브라우저에 따라 동작하지 않을 수도 있습니다. 실 환경에 적용할 때에는 주의하세요. label_field_information: 필드 정보 unavailable_fields_for_this_tracker: 이 유형에서 사용할 수 없는 필드 ================================================ FILE: config/locales/pl.yml ================================================ # Polish strings go here for Rails i18n pl: issue_templates: Szablony zagadnień issue_template: Szablon zagadnienia issue_template_note: info label_enabled: Włącz label_disabled: Wyłącz label_help_message: O szablonach label_show_help_message: Pokazuj pomoc przy tworzeniu / edycji zagadnień. about_help_message: Każdy projekt może mieć swoje wiadomości pomocy dla szablonów. close_help: Zamknij pomoc. about_template_help_message: Możesz zobaczyć instrukcję dla szablonów zagadnień w tym projekcie. label_enabled_help_message: Powoduje możliwość korzystania z tego szablonu. Jeśli chcesz, aby szablon był zapisany jako szkic, odznacz to pole. label_list_templates: "Lista szablonów" label_new_templates: "Dodaj szablon" issue_template_name: "Nazwa szablonu" issue_description: "Treść zagadnienia" issue_title: "Tytuł zagadnienia" label_applied_for_issue: "Pola wpisywane do zagadnienia: " help_for_issue_title: "Jeśli wpisany jest tytuł zagadnienia, to także będzie wpisywane przy wyborze szablonu." help_for_this_field: "Pomoc dla tego pola." permission_manage_issue_templates: "Zarządzaj Szablonami" permission_edit_issue_templates: "Edycja Szablonów" permission_show_issue_templates: "Pokaż Szablony" project_module_issue_templates: "Szablony Zagadnień" label_isdefault_help_message: "Zaznacz aby móc używać tego szablonu jako domyślnego dla danego typu zagadnień." defaulf_template_loaded: "Wczytano domyślny szablon do opisu zagadnienia. (Typ: %{tracker})" text_no_tracker_enabled: "Nie skonfigurowano typów zagadnień dla tego projektu.\nProszę je najpierw ustawić, ponieważ szablony są przypisane do typów zagadnień. " label_enabled_sharing: "Włączone współdzielenie z drzewem podprojektów." label_inherit_templates: "Dziedzicz szablony" 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.)" label_inherited_templates: "Dziedziczone szablony" no_issue_templates_for_this_project: "Brak zdefiniowanych szablonów zagadnień dla tego projektu." link_to_index_edit_template: "Lista szablonów zagadnień / edycja szablonów" erase_issue_subject_and_description: "Czyść tytuł i tekst w opisie zagadnienia." 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." 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.)" label_should_replaced: "Nadpisuj tytuł i opis zagadnienia" 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.)" global_issue_templates: "Globalne Szablony Zagadnień" no_issue_templates_for_this_redmine: "Brak zdefiniowanych globalnych szablonów zagadnień." only_admin_can_associate_global_template: "Tylko administrator może przypisywać globalne szablony do projektów." text_no_tracker_enabled_for_global: "Brak zdefiniowanych typów zagadnień.\nProszę je najpierw ustawić, ponieważ szablony są przypisane do typów zagadnień." ================================================ FILE: config/locales/pt-BR.yml ================================================ # Brazilian strings go here for Rails i18n pt-BR: issue_templates: Modelos de tarefas issue_template: Modelo de tarefa issue_template_note: notas label_enabled: Habilitado label_disabled: Desabilitado label_help_message: Sobre modelos label_show_help_message: Visualizar mensagem de ajuda ao Criar/Atualizar tarefa. about_help_message: Cada projeto pode customizar mensagem de ajuda para os modelos. close_help: Fechar mensagem de ajuda. about_template_help_message: Poder visualizar instruções sobre modelos de tarefas neste projeto. label_enabled_help_message: Opção para ativar este modelo. Se deseja salvar este modelo como rascunho, desative esta opção. label_list_templates: "Lista de modelos" label_new_templates: "Incluir modelo" issue_template_name: "Nome do modelo" issue_description: "Corpo da tarefa" issue_title: "Título da tarefa" label_applied_for_issue: "Campos aplicados para tarefa: " help_for_issue_title: "Se definido o título da tarefa, este será aplicado à tarefa ao selecionar o modelo." help_for_this_field: "Ajuda sobre este campo." permission_manage_issue_templates: "Gerenciar Modelos" permission_edit_issue_templates: "Editar Modelos" permission_show_issue_templates: "Visualizar Modelos" project_module_issue_templates: "Modelos de Tarefas" label_isdefault_help_message: "Se esta opção estiver ativa este será o modelo padrão para este tipo de tarefa." defaulf_template_loaded: "Modelo padrão carregado na área da descrição. (Tipo: %{tracker})" 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. " label_enabled_sharing: "Habilitar compartilhamento com a árvore de projetos." label_inherit_templates: "Herdar modelos" 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.)" label_inherited_templates: "Modelos herdados" no_issue_templates_for_this_project: "Não há modelos de tarefa definidos para este projeto." link_to_index_edit_template: "Lista de modelos de tarefa / editar modelos" erase_issue_subject_and_description: "Limpar assunto e descrição." 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." 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.)" label_should_replaced: "Substituir assunto e descrição" 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.)" global_issue_templates: "Modelos de Tarefa Globais" no_issue_templates_for_this_redmine: "Não há modelos de tarefa globais definidos neste redmine." only_admin_can_associate_global_template: "Somente administradores podem associar modelos globais a este projeto." 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." display_and_filter_issue_templates_in_dialog: "Visualizar conteúdo do template" label_filter_template: "Filtrar modelos" label_msg_confirm_to_replace: "Você quer substituir o assunto e a descrição?" label_apply_global_template_to_all_projects: "Aplica modelos de problemas globais a todos os projetos." note_apply_global_template_to_all_projects_setting_enabled: "Este modelo de problema global é aplicado a todos os projetos por configuração de plug-in." project_list_associated_this_template: "Lista de projetos: (aplicado: %{applied} of %{all})" note_project_local_template_override_global_template: "Se existe um modelo local de projeto, substitua modelos globais e quaisquer modelos globais não são exibidos na tela criar tarefa." help_project_local_template_override_global_template: "Se esta opção for ativada, todos os modelos de passagens globais serão aplicados a todos os projetos. Se um modelos específico do projeto por tipo tarefa estiver definido, ele se substituirá e o template global está oculto na tela de criação de nova tarefa." warning_project_local_template_override_global_template: "Tenha cuidado ao registrar o template de tarefa global, que contém conteúdo confidencial para o qual deseja prestar atenção. Caso esta opção seja ativada, todos os modelos globais serão aplicados a todos os projetos." revert_before_applying_template: "Reverter" template_remove_confirm: "Tem certeza de remover esse template? %{count} subprojetos usam este template." label_number_of_subprojects_use_this_template: "%{count} subprojetos usam este template." enabled_template_cannot_destroy: "Apenas o template desativado pode ser destruído. Verifique se outros subprojetos usam esse template ou não e desativá-lo antes de excluir." orphaned_templates: "Modelos órfãos do tipo tarefa" orphaned_template: "Modelos órfãos do tipo tarefa" ================================================ FILE: config/locales/pt.yml ================================================ # Brazilian strings go here for Rails i18n pt: issue_templates: Modelos de tarefas issue_template: Modelo de tarefas issue_template_note: notas label_enabled: Habilitado label_disabled: Desabilitado label_help_message: Sobre modelos label_show_help_message: Visualizar mensagem de ajuda ao Criar/Atualizar tarefa. about_help_message: Cada projeto pode customizar mensagem de ajuda para os modelos. close_help: Fechar mensagem de ajuda. about_template_help_message: Poder visualizar instruções sobre modelos de tarefas neste projeto. label_enabled_help_message: Opção para ativar este modelo. Se deseja salvar este modelo como rascunho, desative esta opção. label_list_templates: "Lista de modelos" label_new_templates: "Incluir modelo" issue_template_name: "Nome do modelo" issue_description: "Corpo da tarefa" issue_title: "Título da tarefa" label_applied_for_issue: "Campos aplicados para tarefa: " help_for_issue_title: "Se definido o título da tarefa, este será aplicado à tarefa ao selecionar o modelo." help_for_this_field: "Ajuda sobre este campo." permission_manage_issue_templates: "Gerenciar Modelos" permission_edit_issue_templates: "Editar Modelos" permission_show_issue_templates: "Visualizar Modelos" project_module_issue_templates: "Modelos de Tarefas" label_isdefault_help_message: "Se esta opção estiver ativa este será o modelo padrão para este tipo de tarefa." defaulf_template_loaded: "Modelo padrão carregado na área da descrição. (Tipo: %{tracker})" 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. " label_enabled_sharing: "Habilitar compartilhamento com a árvore de projetos." label_inherit_templates: "Herdar modelos" 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.)" label_inherited_templates: "Modelos herdados" no_issue_templates_for_this_project: "Não há modelos de tarefa definidos para este projeto." link_to_index_edit_template: "Lista de modelos de tarefa / editar modelos" erase_issue_subject_and_description: "Limpar assunto e descrição." 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." 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.)" label_should_replaced: "Substituir assunto e descrição" 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.)" global_issue_templates: "Modelos de Tarefa Globais" no_issue_templates_for_this_redmine: "Não há modelos de tarefa globais definidos neste redmine." only_admin_can_associate_global_template: "Somente administradores podem associar modelos globais a este projeto." 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." ================================================ FILE: config/locales/ru.yml ================================================ # Russian strings go here for Rails i18n ru: issue_templates: Шаблоны issue_template: Шаблон задачи issue_template_note: Заметка label_enabled: Вкл. label_disabled: Выкл. label_help_message: Подсказка по шаблонам label_show_help_message: Показывать подсказку по шаблонам при создаии/редактировании задачи. about_help_message: У каждого проекта может быть своя подсказа по шаблонам close_help: Закрыть подсказку about_template_help_message: Вы можете посмотреть подсказку по работе с шаблонами для данного проекта label_enabled_help_message: Если параметр установлен, шаблон доступен для использования в задаче. Отключите, если хотите сохранить шаблон как черновик. label_list_templates: "Список шаблонов" label_new_templates: "Добавить шаблон" issue_template_name: "Название шаблона" issue_description: "Шаблон текста задачи" issue_title: "Заголовок задачи" label_applied_for_issue: "Заполняемые поля задачи: " help_for_issue_title: "Если установлено значение для 'Заголовок задачи', то оно будет автоматически вставляться в Тему задачи при выборе шаблона." help_for_this_field: "Справка по данному полю" permission_manage_issue_templates: "Управление шаблонами" permission_edit_issue_templates: "Редактировать шаблоны" permission_show_issue_templates: "Показывать шаблоны" project_module_issue_templates: "Использовать шаблоны" label_isdefault_help_message: "Если параметр установлен, шаблон будет подставляться автоматически при создании новой задачи для соответствующего трекера." defaulf_template_loaded: "В поле описания задачи подставлен шаблон по умолчанию (Трекер: %{tracker})" text_no_tracker_enabled: "Для проекта не настроен ни один трекер.\nПожалуйста, настройте трекеры, так как шаблоны должны быть ассоциированы с трекерами." label_enabled_sharing: "Разрешить использование шаблона в дочерних проектах." label_inherit_templates: "Унаследовать шаблоны родительского проекта" label_inherit_templates_help_message: "Наследует шаблоны, настроенные для родительского проекта. Унаследованы будут только те шаблоны, для которых это разрешено в параметрах шаблона (настройка Разрешить использование шаблона во всем дереве проектов)." label_inherited_templates: "Унаследованные шаблоны" no_issue_templates_for_this_project: "Для данного проекта не настроены шаблоны задач." link_to_index_edit_template: "Список шаблонов задач / редактирование шаблонов" erase_issue_subject_and_description: "Очистить тему и описание задачи." unused_tracker_at_this_project: "ВНИМАНИЕ: Данный трекер не используется в текущем проекте. Пожалуйста перенастройте шаблон при необходимости." label_enabledshaing_help_message: "Если параметр установлен, данный шаблон может быть унаследован дочерними проектами. (Для работы данной функции необходимо включить наследование в настройках шаблонов дочернего проекта.)" label_should_replaced: "Заменять тему и описание" label_should_replaced_help_message: "Если параметр установлен, текст темы и описания будет очищен и заменен текстом из шаблона. (По умолчанию параметр отключен, и текст добавляется к существующему, а не заменяется.)" global_issue_templates: "Глобальные шаблоны задач" no_issue_templates_for_this_redmine: "Глобальные шаблоны задач, доступные для всех проектов Redmine, не настроены." only_admin_can_associate_global_template: "Подключить глобальные шаблоны задач к данному проекту может только администратор Redmine." text_no_tracker_enabled_for_global: "Не настроен ни один трекер.\nПожалуйста, настройте трекеры, так как шаблоны должны быть ассоциированы с трекерами." display_and_filter_issue_templates_in_dialog: "Подобрать шаблон" label_filter_template: "Подобрать шаблон" label_apply_global_template_to_all_projects: "Применить глобальный шаблон ко всем проектам" label_use_template_when_edit: "Использовать шаблон при редактировании" note_template: "Шаблон комментариев" orphaned_template: "Потерянные шаблоны" project_list_associated_this_template: "Проекты с этим шаблоном" help_project_local_template_override_global_template: "Если эта опция активирована, то глобальные шаблоны будут назначены на все проекты, если проект имеет свой шаблон, то глобальный шаблон будет спрятан при создании новой задачи." warning_project_local_template_override_global_template: "Пожалуйста будте осторожны, т.к. глобальные шаблоны могут содержать конфиденциальную информацию и они назначаются на все проекты." warning_enable_builtin_fields: "Внимание эта функция экспериментальная, используйте на свой страх и риск." help_enable_builtin_fields: "При включении будет показана форма, позволяющая выставить шаблоны для встроенных или пользовтельских полей. Пожалуйста зарегистрируейте настройки в формате JSON." no_note_templates_for_this_project: "Для проекта еще не назначены шаблоны комментариев." ================================================ FILE: config/locales/sr-YU.yml ================================================ # English strings go here for Rails i18n sr: issue_templates: Šabloni problema issue_template: Šablon problema issue_template_note: nota label_enabled: Aktivno label_disabled: Neaktivno label_help_message: O šablonu label_show_help_message: Prikaži poruku za pomoć prilikom kreiranja ili ažuriranja problema. about_help_message: Za svaki projekat se može prilagoditi poruka za pomoć za upotrebu šablona. close_help: Zatvori poruku za pomoć. about_template_help_message: Možete videti instrukcije o korišćenju šablona na ovom projektu. label_enabled_help_message: Aktivira šablon. Ukoliko i dalje razvijate ovaj šablon, ostavite ga deaktiviranim dok nije završen. label_list_templates: "Lista šablona" label_new_templates: "Dodaj šablon" issue_template_name: "Ime šablona" issue_description: "Opis" issue_title: "Predmet" label_applied_for_issue: "Polja problema za koja se šablon odnosi" help_for_issue_title: "Ukoliko je definisan predmet problema on će takođe biti primenjen prilikom selekcije šablona." help_for_this_field: "Pomoć za ovo polje" permission_manage_issue_templates: "Upravljanje šablonima" permission_edit_issue_templates: "Ažuriraj šablon" permission_show_issue_templates: "Prikaži šablon" project_module_issue_templates: "Šabloni problema" label_isdefault_help_message: "Ukoliko je aktivirano, ovaj šablon će biti podrazumevani (default) za dato praćenje (tracker) problema." defaulf_template_loaded: "Učitan je podrazumevani šablon za ovo praćenje problema u polje za opis. (Praćenje: %{tracker})" text_no_tracker_enabled: "Još uvek nisu konfigurisani pratioci (tracker) za ovaj projekat." label_enabled_sharing: "Omogući deljenje sa stablom projekta" label_enabledshaing_help_message: "Aktiviraj da bi subprojekti nasledili šablone." label_inherit_templates: "Nasledi šablon" label_inherit_templates_help_message: "Nasledi šablon od majke projekta" label_inherited_templates: "Nasleđeni šabloni" no_issue_templates_for_this_project: "Nema definisanih šablona za ovaj projekat." link_to_index_edit_template: "Lista šablona problema / ažuriraj šablone." erase_issue_subject_and_description: "Obriši naslov i opis" unused_tracker_at_this_project: "NOTA: Ovo praćenje problema (tracker) se ne koristi u ovom projektu." ================================================ FILE: config/locales/zh-TW.yml ================================================ # Tranditional Chinese strings go here for Rails i18n zh-TW: issue_templates: "問題樣板" issue_template: "問題樣板" issue_template_note: "註釋" label_enabled: "啟用" label_disabled: "停用" label_help_message: "關於樣板" label_show_help_message: "建立/修改問題時顯示提示訊息。" about_help_message: "每個專案都可以為這個樣板定義提示訊息。" close_help: "關閉提示訊息。" about_template_help_message: "你可以看到當前問題的樣板幫助訊息。" label_enabled_help_message: "該確認框用以啟用當前的樣板。若你只想把目前的樣板先當成草稿使用,請不要勾選。" label_list_templates: "樣板列表" label_new_templates: "新建樣板" issue_template_name: "樣板名稱" issue_description: "樣板内容" issue_title: "問題標題" label_applied_for_issue: "該問題所使用的欄位內容" help_for_issue_title: "如果在這邊有設定值,則使用該樣板時回自動帶入。" help_for_this_field: "本欄位說明。" permission_manage_issue_templates: "管理樣板" permission_edit_issue_templates: "編輯樣板" permission_show_issue_templates: "顯示樣板" project_module_issue_templates: "問題樣板" label_isdefault_help_message: "勾選此選項則該問題類型會使用此樣板當作預設值" defaulf_template_loaded: "載入樣板資訊至概述欄位中(追蹤標籤: %{tracker})" text_no_tracker_enabled: "本專案的追蹤標簽中沒有任何一個設定了問題樣板。\n 請在使用前先設定他們" label_enabled_sharing: "在專案樹狀結構中分享" label_inherit_templates: "繼承樣板" label_inherit_templates_help_message: " 從父專案繼承問題樣板(父專案中只有那些有勾選分享的才會出現在此處)。" label_inherited_templates: "繼承樣板" no_issue_templates_for_this_project: "此專案沒有定義任何的問題樣板" link_to_index_edit_template: "問題樣板列表/ 編輯樣板" erase_issue_subject_and_description: "清除主旨以及概述。" unused_tracker_at_this_project: "備註:這個追蹤標簽並沒有被指派給這個專案使用。必要的話請重新設定樣板" label_enabledshaing_help_message: "如果設定值為 True,這個樣板就可以分享給子專案使用(你還是得在子專案中把繼承而來的問題樣板啟用)。" label_should_replaced: "取代標題以及描述" label_should_replaced_help_message: "如果設定值為 True,當前的標題以及描述會被清除,且被樣板的文字所取代(預設是 False, 把文字附加在後面)。" global_issue_templates: "全域問題樣板" no_issue_templates_for_this_redmine: "網站目前沒有全域的問題樣板!" only_admin_can_associate_global_template: "只有 Redmine 管理員可以在這個專案中關聯全域樣板。" text_no_tracker_enabled_for_global: "尚未定義追蹤標籤。\n請先分配模板適用的追蹤標籤。" display_and_filter_issue_templates_in_dialog: "預覽樣板內容" label_filter_template: "篩選樣板" label_msg_confirm_to_replace: "確定要取代主旨和說明嗎?" label_apply_global_template_to_all_projects: "套用全域問題樣板到所有專案。" note_apply_global_template_to_all_projects_setting_enabled: "基於外掛設定,這個全域問題樣板被套用到所有專案。" project_list_associated_this_template: "專案列表: (已套用 %{all} 個中的 %{applied} 個)" note_project_local_template_override_global_template: "如果專案有自己的樣板,覆蓋全域樣板且新增問題時不顯示全域樣板選項。" help_project_local_template_override_global_template: "當啟用這個選項時,所有的全域問題樣板會被套用到所有專案。如果特定專案有針對每個追蹤標籤設定樣板,它將會覆蓋全域樣板,且新增問題時不會顯示全域樣板。" warning_project_local_template_override_global_template: "註冊包含機密內容的全域問題樣板時,請特別注意:一旦啟用了這個選項,所有的全域樣板會被套用到所有專案。" revert_before_applying_template: "還原" template_remove_confirm: "確定要刪除這個樣板嗎?目前有 %{count} 個子問題使用這個樣板。" label_number_of_subprojects_use_this_template: "%{count} 個子專案使用這個樣板。" enabled_template_cannot_destroy: "只有已停用的樣板可以被刪除。請先確認其他子問題是否有使用這個樣板,如果沒有則可以先停用它再來刪除。" orphaned_templates: "追蹤標籤中被孤立的樣板" orphaned_template: "追蹤標籤中被孤立的樣板" label_template_applied: "問題樣板已套用。你可以按「還原」連結來還原。" label_hide_confirm_dialog_in_the_future: "未來直接覆寫,不再詢問。" label_template_for_note: "筆記樣板" label_use_template_when_edit: "編輯問題時使用樣板" note_template: "筆記樣板" no_note_templates_for_this_project: "這個專案沒有任何回應樣板。" label_memo_help_message: "請建立一個筆記來說明為何套用這個樣板。" note_template_name: "筆記樣板名稱" note_description: "回應內容" field_template_visibility: 樣板可見度 note_templates: visibility: mine: 只有我 roles: 只有這些角色 open: 任何人 please_select_at_least_one_role: "請至少選擇一個角色。" ================================================ FILE: config/locales/zh.yml ================================================ # Simplified Chinese strings go here for Rails i18n, based on file ja.yml zh: issue_templates: ISSUE模板管理 issue_template: ISSUE模板管理 issue_template_note: 批注 label_enabled: 启用 label_disabled: 禁用 label_help_message: 关于ISSUE模板 label_show_help_message: 当创建/修改问题时显示对应的帮助信息。 about_help_message: 每个项目都可以自定义ISSUE模板帮助信息。 close_help: 关闭帮助信息。 about_template_help_message: 您可以查看当前项目ISSUE模板的帮助指南。 label_enabled_help_message: 该复选框用于激活当前ISSUE模板。若您只想将ISSUE模板保存为草稿,请取消该复选框的勾选。 label_list_templates: "ISSUE模板列表" label_new_templates: "新建ISSUE模板" issue_template_name: "ISSUE模板名称" issue_description: "ISSUE模板内容" issue_title: "ISSUE主题" label_applied_for_issue: "应用于该ISSUE的字段:" help_for_issue_title: "若在ISSUE模板中已定义了主题,则在使用该ISSUE模板时,将自动替换为对应的主题信息。" help_for_this_field: "当前字段帮助信息" permission_manage_issue_templates: "管理ISSUE模板" permission_edit_issue_templates: "编辑ISSUE模板" permission_show_issue_templates: "显示ISSUE模板" project_module_issue_templates: "ISSUE模板管理" label_isdefault_help_message: "勾选后,设置当前ISSUE模板为指定跟踪类型对应的默认模板。" defaulf_template_loaded: "在描述区域内加载模板默认信息。 (跟踪类型:%{tracker})" text_no_tracker_enabled: "当前项目中尚未配置跟踪类型。\n请在使用ISSUE模板前,先配置项目跟踪类型。" label_enabled_sharing: "启用向下继承(在当前项目的子项目中使用该ISSUE模板)" label_inherit_templates: "继承ISSUE模板" label_inherit_templates_help_message: "从父项目中继承ISSUE模板。(仅限在父项目中使用,且标记为向下继承的ISSUE模板。)" label_inherited_templates: "继承ISSUE模板" no_issue_templates_for_this_project: "当前项目中未定义ISSUE模板信息。" link_to_index_edit_template: "ISSUE模板列表 / 编辑ISSUE模板" erase_issue_subject_and_description: "点击清除主题及描述信息。" unused_tracker_at_this_project: "注意:当前项目中未定义可用的跟踪类型。请根据实际需要,重新配置ISSUE模板。" label_enabledshaing_help_message: "若选中,则当前ISSUE模板可以向下继承,与子项目共享使用。(您需在子项目中激活ISSUE模板继承选项便可使用。)" label_should_replaced: "替换主题及描述信息" label_should_replaced_help_message: "若选中,现有主题及描述信息将被自动清除,并替换为ISSUE模板中定义的内容。(默认不选中,则在现有内容后扩展模板内定义的信息。)" global_issue_templates: "全局ISSUE模板管理" no_issue_templates_for_this_redmine: "当前未定义全局ISSUE模板。请联系Redmine系统管理员。" only_admin_can_associate_global_template: "只有Redmine系统管理员才能在当前项目中关联全局ISSUE模板。" text_no_tracker_enabled_for_global: "未指定跟踪类型。\n在使用ISSUE模板前,请先指定模板适用的跟踪类型。" display_and_filter_issue_templates_in_dialog: "模板过滤器" label_filter_template: "模板过滤器" label_msg_confirm_to_replace: "确认替换主题及描述信息?" label_apply_global_template_to_all_projects: "将全局问题模板应用于全部项目。" note_apply_global_template_to_all_projects_setting_enabled: "该全局问题模板通过插件设置配置后,应用于所有项目。" project_list_associated_this_template: "项目列表: (已应用于全部 %{all} 个项目中的 %{applied} 个)" note_project_local_template_override_global_template: "如果存在项目本地模板,则覆盖全局模板,且在该项目内创建问题时,不显示任何全局模板。" help_project_local_template_override_global_template: "如果启用此选项,则问题的全局模板将应用于所有项目。 如果设置了每个跟踪器的项目特定模板,则会覆盖对应的全局模板,并在创建新问题时,隐藏全局模板。" warning_project_local_template_override_global_template: "注册全局问题模板时,务必注意模板信息中的特定项目的机密信息。 一旦启用此选项,则所有全局模板将应用于所有项目。" revert_before_applying_template: "回退" template_remove_confirm: "您确定要删除此模板吗? 当前有 %{count} 个项目正在使用此模板。" label_number_of_subprojects_use_this_template: "%{count} 个项目正在使用此模板 " enabled_template_cannot_destroy: "已启用的模板不能删除。检查您正在使用的模板是否在其他项目中存在,并在删除之前将其禁用。" orphaned_templates: "基于跟踪的个性化模板" orphaned_template: "基于跟踪的个性化模板" ================================================ FILE: config/routes.rb ================================================ # # TODO: Clean up routing. # Rails.application.routes.draw do concern :tamplate_common do get 'orphaned_templates', on: :collection end concern :previewable do post 'preview', on: :collection end resources :global_issue_templates, except: [:edit], concerns: %i[tamplate_common previewable] # for project issue template resources :projects, only: [] do resources :issue_templates, except: [:edit], concerns: [:tamplate_common] do post 'set_pulldown', on: :collection get 'list_templates', on: :collection end resources :issue_templates_settings, only: [:edit], concerns: [:previewable] do patch 'edit', on: :collection end get 'issue_templates_settings', to: 'issue_templates_settings#index' resources :note_templates, except: [:edit] end resources :issue_templates, only: %i[load preview load_selectable_fields], concerns: [:previewable] do post 'load', on: :collection get 'load_selectable_fields', on: :collection end # for note temlate resources :note_templates, only: %i[load preview list_templates] do post 'load', on: :collection get 'list_templates', on: :collection end # for global note temlate resources :global_note_templates, except: [:edit], concerns: %i[previewable] end ================================================ FILE: db/migrate/0001_create_issue_templates.rb ================================================ class CreateIssueTemplates < ActiveRecord::Migration[4.2] def self.up create_table :issue_templates do |t| t.column :title, :string, null: false t.column :project_id, :integer t.column :tracker_id, :integer, null: false t.column :author_id, :integer, null: false t.column :note, :string t.column :description, :text t.column :enabled, :boolean t.column :created_on, :timestamp t.column :updated_on, :timestamp end add_index :issue_templates, :author_id add_index :issue_templates, :project_id add_index :issue_templates, :tracker_id end def self.down remove_index :issue_templates, :author_id remove_index :issue_templates, :project_id remove_index :issue_templates, :tracker_id drop_table :issue_templates end end ================================================ FILE: db/migrate/0002_create_issue_template_settings.rb ================================================ class CreateIssueTemplateSettings < ActiveRecord::Migration[4.2] def self.up create_table :issue_template_settings do |t| t.column :project_id, :integer t.column :help_message, :text t.column :enabled, :boolean end end def self.down drop_table :issue_template_settings end end ================================================ FILE: db/migrate/0003_add_issue_title_to_issue_templates.rb ================================================ class AddIssueTitleToIssueTemplates < ActiveRecord::Migration[4.2] def self.up add_column :issue_templates, :issue_title, :string IssueTemplate.reset_column_information issue_templates = IssueTemplate.all issue_templates.each do |t| t.issue_title = t.title t.save end end def self.down remove_column :issue_templates, :issue_title end end ================================================ FILE: db/migrate/0004_add_position_to_issue_templates.rb ================================================ class AddPositionToIssueTemplates < ActiveRecord::Migration[4.2] def self.up add_column :issue_templates, :position, :integer, default: 1 IssueTemplate.reset_column_information issue_templates = IssueTemplate.all say_with_time('Update each template to set default position.') do issue_templates.each_with_index { |t, i| t.update_attribute(:position, i + 1) } end end def self.down remove_column :issue_templates, :position end end ================================================ FILE: db/migrate/20121208150810_add_is_default_to_issue_templates.rb ================================================ class AddIsDefaultToIssueTemplates < ActiveRecord::Migration[4.2] def self.up add_column :issue_templates, :is_default, :boolean, default: false end def self.down remove_column :issue_templates, :is_default end end ================================================ FILE: db/migrate/20130630141710_add_enabled_sharing_to_issue_templates.rb ================================================ class AddEnabledSharingToIssueTemplates < ActiveRecord::Migration[4.2] def self.up add_column :issue_templates, :enabled_sharing, :boolean, default: false end def self.down remove_column :issue_templates, :enabled_sharing end end ================================================ FILE: db/migrate/20130701024625_add_inherit_templates_to_issue_template_settings.rb ================================================ class AddInheritTemplatesToIssueTemplateSettings < ActiveRecord::Migration[4.2] def self.up add_column :issue_template_settings, :inherit_templates, :boolean, default: false, null: false end def self.down remove_column :issue_template_settings, :inherit_templates end end ================================================ FILE: db/migrate/2014020191500_add_should_replaced_to_issue_template_settings.rb ================================================ class AddShouldReplacedToIssueTemplateSettings < ActiveRecord::Migration[4.2] def self.up add_column :issue_template_settings, :should_replaced, :boolean, default: false end def self.down remove_column :issue_template_settings, :should_replaced end end ================================================ FILE: db/migrate/20140307024626_create_global_issue_templates.rb ================================================ class CreateGlobalIssueTemplates < ActiveRecord::Migration[4.2] def change create_table :global_issue_templates do |t| t.string :title t.string :issue_title t.integer :tracker_id t.integer :author_id t.string :note t.text :description t.boolean :enabled t.integer :position t.boolean :is_default t.timestamp :created_on t.timestamp :updated_on end add_index :global_issue_templates, :author_id add_index :global_issue_templates, :tracker_id end def self.down remove_index :global_issue_templates, :author_id remove_index :global_issue_templates, :tracker_id drop_table :global_issue_templates end end ================================================ FILE: db/migrate/20140312054531_create_global_issue_templates_projects.rb ================================================ class CreateGlobalIssueTemplatesProjects < ActiveRecord::Migration[4.2] def self.up create_table :global_issue_templates_projects, id: false do |t| t.integer :project_id t.integer :global_issue_template_id end end def self.down drop_table :global_issue_templates_projects end end ================================================ FILE: db/migrate/20140330155030_remove_is_default_from_global_issue_templates.rb ================================================ class RemoveIsDefaultFromGlobalIssueTemplates < ActiveRecord::Migration[4.2] def self.up remove_column :global_issue_templates, :is_default end def self.down end end ================================================ FILE: db/migrate/20160727222420_add_checklist_json_to_issue_templates.rb ================================================ class AddChecklistJsonToIssueTemplates < ActiveRecord::Migration[4.2] def self.up add_column :issue_templates, :checklist_json, :text end def self.down remove_column :issue_templates, :checklist_json end end ================================================ FILE: db/migrate/20160828190000_add_checklist_json_to_global_issue_templates.rb ================================================ class AddChecklistJsonToGlobalIssueTemplates < ActiveRecord::Migration[4.2] def self.up add_column :global_issue_templates, :checklist_json, :text end def self.down remove_column :global_issue_templates, :checklist_json end end ================================================ FILE: db/migrate/20160829001500_change_issue_template_enabled_column.rb ================================================ class ChangeIssueTemplateEnabledColumn < ActiveRecord::Migration[4.2] def self.up change_column :issue_templates, :enabled, :boolean, default: false, null: false end def self.down change_column :issue_templates, :enabled, :boolean end end ================================================ FILE: db/migrate/20160829001530_change_global_issue_template_enabled_column.rb ================================================ class ChangeGlobalIssueTemplateEnabledColumn < ActiveRecord::Migration[4.2] def self.up change_column :global_issue_templates, :enabled, :boolean, default: false, null: false end def self.down change_column :global_issue_templates, :enabled, :boolean end end ================================================ FILE: db/migrate/20170317082100_add_is_default_to_global_issue_templates.rb ================================================ class AddIsDefaultToGlobalIssueTemplates < ActiveRecord::Migration[4.2] def self.up add_column :global_issue_templates, :is_default, :boolean, default: false, null: false end def self.down remove_column :global_issue_templates, :is_default end end ================================================ FILE: db/migrate/20181104065200_add_unique_key_to_global_issue_templates_projects.rb ================================================ class AddUniqueKeyToGlobalIssueTemplatesProjects < ActiveRecord::Migration[4.2] def self.up add_index :global_issue_templates_projects, [:project_id, :global_issue_template_id], unique: true, name: 'projects_global_issue_templates' end def self.down remove_index :global_issue_templates_projects, name: 'projects_global_issue_templates' end end ================================================ FILE: db/migrate/20190303082102_create_note_templates.rb ================================================ class CreateNoteTemplates < ActiveRecord::Migration[5.1] def up create_table :note_templates do |t| t.string :name t.string :description t.string :memo t.integer :project_id t.integer :tracker_id t.integer :author_id t.boolean :enabled t.integer :position t.timestamps end add_index :note_templates, :author_id add_index :note_templates, :project_id add_index :note_templates, :tracker_id add_index :note_templates, :enabled end def down remove_index :note_templates, :author_id remove_index :note_templates, :project_id remove_index :note_templates, :tracker_id remove_index :note_templates, :enabled drop_table :note_templates end end ================================================ FILE: db/migrate/20190714171020_create_note_visible_roles.rb ================================================ # frozen_string_literal: true class CreateNoteVisibleRoles < ActiveRecord::Migration[5.1] def up create_table :note_visible_roles do |t| t.integer :note_template_id t.integer :role_id t.timestamps end add_index :note_visible_roles, :note_template_id add_index :note_visible_roles, :role_id end def down remove_index :note_visible_roles, :role_id remove_index :note_visible_roles, :note_template_id drop_table :note_visible_roles end end ================================================ FILE: db/migrate/20190714211530_add_visibility_to_note_templates.rb ================================================ # frozen_string_literal: true class AddVisibilityToNoteTemplates < ActiveRecord::Migration[5.1] def self.up add_column :note_templates, :visibility, :integer, default: 2 end def self.down remove_column :note_templates, :visibility end end ================================================ FILE: db/migrate/20200101204020_add_related_link_to_issue_templates.rb ================================================ # frozen_string_literal: true class AddRelatedLinkToIssueTemplates < ActiveRecord::Migration[5.2] def self.up add_column :issue_templates, :related_link, :text end def self.down remove_column :issue_templates, :related_link end end ================================================ FILE: db/migrate/20200101204220_add_related_link_to_global_issue_templates.rb ================================================ # frozen_string_literal: true class AddRelatedLinkToGlobalIssueTemplates < ActiveRecord::Migration[5.2] def self.up add_column :global_issue_templates, :related_link, :text end def self.down remove_column :global_issue_templates, :related_link end end ================================================ FILE: db/migrate/20200102204815_add_link_title_to_issue_templates.rb ================================================ # frozen_string_literal: true class AddLinkTitleToIssueTemplates < ActiveRecord::Migration[5.2] def self.up add_column :issue_templates, :link_title, :text end def self.down remove_column :issue_templates, :link_title end end ================================================ FILE: db/migrate/20200102205044_add_link_title_to_global_issue_templates.rb ================================================ # frozen_string_literal: true class AddLinkTitleToGlobalIssueTemplates < ActiveRecord::Migration[5.2] def self.up add_column :global_issue_templates, :link_title, :text end def self.down remove_column :global_issue_templates, :link_title end end ================================================ FILE: db/migrate/20200103213630_add_builtin_fields_json_to_issue_templates.rb ================================================ class AddBuiltinFieldsJsonToIssueTemplates < ActiveRecord::Migration[5.2] def self.up add_column :issue_templates, :builtin_fields_json, :text end def self.down remove_column :issue_templates, :builtin_fields_json end end ================================================ FILE: db/migrate/20200115073600_add_builtin_fields_json_to_global_issue_templates.rb ================================================ class AddBuiltinFieldsJsonToGlobalIssueTemplates < ActiveRecord::Migration[5.2] def self.up add_column :global_issue_templates, :builtin_fields_json, :text end def self.down remove_column :global_issue_templates, :builtin_fields_json end end ================================================ FILE: db/migrate/20200314132500_change_column_note_template_description.rb ================================================ class ChangeColumnNoteTemplateDescription < ActiveRecord::Migration[5.2] def self.up change_column :note_templates, :description, :text end def down change_column :note_templates, :description, :string end end ================================================ FILE: db/migrate/20200405115700_create_global_note_templates.rb ================================================ # frozen_string_literal: true class CreateGlobalNoteTemplates < ActiveRecord::Migration[5.1] def up create_table :global_note_templates do |t| t.string :name t.text :description t.string :memo t.integer :tracker_id t.integer :author_id t.boolean :enabled t.integer :position t.integer :visibility, default: 2 t.timestamps end add_index :global_note_templates, :author_id add_index :global_note_templates, :tracker_id add_index :global_note_templates, :enabled end def down remove_index :global_note_templates, :author_id remove_index :global_note_templates, :tracker_id remove_index :global_note_templates, :enabled drop_table :global_note_templates end end ================================================ FILE: db/migrate/20200405120700_create_global_note_visible_roles.rb ================================================ # frozen_string_literal: true class CreateGlobalNoteVisibleRoles < ActiveRecord::Migration[5.1] def up create_table :global_note_visible_roles do |t| t.integer :global_note_template_id t.integer :role_id t.timestamps end add_index :global_note_visible_roles, :global_note_template_id add_index :global_note_visible_roles, :role_id end def down remove_index :global_note_visible_roles, :role_id remove_index :global_note_visible_roles, :global_note_template_id drop_table :global_note_visible_roles end end ================================================ FILE: db/migrate/20200418114157_create_join_table_global_note_template_project.rb ================================================ class CreateJoinTableGlobalNoteTemplateProject < ActiveRecord::Migration[5.2] def change create_join_table :global_note_templates, :projects, table_name: :global_note_template_projects do |t| # t.index [:global_note_template_id, :project_id] # t.index [:project_id, :global_note_template_id] end end end ================================================ FILE: docker-compose.yml ================================================ version: '3.2' services: # start service for redmine with plugin # 1. $ docker-compose build --force-rm --no-cache # 2. $ docker-compose up -d web # # web: build: context: . image: redmine_sqlite3 container_name: redmine_sqlite3 command: > bash -c "bundle && bundle exec rake db:migrate && bundle exec rake redmine:plugins:migrate && bundle exec rake generate_secret_token && bundle exec rails s -p 3000 -b '0.0.0.0'" environment: RAILS_ENV: development volumes: - .:/tmp/redmine/plugins/redmine_issue_templates - ./.data:/tmp/data ports: - "3000:3000" mysql: image: mysql environment: MYSQL_ROOT_PASSWORD: pasword ports: - "3306:3306" ================================================ FILE: goodcheck.yml ================================================ rules: - id: akiko.redmine.user_password pattern: 'FactoryBot.create(:user, :password_same_login, login:' message: | FactoryBotで trait: ":password_same_login" を使ってログイン名とパスワードを同じユーザを作ってテストする場合は、ログイン名( = パスワード)の長さは8以上になるようにして下さい。 "admin" や "manager" では8文字未満のため失敗します。(Status GreenでもCircleCIのログも確認してね!) glob: - "**/*_spec.rb" - id: akiko.redmine.spec.FactoryBotは省略しない pattern: " create(:" message: | FactoryBotを使う場合は、FactorryBot.create(:symbol, .... ) のようにFactoryBotを省略しないで下さい。 慣れていないと、ActiveRecordのcreate() メソッドと混乱しやすいので。 glob: - "**/*_spec.rb" ================================================ FILE: init.rb ================================================ # frozen_string_literal: true # Redmine Issue Template Plugin # # This is a plugin for Redmine to generate and use issue templates # for each project to assist issue creation. # Created by Akiko Takano. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'redmine' require 'issue_templates/issues_hook' require 'issue_templates/journals_hook' # NOTE: Keep error message for a while to support Redmine3.x users. def issue_template_version_message(original_message = nil) <<-"USAGE" ========================== #{original_message} If you use Redmine3.x, please use Redmine Issue Templates version 0.2.x or clone via 'v0.2.x-support-Redmine3' branch. You can download older version from here: https://github.com/akiko-pusu/redmine_issue_templates/releases ========================== USAGE end def template_menu_allowed? proc { |p| User.current.allowed_to?({ controller: 'issue_templates', action: 'show' }, p) } end Redmine::Plugin.register :redmine_issue_templates do begin name 'Redmine Issue Templates plugin' author 'Akiko Takano' description 'Plugin to generate and use issue templates for each project to assist issue creation.' version '1.1.0' author_url 'http://twitter.com/akiko_pusu' requires_redmine version_or_higher: '4.0' url 'https://github.com/akiko-pusu/redmine_issue_templates' settings partial: 'settings/redmine_issue_templates', default: { apply_global_template_to_all_projects: 'false', apply_template_when_edit_issue: 'false', enable_builtin_fields: 'false' } menu :admin_menu, :redmine_issue_templates, { controller: 'global_issue_templates', action: 'index' }, caption: :global_issue_templates, html: { class: 'icon icon-global_issue_templates' } menu :project_menu, :issue_templates, { controller: 'issue_templates', action: 'index' }, caption: :issue_templates, param: :project_id, after: :settings, if: template_menu_allowed? project_module :issue_templates do permission :edit_issue_templates, issue_templates: %i[new create edit update destroy move], note_templates: %i[new create edit update destroy move] permission :show_issue_templates, issue_templates: %i[index show load set_pulldown list_templates orphaned_templates], note_templates: %i[index show load list_templates] permission :manage_issue_templates, { issue_templates_settings: %i[index edit] }, require: :member end rescue ::Redmine::PluginRequirementError => e raise ::Redmine::PluginRequirementError.new(issue_template_version_message(e.message)) # rubocop:disable Style/RaiseArgs end end ================================================ FILE: lang/en.yml ================================================ # English strings go here my_label: "My label" ================================================ FILE: lib/issue_templates/issues_hook.rb ================================================ # To change this template, choose Tools | Templates # and open the template in the editor. module IssueTemplates class IssuesHook < Redmine::Hook::ViewListener include IssuesHelper CONTROLLERS = %( 'IssuesController' 'IssueTemplatesController' 'ProjectsController' 'IssueTemplatesSettingsController' 'GlobalIssueTemplatesController' 'SettingsController' 'NoteTemplatesController' 'GlobalNoteTemplatesController' ).freeze ACTIONS = %('new' 'update_form' 'create', 'show').freeze def view_layouts_base_html_head(context = {}) o = stylesheet_link_tag('issue_templates', plugin: 'redmine_issue_templates') o << javascript_include_tag('issue_templates', plugin: 'redmine_issue_templates') if need_template_js?(context[:controller]) o end def view_issues_form_details_top(context = {}) issue = context[:issue] parameters = context[:request].parameters return if existing_issue?(issue) return if copied_issue?(parameters) project = context[:project] project_id = issue.project_id.present? ? issue.project_id : project.id return unless create_action?(parameters[:action]) && project_id.present? context[:controller].send( :render_to_string, partial: 'issue_templates/issue_select_form', locals: locals_params(issue, project_id, parameters[:form_update_triggered_by]) ) end render_on :view_issues_sidebar_planning_bottom, partial: 'issue_templates/issue_template_link' private def existing_issue?(issue) return false if apply_template_when_edit_issue? issue.id.present? || issue.tracker_id.blank? end def copied_issue?(parameters) return false if apply_template_when_edit_issue? copy_from = parameters[:copy_from] copy_from.present? end def create_action?(action) return true if apply_template_when_edit_issue? ACTIONS.include?(action) end def setting(project_id) IssueTemplateSetting.find_or_create(project_id) end def need_template_js?(controller) CONTROLLERS.include?(controller.class.name) end def plugin_setting Setting.plugin_redmine_issue_templates end def apply_all_projects? plugin_setting['apply_global_template_to_all_projects'].to_s == 'true' end def apply_template_when_edit_issue? plugin_setting['apply_template_when_edit_issue'].to_s == 'true' end def locals_params(issue, project_id, is_triggered_by) { setting: setting(project_id), issue: issue, is_triggered_by: is_triggered_by, project_id: project_id, pulldown_url: pulldown_url(issue, project_id, is_triggered_by) } end def pulldown_url(issue, project_id, is_triggered_by) pulldown_url = if issue.try(:id).present? url_for(controller: 'issue_templates', action: 'set_pulldown', project_id: project_id, is_triggered_by: is_triggered_by, is_update_issue: issue.try(:id).present?) else url_for(controller: 'issue_templates', action: 'set_pulldown', project_id: project_id, is_triggered_by: is_triggered_by) end pulldown_url end end end ================================================ FILE: lib/issue_templates/journals_hook.rb ================================================ # frozen_string_literal: true # To change this template, choose Tools | Templates # and open the template in the editor. module IssueTemplates class JournalsHook < Redmine::Hook::ViewListener def view_journals_notes_form_after_notes(context = {}) journal = context[:journal] issue = journal.issue tracker_id = issue.try(:tracker_id) templates = target_templates(context, tracker_id) global_templates = global_note_templates(context, tracker_id) return if templates.empty? && global_templates.empty? context[:controller].send( :render_to_string, partial: 'issue_templates/note_form', locals: { type: 'template_edit_journal', templates: templates, issue: issue } ) end # Add journal with edit issue def view_issues_edit_notes_bottom(context = {}) issue = context[:issue] tracker_id = issue.try(:tracker_id) templates = target_templates(context, tracker_id) global_templates = global_note_templates(context, tracker_id) return if templates.empty? && global_templates.empty? context[:controller].send( :render_to_string, partial: 'issue_templates/note_form', locals: { type: 'template_issue_notes', templates: templates, issue: issue } ) end def target_templates(context, tracker_id) (tracker_id, project_id) = tracker_project_ids(context, tracker_id) NoteTemplate.visible_note_templates_condition( user_id: User.current.id, project_id: project_id, tracker_id: tracker_id ) end def global_note_templates(context, tracker_id) (tracker_id, project_id) = tracker_project_ids(context, tracker_id) GlobalNoteTemplate.visible_note_templates_condition( user_id: User.current.id, project_id: project_id, tracker_id: tracker_id ) end def tracker_project_ids(context, tracker_id) project = context[:project] project_id = project.present? ? project.id : issue.try(:project_id) [tracker_id, project_id] end end end ================================================ FILE: lib/tasks/test.rake ================================================ require 'rake/testtask' namespace :redmine_issue_templates do desc 'Run test for redmine_issue_template plugin.' task :test do |task_name| next unless ENV['RAILS_ENV'] == 'test' && task_name.name == 'redmine_issue_templates:test' end Rake::TestTask.new(:test) do |t| t.libs << 'lib' t.pattern = 'plugins/redmine_issue_templates/test/**/*_test.rb' t.verbose = false t.warning = false end desc 'Run spec for redmine_issue_template plugin' task :spec do |task_name| next unless ENV['RAILS_ENV'] == 'test' && task_name.name == 'redmine_issue_templates:spec' begin require 'rspec/core' path = 'plugins/redmine_issue_templates/spec/' options = ['-I plugins/redmine_issue_templates/spec'] options << '--format' options << 'documentation' options << path RSpec::Core::Runner.run(options) rescue LoadError => ex puts "This task should be called only for redmine issue template spec. #{ex.message}" end end end ================================================ FILE: lib/tasks/util.rake ================================================ namespace :redmine_issue_templates do desc 'Apply inhelit template setting to child projects.' task :apply_inhelit_template_to_child_projects, 'project_id' task apply_inhelit_template_to_child_projects: :environment do |_t, args| project_id = args.project_id begin IssueTemplateSetting.apply_template_to_child_projects(project_id) rescue ActiveRecord::RecordNotFound puts "IssueTemplateSetting to project specified by #{project_id} does not exist." end end desc 'Unapply inhelit template setting from child projects.' task :unapply_inhelit_template_from_child_projects, 'project_id' task unapply_inhelit_template_from_child_projects: :environment do |_t, args| project_id = args.project_id begin IssueTemplateSetting.unapply_template_from_child_projects(project_id) rescue ActiveRecord::RecordNotFound puts "IssueTemplateSetting to project specified by #{project_id} does not exist." end end end ================================================ FILE: script/circleci-setup.sh ================================================ #!/bin/sh cd /tmp/ git clone --depth 1 -b $REDMINE_BRANCH https://github.com/redmine/redmine redmine # switch target version of redmine cd /tmp/redmine cat << HERE >> config/database.yml test: adapter: mysql2 database: redmine_test host: 127.0.0.1 username: root password: "" encoding: utf8mb4 sql_mode: false HERE # move redmine source to wercker source directory echo mkdir -p /tmp/redmine/plugins/${CIRCLE_PROJECT_REPONAME} # Move Gemfile.local to Gemfile only for test mv ~/repo/Gemfile.local ~/repo/Gemfile mv ~/repo/* /tmp/redmine/plugins/${CIRCLE_PROJECT_REPONAME}/ mv ~/repo/.* /tmp/redmine/plugins/${CIRCLE_PROJECT_REPONAME}/ mv /tmp/redmine/* ~/repo/ mv /tmp/redmine/.* ~/repo/ ls -la ~/repo/ ================================================ FILE: spec/controllers/concerns/issue_templates_common_spec.rb ================================================ # frozen_string_literal: true require_relative '../../spec_helper' describe 'IssueTemplatesCommon' do before do class FakesController < ApplicationController include Concerns::IssueTemplatesCommon end allow_any_instance_of(FakesController).to receive(:action_name).and_return('fake_action') User.current = FactoryBot.build(:user) end let(:mock_controller) { FakesController.new } describe '#log_action' do subject { mock_controller.log_action } it do expect(Rails.logger).to receive(:info).with("[FakesController] fake_action called by #{User.current.name}").once subject end end end ================================================ FILE: spec/controllers/global_issue_templates_controller_spec.rb ================================================ # frozen_string_literal: true require_relative '../spec_helper' require File.expand_path(File.dirname(__FILE__) + '/../support/controller_helper') describe GlobalIssueTemplatesController, type: :controller do let(:count) { 4 } let(:tracker) { FactoryBot.create(:tracker, :with_default_status) } let(:projects) { FactoryBot.create_list(:project, count) } before do # Prevent to call User.deliver_security_notification when user is created. expect_any_instance_of(User).to receive(:deliver_security_notification).and_return(true) @request.session[:user_id] = user.id end include_context 'As admin' describe 'GET #index' do render_views before do get :index end context 'As Non Admin' do let(:is_admin) { false } include_examples 'Right response', 403 end context 'As Admin' do include_examples 'Right response', 200 end end describe 'GET #new' do render_views before do FactoryBot.create_list(:project, count) FactoryBot.create(:tracker, :with_default_status) get :new end include_examples 'Right response', 200 end describe 'POST #create' do render_views let(:create_params) do { global_issue_template: { title: 'Global Template newtitle for creation test', note: 'Global note for creation test', description: 'Global Template description for creation test', tracker_id: tracker.id, enabled: 1, author_id: user.id, project_ids: project_ids } } end let(:global_issue_template) { GlobalIssueTemplate.first } before do post :create, params: create_params end context 'POST without project ids' do let(:project_ids) { [] } include_examples 'Right response', 302 it do expect(global_issue_template.projects.count).to eq 0 end end context 'POST with check all project ids' do let(:project_ids) { projects.map(&:id) } include_examples 'Right response', 302 it do expect(global_issue_template.projects.count).to eq projects.count end end context 'POST with invalid url' do let(:project_ids) { [] } let(:create_params) do { global_issue_template: { title: 'Global Template newtitle for creation test', note: 'Global note for creation test', description: 'Global Template description for creation test', tracker_id: tracker.id, enabled: 1, author_id: user.id, project_ids: project_ids }.merge(related_link: 'bad format url') } end include_examples 'Right response', 200 it do expect(global_issue_template.present?).to be_falsy end context 'POST with valid url' do let(:project_ids) { [] } let(:create_params) do { global_issue_template: { title: 'Global Template newtitle for creation test', note: 'Global note for creation test', description: 'Global Template description for creation test', tracker_id: tracker.id, enabled: 1, author_id: user.id, project_ids: project_ids }.merge(related_link: 'http://example.com/sample/index.html') } end include_examples 'Right response', 302 it do expect(global_issue_template.present?).to be_truthy end end end end # PATCH GlobalIssueTemplatesController#edit describe 'PUT #update' do render_views let(:global_issue_template) do create(:global_issue_template_with_projects, tracker_id: tracker.id, projects_count: 3) end let(:edit_params) { { description: 'Update Test Global template', project_ids: [''] } } it 'Before update template has the default value' do expect(global_issue_template.projects.count).to eq 3 expect(global_issue_template.description).not_to eq edit_params[:description] end it 'After update number of projects should changed' do put :update, params: { id: global_issue_template.id, global_issue_template: edit_params } expect(global_issue_template.projects.count).to eq 0 end context 'PUT with builtin_fields param' do let(:update_params) do edit_params.merge(builtin_fields: builtin_fields) end before do Setting.send 'plugin_redmine_issue_templates=', 'enable_builtin_fields' => 'true' put :update, params: { id: global_issue_template.id, global_issue_template: update_params } end context 'invalid format' do let(:builtin_fields) { '12345' } include_examples 'Right response', 200 it do msg = flash[:error] expect(msg.present?).to be_truthy expect(msg).to eq 'Please enter a valid JSON fotmat string.' end end context 'PUT with valid builtin_fields param' do let(:builtin_fields) { '{ "foo": "bar" }' } include_examples 'Right response', 302 it do expect(flash[:error].present?).to be_falsy expect(global_issue_template.reload.builtin_fields_json).to eq JSON.parse(builtin_fields) end end end end end ================================================ FILE: spec/controllers/issue_templates_controller_spec.rb ================================================ # frozen_string_literal: true require_relative '../spec_helper' require File.expand_path(File.dirname(__FILE__) + '/../support/controller_helper') RSpec.configure do |c| c.include ControllerHelper end # # Shared Example # shared_examples 'Right response for GET #index', type: :controller do include_examples 'Right response', 200 end describe IssueTemplatesController do let(:count) { 4 } let(:tracker) { FactoryBot.create(:tracker, :with_default_status) } let(:project) { FactoryBot.create(:project) } include_context 'As admin' before do # Prevent to call User.deliver_security_notification when user is created. allow_any_instance_of(User).to receive(:deliver_security_notification).and_return(true) Redmine::Plugin.register(:redmine_issue_templates) do settings partial: 'settings/redmine_issue_templates', default: { 'apply_global_template_to_all_projects' => 'false' } end Setting.rest_api_enabled = '1' @request.session[:user_id] = user.id FactoryBot.create(:enabled_module, project_id: project.id) global_issue_templates = FactoryBot.create_list(:global_issue_template, count, tracker_id: tracker.id) global_issue_templates.each { |template| template.projects << project } FactoryBot.create(:issue_template, tracker_id: tracker.id, project_id: project.id) project.trackers << tracker end after(:all) do Redmine::Plugin.unregister(:redmine_issue_templates) end describe 'GET #index' do render_views before do get :index, params: { project_id: project.id } end include_examples 'Right response for GET #index' end describe 'GET #index with format.json' do render_views context 'Without auth header' do before do clear_token get :index, params: { project_id: project.id }, format: :json end include_examples 'Right response', 401 after do clear_token end end context 'With auth header' do before do auth_with_user user get :index, params: { project_id: project.id }, format: :json end include_examples 'Right response for GET #index' it { expect(response.header['Content-Type']).to match('application/json') } it { expect(JSON.parse(response.body)).to have_key('global_issue_templates') } after do clear_token end end end describe 'GET #list_templates' do context 'Plugin Setting apply_global_template_to_all_projects is not activated' do before do get :list_templates, params: { project_id: project.id, issue_tracker_id: tracker.id } end include_examples 'Right response', 200 end context 'Plugin Setting apply_global_template_to_all_projects is activated' do before do Setting.send 'plugin_redmine_issue_templates=', 'apply_global_template_to_all_projects' => 'true' get :list_templates, params: { project_id: project.id, issue_tracker_id: tracker.id } end include_examples 'Right response', 200 end end describe 'GET #list_templates with format.json' do render_views context 'Without auth header' do before do clear_token get :list_templates, params: { project_id: project.id, issue_tracker_id: tracker.id }, format: :json end include_examples 'Right response', 401 after do clear_token end end context 'With auth header' do before do auth_with_user user get :list_templates, params: { project_id: project.id, issue_tracker_id: tracker.id }, format: :json end include_examples 'Right response', 200 it { expect(response.header['Content-Type']).to match('application/json') } it { expect(JSON.parse(response.body)).to have_key('global_issue_templates') } after do clear_token end end end # Spec for copy feature. describe 'GET #new with existing template id' do let(:original_template) { IssueTemplate.first } before do auth_with_user user get :new, params: { project_id: project.id, copy_from: original_template.id } end include_examples 'Right response', 200 # # TODO: This example should be request spec. # it 'Render new form filled with copied template values' do # issue_template = assigns(:issue_template) # expect(issue_template.id).to be_nil # expect(issue_template.title).to eq "copy_of_#{original_template.title}" # end end describe 'POST #create' do render_views let(:template_params) do { title: 'Issue Template newtitle for creation test', note: 'Note for creation test', description: 'Issue Template description for creation test', tracker_id: tracker.id, enabled: 1, author_id: user.id } end let(:create_params) do { issue_template: template_params } end let(:issue_template) { IssueTemplate.last } context 'POST with valid param' do before do post :create, params: create_params.merge(project_id: project.id) end include_examples 'Right response', 302 it do expect(issue_template.project.id).to eq project.id end end context 'POST with valid builtin_fields param when enable_builtin_fields = true' do let(:builtin_fields) { '{ "foo": "bar" }' } let(:create_params) do { issue_template: template_params.merge(builtin_fields: builtin_fields) } end before do Setting.send 'plugin_redmine_issue_templates=', 'enable_builtin_fields' => 'true' post :create, params: create_params.merge(project_id: project.id) end include_examples 'Right response', 302 it do expect(issue_template.builtin_fields_json).to eq JSON.parse(builtin_fields) end end context 'POST with invalid builtin_fields param when enable_builtin_fields = falsewhen enable_builtin_fields = false' do let(:builtin_fields) { '12345' } let(:create_params) do { issue_template: template_params.merge(builtin_fields: builtin_fields) } end before do Setting.send 'plugin_redmine_issue_templates=', 'enable_builtin_fields' => 'false' post :create, params: create_params.merge(project_id: project.id) end include_examples 'Right response', 302 it do msg = flash[:error] expect(msg.present?).to be_falsy end end end describe 'PUT #update' do render_views let(:template_params) do { title: 'Issue Template updated title for update test', description: 'Issue Template description for update test', tracker_id: tracker.id, enabled: 1, author_id: user.id } end let(:issue_template) { IssueTemplate.last } before do Setting.send 'plugin_redmine_issue_templates=', 'enable_builtin_fields' => 'true' put :update, params: update_params.merge(id: issue_template.id, project_id: project.id) end context 'PUT with invalid builtin_fields param' do let(:builtin_fields) { '12345' } let(:update_params) do { issue_template: template_params.merge(builtin_fields: builtin_fields) } end include_examples 'Right response', 200 it do msg = flash[:error] expect(msg.present?).to be_truthy expect(msg).to eq 'Please enter a valid JSON fotmat string.' end end context 'PUT with valid builtin_fields param' do let(:builtin_fields) { '{ "foo": "bar" }' } let(:update_params) do { issue_template: template_params.merge(builtin_fields: builtin_fields) } end include_examples 'Right response', 302 it do expect(flash[:error].present?).to be_falsy expect(issue_template.reload.title).to eq 'Issue Template updated title for update test' end end end end ================================================ FILE: spec/controllers/settings_controller_spec.rb ================================================ # frozen_string_literal: true require_relative '../spec_helper' require File.expand_path(File.dirname(__FILE__) + '/../support/controller_helper') describe SettingsController, type: :controller do include_context 'As admin' before do Redmine::Plugin.register(:redmine_issue_templates) do settings partial: 'settings/redmine_issue_templates', default: { 'apply_global_template_to_all_projects' => 'false' } end @request.session[:user_id] = user.id end after(:all) do Redmine::Plugin.unregister(:redmine_issue_templates) end describe '#GET plugin' do render_views before do Setting.send 'plugin_redmine_issue_templates=', 'apply_global_template_to_all_projects' => 'false' get :plugin, params: { id: 'redmine_issue_templates' } end include_examples 'Right response', 200 it 'Contains right plugin setting content' do expect(response.body).to match(/id="settings_apply_global_template_to_all_projects"/im) end end describe '#POST plugin' do render_views before do post :plugin, params: { id: 'redmine_issue_templates', settings: { apply_global_template_to_all_projects: true } } end include_examples 'Right response', 302 it 'Setting value is changed true' do settings = Setting.plugin_redmine_issue_templates expect(settings['apply_global_template_to_all_projects']).to be_truthy end end end ================================================ FILE: spec/factories/enabled_modules.rb ================================================ FactoryBot.define do factory :enabled_module do project_id { 1 } name { 'issue_templates' } end end ================================================ FILE: spec/factories/global_issue_templates.rb ================================================ FactoryBot.define do factory :global_issue_template do |t| association :tracker t.sequence(:title) { |n| "global_template-title: #{n}" } t.sequence(:description) { |n| "global_template-description: #{n}" } t.sequence(:note) { |n| "global_template-note: #{n}" } t.sequence(:position) { |n| n } t.enabled { true } t.is_default { false } t.author_id { 1 } factory :global_issue_template_with_projects do transient do projects_count { 5 } end after(:create) do |global_issue_template, evaluator| global_issue_template.projects = create_list(:project, evaluator.projects_count) end end end end ================================================ FILE: spec/factories/global_note_templates.rb ================================================ FactoryBot.define do factory :global_note_template do |t| association :tracker t.sequence(:name) { |n| "global_template-title: #{n}" } t.sequence(:description) { |n| "global_template-description: #{n}" } t.sequence(:memo) { |n| "global_template-note: #{n}" } t.sequence(:position) { |n| n } t.enabled { true } t.author_id { 1 } t.visibility { 2 } # open factory :global_note_template_with_projects do transient do projects_count { 5 } end after(:create) do |global_note_template, evaluator| global_note_template.projects = create_list(:project, evaluator.projects_count) end end end end ================================================ FILE: spec/factories/issue_statuses.rb ================================================ FactoryBot.define do factory :issue_status do sequence(:name) { |n| "status-name: #{n}" } sequence(:position) { |n| n } is_closed { false } end end ================================================ FILE: spec/factories/issue_template_settings.rb ================================================ FactoryBot.define do factory :issue_template_setting do |t| association :project t.sequence(:help_message) { |n| "Project-#{n}: temlpate help" } t.enabled { true } t.inherit_templates { false } t.should_replaced { false } end end ================================================ FILE: spec/factories/issue_templates.rb ================================================ FactoryBot.define do factory :issue_template do |t| association :project association :tracker t.sequence(:title) { |n| "template-title: #{n}" } t.sequence(:issue_title) { |n| "template-issue_title: #{n}" } t.sequence(:description) { |n| "template-description: #{n}" } t.sequence(:note) { |n| "template-note: #{n}" } t.sequence(:position) { |n| n } t.enabled { true } t.enabled_sharing { true } t.author_id { 1 } end end ================================================ FILE: spec/factories/projects.rb ================================================ FactoryBot.define do factory :project do sequence(:name) { |n| "project-name: #{n}" } sequence(:description) { |n| "project-description: #{n}" } sequence(:identifier) { |n| "project-#{n}" } homepage { 'http://ecookbook.somenet.foo/' } is_public { true } trait :with_enabled_modules do after(:build) do |tracker| status = FactoryBot.create(:issue_status) tracker.default_status_id = status.id end end factory :project_with_enabled_modules do after(:create) do |project, _evaluator| FactoryBot.create(:enabled_module, project_id: project.id) end end end end ================================================ FILE: spec/factories/role.rb ================================================ # frozen_string_literal: true FactoryBot.define do factory :role do sequence(:name) { |n| "Developer: #{n}" } builtin { 0 } issues_visibility { 'default' } users_visibility { 'all' } position { 1 } permissions { %i[ edit_project manage_members manage_versions manage_categories view_issues add_issues edit_issues copy_issues manage_issue_relations manage_subtasks add_issue_notes delete_issues view_issue_watchers save_queries view_gantt view_calendar log_time view_time_entries edit_own_time_entries manage_news comment_news view_documents add_documents edit_documents delete_documents view_wiki_pages view_wiki_edits edit_wiki_pages protect_wiki_pages delete_wiki_pages add_messages edit_own_messages delete_own_messages manage_boards view_files manage_files browse_repository view_changesets ] } trait :manager_role do name { 'Manager' } issues_visibility { 'all' } users_visibility { 'all' } permissions { %i[ add_project edit_project close_project select_project_modules manage_members manage_versions manage_categories view_issues add_issues edit_issues manage_issue_relations manage_subtasks add_issue_notes delete_issues view_issue_watchers set_issues_private set_notes_private view_private_notes delete_issue_watchers manage_public_queries save_queries view_gantt view_calendar log_time view_time_entries edit_own_time_entries delete_time_entries manage_news comment_news view_documents add_documents edit_documents delete_documents view_wiki_pages view_wiki_edits edit_wiki_pages delete_wiki_pages_attachments protect_wiki_pages delete_wiki_pages rename_wiki_pages add_messages edit_own_messages delete_own_messages manage_boards view_files manage_files browse_repository manage_repository view_changesets manage_related_issues manage_project_activities ] } end end end ================================================ FILE: spec/factories/trackers.rb ================================================ FactoryBot.define do factory :tracker do sequence(:name) { |n| "tracker-name: #{n}" } sequence(:position) { |n| n } default_status_id { 1 } trait :with_default_status do after(:build) do |tracker| status = FactoryBot.create(:issue_status) tracker.default_status_id = status.id end end end end ================================================ FILE: spec/factories/users.rb ================================================ # frozen_string_literal: true FactoryBot.define do factory :user do |u| # sequence -> exp. :login -> user1, user2..... u.sequence(:login) { |n| "user#{n}" } u.sequence(:firstname) { |n| "User#{n}" } u.sequence(:lastname) { |n| "Test#{n}" } u.sequence(:mail) { |n| "user#{n}@badge.example.com" } u.language { 'en' } # password = foo u.hashed_password { '8f659c8d7c072f189374edacfa90d6abbc26d8ed' } u.salt { '7599f9963ec07b5a3b55b354407120c0' } # login and password is the same. (Note: login length should be longer than 7.) trait :password_same_login do after(:create) do |user| user.password = user.login user.auth_source_id = nil user.save end end trait :as_group do type { 'Group' } lastname { "Group#{n}" } end end end ================================================ FILE: spec/features/admin_spec.rb ================================================ require_relative '../spec_helper' require_relative '../rails_helper' require_relative '../support/login_helper' RSpec.configure do |c| c.include LoginHelper end feature 'PluginSetting to apply Global issue templates to all the projects', js: true do given(:user) { FactoryBot.create(:user, :password_same_login, login: 'admin', language: 'en') } background(:all) do Redmine::Plugin.register(:redmine_issue_templates) do settings partial: 'settings/redmine_issue_templates', default: { 'apply_global_template_to_all_projects' => 'false' } end end background do # Prevent to call User.deliver_security_notification when user is created. allow_any_instance_of(User).to receive(:deliver_security_notification).and_return(true) Setting.send 'plugin_redmine_issue_templates=', 'apply_global_template_to_all_projects' => 'false' user.update_attribute(:admin, true) log_user(user.login, user.login) visit '/settings/plugin/redmine_issue_templates' end scenario 'Settings "apply_global_template_to_all_projects" is displayed.' do expect(page).to have_content('Apply Global issue templates to all the projects.') expect(page).to have_selector('#settings_apply_global_template_to_all_projects') end scenario 'Activate "apply_global_template_to_all_projects".' do expect(page).to have_unchecked_field('settings_apply_global_template_to_all_projects') check 'settings_apply_global_template_to_all_projects' click_on 'Apply' expect(page).to have_selector('#settings_apply_global_template_to_all_projects') expect(page).to have_checked_field('settings_apply_global_template_to_all_projects') end end ================================================ FILE: spec/features/drag_and_drop_spec.rb ================================================ # frozen_string_literal: true require File.expand_path(File.dirname(__FILE__) + '/../rails_helper') require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') require File.expand_path(File.dirname(__FILE__) + '/../support/login_helper') feature 'Templates can be reorder via drag and drop', js: true do include LoginHelper given(:user) { FactoryBot.create(:user, :password_same_login, login: 'manager', language: 'en', admin: false) } given(:project) { create(:project_with_enabled_modules) } given(:tracker) { FactoryBot.create(:tracker, :with_default_status) } given(:role) { FactoryBot.create(:role, :manager_role) } given(:table) { page.find('table.list.issues.table-sortable:first-of-type > tbody') } background do FactoryBot.create_list(:issue_template, 4, project_id: project.id, tracker_id: tracker.id) project.trackers << tracker assign_template_priv(role, add_permission: :show_issue_templates) assign_template_priv(role, add_permission: :edit_issue_templates) member = Member.new(project: project, user_id: user.id) member.member_roles << MemberRole.new(role: role) member.save end scenario 'Can drag and drop' do visit_template_list(user) first_target = table.find('tr:nth-child(1) > td.buttons > span') last_target = table.find('tr:nth-child(4) > td.buttons > span') # change id: 1, 2, 3, 4 to 4, 1, 2, 3 expect do first_target.drag_to(last_target) sleep 0.5 end.to change { IssueTemplate.order(:id).pluck(:position).to_a }.from([1, 2, 3, 4]).to([4, 1, 2, 3]) # change id: 4, 1, 2, 3 to 3, 1, 4, 2 second_target = table.find('tr:nth-child(2) > td.buttons > span') last_target = table.find('tr:nth-child(4) > td.buttons > span') expect do second_target.drag_to(last_target) sleep 0.5 end.to change { IssueTemplate.order(:id).pluck(:position).to_a }.from([4, 1, 2, 3]).to([3, 1, 4, 2]) end private def visit_template_list(user) # TODO: If does not user update, authentication is failed. This is workaround. user.update_attribute(:admin, false) log_user(user.login, user.password) visit "/projects/#{project.identifier}/issue_templates" end def offset_array(from, to) from_location = element_position(from) to_location = element_position(to) [to_location[0] - from_location[0], to_location[1] - from_location[1]] end def element_position(element) Capybara.evaluate_script <<-RUBY function() { var element = document.evaluate('#{element.path}', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; var rect = element.getBoundingClientRect(); return [rect.left, rect.top]; }(); RUBY end end ================================================ FILE: spec/features/issue_template_popup_spec.rb ================================================ # frozen_string_literal: true require_relative '../spec_helper' require_relative '../rails_helper' require_relative '../support/login_helper' RSpec.configure do |c| c.include LoginHelper end feature 'Confirm dialog before overwrite description', js: true do background(:all) do Redmine::Plugin.register(:redmine_issue_templates) do settings partial: 'settings/redmine_issue_templates', default: { 'apply_global_template_to_all_projects' => 'false' } end end given(:user) { FactoryBot.create(:user, :password_same_login, login: 'manager', language: 'en', admin: false) } given(:project) { create(:project_with_enabled_modules) } given(:tracker) { FactoryBot.create(:tracker, :with_default_status) } given(:role) { FactoryBot.create(:role, :manager_role) } given(:issue_priority) { FactoryBot.create(:priority) } given(:first_target) { page.find('#issue_template > optgroup > option:nth-child(1)') } given(:second_target) { page.find('#issue_template > optgroup > option:nth-child(2)') } given(:issue_subject) { page.find('#issue_subject') } given(:issue_description) { page.find('#issue_description') } given(:first_template) { IssueTemplate.first } given(:second_template) { IssueTemplate.second } given(:related_link) { page.find('#issue_template_related_link') } background do FactoryBot.create_list(:issue_template, 2, project_id: project.id, tracker_id: tracker.id, related_link: 'http://example.com/template/wiki#usage') project.trackers << tracker assign_template_priv(role, add_permission: :show_issue_templates) member = Member.new(project: project, user_id: user.id) member.member_roles << MemberRole.new(role: role) member.save end scenario 'Template pulldown is shown when new issue.' do visit_new_issue(user) expect(page).to have_selector('div#template_area select#issue_template') expect(issue_description.value).to eq '' expect(issue_subject.value).to eq '' end scenario 'Select template and content is updated' do visit_new_issue(user) first_target.select_option wait_for_ajax expect(issue_description.value).not_to eq '' expect(issue_description.value).to eq first_template.description expect(issue_subject.value).to eq first_template.issue_title expect(related_link.text).to eq 'Related Link' expect(related_link['href']).to eq 'http://example.com/template/wiki#usage' end context 'Has default template' do before do first_template.is_default = true first_template.save end context 'Overwite option is not activated' do scenario 'Template pulldown is shown when new issue and default is loaded.' do visit_new_issue(user) wait_for_ajax expect(page).to have_selector('div#template_area select#issue_template') expect(issue_description.value).not_to eq '' expect(issue_subject.value).not_to eq '' expect(issue_description.value).to eq first_template.description expect(issue_subject.value).to eq first_template.issue_title end scenario 'Text appended.' do visit_new_issue(user) wait_for_ajax expect(page).to have_selector('div#template_area select#issue_template') second_target.select_option wait_for_ajax expect(page).not_to have_selector('#issue_template_confirm_to_replace_dialog') expect(issue_description.value).not_to eq first_template.description expect(issue_subject.value).not_to eq first_template.issue_title expect(issue_subject.value).to eq "#{first_template.issue_title} #{second_template.issue_title}" expect(issue_description.value).to eq "#{first_template.description}\n\n#{second_template.description}" end context 'Overwite option is activated' do before do setting = IssueTemplateSetting.find_or_create(project.id) setting.update_attribute(:should_replaced, true) end scenario 'Conform window apperead.' do visit_new_issue(user) expect(page).to have_selector('div#template_area select#issue_template') second_target.select_option wait_for_ajax expect(page).to have_selector('#issue_template_confirm_to_replace_dialog') expect(issue_subject.value).not_to eq "#{first_template.issue_title} #{second_template.issue_title}" expect(issue_description.value).not_to eq "#{first_template.description}\n\n#{second_template.description}" end scenario 'Conform window not apperead with using cookie.' do visit_new_issue(user) # set cookie page.driver.browser.manage.add_cookie(name: 'issue_template_confirm_to_replace_hide_dialog', value: '1') expect(page).to have_selector('div#template_area select#issue_template') second_target.select_option wait_for_ajax expect(page).not_to have_selector('#issue_template_confirm_to_replace_dialog') end end end end private def visit_new_issue(user) # TODO: If does not user update, authentication is failed. This is workaround. user.update_attribute(:admin, false) log_user(user.login, user.password) visit "/projects/#{project.identifier}/issues/new" end end ================================================ FILE: spec/features/issue_template_spec.rb ================================================ # frozen_string_literal: true require_relative '../spec_helper' require_relative '../rails_helper' require_relative '../support/login_helper' RSpec.configure do |c| c.include LoginHelper end feature 'IssueTemplate', js: true do # # TODO: Change not to use Redmine's fixture but to use Factory... # fixtures :projects, :users, :roles, :members, :member_roles, :issues, :issue_statuses, :trackers, :projects_trackers, :enabled_modules, :enumerations given(:role) { Role.find(1) } after do page.execute_script 'window.close();' end feature 'Access Redmine top page', js: true do context 'When anonymous ' do scenario 'Link to Global issue template is not displayed.' do visit '/admin' expect(page).not_to have_selector('#admin-menu > ul > li > a.redmine-issue-templates') end end context 'When Administrator' do background do log_user('admin', 'admin') visit '/admin' end scenario 'Link to Global issue template is displayed.' do expect(page).to have_selector('#admin-menu > ul > li > a.redmine-issue-templates') end end end feature 'view fook for issues_sidebar' do given(:issue_template) { FactoryBot.create(:issue_template) } given!(:enabled_module) { FactoryBot.create(:enabled_module) } context 'When user has no priv to use issue template' do background do assign_template_priv(role, remove_permission: :show_issue_templates) log_user('jsmith', 'jsmith') visit '/projects/ecookbook/issues' end scenario 'Link to issue template list is not displayed.' do expect(page).not_to have_selector('h3', text: I18n.t('issue_template')) end end context 'When user has priv to use issue template' do background do assign_template_priv(role, add_permission: :show_issue_templates) log_user('jsmith', 'jsmith') visit '/projects/ecookbook/issues' end scenario 'Link to issue template list is displayed.' do expect(page).to have_selector('h3', text: I18n.t('issue_template')) end end end feature 'create template' do given!(:enabled_module) { FactoryBot.create(:enabled_module) } given(:issue_template_title) { page.find('#issue_template_title') } given(:issue_template_description) { page.find('#issue_template_description') } context 'When user has priv to issue template' do given(:create_button) { page.find('#issue_template-form > input[type="submit"]') } given(:error_message) { page.find('#errorExplanation') } background do assign_template_priv(role, add_permission: :edit_issue_templates) log_user('jsmith', 'jsmith') visit '/projects/ecookbook/issue_templates/new' issue_template_title.set('') issue_template_description.set('Test for issue template description') create_button.click sleep(0.2) end scenario 'create template failed' do expect(error_message).to have_content('Title cannot be blank') end end # enable buildin-fields context 'Setting "enable_builtin_fields" is true' do background do # enable_builtin_fields Setting.send 'plugin_redmine_issue_templates=', 'enable_builtin_fields' => 'true' assign_template_priv(role, add_permission: :edit_issue_templates) log_user('jsmith', 'jsmith') visit '/projects/ecookbook/issue_templates/new' end scenario 'form for builtin_fields are shown' do select 'Bug', from: 'issue_template[tracker_id]' wait_for_ajax expect(page).to have_selector('div#json_generator') expect(page).to have_selector('select#field_selector') select 'Priority', from: 'field_selector' expect(page).to have_select('Value', options: IssuePriority.active.pluck(:name)) select 'Watcher', from: 'field_selector' expect(page).to have_select('Value', options: ['Dave Lopper :3', 'John Smith :2']) end end end feature 'Template feature at new issue screen' do given!(:issue_templates) do FactoryBot.create_list(:issue_template, 2, project_id: 1, tracker_id: 1) end given!(:named_template) do FactoryBot.create(:issue_template, project_id: 1, tracker_id: 1, title: 'Sample Title for rspec', description: 'Sample description for rspec') end given!(:enabled_module) { FactoryBot.create(:enabled_module) } background do assign_template_priv(role, add_permission: :show_issue_templates) log_user('jsmith', 'jsmith') visit '/projects/ecookbook/issues/new' end scenario 'Template filter is enabled.' do expect(page).to have_selector('div#template_area select#issue_template') end scenario 'Template pulldown is enabled.' do expect(page).to have_selector('a#link_template_dialog') end context 'Click Template filter popup' do given(:table) { page.find('div#filtered_templates_list table') } given(:titlebar) { page.find('#issue_template_dialog_title') } background do page.find('#link_template_dialog').click sleep(0.2) end scenario 'Template filter popup has template list' do expect(table).to have_selector('tbody tr', count: 3) end scenario 'popup Template filter' do expect(titlebar).to have_content('Issue template: Bug') end context 'Template filtered' do given(:table) { page.find('div#filtered_templates_list table') } given(:input) { page.find('#template_search_filter') } background do input.set('Sample Title for rspec') end scenario 'Filtered and should have only one template' do expect(table).to have_selector('tbody tr', count: 1) end scenario 'Click filtered link and applied template' do table.find('tbody > tr > td:nth-child(5) > i').click sleep(0.2) description = page.find('#issue_description') expect(description.value).to match 'Sample description for rspec' end end end context 'have subproject' do background do template_setting = IssueTemplateSetting.find_or_create(1) template_setting.inherit_templates = true sub_project = Project.find(3) sub_project.inherit_members = true sub_project.enabled_modules << EnabledModule.new(name: 'issue_templates') sub_project.save FactoryBot.create(:issue_template, project_id: 3, tracker_id: 1, title: 'template for subproject') end scenario 'Select sub project then template for subproject is shown' do sub_project = page.find('#issue_project_id > option[value="3"]') expect(page).to have_selector('#issue_template > optgroup > option', count: 3) template_option = page.find('#issue_template > optgroup > option:nth-child(1)') expect(template_option.text).to eq issue_templates.first.title sub_project.select_option wait_for_ajax template_option = page.find('#issue_template > optgroup > option:nth-child(1)') expect(template_option.text).to eq 'template for subproject' end end end feature 'Prevent to append the same template' do given(:expected_title) { 'Sample Title for rspec' } given(:expected_description) { 'Sample description for rspec' } given!(:named_template) do FactoryBot.create(:issue_template, project_id: 1, tracker_id: 1, title: 'bug template', issue_title: expected_title, description: expected_description) end given!(:issue_template_setting) do FactoryBot.create(:issue_template_setting, project_id: 1, should_replaced: false) end given!(:enabled_module) { FactoryBot.create(:enabled_module) } given(:issue_description) { page.find('#issue_description') } given(:issue_subject) { page.find('#issue_subject') } given(:table) { page.find('div#filtered_templates_list table') } # visible template dialog given(:template_dialog) { page.find('#issue_template_dialog', visible: false) } background do assign_template_priv(role, add_permission: :show_issue_templates) log_user('jsmith', 'jsmith') visit '/projects/ecookbook/issues/new' end context 'Issue has the same title and description with selected template' do background do issue_subject.set(expected_title) issue_description.set(expected_description) page.find('#link_template_dialog').click sleep(0.2) table.find('tbody > tr > td:nth-child(5) > i').click sleep(0.2) end scenario 'Title and Description should not be modified' do expect(issue_description.value).to eq expected_description expect(issue_subject.value).to eq expected_title # dialog should be closed expect(template_dialog).not_to be_visible end end context 'Issue has different title and description with selected template' do background do issue_subject.set('different subject') issue_description.set('different description') page.find('#link_template_dialog').click sleep(0.2) table.find('tbody > tr > td:nth-child(5) > i').click sleep(0.2) end scenario 'Title and Description should be appended text' do expect(issue_description.value).to eq "different description\n\n#{expected_description}" expect(issue_subject.value).to eq "different subject #{expected_title}" # dialog should be closed expect(template_dialog).not_to be_visible end end end feature 'Enabled to revert just after template applied' do given(:issue_description) { page.find('#issue_description') } given(:issue_subject) { page.find('#issue_subject') } given(:expected_title) { 'Sample Title for rspec' } given(:expected_description) { 'Sample description for rspec' } given!(:named_template) do FactoryBot.create(:issue_template, project_id: 1, tracker_id: 1, title: 'Sample Title for rspec', issue_title: 'Sample Title for rspec', description: 'Sample description for rspec') end given!(:enabled_module) { FactoryBot.create(:enabled_module) } background do assign_template_priv(role, add_permission: :show_issue_templates) log_user('jsmith', 'jsmith') visit '/projects/ecookbook/issues/new' issue_subject.set('Test for revert subject') issue_description.set('Test for revert description') select expected_title, from: 'issue_template' sleep(0.2) end scenario 'Title and Description should be appended text' do expect(issue_description.value).to eq "Test for revert description\n\n#{expected_description}" expect(issue_subject.value).to eq "Test for revert subject #{expected_title}" end scenario 'Click Revert and reverted applied template' do page.find('#revert_template').click expect(issue_description.value).to eq 'Test for revert description' expect(issue_subject.value).to eq 'Test for revert subject' end end # builtin fields test feature 'Can apply value to the builtin field' do given(:priority_name) { IssuePriority.active.sample.name } given(:builtin_fields_json_value) do { "issue_priority_id": priority_name, "issue_estimated_hours": 5 } end given(:expected_title) { 'Sample Title for rspec' } given!(:enabled_module) { FactoryBot.create(:enabled_module) } given!(:issue_templates) do FactoryBot.create(:issue_template, title: expected_title, project_id: 1, tracker_id: 1, builtin_fields_json: builtin_fields_json_value) end background do # enable_builtin_fields Setting.send 'plugin_redmine_issue_templates=', 'enable_builtin_fields' => 'true' assign_template_priv(role, add_permission: :show_issue_templates) log_user('jsmith', 'jsmith') visit '/projects/ecookbook/issues/new' select expected_title, from: 'issue_template' sleep(0.2) end scenario 'Builtin fields are filled' do expect(page).to have_select('issue[priority_id]', selected: priority_name) expect(page.find('#issue_estimated_hours').value).to eq '5' end end end ================================================ FILE: spec/features/update_issue_spec.rb ================================================ # frozen_string_literal: true require_relative '../spec_helper' require_relative '../rails_helper' require_relative '../support/login_helper' RSpec.configure do |c| c.include LoginHelper end feature 'Update issue', js: true do given(:user) { FactoryBot.create(:user, :password_same_login, login: 'test-manager', language: 'en', admin: false) } given(:project) { FactoryBot.create(:project_with_enabled_modules) } given(:tracker) { FactoryBot.create(:tracker, :with_default_status) } given(:role) { FactoryBot.create(:role, :manager_role) } given(:status) { IssueStatus.create(name: 'open', is_closed: false) } given(:issue_note) { page.find('textarea#issue_notes') } background(:all) do Redmine::Plugin.register(:redmine_issue_templates) do settings partial: 'settings/redmine_issue_templates', default: { 'apply_global_template_to_all_projects' => 'false', 'apply_template_when_edit_issue' => 'true' } end end background do FactoryBot.create_list(:issue_template, 2, project_id: project.id, tracker_id: tracker.id) project.trackers << tracker assign_template_priv(role, add_permission: :show_issue_templates) member = Member.new(project: project, user_id: user.id) member.member_roles << MemberRole.new(role: role) member.save priority = IssuePriority.create( name: 'Low', position: 1, is_default: false, type: 'IssuePriority', active: true, project_id: nil, parent_id: nil, position_name: 'lowest' ) issue = Issue.create(project_id: project.id, tracker_id: tracker.id, author_id: user.id, priority: priority, subject: 'test_create', status_id: status.id, description: 'IssueTest#test_create') issue.save end scenario 'Click edit link with apply_template_when_edit_issue flag', js: true do Setting.send 'plugin_redmine_issue_templates=', 'apply_template_when_edit_issue' => 'true' visit_update_issue(user) issue = Issue.last visit "/issues/#{issue.id}" page.find('#content > div:nth-child(1) > a.icon.icon-edit').click sleep(0.2) expect(page).to have_selector('div#template_area select#issue_template') end scenario 'Click edit link without apply_template_when_edit_issue flag', js: true do Setting.send 'plugin_redmine_issue_templates=', 'apply_template_when_edit_issue' => 'false' visit_update_issue(user) issue = Issue.last visit "/issues/#{issue.id}" page.find('#content > div:nth-child(1) > a.icon.icon-edit').click sleep(0.2) expect(page).not_to have_selector('div#template_area select#issue_template') end context 'Have note template' do given(:expected_note_description) { 'Note Template desctiption' } before do NoteTemplate.create(project_id: project.id, tracker_id: tracker.id, name: 'Note Template name', description: expected_note_description, enabled: true) end scenario 'Template for note exists' do visit_update_issue(user) issue = Issue.last visit "/issues/#{issue.id}" page.find('#content > div:nth-child(1) > a.icon.icon-edit').click sleep(0.2) expect(page).to have_selector('a#link_template_issue_notes_dialog') page.find('a#link_template_issue_notes_dialog').click wait_for_ajax page.find('#template_issue_notes_dialog table tr:first-child > td:nth-child(3) > a.template-update-link').click wait_for_ajax expect(issue_note.value).to eq expected_note_description end end context 'Have global note template' do given(:expected_note_description) { 'Global Note Template desctiption' } before do Setting.send 'plugin_redmine_issue_templates=', 'apply_global_template_to_all_projects' => 'false' GlobalNoteTemplate.create(tracker_id: tracker.id, name: 'Global Note Template name', visibility: 2, description: expected_note_description, enabled: true) end scenario 'No template for note' do visit_update_issue(user) issue = Issue.last visit "/issues/#{issue.id}" page.find('#content > div:nth-child(1) > a.icon.icon-edit').click sleep(0.2) expect(page).not_to have_selector('a#link_template_issue_notes_dialog') end context 'apply_global_template_to_all_projects is true' do before do Setting.send 'plugin_redmine_issue_templates=', 'apply_global_template_to_all_projects' => 'true' end scenario 'One Global template for note' do visit_update_issue(user) issue = Issue.last visit "/issues/#{issue.id}" page.find('#content > div:nth-child(1) > a.icon.icon-edit').click sleep(0.2) expect(page).to have_selector('a#link_template_issue_notes_dialog') page.find('a#link_template_issue_notes_dialog').click wait_for_ajax template_rows = page.find('div#template_issue_notes_dialog table > tbody') expect(page).to have_selector('div#template_issue_notes_dialog') expect(template_rows).to have_selector('tr:first-child > td:nth-child(3) > a.template-global') template_rows.find('tr:first-child > td:nth-child(3) > a.template-global.template-update-link').click wait_for_ajax expect(issue_note.value).to eq expected_note_description end end end private def visit_update_issue(user) user.update_attribute(:admin, false) log_user(user.login, user.login) end end ================================================ FILE: spec/helpers/issue_templates_helper_spec.rb ================================================ require_relative '../spec_helper' describe IssueTemplatesHelper do describe '#project_tracker?' do let(:trackers) { FactoryBot.create_list(:tracker, 2, :with_default_status) } let(:project) { FactoryBot.create(:project) } let(:tracker) { trackers.first } subject { helper.project_tracker?(tracker, project) } context 'Tracker is associated' do before do project.trackers << tracker end it { is_expected.to be_truthy } end context 'Tracker is not associated' do before do project.trackers << trackers.last end it { is_expected.to be_falsey } end end describe '#non_project_tracker_msg' do it { expect(helper.non_project_tracker_msg(true)).to eq '' } it { expect(helper.non_project_tracker_msg(false)).to match('') } end describe '#template_target_trackers' do let(:trackers) { FactoryBot.create_list(:tracker, 2, :with_default_status) } let(:project) { FactoryBot.create(:project) } let(:tracker) { trackers.last } let(:template) do FactoryBot.create(:issue_template, tracker_id: tracker.id, project_id: project.id) end subject { helper.template_target_trackers(project, template) } before do project.trackers << trackers.first end it { expect(subject.include?([tracker.name, tracker.id])).to be_truthy } it { expect(subject.length).to eq 2 } end describe '#options_for_template_pulldown' do let(:options) do option = Struct.new(:id, :name) [].tap do |options| (0..2).each do |id| options << option.new(id, "name-#{id}") end end end subject { helper.options_for_template_pulldown(options) } it { expect(subject).to match('') } end end ================================================ FILE: spec/models/global_issue_template_spec.rb ================================================ # frozen_string_literal: true require_relative '../spec_helper' describe GlobalIssueTemplate do describe '#valid?' do let(:instance) { GlobalIssueTemplate.new(tracker_id: tracker.id, title: 'sample') } let(:tracker) { FactoryBot.create(:tracker, :with_default_status) } subject { instance.valid? } it 'related_link in invalid format' do instance.related_link = 'non url format string' is_expected.to be_falsey expect(instance.errors.messages.key?(:related_link)).to be_truthy end it 'related_link in valid format' do instance.related_link = 'https://valid.example.com/links.html' is_expected.to be_truthy end end describe '#builtin_fields_json' do let(:tracker) { FactoryBot.create(:tracker, :with_default_status) } let(:global_issue_template) do create(:global_issue_template, tracker_id: tracker.id) end subject { global_issue_template.update(builtin_fields_json: object) } context 'Data is a valid hash' do let(:object) { { 'key': 'value', 'foo': 'bar' } } it { is_expected.to be_truthy } end context 'Data is not a valid hash' do let(:object) { [1, 2, 3] } it { expect { subject }.to raise_error(ActiveRecord::SerializationTypeMismatch) } end end end ================================================ FILE: spec/models/global_note_template_spec.rb ================================================ # frozen_string_literal: true require_relative '../spec_helper' describe GlobalNoteTemplate do let(:tracker) { FactoryBot.create(:tracker, :with_default_status) } let!(:note_template) { FactoryBot.create(:global_note_template, tracker_id: tracker.id, position: 1) } let!(:note_template2) { FactoryBot.create(:global_note_template, tracker_id: tracker.id, position: 2) } let!(:note_template3) { FactoryBot.create(:global_note_template, tracker_id: tracker.id, position: 3) } it 'Instance of GlobalNoteTemplate' do expect(note_template).to be_an_instance_of(GlobalNoteTemplate) end describe 'scope: .sorted' do it 'do sort by position correctly' do expect([note_template, note_template2, note_template3]).to eq GlobalNoteTemplate.sorted expect(GlobalNoteTemplate.sorted.first).to eq note_template end it 'do sort by position correctly after update' do note_template.update(position: GlobalNoteTemplate.count) expect(GlobalNoteTemplate.sorted).to eq [note_template2, note_template3, note_template] end end end ================================================ FILE: spec/models/issue_template_setting_spec.rb ================================================ require_relative '../spec_helper' describe IssueTemplateSetting do let(:project) { FactoryBot.create(:project) } let(:independent_projects) { FactoryBot.create_list(:project, 2) } let(:subject) do FactoryBot.create(:issue_template_setting, project_id: project.id) end let(:child_projects) { Project.where(parent_id: project.id) } shared_examples 'expected for apply/unapply template' do context 'When no project id' do let(:param) {} it 'Raise Exception if no argument' do expect { subject }.to raise_error(ArgumentError) end end context 'When invalid project id' do let(:param) { 0 } it 'Raise NotFound Exception if not specified valid project' do expect { subject }.to raise_error(ActiveRecord::RecordNotFound) end end context 'When valid project id' do let(:param) { project.id } before do FactoryBot.create(:issue_template_setting, project_id: project.id) end it 'instance method is called' do expect_any_instance_of(IssueTemplateSetting).to receive(:update_inherit_template_of_child_projects) subject end end end describe '.apply_template_to_child_projects' do let(:subject) { IssueTemplateSetting.apply_template_to_child_projects(param) } it_behaves_like 'expected for apply/unapply template' end describe '.unapply_template_from_child_projects' do let(:subject) { IssueTemplateSetting.unapply_template_from_child_projects(param) } it_behaves_like 'expected for apply/unapply template' end describe '#child_projects' do context 'no child projects' do it 'return zero count' do expect(subject.child_projects.count).to eq 0 end end context 'has child projects' do before do FactoryBot.create_list(:project, 2, parent_id: project.id) end it 'return right number of all the child projects' do expect(subject.child_projects.count).to eq 2 end context 'has descendent projects' do before do FactoryBot.create_list(:project, 2, parent_id: child_projects.last.id) end it 'return right number of all the descendent projects' do expect(subject.child_projects.count).to eq 4 end it('return zero count after breaking off relationship') do expect(subject.child_projects.count).to eq 4 child_projects.each do |c| c.set_parent!(nil) end subject.project.reload expect(subject.child_projects.count).to eq 0 end end end end describe '#apply_template_to_child_projects' do let(:tracker) { FactoryBot.create(:tracker, :with_default_status) } let!(:enabled_module) { FactoryBot.create(:enabled_module, project_id: project.id) } let!(:issue_templates) do FactoryBot.create_list(:issue_template, 4, project_id: project.id, tracker_id: tracker.id, enabled_sharing: true) end let(:child_project_template_settings) do IssueTemplateSetting.where(project_id: child_projects.ids) end before do FactoryBot.create_list(:project, 4, parent_id: project.id) # change to enabled issue template module child_projects.each do |c| FactoryBot.create(:enabled_module, project_id: c.id) IssueTemplateSetting.find_or_create(c.id) c.trackers = [tracker] c.save end subject.apply_template_to_child_projects end it "child project inhetits parent's template" do expect(child_project_template_settings.first.inherit_templates).to be_truthy end it 'child project can use inherit template' do expect(child_project_template_settings.first.get_inherit_templates.count).to eq 4 end it 'All the child projects setting should be true' do expect(child_project_template_settings.pluck(:inherit_templates).include?(false)).to be_falsey end it 'All the child projects setting should be false after unapplied' do subject.unapply_template_from_child_projects values_inherit_templates = child_project_template_settings.pluck(:inherit_templates) expect(values_inherit_templates.include?(false)).to be_truthy expect(values_inherit_templates.uniq.count).to eq 1 end end end ================================================ FILE: spec/models/issue_template_spec.rb ================================================ # frozen_string_literal: true require_relative '../spec_helper' describe IssueTemplate do let(:tracker) { FactoryBot.create(:tracker, :with_default_status) } let(:project) { FactoryBot.create(:project) } let(:issue_template) { FactoryBot.create(:issue_template, tracker_id: tracker.id, project_id: project.id) } let(:issue_template2) { FactoryBot.create(:issue_template, tracker_id: tracker.id, project_id: project.id) } it 'Instance of IssueTemplate' do expect(issue_template).to be_an_instance_of(IssueTemplate) end describe 'scope .orphaned' do subject { IssueTemplate.orphaned.count } before do # Remove related tracker issue_template.tracker.delete end it { is_expected.to eq 1 } end describe 'scope: .sorted' do it 'do sort by position correctly' do expect([issue_template, issue_template2]).to eq [issue_template2, issue_template].sort expect(IssueTemplate.sorted.first).to eq issue_template end it 'do sort by position correctly after update' do issue_template.update(position: issue_template2.position + 100) expect(IssueTemplate.sorted.first).to eq issue_template2 end end describe '#destroy' do subject { issue_template.destroy } context 'Template is enabled' do before do issue_template.enabled = true issue_template.save end it 'Failed to remove with invalid message' do expect(Rails.logger).to receive(:info).with(/\[Destroy\] IssueTemplate: /).never subject expect(issue_template.errors.present?).to be_truthy end end context 'Template is disabled' do before { issue_template.enabled = false } it 'Removed and log message is generated' do expect(Rails.logger).to receive(:info).with(/\[Destroy\] IssueTemplate: /).once subject expect(issue_template.errors.present?).to be_falsey end end end describe '#valid?' do let(:instance) { described_class.new(tracker_id: tracker.id, project_id: project.id, title: 'sample') } subject { instance.valid? } it 'related_link in invalid format' do instance.related_link = 'non url format string' is_expected.to be_falsey expect(instance.errors.messages.key?(:related_link)).to be_truthy end it 'related_link in valid format' do instance.related_link = 'https://valid.example.com/links.html' is_expected.to be_truthy end end describe '#builtin_fields_json' do subject { issue_template.update(builtin_fields_json: object) } context 'Data is a valid hash' do let(:object) { { 'key': 'value', 'foo': 'bar' } } it { is_expected.to be_truthy } end context 'Data is not a valid hash' do let(:object) { [1, 2, 3] } it { expect { subject }.to raise_error(ActiveRecord::SerializationTypeMismatch) } end end end ================================================ FILE: spec/models/note_visible_role_spec.rb ================================================ # frozen_string_literal: true require_relative '../spec_helper' describe NoteVisibleRole do let(:instance) { described_class.new } it 'Instance of NoteVisibleRole' do expect(instance).to be_an_instance_of(NoteVisibleRole) end end ================================================ FILE: spec/rails_helper.rb ================================================ # frozen_string_literal: true # This file is copied to spec/ when you run 'rails generate rspec:install' ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../../config/environment', __dir__) abort('The Rails environment is running in production mode!') if Rails.env.production? require 'spec_helper' require 'rspec/rails' ActiveRecord::Migration.maintain_test_schema! RSpec.configure do |config| config.fixture_path = "#{::Rails.root}/test/fixtures" config.include FactoryBot::Syntax::Methods config.before :suite do require 'selenium-webdriver' if ENV['DRIVER'] == 'headless' Capybara.register_driver :headless_chrome do |app| capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( # # NOTE: When using Chrome headress, default window size is 800x600. # In case window size is not specified, Redmine renderes its contents with responsive mode. # chromeOptions: { args: %w[headless disable-gpu window-size=1280,800] } ) options = Selenium::WebDriver::Chrome::Options.new options.add_option('w3c', false) Capybara::Selenium::Driver.new( app, browser: :chrome, desired_capabilities: capabilities, options: options ) end else Capybara.register_driver :headless_chrome do |app| capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( chromeOptions: { args: %w[window-size=1280,800] } ) options = Selenium::WebDriver::Chrome::Options.new options.add_option('w3c', false) Capybara::Selenium::Driver.new( app, browser: :chrome, desired_capabilities: capabilities, options: options ) end end end config.before :each, type: :feature do Capybara.javascript_driver = :headless_chrome Capybara.current_driver = :headless_chrome Capybara.default_max_wait_time = 30 end config.include Capybara::DSL config.use_transactional_fixtures = false config.infer_spec_type_from_file_location! config.filter_rails_from_backtrace! require 'database_cleaner' config.before(:suite) do DatabaseCleaner.strategy = :truncation DatabaseCleaner.clean_with(:truncation) end config.before(:each) do DatabaseCleaner.start end config.after(:each) do DatabaseCleaner.clean end end ================================================ FILE: spec/requests/global_note_templates_spec.rb ================================================ # frozen_string_literal: true require_relative '../spec_helper' require File.expand_path(File.dirname(__FILE__) + '/../support/controller_helper') RSpec.configure do |c| c.include ControllerHelper end RSpec.describe 'Global Note Template', type: :request do let(:user) { FactoryBot.create(:user, :password_same_login, login: 'test-manager', language: 'en', admin: true) } let(:tracker) { FactoryBot.create(:tracker, :with_default_status) } let(:target_template_name) { 'Global Note template name' } let(:target_template) { GlobalNoteTemplate.last } before do # do nothing end it 'show global note template list' do login_request(user.login, user.login) get '/global_note_templates' expect(response.status).to eq 200 get '/global_note_templates/new' expect(response.status).to eq 200 end it 'create global note template and load' do login_request(user.login, user.login) post '/global_note_templates', params: { global_note_template: { tracker_id: tracker.id, name: target_template_name, description: 'Global Note template description', memo: 'Test memo', enabled: 1 } } expect(response).to have_http_status(302) post '/note_templates/load', params: { note_template: { note_template_id: target_template.id, template_type: 'global' } } json = JSON.parse(response.body) expect(target_template.name).to eq(json['note_template']['name']) end end ================================================ FILE: spec/requests/note_templates_spec.rb ================================================ # frozen_string_literal: true require_relative '../spec_helper' require File.expand_path(File.dirname(__FILE__) + '/../support/controller_helper') RSpec.configure do |c| c.include ControllerHelper end RSpec.describe 'Note Template', type: :request do let(:user) { FactoryBot.create(:user, :password_same_login, login: 'test-manager', language: 'en', admin: false) } let(:project) { FactoryBot.create(:project_with_enabled_modules) } let(:tracker) { FactoryBot.create(:tracker, :with_default_status) } let(:role) { FactoryBot.create(:role, :manager_role) } let(:target_template) { NoteTemplate.last } before do project.trackers << tracker assign_template_priv(role, add_permission: :show_issue_templates) assign_template_priv(role, add_permission: :edit_issue_templates) member = Member.new(project: project, user_id: user.id) member.member_roles << MemberRole.new(role: role) member.save end it 'show note template list' do login_request(user.login, user.login) get "/projects/#{project.identifier}/note_templates" expect(response.status).to eq 200 get "/projects/#{project.identifier}/note_templates/new" expect(response.status).to eq 200 end it 'create note template and load' do login_request(user.login, user.login) post "/projects/#{project.identifier}/note_templates", params: { note_template: { tracker_id: tracker.id, name: 'Note template name', description: 'Note template description', memo: 'Test memo', enabled: 1 } } expect(response).to have_http_status(302) post '/note_templates/load', params: { note_template: { note_template_id: target_template.id } } json = JSON.parse(response.body) expect(target_template.name).to eq(json['note_template']['name']) end end ================================================ FILE: spec/spec_helper.rb ================================================ require File.expand_path('../../../../config/environment', __FILE__) require 'rspec/rails' require 'simplecov' require 'factory_bot_rails' require 'database_cleaner' SimpleCov.coverage_dir('coverage/redmine_issue_templates_spec') SimpleCov.start 'rails' RSpec.configure do |config| config.fixture_path = "#{::Rails.root}/test/fixtures" config.use_transactional_fixtures = false config.infer_spec_type_from_file_location! config.include FactoryBot::Syntax::Methods FactoryBot.definition_file_paths = [File.expand_path('../factories', __FILE__)] FactoryBot.find_definitions config.before(:all) do FactoryBot.reload end require 'database_cleaner' config.before(:suite) do DatabaseCleaner.strategy = :truncation DatabaseCleaner.clean_with(:truncation) end config.before(:each) do DatabaseCleaner.start end config.after(:each) do DatabaseCleaner.clean end end ================================================ FILE: spec/support/controller_helper.rb ================================================ module ControllerHelper # AuthHeader with api key (Ref.http://www.redmine.org/projects/redmine/wiki/Rest_api) def auth_with_user(user) request.headers['X-Redmine-API-Key'] = user.api_key.to_s end def clear_token request.headers['X-Redmine-API-Key'] = nil end def login_request(login, password) post '/login', params: { username: login, password: password } end def assign_template_priv(role, add_permission: nil, remove_permission: nil) return if add_permission.blank? && remove_permission.blank? role.add_permission! add_permission if add_permission.present? role.remove_permission! remove_permission if remove_permission.present? end shared_context 'As admin' do let(:user) { FactoryBot.create(:user, status: 1, admin: is_admin) } let(:is_admin) { true } end shared_context 'Project and Tracler exists' do let(:count) { 4 } let(:trackers) { FactoryBot.create_list(:tracker, 2, :with_default_status) } let(:projects) { FactoryBot.create_list(:project, count) } end shared_examples 'Right response' do |status_code| it { expect(response.status).to eq status_code } end end ================================================ FILE: spec/support/login_helper.rb ================================================ # frozen_string_literal: true module LoginHelper def log_user(login, password) visit '/login' within('#login-form form') do fill_in 'username', with: login fill_in 'password', with: password find('input[name=login]').click end end def assign_template_priv(role, add_permission: nil, remove_permission: nil) return if add_permission.blank? && remove_permission.blank? role.add_permission! add_permission if add_permission.present? role.remove_permission! remove_permission if remove_permission.present? end def wait_for_ajax Timeout.timeout(Capybara.default_max_wait_time) do loop until finished_all_ajax_requests? end end def finished_all_ajax_requests? page.evaluate_script('jQuery.active').zero? end end ================================================ FILE: test/fixtures/global_issue_templates.yml ================================================ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html one: id: 1 tracker_id: 1 author_id: 1 title: global_title1 description: "global description1" note: note1 enabled: true position: 1 two: id: 2 tracker_id: 3 author_id: 1 title: titel2 description: "global description2" note: note2 enabled: false position: 1 three: id: 3 tracker_id: 2 author_id: 1 title: titel3 description: "global description3" note: note3 enabled: true position: 1 four: id: 4 tracker_id: 1 author_id: 1 title: titel4 description: "global description4" note: note4 enabled: true position: 2 five: id: 5 tracker_id: 1 author_id: 1 title: titel5 description: "global description5" note: note5 enabled: true position: 3 ================================================ FILE: test/fixtures/global_issue_templates_projects.yml ================================================ global_issue_templates_projects_001: project_id: 1 global_issue_template_id: 1 global_issue_templates_projects_002: project_id: 3 global_issue_template_id: 1 ================================================ FILE: test/fixtures/issue_template_settings.yml ================================================ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html one: id: 1 project_id: 1 help_message: MyText enabled: true inherit_templates: false two: id: 2 project_id: 2 help_message: MyText enabled: false inherit_templates: false three: id: 3 project_id: 3 help_message: Project3 help enabled: true inherit_templates: false ================================================ FILE: test/fixtures/issue_templates.yml ================================================ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html one: id: 1 project_id: 1 tracker_id: 1 author_id: 1 title: title1 description: description1 note: note1 enabled: true position: 1 enabled_sharing: true two: id: 2 project_id: 1 tracker_id: 3 author_id: 1 title: titel2 description: description2 note: note2 enabled: false position: 1 enabled_sharing: false three: id: 3 project_id: 1 tracker_id: 2 author_id: 1 title: titel3 description: description3 note: note3 enabled: true position: 1 four: id: 4 project_id: 1 tracker_id: 1 author_id: 2 title: titel4 description: description4 note: note4 enabled: true position: 2 five: id: 5 project_id: 1 tracker_id: 1 author_id: 1 title: title5 description: description5 note: note5 enabled: false position: 3 six: id: 6 project_id: 3 tracker_id: 1 author_id: 1 title: title6 description: description6 note: note5 enabled: true position: 3 ================================================ FILE: test/functional/global_issue_templates_controller_test.rb ================================================ require File.expand_path('../test_helper', __dir__) require 'minitest/autorun' class GlobalIssueTemplatesControllerTest < Redmine::ControllerTest fixtures :projects, :users, :trackers, :global_issue_templates, :global_issue_templates_projects include Redmine::I18n def setup @request.session[:user_id] = 1 # Admin @request.env['HTTP_REFERER'] = '/' # Enabled Template module @project = Project.find(1) @project.enabled_modules << EnabledModule.new(name: 'issue_templates') @project.save! end def test_get_index get :index assert_response :success end def test_update_template put :update, params: { id: 2, global_issue_template: { description: 'Update Test Global template2' } } assert_response :redirect # show global_issue_template = GlobalIssueTemplate.find(2) assert_redirected_to controller: 'global_issue_templates', action: 'show', id: global_issue_template.id assert_equal 'Update Test Global template2', global_issue_template.description end def test_update_template_with_empty_title put :update, params: { id: 2, global_issue_template: { title: '' } } assert_response :success global_issue_template = GlobalIssueTemplate.find(2) assert_not_equal '', global_issue_template.title # render :show assert_select 'h2.global_issue_template', "#{l(:global_issue_templates)}: ##{global_issue_template.id}" # Error message should be displayed. assert_select 'div#errorExplanation', { count: 1, text: /Title cannot be blank/ }, @response.body.to_s end def test_destroy_template post :destroy, params: { id: 2 } assert_redirected_to controller: 'global_issue_templates', action: 'index' assert_raise(ActiveRecord::RecordNotFound) { GlobalIssueTemplate.find(2) } end def test_new_template get :new assert_response :success end def test_create_template num = GlobalIssueTemplate.count post :create, params: { global_issue_template: { title: 'Global Template newtitle for creation test', note: 'Global note for creation test', description: 'Global Template description for creation test', tracker_id: 1, enabled: 1, author_id: 1 } } template = GlobalIssueTemplate.order('id DESC').first assert_response :redirect assert_equal(num + 1, GlobalIssueTemplate.count) assert_not_nil template assert_equal('Global Template newtitle for creation test', template.title) assert_equal('Global note for creation test', template.note) assert_equal('Global Template description for creation test', template.description) assert_equal(1, template.tracker.id) assert_equal(1, template.author.id) end def test_create_template_fail num = GlobalIssueTemplate.count # when title blank, validation bloks to save. post :create, params: { global_issue_template: { title: '', note: 'note', description: 'description', tracker_id: 1, enabled: 1, author_id: 1 } } assert_response :success assert_equal(num, GlobalIssueTemplate.count) # render :new assert_select 'h2', text: "#{l(:issue_templates)} / #{l(:button_add)}" # Error message should be displayed. assert_select 'div#errorExplanation', { count: 1, text: /Title cannot be blank/ }, @response.body.to_s end def test_preview_template get :preview, params: { global_issue_template: { description: 'h1. Global Test data.' } } assert_select 'h1', /Global Test data\./, @response.body.to_s end end ================================================ FILE: test/functional/issue_templates_controller_test.rb ================================================ require File.expand_path('../test_helper', __dir__) require 'minitest/autorun' class IssueTemplatesControllerTest < Redmine::ControllerTest fixtures :projects, :users, :roles, :trackers, :members, :member_roles, :enabled_modules, :issue_templates, :projects_trackers include Redmine::I18n def setup @request.session[:user_id] = 2 @request.env['HTTP_REFERER'] = '/' # Enabled Template module @project = Project.find(1) @project.enabled_modules << EnabledModule.new(name: 'issue_templates') @project.save! # Set default permission: show template Role.find(1).add_permission! :show_issue_templates end def test_get_index_with_non_existing_project # set non existing project get :index, params: { project_id: 100 } assert_response 404 end def test_get_index_without_show_permission Role.find(1).remove_permission! :show_issue_templates get :index, params: { project_id: 1 } assert_response 403 end def test_get_index_with_normal get :index, params: { project_id: 1 } assert_response :success end def test_show_with_non_existing_template get :show, params: { id: 100, project_id: 1 } assert_response 404 end def test_show_return_json_hash get :load, params: { project_id: 1, template_id: 1 } assert_response :success assert_equal 'description1', json_response['issue_template']['description'] end def test_show_return_json_hash_of_global get :load, params: { project_id: 1, template_id: 1, template_type: 'global' } assert_response :success assert_equal 'global description1', json_response['global_issue_template']['description'] end def test_show_render_pulldown get :set_pulldown, params: { project_id: 1, issue_tracker_id: 1 } tracker = Tracker.find(1) assert_response :success assert_select "optgroup[label=#{tracker.name}]" end def test_new_template edit_permission get :new, params: { project_id: 1, author_id: 2 } assert_response :success end def test_create_template edit_permission num = IssueTemplate.count post :create, params: { issue_template: { title: 'newtitle', note: 'note', description: 'description', tracker_id: 1, enabled: 1, author_id: 3 }, project_id: 1 } template = IssueTemplate.last assert_response :redirect assert_equal(num + 1, IssueTemplate.count) assert_not_nil template assert_equal('newtitle', template.title) assert_equal('note', template.note) assert_equal('description', template.description) assert_equal(1, template.project.id) assert_equal(1, template.tracker.id) assert_equal(2, template.author.id) assert_nil(template.checklist_json) assert_equal([], template.checklist) end def test_create_template_with_empty_title edit_permission num = IssueTemplate.count # when title blank, validation bloks to save. post :create, params: { issue_template: { title: '', note: 'note', description: 'description', tracker_id: 1, enabled: 1, author_id: 1 }, project_id: 1 } assert_response :success assert_equal(num, IssueTemplate.count) # render :new assert_select 'h2', text: "#{l(:issue_templates)} / #{l(:button_add)}" # Error message should be displayed. assert_select 'div#errorExplanation', { count: 1, text: /Title cannot be blank/ }, @response.body.to_s end def test_preview_template edit_permission get :preview, params: { issue_template: { description: 'h1. Test data.' } } assert_select 'h1', /Test data\./, @response.body.to_s end def test_update_template edit_permission put :update, params: { id: 2, issue_template: { description: 'Update Test template2' }, project_id: 1 } project = Project.find 1 assert_response :redirect # show issue_template = IssueTemplate.find(2) assert_redirected_to controller: 'issue_templates', action: 'show', id: issue_template.id, project_id: project assert_equal 'Update Test template2', issue_template.description end def test_update_template_with_empty_title edit_permission num = IssueTemplate.count # when title blank, validation bloks to save. put :update, params: { id: 2, issue_template: { title: '' }, project_id: 1 } assert_response :success assert_equal(num, IssueTemplate.count) # render :show assert_select 'h2.issue_template', "#{l(:issue_templates)}: #2" assert_select 'div#edit-issue_template' # Error message should be displayed. assert_select 'div#errorExplanation', { count: 1, text: /Title cannot be blank/ }, @response.body.to_s end def test_delete_template_fail_if_enabled edit_permission post :destroy, params: { id: 1, project_id: 1 } project = Project.find 1 assert_redirected_to controller: 'issue_templates', action: 'show', project_id: project, id: 1 assert_match(/Only disabled template can be destroyed/, flash[:error]) end def test_delete_template_success_if_disabled edit_permission template = IssueTemplate.find(1) template.enabled = false template.save post :destroy, params: { id: 1, project_id: 1 } project = Project.find 1 assert_redirected_to controller: 'issue_templates', action: 'index', project_id: project assert_raise(ActiveRecord::RecordNotFound) { IssueTemplate.find(1) } end def test_edit_template_failed_with_project_id_and_safe_attributes edit_permission put :update, params: { id: 2, issue_template: { description: 'Update Test template2', project_id: 2, author_id: 2 }, project_id: 1 } project = Project.find 1 assert_response :redirect # show issue_template = IssueTemplate.find(2) assert_redirected_to controller: 'issue_templates', action: 'show', id: issue_template.id, project_id: project assert_equal 'Update Test template2', issue_template.description assert_equal(1, issue_template.project.id) assert_equal(1, issue_template.author.id) end def test_child_project_index child_project_setup get :index, params: { project_id: 1 } assert_response :success assert_select 'h2', text: l(:issue_template).to_s, count: 1 assert !@response.body.match(%r{

#{l(:label_inherited_templates)}

}) get :index, params: { project_id: 3 } assert_response :success assert_select 'h2', text: l(:issue_template).to_s, count: 1 assert !@response.body.match(%r{

#{l(:label_inherited_templates)}

}) end def test_child_project_index_with_inherit_templates child_project_setup setting = IssueTemplateSetting.find(3) setting.inherit_templates = true setting.save! get :index, params: { project_id: 3 } assert_response :success assert_select 'h2', text: l(:issue_template).to_s, count: 1 end def test_child_project_render_pulldown_with_parent_template child_project_setup setting = IssueTemplateSetting.find(3) setting.inherit_templates = true setting.save! tracker = Tracker.find(1) get :set_pulldown, params: { project_id: 3, issue_tracker_id: 1 } assert_select "optgroup[label='#{tracker.name}']" assert_select 'option[value="1"]' assert_select 'option[class="global"]' end def json_response ActiveSupport::JSON.decode @response.body end def child_project_setup @project = Project.find(3) @project.enabled_modules << EnabledModule.new(name: 'issue_templates') @project.save! # do as Admin @request.session[:user_id] = 1 end def edit_permission Role.find(1).add_permission! :edit_issue_templates end end ================================================ FILE: test/functional/issue_templates_settings_controller_test.rb ================================================ # frozen_string_literal: true require File.expand_path('../test_helper', __dir__) class IssueTemplatesSettingsControllerTest < Redmine::ControllerTest fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :issue_templates def setup # Enabled Template module enabled_module = EnabledModule.new enabled_module.project_id = 1 enabled_module.name = 'issue_templates' enabled_module.save # set default user to 2 (as member) @request.session[:user_id] = 2 Role.find(1).add_permission! :manage_issue_templates @project = Project.find(1) end def test_update_without_permission Role.find(1).remove_permission! :manage_issue_templates post :edit, params: { project_id: @project, settings: { enabled: '1', help_message: 'Hoo', inherit_templates: true }, setting_id: 1, tab: 'issue_templates' } assert_response 403 end def test_update_with_permission_and_non_project post :edit, params: { project_id: 'dummy', settings: { enabled: '1', help_message: 'Hoo', inherit_templates: true }, setting_id: 1 } assert_response 404 end def test_update_with_permission_and_redirect post :edit, params: { project_id: @project, settings: { enabled: '1', help_message: 'Hoo', inherit_templates: true }, setting_id: 1 } assert_response :redirect assert_redirected_to controller: 'issue_templates_settings', action: 'index', project_id: @project end def test_preview_template_setting post :preview, params: { settings: { help_message: 'h1. Preview test.', enabled: '1' }, project_id: @project } assert_select 'h1', /Preview test\./, @response.body.to_s end def test_create_template_setting IssueTemplateSetting.delete_all post :edit, params: { project_id: @project, settings: { enabled: '1', help_message: 'Hoo', inherit_templates: true }, setting_id: 1 } assert_response :redirect assert_redirected_to controller: 'issue_templates_settings', action: 'index', project_id: @project end end ================================================ FILE: test/functional/issues_controller_test.rb ================================================ require File.expand_path(File.dirname(__FILE__) + '/../test_helper') require 'issues_controller' # Test for view hooks. class IssuesControllerTest < Redmine::ControllerTest fixtures :projects, :users, :roles, :members, :member_roles, :issues, :issue_statuses, :versions, :trackers, :projects_trackers, :issue_categories, :enabled_modules, :enumerations, :attachments, :workflows, :custom_fields, :custom_values, :custom_fields_trackers, :time_entries, :journals, :journal_details, :issue_templates def setup User.current = nil enabled_module = EnabledModule.new enabled_module.project_id = 1 enabled_module.name = 'issue_templates' enabled_module.save roles = Role.all roles.each do |role| role.permissions << :show_issue_templates role.remove_permission! :edit_issue_templates role.save end @request.session[:user_id] = 2 @project = Project.find(1) end def test_index_without_project get :index assert_response :success assert_select 'h3', count: 0, text: I18n.t('issue_template') end def test_index get :index, params: { project_id: @project.id } assert_response :success assert_select 'div#template_area select#issue_template', false, 'Action index should not contain template select pulldown.' assert_select 'h3', text: I18n.t('issue_template') assert_select 'a', { href: "/projects/#{@project}/issue_templates/new" }, false end def test_index_with_edit_permission Role.find(1).add_permission! :edit_issue_templates get :index, params: { project_id: @project.id } assert_select 'h3', text: I18n.t('issue_template') assert_select 'a', href: "/projects/#{@project}/issue_templates/new" end def test_new get :new, params: { project_id: 1 } assert_response :success assert_select 'div#template_area select#issue_template' end # NOTE: When copy, template area should not be displayed. def test_copy get :new, params: { project_id: 1, copy_from: 1 } assert_response :success assert_select 'div#template_area', false end def test_new_without_project get :new assert_response :success assert_select 'div#template_area select#issue_template', true end end ================================================ FILE: test/functional/projects_controller_test.rb ================================================ require File.expand_path(File.dirname(__FILE__) + '/../test_helper') require 'projects_controller' class ProjectsControllerTest < Redmine::ControllerTest fixtures :projects, :users, :roles, :members, :member_roles, :trackers, :projects_trackers, :enabled_modules def setup # as project admin @request.session[:user_id] = 2 Role.find(1).add_permission! :show_issue_templates # Enabled Template module @project = Project.find(1) @project.enabled_modules << EnabledModule.new(name: 'issue_templates') @project.save! end def test_settings get :show, params: { id: 1 } assert_response :success assert_select '#main-menu > ul > li > a.issue-templates' end end ================================================ FILE: test/integration/layout_test.rb ================================================ require File.expand_path(File.dirname(__FILE__) + '/../test_helper') class LayoutTest < Redmine::IntegrationTest fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :projects_trackers, :roles, :member_roles, :members, :enabled_modules, :workflows, :issue_templates def test_issue_template_not_visible_when_module_off # module -> disabled log_user('admin', 'admin') post '/projects/ecookbook/modules', params: { enabled_module_names: ['issue_tracking'], commit: 'Save', id: 'ecookbook' } get '/projects/ecookbook/issues' assert_response :success assert_select 'h3', count: 0, text: I18n.t('issue_template') get '/projects/ecookbook/issues/new' assert_select 'div#template_area select#issue_template', 0 end def test_issue_template_visible_when_module_on # module -> enabled log_user('admin', 'admin') post '/projects/ecookbook/modules', params: { enabled_module_names: %w[issue_tracking issue_templates], commit: 'Save', id: 'ecookbook' } get '/projects/ecookbook/issues' assert_response :success end end ================================================ FILE: test/test_helper.rb ================================================ begin require 'simplecov' require 'simplecov-rcov' rescue LoadError => ex puts <<-"EOS" This test should be called only for redmine issue template test. Test exit with LoadError -- #{ex.message} Please move redmine_issue_templates/Gemfile.local to redmine_issue_templates/Gemfile and run bundle install if you want to to run tests. EOS exit end if ENV['JENKINS'] == 'true' SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter true else SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([SimpleCov::Formatter::HTMLFormatter]) end SimpleCov.coverage_dir('coverage/redmine_issue_templates_test') SimpleCov.start do add_filter do |source_file| # report this plugin only. !source_file.filename.include?('plugins/redmine_issue_templates') || !source_file.filename.end_with?('.rb') end end require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper') ActiveRecord::FixtureSet.create_fixtures(File.dirname(__FILE__) + '/fixtures/', %i[issue_templates issue_template_settings global_issue_templates global_issue_templates_projects]) ================================================ FILE: test/unit/global_issue_templates_test.rb ================================================ require File.expand_path('../test_helper', __dir__) class GlobalIssueTemplatesTest < ActiveSupport::TestCase fixtures :global_issue_templates, :users, :trackers def setup @global_issue_template = GlobalIssueTemplate.find(1) end def test_truth assert_kind_of GlobalIssueTemplate, @global_issue_template end def test_template_enabled enabled = @global_issue_template.enabled? assert_equal true, enabled, @global_issue_template.enabled? @global_issue_template.enabled = false @global_issue_template.save! enabled = @global_issue_template.enabled? assert_equal false, enabled, @global_issue_template.enabled? end def test_sort_by_position a = GlobalIssueTemplate.new(title: 'Template4', position: 2, tracker_id: 1) b = GlobalIssueTemplate.new(title: 'Template5', position: 1, tracker_id: 1) assert_equal [b, a], [a, b].sort end end ================================================ FILE: test/unit/issue_template_setting_test.rb ================================================ require File.expand_path(File.dirname(__FILE__) + '/../test_helper') class IssueTemplateSettingTest < ActiveSupport::TestCase fixtures :issue_template_settings, :projects def setup @issue_template_setting = IssueTemplateSetting.find(1) end def test_truth assert_kind_of IssueTemplateSetting, @issue_template_setting end def test_help_message_enabled enable_help = @issue_template_setting.enable_help? assert_equal(true, enable_help) assert_equal(false, !enable_help) end def test_duplicate_project_setting templ = IssueTemplateSetting.find_or_create(3) templ.attributes = { enabled: true, help_message: 'Help!' } assert templ.save!, 'Failed to save.' # test which has the same proect id templ2 = IssueTemplateSetting.new templ2.attributes = { project_id: 1, enabled: true, help_message: 'Help!' } assert !templ2.save, 'Dupricate project should be denied.' end def test_help_message_disabled # load disabled template setting issue_template_setting = IssueTemplateSetting.find(2) enable_help = issue_template_setting.enable_help? assert_equal(false, enable_help) end def test_find_template_setting # for Project 6 issue_template_setting = IssueTemplateSetting.find_or_create(6) assert_kind_of IssueTemplateSetting, issue_template_setting end end ================================================ FILE: test/unit/issue_template_test.rb ================================================ require File.expand_path(File.dirname(__FILE__) + '/../test_helper') class IssueTemplateTest < ActiveSupport::TestCase fixtures :issue_templates, :projects, :users, :trackers def setup @issue_template = IssueTemplate.find(1) end def test_truth assert_kind_of IssueTemplate, @issue_template end def test_template_enabled enabled = @issue_template.enabled? assert_equal true, enabled, @issue_template.enabled? @issue_template.enabled = false @issue_template.save! enabled = @issue_template.enabled? assert_equal false, enabled, @issue_template.enabled? end def test_sort_by_position a = IssueTemplate.new(title: 'Template1', position: 2, project_id: 1, tracker_id: 1) b = IssueTemplate.new(title: 'Template2', position: 1, project_id: 1, tracker_id: 1) assert_equal [b, a], [a, b].sort end def test_is_default # Reset default data IssueTemplate.update_all(is_default: false) assert !@issue_template.is_default? @issue_template.is_default = true @issue_template.save! assert @issue_template.is_default? templates = IssueTemplate.search_by_project(1).search_by_tracker(1).not_default templates.each do |template| assert !template.is_default? end end end ================================================ FILE: test/unit/note_template_test.rb ================================================ # frozen_string_literal: true require File.expand_path(File.dirname(__FILE__) + '/../test_helper') class NoteTemplateTest < ActiveSupport::TestCase fixtures :projects, :users, :trackers, :roles def setup tracker = Tracker.first params = { author: User.first, project: Project.first, tracker: tracker }.merge( name: "Note Template name for Tracker #{tracker.name}.", description: "Note Template description for Tracker #{tracker.name}.", memo: "Note Template memo for Tracker #{tracker.name}.", enabled: true ) @template = NoteTemplate.create(params) end def teardown; end def test_truth assert_kind_of NoteTemplate, @template end def test_template_enabled enabled = @template.enabled? assert_equal true, enabled, @template.enabled? @template.enabled = false @template.save! enabled = @template.enabled? assert_equal false, enabled, @template.enabled? end def test_sort_by_position a = NoteTemplate.new(name: 'Template1', position: 2, project_id: 1, tracker_id: 1) b = NoteTemplate.new(name: 'Template2', position: 1, project_id: 1, tracker_id: 1) assert_equal [b, a], [a, b].sort end def test_visibility_with_success NoteTemplate.delete_all NoteTemplate.create(name: 'Template1', position: 2, project_id: 1, tracker_id: 1, visibility: 'roles', role_ids: [Role.first.id]) a = NoteTemplate.first assert_equal a.visibility_before_type_cast, 1 a.visibility = 'mine' a.save assert_equal a.visibility_before_type_cast, 0 end def test_visibility_without_role_ids NoteTemplate.delete_all # When enable validation: Raise ActiveRecord::RecordInvalid e = assert_raises ActiveRecord::RecordInvalid do NoteTemplate.create!(name: 'Template1', position: 2, project_id: 1, tracker_id: 1, visibility: 'roles') end # Check error message. assert_equal 'Validation failed: Role ids cannot be blank', e.message end def test_visibility_from_mine_to_roles NoteTemplate.delete_all NoteTemplate.create(name: 'Template1', position: 2, project_id: 1, tracker_id: 1, visibility: 'mine') a = NoteTemplate.first a.visibility = 'roles' # When skip validation: Raise: NoteTemplate::NoteTemplateError: Please select at least one role. e = assert_raises NoteTemplate::NoteTemplateError do a.save(validate: false) end # Check error message. assert_equal 'Please select at least one role.', e.message end end