Skip to content

Commit

Permalink
Merge pull request #22 from nutso/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
teresan committed Jan 4, 2014
2 parents e6f0d28 + f109a65 commit ad8eedb
Show file tree
Hide file tree
Showing 11 changed files with 172 additions and 33 deletions.
3 changes: 2 additions & 1 deletion .gitignore
@@ -1,5 +1,6 @@
# Eclipse project file
# Eclipse project and settings files
.project
.settings/*
# OS generated files
.DS_Store
.DS_Store?
Expand Down
40 changes: 39 additions & 1 deletion ReleaseNotes.md
@@ -1,5 +1,43 @@
# Recurring Tasks Redmine Plugin -- Release Notes

## Known Issues

### Master (Stable)

* (Confirmed) Deleting an issue neither generates a warning nor deletes the recurrence
* (Reported) Incompatibility with Redmine Stable 2.4 (#20) -- possibly line feed issue
* (Confirmed) No ability to view historic recurrences

### Develop

*

## Resolved in Develop (Next Version)

*

## Version 1.2.6

* After deleting an issue that still has a recurrence, recurrence views generate errors
* Menu captions not localized (#21)
* Changing the interval_day, interval_week, interval_month, or interval_year strings in the locale file, or changing locales, after adding recurrences generates an error

## Version 1.2.5

* resolved nil reference for fixed schedule recurrences with no due date (#16)
* includes german translation contributed by @skolarianer

## Version 1.2.0

* more intuitive management within the issues themselves
* add link to add recurrence when viewing an issue (#7)
* display existing recurrence if application when viewing an issue (#6)

## Version 1.1.0

* Project-specific recurring tasks view (#11)
* Better permissions (managed under issues) (#12)

## Version 1.0.2

* Show next scheduled recurrence when displaying a recurring task (#9)
Expand All @@ -9,7 +47,7 @@
## Version 1.0.1

* Added missing translations
* Fixed missing routes
* Fixed missing routes (#1)

## Version 1.0.0

Expand Down
3 changes: 2 additions & 1 deletion app/controllers/recurring_tasks_controller.rb
Expand Up @@ -44,6 +44,7 @@ def edit
def update
logger.info "Updating recurring task #{params[:id]}"

params[:recurring_task][:interval_unit] = RecurringTask.get_interval_from_localized_name(params[:recurring_task][:interval_localized_name])
if @recurring_task.update_attributes(params[:recurring_task])
flash[:notice] = l(:recurring_task_saved)
redirect_to :action => :show
Expand Down Expand Up @@ -83,6 +84,6 @@ def find_recurring_task
end

def set_interval_units
@interval_units = RecurringTask::INTERVAL_UNITS
@interval_units = RecurringTask::INTERVAL_UNITS_LOCALIZED
end
end
83 changes: 72 additions & 11 deletions app/models/recurring_task.rb
Expand Up @@ -4,30 +4,77 @@ class RecurringTask < ActiveRecord::Base
belongs_to :issue, :foreign_key => 'current_issue_id'
has_one :project, through: :issue

# these are the flags used in the database to denote the interval
# the actual text displayed to the user is controlled in the language file
INTERVAL_DAY = 'd'
INTERVAL_WEEK = 'w'
INTERVAL_MONTH = 'm'
INTERVAL_YEAR = 'y'

# must come before validations otherwise unitialized
INTERVAL_UNITS = [l(:interval_day), l(:interval_week), l(:interval_month), l(:interval_year)]
INTERVAL_UNITS_LOCALIZED = [l(:interval_day), l(:interval_week), l(:interval_month), l(:interval_year)]

validates :interval_unit, presence: true, inclusion: { in: RecurringTask::INTERVAL_UNITS, message: "#{l(:error_invalid_interval)} %{value}" }
validates :interval_localized_name, presence: true, inclusion: { in: RecurringTask::INTERVAL_UNITS_LOCALIZED, message: "#{l(:error_invalid_interval)} '%{value}' (Validation)" }
validates :interval_number, presence: true, numericality: {only_integer: true, greater_than: 0}
# cannot validate presence of issue if want to use other features
# validates :issue, presence: true
# validates :issue, presence: true # cannot validate presence of issue if want to use other features
# validates :fixed_schedule # requiring presence requires true

validates_associated :issue # just in case we build in functionality to add an issue at the same time, verify the issue is ok

# text for the interval name
def interval_localized_name
case interval_unit
when INTERVAL_DAY
l(:interval_day)
when INTERVAL_WEEK
l(:interval_week)
when INTERVAL_MONTH
l(:interval_month)
when INTERVAL_YEAR
l(:interval_year)
else
logger.error "#{l(:error_invalid_interval)} #{interval_unit} (interval_localized_name)"
""
end
end

# interval database name for the localized text
def interval_localized_name=(value)
@interval_localized_name = value
interval_unit= RecurringTask.get_interval_from_localized_name(value)
end

# used for migration #2
def self.get_interval_from_localized_name(value)
case value
when l(:interval_day)
INTERVAL_DAY
when l(:interval_week)
INTERVAL_WEEK
when l(:interval_month)
INTERVAL_MONTH
when l(:interval_year)
INTERVAL_YEAR
else
logger.error "#{l(:error_invalid_interval)} #{value} (interval_localized_name=)"
""
end
end

# time interval value of the recurrence pattern
def recurrence_pattern
case interval_unit
when l(:interval_day)
when INTERVAL_DAY
interval_number.days
when l(:interval_week)
when INTERVAL_WEEK
interval_number.weeks
when l(:interval_month)
when INTERVAL_MONTH
interval_number.months
when l(:interval_year)
when INTERVAL_YEAR
interval_number.years
else
logger.error "Unsupported interval unit: #{interval_unit}"
logger.error "#{l(:error_invalid_interval)} #{interval_unit} (recurrence_pattern)"
1.years
end
end

Expand All @@ -43,7 +90,12 @@ def self.all_for_project project

# next due date for the task, if there is one (relative tasks won't have a next schedule until the current issue is closed)
def next_scheduled_recurrence
previous_date_for_recurrence + recurrence_pattern unless previous_date_for_recurrence.nil?
if previous_date_for_recurrence.nil?
logger.error "Previous date for recurrence was nil for recurrence #{id}"
Date.today
else
previous_date_for_recurrence + recurrence_pattern
end
end

# whether a recurrence needs to be added
Expand Down Expand Up @@ -94,6 +146,15 @@ def self.add_recurrences!
# for a fixed schedule, this is the due date
# for a relative schedule, this is the date closed
def previous_date_for_recurrence
if fixed_schedule and !issue.due_date.nil? then issue.due_date elsif issue.closed_on.nil? then issue.updated_on else issue.closed_on end
if issue.nil?
logger.error "Issue is nil for recurrence #{id}."
Date.today
elsif fixed_schedule and !issue.due_date.nil?
issue.due_date
elsif issue.closed_on.nil?
issue.updated_on
else
issue.closed_on
end
end
end
2 changes: 1 addition & 1 deletion app/views/recurring_tasks/_form.html.erb
Expand Up @@ -4,7 +4,7 @@
<p><%= f.hidden_field :id %></p>
<p><%= label(:recurring_task, :current_issue_id, l(:field_issue)) %><%= collection_select('recurring_task', 'current_issue_id', @recurrable_issues, :id, :subj_date) %></p>
<p><%= f.number_field :interval_number %></p>
<p><%= label(:recurring_task, :interval_unit, l(:field_interval_unit)) %><%= select 'recurring_task', 'interval_unit', @interval_units %></p>
<p><%= label(:recurring_task, :interval_localized_name, l(:field_interval_unit)) %><%= select 'recurring_task', 'interval_localized_name', @interval_units %></p>
<p><%= f.check_box :fixed_schedule %> </p>
<p><%= f.submit %></p>
<% end %>
Expand Down
23 changes: 14 additions & 9 deletions app/views/recurring_tasks/index.html.erb
Expand Up @@ -8,7 +8,7 @@
<table class="list">
<thead>
<tr>
<% if !@project %><th><%= l(:field_project)%></th><% end %>
<% if @project.nil? %><th><%= l(:field_project)%></th><% end %>
<th><%= l(:label_current_issue)%></th>
<th><%= l(:label_recurrence_pattern)%></th>
<th><%= l(:field_fixed_schedule)%></th>
Expand All @@ -18,14 +18,19 @@
</thead>
<tbody>
<% @recurring_tasks.each do |rt| %>
<tr class="<%= cycle('odd', 'even') %>">
<% if !@project %><td><%= link_to(rt.project, project_path(rt.project)) %></td><% end %>
<td><%= rt.issue.nil? ? l(:label_recurring_task_issue_empty) : link_to(rt.issue.subj_date, {:action => 'show', :id => rt.id, :project_id => rt.project.id}) %></td>
<td><%= pluralize(rt.interval_number, rt.interval_unit) %></td>
<td><%= rt.fixed_schedule %></td>
<td><%= format_date(rt.next_scheduled_recurrence) %></td>
<td><%= edit_button rt %></td>
</tr>
<tr class="<%= cycle('odd', 'even') %>">
<% begin %>
<% logger.info rt.id %>
<% if @project.nil? %><td><%= rt.project.nil? ? l(:label_recurring_task_project_empty) : link_to(rt.project, project_path(rt.project)) %></td><% end %>
<td><%= rt.issue.nil? ? l(:label_recurring_task_issue_empty) : link_to(rt.issue.subj_date, {:action => 'show', :id => rt.id, :project_id => rt.project.id}) %></td>
<td><%= pluralize(rt.interval_number, rt.interval_localized_name) %></td>
<td><%= rt.fixed_schedule %></td>
<td><%= format_date(rt.next_scheduled_recurrence) %></td>
<td><%= edit_button rt %></td>
<% rescue => e %>
<td><%= "ERROR: #{e}" %></td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
Expand Down
2 changes: 1 addition & 1 deletion app/views/recurring_tasks/show.html.erb
Expand Up @@ -4,7 +4,7 @@

<h2><%= @recurring_task.issue.nil? ? l(:label_recurring_task_issue_empty) : link_to(@recurring_task.issue.subj_date, {:controller => 'issues', :action => 'show', :id => @recurring_task.issue.id}) %></h2>

<p><%= l(:label_recurrence_pattern) %> <%= pluralize(@recurring_task.interval_number, @recurring_task.interval_unit) %></p>
<p><%= l(:label_recurrence_pattern) %> <%= pluralize(@recurring_task.interval_number, @recurring_task.interval_localized_name) %></p>
<p><%= @recurring_task.fixed_schedule ? l(:label_recurs_fixed) : l(:label_recurs_dependent) %></p>

<p><%= l(:label_next_scheduled_run) %>: <%= format_date(@recurring_task.next_scheduled_recurrence) %></p>
Expand Down
2 changes: 2 additions & 0 deletions config/locales/de.yml
Expand Up @@ -7,6 +7,8 @@ de:
label_no_recurring_tasks: "Keine wiederkehrenden Themen gefunden."
label_next_scheduled_run: "Nächste Ausführung"
label_no_recurrence: "Keine Wiederholung."
label_no_project: "Kein Projekt."


label_recurs_fixed: "nach einem festen Zeitplan"
label_recurs_dependent: "nach Abschluss der vorherigen Durchführung"
Expand Down
12 changes: 7 additions & 5 deletions config/locales/en.yml
@@ -1,24 +1,26 @@
# English strings go here for Rails i18n
en:
label_recurring_tasks: "Recurring Tasks"
label_recurring_tasks: "Recurring Issues"
label_current_issue: "Current Issue for Recurrence"
label_recurrence_pattern: "Recurs every"
label_add_recurring_task: "Add Recurrence"
label_no_recurring_tasks: "No recurring tasks found on the system."
label_no_recurring_tasks: "No recurring issues found on the system."
label_next_scheduled_run: "Next scheduled run"
label_no_recurrence: "No recurrence."
label_no_project: "No project."

label_recurs_fixed: "on a fixed schedule"
label_recurs_dependent: "after previous task completion"
label_recurs_dependent: "after previous issue completion"

label_recurring_task_issue_empty: "N/A"
label_recurring_task_project_empty: "N/A"

label_belongs_to_project: "Belongs to project"
label_assigned_to: "Assigned to"

error_invalid_interval: "Interval provided is invalid."
error_recurring_task_not_found: "Could not find recurring task. "
error_recurring_task_could_not_remove: "Could not remove recurrence from task. "
error_recurring_task_not_found: "Could not find recurring issue. "
error_recurring_task_could_not_remove: "Could not remove recurrence from issue. "

field_interval_number: "Interval number"
field_interval_unit: "Interval Unit(s)"
Expand Down
29 changes: 29 additions & 0 deletions db/migrate/002_standardize_recurrence_units_nonlocalized.rb
@@ -0,0 +1,29 @@
# Previous to this migration, the value for recurring_tasks.interval_units
# was pulled from the localization file.
# This made the values more user-friendly when looking at the database directly
# however caused issues when the text changed, or locale changed.
# Here we are switching to just flags to denote the type of unit instead of text.
class StandardizeRecurrenceUnitsNonlocalized < ActiveRecord::Migration
def up
RecurringTask.all.each do |rt|
begin
logger.info "Migrating task ##{rt.id} from #{rt.interval_unit}"
rt.interval_unit = RecurringTask.get_interval_from_localized_name(rt.interval_unit)
rt.save!(:validate => false)
rescue => e
msg = "Migration for recurrence FAILED. ##{rt.id} from #{rt.interval_unit} FAILED. You will need to update this manually. Error: #{e}" # TODO localize
logger.error msg
say msg # also display to user
end
end
end

# There is no rolling back - this is a change to the DATA in the database,
# not to the structure itself.
# There is no guarantee that the current localized translation was the value
# previously in the database.
def down
# say "ActiveRecord::IrreversibleMigration"
raise ActiveRecord::IrreversibleMigration
end
end
6 changes: 3 additions & 3 deletions init.rb
Expand Up @@ -10,10 +10,10 @@
author_url 'https://github.com/nutso/'
url 'https://github.com/nutso/redmine-plugin-recurring-tasks'
description 'Allows you to set a task to recur on a regular schedule, or when marked complete, regenerate a new task due in the future. Plugin is based -- very loosely -- on the periodic tasks plugin published by Tanguy de Courson'
version '1.2.5'
version '1.2.6'

Redmine::MenuManager.map :top_menu do |menu|
menu.push :recurring_tasks, { :controller => 'recurring_tasks', :action => 'index' }, :caption => 'Recurring Issues', :if => Proc.new { User.current.admin? } # TODO localize string
menu.push :recurring_tasks, { :controller => 'recurring_tasks', :action => 'index' }, :caption => :label_recurring_tasks, :if => Proc.new { User.current.admin? }
end

# Permissions map to issue permissions (#12)
Expand All @@ -26,7 +26,7 @@
end

# project-specific recurring tasks view (#11)
menu :project_menu, :recurring_tasks, { :controller => 'recurring_tasks', :action => 'index' }, :caption => 'Recurring Issues', :after => :new_issue, :param => :project_id # TODO localize string
menu :project_menu, :recurring_tasks, { :controller => 'recurring_tasks', :action => 'index' }, :caption => :label_recurring_tasks, :after => :new_issue, :param => :project_id

# Send patches to models and controllers
Rails.configuration.to_prepare do
Expand Down

0 comments on commit ad8eedb

Please sign in to comment.