Repository navigation
[SE-641] Allow to create mentions via the API #26028
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
judithroth
wants to merge
5
commits into
dev
from
feature/se-641-mention-users-in-documents-backend-part-1
+1,732
−31
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
4d74532
[SE-641] Add APIv3 endpoint for creating notifications about mentions…
judithroth 2794437
[SE-641] Return reduced representer after successfull creation of a n…
judithroth b088580
[SE-641] Send email notification for mention in document
judithroth 93f8234
[SE-641] Document new API endpoints for creating notifications
judithroth 6a8556d
[SE-641] Mark DocumentsMailer as previewed in the previews spec
ihordubas99 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
100 changes: 100 additions & 0 deletions
100
app/contracts/notifications/create_mention_from_api_contract.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| #-- copyright | ||
| # OpenProject is an open source project management software. | ||
| # Copyright (C) the OpenProject GmbH | ||
| # | ||
| # This program is free software; you can redistribute it and/or | ||
| # modify it under the terms of the GNU General Public License version 3. | ||
| # | ||
| # OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: | ||
| # Copyright (C) 2006-2013 Jean-Philippe Lang | ||
| # Copyright (C) 2010-2013 the ChiliProject Team | ||
| # | ||
| # This program is free software; you can redistribute it and/or | ||
| # modify it under the terms of the GNU General Public License | ||
| # as published by the Free Software Foundation; either version 2 | ||
| # of the License, or (at your option) any later version. | ||
| # | ||
| # This program is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| # GNU General Public License for more details. | ||
| # | ||
| # You should have received a copy of the GNU General Public License | ||
| # along with this program; if not, write to the Free Software | ||
| # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
| # | ||
| # See COPYRIGHT and LICENSE files for more details. | ||
| #++ | ||
|
|
||
| module Notifications | ||
| # Validates notifications that are created on behalf of the current user | ||
| # mentioning another user in a resource, e.g. a document | ||
| # (see Notifications::CreateFromApiService). | ||
| # | ||
| # A resource type needs to opt in to this by defining `api_mention_permission` | ||
| # on its Notifications::CreateFromModelService strategy. That permission is required | ||
| # for the mentioning user while the strategy's `permission` is required for the | ||
| # mentioned user (recipient). | ||
| class CreateMentionFromApiContract < CreateContract | ||
| validate :validate_resource_mentionable | ||
| validate :validate_recipient_mentionable | ||
|
|
||
| protected | ||
|
|
||
| # Only mentions can be created this way. This replaces the check of the | ||
| # parent contract which only requires a reason to be present. | ||
| def validate_reason_present | ||
| errors.add(:reason, :inclusion) unless model.reason_mentioned? | ||
| end | ||
|
|
||
| def validate_resource_mentionable | ||
| if strategy.nil? | ||
| errors.add(:resource, :mentions_not_supported) | ||
| elsif !resource_visible? | ||
| errors.add(:base, :error_not_found) | ||
| elsif !user.allowed_in_project?(strategy.api_mention_permission, resource_project) | ||
| errors.add(:base, :error_unauthorized) | ||
| end | ||
| end | ||
|
|
||
| def validate_recipient_mentionable | ||
| return if model.recipient.nil? || strategy.nil? || !resource_visible? | ||
|
|
||
| if !recipient_active? | ||
| errors.add(:recipient, :not_an_active_user) | ||
| elsif !recipient_allowed? | ||
| errors.add(:recipient, :not_allowed) | ||
| end | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def strategy | ||
| return @strategy if defined?(@strategy) | ||
|
|
||
| strategy = Notifications::CreateFromModelService.strategy_for(model.resource_type) | ||
|
|
||
| @strategy = strategy.respond_to?(:api_mention_permission) ? strategy : nil | ||
| end | ||
|
|
||
| def resource_visible? | ||
| return @resource_visible if defined?(@resource_visible) | ||
|
|
||
| @resource_visible = model.resource.present? && model.resource.visible?(user) | ||
| end | ||
|
|
||
| def recipient_active? | ||
| model.recipient.active? && !model.recipient.builtin? | ||
| end | ||
|
|
||
| def recipient_allowed? | ||
| model.recipient.allowed_in_project?(strategy.permission(nil, :mentioned), resource_project) | ||
| end | ||
|
|
||
| def resource_project | ||
| model.resource.project | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| #-- copyright | ||
| # OpenProject is an open source project management software. | ||
| # Copyright (C) the OpenProject GmbH | ||
| # | ||
| # This program is free software; you can redistribute it and/or | ||
| # modify it under the terms of the GNU General Public License version 3. | ||
| # | ||
| # OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: | ||
| # Copyright (C) 2006-2013 Jean-Philippe Lang | ||
| # Copyright (C) 2010-2013 the ChiliProject Team | ||
| # | ||
| # This program is free software; you can redistribute it and/or | ||
| # modify it under the terms of the GNU General Public License | ||
| # as published by the Free Software Foundation; either version 2 | ||
| # of the License, or (at your option) any later version. | ||
| # | ||
| # This program is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| # GNU General Public License for more details. | ||
| # | ||
| # You should have received a copy of the GNU General Public License | ||
| # along with this program; if not, write to the Free Software | ||
| # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
| # | ||
| # See COPYRIGHT and LICENSE files for more details. | ||
| #++ | ||
|
|
||
| module NotificationSettings::Scopes | ||
| module EnabledFor | ||
| extend ActiveSupport::Concern | ||
|
|
||
| class_methods do | ||
| # Returns the notification settings that prevail in the given project | ||
| # (see #applicable) and have the given reason (e.g. :mentioned) enabled. | ||
| # A project specific setting overrides the global one in both directions. | ||
| def enabled_for(reason, project) | ||
| where(reason => true) | ||
| .applicable(project) | ||
| end | ||
| end | ||
| end | ||
| end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| #-- copyright | ||
| # OpenProject is an open source project management software. | ||
| # Copyright (C) the OpenProject GmbH | ||
| # | ||
| # This program is free software; you can redistribute it and/or | ||
| # modify it under the terms of the GNU General Public License version 3. | ||
| # | ||
| # OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: | ||
| # Copyright (C) 2006-2013 Jean-Philippe Lang | ||
| # Copyright (C) 2010-2013 the ChiliProject Team | ||
| # | ||
| # This program is free software; you can redistribute it and/or | ||
| # modify it under the terms of the GNU General Public License | ||
| # as published by the Free Software Foundation; either version 2 | ||
| # of the License, or (at your option) any later version. | ||
| # | ||
| # This program is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| # GNU General Public License for more details. | ||
| # | ||
| # You should have received a copy of the GNU General Public License | ||
| # along with this program; if not, write to the Free Software | ||
| # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
| # | ||
| # See COPYRIGHT and LICENSE files for more details. | ||
| #++ | ||
|
|
||
| module Notifications | ||
| # Creates a notification requested via the API. Currently, only mentions are supported, | ||
| # i.e. notifications for a user (recipient) being mentioned by the current user | ||
| # in a resource, e.g. a document. Contrary to notifications created by | ||
| # Notifications::CreateFromModelService, these notifications are not bound to a journal. | ||
| # | ||
| # Mentioning is validated by Notifications::CreateMentionFromApiContract. If the mention is valid | ||
| # but the recipient is not to be notified, because the recipient mentioned themselves | ||
| # or has disabled notifications for being mentioned, a successful result without a | ||
| # notification is returned. | ||
| # | ||
| # If the resource's strategy supports mails for the reason, the mail is sent right away, | ||
| # just like Notifications::WorkflowJob does for mentions in journals. | ||
| class CreateFromApiService | ||
| attr_reader :user | ||
|
|
||
| def initialize(user:) | ||
| @user = user | ||
| end | ||
|
|
||
| # @param reason [String, Symbol] has to be "mentioned" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the parameter has to be the same constant value, do we need it? |
||
| # @param resource_type [String] class name of the resource, e.g. "Document" | ||
| # @param resource_id [Integer, String] | ||
| # @param recipient_id [Integer, String] | ||
| def call(reason:, resource_type:, resource_id:, recipient_id:) | ||
| notification = build_notification(reason:, resource_type:, resource_id:, recipient_id:) | ||
|
|
||
| contract = Notifications::CreateMentionFromApiContract.new(notification, user) | ||
|
|
||
| return ServiceResult.failure(result: notification, errors: contract.errors) unless contract.valid? | ||
| return ServiceResult.success if skip_notification?(notification) | ||
|
|
||
| Notifications::CreateService | ||
| .new(user:, model: notification) | ||
| .call | ||
| .tap { |result| send_mail(result.result) if result.success? } | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def build_notification(reason:, resource_type:, resource_id:, recipient_id:) | ||
| reason = reason.to_s | ||
| strategy = Notifications::CreateFromModelService.strategy_for(resource_type) | ||
|
|
||
| Notification.new( | ||
| reason: Notification.reasons.key?(reason) ? reason : nil, | ||
| # Only types having a strategy are kept. That way, arbitrary (non existing) class names, | ||
| # which would fail to load the resource, never end up on the notification. The contract | ||
| # reports the missing support for the resource type. | ||
| resource_type: strategy ? resource_type : nil, | ||
| resource_id:, | ||
| recipient_id:, | ||
| actor: user, | ||
| journal: nil, | ||
| read_ian: strategy&.supports_ian?(reason) ? false : nil, | ||
| mail_alert_sent: strategy&.supports_mail?(reason) ? false : nil, | ||
| mail_reminder_sent: nil | ||
| ) | ||
| end | ||
|
|
||
| def send_mail(notification) | ||
| return unless notification.mail_alert_sent == false | ||
|
|
||
| # Enqueue mail asynchronously (not waiting for delivery) | ||
| Notifications::MailService | ||
| .new(notification) | ||
| .call | ||
| end | ||
|
|
||
| def skip_notification?(notification) | ||
| notification.recipient == user || | ||
| !mention_notification_enabled?(notification) | ||
| end | ||
|
|
||
| def mention_notification_enabled?(notification) | ||
| NotificationSetting | ||
| .enabled_for(NotificationSetting::MENTIONED, notification.resource.project) | ||
| .exists?(user: notification.recipient) | ||
| end | ||
| end | ||
| end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
docs/api/apiv3/components/examples/notification_created_response.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # Example: Notification created response | ||
| --- | ||
| value: | ||
| _type: Notification | ||
| id: 42 | ||
| _links: | ||
| resource: | ||
| href: '/api/v3/documents/5' | ||
| title: Meeting minutes | ||
| recipient: | ||
| href: '/api/v3/users/13' | ||
| title: Darth Nihilus |
9 changes: 9 additions & 0 deletions
9
docs/api/apiv3/components/examples/notification_mention_create_request.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # Example: Notification mention create request | ||
| --- | ||
| value: | ||
| reason: mentioned | ||
| _links: | ||
| resource: | ||
| href: '/api/v3/documents/5' | ||
| recipient: | ||
| href: '/api/v3/users/13' |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These auto injected things kinda get me off guard every time.
My feeling is that it would be nicer to have this simple scope explicit on the class it's being used. Unless it's used in multiple places, then I guess extracting into a concern kidna makes sense.
Still no action necessary, just me preferring explicitness.