#-- copyright # OpenProject is a project management system. # Copyright (C) 2012-2013 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", "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 end def available_project_types ProjectType.find_all_by_id(: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