Skip to content

Commit

Permalink
Merge pull request #112 from hicknhack-software/feature/redmine4.0
Browse files Browse the repository at this point in the history
Feature/redmine4.0
  • Loading branch information
dmartingit committed Mar 14, 2019
2 parents cb8e6e0 + 4fb5e5c commit d2bfba9
Show file tree
Hide file tree
Showing 26 changed files with 162 additions and 133 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -8,3 +8,4 @@ Gemfile.lock

tmp/rubycritic/
/.bundle
.ruby-version
15 changes: 6 additions & 9 deletions .travis.yml
Expand Up @@ -11,19 +11,18 @@ env:
- PLUGIN=redmine_hourglass
- REDMINE=test_redmine
- TRACE=--trace
- RAILS_ENV=test

matrix:
include:
- name: Ruby 2.0 / Redmine 3.2
rvm: 2.0.0
env:
- REDMINE_VERSION=3.2.9
- DATABASE=SQLITE3
- name: Ruby 2.0 / Redmine 3.3
rvm: 2.0.0
env:
- REDMINE_VERSION=3.3.9
- DATABASE=SQLITE3

- name: Ruby 2.1 / Redmine 3.3
rvm: 2.1.10
Expand Down Expand Up @@ -51,31 +50,29 @@ matrix:
rvm: 2.3.8
env:
- REDMINE_VERSION=3.4.1
- DATABASE=SQLITE3
- name: Ruby 2.3 / Redmine 3.4.9
rvm: 2.3.8
env:
- REDMINE_VERSION=3.4.9
- DATABASE=SQLITE3

- name: Ruby 2.3 / Redmine 4
rvm: 2.3.8
env:
- REDMINE_VERSION=4.0.2

- name: Ruby 2.5 / Redmine 4
rvm: 2.5.3
env:
- REDMINE_VERSION=4.0.2
- DATABASE=SQLITE3

- name: Ruby 2.6 / Redmine 4
rvm: 2.6.1
env:
- REDMINE_VERSION=4.0.2
- DATABASE=SQLITE3

fast_finish: true
allow_failures:
- env:
- REDMINE_VERSION=4.0.2
- DATABASE=SQLITE3
- rvm: 2.6.1

before_install:
- source .travis/clone_redmine.sh
Expand Down
4 changes: 2 additions & 2 deletions .travis/clone_redmine.sh
Expand Up @@ -48,11 +48,11 @@ fi
ln -s "$PATH_TO_PLUGIN" "$PATH_TO_PLUGINS/$PLUGIN"

case $DATABASE in
SQLITE3) cp $PATH_TO_PLUGINS/$PLUGIN/.travis/sqlite3_database.yml config/database.yml
;;
MYSQL) cp $PATH_TO_PLUGINS/$PLUGIN/.travis/mysql_database.yml config/database.yml
;;
POSTGRESQL)
cp $PATH_TO_PLUGINS/$PLUGIN/.travis/postgresql_database.yml config/database.yml
;;
*) cp $PATH_TO_PLUGINS/$PLUGIN/.travis/sqlite3_database.yml config/database.yml
;;
esac
6 changes: 5 additions & 1 deletion app/controllers/concerns/boolean_parsing.rb
Expand Up @@ -4,7 +4,11 @@ module BooleanParsing
def parse_boolean(keys, params)
keys = [keys] if keys.is_a? Symbol
keys.each do |key|
params[key] = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[key])
if Rails::VERSION::MAJOR <= 4
params[key] = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[key])
else
params[key] = ActiveRecord::Type::Boolean.new.cast(params[key])
end
end
params
end
Expand Down
26 changes: 17 additions & 9 deletions app/controllers/hourglass/api_base_controller.rb
Expand Up @@ -14,6 +14,7 @@ class ApiBaseController < ApplicationController
include ::AuthorizationConcern

private

