Skip to content
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

[FEATURE] Enabled User @mentions and @mention-filters in core editor package #2544

Merged
merged 47 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
950bd7a
feat: created custom mention component
henit-chobisa Oct 26, 2023
bfc5744
feat: added mention suggestions and suggestion highlights
henit-chobisa Oct 26, 2023
718a20d
feat: created mention suggestion list for displaying mention suggestions
henit-chobisa Oct 26, 2023
f15022a
feat: created custom mention text component, for handling click event
henit-chobisa Oct 26, 2023
defab7c
feat: exposed mention component
henit-chobisa Oct 26, 2023
3e77b6e
feat: integrated and exposed `mentions` componenet with `editor-core`
henit-chobisa Oct 26, 2023
1d3618d
feat: integrated mentions extension with the core editor package
henit-chobisa Oct 26, 2023
20e5274
feat: exposed suggestion types from mentions
henit-chobisa Oct 26, 2023
dd07694
feat: added `mention-suggestion` parameters in `r-t-e` and `l-t-e`
henit-chobisa Oct 26, 2023
e587d65
feat: added `IssueMention` model in apiserver models
henit-chobisa Oct 26, 2023
14f21c7
chore: updated activities background job and added bs4 in requirements
henit-chobisa Oct 26, 2023
f25e6e1
feat: added mention removal logic in issue_activity
henit-chobisa Oct 26, 2023
797d027
chore: exposed mention types from `r-t-e` and `l-t-e`
henit-chobisa Oct 26, 2023
a771793
feat: integrated mentions in side peek view description form
henit-chobisa Oct 26, 2023
5c51af2
feat: added mentions in issue modal form
henit-chobisa Oct 26, 2023
ffdbc5a
feat: created custom react-hook for editor suggestions
henit-chobisa Oct 26, 2023
9b32a4c
feat: integrated mention suggestions block in RichTextEditor
henit-chobisa Oct 26, 2023
d73ee84
feat: added `mentions` integration in `lite-text-editor` instances
henit-chobisa Oct 26, 2023
04c1b2e
fix: tailwind loading nodemodules from packages
henit-chobisa Oct 26, 2023
f5fb9f2
feat: added styles for the mention suggestion list
henit-chobisa Oct 26, 2023
9b0f854
fix: update module import to resolve build failure
henit-chobisa Oct 26, 2023
684c860
feat: added mentions as an issue filter
henit-chobisa Oct 27, 2023
015c775
feat: added UI Changes to Implement `mention` filters
henit-chobisa Oct 27, 2023
603a21b
feat: added `mentions` as a filter option in the header
henit-chobisa Oct 27, 2023
7a7113b
feat: added mentions in the filter list options
henit-chobisa Oct 27, 2023
47ab302
feat: added mentions in default display filter options
henit-chobisa Oct 27, 2023
d57a7aa
feat: added filters in applied and issue params in store
henit-chobisa Oct 27, 2023
be7eae0
feat: modified types for adding mentions as a filter option
henit-chobisa Oct 27, 2023
00f7095
feat: modified `notification-card` to display message when it exists …
henit-chobisa Oct 27, 2023
0ea956f
Merge branch 'develop' into feat/editor-mentions
henit-chobisa Oct 27, 2023
988f4b0
feat: rewrote user mention management upon the changes made in develop
henit-chobisa Oct 30, 2023
f251c16
chore: merged debounce PR with the current PR for tracing changes
henit-chobisa Oct 30, 2023
4331570
Merge branch 'develop' into feat/editor-mentions
henit-chobisa Oct 30, 2023
6ae278e
fix: mentions_filters updated with the new setup
henit-chobisa Oct 30, 2023
52813cd
feat: updated requirements for bs4
henit-chobisa Oct 30, 2023
c5e20ce
feat: modified `mentions-filter` to remove many to many dependency
henit-chobisa Oct 31, 2023
595cb98
feat: implemented list manipulation instead of for loop
henit-chobisa Oct 31, 2023
b76bb91
feat: added readonly functionality in `read-only` editor core
henit-chobisa Oct 31, 2023
d233385
feat: added UI Changes for read-only mode
henit-chobisa Oct 31, 2023
03aa75f
Merge branch 'develop' into feat/editor-mentions
henit-chobisa Oct 31, 2023
deed7d9
feat: added mentions store in web Root Store
henit-chobisa Oct 31, 2023
e8c930f
chore: renamed `use-editor-suggestions` hook
henit-chobisa Oct 31, 2023
d05f870
feat: UI Improvements for conditional highlights w.r.t readonly in me…
henit-chobisa Oct 31, 2023
beb0590
fix: removed mentions from `filter_set` parameters
henit-chobisa Oct 31, 2023
da2893e
fix: merge conflicts resolved
sriramveeraghanta Nov 1, 2023
cbdcddb
fix: minor merge fixes
sriramveeraghanta Nov 1, 2023
df77b69
fix: package lock updates
sriramveeraghanta Nov 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apiserver/plane/api/views/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ def get_serializer_class(self):

