Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
47edd17
feat: modified `issue_activity` background job for handling mentions
henit-chobisa Oct 2, 2023
fb8c339
feat: Extended Tiptap Mentions for adding type, id & label
henit-chobisa Oct 2, 2023
7f5abf7
feat: Intialized Mentions Extension
henit-chobisa Oct 2, 2023
7f8f80f
feat: added suggestion type to be used for passing suggestions
henit-chobisa Oct 2, 2023
101d8a8
feat: added suggestion controller & renderer
henit-chobisa Oct 2, 2023
7f9a38c
feat: added mentions in tiptap extensions
henit-chobisa Oct 2, 2023
d290715
feat: provided mentionSuggestion from issue creation form
henit-chobisa Oct 2, 2023
ea9f102
feat: added mention styles
henit-chobisa Oct 2, 2023
7058c11
chore: added tiptap-mention dependency in package.json
henit-chobisa Oct 2, 2023
d33045c
feat: modified notification-card to provide priviledge to preconstruc…
henit-chobisa Oct 2, 2023
58b0b17
feat: removed print log from issue_activities background job
henit-chobisa Oct 2, 2023
ae91d58
feat: added mention implementation on issue form
henit-chobisa Oct 3, 2023
8da9781
feat: added mention node custom implementation
henit-chobisa Oct 3, 2023
f5415c3
feat: added self and redirect uri in list component
henit-chobisa Oct 3, 2023
daeafee
feat: added tag based parsing in backend instead of class based
henit-chobisa Oct 3, 2023
26bc9d4
feat: removed saving self tag
henit-chobisa Oct 3, 2023
9ac4e7d
feat: added mention highlights and implementation
henit-chobisa Oct 3, 2023
29edc0c
feat: removed self tag from mentions
henit-chobisa Oct 3, 2023
2fec762
fix: created subscriber and mention notification together
henit-chobisa Oct 9, 2023
d7891ff
feat: added `issue_mention` table
henit-chobisa Oct 11, 2023
616561c
feat: commiting stashed changes
henit-chobisa Oct 18, 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
Binary file added apiserver/dump.rdb
Binary file not shown.
8 changes: 8 additions & 0 deletions apiserver/plane/api/views/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def get_serializer_class(self):
filterset_fields = [
"state__name",
"assignees__id",
"mentions__id",
"workspace__id",
]

Expand Down Expand Up @@ -167,6 +168,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 All @@ -187,6 +189,8 @@ def list(self, request, slug, project_id):
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]

order_by_param = request.GET.get("order_by", "-created_at")

# Add a mention_filter here in place

issue_queryset = (
self.get_queryset()
Expand Down Expand Up @@ -286,6 +290,8 @@ def list(self, request, slug, project_id):
return Response(issues, status=status.HTTP_200_OK)

except Exception as e:
print("===================EXCEPTION OCCURED====================")
print(e)
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
Expand Down Expand Up @@ -475,6 +481,8 @@ def get(self, request, slug):

return Response(issues, status=status.HTTP_200_OK)
except Exception as e:
print("========================EXCEPTION OCCURED==========================")
print(e)
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
Expand Down
402 changes: 278 additions & 124 deletions apiserver/plane/bgtasks/issue_activites_task.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apiserver/plane/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from celery.schedules import crontab

# Set the default Django settings module for the 'celery' program.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.local")

ri = redis_instance()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,39 +41,39 @@ class Migration(migrations.Migration):
]

operations = [
migrations.CreateModel(
name='GlobalView',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=255, verbose_name='View Name')),
('description', models.TextField(blank=True, verbose_name='View Description')),
('query', models.JSONField(verbose_name='View Query')),
('access', models.PositiveSmallIntegerField(choices=[(0, 'Private'), (1, 'Public')], default=1)),
('query_data', models.JSONField(default=dict)),
('sort_order', models.FloatField(default=65535)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='global_views', to='db.workspace')),
],
options={
'verbose_name': 'Global View',
'verbose_name_plural': 'Global Views',
'db_table': 'global_views',
'ordering': ('-created_at',),
},
),
migrations.AddField(
model_name='workspacemember',
name='issue_props',
field=models.JSONField(default=plane.db.models.workspace.get_issue_props),
),
migrations.AddField(
model_name='issueactivity',
name='epoch',
field=models.FloatField(null=True),
),
migrations.RunPython(update_issue_activity_priority),
migrations.RunPython(update_issue_activity_blocked),
# migrations.CreateModel(
# name='GlobalView',
# fields=[
# ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
# ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
# ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
# ('name', models.CharField(max_length=255, verbose_name='View Name')),
# ('description', models.TextField(blank=True, verbose_name='View Description')),
# ('query', models.JSONField(verbose_name='View Query')),
# ('access', models.PositiveSmallIntegerField(choices=[(0, 'Private'), (1, 'Public')], default=1)),
# ('query_data', models.JSONField(default=dict)),
# ('sort_order', models.FloatField(default=65535)),
# ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
# ('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
# ('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='global_views', to='db.workspace')),
# ],
# options={
# 'verbose_name': 'Global View',
# 'verbose_name_plural': 'Global Views',
# 'db_table': 'global_views',
# 'ordering': ('-created_at',),
# },
# ),
# migrations.AddField(
# model_name='workspacemember',
# name='issue_props',
# field=models.JSONField(default=plane.db.models.workspace.get_issue_props),
# ),
# migrations.AddField(
# model_name='issueactivity',
# name='epoch',
# field=models.FloatField(null=True),
# ),
# migrations.RunPython(update_issue_activity_priority),
# migrations.RunPython(update_issue_activity_blocked),
]
35 changes: 35 additions & 0 deletions apiserver/plane/db/migrations/0046_issue_mention.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 4.2.5 on 2023-10-11 05:01

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid

