diff --git a/CHANGELOG.md b/CHANGELOG.md index 75be34e929ed..99f6cfa17569 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ end **Added**: - **decidim-assemblies**: Add members to assemblies. [\#3008](https://github.com/decidim/decidim/pull/3008) +- **decidim-assemblies**: An assembly member can be related to an existing user. [\#3302](https://github.com/decidim/decidim/pull/3302) - **decidim-meetings**: Add organizer to meeting and meeting types [\#3136](https://github.com/decidim/decidim/pull/3136) - **decidim-meetings**: Add Minutes entity to manage Minutes. [\#3213](https://github.com/decidim/decidim/pull/3213) - **decidim-admin**: Links to participatory space index & show pages from the admin dashboard. [\#3325](https://github.com/decidim/decidim/pull/3325) diff --git a/decidim-admin/lib/decidim/admin/form_builder.rb b/decidim-admin/lib/decidim/admin/form_builder.rb index fcb6edc9008c..099df0d7520b 100644 --- a/decidim-admin/lib/decidim/admin/form_builder.rb +++ b/decidim-admin/lib/decidim/admin/form_builder.rb @@ -19,7 +19,7 @@ class FormBuilder < Decidim::FormBuilder # @param [Hash] prompt_options # Prompt configuration. A hash with options: # - :url (String) The url where the ajax endpoint to fill the select - # - :text (String) Text to use as placeholder + # - :placeholder (String) Text to use as placeholder # - :no_results (String) (optional) Text to use when there are no matching results (default: No results found) # - :search_prompt (String) (optional) Text to prompt for search input (default: Type at least three characters to search) # diff --git a/decidim-assemblies/app/assets/javascripts/decidim/assemblies/admin/assembly_members.js.es6 b/decidim-assemblies/app/assets/javascripts/decidim/assemblies/admin/assembly_members.js.es6 index 2cf765d424e1..56859528f74b 100644 --- a/decidim-assemblies/app/assets/javascripts/decidim/assemblies/admin/assembly_members.js.es6 +++ b/decidim-assemblies/app/assets/javascripts/decidim/assemblies/admin/assembly_members.js.es6 @@ -1,6 +1,28 @@ ((exports) => { const { createFieldDependentInputs } = exports.DecidimAdmin; + const $assemblyMemberType = $("#assembly_member_existing_user"); + + createFieldDependentInputs({ + controllerField: $assemblyMemberType, + wrapperSelector: ".user-fields", + dependentFieldsSelector: ".user-fields--full-name", + dependentInputSelector: "input", + enablingCondition: ($field) => { + return $field.val() === "false" + } + }); + + createFieldDependentInputs({ + controllerField: $assemblyMemberType, + wrapperSelector: ".user-fields", + dependentFieldsSelector: ".user-fields--user-picker", + dependentInputSelector: "input", + enablingCondition: ($field) => { + return $field.val() === "true" + } + }); + const $assemblyMemberPosition = $("#assembly_member_position"); createFieldDependentInputs({ diff --git a/decidim-assemblies/app/commands/decidim/assemblies/admin/create_assembly_member.rb b/decidim-assemblies/app/commands/decidim/assemblies/admin/create_assembly_member.rb index 411e7a064307..0039c02cecce 100644 --- a/decidim-assemblies/app/commands/decidim/assemblies/admin/create_assembly_member.rb +++ b/decidim-assemblies/app/commands/decidim/assemblies/admin/create_assembly_member.rb @@ -61,7 +61,8 @@ def create_assembly_member! :position_other, :weight ).merge( - assembly: assembly + assembly: assembly, + user: form.user ), log_info ) diff --git a/decidim-assemblies/app/commands/decidim/assemblies/admin/update_assembly_member.rb b/decidim-assemblies/app/commands/decidim/assemblies/admin/update_assembly_member.rb index 8b1b5922b923..b4228bfb7d48 100644 --- a/decidim-assemblies/app/commands/decidim/assemblies/admin/update_assembly_member.rb +++ b/decidim-assemblies/app/commands/decidim/assemblies/admin/update_assembly_member.rb @@ -57,6 +57,8 @@ def update_assembly_member! :position, :position_other, :weight + ).merge( + user: form.user ), log_info ) diff --git a/decidim-assemblies/app/forms/decidim/assemblies/admin/assembly_member_form.rb b/decidim-assemblies/app/forms/decidim/assemblies/admin/assembly_member_form.rb index c7ccd9166d99..3262c68cf8d5 100644 --- a/decidim-assemblies/app/forms/decidim/assemblies/admin/assembly_member_form.rb +++ b/decidim-assemblies/app/forms/decidim/assemblies/admin/assembly_member_form.rb @@ -18,11 +18,24 @@ class AssemblyMemberForm < Form attribute :designation_mode, String attribute :position, String attribute :position_other, String + attribute :user_id, Integer + attribute :existing_user, Boolean, default: false - validates :full_name, :designation_date, presence: true + validates :designation_date, presence: true + validates :full_name, presence: true, unless: proc { |object| object.existing_user } validates :position, inclusion: { in: Decidim::AssemblyMember::POSITIONS } validates :position_other, presence: true, if: ->(form) { form.position == "other" } validates :ceased_date, date: { after: :designation_date, allow_blank: true } + validates :user, presence: true, if: proc { |object| object.existing_user } + + def map_model(model) + self.user_id = model.decidim_user_id + self.existing_user = user_id.present? + end + + def user + @user ||= current_organization.users.find_by(id: user_id) + end def positions_for_select Decidim::AssemblyMember::POSITIONS.map do |position| diff --git a/decidim-assemblies/app/models/decidim/assembly_member.rb b/decidim-assemblies/app/models/decidim/assembly_member.rb index 2d2474ed613f..56169ecdbfbc 100644 --- a/decidim-assemblies/app/models/decidim/assembly_member.rb +++ b/decidim-assemblies/app/models/decidim/assembly_member.rb @@ -9,6 +9,7 @@ class AssemblyMember < ApplicationRecord POSITIONS = %w(president vice_president secretary other).freeze + belongs_to :user, foreign_key: "decidim_user_id", class_name: "Decidim::User", optional: true belongs_to :assembly, foreign_key: "decidim_assembly_id", class_name: "Decidim::Assembly" alias participatory_space assembly diff --git a/decidim-assemblies/app/presenters/decidim/admin/assembly_member_presenter.rb b/decidim-assemblies/app/presenters/decidim/admin/assembly_member_presenter.rb new file mode 100644 index 000000000000..93f3e2dcd975 --- /dev/null +++ b/decidim-assemblies/app/presenters/decidim/admin/assembly_member_presenter.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Decidim + module Admin + # + # Decorator for assembly members + # + class AssemblyMemberPresenter < SimpleDelegator + def name + if user + "#{user.name} (#{Decidim::UserPresenter.new(user).nickname})" + else + full_name + end + end + + def position + return position_other if __getobj__.position == "other" + + I18n.t(__getobj__.position, scope: "decidim.admin.models.assembly_member.positions", default: "") + end + end + end +end diff --git a/decidim-assemblies/app/presenters/decidim/assembly_member_presenter.rb b/decidim-assemblies/app/presenters/decidim/assembly_member_presenter.rb index 44a72a3a1366..57850cc8153a 100644 --- a/decidim-assemblies/app/presenters/decidim/assembly_member_presenter.rb +++ b/decidim-assemblies/app/presenters/decidim/assembly_member_presenter.rb @@ -11,6 +11,14 @@ def age delegate :profile_url, :avatar_url, to: :user, allow_nil: true + def name + user ? user.name : full_name + end + + def nickname + user.nickname if user + end + def personal_information [ gender.presence, @@ -24,5 +32,15 @@ def position I18n.t(__getobj__.position, scope: "decidim.admin.models.assembly_member.positions", default: "") end + + private + + def user + @user ||= begin + if (user = __getobj__.user.presence) + Decidim::UserPresenter.new(user) + end + end + end end end diff --git a/decidim-assemblies/app/views/decidim/assemblies/admin/assembly_members/_form.html.erb b/decidim-assemblies/app/views/decidim/assemblies/admin/assembly_members/_form.html.erb index dabcf27c32c0..d99ad1750cba 100644 --- a/decidim-assemblies/app/views/decidim/assemblies/admin/assembly_members/_form.html.erb +++ b/decidim-assemblies/app/views/decidim/assemblies/admin/assembly_members/_form.html.erb @@ -6,8 +6,21 @@
-
- <%= form.text_field :full_name, autofocus: true %> +
+
+ <%= form.select :existing_user, [[t(".non_user"), false], [t(".existing_user"), true]], label: t(".user_type") %> +
+ +
+ <%= form.text_field :full_name, autofocus: true %> +
+ +
+ <% prompt_options = { url: decidim_admin.users_organization_url, placeholder: t(".select_user") } %> + <%= form.autocomplete_select(:user_id, form.object.user.presence, { multiple: false }, prompt_options) do |user| + { value: user.id, label: "#{user.name} (@#{user.nickname})" } + end %> +
diff --git a/decidim-assemblies/app/views/decidim/assemblies/admin/assembly_members/index.html.erb b/decidim-assemblies/app/views/decidim/assemblies/admin/assembly_members/index.html.erb index 6d820fd83f78..bcbce70196a8 100644 --- a/decidim-assemblies/app/views/decidim/assemblies/admin/assembly_members/index.html.erb +++ b/decidim-assemblies/app/views/decidim/assemblies/admin/assembly_members/index.html.erb @@ -59,12 +59,13 @@ <% @assembly_members.each do |member| %> + <% member_presenter = Decidim::Admin::AssemblyMemberPresenter.new(member) %> - <%= member.full_name %> + <%= member_presenter.name %> - <%= Decidim::AssemblyMemberPresenter.new(member).position %> + <%= member_presenter.position %> <%= l member.designation_date, format: :datepicker %> diff --git a/decidim-assemblies/app/views/decidim/assembly_members/_assembly_member.html.erb b/decidim-assemblies/app/views/decidim/assembly_members/_assembly_member.html.erb index c2018cb8dc5f..bf4c61b9a057 100644 --- a/decidim-assemblies/app/views/decidim/assembly_members/_assembly_member.html.erb +++ b/decidim-assemblies/app/views/decidim/assembly_members/_assembly_member.html.erb @@ -7,21 +7,41 @@
- - <%= image_tag asset_path("decidim/default-avatar.svg") %> - -
-
- <%= member_presenter.full_name %> + <% if (profile_url = member_presenter.profile_url) %> + + <%= image_tag member_presenter.avatar_url(:big) %> + +
+ + <% if (nickname = member_presenter.nickname) %> + + <%= nickname %> + + <% end %>
-
+ <% else %> + <%= image_tag asset_path("decidim/default-avatar.svg") %> +
+
<%= member_presenter.name %>
+ <% if (nickname = member_presenter.nickname) %> + <%= nickname %> + <% end %> +
+ <% end %>
-
<%= member_presenter.position %>
-
<%= t(".desiganted_on") %> <%= l assembly_member.designation_date, format: :datepicker %>
+
+ <%= member_presenter.position %> +
+
<%= t(".desiganted_on") %> + <%= l assembly_member.designation_date, format: :datepicker %>
<%= member_presenter.personal_information %>
diff --git a/decidim-assemblies/config/locales/en.yml b/decidim-assemblies/config/locales/en.yml index 7943ececc20a..a97be23e27a3 100644 --- a/decidim-assemblies/config/locales/en.yml +++ b/decidim-assemblies/config/locales/en.yml @@ -208,7 +208,11 @@ en: slug_help: 'URL slugs are used to generate the URLs that point to this assembly. Only accepts letters, numbers and dashes, and must start with a letter. Example: %{url}' assembly_members: form: + existing_user: Existing user + non_user: Non user select_a_position: Select a position + select_user: Select an user + user_type: User type index: filter: all: All diff --git a/decidim-assemblies/db/migrate/20180426162405_assembly_member_belongs_to_user.rb b/decidim-assemblies/db/migrate/20180426162405_assembly_member_belongs_to_user.rb new file mode 100644 index 000000000000..2cef532ec56e --- /dev/null +++ b/decidim-assemblies/db/migrate/20180426162405_assembly_member_belongs_to_user.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AssemblyMemberBelongsToUser < ActiveRecord::Migration[5.1] + def change + add_reference :decidim_assembly_members, :decidim_user, index: { name: "index_decidim_assembly_members_on_decidim_user_id" } + end +end diff --git a/decidim-assemblies/lib/decidim/assemblies/test/factories.rb b/decidim-assemblies/lib/decidim/assemblies/test/factories.rb index ca3c5bb64074..7357f4aefc9f 100644 --- a/decidim-assemblies/lib/decidim/assemblies/test/factories.rb +++ b/decidim-assemblies/lib/decidim/assemblies/test/factories.rb @@ -128,5 +128,9 @@ trait :ceased do ceased_date { Faker::Date.between(1.day.ago, 5.days.ago) } end + + trait :with_user do + user { create(:user, organization: assembly.organization) } + end end end diff --git a/decidim-assemblies/spec/commands/create_assembly_member_spec.rb b/decidim-assemblies/spec/commands/create_assembly_member_spec.rb index 6a72bf5e2f88..58f2552dc986 100644 --- a/decidim-assemblies/spec/commands/create_assembly_member_spec.rb +++ b/decidim-assemblies/spec/commands/create_assembly_member_spec.rb @@ -7,12 +7,14 @@ module Decidim::Assemblies subject { described_class.new(form, current_user, assembly) } let(:assembly) { create(:assembly) } + let(:user) { nil } let!(:current_user) { create :user, :confirmed, organization: assembly.organization } let(:form) do instance_double( Admin::AssemblyMemberForm, invalid?: invalid, full_name: "Full name", + user: user, attributes: { weight: 0, full_name: "Full name", @@ -63,6 +65,15 @@ module Decidim::Assemblies action_log = Decidim::ActionLog.last expect(action_log.version).to be_present end + + context "with an existing user in the platform" do + let!(:user) { create(:user, organization: assembly.organization) } + + it "sets the user" do + subject.call + expect(assembly_member.user).to eq user + end + end end end end diff --git a/decidim-assemblies/spec/commands/update_assembly_member_spec.rb b/decidim-assemblies/spec/commands/update_assembly_member_spec.rb index 2629c06e7170..aed4541b96dc 100644 --- a/decidim-assemblies/spec/commands/update_assembly_member_spec.rb +++ b/decidim-assemblies/spec/commands/update_assembly_member_spec.rb @@ -7,14 +7,16 @@ module Decidim::Assemblies subject { described_class.new(form, assembly_member) } let!(:assembly) { create(:assembly) } - let(:assembly_member) { create :assembly_member, assembly: assembly } + let(:assembly_member) { create :assembly_member, :with_user, assembly: assembly } let!(:current_user) { create :user, :confirmed, organization: assembly.organization } + let(:user) { nil } let(:form) do instance_double( Admin::AssemblyMemberForm, invalid?: invalid, current_user: current_user, full_name: "New name", + user: user, attributes: { weight: 0, full_name: "New name", @@ -60,6 +62,16 @@ module Decidim::Assemblies action_log = Decidim::ActionLog.last expect(action_log.version).to be_present end + + context "when is an existing user in the platform" do + let!(:user) { create :user, organization: assembly.organization } + + it "sets the user" do + expect do + subject.call + end.to change { assembly_member.reload && assembly_member.user }.from(assembly_member.user).to(user) + end + end end end end diff --git a/decidim-assemblies/spec/forms/assembly_member_form_spec.rb b/decidim-assemblies/spec/forms/assembly_member_form_spec.rb index 0e96cae65e3a..cc2199e540e8 100644 --- a/decidim-assemblies/spec/forms/assembly_member_form_spec.rb +++ b/decidim-assemblies/spec/forms/assembly_member_form_spec.rb @@ -19,6 +19,8 @@ module Admin let(:designation_date) { Time.current } let(:gender) { ::Faker::Lorem.word } let(:position) { Decidim::AssemblyMember::POSITIONS.first } + let(:existing_user) { false } + let(:user_id) { nil } let(:attributes) do { @@ -27,7 +29,9 @@ module Admin "designation_date" => designation_date, "gender" => gender, "position" => position, - "birthday" => Time.current + "birthday" => Time.current, + "existing_user" => existing_user, + "user_id" => user_id } } end @@ -72,6 +76,26 @@ module Admin end end + context "when existing user is present" do + let(:existing_user) { true } + + context "and no user is provided" do + it { is_expected.to be_invalid } + end + + context "and user exists" do + let(:user_id) { create(:user, organization: organization).id } + + it { is_expected.to be_valid } + end + + context "and no such user exists" do + let(:user_id) { 999_999 } + + it { is_expected.to be_invalid } + end + end + context "when ceased date is present" do context "and is older than designation date" do subject(:form) { described_class.from_params(attributes.merge(ceased_date: (designation_date - 1.minute))).with_context(context) } @@ -91,6 +115,28 @@ module Admin it { is_expected.to be_valid } end end + + describe "user" do + subject { form.user } + + context "when the user exists" do + let(:user_id) { create(:user, organization: organization).id } + + it { is_expected.to be_kind_of(Decidim::User) } + end + + context "when the user does not exist" do + let(:user_id) { 999_999 } + + it { is_expected.to eq(nil) } + end + + context "when the user is from another organization" do + let(:user_id) { create(:user).id } + + it { is_expected.to eq(nil) } + end + end end end end diff --git a/decidim-assemblies/spec/presenters/decidim/admin/assembly_member_presenter_spec.rb b/decidim-assemblies/spec/presenters/decidim/admin/assembly_member_presenter_spec.rb new file mode 100644 index 000000000000..32e26cc424fe --- /dev/null +++ b/decidim-assemblies/spec/presenters/decidim/admin/assembly_member_presenter_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + describe Admin::AssemblyMemberPresenter, type: :helper do + let(:assembly_member) do + build(:assembly_member, full_name: "Full name") + end + + describe "name" do + subject { described_class.new(assembly_member).name } + + it { is_expected.to eq "Full name" } + + context "when member is an existing user" do + let(:user) { build(:user, name: "Julia G.", nickname: "julia_g") } + let(:assembly_member) { build(:assembly_member, full_name: "Full name", user: user) } + + it { is_expected.to eq "Julia G. (@julia_g)" } + end + end + + describe "position" do + subject { described_class.new(assembly_member).position } + + context "when position is predefined" do + it { is_expected.to eq t(assembly_member.position, scope: "decidim.admin.models.assembly_member.positions") } + end + + context "when position is other" do + let(:assembly_member) { build(:assembly_member, position: "other", position_other: "Custom position") } + + it "show the custom position value" do + expect(subject).to eq("Custom position") + end + end + end + end +end diff --git a/decidim-assemblies/spec/presenters/decidim/assembly_member_presenter_spec.rb b/decidim-assemblies/spec/presenters/decidim/assembly_member_presenter_spec.rb index f6e7160e492d..4ad83653f517 100644 --- a/decidim-assemblies/spec/presenters/decidim/assembly_member_presenter_spec.rb +++ b/decidim-assemblies/spec/presenters/decidim/assembly_member_presenter_spec.rb @@ -13,6 +13,32 @@ module Decidim build(:assembly_member, full_name: "Full name", birthday: birthday) end + describe "name" do + subject { described_class.new(assembly_member).name } + + it { is_expected.to eq "Full name" } + + context "when member is an existing user" do + let(:user) { build(:user, name: "Julia G.", nickname: "julia_g") } + let(:assembly_member) { build(:assembly_member, full_name: "Full name", user: user) } + + it { is_expected.to eq "Julia G." } + end + end + + describe "nickname" do + subject { described_class.new(assembly_member).nickname } + + it { is_expected.to be_nil } + + context "when member is an existing user" do + let(:user) { build(:user, name: "Julia G.", nickname: "julia_g") } + let(:assembly_member) { build(:assembly_member, full_name: "Full name", user: user) } + + it { is_expected.to eq "@julia_g" } + end + end + describe "age" do subject { described_class.new(assembly_member).age } diff --git a/decidim-assemblies/spec/shared/manage_assembly_members_examples.rb b/decidim-assemblies/spec/shared/manage_assembly_members_examples.rb index cfb7e01a997f..5c0b845df7ba 100644 --- a/decidim-assemblies/spec/shared/manage_assembly_members_examples.rb +++ b/decidim-assemblies/spec/shared/manage_assembly_members_examples.rb @@ -16,28 +16,57 @@ end end - it "creates a new assembly member" do - find(".card-title a.new").click + context "without existing user" do + it "creates a new assembly member" do + find(".card-title a.new").click - execute_script("$('#date_field_assembly_member_designation_date').focus()") - find(".datepicker-days .active").click + execute_script("$('#date_field_assembly_member_designation_date').focus()") + find(".datepicker-days .active").click - within ".new_assembly_member" do - fill_in( - :assembly_member_full_name, - with: "Daisy O'connor" - ) + within ".new_assembly_member" do + fill_in( + :assembly_member_full_name, + with: "Daisy O'connor" + ) - select "President", from: :assembly_member_position + select "President", from: :assembly_member_position - find("*[type=submit]").click + find("*[type=submit]").click + end + + expect(page).to have_admin_callout("successfully") + expect(page).to have_current_path decidim_admin_assemblies.assembly_members_path(assembly) + + within "#assembly_members table" do + expect(page).to have_content("Daisy O'connor") + end end + end - expect(page).to have_admin_callout("successfully") - expect(page).to have_current_path decidim_admin_assemblies.assembly_members_path(assembly) + context "with existing user" do + let!(:member_user) { create :user, organization: assembly.organization } - within "#assembly_members table" do - expect(page).to have_content("Daisy O'connor") + it "creates a new assembly member" do + find(".card-title a.new").click + + execute_script("$('#date_field_assembly_member_designation_date').focus()") + find(".datepicker-days .active").click + + within ".new_assembly_member" do + select "Existing user", from: :assembly_member_existing_user + autocomplete_select "#{member_user.name} (@#{member_user.nickname})", from: :user_id + + select "President", from: :assembly_member_position + + find("*[type=submit]").click + end + + expect(page).to have_admin_callout("successfully") + expect(page).to have_current_path decidim_admin_assemblies.assembly_members_path(assembly) + + within "#assembly_members table" do + expect(page).to have_content("#{member_user.name} (@#{member_user.nickname})") + end end end @@ -68,7 +97,7 @@ end end - it "deletes a assembly_member" do + it "deletes the assembly member" do within find("#assembly_members tr", text: assembly_member.full_name) do accept_confirm { find("a.action-icon--remove").click } end diff --git a/decidim-assemblies/spec/system/assembly_members_spec.rb b/decidim-assemblies/spec/system/assembly_members_spec.rb index f8a16a91668e..6338bd820362 100644 --- a/decidim-assemblies/spec/system/assembly_members_spec.rb +++ b/decidim-assemblies/spec/system/assembly_members_spec.rb @@ -81,10 +81,10 @@ expect(page).to have_selector("article.card--member", count: 2) assembly_members.each do |assembly_member| - expect(page).to have_content(Decidim::AssemblyMemberPresenter.new(assembly_member).full_name) + expect(page).to have_content(Decidim::AssemblyMemberPresenter.new(assembly_member).name) end - expect(page).not_to have_content(Decidim::AssemblyMemberPresenter.new(ceased_assembly_member).full_name) + expect(page).not_to have_content(Decidim::AssemblyMemberPresenter.new(ceased_assembly_member).name) end end end