# use only these codes:
# :ok (200)
# :not_modified (304)
Expand All @@ -24,8 +25,8 @@ class ApiBaseController < ApplicationController
# :internal_server_error (500)
def respond_with_error(status, message, **options)
render json: {
message: message.is_a?(Array) && options[:array_mode] == :sentence ? message.to_sentence : message,
status: Rack::Utils.status_code(status)
message: message.is_a?(Array) && options[:array_mode] == :sentence ? message.to_sentence : message,
status: Rack::Utils.status_code(status)
},
status: status
throw :halt unless options[:no_halt]
Expand Down Expand Up @@ -71,18 +72,25 @@ def list_records(klass)
scope = @query.results_scope order: sort_clause
offset, limit = api_offset_and_limit
respond_with_success(
count: scope.count,
offset: offset,
limit: limit,
records: scope.offset(offset).limit(limit).to_a
count: scope.count,
offset: offset,
limit: limit,
records: scope.offset(offset).limit(limit).to_a
)
end

def bulk(params_key = controller_name, &block)
@bulk_success = []
@bulk_errors = []
params[params_key].each_with_index do |(id, params), index|
id, params = "new#{index}", id if id.is_a?(Hash)
entries = params[params_key]
entries = entries.to_unsafe_h if Rails::VERSION::MAJOR >= 5 && entries.instance_of?(ActionController::Parameters)
entries.each_with_index do |(id, params), index|
if Rails::VERSION::MAJOR <= 4
id, params = "new#{index}", id if id.is_a?(Hash)
else
id, params = "new#{index}", id if id.instance_of?(ActionController::Parameters)
params = ActionController::Parameters.new(params) if params.is_a?(Hash)
end
error_preface = id.start_with?('new') ? bulk_error_preface(index, mode: :create) : bulk_error_preface(id)
evaluate_entry bulk_entry(id, params, &block), error_preface
end
Expand Down Expand Up @@ -132,7 +140,7 @@ def internal_server_error(e)
end

def flash_array(type, messages)
flash[type] = render_to_string partial: 'hourglass_ui/flash_array', locals: {messages: messages}
flash[type] = render_to_string partial: 'hourglass_ui/flash_array', locals: { messages: messages }
end

def custom_field_keys(params_hash)
Expand Down
13 changes: 7 additions & 6 deletions app/controllers/hourglass/time_logs_controller.rb
Expand Up @@ -107,6 +107,7 @@ def bulk_destroy
end

private

def time_log_params(params_hash = params.require(:time_log))
parse_boolean :round, params_hash.permit(:start, :stop, :comments, :round, :user_id)
end
Expand All @@ -117,16 +118,16 @@ def create_time_log_params(params_hash = params.require(:time_log))

def split_params
parse_boolean [:round, :insert_new_before],
{
split_at: Time.parse(params[:split_at]),
insert_new_before: params[:insert_new_before],
round: params[:round]
}
{
split_at: Time.parse(params[:split_at]),
insert_new_before: params[:insert_new_before],
round: params[:round]
}
end

def time_booking_params(params_hash = params.require(:time_booking))
parse_boolean :round, params_hash.permit(:comments, :project_id, :issue_id, :activity_id, :round,
custom_field_values: custom_field_keys(params_hash))
custom_field_values: custom_field_keys(params_hash))
end

def time_log_from_id(id = params[:id])
Expand Down
23 changes: 23 additions & 0 deletions app/models/concerns/hourglass/type_parsing.rb
@@ -0,0 +1,23 @@
module Hourglass::TypeParsing
def parse_type(type, attribute)
if Rails::VERSION::MAJOR <= 4
case type
when :boolean
ActiveRecord::Type::Boolean.new.type_cast_from_user(attribute)
when :integer
ActiveRecord::Type::Integer.new.type_cast_from_user(attribute)
when :float
ActiveRecord::Type::Float.new.type_cast_from_user(attribute)
end
else
case type
when :boolean
ActiveRecord::Type::Boolean.new.cast(attribute)
when :integer
ActiveRecord::Type::Integer.new.cast(attribute)
when :float
ActiveRecord::Type::Float.new.cast(attribute)
end
end
end
end
15 changes: 2 additions & 13 deletions app/models/hourglass/global_settings.rb
@@ -1,5 +1,6 @@
module Hourglass
class GlobalSettings
include TypeParsing
include ActiveModel::Model

