diff --git a/.github/workflows/4_1_6.yml b/.github/workflows/4_1_7.yml similarity index 92% rename from .github/workflows/4_1_6.yml rename to .github/workflows/4_1_7.yml index 940cb20..643e3cb 100644 --- a/.github/workflows/4_1_6.yml +++ b/.github/workflows/4_1_7.yml @@ -1,8 +1,8 @@ -name: Tests 4.1.6 +name: Tests 4.1.7 env: PLUGIN_NAME: redmine_tiny_features - REDMINE_VERSION: 4.1.6 + REDMINE_VERSION: 4.1.7 RAILS_ENV: test on: @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout Redmine - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: redmine/redmine ref: ${{ env.REDMINE_VERSION }} @@ -72,15 +72,16 @@ jobs: sudo mv policy.xml /etc/ImageMagick-6/policy.xml - name: Setup Ruby - uses: actions/setup-ruby@v1 + uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} + bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Setup Bundler run: gem install bundler -v '~> 1.0' - name: Checkout dependencies - Base RSpec plugin - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: jbbarth/redmine_base_rspec path: redmine/plugins/redmine_base_rspec @@ -113,19 +114,19 @@ jobs: bundle exec rails test:scm:setup:subversion - name: Checkout dependencies - Base Deface plugin - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: jbbarth/redmine_base_deface path: redmine/plugins/redmine_base_deface - name: Checkout dependencies - Base StimulusJS plugin - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: nanego/redmine_base_stimulusjs path: redmine/plugins/redmine_base_stimulusjs - name: Checkout plugin - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: redmine/plugins/${{ env.PLUGIN_NAME }} diff --git a/.github/workflows/4_2_4.yml b/.github/workflows/4_2_9.yml similarity index 90% rename from .github/workflows/4_2_4.yml rename to .github/workflows/4_2_9.yml index b16d641..20c4194 100644 --- a/.github/workflows/4_2_4.yml +++ b/.github/workflows/4_2_9.yml @@ -1,8 +1,8 @@ -name: Tests 4.2.4 +name: Tests 4.2.9 env: PLUGIN_NAME: redmine_tiny_features - REDMINE_VERSION: 4.2.4 + REDMINE_VERSION: 4.2.9 RAILS_ENV: test on: @@ -16,7 +16,7 @@ jobs: strategy: matrix: - ruby: ['2.6'] + ruby: ['2.7'] db: ['postgres'] fail-fast: false @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout Redmine - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: redmine/redmine ref: ${{ env.REDMINE_VERSION }} @@ -72,15 +72,16 @@ jobs: sudo mv policy.xml /etc/ImageMagick-6/policy.xml - name: Setup Ruby - uses: actions/setup-ruby@v1 + uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} + bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Setup Bundler run: gem install bundler -v '~> 1.0' - name: Checkout dependencies - Base RSpec plugin - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: jbbarth/redmine_base_rspec path: redmine/plugins/redmine_base_rspec @@ -88,6 +89,7 @@ jobs: - name: Prepare Redmine source working-directory: redmine run: | + sed -i -e 's/.*puma.*/ gem "puma", "< 6"/' Gemfile sed -i '/rubocop/d' Gemfile rm -f .rubocop* cp plugins/redmine_base_rspec/spec/support/database-${{ matrix.db }}.yml config/database.yml @@ -113,19 +115,19 @@ jobs: bundle exec rails test:scm:setup:subversion - name: Checkout dependencies - Base Deface plugin - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: jbbarth/redmine_base_deface path: redmine/plugins/redmine_base_deface - name: Checkout dependencies - Base StimulusJS plugin - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: nanego/redmine_base_stimulusjs path: redmine/plugins/redmine_base_stimulusjs - name: Checkout plugin - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: redmine/plugins/${{ env.PLUGIN_NAME }} diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index d555e3b..5a2fa00 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout Redmine - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: redmine/redmine ref: ${{ env.REDMINE_VERSION }} @@ -72,15 +72,16 @@ jobs: sudo mv policy.xml /etc/ImageMagick-6/policy.xml - name: Setup Ruby - uses: actions/setup-ruby@v1 + uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} + bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Setup Bundler run: gem install bundler -v '~> 1.0' - name: Checkout dependencies - Base RSpec plugin - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: jbbarth/redmine_base_rspec path: redmine/plugins/redmine_base_rspec @@ -113,19 +114,19 @@ jobs: bundle exec rails test:scm:setup:subversion - name: Checkout dependencies - Base Deface plugin - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: jbbarth/redmine_base_deface path: redmine/plugins/redmine_base_deface - name: Checkout dependencies - Base StimulusJS plugin - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: nanego/redmine_base_stimulusjs path: redmine/plugins/redmine_base_stimulusjs - name: Checkout plugin - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: redmine/plugins/${{ env.PLUGIN_NAME }} diff --git a/README.md b/README.md index 524e97b..8c4a00a 100644 --- a/README.md +++ b/README.md @@ -8,20 +8,27 @@ Here is a complete list of the features: * Hide optional advanced fields in **version** form * Define a **default project** selected when creating a new issue without being in a specific project * Add **check-all / uncheck-all shortcuts** to roles filters -* Improve **roles synthesis** by adding missing informations about issues permissions and trackers -* Improve load time of **users filters** when there are thousands entries +* Improve **roles synthesis** by adding missing data about issues permissions and trackers +* Use or not the select2 plugin +* Improve load time of **users filters** when there are thousands entries and when the select2 plugin is used * Save **note deletion** in issue journal * Fix **pasted images** when using Chrome (may be fixed in future Redmine versions according to this issue https://www.redmine.org/issues/36013) +* **Reminders rake task: add max-delay option** to define the maximum number of days after which reminders stop to be sent +* Add **range** custom-field format +* Apply **default value** to existing-issues custom-fields if field is required and not set +* Add **enabled modules** filter and column in projects list +* Add **prevent issue copy** attribute to trackers +* Add new permission to always see **users's email addresses**, bypassing user email_hiding setting ## Test status |Plugin branch| Redmine Version | Test Status | |-------------|-------------------|------------------| -|master | 4.2.4 | [![4.2.4][1]][5] | -|master | 4.1.6 | [![4.1.6][2]][5] | +|master | 4.2.9 | [![4.2.9][1]][5] | +|master | 4.1.7 | [![4.1.7][2]][5] | |master | master | [![master][3]][5]| -[1]: https://github.com/nanego/redmine_tiny_features/actions/workflows/4_2_4.yml/badge.svg -[2]: https://github.com/nanego/redmine_tiny_features/actions/workflows/4_1_6.yml/badge.svg +[1]: https://github.com/nanego/redmine_tiny_features/actions/workflows/4_2_9.yml/badge.svg +[2]: https://github.com/nanego/redmine_tiny_features/actions/workflows/4_1_7.yml/badge.svg [3]: https://github.com/nanego/redmine_tiny_features/actions/workflows/master.yml/badge.svg [5]: https://github.com/nanego/redmine_tiny_features/actions diff --git a/app/overrides/context_menus/issues.rb b/app/overrides/context_menus/issues.rb new file mode 100644 index 0000000..473f6f5 --- /dev/null +++ b/app/overrides/context_menus/issues.rb @@ -0,0 +1,6 @@ +Deface::Override.new :virtual_path => 'context_menus/issues', + :name => 'hide-copy-issue-if-tracker-prevents-it', + :insert_bottom => 'erb[loud]:contains("context_menu_link l(:button_copy), project_copy_issue_path(@project, @issue)")', + :text => <<-HIDE_LINK + if !@issue.tracker.prevent_issue_copy +HIDE_LINK diff --git a/app/overrides/issues/_action_menu.rb b/app/overrides/issues/_action_menu.rb new file mode 100644 index 0000000..2869d79 --- /dev/null +++ b/app/overrides/issues/_action_menu.rb @@ -0,0 +1,47 @@ +Deface::Override.new :virtual_path => 'issues/_action_menu', + :name => 'hide-copy-issue-if-tracker-prevents-it', + :insert_bottom => 'erb[loud]:contains("link_to l(:button_copy)")', + :text => <<-HIDE_LINK + && !@issue.tracker.prevent_issue_copy +HIDE_LINK +Deface::Override.new :virtual_path => 'issues/_action_menu', + :name => 'call-renderPartialEdit-before-showAndScrollTo', + :replace => "erb[loud]:contains('l(:button_edit)')", + :text => <<-REPLACE_BUTTON + +<% # Put the condition here, not at the beginning of the file, so that we can check the configuration each time we download the page, + # if we put the condition at the beginning of the file it does not execute only once, (start application) %> +<% if Setting.plugin_redmine_tiny_features.key?('do_not_preload_issue_edit_form') %> + <%= link_to l(:button_edit), edit_issue_path(@issue), + :onclick => 'renderPartialEdit(); return false;', + :class => 'icon icon-edit', :accesskey => accesskey(:edit) if @issue.editable? %> + +<% else %> + <%= link_to l(:button_edit), edit_issue_path(@issue), + :onclick => 'showAndScrollTo("update", "issue_notes"); return false;', + :class => 'icon icon-edit', :accesskey => accesskey(:edit) if @issue.editable? %> +<% end %> +REPLACE_BUTTON diff --git a/app/overrides/issues/_action_menu_edit.rb b/app/overrides/issues/_action_menu_edit.rb new file mode 100644 index 0000000..598313c --- /dev/null +++ b/app/overrides/issues/_action_menu_edit.rb @@ -0,0 +1,8 @@ +Deface::Override.new :virtual_path => 'issues/_action_menu_edit', + :name => 'prevents-render-edit-partial-render-it-only-on-edit-button-click', + :replace => "erb[loud]:contains(\"render :partial => 'edit'\")", + :text => <<-RENDER_PARTIAL +<% if !Setting.plugin_redmine_tiny_features.key?('do_not_preload_issue_edit_form') %> + <%= render :partial => 'edit' %> +<% end %> +RENDER_PARTIAL diff --git a/app/overrides/queries/_filters.rb b/app/overrides/queries/_filters.rb index 9677e53..451b9a4 100644 --- a/app/overrides/queries/_filters.rb +++ b/app/overrides/queries/_filters.rb @@ -3,11 +3,17 @@ :insert_after => "div.add-filter", :text => < + // declaration this functions here, in order to generate the path by rails + + function useRedminePluginSelect2(){ + return <%= Setting.plugin_redmine_tiny_features.key?('use_select2') %> + } - // declaration this function here, in order to generate the path by rails function updateSelect2ForElements() { - var activePagination = '<%= Setting["plugin_redmine_tiny_features"]["paginate_issue_filters_values"].present?%>'; - if (activePagination === 'true'){ + var activeSelect2 = useRedminePluginSelect2() + var activePagination = '<%= Setting["plugin_redmine_tiny_features"]["paginate_issue_filters_values"].present? %>'; + + if (activeSelect2 && activePagination === 'true'){ // check if values_author_id_1 defined if ($('#values_author_id_1').length > 0) { setConfigurationForSelect2($('#values_author_id_1'), '<%= author_values_pagination_path %>'); @@ -27,6 +33,5 @@ } } } - SELECT2 \ No newline at end of file diff --git a/app/overrides/trackers/_form.rb b/app/overrides/trackers/_form.rb new file mode 100644 index 0000000..5596f58 --- /dev/null +++ b/app/overrides/trackers/_form.rb @@ -0,0 +1,7 @@ +Deface::Override.new :virtual_path => "trackers/_form", + :name => "add-prevent-copy-issues", + :insert_after => "p:eq(2)", + :text => <<-EOS +

<%= f.check_box :prevent_issue_copy %>

+EOS + diff --git a/app/overrides/users/_preferences.rb b/app/overrides/users/_preferences.rb new file mode 100644 index 0000000..c6ef768 --- /dev/null +++ b/app/overrides/users/_preferences.rb @@ -0,0 +1,8 @@ +Deface::Override.new :virtual_path => "users/_preferences", + :name => "add-condition-disable_email_hiding_before_check_box_hide_mail", + :surround => 'p:contains("check_box :hide_mail")', + :text => < + <%= render_original %> +<% end %> +STRING_EMAIL diff --git a/app/overrides/users/show.rb b/app/overrides/users/show.rb new file mode 100644 index 0000000..05e4f64 --- /dev/null +++ b/app/overrides/users/show.rb @@ -0,0 +1,6 @@ +Deface::Override.new :virtual_path => "users/show", + :name => "add-condition-disable_email_hiding", + :replace => "erb[silent]:contains('unless @user.pref.hide_mail')", + :text => < true) %> +STRING_EMAIL diff --git a/app/views/custom_fields/formats/_range.html.erb b/app/views/custom_fields/formats/_range.html.erb new file mode 100644 index 0000000..69c8277 --- /dev/null +++ b/app/views/custom_fields/formats/_range.html.erb @@ -0,0 +1,14 @@ +