class Migration(migrations.Migration):

dependencies = [
('db', '0045_issueactivity_epoch_workspacemember_issue_props_and_more'),
]

operations = [
migrations.CreateModel(
name="issue_mentions",
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('mention', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issue_mention', to=settings.AUTH_USER_MODEL)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issuemention_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('issue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issue_mention', to='db.issue')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_issuemention', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issuemention_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_issuemention', to='db.workspace')),
],
options={
'verbose_name': 'IssueMention',
'verbose_name_plural': 'IssueMentions',
'db_table': 'issue_mentions',
'ordering': ('-created_at',),
},
)
]
1 change: 1 addition & 0 deletions apiserver/plane/db/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
IssueBlocker,
IssueLabel,
IssueAssignee,
IssueMention,
Label,
IssueBlocker,
IssueRelation,
Expand Down
27 changes: 26 additions & 1 deletion apiserver/plane/db/models/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ class Issue(ProjectBaseModel):
through="IssueAssignee",
through_fields=("issue", "assignee"),
)
mentions = models.ManyToManyField(
settings.AUTH_USER_MODEL,
blank=True,
related_name="mention",
through="IssueMention",
through_fields=("issue", "mention"),
)
sequence_id = models.IntegerField(default=1, verbose_name="Issue Sequence ID")
labels = models.ManyToManyField(
"db.Label", blank=True, related_name="labels", through="IssueLabel"
Expand Down Expand Up @@ -208,9 +215,27 @@ class Meta:
ordering = ("-created_at",)

def __str__(self):
return f"{self.issue.name} {self.related_issue.name}"
return f"{self.issue.name} {self.related_issue.name}"


class IssueMention(ProjectBaseModel):
issue = models.ForeignKey(
Issue, on_delete=models.CASCADE, related_name="issue_mention"
)
mention = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="issue_mention",
)
class Meta:
unique_together = ["issue", "mention"]
verbose_name = "Issue Mention"
verbose_name_plural = "Issue Mentions"
db_table = "issue_mentions"
ordering = ("-created_at",)

def __str__(self):
return f"{self.issue.name} {self.mention.email}"
class IssueAssignee(ProjectBaseModel):
issue = models.ForeignKey(
Issue, on_delete=models.CASCADE, related_name="issue_assignee"
Expand Down
12 changes: 6 additions & 6 deletions apiserver/plane/settings/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ.get("PGUSER", "plane"),
"USER": "",
"PASSWORD": "",
"NAME": os.environ.get("PGUSER", "postgres"),
"USER": "plane",
"PASSWORD": "plane",
"HOST": os.environ.get("PGHOST", "localhost"),
}
}
Expand Down Expand Up @@ -85,7 +85,7 @@

REDIS_HOST = "localhost"
REDIS_PORT = 6379
REDIS_URL = os.environ.get("REDIS_URL")
REDIS_URL = "redis://localhost:6379/"


MEDIA_URL = "/uploads/"
Expand All @@ -108,8 +108,8 @@

LOGGER_BASE_URL = os.environ.get("LOGGER_BASE_URL", False)

CELERY_RESULT_BACKEND = os.environ.get("REDIS_URL")
CELERY_BROKER_URL = os.environ.get("REDIS_URL")
CELERY_RESULT_BACKEND = "redis://localhost:6379/"
CELERY_BROKER_URL = "redis://localhost:6379/"

GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", False)