attr_accessor :round_sums_only,
Expand All @@ -14,8 +15,7 @@ class GlobalSettings

validates :round_sums_only, inclusion: { in: ['true', 'false', '1', '0', true, false] }
validates :round_minimum, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 24 }
validates :round_limit, numericality: { only_integer: true, greater_than_or_equal_to: 0,
less_than_or_equal_to: 100 }
validates :round_limit, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }
validates :round_default, inclusion: { in: ['true', 'false', '1', '0', true, false] }
validates :round_carry_over_due, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 24 }
validates :report_title, length: { maximum: 23 }, presence: true
Expand Down Expand Up @@ -68,16 +68,5 @@ def resolve_types
self.report_logo_width = parse_type :integer, @report_logo_width
self.global_tracker = parse_type :boolean, @global_tracker
end

def parse_type(type, attribute)
case type
when :boolean
ActiveRecord::Type::Boolean.new.type_cast_from_user(attribute)
when :integer
ActiveRecord::Type::Integer.new.type_cast_from_user(attribute)
when :float
ActiveRecord::Type::Float.new.type_cast_from_user(attribute)
end
end
end
end
20 changes: 5 additions & 15 deletions app/models/hourglass/project_settings.rb
@@ -1,5 +1,6 @@
module Hourglass
class ProjectSettings
include TypeParsing
include ActiveModel::Model

attr_accessor :round_sums_only,
Expand All @@ -9,13 +10,12 @@ class ProjectSettings
:round_carry_over_due

validates :round_sums_only, inclusion: { in: ['true', 'false', true, false] }, allow_blank: true
validates :round_minimum, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 24 },
allow_blank: true
validates :round_minimum, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 24 }, allow_blank: true
validates :round_limit, numericality: { only_integer: true, greater_than_or_equal_to: 0,
less_than_or_equal_to: 100 }, allow_blank: true
validates :round_default, inclusion: { in: ['true', 'false', true, false] }, allow_blank: true
validates :round_carry_over_due, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 24 },
allow_blank: true
validates :round_carry_over_due, numericality: { greater_than_or_equal_to: 0,
less_than_or_equal_to: 24 }, allow_blank: true

def initialize(project = nil)
@project = project
Expand All @@ -36,6 +36,7 @@ def update(attributes)
end

private

def from_hash(attributes)
self.round_sums_only = attributes[:round_sums_only]
self.round_minimum = attributes[:round_minimum]
Expand All @@ -61,16 +62,5 @@ def resolve_types
self.round_default = parse_type :boolean, @round_default
self.round_carry_over_due = parse_type :float, @round_carry_over_due
end

