OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openproject/app/models/timeline.rb

443 lines
11 KiB

#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class Timeline < ActiveRecord::Base
class Empty
attr_accessor :id, :name
def id
@id ||= -1
end
def name
@name ||= ::I18n.t('timelines.filter.noneElement')
end
end
unloadable
serialize :options
self.table_name = 'timelines'
default_scope :order => 'name ASC'
belongs_to :project, :class_name => "Project"
validates_presence_of :name, :project
validates_length_of :name, :maximum => 255, :unless => lambda { |e| e.name.blank? }
validate :validate_option_dates
validate :validate_option_numeric
attr_accessible :name, :options
before_save :remove_empty_options_values
before_save :split_joined_options_values
@@allowed_option_keys = [
"custom_fields",
"columns",
"compare_to_absolute",
"compare_to_relative",
"compare_to_relative_unit",
"compare_to_historical_one",
"compare_to_historical_two",
"comparison",
"exclude_empty",
"exclude_own_planning_elements",
"exclude_reporters",
"exist",
"grouping_one_enabled",
"grouping_one_selection",
"grouping_one_sort",
"grouping_two_enabled",
"grouping_two_selection",
"grouping_two_sort",
"hide_chart",
"hide_other_group",
"initial_outline_expansion",
"parents",
"planning_element_responsibles",
"planning_element_assignee",
"planning_element_status",
"planning_element_time",
Merge commit 'feature/rails3' into feature/better-api-refactoring Conflicts: app/assets/javascripts/timelines_select_boxes.js app/controllers/authentication_controller.rb app/controllers/colors_controller.rb app/controllers/planning_element_journals_controller.rb app/controllers/planning_element_statuses_controller.rb app/controllers/planning_element_types_controller.rb app/controllers/planning_elements_controller.rb app/controllers/project_associations_controller.rb app/controllers/project_types_controller.rb app/controllers/reported_project_statuses_controller.rb app/controllers/reportings_controller.rb app/controllers/scenarios_controller.rb app/controllers/timelines/timelines_principals_controller.rb app/controllers/timelines/timelines_projects_controller.rb app/controllers/timelines_controller.rb app/models/alternate_date.rb app/models/color.rb app/models/enabled_planning_element_type.rb app/models/planning_element.rb app/models/planning_element_scenario.rb app/models/planning_element_status.rb app/models/planning_element_type.rb app/models/project_association.rb app/models/project_type.rb app/models/reported_project_status.rb app/models/reporting.rb app/models/scenario.rb app/models/timeline.rb app/models/timelines/available_project_status.rb app/models/timelines/default_planning_element_type.rb app/views/colors/confirm_destroy.html.erb app/views/colors/edit.html.erb app/views/colors/index.html.erb app/views/colors/new.html.erb app/views/planning_element_types/confirm_destroy.html.erb app/views/planning_element_types/edit.html.erb app/views/planning_element_types/index.html.erb app/views/planning_element_types/new.html.erb app/views/planning_elements/confirm_destroy.html.erb app/views/planning_elements/confirm_destroy_permanently.html.erb app/views/planning_elements/confirm_move_to_trash.html.erb app/views/planning_elements/edit.html.erb app/views/planning_elements/index.html.erb app/views/planning_elements/new.html.erb app/views/planning_elements/recycle_bin.html.erb app/views/planning_elements/show.html.erb app/views/project_associations/confirm_destroy.html.erb app/views/project_associations/edit.html.erb app/views/project_associations/index.html.erb app/views/project_associations/new.html.erb app/views/project_types/confirm_destroy.html.erb app/views/project_types/edit.html.erb app/views/project_types/index.html.erb app/views/project_types/new.html.erb app/views/reportings/confirm_destroy.html.erb app/views/reportings/edit.html.erb app/views/reportings/index.html.erb app/views/reportings/new.html.erb app/views/reportings/show.html.erb app/views/scenarios/confirm_destroy.html.erb app/views/scenarios/edit.html.erb app/views/scenarios/new.html.erb app/views/timelines/_comparison.html.erb app/views/timelines/_general.html.erb app/views/timelines/_vertical_planning_elements.html.erb app/views/timelines/confirm_destroy.html.erb app/views/timelines/timelines_colors/index.api.rsb app/views/timelines/timelines_colors/show.api.rsb app/views/timelines/timelines_planning_element_statuses/show.api.rsb app/views/timelines/timelines_planning_element_types/show.api.rsb app/views/timelines/timelines_planning_elements/_edit.html.erb app/views/timelines/timelines_planning_elements/_table_header.html.erb app/views/timelines/timelines_planning_elements/confirm_destroy_all.html.erb app/views/timelines/timelines_planning_elements/confirm_restore_all.html.erb app/views/timelines/timelines_project_associations/show.api.rsb app/views/timelines/timelines_project_types/show.api.rsb app/views/timelines/timelines_projects/show.api.rsb app/views/timelines/timelines_reported_project_statuses/show.api.rsb app/views/timelines/timelines_reportings/show.api.rsb app/views/timelines/timelines_scenarios/show.api.rsb app/views/timelines/timelines_timelines/_form.html.erb db/migrate/20130409133709_create_timelines_available_project_statuses.rb db/migrate/20130409133711_create_timelines_enabled_planning_element_types.rb db/migrate/20130409133712_create_timelines_default_planning_element_types.rb db/migrate/20130409133720_add_deleted_at_to_timelines_planning_elements.rb lib/extended_http.rb lib/hooks/activity_index_head_hook.rb lib/hooks/view_projects_form_hook.rb lib/nested_attributes_for_api.rb lib/timelines/hooks.rb lib/timelines/pagination.rb lib/timestamps_compatibility.rb spec/controllers/timelines_projects_controller_spec.rb spec/factories/timelines_alternate_date_factory.rb spec/factories/timelines_available_project_status_factory.rb spec/factories/timelines_default_planning_element_type_factory.rb spec/factories/timelines_enabled_planning_element_type_factory.rb spec/factories/timelines_planning_element_journal_factory.rb spec/factories/timelines_project_factory.rb
12 years ago
"planning_element_time_absolute_one",
"planning_element_time_absolute_two",
"planning_element_time_relative_one",
"planning_element_time_relative_one_unit",
"planning_element_time_relative_two",
"planning_element_time_relative_two_unit",
"planning_element_time_types",
"planning_element_types",
"project_responsibles",
"project_sort",
"project_status",
"project_types",
"timeframe_end",
"timeframe_start",
"vertical_planning_elements",
"zoom_factor"
]
@@available_columns = [
"start_date",
"due_date",
"type",
"status",
"responsible",
"assigned_to"
]
@@available_zoom_factors = [
'years',
'quarters',
'months',
'weeks',
'days'
]
@@available_initial_outline_expansions = [
'aggregation',
'level1',
'level2',
'level3',
'level4',
'level5',
'all'
]
def filter_options
@@allowed_option_keys
end
def validate_option_numeric
numeric = ["compare_to_relative", "planning_element_time_relative_one", "planning_element_time_relative_two"]
numeric.each do |field|
begin
if options[field] && options[field] != "" && options[field].to_i.to_s != options[field] then
errors.add :options, l("timelines.filter.errors." + field) + l("activerecord.errors.messages.not_a_number")
end
rescue ArgumentError
end
end
end
def validate_option_dates
date_fields = ["timeframe_start", "timeframe_end", "compare_to_absolute", "planning_element_time_absolute_one", "planning_element_time_absolute_two"]
date_fields.each do |field|
begin
if options[field] && options[field] != "" then
Date.parse(options[field])
end
rescue ArgumentError
errors.add :options, l("timelines.filter.errors." + field) + l("activerecord.errors.messages.not_a_date")
end
end
end
def default_options
{}
end
def options
read_attribute(:options) || self.default_options
end
def options=(other)
other.assert_valid_keys(*filter_options)
write_attribute(:options, other)
end
def json_options
json = with_escape_html_entities_in_json{ options.to_json }
json.html_safe
end
def custom_field_columns
project.all_work_package_custom_fields.map { |a| {name: a.name, id: "cf_#{a.id}"}}
end
def available_columns
@@available_columns
end
def available_initial_outline_expansions
@@available_initial_outline_expansions
end
def selected_initial_outline_expansion
if options["initial_outline_expansion"].present?
options["initial_outline_expansion"].first.to_i
else
-1
end
end
def available_zoom_factors
@@available_zoom_factors
end
def selected_zoom_factor
if options["zoom_factor"].present?
options["zoom_factor"].first.to_i
else
-1
end
end
def available_planning_element_types
# TODO: this should not be all planning element types, but instead
# all types that are available in the project the timeline is
# referencing, and all planning element types available in projects
# that are reporting into the project that this timeline is
# referencing.
Type.find(:all, :order => :name)
end
def available_planning_element_status
types = Project.visible.includes(:types).map(&:types).flatten.uniq
types.map(&:statuses).flatten.uniq
end
def selected_planning_element_status
resolve_with_none_element(:planning_element_status) do |ary|
Status.find_all_by_id(ary)
end
end
def selected_planning_element_types
resolve_with_none_element(:planning_element_types) do |ary|
Type.find_all_by_id(ary)
end
end
def selected_planning_element_time_types
resolve_with_none_element(:planning_element_time_types) do |ary|
Type.find_all_by_id(ary)
end
Merge commit 'feature/rails3' into feature/better-api-refactoring Conflicts: app/assets/javascripts/timelines_select_boxes.js app/controllers/authentication_controller.rb app/controllers/colors_controller.rb app/controllers/planning_element_journals_controller.rb app/controllers/planning_element_statuses_controller.rb app/controllers/planning_element_types_controller.rb app/controllers/planning_elements_controller.rb app/controllers/project_associations_controller.rb app/controllers/project_types_controller.rb app/controllers/reported_project_statuses_controller.rb app/controllers/reportings_controller.rb app/controllers/scenarios_controller.rb app/controllers/timelines/timelines_principals_controller.rb app/controllers/timelines/timelines_projects_controller.rb app/controllers/timelines_controller.rb app/models/alternate_date.rb app/models/color.rb app/models/enabled_planning_element_type.rb app/models/planning_element.rb app/models/planning_element_scenario.rb app/models/planning_element_status.rb app/models/planning_element_type.rb app/models/project_association.rb app/models/project_type.rb app/models/reported_project_status.rb app/models/reporting.rb app/models/scenario.rb app/models/timeline.rb app/models/timelines/available_project_status.rb app/models/timelines/default_planning_element_type.rb app/views/colors/confirm_destroy.html.erb app/views/colors/edit.html.erb app/views/colors/index.html.erb app/views/colors/new.html.erb app/views/planning_element_types/confirm_destroy.html.erb app/views/planning_element_types/edit.html.erb app/views/planning_element_types/index.html.erb app/views/planning_element_types/new.html.erb app/views/planning_elements/confirm_destroy.html.erb app/views/planning_elements/confirm_destroy_permanently.html.erb app/views/planning_elements/confirm_move_to_trash.html.erb app/views/planning_elements/edit.html.erb app/views/planning_elements/index.html.erb app/views/planning_elements/new.html.erb app/views/planning_elements/recycle_bin.html.erb app/views/planning_elements/show.html.erb app/views/project_associations/confirm_destroy.html.erb app/views/project_associations/edit.html.erb app/views/project_associations/index.html.erb app/views/project_associations/new.html.erb app/views/project_types/confirm_destroy.html.erb app/views/project_types/edit.html.erb app/views/project_types/index.html.erb app/views/project_types/new.html.erb app/views/reportings/confirm_destroy.html.erb app/views/reportings/edit.html.erb app/views/reportings/index.html.erb app/views/reportings/new.html.erb app/views/reportings/show.html.erb app/views/scenarios/confirm_destroy.html.erb app/views/scenarios/edit.html.erb app/views/scenarios/new.html.erb app/views/timelines/_comparison.html.erb app/views/timelines/_general.html.erb app/views/timelines/_vertical_planning_elements.html.erb app/views/timelines/confirm_destroy.html.erb app/views/timelines/timelines_colors/index.api.rsb app/views/timelines/timelines_colors/show.api.rsb app/views/timelines/timelines_planning_element_statuses/show.api.rsb app/views/timelines/timelines_planning_element_types/show.api.rsb app/views/timelines/timelines_planning_elements/_edit.html.erb app/views/timelines/timelines_planning_elements/_table_header.html.erb app/views/timelines/timelines_planning_elements/confirm_destroy_all.html.erb app/views/timelines/timelines_planning_elements/confirm_restore_all.html.erb app/views/timelines/timelines_project_associations/show.api.rsb app/views/timelines/timelines_project_types/show.api.rsb app/views/timelines/timelines_projects/show.api.rsb app/views/timelines/timelines_reported_project_statuses/show.api.rsb app/views/timelines/timelines_reportings/show.api.rsb app/views/timelines/timelines_scenarios/show.api.rsb app/views/timelines/timelines_timelines/_form.html.erb db/migrate/20130409133709_create_timelines_available_project_statuses.rb db/migrate/20130409133711_create_timelines_enabled_planning_element_types.rb db/migrate/20130409133712_create_timelines_default_planning_element_types.rb db/migrate/20130409133720_add_deleted_at_to_timelines_planning_elements.rb lib/extended_http.rb lib/hooks/activity_index_head_hook.rb lib/hooks/view_projects_form_hook.rb lib/nested_attributes_for_api.rb lib/timelines/hooks.rb lib/timelines/pagination.rb lib/timestamps_compatibility.rb spec/controllers/timelines_projects_controller_spec.rb spec/factories/timelines_alternate_date_factory.rb spec/factories/timelines_available_project_status_factory.rb spec/factories/timelines_default_planning_element_type_factory.rb spec/factories/timelines_enabled_planning_element_type_factory.rb spec/factories/timelines_planning_element_journal_factory.rb spec/factories/timelines_project_factory.rb
12 years ago
end
def available_project_types
ProjectType.all
end
def selected_project_types
resolve_with_none_element(:project_types) do |ary|
ProjectType.find_all_by_id(ary)
end
end
def available_project_status
ReportedProjectStatus.find(:all, :order => :name)
end
def selected_project_status
resolve_with_none_element(:project_status) do |ary|
ReportedProjectStatus.find_all_by_id(ary)
end
end
def available_responsibles
User.find(:all).sort_by(&:name)
end
def selected_project_responsibles
resolve_with_none_element(:project_responsibles) do |ary|
User.find_all_by_id(ary)
end
end
def selected_planning_element_responsibles
resolve_with_none_element(:planning_element_responsibles) do |ary|
User.find_all_by_id(ary)
end
end
def custom_field_list_value(field_id)
value = self.custom_fields_filter[field_id]
if value then
value.join(",")
else
""
end
end
def custom_fields_filter
options["custom_fields"] || {}
end
def get_custom_fields
project.all_work_package_custom_fields
end
def selected_planning_element_assignee
resolve_with_none_element(:planning_element_assignee) do |ary|
User.find(ary)
end
end
def available_parents
selectable_projects
end
def selected_parents
resolve_with_none_element(:parents) do |ary|
Project.find_all_by_id(ary)
end
end
def selected_columns
if options["columns"].present?
options["columns"]
else
[]
end
end
def planning_element_time
if options["planning_element_time"].present?
options["planning_element_time"]
else
'absolute'
end
end
def comparison
if options["comparison"].present?
options["comparison"]
else
'none'
end
end
def selected_grouping_projects
resolve_with_none_element(:grouping_one_selection) do |ary|
projects = Project.find_all_by_id(ary)
projectsHashMap = Hash[projects.collect { |v| [v.id, v]}]
ary.map { |a| projectsHashMap[a] }
end
end
def available_grouping_projects
selectable_projects
end
def selectable_projects
Project.selectable_projects
end
def selected_grouping_project_types
resolve_with_none_element(:grouping_two_selection) do |ary|
ProjectType.find_all_by_id(ary)
end
end
def available_grouping_project_types
ProjectType.available_grouping_project_types
end
protected
def remove_empty_options_values
unless self[:options].nil?
self[:options].reject! do |key, value|
value.instance_of?(Array) && value.length == 1 && value.first.empty?
end
end
end
def split_joined_options_values
unless self[:options].nil?
self[:options].each_pair do |key, value|
if value.instance_of?(Array) && value.length == 1 then
self[:options][key] = value[0].split(",")
end
end
unless self[:options][:custom_fields].nil?
self[:options][:custom_fields].each_pair do |key, value|
if value.instance_of?(Array) && value.length == 1 then
self[:options][:custom_fields][key] = value[0].split(",")
end
end
end
end
end
def array_of_ids_or_empty_array(options_field)
array_or_empty(options_field) { |ary| ary.delete_if(&:empty?).map(&:to_i) }
end
def array_or_empty(options_field)
if options[options_field].present?
if block_given?
yield options[options_field]
else
return options[options_field]
end
else
[]
end
end
def resolve_with_none_element(options_field, &block)
collection = []
collection += [Empty.new] if (ary = array_of_comma_seperated(options_field)).delete(-1)
begin
collection += block.call(ary);
rescue
end
return collection
end
def array_of_comma_seperated(options_field)
array_or_empty(options_field) do |ary|
ary.map(&:to_i).reject do |value|
value < -1 || value == 0
end
end
end
# TODO: this should go somewhere else, once it is needed at multiple places
def with_escape_html_entities_in_json
oldvalue = ActiveSupport.escape_html_entities_in_json
ActiveSupport.escape_html_entities_in_json = true
yield
ensure
ActiveSupport.escape_html_entities_in_json = oldvalue
end
end