filterset_fields = [
"state__name",
"mentions__id",
henit-chobisa marked this conversation as resolved.
Show resolved Hide resolved
"assignees__id",
"workspace__id",
]
Expand All @@ -122,6 +123,7 @@ def get_queryset(self):
.select_related("workspace")
.select_related("state")
.select_related("parent")
.prefetch_related("mentions")
.prefetch_related("assignees")
.prefetch_related("labels")
.prefetch_related(
Expand Down
287 changes: 268 additions & 19 deletions apiserver/plane/bgtasks/issue_activites_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# Third Party imports
from celery import shared_task
from sentry_sdk import capture_exception
from bs4 import BeautifulSoup

# Module imports
from plane.db.models import (
Expand All @@ -24,6 +25,7 @@
IssueSubscriber,
Notification,
IssueAssignee,
IssueMention,
IssueReaction,
CommentReaction,
IssueComment,
Expand Down Expand Up @@ -1434,8 +1436,89 @@ def delete_draft_issue_activity(
)
)

def delete_draft_issue_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities, epoch
):
issue_activities.append(
IssueActivity(
project=project,
workspace=project.workspace,
comment=f"deleted the draft issue",
field="draft",
verb="deleted",
actor=actor,
epoch=epoch,
)
)

# Get New Mentions
def get_new_mentions(requested_instance, current_instance):
# requested_data is the newer instance of the current issue
# current_instance is the older instance of the current issue, saved in the database

# extract mentions from both the instance of data
mentions_older = extract_mentions(current_instance)
mentions_newer = extract_mentions(requested_instance)

# Getting Set Difference from mentions_newer
new_mentions = [mention for mention in mentions_newer if mention not in mentions_older]

return new_mentions

# Get Removed Mention
def get_new_mentions(requested_instance, current_instance):
# requested_data is the newer instance of the current issue
# current_instance is the older instance of the current issue, saved in the database

# extract mentions from both the instance of data
mentions_older = extract_mentions(current_instance)
mentions_newer = extract_mentions(requested_instance)

# Getting Set Difference from mentions_newer
removed_mentions = [mention for mention in mentions_older if mention not in mentions_newer]

return removed_mentions

# Adds mentions as subscribers
def extract_mentions_as_subscribers(project, issue, mentions):
# mentions is an array of User IDs representing the FILTERED set of mentioned users

bulk_mention_subscribers = []

for mention_id in mentions:
# If the particular mention has not already been subscribed to the issue, he must be sent the mentioned notification
if not IssueSubscriber.objects.filter(
issue_id=issue.id,
subscriber=mention_id,
project=project.id,
).exists():
mentioned_user = User.objects.get(pk=mention_id)
bulk_mention_subscribers.append(IssueSubscriber(
workspace=project.workspace,
project=project,
issue=issue,
subscriber=mentioned_user,
))
return bulk_mention_subscribers

# Parse Issue Description & extracts mentions
def extract_mentions(issue_instance):
try:
# issue_instance has to be a dictionary passed, containing the description_html and other set of activity data.
mentions = []
# Convert string to dictionary
data = json.loads(issue_instance)
html = data.get("description_html")
soup = BeautifulSoup(html, 'html.parser')
mention_tags = soup.find_all('mention-component', attrs={'target': 'users'})

for mention_tag in mention_tags:
mentions.append(mention_tag['id'])

return list(set(mentions))
except Exception as e:
return []