def parse_type(type, attribute)
case type
when :boolean
ActiveRecord::Type::Boolean.new.type_cast_from_user(attribute)
when :integer
ActiveRecord::Type::Integer.new.type_cast_from_user(attribute)
when :float
ActiveRecord::Type::Float.new.type_cast_from_user(attribute)
end
end
end
end
2 changes: 1 addition & 1 deletion app/views/hourglass_ui/forms/fields/_activity.slim
Expand Up @@ -2,4 +2,4 @@
.label
= form.label :activity_id
.input
= form.collection_select :activity_id, TimeEntryActivity.applicable(entry.project), :id, :name, {include_blank: !local_assigns[:required]}, disabled: local_assigns[:disabled], required: local_assigns[:required]
= form.collection_select :activity_id, TimeEntryActivity.applicable(entry.project), :id, :name, { include_blank: true }, disabled: local_assigns[:disabled], required: local_assigns[:required]
2 changes: 1 addition & 1 deletion app/views/hourglass_ui/forms/fields/_project.slim
Expand Up @@ -4,4 +4,4 @@
- if local_assigns[:with_link]
= link_to '', project_path(id: entry.project_id || ''), class: css_classes('icon icon-link', ('hidden' unless entry.project))
.input
= form.select :project_id, projects_for_project_select(entry.project), {include_blank: !local_assigns[:required]}, disabled: local_assigns[:disabled], required: local_assigns[:required]
= form.select :project_id, projects_for_project_select(entry.project), { include_blank: true }, disabled: local_assigns[:disabled], required: local_assigns[:required]
2 changes: 1 addition & 1 deletion app/views/hourglass_ui/forms/fields/_user_id.slim
Expand Up @@ -3,4 +3,4 @@
= form.label :user_id
.input
- project = entry && entry.respond_to?(:project) && entry.project || nil
= form.select :user_id, options_from_collection_for_select(user_collection(project), :id, :name, entry.user_id || User.current.id), {include_blank: !local_assigns[:required]}, disabled: local_assigns[:disabled], required: local_assigns[:required]
= form.select :user_id, options_from_collection_for_select(user_collection(project), :id, :name, entry.user_id || User.current.id), { include_blank: true }, disabled: local_assigns[:disabled], required: local_assigns[:required]
4 changes: 2 additions & 2 deletions app/views/hourglass_ui/time_bookings/context_menu.slim
@@ -1,6 +1,6 @@
- ids = @records.map(&:id)
ul
li = context_menu_link t(:button_edit), hourglass_ui_bulk_edit_time_bookings_path(ids: ids), class: 'icon-edit js-show-inline-form-multi', title: t(:button_edit), remote: true, data: {type: 'html'}, disabled: !@records.all? { |time_booking| policy(time_booking).change? }
li = context_menu_link t(:button_delete), @records.length == 1 ? hourglass_time_booking_path(@records.first) : bulk_destroy_hourglass_time_bookings_path(time_bookings: ids), class: 'icon-del js-hourglass-remote', title: t(:button_delete), remote: true, method: :delete, data: {confirm: t(:text_are_you_sure)}, disabled: !@records.all? { |time_booking| policy(time_booking).destroy? }
li = context_menu_link t(:button_edit), hourglass_ui_bulk_edit_time_bookings_path(ids: ids), class: 'icon icon-edit js-show-inline-form-multi', title: t(:button_edit), remote: true, data: {type: 'html'}, disabled: !@records.all? { |time_booking| policy(time_booking).change? }
li = context_menu_link t(:button_delete), @records.length == 1 ? hourglass_time_booking_path(@records.first) : bulk_destroy_hourglass_time_bookings_path(time_bookings: ids), class: 'icon icon-del js-hourglass-remote', title: t(:button_delete), remote: true, method: :delete, data: {confirm: t(:text_are_you_sure)}, disabled: !@records.all? { |time_booking| policy(time_booking).destroy? }


