From c5178056c690a51475b10ae663dcc4e8fe1ff7c6 Mon Sep 17 00:00:00 2001 From: Trevor Vallender Date: Sun, 4 Feb 2024 14:32:27 +0000 Subject: [PATCH] [wip] Add journals to jobs --- app/controllers/jobs_controller.rb | 3 + app/helpers/jobs_helper.rb | 195 ++++++++++++++++++++++++++ app/models/job.rb | 33 +++++ app/views/jobs/_form.html.erb | 6 + app/views/jobs/show.html.erb | 4 + app/views/jobs/tabs/_history.html.erb | 31 ++++ config/locales/en.yml | 5 + 7 files changed, 277 insertions(+) create mode 100644 app/views/jobs/tabs/_history.html.erb diff --git a/app/controllers/jobs_controller.rb b/app/controllers/jobs_controller.rb index 6314b06..c48a997 100644 --- a/app/controllers/jobs_controller.rb +++ b/app/controllers/jobs_controller.rb @@ -7,6 +7,7 @@ class JobsController < ApplicationController end def show + @journals = @job.journals end def new @@ -18,6 +19,7 @@ class JobsController < ApplicationController end def update + @job.init_journal(User.current) if @job.update(remove_empty_time_budgets(job_params)) redirect_to project_job_path(@job.project, @job) else @@ -55,6 +57,7 @@ class JobsController < ApplicationController :name, :description, :category_id, + :notes, time_budgets_attributes: [:id, :category_id, :hours, :job_id, :_destroy] ) end diff --git a/app/helpers/jobs_helper.rb b/app/helpers/jobs_helper.rb index a8b21ee..422918f 100644 --- a/app/helpers/jobs_helper.rb +++ b/app/helpers/jobs_helper.rb @@ -12,4 +12,199 @@ module JobsHelper (#{l_hours_short(budget.total_time_logged)}/#{l_hours_short(budget.hours)})", class: "progress") end + + def job_history_tabs + tabs = [] + + if @journals.present? + has_notes = @journals.any? { |journal| journal.notes.present? } + tabs << + { + name: "history", + label: "History", + onclick: 'showJobHistory("history", this.href)', + partial: "jobs/tabs/history", + locals: { + job: @job, + journals: @journals + } + } + if has_notes + tabs << { + name: "notes", + label: "Notes", + onclick: 'showJobHistory("notes", this.href)', + } + end + end + tabs + end + + def job_history_default_tab + return params[:tab] if params[:tab].present? + + "history" + end + + # Below methods copied directly from issues_helper with few adjustments + + + # Returns the textual representation of a journal details + # as an array of strings + def details_to_strings(details, no_html=false, options={}) + options[:only_path] = !(options[:only_path] == false) + strings = [] + values_by_field = {} + details.each do |detail| + if detail.property == 'cf' + field = detail.custom_field + if field && field.multiple? + values_by_field[field] ||= {:added => [], :deleted => []} + if detail.old_value + values_by_field[field][:deleted] << detail.old_value + end + if detail.value + values_by_field[field][:added] << detail.value + end + next + end + end + strings << show_detail(detail, no_html, options) + end + if values_by_field.present? + values_by_field.each do |field, changes| + if changes[:added].any? + detail = MultipleValuesDetail.new('cf', field.id.to_s, field) + detail.value = changes[:added] + strings << show_detail(detail, no_html, options) + end + if changes[:deleted].any? + detail = MultipleValuesDetail.new('cf', field.id.to_s, field) + detail.old_value = changes[:deleted] + strings << show_detail(detail, no_html, options) + end + end + end + strings + end + + def show_detail(detail, no_html=false, options={}) + multiple = false + show_diff = false + no_details = false + + case detail.property + when 'attr' + field = detail.prop_key.to_s.gsub(/\_id$/, "") + label = l(("field_" + field).to_sym) + case detail.prop_key + when 'due_date', 'start_date' + value = format_date(detail.value.to_date) if detail.value + old_value = format_date(detail.old_value.to_date) if detail.old_value + + when 'project_id', 'status_id', 'tracker_id', 'assigned_to_id', + 'priority_id', 'category_id', 'fixed_version_id' + value = find_name_by_reflection(field, detail.value) + old_value = find_name_by_reflection(field, detail.old_value) + + when 'description' + show_diff = true + end + when 'relation' + if detail.value && !detail.old_value + rel_issue = Issue.visible.find_by_id(detail.value) + value = + if rel_issue.nil? + "#{l(:label_issue)} ##{detail.value}" + else + (no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path])) + end + elsif detail.old_value && !detail.value + rel_issue = Issue.visible.find_by_id(detail.old_value) + old_value = + if rel_issue.nil? + "#{l(:label_issue)} ##{detail.old_value}" + else + (no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path])) + end + end + relation_type = IssueRelation::TYPES[detail.prop_key] + label = l(relation_type[:name]) if relation_type + end + call_hook(:helper_issues_show_detail_after_setting, + {:detail => detail, :label => label, :value => value, :old_value => old_value}) + + label ||= detail.prop_key + value ||= detail.value + old_value ||= detail.old_value + + unless no_html + label = content_tag('strong', label) + old_value = content_tag("i", h(old_value)) if detail.old_value + if detail.old_value && detail.value.blank? && detail.property != 'relation' + old_value = content_tag("del", old_value) + end + if detail.property == 'attachment' && value.present? && + atta = detail.journal.journalized.attachments.detect {|a| a.id == detail.prop_key.to_i} + # Link to the attachment if it has not been removed + value = link_to_attachment(atta, only_path: options[:only_path]) + if options[:only_path] != false + value += ' ' + value += link_to_attachment atta, class: 'icon-only icon-download', title: l(:button_download), download: true + end + else + value = content_tag("i", h(value)) if value + end + end + + if no_details + s = l(:text_journal_changed_no_detail, :label => label).html_safe + elsif show_diff + s = l(:text_journal_changed_no_detail, :label => label) + unless no_html + diff_link = + link_to( + l(:label_diff), + diff_journal_url(detail.journal_id, :detail_id => detail.id, + :only_path => options[:only_path]), + :title => l(:label_view_diff)) + s << " (#{diff_link})" + end + s.html_safe + elsif detail.value.present? + case detail.property + when 'attr', 'cf' + if detail.old_value.present? + l(:text_journal_changed, :label => label, :old => old_value, :new => value).html_safe + elsif multiple + l(:text_journal_added, :label => label, :value => value).html_safe + else + l(:text_journal_set_to, :label => label, :value => value).html_safe + end + when 'attachment', 'relation' + l(:text_journal_added, :label => label, :value => value).html_safe + end + else + l(:text_journal_deleted, :label => label, :old => old_value).html_safe + end + end + + # Find the name of an associated record stored in the field attribute + # For project, return the associated record only if is visible for the current User + def find_name_by_reflection(field, id) + return nil if id.blank? + + @detail_value_name_by_reflection ||= Hash.new do |hash, key| + association = Issue.reflect_on_association(key.first.to_sym) + name = nil + if association + record = association.klass.find_by_id(key.last) + if (record && !record.is_a?(Project)) || (record.is_a?(Project) && record.visible?) + name = record.name.force_encoding('UTF-8') + end + end + hash[key] = name + end + @detail_value_name_by_reflection[[field, id]] + end end diff --git a/app/models/job.rb b/app/models/job.rb index 57763ab..0d92b79 100644 --- a/app/models/job.rb +++ b/app/models/job.rb @@ -12,13 +12,21 @@ class Job < ActiveRecord::Base has_many :time_entries, dependent: :restrict_with_error has_many :time_budgets, dependent: :destroy + has_many :journals, as: :journalized, dependent: :destroy, inverse_of: :journalized + delegate :notes, :notes=, to: :current_journal, allow_nil: true accepts_nested_attributes_for :time_budgets, allow_destroy: true + acts_as_customizable + acts_as_watchable + acts_as_mentionable attributes: [ "description" ] + scope :project_or_parent, ->(project) { where(project_id: [project&.id, project&.parent&.id]) } scope :active, -> { where(starts_on: ..Date.today, ends_on: Date.today..) } safe_attributes 'name', 'description' + after_save :create_journal + def with_all_time_budgets time_budgets.build(job_id: id, category_id: nil) unless time_budgets.where(category_id: nil).exists? TimeBudgetCategory.where.not(id: time_budgets.pluck(:category_id)).each do |category| @@ -54,6 +62,31 @@ class Job < ActiveRecord::Base ActionController::Base.helpers.link_to name, ActionController::Base.helpers.project_job_path(project, self) end + def init_journal(user, notes = "") + @current_journal = Journal.new(journalized: self, user: user, notes: notes) + end + + def current_journal + @current_journal + end + + def create_journal + current_journal.save if current_journal + end + + def journalized_attribute_names + Job.column_names - %w(id created_at updated_at) + end + + def notified_users + [] + end + + def notes_addable?(user = User.current) + #user_tracker_permission?(user, :add_job_notes) + true + end + def self.fields_for_order_statement "jobs.name" end diff --git a/app/views/jobs/_form.html.erb b/app/views/jobs/_form.html.erb index 4feec0f..c485e52 100644 --- a/app/views/jobs/_form.html.erb +++ b/app/views/jobs/_form.html.erb @@ -38,5 +38,11 @@ <%= wikitoolbar_for 'job_description' %> + + <% unless @job.new_record? %> + <%= f.text_area :notes, cols: 60, rows: 15, class: "wiki-edit", + data: { auto_complete: true }, id: "job_notes" %> + <%= wikitoolbar_for 'job_notes' %> + <% end %> <%= f.submit %> <% end %> diff --git a/app/views/jobs/show.html.erb b/app/views/jobs/show.html.erb index 940e38b..5c8e7e5 100644 --- a/app/views/jobs/show.html.erb +++ b/app/views/jobs/show.html.erb @@ -43,3 +43,7 @@ + +
+ <%= render_tabs job_history_tabs, job_history_default_tab %> +
diff --git a/app/views/jobs/tabs/_history.html.erb b/app/views/jobs/tabs/_history.html.erb new file mode 100644 index 0000000..91553f8 --- /dev/null +++ b/app/views/jobs/tabs/_history.html.erb @@ -0,0 +1,31 @@ +<% + job = tab[:locals][:job] + journals = tab[:locals][:journals] +%> + +<% reply_links = job.notes_addable? %> +<% journals.each_with_index do |journal, indice| %> +
+
+
+ + + + <%= indice %> +
+

+ <%= avatar(journal.user) %> + <%= authoring journal.created_on, journal.user, label: :label_updated_time_by %> +

+ <%= journal.notes %> + + <% if journal.details.any? %> +
    + <% details_to_strings(journal.visible_details).each do |string| %> +
  • <%= string %>
  • + <% end %> +
+ <% end %> +
+
+<% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index c5aeb04..ee8e217 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -9,3 +9,8 @@ en: enumeration_time_budget_category: Time budget categories enumeration_job_category: Job categories + History: History + Notes: Notes + + field_starts_on: Starts on + field_ends_on: Ends on