From e4abba6eb6afd348539b00c9048703e4503252e8 Mon Sep 17 00:00:00 2001 From: Matthew Rider Date: Wed, 31 Jul 2024 12:32:23 +0200 Subject: [PATCH] Scope additional resources at the organization level (#1945) --- .../chat_message_sent.rb | 5 +- .../contributor_marked_inactive.rb | 5 +- .../contributor_subscribed.rb | 5 +- .../message_received.rb | 5 +- .../onboarding_completed.rb | 5 +- .../request_scheduled.rb | 5 +- .../contributor_quick_edit_form.rb | 7 +- .../contributors_index/contributors_index.rb | 9 +- .../request_form/request_form.html.erb | 2 +- app/components/request_form/request_form.rb | 10 +-- app/controllers/contributors_controller.rb | 2 +- app/controllers/dashboard_controller.rb | 1 + app/controllers/requests_controller.rb | 9 +- app/controllers/search_controller.rb | 4 +- .../mark_inactive_contributor_inactive_job.rb | 5 +- app/jobs/resubscribe_contributor_job.rb | 5 +- app/jobs/unsubscribe_contributor_job.rb | 5 +- app/models/activity_notification.rb | 1 + app/models/contributor.rb | 28 +----- app/models/message.rb | 22 ++++- app/models/organization.rb | 20 +++++ app/models/request.rb | 6 +- app/views/contributors/index.html.erb | 3 +- app/views/contributors/show.html.erb | 2 +- app/views/requests/edit.html.erb | 2 +- app/views/requests/new.html.erb | 2 +- ...ganization_id_to_activity_notifications.rb | 29 +++++++ ...6063319_add_organization_id_to_taggings.rb | 29 +++++++ ...30085839_rebuild_pg_search_multi_search.rb | 15 ++++ db/data_schema.rb | 2 +- ...ganization_id_to_activity_notifications.rb | 7 ++ ..._to_taggings.acts_as_taggable_on_engine.rb | 15 ++++ ..._organization_id_to_pg_search_documents.rb | 7 ++ db/schema.rb | 10 ++- db/seeds/multi_tenancy.rb | 85 +++++++++++++++++++ spec/components/contributors_index_spec.rb | 1 + spec/components/request_form_spec.rb | 9 +- ..._inactive_contributor_inactive_job_spec.rb | 5 +- spec/jobs/resubscribe_contributor_job_spec.rb | 12 +-- spec/mailboxes/replies_mailbox_spec.rb | 19 +++-- spec/models/contributor_spec.rb | 38 +++++---- spec/models/message_spec.rb | 56 ++++++++++-- spec/models/organization_spec.rb | 14 +++ spec/requests/telegram/webhook_spec.rb | 2 +- spec/requests/threema/webhook_spec.rb | 2 +- .../shared_examples/activity_notifications.rb | 2 +- .../contributor_resubscribes.rb | 4 +- .../contributor_unsubscribes.rb | 4 +- spec/system/contributors/filter_spec.rb | 7 +- .../dashboard/activity_notifications_spec.rb | 9 +- spec/system/profile/index_spec.rb | 3 +- .../system/requests/deleting_requests_spec.rb | 16 ++-- spec/system/requests/editing_requests_spec.rb | 6 +- .../requests/scheduling_requests_spec.rb | 4 +- 54 files changed, 447 insertions(+), 140 deletions(-) create mode 100644 db/data/20240726061113_add_organization_id_to_activity_notifications.rb create mode 100644 db/data/20240726063319_add_organization_id_to_taggings.rb create mode 100644 db/data/20240730085839_rebuild_pg_search_multi_search.rb create mode 100644 db/migrate/20240528094251_add_organization_id_to_activity_notifications.rb create mode 100644 db/migrate/20240726062741_add_tenant_to_taggings.acts_as_taggable_on_engine.rb create mode 100644 db/migrate/20240726065204_add_organization_id_to_pg_search_documents.rb create mode 100644 db/seeds/multi_tenancy.rb diff --git a/app/activity_notifications/chat_message_sent.rb b/app/activity_notifications/chat_message_sent.rb index ab67709c7..faef6f402 100644 --- a/app/activity_notifications/chat_message_sent.rb +++ b/app/activity_notifications/chat_message_sent.rb @@ -3,7 +3,7 @@ class ChatMessageSent < Noticed::Base deliver_by :database, format: :to_database, association: :notifications_as_recipient - param :contributor_id, :request_id, :user_id, :message_id + param :contributor_id, :request_id, :user_id, :message_id, :organization_id def to_database { @@ -11,7 +11,8 @@ def to_database contributor_id: params[:contributor_id], request_id: params[:request_id], user_id: params[:user_id], - message_id: params[:message_id] + message_id: params[:message_id], + organization_id: params[:organization_id] } end diff --git a/app/activity_notifications/contributor_marked_inactive.rb b/app/activity_notifications/contributor_marked_inactive.rb index 4375569cb..5d6f3a4a0 100644 --- a/app/activity_notifications/contributor_marked_inactive.rb +++ b/app/activity_notifications/contributor_marked_inactive.rb @@ -3,12 +3,13 @@ class ContributorMarkedInactive < Noticed::Base deliver_by :database, format: :to_database, association: :notifications_as_recipient - param :contributor_id + param :contributor_id, :organization_id def to_database { type: self.class.name, - contributor_id: params[:contributor_id] + contributor_id: params[:contributor_id], + organization_id: params[:organization_id] } end diff --git a/app/activity_notifications/contributor_subscribed.rb b/app/activity_notifications/contributor_subscribed.rb index 148a9e80c..83a018e2d 100644 --- a/app/activity_notifications/contributor_subscribed.rb +++ b/app/activity_notifications/contributor_subscribed.rb @@ -3,12 +3,13 @@ class ContributorSubscribed < Noticed::Base deliver_by :database, format: :to_database, association: :notifications_as_recipient - param :contributor_id + param :contributor_id, :organization_id def to_database { type: self.class.name, - contributor_id: params[:contributor_id] + contributor_id: params[:contributor_id], + organization_id: params[:organization_id] } end diff --git a/app/activity_notifications/message_received.rb b/app/activity_notifications/message_received.rb index 6dfc7884b..f5cb37a96 100644 --- a/app/activity_notifications/message_received.rb +++ b/app/activity_notifications/message_received.rb @@ -3,14 +3,15 @@ class MessageReceived < Noticed::Base deliver_by :database, format: :to_database, association: :notifications_as_recipient - param :contributor_id, :request_id, :message_id + param :contributor_id, :request_id, :message_id, :organization_id def to_database { type: self.class.name, contributor_id: params[:contributor_id], request_id: params[:request_id], - message_id: params[:message_id] + message_id: params[:message_id], + organization_id: params[:organization_id] } end diff --git a/app/activity_notifications/onboarding_completed.rb b/app/activity_notifications/onboarding_completed.rb index a2116ed2d..3398d8595 100644 --- a/app/activity_notifications/onboarding_completed.rb +++ b/app/activity_notifications/onboarding_completed.rb @@ -3,12 +3,13 @@ class OnboardingCompleted < Noticed::Base deliver_by :database, format: :to_database, association: :notifications_as_recipient - param :contributor_id + param :contributor_id, :organization_id def to_database { type: self.class.name, - contributor_id: params[:contributor_id] + contributor_id: params[:contributor_id], + organization_id: params[:organization_id] } end diff --git a/app/activity_notifications/request_scheduled.rb b/app/activity_notifications/request_scheduled.rb index 84af6be74..a2cfeaf00 100644 --- a/app/activity_notifications/request_scheduled.rb +++ b/app/activity_notifications/request_scheduled.rb @@ -3,12 +3,13 @@ class RequestScheduled < Noticed::Base deliver_by :database, format: :to_database, association: :notifications_as_recipient - param :request_id + param :request_id, :organization_id def to_database { type: self.class.name, - request_id: params[:request_id] + request_id: params[:request_id], + organization_id: params[:organization_id] } end diff --git a/app/components/contributor_quick_edit_form/contributor_quick_edit_form.rb b/app/components/contributor_quick_edit_form/contributor_quick_edit_form.rb index a963b5cd8..92f771df4 100644 --- a/app/components/contributor_quick_edit_form/contributor_quick_edit_form.rb +++ b/app/components/contributor_quick_edit_form/contributor_quick_edit_form.rb @@ -2,18 +2,19 @@ module ContributorQuickEditForm class ContributorQuickEditForm < ApplicationComponent - def initialize(contributor:) + def initialize(organization:, contributor:) super + @organization = organization @contributor = contributor end private - attr_reader :contributor + attr_reader :organization, :contributor def available_tags - Contributor.all_tags_with_count.to_json + organization.contributors_tags_with_count.to_json end end end diff --git a/app/components/contributors_index/contributors_index.rb b/app/components/contributors_index/contributors_index.rb index 06cc693e6..fc05ab37f 100644 --- a/app/components/contributors_index/contributors_index.rb +++ b/app/components/contributors_index/contributors_index.rb @@ -2,7 +2,7 @@ module ContributorsIndex class ContributorsIndex < ApplicationComponent - def initialize(organization:, contributors:, state:, active_count:, inactive_count:, unsubscribed_count:, filter_count:, tag_list: nil) + def initialize(organization:, contributors:, state:, active_count:, inactive_count:, unsubscribed_count:, filter_count:, available_tags:, tag_list: nil) super @organization = organization @@ -13,15 +13,12 @@ def initialize(organization:, contributors:, state:, active_count:, inactive_cou @inactive_count = inactive_count @unsubscribed_count = unsubscribed_count @filter_count = filter_count + @available_tags = available_tags end private - attr_reader :organization, :contributors, :tag_list, :state, :active_count, :inactive_count, :unsubscribed_count, :filter_count - - def available_tags - Contributor.all_tags_with_count.to_json - end + attr_reader :organization, :contributors, :tag_list, :state, :active_count, :inactive_count, :unsubscribed_count, :filter_count, :available_tags def active_contributors_count tag_list.present? && state == :active ? filter_count : active_count diff --git a/app/components/request_form/request_form.html.erb b/app/components/request_form/request_form.html.erb index 3f2ff0115..79fca9290 100644 --- a/app/components/request_form/request_form.html.erb +++ b/app/components/request_form/request_form.html.erb @@ -135,7 +135,7 @@
<%= c 'device_frame', class: 'RequestForm-preview' do %> - <%= c 'chat_preview', organization: organization do %> + <%= c 'chat_preview', organization: request.organization do %> <% end %> <% end %> diff --git a/app/components/request_form/request_form.rb b/app/components/request_form/request_form.rb index 2bd59d008..528419de3 100644 --- a/app/components/request_form/request_form.rb +++ b/app/components/request_form/request_form.rb @@ -2,20 +2,16 @@ module RequestForm class RequestForm < ApplicationComponent - def initialize(organization:, request:) + def initialize(request:, available_tags:) super - @organization = organization @request = request + @available_tags = available_tags end private - attr_reader :organization, :request - - def available_tags - Contributor.all_tags_with_count.to_json - end + attr_reader :request, :available_tags def schedule_send_for_or_default datetime = request.planned? ? request.schedule_send_for : Time.current diff --git a/app/controllers/contributors_controller.rb b/app/controllers/contributors_controller.rb index 188e1bb72..149299c2e 100644 --- a/app/controllers/contributors_controller.rb +++ b/app/controllers/contributors_controller.rb @@ -27,7 +27,7 @@ def index @active_count = Contributor.active.count @inactive_count = Contributor.inactive.count @unsubscribed_count = Contributor.unsubscribed.count - @available_tags = Contributor.all_tags_with_count.to_json + @available_tags = @organization.contributors_tags_with_count.to_json @contributors = filtered_contributors @contributors = @contributors.with_tags(tag_list_params) diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 8f8d3b5b1..2b69c0b8e 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -13,6 +13,7 @@ def index def activity_notifications grouped = current_user.notifications_as_recipient + .where(organization_id: @organization.id) .newest_first .includes({ contributor: { avatar_attachment: :blob } }, :request, :message, :user) .last_four_weeks diff --git a/app/controllers/requests_controller.rb b/app/controllers/requests_controller.rb index 47b29ec8d..68f414915 100644 --- a/app/controllers/requests_controller.rb +++ b/app/controllers/requests_controller.rb @@ -5,6 +5,7 @@ class RequestsController < ApplicationController before_action :notifications_params, only: :notifications before_action :disallow_edit, only: %i[edit update] before_action :disallow_destroy, only: :destroy + before_action :available_tags, only: %i[new edit] def index @filter = filter_param @@ -34,7 +35,7 @@ def create end def new - @request = Request.new + @request = Request.new(organization: @organization) end def edit; end @@ -58,7 +59,7 @@ def update def destroy if @request.destroy - redirect_to requests_url(filter: :planned), notice: t('request.destroy.successful', request_title: @request.title) + redirect_to requests_url(filter: :planned), flash: { notice: t('request.destroy.successful', request_title: @request.title) } else render :edit, status: :unprocessable_entity end @@ -111,6 +112,10 @@ def set_request @request = Request.find(params[:id]) end + def available_tags + @available_tags ||= @organization.contributors_tags_with_count.to_json + end + def request_params params.require(:request).permit(:title, :text, :tag_list, :schedule_send_for, files: []) end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 131885813..c9d61b487 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -4,6 +4,8 @@ class SearchController < ApplicationController def index @results = [] query = params[:q] - @results = PgSearch.multisearch(query).map(&:searchable) if query + return unless query + + @results = PgSearch.multisearch(query).where(organization_id: @organization.id).map(&:searchable) end end diff --git a/app/jobs/mark_inactive_contributor_inactive_job.rb b/app/jobs/mark_inactive_contributor_inactive_job.rb index c36d67f4e..fc06e58f0 100644 --- a/app/jobs/mark_inactive_contributor_inactive_job.rb +++ b/app/jobs/mark_inactive_contributor_inactive_job.rb @@ -12,7 +12,10 @@ def perform(organization_id:, contributor_id:) contributor.deactivated_at = Time.current contributor.save(validate: false) - ContributorMarkedInactive.with(contributor_id: contributor.id).deliver_later(User.all) + ContributorMarkedInactive.with( + contributor_id: contributor.id, + organization_id: organization.id + ).deliver_later(organization.users + User.admin.all) User.admin.find_each do |admin| PostmarkAdapter::Outbound.contributor_marked_as_inactive!(admin, contributor, organization) end diff --git a/app/jobs/resubscribe_contributor_job.rb b/app/jobs/resubscribe_contributor_job.rb index c325191b0..b83cf65d4 100644 --- a/app/jobs/resubscribe_contributor_job.rb +++ b/app/jobs/resubscribe_contributor_job.rb @@ -24,7 +24,10 @@ def perform(organization_id, contributor_id, adapter) contributor.update!(unsubscribed_at: nil) adapter.send_welcome_message!(contributor, organization) - ContributorSubscribed.with(contributor_id: contributor.id).deliver_later(User.all) + ContributorSubscribed.with( + contributor_id: contributor.id, + organization_id: organization.id + ).deliver_later(organization.users + User.admin.all) User.admin.find_each do |admin| PostmarkAdapter::Outbound.contributor_resubscribed!(admin, contributor, organization) end diff --git a/app/jobs/unsubscribe_contributor_job.rb b/app/jobs/unsubscribe_contributor_job.rb index cf368bdc5..7d5c50596 100644 --- a/app/jobs/unsubscribe_contributor_job.rb +++ b/app/jobs/unsubscribe_contributor_job.rb @@ -13,7 +13,10 @@ def perform(organization_id, contributor_id, adapter) contributor.update!(unsubscribed_at: Time.current) adapter.send_unsubsribed_successfully_message!(contributor, organization) - ContributorMarkedInactive.with(contributor_id: contributor.id).deliver_later(User.all) + ContributorMarkedInactive.with( + contributor_id: contributor.id, + organization_id: organization.id + ).deliver_later(organization.users + User.admin.all) User.admin.find_each do |admin| PostmarkAdapter::Outbound.contributor_unsubscribed!(admin, contributor, organization) end diff --git a/app/models/activity_notification.rb b/app/models/activity_notification.rb index 489d64c75..03af43abd 100644 --- a/app/models/activity_notification.rb +++ b/app/models/activity_notification.rb @@ -7,6 +7,7 @@ class ActivityNotification < ApplicationRecord belongs_to :request, optional: true belongs_to :message, optional: true belongs_to :user, optional: true + belongs_to :organization scope :last_four_weeks, -> { where(created_at: 4.weeks.ago..Time.current) } end diff --git a/app/models/contributor.rb b/app/models/contributor.rb index 44a6c213c..87c2220bf 100644 --- a/app/models/contributor.rb +++ b/app/models/contributor.rb @@ -9,7 +9,8 @@ class Contributor < ApplicationRecord after_create_commit :notify_recipient - multisearchable against: %i[first_name last_name username note] + multisearchable against: %i[first_name last_name username note], + additional_attributes: ->(contributor) { { organization_id: contributor.organization_id } } has_many :replies, class_name: 'Message', as: :sender, dependent: :destroy has_many :received_messages, class_name: 'Message', inverse_of: :recipient, foreign_key: 'recipient_id', dependent: :destroy @@ -24,6 +25,7 @@ class Contributor < ApplicationRecord accepts_nested_attributes_for :json_web_token acts_as_taggable_on :tags + acts_as_taggable_tenant :organization_id default_scope { order(:first_name, :last_name) } scope :active, -> { where(deactivated_at: nil, unsubscribed_at: nil) } @@ -66,28 +68,6 @@ def self.with_lowercased_email(email) find_by('lower(email) in (?)', Array.wrap(email).map(&:downcase)) end - def self.all_tags_with_count - ActsAsTaggableOn::Tag - .joins(:taggings) - .select('tags.id, tags.name, count(taggings.id) as taggings_count') - .group('tags.id') - .where(taggings: { taggable_type: name }) - .all - .map do |tag| - { - id: tag.id, - name: tag.name, - value: tag.name, - count: tag.taggings_count, - color: Contributor.tag_color_from_id(tag.id) - } - end - end - - def self.tag_color_from_id(tag_id) - ApplicationController.helpers.color_from_id(tag_id) - end - def reply(message_decorator) request = active_request or return nil ActiveRecord::Base.transaction do @@ -241,7 +221,7 @@ def stats private def notify_recipient - OnboardingCompleted.with(contributor_id: id).deliver_later(User.all) + OnboardingCompleted.with(contributor_id: id, organization_id: organization.id).deliver_later(organization.users + User.admin.all) end end # rubocop:enable Metrics/ClassLength diff --git a/app/models/message.rb b/app/models/message.rb index ac2d21d2d..c8891201a 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -5,7 +5,8 @@ class Message < ApplicationRecord default_scope { order(created_at: :desc) } - multisearchable against: :text, if: :reply? + multisearchable against: :text, if: :reply?, + additional_attributes: ->(message) { { organization_id: message.organization.id } } belongs_to :sender, polymorphic: true, optional: true belongs_to :recipient, class_name: 'Contributor', optional: true @@ -63,14 +64,27 @@ def sent_from_contributor? private + # rubocop:disable Metrics/AbcSize def notify_recipient + # binding.pry if reply? - MessageReceived.with(contributor_id: sender_id, request_id: request.id, message_id: id).deliver_later(User.all) + MessageReceived.with( + contributor_id: sender_id, + request_id: request.id, + message_id: id, + organization_id: organization.id + ).deliver_later(organization.users + User.admin.all) elsif !broadcasted? - ChatMessageSent.with(contributor_id: recipient.id, request_id: request.id, user_id: sender_id, - message_id: id).deliver_later(User.all) + ChatMessageSent.with( + contributor_id: recipient.id, + request_id: request.id, + user_id: sender_id, + message_id: id, + organization_id: organization.id + ).deliver_later(organization.users + User.admin.all) end end + # rubocop:enable Metrics/AbcSize def send_if_outbound return if manually_created? || reply? diff --git a/app/models/organization.rb b/app/models/organization.rb index aa22f6e59..5e2f002ed 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -12,6 +12,7 @@ class Organization < ApplicationRecord has_many :users, class_name: 'User', dependent: :destroy has_many :contributors, dependent: :destroy has_many :requests, dependent: :destroy + has_many :notifications_as_mentioned, class_name: 'ActivityNotification', dependent: :destroy has_one_attached :onboarding_logo has_one_attached :onboarding_hero @@ -93,6 +94,25 @@ def threema_instance Threema.new(api_identity: threemarb_api_identity, api_secret: threemarb_api_secret, private_key: threemarb_private) end + def contributors_tags_with_count + ActsAsTaggableOn::Tag + .for_tenant(id) + .joins(:taggings) + .where(taggings: { taggable_type: Contributor.name }) + .select('tags.id, tags.name, count(taggings.id) as taggings_count') + .group('tags.id') + .all + .map do |tag| + { + id: tag.id, + name: tag.name, + value: tag.name, + count: tag.taggings_count, + color: ApplicationController.helpers.color_from_id(tag.id) + } + end + end + private def notify_admin diff --git a/app/models/request.rb b/app/models/request.rb index edf2b9893..f18cdb12c 100644 --- a/app/models/request.rb +++ b/app/models/request.rb @@ -4,7 +4,8 @@ class Request < ApplicationRecord include PlaceholderHelper include PgSearch::Model - multisearchable against: %i[title text] + multisearchable against: %i[title text], + additional_attributes: ->(request) { { organization_id: request.organization_id } } belongs_to :user belongs_to :organization @@ -24,6 +25,7 @@ class Request < ApplicationRecord validates :text, length: { maximum: 1500 }, presence: true, unless: -> { files.attached? } acts_as_taggable_on :tags + acts_as_taggable_tenant :organization_id after_create :broadcast_request @@ -69,7 +71,7 @@ def messages_by_contributor def self.broadcast!(request) if request.planned? BroadcastRequestJob.delay(run_at: request.schedule_send_for).perform_later(request.id) - RequestScheduled.with(request_id: request.id).deliver_later(User.all) + RequestScheduled.with(request_id: request.id, organization_id: request.organization.id).deliver_later(User.all) else Contributor.active.with_tags(request.tag_list).each do |contributor| message = Message.new( diff --git a/app/views/contributors/index.html.erb b/app/views/contributors/index.html.erb index 3f483b247..34d1ab28f 100644 --- a/app/views/contributors/index.html.erb +++ b/app/views/contributors/index.html.erb @@ -6,5 +6,6 @@ active_count: @active_count, inactive_count: @inactive_count, unsubscribed_count: @unsubscribed_count, - filter_count: @filter_count + filter_count: @filter_count, + available_tags: @available_tags %> diff --git a/app/views/contributors/show.html.erb b/app/views/contributors/show.html.erb index 777f69bf4..4f971bad4 100644 --- a/app/views/contributors/show.html.erb +++ b/app/views/contributors/show.html.erb @@ -16,7 +16,7 @@ <%= c 'section', styles: [:wide, :xlargeSpaceBetween, :noMarginTop] do %> <%= c 'contributor_header', organization: @organization, contributor: @contributor %> - <%= c 'contributor_quick_edit_form', contributor: @contributor %> + <%= c 'contributor_quick_edit_form', organization: @organization, contributor: @contributor %>
diff --git a/app/views/requests/edit.html.erb b/app/views/requests/edit.html.erb index 4bf266529..01afaabaa 100644 --- a/app/views/requests/edit.html.erb +++ b/app/views/requests/edit.html.erb @@ -6,6 +6,6 @@ <% end %> <%= c 'section' do %> - <%= c 'request_form', organization: @organization, request: @request %> + <%= c 'request_form', request: @request, available_tags: @available_tags %> <% end %> <% end %> diff --git a/app/views/requests/new.html.erb b/app/views/requests/new.html.erb index b062f0ea5..65fc9f80b 100644 --- a/app/views/requests/new.html.erb +++ b/app/views/requests/new.html.erb @@ -7,6 +7,6 @@ <% end %> <%= c 'section' do %> - <%= c 'request_form', organization: @organization, request: @request %> + <%= c 'request_form', request: @request, available_tags: @available_tags %> <% end %> <% end %> diff --git a/db/data/20240726061113_add_organization_id_to_activity_notifications.rb b/db/data/20240726061113_add_organization_id_to_activity_notifications.rb new file mode 100644 index 000000000..06f348b66 --- /dev/null +++ b/db/data/20240726061113_add_organization_id_to_activity_notifications.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class AddOrganizationIdToActivityNotifications < ActiveRecord::Migration[6.1] + def up + ActiveRecord::Base.transaction do + organization = Organization.singleton + return unless organization + + ActivityNotification.find_each do |notification| + notification.organization_id = organization.id + notification.save! + Rails.logger.debug '.' + end + end + end + + def down + ActiveRecord::Base.transaction do + organization = Organization.singleton + return unless organization + + ActivityNotification.find_each do |notification| + notification.organization_id = nil + notification.save! + Rails.logger.debug '.' + end + end + end +end diff --git a/db/data/20240726063319_add_organization_id_to_taggings.rb b/db/data/20240726063319_add_organization_id_to_taggings.rb new file mode 100644 index 000000000..a5bd95ad5 --- /dev/null +++ b/db/data/20240726063319_add_organization_id_to_taggings.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class AddOrganizationIdToTaggings < ActiveRecord::Migration[6.1] + def up + ActiveRecord::Base.transaction do + organization = Organization.singleton + return unless organization + + ActsAsTaggableOn::Tagging.find_each do |tag| + tag.tenant = organization.id + tag.save! + Rails.logger.debug '.' + end + end + end + + def down + ActiveRecord::Base.transaction do + organization = Organization.singleton + return unless organization + + ActsAsTaggableOn::Tagging.find_each do |tag| + tag.tenant = nil + tag.save! + Rails.logger.debug '.' + end + end + end +end diff --git a/db/data/20240730085839_rebuild_pg_search_multi_search.rb b/db/data/20240730085839_rebuild_pg_search_multi_search.rb new file mode 100644 index 000000000..f1302a1d3 --- /dev/null +++ b/db/data/20240730085839_rebuild_pg_search_multi_search.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class RebuildPgSearchMultiSearch < ActiveRecord::Migration[6.1] + def up + PgSearch::Multisearch.rebuild(Contributor) + PgSearch::Multisearch.rebuild(Message) + PgSearch::Multisearch.rebuild(Request) + end + + def down + PgSearch::Multisearch.rebuild(Contributor) + PgSearch::Multisearch.rebuild(Message) + PgSearch::Multisearch.rebuild(Request) + end +end diff --git a/db/data_schema.rb b/db/data_schema.rb index 72cc88a37..05ab7451c 100644 --- a/db/data_schema.rb +++ b/db/data_schema.rb @@ -1,3 +1,3 @@ # frozen_string_literal: true -DataMigrate::Data.define(version: 20_240_722_092_527) +DataMigrate::Data.define(version: 20_240_730_085_839) diff --git a/db/migrate/20240528094251_add_organization_id_to_activity_notifications.rb b/db/migrate/20240528094251_add_organization_id_to_activity_notifications.rb new file mode 100644 index 000000000..74278d2f0 --- /dev/null +++ b/db/migrate/20240528094251_add_organization_id_to_activity_notifications.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddOrganizationIdToActivityNotifications < ActiveRecord::Migration[6.1] + def change + add_reference :activity_notifications, :organization, foreign_key: true + end +end diff --git a/db/migrate/20240726062741_add_tenant_to_taggings.acts_as_taggable_on_engine.rb b/db/migrate/20240726062741_add_tenant_to_taggings.acts_as_taggable_on_engine.rb new file mode 100644 index 000000000..2aa74171d --- /dev/null +++ b/db/migrate/20240726062741_add_tenant_to_taggings.acts_as_taggable_on_engine.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# This migration comes from acts_as_taggable_on_engine (originally 7) + +class AddTenantToTaggings < ActiveRecord::Migration[6.0] + def self.up + add_column ActsAsTaggableOn.taggings_table, :tenant, :string, limit: 128 + add_index ActsAsTaggableOn.taggings_table, :tenant unless index_exists? ActsAsTaggableOn.taggings_table, :tenant + end + + def self.down + remove_index ActsAsTaggableOn.taggings_table, :tenant + remove_column ActsAsTaggableOn.taggings_table, :tenant + end +end diff --git a/db/migrate/20240726065204_add_organization_id_to_pg_search_documents.rb b/db/migrate/20240726065204_add_organization_id_to_pg_search_documents.rb new file mode 100644 index 000000000..01a09c92a --- /dev/null +++ b/db/migrate/20240726065204_add_organization_id_to_pg_search_documents.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddOrganizationIdToPgSearchDocuments < ActiveRecord::Migration[6.1] + def change + add_reference :pg_search_documents, :organization, foreign_key: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 01c71f1dd..4d882cf03 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_06_27_182314) do +ActiveRecord::Schema.define(version: 2024_07_26_065204) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -65,8 +65,10 @@ t.bigint "message_id" t.bigint "request_id" t.bigint "user_id" + t.bigint "organization_id" t.index ["contributor_id"], name: "index_activity_notifications_on_contributor_id" t.index ["message_id"], name: "index_activity_notifications_on_message_id" + t.index ["organization_id"], name: "index_activity_notifications_on_organization_id" t.index ["read_at"], name: "index_activity_notifications_on_read_at" t.index ["recipient_type", "recipient_id"], name: "index_activity_notifications_on_recipient" t.index ["request_id"], name: "index_activity_notifications_on_request_id" @@ -252,6 +254,8 @@ t.bigint "searchable_id" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.bigint "organization_id" + t.index ["organization_id"], name: "index_pg_search_documents_on_organization_id" t.index ["searchable_type", "searchable_id"], name: "index_pg_search_documents_on_searchable_type_and_searchable_id" end @@ -292,6 +296,7 @@ t.integer "tagger_id" t.string "context", limit: 128 t.datetime "created_at" + t.string "tenant", limit: 128 t.index ["context"], name: "index_taggings_on_context" t.index ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true t.index ["tag_id"], name: "index_taggings_on_tag_id" @@ -301,6 +306,7 @@ t.index ["taggable_type"], name: "index_taggings_on_taggable_type" t.index ["tagger_id", "tagger_type"], name: "index_taggings_on_tagger_id_and_tagger_type" t.index ["tagger_id"], name: "index_taggings_on_tagger_id" + t.index ["tenant"], name: "index_taggings_on_tenant" end create_table "tags", id: :serial, force: :cascade do |t| @@ -334,6 +340,7 @@ add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "activity_notifications", "contributors" add_foreign_key "activity_notifications", "messages" + add_foreign_key "activity_notifications", "organizations" add_foreign_key "activity_notifications", "requests" add_foreign_key "activity_notifications", "users" add_foreign_key "contributors", "organizations" @@ -343,6 +350,7 @@ add_foreign_key "messages", "requests" add_foreign_key "organizations", "business_plans" add_foreign_key "organizations", "users", column: "contact_person_id" + add_foreign_key "pg_search_documents", "organizations" add_foreign_key "photos", "messages" add_foreign_key "requests", "organizations" add_foreign_key "requests", "users" diff --git a/db/seeds/multi_tenancy.rb b/db/seeds/multi_tenancy.rb new file mode 100644 index 000000000..72c5997a6 --- /dev/null +++ b/db/seeds/multi_tenancy.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'factory_bot_rails' +require 'faker' + +request_count = 20 + +business_plan = BusinessPlan.find_by(name: 'Free') +organizations = 3.times.collect do + Organization.create!( + name: Faker::Company.name, + upgrade_discount: rand(0..25), + business_plan: business_plan + ) +end +users = 10.times.collect do + FactoryBot.create(:user, organization: organizations.sample) +end + +# images = 10.times.map { URI(Faker::Avatar.image(size: '50x50', format: 'png', set: 'set5')) } + +FactoryBot.modify do + factory :contributor do + organization { organizations.sample } + # after(:build) do |contributor| + # image = images.sample + # contributor.avatar.attach( + # io: image.open, + # filename: File.basename(image.path) + # ) + # end + end +end + +FactoryBot.modify do + factory :request do + title { Faker::Lorem.question } + text { Faker::Lorem.paragraph } + user { users.sample } + end +end + +Rails.logger.debug 'Seeding requests..' +requests = request_count.times.collect do + created_at = Time.zone.at(rand(14.days.ago..Time.current)) + broadcasted_at = Time.zone.at(rand(created_at..Time.current)) + FactoryBot.build(:request, + created_at: created_at, + broadcasted_at: broadcasted_at, + organization: organizations.sample) do |request| + request.class.skip_callback(:create, :after, :broadcast_request, raise: false) + request.save! + end +end + +Rails.logger.debug 'Seeding contributors..' + +FactoryBot.create_list(:contributor, 5) +FactoryBot.create_list(:contributor, 20, :threema_contributor, :skip_validations) +FactoryBot.create_list(:contributor, 30, :telegram_contributor) +FactoryBot.create_list(:contributor, 40, :signal_contributor) +FactoryBot.create_list(:contributor, 100, :whats_app_contributor) +contributors = Contributor.all + +FactoryBot.modify do + factory :message do + sender_type { 'Contributor' } + text { Faker::Lorem.paragraph } + unknown_content { false } + broadcasted { false } + sender { contributors.sample } + recipient { nil } + end +end + +Rails.logger.debug 'Seeding messages..' +message_count = request_count * (contributors.count * 0.5).to_i +message_count.times do + request = requests.sample + created_at = Time.zone.at(rand(request.broadcasted_at..Time.current)) + FactoryBot.build(:message, request: request, created_at: created_at, updated_at: created_at) do |message| + message.class.skip_callback(:create, :after, :send_if_outbound, raise: false) + message.save! + end +end diff --git a/spec/components/contributors_index_spec.rb b/spec/components/contributors_index_spec.rb index 145c77dc6..a5ecf2064 100644 --- a/spec/components/contributors_index_spec.rb +++ b/spec/components/contributors_index_spec.rb @@ -15,6 +15,7 @@ inactive_count: 1, unsubscribed_count: 0, filter_count: 0, + available_tags: [], tag_list: [] } end diff --git a/spec/components/request_form_spec.rb b/spec/components/request_form_spec.rb index 28e1acfd5..1c1047842 100644 --- a/spec/components/request_form_spec.rb +++ b/spec/components/request_form_spec.rb @@ -6,7 +6,7 @@ subject { render_inline(described_class.new(**params)) } let(:organization) { create(:organization) } - let(:params) { { organization: organization, request: build(:request) } } + let(:params) { { request: build(:request, organization: organization), available_tags: [] } } it { should have_css('.RequestForm') } it { is_expected.not_to have_css('button[data-action="request-form#openModal"]', @@ -14,7 +14,12 @@ } context 'planned request' do - let(:params) { { organization: organization, request: create(:request, broadcasted_at: nil, schedule_send_for: 1.day.from_now) } } + let(:params) do + { + request: create(:request, broadcasted_at: nil, schedule_send_for: 1.day.from_now, organization: organization), + available_tags: [] + } + end it 'renders a button to open a confirm destroy modal' do expect(subject).to have_css('button[data-action="request-form#openModal"]', diff --git a/spec/jobs/mark_inactive_contributor_inactive_job_spec.rb b/spec/jobs/mark_inactive_contributor_inactive_job_spec.rb index 39877c7d0..263bc23fd 100644 --- a/spec/jobs/mark_inactive_contributor_inactive_job_spec.rb +++ b/spec/jobs/mark_inactive_contributor_inactive_job_spec.rb @@ -32,7 +32,10 @@ end context 'that does belong to the organization' do - before { contributor.update(organization_id: organization.id) } + before do + contributor.update(organization_id: organization.id) + non_admin_user.update(organization_id: organization.id) + end it { is_expected.to change { contributor.reload.deactivated_at }.from(nil).to(kind_of(ActiveSupport::TimeWithZone)) } it_behaves_like 'an ActivityNotification', 'ContributorMarkedInactive' diff --git a/spec/jobs/resubscribe_contributor_job_spec.rb b/spec/jobs/resubscribe_contributor_job_spec.rb index 7e213a006..4065e5cf1 100644 --- a/spec/jobs/resubscribe_contributor_job_spec.rb +++ b/spec/jobs/resubscribe_contributor_job_spec.rb @@ -4,7 +4,7 @@ RSpec.describe ResubscribeContributorJob do describe '#perform_later(contributor_id, adapter)' do - let(:user) { create(:user) } + let(:user) { create(:user, organization: organization) } let(:organization) { create(:organization) } subject { -> { described_class.new.perform(organization.id, contributor.id, adapter) } } @@ -27,7 +27,7 @@ signal_onboarding_completed_at: 1.week.ago, unsubscribed_at: 1.day.ago, deactivated_at: Time.current, - deactivated_by_user: create(:user), + deactivated_by_user: user, organization: organization) end end @@ -62,7 +62,7 @@ telegram_id: 123_456_789, unsubscribed_at: 1.week.ago, deactivated_at: Time.current, - deactivated_by_user: create(:user), + deactivated_by_user: user, organization: organization) end end @@ -107,7 +107,7 @@ threema_id: threema_id, unsubscribed_at: 1.month.ago, deactivated_at: Time.current, - deactivated_by_user: create(:user), + deactivated_by_user: user, organization: organization) end end @@ -147,7 +147,7 @@ whats_app_phone_number: '+491234567', unsubscribed_at: 5.days.ago, deactivated_at: Time.current, - deactivated_by_user: create(:user), + deactivated_by_user: user, organization: organization) end end @@ -184,7 +184,7 @@ whats_app_phone_number: '+491234567', unsubscribed_at: 5.days.ago, deactivated_at: Time.current, - deactivated_by_user: create(:user), + deactivated_by_user: user, organization: organization) end end diff --git a/spec/mailboxes/replies_mailbox_spec.rb b/spec/mailboxes/replies_mailbox_spec.rb index 006bf8692..279f8d9e3 100644 --- a/spec/mailboxes/replies_mailbox_spec.rb +++ b/spec/mailboxes/replies_mailbox_spec.rb @@ -34,22 +34,29 @@ it { should_not(change { Message.count }) } describe 'given an active request' do - let(:request) { create(:request, title: 'Wie geht es euren Haustieren in Corona-Zeiten?') } + let(:request) { create(:request, title: 'Wie geht es euren Haustieren in Corona-Zeiten?', organization: organization) } before(:each) { create(:message, request: request, sender: nil, recipient: contributor, broadcasted: true) } + let!(:admin) { create(:user, admin: true) } it { should(change { Message.count }.from(1).to(2)) } describe 'after email processing' do let(:replies) { Message.where(sender: contributor).pluck(:text) } - before(:each) { subject.call } + before do + organization.update!(users: create_list(:user, 5)) + subject.call + end it { should(change { Message.count }.from(2).to(3)) } - describe 'MessageReceived ActivityNotification' do - context 'creates an ActivityNotification' do - it_behaves_like 'an ActivityNotification', 'MessageReceived' - end + it 'it creates a MessageReceived notification for each user and admin' do + subject.call + recipient_ids = ActivityNotification.where(type: MessageReceived.name).pluck(:recipient_id).uniq.sort + user_ids = organization.users.pluck(:id) + admin_id = admin.id + ids = (user_ids << admin_id).sort + expect(recipient_ids).to eq(ids) end describe 'with matching from address' do diff --git a/spec/models/contributor_spec.rb b/spec/models/contributor_spec.rb index d31dcfdde..38876f54c 100644 --- a/spec/models/contributor_spec.rb +++ b/spec/models/contributor_spec.rb @@ -4,9 +4,11 @@ RSpec.describe Contributor, type: :model do let(:the_request) do - create(:request, title: 'Hitchhiker’s Guide', text: 'What is the answer to life, the universe, and everything?') + create(:request, title: 'Hitchhiker’s Guide', text: 'What is the answer to life, the universe, and everything?', + organization: organization, user: create(:user, organization: organization)) end - let(:contributor) { create(:contributor, email: 'contributor@example.org') } + let(:organization) { create(:organization) } + let(:contributor) { create(:contributor, email: 'contributor@example.org', organization: organization) } it 'is sorted in alphabetical order' do zora = create(:contributor, first_name: 'Zora', last_name: 'Zimmermann') @@ -328,20 +330,6 @@ end end - describe '.all_tags_with_count' do - subject { Contributor.all_tags_with_count.pluck(:name, :count) } - - context 'given a contributor with a tag' do - let!(:contributor) { create(:contributor, tag_list: %w[Homeowner]) } - it { should eq([['Homeowner', 1]]) } - - context 'and a request with the same tag' do - let!(:request) { create(:request, tag_list: %w[Homeowner]) } - it { should eq([['Homeowner', 1]]) } - end - end - end - describe '#conversations' do let(:received_message) do create(:message, text: 'Message with the contributor as recipient', recipient: contributor) @@ -952,8 +940,22 @@ end describe '#after_create_commit' do - subject { create(:contributor) } + subject { create(:contributor, organization: organization) } - it_behaves_like 'an ActivityNotification', 'OnboardingCompleted' + before do + users = create_list(:user, 5, organization: organization) + organization.update(users: users) + end + + it 'behaves like an ActivityNotification' do + expect { subject }.to change(ActivityNotification.where(type: 'OnboardingCompleted'), :count).by(User.count) + end + + it 'for each user' do + subject + recipient_ids = ActivityNotification.where(type: 'OnboardingCompleted').pluck(:recipient_id).uniq.sort + user_ids = User.pluck(:id).sort + expect(recipient_ids).to eq(user_ids) + end end end diff --git a/spec/models/message_spec.rb b/spec/models/message_spec.rb index 7f6d1b738..274616422 100644 --- a/spec/models/message_spec.rb +++ b/spec/models/message_spec.rb @@ -91,12 +91,13 @@ describe '#after_commit(on: :commit)' do let!(:user) { create(:user) } - let(:request) { create(:request, user: user, organization: organization) } - let(:message) { create(:message, sender: user, recipient: recipient, broadcasted: true, request: request) } + let!(:request) { create(:request, user: user, organization: organization) } + let(:message) { create(:message, sender: user, recipient: recipient, broadcasted: broadcasted, request: request) } let(:organization) do create(:organization, name: '100eyes', telegram_bot_api_key: 'TELEGRAM_BOT_API_KEY', telegram_bot_username: 'USERNAME') end let(:recipient) { create(:contributor, organization: organization) } + let(:broadcasted) { true } describe 'given a recipient with telegram' do before do @@ -123,14 +124,55 @@ end end - describe 'ActivityNotification' do - subject { create(:message, request: request) } + describe '#notify_recipient' do + subject { message } - it 'Message Received is not created for outbound messages' do - expect { message }.not_to(change { ActivityNotification.where(type: 'MessageReceived').count }) + let!(:admin) { create(:user, admin: true) } + + before do + Contributor.skip_callback(:commit, :after, :notify_recipient, raise: false) + organization.update!(users: create_list(:user, 5, organization: organization)) + end + + after do + Contributor.set_callback(:commit, :after, :notify_recipient, raise: false) + end + + context 'given an outbound message' do + it 'is broadcasted, it does not create an ActivityNotification' do + expect { subject }.not_to change(ActivityNotification, :count) + end + + context 'is not broadcast' do + let(:broadcasted) { false } + + it 'does not create a MessageReceived notification' do + expect { subject }.not_to(change { ActivityNotification.where(type: MessageReceived.name).count }) + end + + it 'it creates a ChatMessageSent notification for each user and admin' do + subject + recipient_ids = ActivityNotification.where(type: ChatMessageSent.name).pluck(:recipient_id).uniq.sort + user_ids = organization.users.pluck(:id) + admin_id = admin.id + ids = (user_ids << admin_id).sort + expect(recipient_ids).to eq(ids) + end + end end - it_behaves_like 'an ActivityNotification', 'MessageReceived' + context 'given an inbound message' do + subject { create(:message, :inbound, request: request) } + + it 'it creates a MessageReceived for each user and admin' do + subject + recipient_ids = ActivityNotification.where(type: MessageReceived.name).pluck(:recipient_id).uniq.sort + user_ids = organization.users.pluck(:id) + admin_id = admin.id + ids = (user_ids << admin_id).sort + expect(recipient_ids).to eq(ids) + end + end end end diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb index b1826111f..3bba635cc 100644 --- a/spec/models/organization_spec.rb +++ b/spec/models/organization_spec.rb @@ -53,4 +53,18 @@ end end end + + describe '#contributors_tags_with_count' do + subject { organization.contributors_tags_with_count.pluck(:name, :count) } + + context 'given a contributor with a tag' do + let!(:contributor) { create(:contributor, tag_list: %w[Homeowner], organization: organization) } + it { should eq([['Homeowner', 1]]) } + + context 'and a request with the same tag' do + let!(:request) { create(:request, tag_list: %w[Homeowner], organization: organization) } + it { should eq([['Homeowner', 1]]) } + end + end + end end diff --git a/spec/requests/telegram/webhook_spec.rb b/spec/requests/telegram/webhook_spec.rb index 05b7ba815..e1ce59762 100644 --- a/spec/requests/telegram/webhook_spec.rb +++ b/spec/requests/telegram/webhook_spec.rb @@ -138,7 +138,7 @@ it { expect { subject.call }.not_to(change { Message.count }) } context 'given a recent request' do - before { create(:request, organization: organization) } + before { create(:request, organization: organization, user: create(:user, organization: organization)) } it { expect { subject.call }.to(change { Message.count }.from(0).to(1)) } it { expect { subject.call }.not_to respond_with_message } it_behaves_like 'an ActivityNotification', 'MessageReceived' diff --git a/spec/requests/threema/webhook_spec.rb b/spec/requests/threema/webhook_spec.rb index 7ddf341cd..d24d940a7 100644 --- a/spec/requests/threema/webhook_spec.rb +++ b/spec/requests/threema/webhook_spec.rb @@ -47,7 +47,7 @@ context 'With known contributor' do let!(:contributor) { create(:contributor, :skip_validations, threema_id: 'V5EA564T', organization: organization) } - let!(:request) { create(:request) } + let!(:request) { create(:request, organization: organization, user: create(:user, organization: organization)) } before do allow(threema_mock).to receive(:instance_of?).with(Threema::Receive::Text).and_return(true) diff --git a/spec/support/shared_examples/activity_notifications.rb b/spec/support/shared_examples/activity_notifications.rb index 21ebe5b82..c83e9a97e 100644 --- a/spec/support/shared_examples/activity_notifications.rb +++ b/spec/support/shared_examples/activity_notifications.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples 'an ActivityNotification' do |event_type| - let!(:users) { create_list(:user, 5) } + let!(:users) { create_list(:user, 5, organization: organization) } context 'creates activity notifications' do it " of type #{event_type}" do diff --git a/spec/support/shared_examples/contributor_resubscribes.rb b/spec/support/shared_examples/contributor_resubscribes.rb index 8735c79f7..21a50d976 100644 --- a/spec/support/shared_examples/contributor_resubscribes.rb +++ b/spec/support/shared_examples/contributor_resubscribes.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true RSpec.shared_examples 'a Contributor resubscribes' do |adapter| - let!(:request) { create(:request, organization: organization) } + let!(:request) { create(:request, organization: organization, user: non_admin_user) } let!(:admin) { create_list(:user, 2, admin: true) } - let!(:non_admin_user) { create(:user) } + let!(:non_admin_user) { create(:user, organization: organization) } let(:welcome_message) do organization.onboarding_success_text end diff --git a/spec/support/shared_examples/contributor_unsubscribes.rb b/spec/support/shared_examples/contributor_unsubscribes.rb index d1e146548..d98aea60a 100644 --- a/spec/support/shared_examples/contributor_unsubscribes.rb +++ b/spec/support/shared_examples/contributor_unsubscribes.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true RSpec.shared_examples 'a Contributor unsubscribes' do |adapter| - let!(:request) { create(:request, organization: organization) } + let!(:request) { create(:request, organization: organization, user: non_admin_user) } let!(:admin) { create_list(:user, 2, admin: true) } - let!(:non_admin_user) { create(:user) } + let!(:non_admin_user) { create(:user, organization: organization) } let(:unsubscribe_successful_message) do [I18n.t('adapter.shared.unsubscribe.successful')].join("\n\n") end diff --git a/spec/system/contributors/filter_spec.rb b/spec/system/contributors/filter_spec.rb index 1b5c79936..1fbcfaa56 100644 --- a/spec/system/contributors/filter_spec.rb +++ b/spec/system/contributors/filter_spec.rb @@ -4,9 +4,10 @@ RSpec.describe 'Filter contributors' do let(:user) { create(:user) } - let!(:active_contributor) { create(:contributor, tag_list: ['entwickler']) } - let!(:inactive_contributor) { create(:contributor, :inactive, tag_list: ['entwickler']) } - let!(:another_contributor) { create(:contributor) } + let(:organization) { create(:organization) } + let!(:active_contributor) { create(:contributor, tag_list: ['entwickler'], organization: organization) } + let!(:inactive_contributor) { create(:contributor, :inactive, tag_list: ['entwickler'], organization: organization) } + let!(:another_contributor) { create(:contributor, organization: organization) } it 'Editor lists contributors' do visit contributors_path(as: user) diff --git a/spec/system/dashboard/activity_notifications_spec.rb b/spec/system/dashboard/activity_notifications_spec.rb index d2d8acea5..90e606893 100644 --- a/spec/system/dashboard/activity_notifications_spec.rb +++ b/spec/system/dashboard/activity_notifications_spec.rb @@ -17,10 +17,11 @@ create(:user, first_name: 'Coworker', last_name: 'Extraordinaire', email: coworker_email, password: password, otp_enabled: otp_enabled, organization: organization) end - let(:request) { create(:request) } - let(:contributor_without_avatar) { create(:contributor) } + let(:request) { create(:request, user: user, organization: organization) } + let(:contributor_without_avatar) { create(:contributor, organization: organization) } let(:another_contributor) { create(:contributor) } + before { organization.update(users: [user, coworker]) } after { Timecop.return } it 'displays the activity notifications on dashboard' do @@ -32,7 +33,7 @@ expect(page).to have_text('Du hast im Moment keine neuen Benachrichtigungen.') # OnboardingCompleted - contributor = create(:contributor, :with_an_avatar) + contributor = create(:contributor, :with_an_avatar, organization: organization) Timecop.travel(1.minute.from_now) visit dashboard_path(as: user) @@ -45,7 +46,7 @@ expect(page).to have_link('Zum Profil', href: contributor_path(contributor)) # I shouldn't be grouped - contributor_two = create(:contributor, first_name: 'Timmy', last_name: 'Timmerson') + contributor_two = create(:contributor, first_name: 'Timmy', last_name: 'Timmerson', organization: organization) visit dashboard_path(as: user) expect(page).to have_css('svg.Avatar-initials') diff --git a/spec/system/profile/index_spec.rb b/spec/system/profile/index_spec.rb index bbf29fe23..42508de47 100644 --- a/spec/system/profile/index_spec.rb +++ b/spec/system/profile/index_spec.rb @@ -12,8 +12,9 @@ let(:user_to_be_deactivated) { create(:user, first_name: 'User', last_name: 'ToBeDeactivated') } let(:current_plan) { business_plans[1] } let(:contact_person) { create(:user, first_name: 'Isaac', last_name: 'Bonga') } + let!(:contributors_of_organization) { create_list(:contributor, 5, organization: organization) } let(:organization) do - create(:organization, business_plan: current_plan, contact_person: contact_person, contributors_count: 5, + create(:organization, business_plan: current_plan, contact_person: contact_person, upgrade_discount: 15).tap do |org| users = [user, contact_person, user_to_be_deactivated, create(:user)] org.users << users diff --git a/spec/system/requests/deleting_requests_spec.rb b/spec/system/requests/deleting_requests_spec.rb index 35b3d1709..c217843d5 100644 --- a/spec/system/requests/deleting_requests_spec.rb +++ b/spec/system/requests/deleting_requests_spec.rb @@ -3,14 +3,15 @@ require 'rails_helper' RSpec.describe 'Deleting requests' do - let(:user) { create(:user) } - let!(:broadcasted_request) { create(:request) } - let!(:planned_request) { create(:request, schedule_send_for: 1.hour.from_now) } - let!(:another_planned_request) { create(:request, schedule_send_for: 5.minutes.from_now) } + let(:organization) { create(:organization) } + let(:user) { create(:user, organization: organization) } + let!(:broadcasted_request) { create(:request, organization: organization, user: user) } + let!(:planned_request) { create(:request, schedule_send_for: 1.hour.from_now, organization: organization, user: user) } + let!(:another_planned_request) { create(:request, schedule_send_for: 5.minutes.from_now, organization: organization, user: user) } before do allow(Request).to receive(:broadcast!).and_call_original - create(:contributor) + create(:contributor, organization: organization) end it 'conditonally allows deleting' do @@ -42,8 +43,8 @@ expect(page).to have_content(I18n.t('components.destroy_planned_request_modal.heading', request_title: planned_request.title)) click_on 'löschen' - expect(page).to have_content(I18n.t('request.destroy.successful', request_title: planned_request.title)) expect(page).to have_current_path(requests_path(filter: :planned)) + expect(page).to have_content(I18n.t('request.destroy.successful', request_title: planned_request.title)) # Planned request, that was then sent out @@ -55,13 +56,12 @@ expect(page).to have_current_path(edit_request_path(another_planned_request)) Timecop.travel(10.minutes.from_now) another_planned_request.update(broadcasted_at: 5.minutes.ago) - click_on I18n.t('components.request_form.planned_request.destroy.button_text') expect(page).to have_content(I18n.t('components.request_form.planned_request.destroy.button_text', request_title: another_planned_request.title)) click_on 'löschen' - expect(page).to have_content(I18n.t('request.destroy.broadcasted_request_unallowed', request_title: another_planned_request.title)) expect(page).to have_current_path(requests_path) + expect(page).to have_content(I18n.t('request.destroy.broadcasted_request_unallowed', request_title: another_planned_request.title)) end end diff --git a/spec/system/requests/editing_requests_spec.rb b/spec/system/requests/editing_requests_spec.rb index cbeaff78e..09d4374c0 100644 --- a/spec/system/requests/editing_requests_spec.rb +++ b/spec/system/requests/editing_requests_spec.rb @@ -4,7 +4,7 @@ RSpec.describe 'Editing requests', js: true do let(:organization) { create(:organization) } - let(:user) { create(:user) } + let(:user) { create(:user, organization: organization) } let(:sent_request) { create(:request, organization: organization) } let(:request_scheduled_in_future) { create(:request, schedule_send_for: 2.minutes.from_now, organization: organization) } @@ -37,8 +37,8 @@ formatted = I18n.l(scheduled_datetime, format: :long) success_message = "Ihre Frage wurde erfolgreich geplant, um am #{formatted} an ein Community-Mitglied gesendet zu werden." - expect(page).to have_content(success_message) - expect(page).to have_content('Did you get my scheduled request?') expect(page).to have_current_path(requests_path(filter: :planned)) + expect(page).to have_content('Did you get my scheduled request?') + expect(page).to have_content(success_message) end end diff --git a/spec/system/requests/scheduling_requests_spec.rb b/spec/system/requests/scheduling_requests_spec.rb index 8d23802dc..3609dc561 100644 --- a/spec/system/requests/scheduling_requests_spec.rb +++ b/spec/system/requests/scheduling_requests_spec.rb @@ -25,9 +25,9 @@ formatted = I18n.l(scheduled_datetime, format: :long) success_message = "Ihre Frage wurde erfolgreich geplant, um am #{formatted} an ein Community-Mitglied gesendet zu werden." - expect(page).to have_content(success_message) - expect(page).to have_content('Did you get my scheduled request?') expect(page).to have_current_path(requests_path(filter: :planned)) + expect(page).to have_content('Did you get my scheduled request?') + expect(page).to have_content(success_message) end end end