10 changes: 5 additions & 5 deletions app/views/hourglass_ui/time_logs/context_menu.slim
@@ -1,8 +1,8 @@
- ids = @records.map(&:id)
ul
li = context_menu_link t('hourglass.ui.lists.button_book'), hourglass_ui_bulk_book_time_logs_path(ids: ids), class: 'icon-time js-show-inline-form-multi', title: t('hourglass.ui.lists.button_book'), remote: true, data: {type: 'html'}, disabled: !@records.all? { |time_log| policy(time_log).book? }
li = context_menu_link t(:button_edit), hourglass_ui_bulk_edit_time_logs_path(ids: ids), class: 'icon-edit js-show-inline-form-multi', title: t(:button_edit), remote: true, data: {type: 'html'}, disabled: !@records.all? { |time_log| policy(time_log).change? }
li = context_menu_link t(:button_delete), @records.length == 1 ? hourglass_time_log_path(@records.first) : bulk_destroy_hourglass_time_logs_path(time_logs: ids), class: 'icon-del js-hourglass-remote', title: t(:button_delete), remote: true, method: :delete, data: {confirm: t(:text_are_you_sure)}, disabled: !@records.all? { |time_log| policy(time_log).destroy? }
li = context_menu_link t('hourglass.ui.time_logs.button_join'), join_hourglass_time_logs_path(ids: ids), class: 'icon-hourglass-join js-hourglass-remote', title: t('hourglass.ui.time_logs.button_join'), remote: true, data: {method: 'post'}, disabled: ids.length == 1 || !@records.all? { |time_log| policy(time_log).join? } && Hourglass::TimeLog.joinable?(*ids)
li = context_menu_link t('hourglass.ui.lists.button_book'), hourglass_ui_bulk_book_time_logs_path(ids: ids), class: 'icon icon-time js-show-inline-form-multi', title: t('hourglass.ui.lists.button_book'), remote: true, data: {type: 'html'}, disabled: !@records.all? { |time_log| policy(time_log).book? }
li = context_menu_link t(:button_edit), hourglass_ui_bulk_edit_time_logs_path(ids: ids), class: 'icon icon-edit js-show-inline-form-multi', title: t(:button_edit), remote: true, data: {type: 'html'}, disabled: !@records.all? { |time_log| policy(time_log).change? }
li = context_menu_link t(:button_delete), @records.length == 1 ? hourglass_time_log_path(@records.first) : bulk_destroy_hourglass_time_logs_path(time_logs: ids), class: 'icon icon-del js-hourglass-remote', title: t(:button_delete), remote: true, method: :delete, data: {confirm: t(:text_are_you_sure)}, disabled: !@records.all? { |time_log| policy(time_log).destroy? }
li = context_menu_link t('hourglass.ui.time_logs.button_join'), join_hourglass_time_logs_path(ids: ids), class: 'icon icon-hourglass-join js-hourglass-remote', title: t('hourglass.ui.time_logs.button_join'), remote: true, data: {method: 'post'}, disabled: ids.length == 1 || !@records.all? { |time_log| policy(time_log).join? } && Hourglass::TimeLog.joinable?(*ids)
- time_bookings = @records.map(&:time_booking).compact
li = context_menu_link t('hourglass.ui.lists.button_delete_booking'), time_bookings.length == 1 ? hourglass_time_booking_path(time_bookings.first) : bulk_destroy_hourglass_time_bookings_path(time_bookings: time_bookings.map(&:id)), class: 'icon-del js-hourglass-remote', title: t('hourglass.ui.lists.button_delete_booking'), remote: true, method: :delete, data: {confirm: t(:text_are_you_sure)}, disabled: !time_bookings.present? || !time_bookings.all? { |time_booking| policy(time_booking).destroy? }
li = context_menu_link t('hourglass.ui.lists.button_delete_booking'), time_bookings.length == 1 ? hourglass_time_booking_path(time_bookings.first) : bulk_destroy_hourglass_time_bookings_path(time_bookings: time_bookings.map(&:id)), class: 'icon icon-del js-hourglass-remote', title: t('hourglass.ui.lists.button_delete_booking'), remote: true, method: :delete, data: {confirm: t(:text_are_you_sure)}, disabled: !time_bookings.present? || !time_bookings.all? { |time_booking| policy(time_booking).destroy? }
4 changes: 2 additions & 2 deletions app/views/hourglass_ui/time_trackers/context_menu.slim
@@ -1,4 +1,4 @@
- ids = @records.map(&:id)
ul
li = context_menu_link t(:button_edit), hourglass_ui_bulk_edit_time_trackers_path(ids: ids), class: 'icon-edit js-show-inline-form-multi', title: t(:button_edit), remote: true, data: {type: 'html'}, disabled: !@records.all? { |time_tracker| policy(time_tracker).change? }
li = context_menu_link t(:button_delete), @records.length == 1 ? hourglass_time_tracker_path(@records.first) : bulk_destroy_hourglass_time_trackers_path(time_trackers: ids), class: 'icon-del js-hourglass-remote', title: t(:button_delete), remote: true, method: :delete, data: {confirm: t(:text_are_you_sure)}, disabled: !@records.all? { |time_tracker| policy(time_tracker).destroy? }
li = context_menu_link t(:button_edit), hourglass_ui_bulk_edit_time_trackers_path(ids: ids), class: 'icon icon-edit js-show-inline-form-multi', title: t(:button_edit), remote: true, data: {type: 'html'}, disabled: !@records.all? { |time_tracker| policy(time_tracker).change? }
li = context_menu_link t(:button_delete), @records.length == 1 ? hourglass_time_tracker_path(@records.first) : bulk_destroy_hourglass_time_trackers_path(time_trackers: ids), class: 'icon icon-del js-hourglass-remote', title: t(:button_delete), remote: true, method: :delete, data: {confirm: t(:text_are_you_sure)}, disabled: !@records.all? { |time_tracker| policy(time_tracker).destroy? }
8 changes: 4 additions & 4 deletions config/initializers/autoload.rb
@@ -1,8 +1,8 @@
[
%w(app models concerns),
%w(app controllers concerns),
%w(app policies),
%w(app policies concerns)
%w(app models concerns),
%w(app controllers concerns),
%w(app policies),
%w(app policies concerns)
].each do |path|
ActiveSupport::Dependencies.autoload_paths << File.join(Hourglass::PLUGIN_ROOT, *path)
end
6 changes: 5 additions & 1 deletion config/initializers/redmine_integration.rb
Expand Up @@ -3,7 +3,11 @@ def add_patch(module_to_patch, method: :include)
module_to_patch.send method, patch unless module_to_patch.ancestors.include? patch
end

