diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index e56f6d68e..55ab8193c 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -595,6 +595,17 @@ code { } } +.log-value { + max-height: rem-calc(65); + overflow: hidden; + max-width: rem-calc(200); + + &:hover { + max-height: rem-calc(1000); + transition: max-height 0.9s; + } +} + // 04. Stats // --------- diff --git a/app/controllers/admin/budget_investments_controller.rb b/app/controllers/admin/budget_investments_controller.rb index 73a19dc7a..e4416a9ee 100644 --- a/app/controllers/admin/budget_investments_controller.rb +++ b/app/controllers/admin/budget_investments_controller.rb @@ -2,17 +2,19 @@ class Admin::BudgetInvestmentsController < Admin::BaseController include FeatureFlags include CommentableActions include DownloadSettingsHelper + include ChangeLogHelper feature_flag :budgets has_orders %w[oldest], only: [:show, :edit] has_filters %w[all], only: [:index, :toggle_selection] - before_action :load_budget + before_action :load_budget, except: :show_investment_log before_action :load_investment, only: [:show, :edit, :update, :toggle_selection] before_action :load_ballot, only: [:show, :index] before_action :parse_valuation_filters before_action :load_investments, only: [:index, :toggle_selection] + before_action :load_change_log, only: [:show] def index respond_to do |format| @@ -129,4 +131,8 @@ class Admin::BudgetInvestmentsController < Admin::BaseController end end end + + def load_change_log + @logs = Budget::Investment::ChangeLog.by_investment(@investment.id) + end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 56da7330b..794dcd26b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -13,6 +13,7 @@ class ApplicationController < ActionController::Base before_action :set_locale before_action :track_email_campaign before_action :set_return_url + before_action :set_current_user before_action :set_fallbacks_to_all_available_locales check_authorization unless: :devise_controller? @@ -120,6 +121,10 @@ class ApplicationController < ActionController::Base Budget.current end + def set_current_user + User.current_user = current_user + end + def set_fallbacks_to_all_available_locales Globalize.set_fallbacks_to_all_available_locales end diff --git a/app/helpers/change_log_helper.rb b/app/helpers/change_log_helper.rb new file mode 100644 index 000000000..7730951a0 --- /dev/null +++ b/app/helpers/change_log_helper.rb @@ -0,0 +1,8 @@ +module ChangeLogHelper + + def show_investment_log + @log = Budget::Investment::ChangeLog.find_by(id: params[:id]) + render "admin/change_logs/show" + end + +end diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index c6ae03a00..711b34037 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -3,6 +3,7 @@ class Budget class Investment < ApplicationRecord SORTING_OPTIONS = {id: "id", title: "title", supports: "cached_votes_up"}.freeze + include ActiveModel::Dirty include Rails.application.routes.url_helpers include Measurable include Sanitizable @@ -100,6 +101,7 @@ class Budget after_save :recalculate_heading_winners before_validation :set_responsible_name before_validation :set_denormalized_ids + after_update :change_log def comments_count comments.count @@ -391,5 +393,18 @@ class Budget self.budget_id ||= heading.try(:group).try(:budget_id) end + def change_log + self.changed.each do |field| + unless field == "updated_at" + log = Budget::Investment::ChangeLog.new + log.field = field + log.author_id = User.current_user.id unless User.current_user.nil? + log.investment_id = self.id + log.new_value = self.send field + log.old_value = self.send "#{field}_was" + !log.save + end + end + end end end diff --git a/app/models/budget/investment/change_log.rb b/app/models/budget/investment/change_log.rb new file mode 100644 index 000000000..b2ae65a3e --- /dev/null +++ b/app/models/budget/investment/change_log.rb @@ -0,0 +1,10 @@ +class Budget::Investment::ChangeLog < ActiveRecord::Base + belongs_to :author, -> { with_hidden }, class_name: "User", foreign_key: "author_id", required: false + + validates :old_value, presence: true + validates :new_value, presence: true + validates :field, presence: true + + scope :by_investment, ->(investment_id) { where(investment_id: investment_id) } + +end diff --git a/app/models/user.rb b/app/models/user.rb index 6bf6fe155..480101cff 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -350,6 +350,15 @@ class User < ApplicationRecord followables.compact.map { |followable| followable.tags.map(&:name) }.flatten.compact.uniq end + + def self.current_user + Thread.current[:user] + end + + def self.current_user=(user) + Thread.current[:user] = user + end + def send_devise_notification(notification, *args) devise_mailer.send(notification, self, *args).deliver_later end diff --git a/app/views/admin/budget_investments/show.html.erb b/app/views/admin/budget_investments/show.html.erb index b77b9216b..b6f7f8974 100644 --- a/app/views/admin/budget_investments/show.html.erb +++ b/app/views/admin/budget_investments/show.html.erb @@ -67,3 +67,5 @@ <%= render "valuation/budget_investments/valuation_comments" %> <%= render "admin/milestones/milestones", milestoneable: @investment %> + +<%= render "admin/change_logs/change_log", logs: @logs %> diff --git a/app/views/admin/change_logs/_change_log.html.erb b/app/views/admin/change_logs/_change_log.html.erb new file mode 100644 index 000000000..e3105b965 --- /dev/null +++ b/app/views/admin/change_logs/_change_log.html.erb @@ -0,0 +1,44 @@ +