+ + <%= f.text_field :min_value, :size => 5, :no_label => true %> - + <%= f.text_field :max_value, :size => 5, :no_label => true %> +

+ +

+ + <%= f.text_field :steps, :size => 5, :no_label => true %> +

+

+ + <%= f.text_field :default_value, :size => 5, :no_label => true %> +

diff --git a/app/views/issues/_edit_closed.html.erb b/app/views/issues/_edit_closed.html.erb index 4530e23..3e7c631 100644 --- a/app/views/issues/_edit_closed.html.erb +++ b/app/views/issues/_edit_closed.html.erb @@ -34,8 +34,6 @@ }) $("#issue-form").on("change", "#issue_status_id", function () { var status_id = parseInt($(this).val()) - console.log(status_id) - console.log($.inArray(status_id, <%= IssueStatus.where(:is_closed => true).pluck(:id).inspect %>)) if ($.inArray(status_id, <%= IssueStatus.where(:is_closed => true).pluck(:id).inspect %>) < 0) { showIfIssueOpen() } diff --git a/app/views/settings/_redmine_plugin_tiny_features_settings.html.erb b/app/views/settings/_redmine_plugin_tiny_features_settings.html.erb index c4842ca..f7d096d 100644 --- a/app/views/settings/_redmine_plugin_tiny_features_settings.html.erb +++ b/app/views/settings/_redmine_plugin_tiny_features_settings.html.erb @@ -13,7 +13,7 @@

<%= label_tag l("default_open_status") %> <%= select_tag "settings[default_open_status]", options_for_select(IssueStatus.where(is_closed: false).sorted.map { |status| [status.name, status.id] }, - selected: Setting["plugin_redmine_tiny_features"]["default_open_status"]) %> + selected: Setting["plugin_redmine_tiny_features"]["default_open_status"]) %>


@@ -34,13 +34,22 @@


- +<% if Redmine::Plugin.installed?(:redmine_base_select2) %>

+ <%= label_tag '', { style: 'width: auto;' } do %> + <%= check_box_tag "settings[use_select2]", '1', Setting["plugin_redmine_tiny_features"]["use_select2"], :onclick=>"hidePagination()" %> + <%= l("setting_use_select2") %> + <% end %> +

+ + +

<%= label_tag '', { style: 'width: auto;' } do %> <%= check_box_tag "settings[paginate_issue_filters_values]", '1', Setting["plugin_redmine_tiny_features"]["paginate_issue_filters_values"] %> <%= l("setting_paginate_issue_filters_values") %> <% end %>

+<% end %>

@@ -50,10 +59,45 @@ <% end %>

+ +

+ <%= label_tag '', { style: 'width: auto;' } do %> + <%= check_box_tag "settings[disable_email_hiding]", '1', Setting["plugin_redmine_tiny_features"]["disable_email_hiding"] %> + <%= l("setting_disable_email_hiding") %> + <% end %> +

+ +

+ <%= label_tag '', { style: 'width: auto;' } do %> + <%= check_box_tag "settings[do_not_preload_issue_edit_form]", '1', Setting["plugin_redmine_tiny_features"]["do_not_preload_issue_edit_form"] %> + <%= l("setting_do_not_preload_issue_edit_form") %> + <% end %> +

+ <%= javascript_tag do %> $(function() { - if ((typeof $().select2) === 'function') { - $('#settings_default_project').select2(); - } + if ((typeof $().select2) === 'function') { + $('#settings_default_project').select2(); + } }); + + // Avoid to activate pagination if select2 is not active + function hidePagination(){ + paginationObject = $('#paginate_issue_filters_values input')[0] + paginationLabel = $('#paginate_issue_filters_values label')[0] + select2Checked = $('#settings_use_select2')[0].checked + + if(!select2Checked){ + paginationObject.checked = false + paginationObject.disabled = true + paginationLabel.classList.add("disabled-settings-redmine-tiny-features") + + } else { + paginationObject.disabled = false + paginationLabel.classList.remove("disabled-settings-redmine-tiny-features") + } + } + + // Add to disabled option pagination when select2 option is not checked when the page is launched + hidePagination() <% end %> diff --git a/assets/javascripts/redmine_tiny_features.js b/assets/javascripts/redmine_tiny_features.js index 33ebf40..3a97704 100644 --- a/assets/javascripts/redmine_tiny_features.js +++ b/assets/javascripts/redmine_tiny_features.js @@ -35,8 +35,12 @@ function redminePluginDatetimeCustomFieldInstalled() { return !(typeof buildDateTimeFilterRow === "undefined") } +// check if plugin redmine_limited_visibility is installed +function redminePluginLimitedVisibilityIsInstalled() { + return !(typeof buildListVisibilityFilterRow === "undefined") +} + $(function() { - if (!redminePluginDatetimeCustomFieldInstalled()) { addFilter = function (field, operator, values) { var fieldId = field.replace('.', '_'); var tr = $('#tr_'+fieldId); @@ -55,42 +59,67 @@ $(function() { if (tr.length > 0) { tr.show(); } else { - buildFilterRow(field, operator, values); + if (redminePluginDatetimeCustomFieldInstalled() && (filterOptions['type'] == "date" || filterOptions['type'] == "date_past" )) { + buildDateTimeFilterRow(field, operator, values); + } else { + if (redminePluginLimitedVisibilityIsInstalled() && (filterOptions['type'] == "list_visibility")) { + buildListVisibilityFilterRow(field, operator, values); + } else { + buildFilterRow(field, operator, values); + } + } } $('#cb_'+fieldId).prop('checked', true); - toggleFilter(field); - toggleMultiSelectIconInit(); + + if (redminePluginLimitedVisibilityIsInstalled() && ($("#operators_" + fieldId).val() == 'mine')) { + enableValues(field, []); + } else { + toggleFilter(field); + } + + if(!redminePluginDatetimeCustomFieldInstalled()){ + toggleMultiSelectIconInit(); + } $('#add_filter_select').val('').find('option').each(function() { if ($(this).attr('value') == field) { $(this).attr('disabled', true); } }); - addSelect2ToSelectTagsForTinyFeatures(); } - } }); +function toggleMultiSelect(el) { + if (el.attr('multiple')) { + el.removeAttr('multiple'); + el.attr('size', 1); + } else { + el.attr('multiple', true); + if (el.children().length > 10) + el.attr('size', 10); + else + el.attr('size', 4); + } + // Patch + addSelect2ToSelectTagsForTinyFeatures() +} + /* Override for addSelect2ToSelectTags, because of addFilter takes time ,we should wait for it to finish. We use this method of override by variable to ensure that it is executed even if the function addSelect2ToSelectTags of plugin redmine_datetime_custom_field installed loaded after addSelect2ToSelectTags of this plugin */ -addSelect2ToSelectTags = function(){ - $(document).ready(function(){ - addSelect2ToSelectTagsForTinyFeatures(); - }); -} - function addSelect2ToSelectTagsForTinyFeatures() { - if ((typeof $().select2) === 'function') { - $('#filters select.value').select2({ - containerCss: {width: '300px', minwidth: '300px'}, - width: 'style' - }); - updateSelect2ForElements(); - } + $(document).ready(function(){ + if (((typeof $().select2) === 'function') && useRedminePluginSelect2() ) { + $('#filters select.value').select2({ + containerCss: {width: '300px', minwidth: '300px'}, + width: 'style' + }); + updateSelect2ForElements(); + } + }) } function setConfigurationForSelect2(element, url) { diff --git a/assets/stylesheets/tiny_features.css b/assets/stylesheets/tiny_features.css index 78e3454..a37619c 100644 --- a/assets/stylesheets/tiny_features.css +++ b/assets/stylesheets/tiny_features.css @@ -13,3 +13,14 @@ table.trackers-permissions td.role {color:#999;font-size:90%;font-weight:normal !important;text-align:center;vertical-align:bottom;} .permissions-header {padding-left: 16px;display: inline-block;} + +.disabled-settings-redmine-tiny-features { + color : #aaa +} + +.range_selected_value { + color: #505050; + line-height: 1.5em; + word-wrap: break-word; + padding-left: 8px; +} diff --git a/config/locales/en.yml b/config/locales/en.yml index 06b170d..a6a5108 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -20,4 +20,14 @@ en: label_note: 'Note ' setting_journalize_note_deletion: Journalize the note deletion label_of: 'of ' - text_note_deleted: "%{label} (#%{old}) deleted" \ No newline at end of file + text_note_deleted: "%{label} (#%{old}) deleted" + setting_use_select2: Apply select2 to filters fields + label_range: "Range" + label_min_max_range: "Min / Max values" + label_steps: "Step" + field_module_enabled: "Enabled modules" + field_prevent_issue_copy: "Prevent issue copy" + setting_disable_email_hiding: "Disable the emails hiding function" + permission_always_see_user_email_addresses: "Always see user email addresses" + project_module_user_email: "User email addresses" + setting_do_not_preload_issue_edit_form: "Load the issue's edit form only by clicking on the Edit link" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index f3c9d88..461c49e 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -21,3 +21,13 @@ fr: setting_journalize_note_deletion: Traçabilité des effacements de note label_of: 'de ' text_note_deleted: "%{label} (#%{old}) supprimée" + setting_use_select2: Activer Select2 pour faciliter l'utilisation des listes déroulantes + label_range: "Intervalle" + label_min_max_range: "Valeurs minimale et maximale" + label_steps: "Incrément" + field_module_enabled: "Modules activés" + field_prevent_issue_copy: "Empêcher la copie de demandes" + setting_disable_email_hiding: "Désactiver la fonction de masquage des emails" + permission_always_see_user_email_addresses: "Toujours voir les adresses mail des utilisateurs" + project_module_user_email: "Adresses mail des utilisateurs" + setting_do_not_preload_issue_edit_form: "Charger le formulaire d'édition de la demande uniquement en cliquant sur le lien Modifier" diff --git a/config/routes.rb b/config/routes.rb index 18a99c2..c5d2787 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,6 @@ resources :projects do put :custom_field_enumerations, :controller => 'project_custom_field_enumerations', action: 'update', as: 'update_custom_field_enumerations' end +match 'issues/render_form_by_ajax/:id', :controller => 'issues', :action => 'render_form_by_ajax', :via => :get, :as => 'render_form_by_ajax' match 'queries/author_values_pagination', :controller => 'queries', :action => 'author_values_pagination' , :via => :get, :as => 'author_values_pagination' match 'queries/assigned_to_values_pagination', :controller => 'queries', :action => 'assigned_to_values_pagination' , :via => :get, :as => 'assigned_to_values_pagination' \ No newline at end of file diff --git a/db/migrate/002_add_step_to_custom_fields.rb b/db/migrate/002_add_step_to_custom_fields.rb new file mode 100644 index 0000000..4487b15 --- /dev/null +++ b/db/migrate/002_add_step_to_custom_fields.rb @@ -0,0 +1,7 @@ +class AddStepToCustomFields < ActiveRecord::Migration[5.2] + def change + add_column :custom_fields, :steps, :integer + add_column :custom_fields, :min_value, :integer + add_column :custom_fields, :max_value, :integer + end +end diff --git a/db/migrate/003_add_trackers_prevent_copy_issues.rb b/db/migrate/003_add_trackers_prevent_copy_issues.rb new file mode 100644 index 0000000..482bab4 --- /dev/null +++ b/db/migrate/003_add_trackers_prevent_copy_issues.rb @@ -0,0 +1,5 @@ +class AddTrackersPreventCopyIssues < ActiveRecord::Migration[5.2] + def change + add_column :trackers, :prevent_issue_copy, :boolean, :default => false + end +end diff --git a/init.rb b/init.rb index 35088fc..9819f2d 100644 --- a/init.rb +++ b/init.rb @@ -15,6 +15,11 @@ require_dependency 'redmine_tiny_features/time_entry_query_patch' require_dependency 'redmine_tiny_features/issues_helper_patch' require_dependency 'redmine_tiny_features/journal_patch' + require_dependency 'redmine_tiny_features/tracker_patch' + require_dependency 'redmine_tiny_features/mailer_patch' + require_dependency 'redmine_tiny_features/custom_field_enumeration_patch' + require_dependency 'redmine_tiny_features/custom_field_patch' + require_dependency 'redmine_tiny_features/project_query_patch' end Redmine::Plugin.register :redmine_tiny_features do @@ -26,14 +31,20 @@ author_url 'https://github.com/nanego' requires_redmine_plugin :redmine_base_rspec, :version_or_higher => '0.0.4' if Rails.env.test? requires_redmine_plugin :redmine_base_deface, :version_or_higher => '0.0.1' - permission :manage_project_enumerations, {} + + project_module :user_email do + permission :always_see_user_email_addresses ,{} + end settings partial: 'settings/redmine_plugin_tiny_features_settings', default: { 'warning_message_on_closed_issues': '1', 'open_issue_when_editing_closed_issues': '', 'simplified_version_form': '1', 'default_project': '', - 'paginate_issue_filters_values': Rails.env.test? ? '0' : '1', - 'journalize_note_deletion': Rails.env.test? ? '0' : '1' + 'paginate_issue_filters_values': Rails.env.test? || !(Redmine::Plugin.installed?(:redmine_base_select2)) ? '0' : '1', + 'journalize_note_deletion': Rails.env.test? ? '0' : '1', + 'use_select2': Rails.env.test? || !(Redmine::Plugin.installed?(:redmine_base_select2)) ? '0' : '1', + 'do_not_preload_issue_edit_form': Rails.env.test? ? '0' : '1', + 'disable_email_hiding': '', } end diff --git a/lib/redmine_tiny_features/custom_field_enumeration_patch.rb b/lib/redmine_tiny_features/custom_field_enumeration_patch.rb new file mode 100644 index 0000000..5451669 --- /dev/null +++ b/lib/redmine_tiny_features/custom_field_enumeration_patch.rb @@ -0,0 +1,5 @@ +require_dependency 'custom_field_enumeration' + +class CustomFieldEnumeration < ActiveRecord::Base + has_many :disabled_custom_field_enumerations, :dependent => :delete_all +end \ No newline at end of file diff --git a/lib/redmine_tiny_features/custom_field_patch.rb b/lib/redmine_tiny_features/custom_field_patch.rb new file mode 100644 index 0000000..11789f7 --- /dev/null +++ b/lib/redmine_tiny_features/custom_field_patch.rb @@ -0,0 +1,8 @@ +require_dependency 'custom_field' + +class CustomField < ActiveRecord::Base + ##### PATCH ,to call the destroy method of (CustomFieldEnumeration has :dependent => :delete_all) + has_many :enumerations, lambda {order(:position)}, + :class_name => 'CustomFieldEnumeration', + :dependent => :destroy +end \ No newline at end of file diff --git a/lib/redmine_tiny_features/field_format_patch.rb b/lib/redmine_tiny_features/field_format_patch.rb index a7838d4..c28e0d7 100644 --- a/lib/redmine_tiny_features/field_format_patch.rb +++ b/lib/redmine_tiny_features/field_format_patch.rb @@ -14,5 +14,92 @@ def possible_values_records(custom_field, object = nil) end end + class List < Base + # Renders the edit tag as check box or radio tags + def check_box_edit_tag(view, tag_id, tag_name, custom_value, options={}) + opts = [] + unless custom_value.custom_field.multiple? || custom_value.custom_field.is_required? + opts << ["(#{l(:label_none)})", ''] + end + opts += possible_custom_value_options(custom_value) + s = ''.html_safe + tag_method = custom_value.custom_field.multiple? ? :check_box_tag : :radio_button_tag + opts.each do |label, value| + value ||= label + checked = (custom_value.value.is_a?(Array) && custom_value.value.include?(value)) || + custom_value.value.to_s == value + + ################# + ##### START PATCH + if custom_value.value.blank? && custom_value.custom_field.is_required? && custom_value.custom_field.default_value.present? + checked = custom_value.custom_field.default_value == value + end + ## END PATCH + ################# + + tag = view.send(tag_method, tag_name, value, checked, :id => nil) + s << view.content_tag('label', tag + ' ' + label) + end + if custom_value.custom_field.multiple? + s << view.hidden_field_tag(tag_name, '', :id => nil) + end + css = "#{options[:class]} check_box_group" + view.content_tag('span', s, options.merge(:class => css)) + end + end + + class RangeFormat < Numeric + add 'range' + + self.form_partial = 'custom_fields/formats/range' + + def label + "label_range" + end + + field_attributes :steps, :min_value, :max_value + + def edit_tag(view, tag_id, tag_name, custom_value, options = {}) + edit_tag = view.range_field_tag(tag_name, + custom_value.value || custom_value.custom_field.default_value, + options.merge(id: tag_id, + min: custom_value.custom_field.min_value, + max: custom_value.custom_field.max_value, + step: custom_value.custom_field.steps)) + edit_tag << view.content_tag(:span, custom_value.value, class: "range_selected_value") + edit_tag << view.javascript_tag( + <<~JAVASCRIPT + $(document).on("input change", "##{tag_id}", function(e) { + var value = $(this).val(); + $(this).next('.range_selected_value').html(value); + }) + JAVASCRIPT + ) + edit_tag + end + + def cast_single_value(custom_field, value, customized = nil) + value.to_i + end + + def validate_single_value(custom_field, value, customized = nil) + errs = super + errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless /^[+-]?\d+$/.match?(value.to_s.strip) + errs + end + + def query_filter_options(custom_field, query) + { :type => :integer } + end + + def group_statement(custom_field) + order_statement(custom_field) + end + end + end end + +class CustomField < ActiveRecord::Base + safe_attributes("steps", "min_value", "max_value") +end diff --git a/lib/redmine_tiny_features/hooks.rb b/lib/redmine_tiny_features/hooks.rb index 47b498c..d1126c5 100644 --- a/lib/redmine_tiny_features/hooks.rb +++ b/lib/redmine_tiny_features/hooks.rb @@ -2,12 +2,12 @@ module RedmineTinyFeatures class Hooks < Redmine::Hook::ViewListener #adds our css on each page def view_layouts_base_html_head(context) - stylesheet_link_tag("tiny_features", :plugin => "redmine_tiny_features")+ - javascript_include_tag('redmine_tiny_features.js', plugin: 'redmine_tiny_features') + stylesheet_link_tag("tiny_features", :plugin => "redmine_tiny_features") + + javascript_include_tag('redmine_tiny_features.js', plugin: 'redmine_tiny_features') end - def controller_journals_edit_post(context={}) - if Setting["plugin_redmine_tiny_features"]["journalize_note_deletion"].present? + def controller_journals_edit_post(context = {}) + if Setting["plugin_redmine_tiny_features"]["journalize_note_deletion"].present? journal = context[:journal] if context[:journal].notes.blank? journal.issue.init_journal(User.current).note_removed(journal) diff --git a/lib/redmine_tiny_features/issue_query_patch.rb b/lib/redmine_tiny_features/issue_query_patch.rb index 92d8d9d..8d6ff4b 100644 --- a/lib/redmine_tiny_features/issue_query_patch.rb +++ b/lib/redmine_tiny_features/issue_query_patch.rb @@ -14,7 +14,7 @@ module IssueQueryPatch def initialize_available_filters super # Add this condition,because of there are tests for available_filters in redmine core - if Setting["plugin_redmine_tiny_features"]["paginate_issue_filters_values"].present? + if Setting["plugin_redmine_tiny_features"]["use_select2"].present? && Setting["plugin_redmine_tiny_features"]["paginate_issue_filters_values"].present? add_available_filter( "author_id", :type => :list, :values => lambda { [] } diff --git a/lib/redmine_tiny_features/issues_controller_patch.rb b/lib/redmine_tiny_features/issues_controller_patch.rb index db5ebd9..40b250b 100644 --- a/lib/redmine_tiny_features/issues_controller_patch.rb +++ b/lib/redmine_tiny_features/issues_controller_patch.rb @@ -3,6 +3,17 @@ class IssuesController append_before_action :find_optional_project_for_new_issue, :only => [:new] + skip_before_action :authorize, :only => [:render_form_by_ajax] + + + def render_form_by_ajax + @issue = Issue.find(params[:id]) + + return unless update_issue_from_params + + render json: { html: render_to_string(partial: 'edit') } + + end private diff --git a/lib/redmine_tiny_features/mailer_patch.rb b/lib/redmine_tiny_features/mailer_patch.rb new file mode 100644 index 0000000..a9dd415 --- /dev/null +++ b/lib/redmine_tiny_features/mailer_patch.rb @@ -0,0 +1,59 @@ +require_dependency 'mailer' + +class Mailer < ActionMailer::Base + + # Sends reminders to issue assignees + # Available options: + # * :days => how many days in the future to remind about (defaults to 7) + # * :tracker => id of tracker for filtering issues (defaults to all trackers) + # * :project => id or identifier of project to process (defaults to all projects) + # * :users => array of assigned user/group ids who should be reminded + # * :version => name of target version for filtering issues (defaults to none) + ####### ADDED BY TINY FEATURES PLUGIN 1/3 ####### + # * :max_delay => ignore older issues: how many days after due date to stop sending reminders (defaults to none) + def self.reminders(options = {}) + days = options[:days] || 7 + ####### ADDED BY TINY FEATURES PLUGIN 2/3 ####### + max_delay = options[:max_delay] || nil + project = options[:project] ? Project.find(options[:project]) : nil + tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil + target_version_id = options[:version] ? Version.named(options[:version]).pluck(:id) : nil + if options[:version] && target_version_id.blank? + raise ActiveRecord::RecordNotFound.new("Couldn't find Version named #{options[:version]}") + end + + user_ids = options[:users] + + scope = + Issue.open.where( + "#{Issue.table_name}.assigned_to_id IS NOT NULL" \ + " AND #{Project.table_name}.status = #{Project::STATUS_ACTIVE}" \ + " AND #{Issue.table_name}.due_date <= ?", days.day.from_now.to_date + ) + scope = scope.where(:assigned_to_id => user_ids) if user_ids.present? + scope = scope.where(:project_id => project.id) if project + scope = scope.where(:fixed_version_id => target_version_id) if target_version_id.present? + scope = scope.where(:tracker_id => tracker.id) if tracker + ####### ADDED BY TINY FEATURES PLUGIN 3/3 ####### + scope = scope.where("#{Issue.table_name}.due_date > ?", max_delay.day.ago.to_date) if max_delay + issues_by_assignee = scope.includes(:status, :assigned_to, :project, :tracker). + group_by(&:assigned_to) + issues_by_assignee.keys.each do |assignee| + if assignee.is_a?(Group) + assignee.users.each do |user| + issues_by_assignee[user] ||= [] + issues_by_assignee[user] += issues_by_assignee[assignee] + end + end + end + + issues_by_assignee.each do |assignee, issues| + if assignee.is_a?(User) && assignee.active? && issues.present? + visible_issues = issues.select { |i| i.visible?(assignee) } + visible_issues.sort! { |a, b| (a.due_date <=> b.due_date).nonzero? || (a.id <=> b.id) } + reminder(assignee, visible_issues, days).deliver_later if visible_issues.present? + end + end + end + +end diff --git a/lib/redmine_tiny_features/project_patch.rb b/lib/redmine_tiny_features/project_patch.rb index 00a05a5..2e7b9e8 100644 --- a/lib/redmine_tiny_features/project_patch.rb +++ b/lib/redmine_tiny_features/project_patch.rb @@ -2,6 +2,10 @@ class Project < ActiveRecord::Base - has_many :disabled_custom_field_enumerations + has_many :disabled_custom_field_enumerations, :dependent => :delete_all + + def module_enabled + EnabledModule.where("project_id = ? ", self.id).order(:name).map { |e_m| l("project_module_#{e_m.name}") } + end end diff --git a/lib/redmine_tiny_features/project_query_patch.rb b/lib/redmine_tiny_features/project_query_patch.rb new file mode 100644 index 0000000..41d2594 --- /dev/null +++ b/lib/redmine_tiny_features/project_query_patch.rb @@ -0,0 +1,38 @@ +require_dependency 'query' +require_dependency 'project_query' + +class ProjectQuery < Query + self.available_columns << QueryColumn.new(:module_enabled, :sortable => false, :default_order => 'asc') +end + +module PluginRedmineTinyFeatures + module ProjectQueryPatch + + def initialize_available_filters + super + + module_values = EnabledModule.all.collect { |s| [l("project_module_#{s.name}"), s.name] }.sort_by { |v| v.first }.uniq + add_available_filter("module_enabled", :type => :list_subprojects, :values => module_values) + end + + def sql_for_module_enabled_field(field, operator, value) + case operator + when "!*", "*" + module_enabled_table = EnabledModule.table_name + project_table = Project.table_name + #return only the projects for which a particular module or module group is activated + "#{project_table}.id #{ operator == '*' ? 'IN' : 'NOT IN' } (SELECT #{module_enabled_table}.project_id FROM #{module_enabled_table} " + + "JOIN #{project_table} ON #{module_enabled_table}.project_id = #{project_table}.id " + ') ' + when "=", "!" + module_enabled_table = EnabledModule.table_name + project_table = Project.table_name + #return only the projects for which a particular module or module group is activated + "#{project_table}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{module_enabled_table}.project_id FROM #{module_enabled_table} " + + "JOIN #{project_table} ON #{module_enabled_table}.project_id = #{project_table}.id AND " + + sql_for_field('name', '=', value, module_enabled_table, 'name') + ') ' + end + end + end +end + +ProjectQuery.prepend PluginRedmineTinyFeatures::ProjectQueryPatch diff --git a/lib/redmine_tiny_features/time_entry_query_patch.rb b/lib/redmine_tiny_features/time_entry_query_patch.rb index 3382a58..9b2ac51 100644 --- a/lib/redmine_tiny_features/time_entry_query_patch.rb +++ b/lib/redmine_tiny_features/time_entry_query_patch.rb @@ -17,7 +17,7 @@ module TimeEntryQueryPatch def initialize_available_filters super # Add this condition,because of there are tests for available_filters in redmine core - if Setting["plugin_redmine_tiny_features"]["paginate_issue_filters_values"].present? + if Setting["plugin_redmine_tiny_features"]["use_select2"].present? && Setting["plugin_redmine_tiny_features"]["paginate_issue_filters_values"].present? add_available_filter( "user_id", :type => :list_optional, :values => lambda { [] } diff --git a/lib/redmine_tiny_features/tracker_patch.rb b/lib/redmine_tiny_features/tracker_patch.rb new file mode 100644 index 0000000..4877708 --- /dev/null +++ b/lib/redmine_tiny_features/tracker_patch.rb @@ -0,0 +1,3 @@ +class Tracker < ActiveRecord::Base + safe_attributes('prevent_issue_copy') +end diff --git a/lib/tasks/reminder.rake b/lib/tasks/reminder.rake new file mode 100644 index 0000000..874cf9c --- /dev/null +++ b/lib/tasks/reminder.rake @@ -0,0 +1,47 @@ +# Redmine - project management software +# Copyright (C) 2006-2021 Jean-Philippe Lang +# +# 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. + +desc <<-END_DESC +Send reminders about issues due in the next days. + +Available options: + * days => number of days to remind about (defaults to 7) + * tracker => id of tracker (defaults to all trackers) + * project => id or identifier of project (defaults to all projects) + * users => comma separated list of user/group ids who should be reminded + * version => name of target version for filtering issues (defaults to none) + * max_delay => ignore older issues: how many days after due date to stop sending reminders (defaults to none) + +Example: + rails redmine_tiny_features:send_reminders days=7 users="1,23, 56" max_delay=100 RAILS_ENV="production" +END_DESC + +namespace :redmine_tiny_features do + task :send_reminders => :environment do + options = {} + options[:days] = ENV['days'].presence&.to_i + options[:project] = ENV['project'].presence + options[:tracker] = ENV['tracker'].presence&.to_i + options[:users] = ENV['users'].presence.to_s.split(',').each(&:strip!) + options[:version] = ENV['version'].presence + options[:max_delay] = ENV['max_delay'].presence&.to_i + + Mailer.with_synched_deliveries do + Mailer.reminders(options) + end + end +end diff --git a/spec/controllers/issues_controller_spec.rb b/spec/controllers/issues_controller_spec.rb new file mode 100644 index 0000000..5a8a355 --- /dev/null +++ b/spec/controllers/issues_controller_spec.rb @@ -0,0 +1,28 @@ +require "spec_helper" + +describe IssuesController, type: :controller do + render_views + + fixtures :projects, :users, :members, :member_roles, :roles, + :issues, :journals, :journal_details, :enabled_modules, + :trackers, :issue_statuses, :enumerations, :custom_fields, + :custom_values, :custom_fields_projects, :projects_trackers + + before do + User.current = User.find(1) + @request.session[:user_id] = 1 # admin + end + + describe "range custom-field format" do + it "can sum several range fields" do + field = IssueCustomField.generate!(:field_format => 'range', :is_for_all => true) + CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '20') + CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '30') + get(:index, :params => { :t => ["cf_#{field.id}"] }) + assert_response :success + assert_select '.query-totals' + assert_select ".total-for-cf-#{field.id} span.value", :text => '50' + end + end + +end diff --git a/spec/controllers/journals_controller_spec.rb b/spec/controllers/journals_controller_spec.rb index 25a7e28..fa53c7d 100644 --- a/spec/controllers/journals_controller_spec.rb +++ b/spec/controllers/journals_controller_spec.rb @@ -20,6 +20,7 @@ "warning_message_on_closed_issues"=>"1", "default_open_status"=>"2", "default_project"=>"1", + "use_select2"=>"1", "paginate_issue_filters_values"=>"1", "journalize_note_deletion"=>"1" } diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb new file mode 100644 index 0000000..b415625 --- /dev/null +++ b/spec/controllers/projects_controller_spec.rb @@ -0,0 +1,61 @@ +require "spec_helper" +require "active_support/testing/assertions" + +describe ProjectsController, type: :controller do + include ActiveSupport::Testing::Assertions + + render_views + + fixtures :users, :projects, :enabled_modules + + before do + @request.session[:user_id] = 1 #=> admin ; permissions are hard... + end + + context "Add a filter on the projects page: module activated" do + + it "should query use module_enabled when index with column module_enabled" do + columns = ['name', 'module_enabled'] + get :index, params: { :set_filter => 1, :c => columns } + expect(response).to be_successful + # query should use specified columns + query = assigns(:query) + + assert_kind_of ProjectQuery, query + expect(query.column_names.map(&:to_s)).to eq columns + end + + it "should ensure that the changes are compatible with the CSV" do + module_test = "issue_tracking" + columns = ['name', 'module_enabled'] + + get :index, params: { :set_filter => 1, + :f => ["module_enabled"], + :op => { "module_enabled" => "=" }, + :v => { "module_enabled" => [module_test] }, + :c => columns, + :format => 'csv' } + + expect(response).to be_successful + expect(response.media_type).to eq 'text/csv' + + lines = response.body.chomp.split("\n") + + expect(lines[0].split(',')[1]).to eq "Enabled modules" + + # projects for which the module Issue tracking is enabled + ids = EnabledModule.where(name: module_test).map{ |e_m| e_m.project_id } + # except the first line ( names of columns ) + expect(lines.count - 1).to eq(ids.count) + + project_csv = [] + + lines.count.times do |count| + project_csv.push( lines[count].split(',')[0]) if count > 0 + expect(lines[count].split(',').to_s).to include("Issue tracking") if count > 0 + end + + expect(project_csv.sort).to eq(Project.where(id: ids).map(&:name).sort) + end + end +end \ No newline at end of file diff --git a/spec/controllers/queries_controller_spec.rb b/spec/controllers/queries_controller_spec.rb index 8200e54..0185e44 100644 --- a/spec/controllers/queries_controller_spec.rb +++ b/spec/controllers/queries_controller_spec.rb @@ -5,7 +5,6 @@ describe QueriesController, type: :controller do - fixtures :users, :projects, :members, :roles before do diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb new file mode 100644 index 0000000..4662f5e --- /dev/null +++ b/spec/controllers/users_controller_spec.rb @@ -0,0 +1,59 @@ +require "spec_helper" +require "active_support/testing/assertions" + +describe UsersController, type: :controller do + include ActiveSupport::Testing::Assertions + + render_views + + fixtures :users, :email_addresses + + before do + @request.session[:user_id] = 1 #=> admin ; permissions are hard... + end + + context "Disable emails hiding function" do + + it "should show user's email when the option is selected, even if the user hides his email" do + Setting["plugin_redmine_tiny_features"]["disable_email_hiding"] = '1' + expect(User.find(2).pref.hide_mail).to eq true + get :show, params: { :id => 2 } + + expect(response.body).to include(User.find(2).mail) + end + + it "should hide user's email when the option is unselected, if the user hides his email" do + @request.session[:user_id] = 3 + Setting["plugin_redmine_tiny_features"]["disable_email_hiding"] = '' + expect(User.find(2).pref.hide_mail).to eq true + get :show, params: { :id => 2 } + + expect(response.body).to_not include(User.find(2).mail) + end + + it "should hide the option (Hide my email address) when the option is selected" do + Setting["plugin_redmine_tiny_features"]["disable_email_hiding"] = '1' + get :edit, params: { :id => 2 } + expect(response.body).to_not include("Hide my email address") + end + + it "should show the option (Hide my email address) when the option is unselected" do + Setting["plugin_redmine_tiny_features"]["disable_email_hiding"] = '' + get :edit, params: { :id => 2 } + expect(response.body).to include("Hide my email address") + end + + it "should show user's email when the current use has the permission always_see_user_email_addresses" do + User.current = User.find(1) + role_test = Role.find(1) + role_test.add_permission!(:always_see_user_email_addresses) + role_test.save + + expect(User.find(2).pref.hide_mail).to eq true + get :show, params: { :id => 2 } + + expect(response.body).to include(User.find(2).mail) + end + end + +end \ No newline at end of file diff --git a/spec/models/custom_field_spec.rb b/spec/models/custom_field_spec.rb new file mode 100644 index 0000000..fcf147e --- /dev/null +++ b/spec/models/custom_field_spec.rb @@ -0,0 +1,72 @@ +require "spec_helper" + +describe "CustomField" do + describe "Range custom-field format" do + + it "validates Range custom-field before saving" do + field = CustomField.new(:name => 'test_before_validation', :field_format => 'range') + field.min_value = 0 + field.max_value = 20 + field.steps = 2 + field.searchable = true + expect(field.save).to be_truthy + + field.reload + + expect(field.steps).to eq 2 + expect(field.max_value).to eq 20 + expect(field.searchable).to be_falsey # Integer values are not searchable + end + + it "validates the default_value" do + field = CustomField.new(:name => 'Test', :field_format => 'range') + field.default_value = 'abc' + expect(field.valid?).to be_falsey + field.default_value = '6' + expect(field.valid?).to be_truthy + end + + it "validates possible values" do + f = CustomField.new(:field_format => 'range') + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + assert !f.valid_field_value?(' ') + assert f.valid_field_value?('123') + assert f.valid_field_value?(' 123 ') + assert f.valid_field_value?('+123') + assert f.valid_field_value?('-123') + assert !f.valid_field_value?('9abc') + assert f.valid_field_value?(123) + end + + describe "Update the table disabled_custom_field_enumerations in case of cascade deleting" do + let(:project) { Project.find(1) } + let(:field) { CustomField.create(:name => 'Test', :field_format => 'enumeration') } + let(:c_f_e1) { CustomFieldEnumeration.create(name: 'val1', position: 1, active: true, custom_field_id: field.id) } + let(:c_f_e2) { CustomFieldEnumeration.create(name: 'val2', position: 2, active: true, custom_field_id: field.id) } + + before do + CustomFieldEnumeration.create(name: 'val3', position: 3, active: true, custom_field_id: field.id) + DisabledCustomFieldEnumeration.create(project: project, custom_field_enumeration_id: c_f_e1.id) + DisabledCustomFieldEnumeration.create(project: project, custom_field_enumeration_id: c_f_e2.id) + expect(DisabledCustomFieldEnumeration.count).to eq(2) + end + + it "Should update the table disabled_custom_field_enumerations when deleting a project" do + project.destroy + expect(DisabledCustomFieldEnumeration.count).to eq(0) + end + + it "Should update the table disabled_custom_field_enumerations when deleting a customField" do + field.destroy + expect(DisabledCustomFieldEnumeration.count).to eq(0) + end + + it "Should update the table disabled_custom_field_enumerations when deleting a CustomFieldEnumeration" do + c_f_e1.destroy + expect(DisabledCustomFieldEnumeration.count).to eq(1) + end + end + end +end diff --git a/spec/models/issue_query_patch_spec.rb b/spec/models/issue_query_patch_spec.rb index 33aea92..3473734 100644 --- a/spec/models/issue_query_patch_spec.rb +++ b/spec/models/issue_query_patch_spec.rb @@ -13,10 +13,11 @@ it "should contain empty values for available_filter author_id, assigned_to_id, updated_by, last_updated_by" do - Setting.send "plugin_redmine_tiny_features=", { + Setting["plugin_redmine_tiny_features"] = { "warning_message_on_closed_issues"=>"1", "default_open_status"=>"2", "default_project"=>"1", + "use_select2"=>"1", "paginate_issue_filters_values"=>"1" } @@ -26,4 +27,36 @@ expect(issue_query.available_filters["updated_by"][:values]).to be_empty expect(issue_query.available_filters["last_updated_by"][:values]).to be_empty end + + it "should not contain empty values for available_filter author_id, assigned_to_id, updated_by, last_updated_by (use_select2 deactivated only)" do + + Setting["plugin_redmine_tiny_features"] = { + "warning_message_on_closed_issues"=>"1", + "default_open_status"=>"2", + "default_project"=>"1", + "paginate_issue_filters_values"=>"1" + } + + issue_query = IssueQuery.new + expect(issue_query.available_filters["author_id"][:values]).not_to be_empty + expect(issue_query.available_filters["assigned_to_id"][:values]).not_to be_empty + expect(issue_query.available_filters["updated_by"][:values]).not_to be_empty + expect(issue_query.available_filters["last_updated_by"][:values]).not_to be_empty + end + + it "should not contain empty values for available_filter author_id, assigned_to_id, updated_by, last_updated_by (use_select2 and paginate_issue_filters_values deactivated)" do + + Setting["plugin_redmine_tiny_features"] = { + "warning_message_on_closed_issues"=>"1", + "default_open_status"=>"2", + "default_project"=>"1", + } + + issue_query = IssueQuery.new + expect(issue_query.available_filters["author_id"][:values]).not_to be_empty + expect(issue_query.available_filters["assigned_to_id"][:values]).not_to be_empty + expect(issue_query.available_filters["updated_by"][:values]).not_to be_empty + expect(issue_query.available_filters["last_updated_by"][:values]).not_to be_empty + end + end diff --git a/spec/models/mailer_patch_spec.rb b/spec/models/mailer_patch_spec.rb new file mode 100644 index 0000000..9bbf974 --- /dev/null +++ b/spec/models/mailer_patch_spec.rb @@ -0,0 +1,42 @@ +require "spec_helper" +require "rails_helper" + +describe "Mailer" do + + include ActiveSupport::Testing::Assertions + + fixtures :users, :email_addresses, :user_preferences, + :roles, :members, :member_roles, + :issues, :issue_statuses, :issue_relations, + :versions, :trackers, :projects_trackers, + :issue_categories, :enabled_modules, :enumerations + + it "does not send reminders for older issues when using max_delay option" do + + user = User.find(2) + user.pref.update_attribute :time_zone, 'UTC' + + Issue.generate!(:assigned_to => user, :due_date => 20.days.from_now, :subject => 'quux') + Issue.generate!(:assigned_to => user, :due_date => 0.days.from_now, :subject => 'baz') + Issue.generate!(:assigned_to => user, :due_date => 1.days.from_now, :subject => 'qux') + Issue.generate!(:assigned_to => user, :due_date => 10.days.ago, :subject => 'foo') + Issue.generate!(:assigned_to => user, :due_date => 100.days.ago, :subject => 'bar') + ActionMailer::Base.deliveries.clear + + Mailer.reminders(:days => 7, :users => [user.id], :max_delay => 30) + + assert_equal 1, ActionMailer::Base.deliveries.size + + email = ActionMailer::Base.deliveries.last + expect(email).to_not be_nil + + mail_boy = email.parts.first.body.encoded + + expect(mail_boy).to_not match(/quux/) + expect(mail_boy).to match(/foo \(10 days late\)/) + expect(mail_boy).to match(/baz \(Due in 0 days\)/) + expect(mail_boy).to match(/qux \(Due in 1 day\)/) + expect(mail_boy).to_not match(/bar/) + + end +end diff --git a/spec/models/project_query_spec.rb b/spec/models/project_query_spec.rb new file mode 100644 index 0000000..5e92a51 --- /dev/null +++ b/spec/models/project_query_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +describe "ProjectQuery" do + fixtures :users, :projects, :enabled_modules + + before do + User.current = User.find(1) + end + + def find_projects_with_query(query) + Project.where( + query.statement + ).all + end + + it "should ProjectQuery have available_filters module_enabled" do + query = ProjectQuery.new + expect(query.available_filters).to include 'module_enabled' + end + + describe "should filter projects with module_enabled" do + let!(:module_test) { "wiki" } + + it "operator equal =" do + # projects for which the module wiki is enabled + ids = EnabledModule.where(name: module_test).map{|e_m| e_m.project_id } + query = ProjectQuery.new(:name => '_', :filters => { 'module_enabled' => { :operator => '=', :values => [module_test] } }) + result = find_projects_with_query(query) + + expect(result.count).to eq (ids.count) + expect(result.to_a).to eq(Project.where(id: ids)) + end + + it "operator not equal !" do + ids = EnabledModule.where(name: module_test).map{|e_m| e_m.project_id } + query = ProjectQuery.new(:name => '_', :filters => { 'module_enabled' => { :operator => '!', :values => [module_test] } }) + result = find_projects_with_query(query) + + expect(result.count).to eq ((Project.count - ids.count)) + expect(result.to_a).not_to eq(Project.where(id: ids)) + end + + it "operator all *" do + query = ProjectQuery.new(:name => '_', :filters => { 'module_enabled' => { :operator => '*', :values => [''] } }) + result = find_projects_with_query(query) + + expect(result.count).to eq (EnabledModule.all.map{ |e_m| e_m.project_id }.uniq.count) + end + + it "operator any !*" do + query = ProjectQuery.new(:name => '_', :filters => { 'module_enabled' => { :operator => '!*', :values => [''] } }) + result = find_projects_with_query(query) + + expect(result.count).to eq ((Project.all.map(&:id).uniq - EnabledModule.all.map{ |e_m| e_m.project_id }.uniq).count) + end + end +end diff --git a/spec/models/time_entry_query_patch_spec.rb b/spec/models/time_entry_query_patch_spec.rb index d8230dc..f99a630 100644 --- a/spec/models/time_entry_query_patch_spec.rb +++ b/spec/models/time_entry_query_patch_spec.rb @@ -12,10 +12,11 @@ end it "should contain empty values for available_filter author_id, user_id" do - Setting.send "plugin_redmine_tiny_features=", { + Setting["plugin_redmine_tiny_features"] = { "warning_message_on_closed_issues"=>"1", "default_open_status"=>"2", "default_project"=>"1", + "use_select2"=>"1", "paginate_issue_filters_values"=>"1" } diff --git a/spec/models/tracker_query_spec.rb b/spec/models/tracker_query_spec.rb new file mode 100644 index 0000000..2bcc8ab --- /dev/null +++ b/spec/models/tracker_query_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe "TrackerPatch" do + fixtures :trackers + + it "should Tracker#prevent_issue_copy" do + tracker = Tracker.find(1) + tracker.safe_attributes = ({ "prevent_issue_copy" => 1 }) + assert tracker.prevent_issue_copy + + tracker.safe_attributes = ({ "prevent_issue_copy" => 0 }) + assert !tracker.prevent_issue_copy + end +end diff --git a/spec/system/issues_spec.rb b/spec/system/issues_spec.rb index 9416000..8c0d95d 100644 --- a/spec/system/issues_spec.rb +++ b/spec/system/issues_spec.rb @@ -115,5 +115,64 @@ def log_user(login, password) end end -end + describe "option prevent copy issues" do + it "Show link copy when its tracker allows copy issues page(issue/show)" do + visit 'issues/2' + expect(page).to have_selector('a', class: 'icon-copy', text: 'Copy') + end + + it "Show link copy when its tracker allows copy issues page(issue/index)" do + visit 'issues/' + + find('tr#issue-2>td.buttons>a.icon-actions').click + expect(page).to have_selector('a.icon-copy') + end + + it "Hide link copy when its tracker prevents copy issues page(issue/show)" do + tracker_test = Tracker.find(2) + tracker_test.prevent_issue_copy = true + tracker_test.save + + visit 'issues/2' + expect(page).to_not have_selector('a', class: 'icon-copy', text: 'Copy') + end + + it "Hide link copy when its tracker prevents copy issues page(issue/index)" do + tracker_test = Tracker.find(2) + tracker_test.prevent_issue_copy = true + tracker_test.save + visit 'issues/' + find('tr#issue-2>td.buttons>a.icon-actions').click + expect(page).to_not have_selector('a.icon-copy') + end + end + + describe "Load the issue's edit" do + it "Load the form when the option (do_not_preload_issue_edit_form) is unselected" do + visit 'issues/2' + expect(page).to have_selector('#issue-form', visible: :hidden) + end + + it "only on click button edit and the option (do_not_preload_issue_edit_form) is selected" do + Setting.send "plugin_redmine_tiny_features=", { + "warning_message_on_closed_issues" => "1", + "default_open_status" => "2", + "default_project" => "1", + "do_not_preload_issue_edit_form" => "1", + } + visit 'issues/2' + expect(page).to_not have_selector('#issue-form', visible: :hidden) + find('.icon-edit', match: :first).click + # wait for render form + sleep 10 + expect(page).to have_selector('#issue-form') + Setting.send "plugin_redmine_tiny_features=", { + "warning_message_on_closed_issues" => "1", + "default_open_status" => "2", + "default_project" => "1", + "do_not_preload_issue_edit_form" => "0", + } + end + end +end diff --git a/spec/system/settings_redmine_tiny_features_spec.rb b/spec/system/settings_redmine_tiny_features_spec.rb index 9b703f6..6d19fb4 100644 --- a/spec/system/settings_redmine_tiny_features_spec.rb +++ b/spec/system/settings_redmine_tiny_features_spec.rb @@ -4,13 +4,36 @@ fixtures :users it "Should active the option paginate_issue_filters_values" do + if Redmine::Plugin.installed?(:redmine_base_select2) + log_user('admin', 'admin') + visit 'settings/plugin/redmine_tiny_features' + find("input[name='settings[use_select2]']").click + find("input[name='settings[paginate_issue_filters_values]']").click + find("input[name='commit']").click + expect(Setting["plugin_redmine_tiny_features"]["use_select2"]).to eq '1' + expect(Setting["plugin_redmine_tiny_features"]["paginate_issue_filters_values"]).to eq "1" + end + end + + it "Should active the option do_not_preload_issue_edit_form" do log_user('admin', 'admin') + Setting.send "plugin_redmine_tiny_features=", { + "warning_message_on_closed_issues" => "1", + "default_open_status" => "2", + "default_project" => "1", + } visit 'settings/plugin/redmine_tiny_features' - - find("input[name='settings[paginate_issue_filters_values]']").click - find("input[name='commit']").click - expect(Setting["plugin_redmine_tiny_features"]["paginate_issue_filters_values"]).to eq "1" - + + find("input[name='settings[do_not_preload_issue_edit_form]']").click + find("input[name='commit']").click + + expect(Setting["plugin_redmine_tiny_features"]["do_not_preload_issue_edit_form"]).to eq '1' + Setting.send "plugin_redmine_tiny_features=", { + "warning_message_on_closed_issues" => "1", + "default_open_status" => "2", + "default_project" => "1", + "do_not_preload_issue_edit_form" => "0", + } end end diff --git a/spec/views/issues/show.html.erb_spec.rb b/spec/views/issues/show.html.erb_spec.rb index 70b40d9..be1ef47 100644 --- a/spec/views/issues/show.html.erb_spec.rb +++ b/spec/views/issues/show.html.erb_spec.rb @@ -33,7 +33,7 @@ it "contains a warning to prevent to re-open it if a new issue is more appropriate" do Setting["plugin_redmine_tiny_features"]["warning_message_on_closed_issues"] = '1' - + assign(:issue, issue) assign(:journals, issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").to_a) assign(:allowed_statuses, issue.new_statuses_allowed_to(User.current))