ActionDispatch::Callbacks.to_prepare do
if Rails::VERSION::MAJOR >= 5
ActiveSupport::Reloader
else
ActionDispatch::Callbacks
end.to_prepare do
[Project, TimeEntry, User, ProjectsHelper, SettingsController, UserPreference, TimeEntryActivity].each { |module_to_patch| add_patch module_to_patch }
[Query].each { |module_to_patch| add_patch module_to_patch, method: :prepend }

Expand Down
4 changes: 3 additions & 1 deletion db/migrate/1418311263_create_hourglass_time_logs.rb
@@ -1,4 +1,6 @@
class CreateHourglassTimeLogs < ActiveRecord::Migration
require 'hourglass/hourglass_migration'

class CreateHourglassTimeLogs < HourglassMigration
def change
create_table :hourglass_time_logs do |t|
t.datetime :start, null: false
Expand Down
4 changes: 3 additions & 1 deletion db/migrate/1418311331_create_hourglass_time_bookings.rb
@@ -1,4 +1,6 @@
class CreateHourglassTimeBookings < ActiveRecord::Migration
require 'hourglass/hourglass_migration'

class CreateHourglassTimeBookings < HourglassMigration
def change
create_table :hourglass_time_bookings do |t|
t.datetime :start, null: false
Expand Down
4 changes: 3 additions & 1 deletion db/migrate/1418914331_create_hourglass_time_trackers.rb
@@ -1,4 +1,6 @@
class CreateHourglassTimeTrackers < ActiveRecord::Migration
require 'hourglass/hourglass_migration'

class CreateHourglassTimeTrackers < HourglassMigration
def change
create_table :hourglass_time_trackers do |t|
t.datetime :start, null: false
Expand Down

0 comments on commit d2bfba9

Please sign in to comment.