# Receive message from room group
@shared_task
def issue_activity(
type,
Expand All @@ -1454,12 +1537,35 @@ def issue_activity(
issue = Issue.objects.filter(pk=issue_id).first()
workspace_id = project.workspace_id

if issue is not None:
try:
issue.updated_at = timezone.now()
issue.save(update_fields=["updated_at"])
except Exception as e:
pass
if type not in [
"cycle.activity.created",
"cycle.activity.deleted",
"module.activity.created",
"module.activity.deleted",
"issue_reaction.activity.created",
"issue_reaction.activity.deleted",
"comment_reaction.activity.created",
"comment_reaction.activity.deleted",
"issue_vote.activity.created",
"issue_vote.activity.deleted",
]:
issue = Issue.objects.filter(pk=issue_id).first()

if issue is not None:
try:
issue.updated_at = timezone.now()
issue.save(update_fields=["updated_at"])
except Exception as e:
pass

if subscriber:
# add the user to issue subscriber
try:
_ = IssueSubscriber.objects.get_or_create(
issue_id=issue_id, subscriber=actor
)
except Exception as e:
pass

ACTIVITY_MAPPER = {
"issue.activity.created": create_issue_activity,
Expand Down Expand Up @@ -1504,7 +1610,8 @@ def issue_activity(
)

# Save all the values to database
issue_activities_created = IssueActivity.objects.bulk_create(issue_activities)
issue_activities_created = IssueActivity.objects.bulk_create(
issue_activities)
# Post the updates to segway for integrations and webhooks
if len(issue_activities_created):
# Don't send activities if the actor is a bot
Expand All @@ -1524,17 +1631,159 @@ def issue_activity(
except Exception as e:
capture_exception(e)

notifications.delay(
type=type,
issue_id=issue_id,
actor_id=actor_id,
project_id=project_id,
subscriber=subscriber,
issue_activities_created=json.dumps(
IssueActivitySerializer(issue_activities_created, many=True).data,
cls=DjangoJSONEncoder,
),
)
if type not in [
"cycle.activity.created",
"cycle.activity.deleted",
"module.activity.created",
"module.activity.deleted",
"issue_reaction.activity.created",
"issue_reaction.activity.deleted",
"comment_reaction.activity.created",
"comment_reaction.activity.deleted",
"issue_vote.activity.created",
"issue_vote.activity.deleted",
]:
# Create Notifications
bulk_notifications = []

"""
Mention Tasks
1. Perform Diffing and Extract the mentions, that mention notification needs to be sent
2. From the latest set of mentions, extract the users which are not a subscribers & make them subscribers
"""

# Get new mentions from the newer instance
new_mentions = get_new_mentions(requested_instance=requested_data, current_instance=current_instance)
removed_mention = get_removed_mentions(requested_instance=requested_data, current_instance=current_instance)

# Get New Subscribers from the mentions of the newer instance
requested_mentions = extract_mentions(issue_instance=requested_data)
mention_subscribers = extract_mentions_as_subscribers(project=project, issue=issue, mentions=requested_mentions)

# Fetch Issue Subscribers, excluding actor & new_mentions ( they should be sent mention notification not update description notification )
issue_subscribers = list(
IssueSubscriber.objects.filter(
project=project, issue_id=issue_id)
.exclude(subscriber_id__in=list(new_mentions + [ actor_id ]))
.values_list("subscriber", flat=True)
)

issue_assignees = list(
IssueAssignee.objects.filter(
project=project, issue_id=issue_id)
.exclude(assignee_id=actor_id)
.values_list("assignee", flat=True)
)

issue_subscribers = issue_subscribers + issue_assignees

issue = Issue.objects.filter(pk=issue_id).first()

# Add bot filtering
if (
issue is not None
and issue.created_by_id is not None
and not issue.created_by.is_bot
and str(issue.created_by_id) != str(actor_id)
):
issue_subscribers = issue_subscribers + [issue.created_by_id]

for subscriber in list(set(issue_subscribers)):
for issue_activity in issue_activities_created:
bulk_notifications.append(
Notification(
workspace=project.workspace,
sender="in_app:issue_activities",
triggered_by_id=actor_id,
receiver_id=subscriber,
entity_identifier=issue_id,
entity_name="issue",
project=project,
title=issue_activity.comment,
data={
"issue": {
"id": str(issue_id),
"name": str(issue.name),
"identifier": str(issue.project.identifier),
"sequence_id": issue.sequence_id,
"state_name": issue.state.name,
"state_group": issue.state.group,
},
"issue_activity": {
"id": str(issue_activity.id),
"verb": str(issue_activity.verb),
"field": str(issue_activity.field),
"actor": str(issue_activity.actor_id),
"new_value": str(issue_activity.new_value),
"old_value": str(issue_activity.old_value),
"issue_comment": str(
issue_activity.issue_comment.comment_stripped
if issue_activity.issue_comment is not None
else ""
),
},
},
)
)

# Add Mentioned as Issue Subscribers
IssueSubscriber.objects.bulk_create(mention_subscribers, batch_size=100)

# Send Notifications to the Mentioned and Add Mentioned as Subscribers
for mention_id in new_mentions:
for issue_activity in issue_activities_created:
if (issue_activity.verb == "created" or issue_activity.verb == "updated"):
bulk_notifications.append(
Notification(
workspace=project.workspace,
sender="in_app:issue_activities:mention",
triggered_by_id=actor_id,
receiver_id=mention_id,
entity_identifier=issue_id,
entity_name="issue",
project=project,
message=f"You have been mentioned in the issue {issue.name}",
data={
"issue": {
"id": str(issue_id),
"name": str(issue.name),
"identifier": str(issue.project.identifier),
"sequence_id": issue.sequence_id,
"state_name": issue.state.name,
"state_group": issue.state.group,
},
"issue_activity": {
"id": str(issue_activity.id),
"verb": str(issue_activity.verb),
"field": "description",
"actor": str(issue_activity.actor_id),
"new_value": str(issue_activity.new_value),
"old_value": str(issue_activity.old_value),
},
},
)
)

# Create New Mentions Here
aggregated_issue_mentions = []

for mention_id in new_mentions:
mentioned_user = User.objects.get(pk=mention_id)
aggregated_issue_mentions.append(
IssueMention(
mention=mentioned_user,
issue=issue,
project=project,
workspace=project.workspace
)
)

IssueMention.objects.bulk_create(aggregated_issue_mentions, batch_size=100)
IssueMention.objects.filter(issue=issue.id, mention__in=removed_mention).delete()

# Bulk create notifications
Notification.objects.bulk_create(
bulk_notifications, batch_size=100)

return
except Exception as e:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import django.db.models.deletion
import plane.db.models.issue


class Migration(migrations.Migration):

dependencies = [
Expand All @@ -18,4 +17,4 @@ class Migration(migrations.Migration):
name='properties',
field=models.JSONField(default=plane.db.models.issue.get_default_properties),
),
]
]
Loading
Loading