<%= t("admin.change_log.title") %>

+ +<% if logs.empty? %> + +<% else %> + + + + + + + + + + + + + + <% logs.each do |log| %> + + + + + + + + + + <% end %> + +
<%= t("admin.change_log.id") %><%= t("admin.change_log.field") %><%= t("admin.change_log.old_value") %><%= t("admin.change_log.new_value") %><%= t("admin.change_log.edited_at") %><%= t("admin.change_log.edited_by") %><%= t("admin.change_log.actions") %>
<%= log.id %><%= log.field.capitalize %> +
<%= log.old_value %>
+
+
<%= log.new_value %>
+
+ <%= log.created_at.to_date %> + + <%= log.author.name unless log.author.nil? %> + + <%= link_to admin_change_log_path(id: log) do %> + + <% end %> +
+<% end %> diff --git a/app/views/admin/change_logs/show.html.erb b/app/views/admin/change_logs/show.html.erb new file mode 100644 index 000000000..5666e4b58 --- /dev/null +++ b/app/views/admin/change_logs/show.html.erb @@ -0,0 +1,12 @@ +

<%= t("admin.change_log.title") %>

+ + +

<%= @log.id %>

+ +

<%= @log.old_value %>

+ +

<%= @log.new_value %>

+ +

<%= @log.created_at.to_date %>

+ +

<%= @log.author.name unless @log.author.nil? %>

diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index fa37d00f9..1499ebb94 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -1638,3 +1638,13 @@ en: translations: remove_language: Remove language add_language: Add language + change_log: + title: "Change Log" + id: "ID" + field: "Field" + new_value: "New Value" + old_value: "Old Value" + edited_at: "Edited at" + edited_by: "Edited by" + actions: "Actions" + empty: "There are not changes logged" diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index 2bf66166e..7f471b909 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -1636,3 +1636,13 @@ es: translations: remove_language: Eliminar idioma add_language: AƱadir idioma + change_log: + title: "Historial" + id: "ID" + field: "Campo" + new_value: "Valor nuevo" + old_value: "Valor anterior" + edited_at: "Editado el" + edited_by: "Editado por" + actions: "Acciones" + empty: "No hay cambios registrados" diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 557e13a11..314a6ff16 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -245,6 +245,10 @@ namespace :admin do resources :actions, only: [:index, :new, :create, :edit, :update, :destroy] resources :administrator_tasks, only: [:index, :edit, :update] end + get 'download_settings/:resource', to: 'download_settings#edit', as: 'edit_download_settings' put 'download_settings/:resource', to: 'download_settings#update', as: 'update_download_settings' + + get "/change_log/:id", to: "budget_investments#show_investment_log", as: "change_log" + end diff --git a/db/migrate/20190423084112_create_budget_investment_change_logs.rb b/db/migrate/20190423084112_create_budget_investment_change_logs.rb new file mode 100644 index 000000000..bd663ef49 --- /dev/null +++ b/db/migrate/20190423084112_create_budget_investment_change_logs.rb @@ -0,0 +1,13 @@ +class CreateBudgetInvestmentChangeLogs < ActiveRecord::Migration + def change + create_table :budget_investment_change_logs do |t| + t.integer :investment_id + t.integer :author_id + t.string :field + t.string :new_value + t.string :old_value + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 364d92c98..5e0176209 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -134,9 +134,9 @@ ActiveRecord::Schema.define(version: 20190607160900) do t.integer "budget_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "ballot_lines_count", default: 0 t.boolean "physical", default: false t.integer "poll_ballot_id" + t.integer "ballot_lines_count", default: 0 end create_table "budget_content_blocks", force: :cascade do |t| @@ -188,6 +188,16 @@ ActiveRecord::Schema.define(version: 20190607160900) do t.index ["group_id"], name: "index_budget_headings_on_group_id", using: :btree end + create_table "budget_investment_change_logs", force: :cascade do |t| + t.integer "investment_id" + t.integer "author_id" + t.string "field" + t.string "new_value" + t.string "old_value" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "budget_investment_milestone_translations", force: :cascade do |t| t.integer "budget_investment_milestone_id", null: false t.string "locale", null: false @@ -1177,12 +1187,12 @@ ActiveRecord::Schema.define(version: 20190607160900) do t.integer "comments_count", default: 0 t.integer "author_id" t.datetime "hidden_at" - t.string "slug" t.boolean "results_enabled", default: false t.boolean "stats_enabled", default: false t.datetime "created_at" t.datetime "updated_at" t.integer "budget_id" + t.string "slug" t.string "related_type" t.integer "related_id" t.index ["budget_id"], name: "index_polls_on_budget_id", unique: true, using: :btree diff --git a/spec/features/admin/change_log_spec.rb b/spec/features/admin/change_log_spec.rb new file mode 100644 index 000000000..288c0d5d0 --- /dev/null +++ b/spec/features/admin/change_log_spec.rb @@ -0,0 +1,71 @@ +require "rails_helper" + +feature "Admin change log" do + let(:budget) {create(:budget)} + let(:administrator) do + create(:administrator, user: create(:user, username: "Ana", email: "ana@admins.org")) + end + + context "Investments Participatory Budgets" do + + background do + @admin = create(:administrator) + login_as(@admin.user) + end + + scenario "No changes" do + budget_investment = create(:budget_investment, + price: 1234, + price_first_year: 1000, + feasibility: "unfeasible", + unfeasibility_explanation: "It is impossible", + administrator: administrator) + + visit admin_budget_budget_investments_path(budget_investment.budget) + + click_link budget_investment.title + + expect(page).to have_content(budget_investment.title) + expect(page).to have_content(budget_investment.description) + expect(page).to have_content(budget_investment.author.name) + expect(page).to have_content(budget_investment.heading.name) + expect(page).to have_content("There are not changes logged") + end + + scenario "Changes" do + budget_investment = create(:budget_investment, + price: 1234, + price_first_year: 1000, + feasibility: "unfeasible", + unfeasibility_explanation: "It is impossible", + administrator: administrator) + + visit admin_budget_budget_investments_path(budget_investment.budget) + + click_link budget_investment.title + + expect(page).to have_content(budget_investment.title) + expect(page).to have_content(budget_investment.description) + expect(page).to have_content(budget_investment.author.name) + expect(page).to have_content(budget_investment.heading.name) + expect(page).to have_content("There are not changes logged") + + budget_investment.update(title: "test") + + visit admin_budget_budget_investments_path(budget_investment.budget) + + click_link budget_investment.title + + expect(page).not_to have_content("There are not changes logged") + expect(page).to have_content("Change Log") + expect(page).to have_content("Title") + expect(page).to have_content("test") + expect(page).to have_content("Field") + expect(page).to have_content("Old Value") + expect(page).to have_content("New Value") + expect(page).to have_content("Edited at") + expect(page).to have_content("Edited by") + end + + end +end