Expand Down
2 changes: 1 addition & 1 deletion apiserver/plane/settings/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def redis_instance():
or os.environ.get("DJANGO_SETTINGS_MODULE", "plane.settings.production")
== "plane.settings.local"
):
ri = redis.Redis.from_url(settings.REDIS_URL, db=0)
ri = redis.Redis.from_url("redis://localhost:6379/", db=0)
else:
url = urlparse(settings.REDIS_URL)
ri = redis.Redis(
Expand Down
10 changes: 10 additions & 0 deletions apiserver/plane/utils/issue_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ def filter_assignees(params, filter, method):
filter["assignees__in"] = params.get("assignees")
return filter

def filter_mentions(params, filter, method):
if method == "GET":
mentions = params.get("mentions").split(",")
if len(mentions) and "" not in mentions:
filter["mentions__in"] = mentions
else:
if params.get("mentions", None) and len(params.get("mentions")):
filter["mentions__in"] = params.get("mentions")
return filter

def filter_created_by(params, filter, method):
if method == "GET":
Expand Down Expand Up @@ -297,6 +306,7 @@ def issue_filters(query_params, method):
"parent": filter_parent,
"labels": filter_labels,
"assignees": filter_assignees,
"mentions": filter_mentions,
"created_by": filter_created_by,
"name": filter_name,
"created_at": filter_created_at,
Expand Down
85 changes: 85 additions & 0 deletions packages/editor/core/dist/index.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { ClassValue } from 'clsx';
import { EditorView, EditorProps } from '@tiptap/pm/view';
import { Editor } from '@tiptap/react';
import { ReactNode } from 'react';
import { BoldIcon } from 'lucide-react';
import { Editor as Editor$1, Range } from '@tiptap/core';

interface EditorClassNames {
noBorder?: boolean;
borderOnFocus?: boolean;
customClassName?: string;
}
declare const getEditorClassNames: ({ noBorder, borderOnFocus, customClassName }: EditorClassNames) => string;
declare function cn(...inputs: ClassValue[]): string;

type UploadImage = (file: File) => Promise<string>;

declare function startImageUpload(file: File, view: EditorView, pos: number, uploadFile: UploadImage, setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void): Promise<void>;

interface EditorContainerProps {
editor: Editor | null;
editorClassNames: string;
children: ReactNode;
}
declare const EditorContainer: ({ editor, editorClassNames, children }: EditorContainerProps) => JSX.Element;

interface EditorContentProps {
editor: Editor | null;
editorContentCustomClassNames: string | undefined;
children?: ReactNode;
}
declare const EditorContentWrapper: ({ editor, editorContentCustomClassNames, children }: EditorContentProps) => JSX.Element;

type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise<any>;

interface CustomEditorProps {
editable?: boolean;
uploadFile: UploadImage;
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
setShouldShowAlert?: (showAlert: boolean) => void;
value: string;
deleteFile: DeleteImage;
debouncedUpdatesEnabled?: boolean;
onChange?: (json: any, html: string) => void;
extensions?: any;
editorProps?: EditorProps;
forwardedRef?: any;
}
declare const useEditor: ({ uploadFile, editable, deleteFile, editorProps, value, extensions, onChange, setIsSubmitting, debouncedUpdatesEnabled, forwardedRef, setShouldShowAlert, }: CustomEditorProps) => Editor | null;

interface CustomReadOnlyEditorProps {
value: string;
forwardedRef?: any;
}
declare const useReadOnlyEditor: ({ value, forwardedRef }: CustomReadOnlyEditorProps) => Editor | null;

interface EditorMenuItem {
name: string;
isActive: () => boolean;
command: () => void;
icon: typeof BoldIcon;
}
declare const BoldItem: (editor: Editor) => EditorMenuItem;
declare const ItalicItem: (editor: Editor) => EditorMenuItem;
declare const UnderLineItem: (editor: Editor) => EditorMenuItem;
declare const StrikeThroughItem: (editor: Editor) => EditorMenuItem;
declare const CodeItem: (editor: Editor) => EditorMenuItem;
declare const BulletListItem: (editor: Editor) => EditorMenuItem;
declare const NumberedListItem: (editor: Editor) => EditorMenuItem;
declare const QuoteItem: (editor: Editor) => EditorMenuItem;
declare const TableItem: (editor: Editor) => EditorMenuItem;
declare const ImageItem: (editor: Editor, uploadFile: UploadImage, setIsSubmitting?: ((isSubmitting: "submitting" | "submitted" | "saved") => void) | undefined) => EditorMenuItem;

declare const toggleBold: (editor: Editor$1, range?: Range) => void;
declare const toggleItalic: (editor: Editor$1, range?: Range) => void;
declare const toggleUnderline: (editor: Editor$1, range?: Range) => void;
declare const toggleCode: (editor: Editor$1, range?: Range) => void;
declare const toggleOrderedList: (editor: Editor$1, range?: Range) => void;
declare const toggleBulletList: (editor: Editor$1, range?: Range) => void;
declare const toggleStrike: (editor: Editor$1, range?: Range) => void;
declare const toggleBlockquote: (editor: Editor$1, range?: Range) => void;
declare const insertTableCommand: (editor: Editor$1, range?: Range) => void;
declare const insertImageCommand: (editor: Editor$1, uploadFile: UploadImage, setIsSubmitting?: ((isSubmitting: "submitting" | "submitted" | "saved") => void) | undefined, range?: Range) => void;

export { BoldItem, BulletListItem, CodeItem, EditorContainer, EditorContentWrapper, EditorMenuItem, ImageItem, ItalicItem, NumberedListItem, QuoteItem, StrikeThroughItem, TableItem, UnderLineItem, cn, getEditorClassNames, insertImageCommand, insertTableCommand, startImageUpload, toggleBlockquote, toggleBold, toggleBulletList, toggleCode, toggleItalic, toggleOrderedList, toggleStrike, toggleUnderline, useEditor, useReadOnlyEditor };
Loading