chore: format files using prettier (#7364)
* chore: format files using prettier * chore: api server files formatted
This commit is contained in:
parent
0225d806cc
commit
6ce700fd5d
149 changed files with 1518 additions and 919 deletions
|
|
@ -60,6 +60,7 @@ from plane.utils.host import base_host
|
|||
from plane.bgtasks.webhook_task import model_activity
|
||||
from plane.bgtasks.work_item_link_task import crawl_work_item_link_title
|
||||
|
||||
|
||||
class WorkspaceIssueAPIEndpoint(BaseAPIView):
|
||||
"""
|
||||
This viewset provides `retrieveByIssueId` on workspace level
|
||||
|
|
|
|||
|
|
@ -102,4 +102,4 @@ class CycleUserPropertiesSerializer(BaseSerializer):
|
|||
class Meta:
|
||||
model = CycleUserProperties
|
||||
fields = "__all__"
|
||||
read_only_fields = ["workspace", "project", "cycle" "user"]
|
||||
read_only_fields = ["workspace", "project", "cycle", "user"]
|
||||
|
|
|
|||
|
|
@ -726,7 +726,6 @@ class IssueSerializer(DynamicBaseSerializer):
|
|||
|
||||
|
||||
class IssueListDetailSerializer(serializers.Serializer):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Extract expand parameter and store it as instance variable
|
||||
self.expand = kwargs.pop("expand", []) or []
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ class ProjectMemberAdminSerializer(BaseSerializer):
|
|||
|
||||
|
||||
class ProjectMemberRoleSerializer(DynamicBaseSerializer):
|
||||
original_role = serializers.IntegerField(source='role', read_only=True)
|
||||
original_role = serializers.IntegerField(source="role", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ProjectMember
|
||||
|
|
|
|||
|
|
@ -110,7 +110,11 @@ class UserMeSettingsSerializer(BaseSerializer):
|
|||
workspace_member__member=obj.id,
|
||||
workspace_member__is_active=True,
|
||||
).first()
|
||||
logo_asset_url = workspace.logo_asset.asset_url if workspace.logo_asset is not None else ""
|
||||
logo_asset_url = (
|
||||
workspace.logo_asset.asset_url
|
||||
if workspace.logo_asset is not None
|
||||
else ""
|
||||
)
|
||||
return {
|
||||
"last_workspace_id": profile.last_workspace_id,
|
||||
"last_workspace_slug": (
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from plane.utils.issue_filters import issue_filters
|
|||
|
||||
|
||||
class ViewIssueListSerializer(serializers.Serializer):
|
||||
|
||||
def get_assignee_ids(self, instance):
|
||||
return [assignee.assignee_id for assignee in instance.issue_assignee.all()]
|
||||
|
||||
|
|
|
|||
|
|
@ -160,7 +160,8 @@ class AdvanceAnalyticsStatsEndpoint(AdvanceAnalyticsBaseView):
|
|||
)
|
||||
|
||||
return (
|
||||
base_queryset.values("project_id", "project__name").annotate(
|
||||
base_queryset.values("project_id", "project__name")
|
||||
.annotate(
|
||||
cancelled_work_items=Count("id", filter=Q(state__group="cancelled")),
|
||||
completed_work_items=Count("id", filter=Q(state__group="completed")),
|
||||
backlog_work_items=Count("id", filter=Q(state__group="backlog")),
|
||||
|
|
@ -173,8 +174,7 @@ class AdvanceAnalyticsStatsEndpoint(AdvanceAnalyticsBaseView):
|
|||
def get_work_items_stats(self) -> Dict[str, Dict[str, int]]:
|
||||
base_queryset = Issue.issue_objects.filter(**self.filters["base_filters"])
|
||||
return (
|
||||
base_queryset
|
||||
.values("project_id", "project__name")
|
||||
base_queryset.values("project_id", "project__name")
|
||||
.annotate(
|
||||
cancelled_work_items=Count("id", filter=Q(state__group="cancelled")),
|
||||
completed_work_items=Count("id", filter=Q(state__group="completed")),
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
workspace__slug=self.kwargs.get("slug"),
|
||||
receiver_id=self.request.user.id,
|
||||
)
|
||||
.select_related("workspace", "project," "triggered_by", "receiver")
|
||||
.select_related("workspace", "project", "triggered_by", "receiver")
|
||||
)
|
||||
|
||||
@allow_permission(
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ def mock_redis():
|
|||
mock_redis_client.ttl.return_value = -1
|
||||
|
||||
# Start the patch
|
||||
with patch('plane.settings.redis.redis_instance', return_value=mock_redis_client):
|
||||
with patch("plane.settings.redis.redis_instance", return_value=mock_redis_client):
|
||||
yield mock_redis_client
|
||||
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ def mock_elasticsearch():
|
|||
mock_es_client.delete.return_value = {"_id": "test_id", "result": "deleted"}
|
||||
|
||||
# Start the patch
|
||||
with patch('elasticsearch.Elasticsearch', return_value=mock_es_client):
|
||||
with patch("elasticsearch.Elasticsearch", return_value=mock_es_client):
|
||||
yield mock_es_client
|
||||
|
||||
|
||||
|
|
@ -68,39 +68,30 @@ def mock_mongodb():
|
|||
# Configure common MongoDB collection operations
|
||||
mock_mongo_collection.find_one.return_value = None
|
||||
mock_mongo_collection.find.return_value = MagicMock(
|
||||
__iter__=lambda x: iter([]),
|
||||
count=lambda: 0
|
||||
__iter__=lambda x: iter([]), count=lambda: 0
|
||||
)
|
||||
mock_mongo_collection.insert_one.return_value = MagicMock(
|
||||
inserted_id="mock_id_123",
|
||||
acknowledged=True
|
||||
inserted_id="mock_id_123", acknowledged=True
|
||||
)
|
||||
mock_mongo_collection.insert_many.return_value = MagicMock(
|
||||
inserted_ids=["mock_id_123", "mock_id_456"],
|
||||
acknowledged=True
|
||||
inserted_ids=["mock_id_123", "mock_id_456"], acknowledged=True
|
||||
)
|
||||
mock_mongo_collection.update_one.return_value = MagicMock(
|
||||
modified_count=1,
|
||||
matched_count=1,
|
||||
acknowledged=True
|
||||
modified_count=1, matched_count=1, acknowledged=True
|
||||
)
|
||||
mock_mongo_collection.update_many.return_value = MagicMock(
|
||||
modified_count=2,
|
||||
matched_count=2,
|
||||
acknowledged=True
|
||||
modified_count=2, matched_count=2, acknowledged=True
|
||||
)
|
||||
mock_mongo_collection.delete_one.return_value = MagicMock(
|
||||
deleted_count=1,
|
||||
acknowledged=True
|
||||
deleted_count=1, acknowledged=True
|
||||
)
|
||||
mock_mongo_collection.delete_many.return_value = MagicMock(
|
||||
deleted_count=2,
|
||||
acknowledged=True
|
||||
deleted_count=2, acknowledged=True
|
||||
)
|
||||
mock_mongo_collection.count_documents.return_value = 0
|
||||
|
||||
# Start the patch
|
||||
with patch('pymongo.MongoClient', return_value=mock_mongo_client):
|
||||
with patch("pymongo.MongoClient", return_value=mock_mongo_client):
|
||||
yield mock_mongo_client
|
||||
|
||||
|
||||
|
|
@ -112,6 +103,6 @@ def mock_celery():
|
|||
This fixture patches Celery's task.delay() to prevent actual task execution.
|
||||
"""
|
||||
# Start the patch
|
||||
with patch('celery.app.task.Task.delay') as mock_delay:
|
||||
with patch("celery.app.task.Task.delay") as mock_delay:
|
||||
mock_delay.return_value = MagicMock(id="mock-task-id")
|
||||
yield mock_delay
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -16,7 +16,9 @@ from plane.license.models import Instance
|
|||
@pytest.fixture
|
||||
def setup_instance(db):
|
||||
"""Create and configure an instance for authentication tests"""
|
||||
instance_id = uuid.uuid4() if not Instance.objects.exists() else Instance.objects.first().id
|
||||
instance_id = (
|
||||
uuid.uuid4() if not Instance.objects.exists() else Instance.objects.first().id
|
||||
)
|
||||
|
||||
# Create or update instance with all required fields
|
||||
instance, _ = Instance.objects.update_or_create(
|
||||
|
|
@ -28,7 +30,7 @@ def setup_instance(db):
|
|||
"domain": "http://localhost:8000",
|
||||
"last_checked_at": timezone.now(),
|
||||
"is_setup_done": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
return instance
|
||||
|
||||
|
|
@ -36,7 +38,9 @@ def setup_instance(db):
|
|||
@pytest.fixture
|
||||
def django_client():
|
||||
"""Return a Django test client with User-Agent header for handling redirects"""
|
||||
client = Client(HTTP_USER_AGENT="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1")
|
||||
client = Client(
|
||||
HTTP_USER_AGENT="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1"
|
||||
)
|
||||
return client
|
||||
|
||||
|
||||
|
|
@ -79,7 +83,9 @@ class TestMagicLinkGenerate:
|
|||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.magic_link_code_task.magic_link.delay")
|
||||
def test_magic_generate(self, mock_magic_link, api_client, setup_user, setup_instance):
|
||||
def test_magic_generate(
|
||||
self, mock_magic_link, api_client, setup_user, setup_instance
|
||||
):
|
||||
"""Test successful magic link generation"""
|
||||
url = reverse("magic-generate")
|
||||
|
||||
|
|
@ -97,7 +103,9 @@ class TestMagicLinkGenerate:
|
|||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.magic_link_code_task.magic_link.delay")
|
||||
def test_max_generate_attempt(self, mock_magic_link, api_client, setup_user, setup_instance):
|
||||
def test_max_generate_attempt(
|
||||
self, mock_magic_link, api_client, setup_user, setup_instance
|
||||
):
|
||||
"""Test exceeding maximum magic link generation attempts"""
|
||||
url = reverse("magic-generate")
|
||||
|
||||
|
|
@ -163,10 +171,9 @@ class TestSignInEndpoint:
|
|||
url, {"email": "user@plane.so", "password": "user123"}, follow=True
|
||||
)
|
||||
|
||||
|
||||
# Check for the specific authentication error in the URL
|
||||
redirect_urls = [url for url, _ in response.redirect_chain]
|
||||
redirect_contents = ' '.join(redirect_urls)
|
||||
redirect_contents = " ".join(redirect_urls)
|
||||
|
||||
# The actual error code for invalid password is AUTHENTICATION_FAILED_SIGN_IN
|
||||
assert "AUTHENTICATION_FAILED_SIGN_IN" in redirect_contents
|
||||
|
|
@ -201,14 +208,13 @@ class TestSignInEndpoint:
|
|||
response = django_client.post(
|
||||
url,
|
||||
{"email": "user@plane.so", "password": "user@123", "next_path": next_path},
|
||||
follow=False
|
||||
follow=False,
|
||||
)
|
||||
|
||||
# Check that the initial response is a redirect (302) without error code
|
||||
assert response.status_code == 302
|
||||
assert "error_code" not in response.url
|
||||
|
||||
|
||||
# In a real browser, the next_path would be used to build the absolute URL
|
||||
# Since we're just testing the authentication logic, we won't check for the exact URL structure
|
||||
# Instead, just verify that we're authenticated
|
||||
|
|
@ -237,16 +243,16 @@ class TestMagicSignIn:
|
|||
assert "MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED" in response.redirect_chain[-1][0]
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_expired_invalid_magic_link(self, django_client, setup_user, setup_instance):
|
||||
def test_expired_invalid_magic_link(
|
||||
self, django_client, setup_user, setup_instance
|
||||
):
|
||||
"""Test magic link sign-in with expired/invalid link"""
|
||||
ri = redis_instance()
|
||||
ri.delete("magic_user@plane.so")
|
||||
|
||||
url = reverse("magic-sign-in")
|
||||
response = django_client.post(
|
||||
url,
|
||||
{"email": "user@plane.so", "code": "xxxx-xxxxx-xxxx"},
|
||||
follow=False
|
||||
url, {"email": "user@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=False
|
||||
)
|
||||
|
||||
# Check that we get a redirect
|
||||
|
|
@ -254,7 +260,10 @@ class TestMagicSignIn:
|
|||
|
||||
# The actual error code is EXPIRED_MAGIC_CODE_SIGN_IN (when key doesn't exist)
|
||||
# or INVALID_MAGIC_CODE_SIGN_IN (when key exists but code doesn't match)
|
||||
assert "EXPIRED_MAGIC_CODE_SIGN_IN" in response.url or "INVALID_MAGIC_CODE_SIGN_IN" in response.url
|
||||
assert (
|
||||
"EXPIRED_MAGIC_CODE_SIGN_IN" in response.url
|
||||
or "INVALID_MAGIC_CODE_SIGN_IN" in response.url
|
||||
)
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_user_does_not_exist(self, django_client, setup_instance):
|
||||
|
|
@ -263,7 +272,7 @@ class TestMagicSignIn:
|
|||
response = django_client.post(
|
||||
url,
|
||||
{"email": "nonexistent@plane.so", "code": "xxxx-xxxxx-xxxx"},
|
||||
follow=True
|
||||
follow=True,
|
||||
)
|
||||
|
||||
# Check redirect contains error code
|
||||
|
|
@ -271,7 +280,9 @@ class TestMagicSignIn:
|
|||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.magic_link_code_task.magic_link.delay")
|
||||
def test_magic_code_sign_in(self, mock_magic_link, django_client, api_client, setup_user, setup_instance):
|
||||
def test_magic_code_sign_in(
|
||||
self, mock_magic_link, django_client, api_client, setup_user, setup_instance
|
||||
):
|
||||
"""Test successful magic link sign-in process"""
|
||||
# First generate a magic link token
|
||||
gen_url = reverse("magic-generate")
|
||||
|
|
@ -288,9 +299,7 @@ class TestMagicSignIn:
|
|||
# Use Django client to test the redirect flow without following redirects
|
||||
url = reverse("magic-sign-in")
|
||||
response = django_client.post(
|
||||
url,
|
||||
{"email": "user@plane.so", "code": token},
|
||||
follow=False
|
||||
url, {"email": "user@plane.so", "code": token}, follow=False
|
||||
)
|
||||
|
||||
# Check that the initial response is a redirect without error code
|
||||
|
|
@ -302,7 +311,9 @@ class TestMagicSignIn:
|
|||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.magic_link_code_task.magic_link.delay")
|
||||
def test_magic_sign_in_with_next_path(self, mock_magic_link, django_client, api_client, setup_user, setup_instance):
|
||||
def test_magic_sign_in_with_next_path(
|
||||
self, mock_magic_link, django_client, api_client, setup_user, setup_instance
|
||||
):
|
||||
"""Test magic sign-in with next_path parameter"""
|
||||
# First generate a magic link token
|
||||
gen_url = reverse("magic-generate")
|
||||
|
|
@ -322,7 +333,7 @@ class TestMagicSignIn:
|
|||
response = django_client.post(
|
||||
url,
|
||||
{"email": "user@plane.so", "code": token, "next_path": next_path},
|
||||
follow=False
|
||||
follow=False,
|
||||
)
|
||||
|
||||
# Check that the initial response is a redirect without error code
|
||||
|
|
@ -357,9 +368,7 @@ class TestMagicSignUp:
|
|||
|
||||
url = reverse("magic-sign-up")
|
||||
response = django_client.post(
|
||||
url,
|
||||
{"email": "existing@plane.so", "code": "xxxx-xxxxx-xxxx"},
|
||||
follow=True
|
||||
url, {"email": "existing@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=True
|
||||
)
|
||||
|
||||
# Check redirect contains error code
|
||||
|
|
@ -370,9 +379,7 @@ class TestMagicSignUp:
|
|||
"""Test magic link sign-up with expired/invalid link"""
|
||||
url = reverse("magic-sign-up")
|
||||
response = django_client.post(
|
||||
url,
|
||||
{"email": "new@plane.so", "code": "xxxx-xxxxx-xxxx"},
|
||||
follow=False
|
||||
url, {"email": "new@plane.so", "code": "xxxx-xxxxx-xxxx"}, follow=False
|
||||
)
|
||||
|
||||
# Check that we get a redirect
|
||||
|
|
@ -380,11 +387,16 @@ class TestMagicSignUp:
|
|||
|
||||
# The actual error code is EXPIRED_MAGIC_CODE_SIGN_UP (when key doesn't exist)
|
||||
# or INVALID_MAGIC_CODE_SIGN_UP (when key exists but code doesn't match)
|
||||
assert "EXPIRED_MAGIC_CODE_SIGN_UP" in response.url or "INVALID_MAGIC_CODE_SIGN_UP" in response.url
|
||||
assert (
|
||||
"EXPIRED_MAGIC_CODE_SIGN_UP" in response.url
|
||||
or "INVALID_MAGIC_CODE_SIGN_UP" in response.url
|
||||
)
|
||||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.magic_link_code_task.magic_link.delay")
|
||||
def test_magic_code_sign_up(self, mock_magic_link, django_client, api_client, setup_instance):
|
||||
def test_magic_code_sign_up(
|
||||
self, mock_magic_link, django_client, api_client, setup_instance
|
||||
):
|
||||
"""Test successful magic link sign-up process"""
|
||||
email = "newuser@plane.so"
|
||||
|
||||
|
|
@ -403,9 +415,7 @@ class TestMagicSignUp:
|
|||
# Use Django client to test the redirect flow without following redirects
|
||||
url = reverse("magic-sign-up")
|
||||
response = django_client.post(
|
||||
url,
|
||||
{"email": email, "code": token},
|
||||
follow=False
|
||||
url, {"email": email, "code": token}, follow=False
|
||||
)
|
||||
|
||||
# Check that the initial response is a redirect without error code
|
||||
|
|
@ -420,7 +430,9 @@ class TestMagicSignUp:
|
|||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.magic_link_code_task.magic_link.delay")
|
||||
def test_magic_sign_up_with_next_path(self, mock_magic_link, django_client, api_client, setup_instance):
|
||||
def test_magic_sign_up_with_next_path(
|
||||
self, mock_magic_link, django_client, api_client, setup_instance
|
||||
):
|
||||
"""Test magic sign-up with next_path parameter"""
|
||||
email = "newuser2@plane.so"
|
||||
|
||||
|
|
@ -440,9 +452,7 @@ class TestMagicSignUp:
|
|||
url = reverse("magic-sign-up")
|
||||
next_path = "onboarding"
|
||||
response = django_client.post(
|
||||
url,
|
||||
{"email": email, "code": token, "next_path": next_path},
|
||||
follow=False
|
||||
url, {"email": email, "code": token, "next_path": next_path}, follow=False
|
||||
)
|
||||
|
||||
# Check that the initial response is a redirect without error code
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ class TestWorkspaceAPI:
|
|||
|
||||
@pytest.mark.django_db
|
||||
@patch("plane.bgtasks.workspace_seed_task.workspace_seed.delay")
|
||||
def test_create_workspace_valid_data(self, mock_workspace_seed, session_client, create_user):
|
||||
def test_create_workspace_valid_data(
|
||||
self, mock_workspace_seed, session_client, create_user
|
||||
):
|
||||
"""Test creating a workspace with valid data"""
|
||||
url = reverse("workspace")
|
||||
user = create_user # Use the create_user fixture directly as it returns a user object
|
||||
|
|
@ -30,7 +32,7 @@ class TestWorkspaceAPI:
|
|||
workspace_data = {
|
||||
"name": "Plane",
|
||||
"slug": "pla-ne-test",
|
||||
"company_name": "Plane Inc."
|
||||
"company_name": "Plane Inc.",
|
||||
}
|
||||
|
||||
# Make the request
|
||||
|
|
@ -57,15 +59,13 @@ class TestWorkspaceAPI:
|
|||
mock_workspace_seed.assert_called_once_with(response.data["id"])
|
||||
|
||||
@pytest.mark.django_db
|
||||
@patch('plane.bgtasks.workspace_seed_task.workspace_seed.delay')
|
||||
@patch("plane.bgtasks.workspace_seed_task.workspace_seed.delay")
|
||||
def test_create_duplicate_workspace(self, mock_workspace_seed, session_client):
|
||||
"""Test creating a duplicate workspace"""
|
||||
url = reverse("workspace")
|
||||
|
||||
# Create first workspace
|
||||
session_client.post(
|
||||
url, {"name": "Plane", "slug": "pla-ne"}, format="json"
|
||||
)
|
||||
session_client.post(url, {"name": "Plane", "slug": "pla-ne"}, format="json")
|
||||
|
||||
# Try to create a workspace with the same slug
|
||||
response = session_client.post(
|
||||
|
|
|
|||
|
|
@ -2,26 +2,21 @@ import factory
|
|||
from uuid import uuid4
|
||||
from django.utils import timezone
|
||||
|
||||
from plane.db.models import (
|
||||
User,
|
||||
Workspace,
|
||||
WorkspaceMember,
|
||||
Project,
|
||||
ProjectMember
|
||||
)
|
||||
from plane.db.models import User, Workspace, WorkspaceMember, Project, ProjectMember
|
||||
|
||||
|
||||
class UserFactory(factory.django.DjangoModelFactory):
|
||||
"""Factory for creating User instances"""
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
django_get_or_create = ('email',)
|
||||
django_get_or_create = ("email",)
|
||||
|
||||
id = factory.LazyFunction(uuid4)
|
||||
email = factory.Sequence(lambda n: f'user{n}@plane.so')
|
||||
password = factory.PostGenerationMethodCall('set_password', 'password')
|
||||
first_name = factory.Sequence(lambda n: f'First{n}')
|
||||
last_name = factory.Sequence(lambda n: f'Last{n}')
|
||||
email = factory.Sequence(lambda n: f"user{n}@plane.so")
|
||||
password = factory.PostGenerationMethodCall("set_password", "password")
|
||||
first_name = factory.Sequence(lambda n: f"First{n}")
|
||||
last_name = factory.Sequence(lambda n: f"Last{n}")
|
||||
is_active = True
|
||||
is_superuser = False
|
||||
is_staff = False
|
||||
|
|
@ -29,13 +24,14 @@ class UserFactory(factory.django.DjangoModelFactory):
|
|||
|
||||
class WorkspaceFactory(factory.django.DjangoModelFactory):
|
||||
"""Factory for creating Workspace instances"""
|
||||
|
||||
class Meta:
|
||||
model = Workspace
|
||||
django_get_or_create = ('slug',)
|
||||
django_get_or_create = ("slug",)
|
||||
|
||||
id = factory.LazyFunction(uuid4)
|
||||
name = factory.Sequence(lambda n: f'Workspace {n}')
|
||||
slug = factory.Sequence(lambda n: f'workspace-{n}')
|
||||
name = factory.Sequence(lambda n: f"Workspace {n}")
|
||||
slug = factory.Sequence(lambda n: f"workspace-{n}")
|
||||
owner = factory.SubFactory(UserFactory)
|
||||
created_at = factory.LazyFunction(timezone.now)
|
||||
updated_at = factory.LazyFunction(timezone.now)
|
||||
|
|
@ -43,6 +39,7 @@ class WorkspaceFactory(factory.django.DjangoModelFactory):
|
|||
|
||||
class WorkspaceMemberFactory(factory.django.DjangoModelFactory):
|
||||
"""Factory for creating WorkspaceMember instances"""
|
||||
|
||||
class Meta:
|
||||
model = WorkspaceMember
|
||||
|
||||
|
|
@ -56,21 +53,23 @@ class WorkspaceMemberFactory(factory.django.DjangoModelFactory):
|
|||
|
||||
class ProjectFactory(factory.django.DjangoModelFactory):
|
||||
"""Factory for creating Project instances"""
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
django_get_or_create = ('name', 'workspace')
|
||||
django_get_or_create = ("name", "workspace")
|
||||
|
||||
id = factory.LazyFunction(uuid4)
|
||||
name = factory.Sequence(lambda n: f'Project {n}')
|
||||
name = factory.Sequence(lambda n: f"Project {n}")
|
||||
workspace = factory.SubFactory(WorkspaceFactory)
|
||||
created_by = factory.SelfAttribute('workspace.owner')
|
||||
updated_by = factory.SelfAttribute('workspace.owner')
|
||||
created_by = factory.SelfAttribute("workspace.owner")
|
||||
updated_by = factory.SelfAttribute("workspace.owner")
|
||||
created_at = factory.LazyFunction(timezone.now)
|
||||
updated_at = factory.LazyFunction(timezone.now)
|
||||
|
||||
|
||||
class ProjectMemberFactory(factory.django.DjangoModelFactory):
|
||||
"""Factory for creating ProjectMember instances"""
|
||||
|
||||
class Meta:
|
||||
model = ProjectMember
|
||||
|
||||
|
|
|
|||
|
|
@ -16,72 +16,79 @@ class TestAuthSmoke:
|
|||
|
||||
# 1. Test bad login - test with wrong password
|
||||
response = requests.post(
|
||||
url,
|
||||
data={
|
||||
"email": user_data["email"],
|
||||
"password": "wrong-password"
|
||||
}
|
||||
url, data={"email": user_data["email"], "password": "wrong-password"}
|
||||
)
|
||||
|
||||
# For bad credentials, any of these status codes would be valid
|
||||
# The test shouldn't be brittle to minor implementation changes
|
||||
assert response.status_code != 500, "Authentication should not cause server errors"
|
||||
assert response.status_code != 500, (
|
||||
"Authentication should not cause server errors"
|
||||
)
|
||||
assert response.status_code != 404, "Authentication endpoint should exist"
|
||||
|
||||
if response.status_code == 200:
|
||||
# If API returns 200 for failures, check the response body for error indication
|
||||
if hasattr(response, 'json'):
|
||||
if hasattr(response, "json"):
|
||||
try:
|
||||
data = response.json()
|
||||
# JSON response might indicate error in its structure
|
||||
assert "error" in data or "error_code" in data or "detail" in data or response.url.endswith("sign-in"), \
|
||||
"Error response should contain error details"
|
||||
assert (
|
||||
"error" in data
|
||||
or "error_code" in data
|
||||
or "detail" in data
|
||||
or response.url.endswith("sign-in")
|
||||
), "Error response should contain error details"
|
||||
except ValueError:
|
||||
# It's ok if response isn't JSON format
|
||||
pass
|
||||
elif response.status_code in [302, 303]:
|
||||
# If it's a redirect, it should redirect to a login page or error page
|
||||
redirect_url = response.headers.get('Location', '')
|
||||
assert "error" in redirect_url or "sign-in" in redirect_url, \
|
||||
redirect_url = response.headers.get("Location", "")
|
||||
assert "error" in redirect_url or "sign-in" in redirect_url, (
|
||||
"Failed login should redirect to login page or error page"
|
||||
)
|
||||
|
||||
# 2. Test good login with correct credentials
|
||||
response = requests.post(
|
||||
url,
|
||||
data={
|
||||
"email": user_data["email"],
|
||||
"password": user_data["password"]
|
||||
},
|
||||
allow_redirects=False # Don't follow redirects
|
||||
data={"email": user_data["email"], "password": user_data["password"]},
|
||||
allow_redirects=False, # Don't follow redirects
|
||||
)
|
||||
|
||||
# Successful auth should not be a client error or server error
|
||||
assert response.status_code not in range(400, 600), \
|
||||
assert response.status_code not in range(400, 600), (
|
||||
f"Authentication with valid credentials failed with status {response.status_code}"
|
||||
)
|
||||
|
||||
# Specific validation based on response type
|
||||
if response.status_code in [302, 303]:
|
||||
# Redirect-based auth: check that redirect URL doesn't contain error
|
||||
redirect_url = response.headers.get('Location', '')
|
||||
assert "error" not in redirect_url and "error_code" not in redirect_url, \
|
||||
redirect_url = response.headers.get("Location", "")
|
||||
assert "error" not in redirect_url and "error_code" not in redirect_url, (
|
||||
"Successful login redirect should not contain error parameters"
|
||||
)
|
||||
|
||||
elif response.status_code == 200:
|
||||
# API token-based auth: check for tokens or user session
|
||||
if hasattr(response, 'json'):
|
||||
if hasattr(response, "json"):
|
||||
try:
|
||||
data = response.json()
|
||||
# If it's a token response
|
||||
if "access_token" in data:
|
||||
assert "refresh_token" in data, "JWT auth should return both access and refresh tokens"
|
||||
assert "refresh_token" in data, (
|
||||
"JWT auth should return both access and refresh tokens"
|
||||
)
|
||||
# If it's a user session response
|
||||
elif "user" in data:
|
||||
assert "is_authenticated" in data and data["is_authenticated"], \
|
||||
"User session response should indicate authentication"
|
||||
assert (
|
||||
"is_authenticated" in data and data["is_authenticated"]
|
||||
), "User session response should indicate authentication"
|
||||
# Otherwise it should at least indicate success
|
||||
else:
|
||||
assert not any(error_key in data for error_key in ["error", "error_code", "detail"]), \
|
||||
"Success response should not contain error keys"
|
||||
assert not any(
|
||||
error_key in data
|
||||
for error_key in ["error", "error_code", "detail"]
|
||||
), "Success response should not contain error keys"
|
||||
except ValueError:
|
||||
# Non-JSON is acceptable if it's a redirect or HTML response
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -13,10 +13,7 @@ class TestWorkspaceModel:
|
|||
"""Test creating a workspace"""
|
||||
# Create a workspace
|
||||
workspace = Workspace.objects.create(
|
||||
name="Test Workspace",
|
||||
slug="test-workspace",
|
||||
id=uuid4(),
|
||||
owner=create_user
|
||||
name="Test Workspace", slug="test-workspace", id=uuid4(), owner=create_user
|
||||
)
|
||||
|
||||
# Verify it was created
|
||||
|
|
@ -30,17 +27,14 @@ class TestWorkspaceModel:
|
|||
"""Test creating a workspace member"""
|
||||
# Create a workspace
|
||||
workspace = Workspace.objects.create(
|
||||
name="Test Workspace",
|
||||
slug="test-workspace",
|
||||
id=uuid4(),
|
||||
owner=create_user
|
||||
name="Test Workspace", slug="test-workspace", id=uuid4(), owner=create_user
|
||||
)
|
||||
|
||||
# Create a workspace member
|
||||
workspace_member = WorkspaceMember.objects.create(
|
||||
workspace=workspace,
|
||||
member=create_user,
|
||||
role=20 # Admin role
|
||||
role=20, # Admin role
|
||||
)
|
||||
|
||||
# Verify it was created
|
||||
|
|
|
|||
|
|
@ -13,18 +13,13 @@ class TestWorkspaceLiteSerializer:
|
|||
"""Test that the serializer includes the correct fields"""
|
||||
# Create a user to be the owner
|
||||
owner = User.objects.create(
|
||||
email="test@example.com",
|
||||
first_name="Test",
|
||||
last_name="User"
|
||||
email="test@example.com", first_name="Test", last_name="User"
|
||||
)
|
||||
|
||||
# Create a workspace with explicit ID to test serialization
|
||||
workspace_id = uuid4()
|
||||
workspace = Workspace.objects.create(
|
||||
name="Test Workspace",
|
||||
slug="test-workspace",
|
||||
id=workspace_id,
|
||||
owner=owner
|
||||
name="Test Workspace", slug="test-workspace", id=workspace_id, owner=owner
|
||||
)
|
||||
|
||||
# Serialize the workspace
|
||||
|
|
@ -43,23 +38,17 @@ class TestWorkspaceLiteSerializer:
|
|||
"""Test that the serializer fields are read-only"""
|
||||
# Create a user to be the owner
|
||||
owner = User.objects.create(
|
||||
email="test2@example.com",
|
||||
first_name="Test",
|
||||
last_name="User"
|
||||
email="test2@example.com", first_name="Test", last_name="User"
|
||||
)
|
||||
|
||||
# Create a workspace
|
||||
workspace = Workspace.objects.create(
|
||||
name="Test Workspace",
|
||||
slug="test-workspace",
|
||||
id=uuid4(),
|
||||
owner=owner
|
||||
name="Test Workspace", slug="test-workspace", id=uuid4(), owner=owner
|
||||
)
|
||||
|
||||
# Try to update via serializer
|
||||
serializer = WorkspaceLiteSerializer(
|
||||
workspace,
|
||||
data={"name": "Updated Name", "slug": "updated-slug"}
|
||||
workspace, data={"name": "Updated Name", "slug": "updated-slug"}
|
||||
)
|
||||
|
||||
# Serializer should be valid (since read-only fields are ignored)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ class TestUUIDUtils:
|
|||
assert is_valid_uuid("not-a-uuid") is False
|
||||
assert is_valid_uuid("123456789") is False
|
||||
assert is_valid_uuid("") is False
|
||||
assert is_valid_uuid("00000000-0000-0000-0000-000000000000") is False # This is a valid UUID but version 1
|
||||
assert (
|
||||
is_valid_uuid("00000000-0000-0000-0000-000000000000") is False
|
||||
) # This is a valid UUID but version 1
|
||||
|
||||
def test_convert_uuid_to_integer(self):
|
||||
"""Test convert_uuid_to_integer function"""
|
||||
|
|
@ -46,4 +48,6 @@ class TestUUIDUtils:
|
|||
test_uuid = uuid.UUID(test_uuid_str)
|
||||
|
||||
# Should get the same result whether passing UUID or string
|
||||
assert convert_uuid_to_integer(test_uuid) == convert_uuid_to_integer(test_uuid_str)
|
||||
assert convert_uuid_to_integer(test_uuid) == convert_uuid_to_integer(
|
||||
test_uuid_str
|
||||
)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class Cursor:
|
|||
|
||||
# Return the representation of the cursor
|
||||
def __repr__(self):
|
||||
return f"{type(self).__name__,}: value={self.value} offset={self.offset}, is_prev={int(self.is_prev)}"
|
||||
return f"{(type(self).__name__,)}: value={self.value} offset={self.offset}, is_prev={int(self.is_prev)}" # noqa: E501
|
||||
|
||||
# Return if the cursor is true
|
||||
def __bool__(self):
|
||||
|
|
|
|||
|
|
@ -6,36 +6,20 @@ import sys
|
|||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Run Plane tests")
|
||||
parser.add_argument("-u", "--unit", action="store_true", help="Run unit tests only")
|
||||
parser.add_argument(
|
||||
"-u", "--unit",
|
||||
action="store_true",
|
||||
help="Run unit tests only"
|
||||
"-c", "--contract", action="store_true", help="Run contract tests only"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--contract",
|
||||
action="store_true",
|
||||
help="Run contract tests only"
|
||||
"-s", "--smoke", action="store_true", help="Run smoke tests only"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s", "--smoke",
|
||||
action="store_true",
|
||||
help="Run smoke tests only"
|
||||
"-o", "--coverage", action="store_true", help="Generate coverage report"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o", "--coverage",
|
||||
action="store_true",
|
||||
help="Generate coverage report"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p", "--parallel",
|
||||
action="store_true",
|
||||
help="Run tests in parallel"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose",
|
||||
action="store_true",
|
||||
help="Verbose output"
|
||||
"-p", "--parallel", action="store_true", help="Run tests in parallel"
|
||||
)
|
||||
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Build command
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ type TArgs = {
|
|||
documentType: TDocumentTypes | undefined;
|
||||
pageId: string;
|
||||
params: URLSearchParams;
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchDocument = async (args: TArgs): Promise<Uint8Array | null> => {
|
||||
const { documentType } = args;
|
||||
throw Error(`Fetch failed: Invalid document type ${documentType} provided.`);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ type TArgs = {
|
|||
pageId: string;
|
||||
params: URLSearchParams;
|
||||
updatedDescription: Uint8Array;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateDocument = async (args: TArgs): Promise<void> => {
|
||||
const { documentType } = args;
|
||||
throw Error(`Update failed: Invalid document type ${documentType} provided.`);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,18 +9,12 @@ import { Redis as HocusPocusRedis } from "@hocuspocus/extension-redis";
|
|||
import { manualLogger } from "@/core/helpers/logger.js";
|
||||
import { getRedisUrl } from "@/core/lib/utils/redis-url.js";
|
||||
// core libraries
|
||||
import {
|
||||
fetchPageDescriptionBinary,
|
||||
updatePageDescription,
|
||||
} from "@/core/lib/page.js";
|
||||
import { fetchPageDescriptionBinary, updatePageDescription } from "@/core/lib/page.js";
|
||||
// plane live libraries
|
||||
import { fetchDocument } from "@/plane-live/lib/fetch-document.js";
|
||||
import { updateDocument } from "@/plane-live/lib/update-document.js";
|
||||
// types
|
||||
import {
|
||||
type HocusPocusServerContext,
|
||||
type TDocumentTypes,
|
||||
} from "@/core/types/common.js";
|
||||
import { type HocusPocusServerContext, type TDocumentTypes } from "@/core/types/common.js";
|
||||
|
||||
export const getExtensions: () => Promise<Extension[]> = async () => {
|
||||
const extensions: Extension[] = [
|
||||
|
|
@ -35,20 +29,14 @@ export const getExtensions: () => Promise<Extension[]> = async () => {
|
|||
const cookie = (context as HocusPocusServerContext).cookie;
|
||||
// query params
|
||||
const params = requestParameters;
|
||||
const documentType = params.get("documentType")?.toString() as
|
||||
| TDocumentTypes
|
||||
| undefined;
|
||||
const documentType = params.get("documentType")?.toString() as TDocumentTypes | undefined;
|
||||
// TODO: Fix this lint error.
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async (resolve) => {
|
||||
try {
|
||||
let fetchedData = null;
|
||||
if (documentType === "project_page") {
|
||||
fetchedData = await fetchPageDescriptionBinary(
|
||||
params,
|
||||
pageId,
|
||||
cookie,
|
||||
);
|
||||
fetchedData = await fetchPageDescriptionBinary(params, pageId, cookie);
|
||||
} else {
|
||||
fetchedData = await fetchDocument({
|
||||
cookie,
|
||||
|
|
@ -63,18 +51,11 @@ export const getExtensions: () => Promise<Extension[]> = async () => {
|
|||
}
|
||||
});
|
||||
},
|
||||
store: async ({
|
||||
context,
|
||||
state,
|
||||
documentName: pageId,
|
||||
requestParameters,
|
||||
}) => {
|
||||
store: async ({ context, state, documentName: pageId, requestParameters }) => {
|
||||
const cookie = (context as HocusPocusServerContext).cookie;
|
||||
// query params
|
||||
const params = requestParameters;
|
||||
const documentType = params.get("documentType")?.toString() as
|
||||
| TDocumentTypes
|
||||
| undefined;
|
||||
const documentType = params.get("documentType")?.toString() as TDocumentTypes | undefined;
|
||||
|
||||
// TODO: Fix this lint error.
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
|
|
@ -107,16 +88,12 @@ export const getExtensions: () => Promise<Extension[]> = async () => {
|
|||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
redisClient.on("error", (error: any) => {
|
||||
if (
|
||||
error?.code === "ENOTFOUND" ||
|
||||
error.message.includes("WRONGPASS") ||
|
||||
error.message.includes("NOAUTH")
|
||||
) {
|
||||
if (error?.code === "ENOTFOUND" || error.message.includes("WRONGPASS") || error.message.includes("NOAUTH")) {
|
||||
redisClient.disconnect();
|
||||
}
|
||||
manualLogger.warn(
|
||||
`Redis Client wasn't able to connect, continuing without Redis (you won't be able to sync data between multiple plane live servers)`,
|
||||
error,
|
||||
error
|
||||
);
|
||||
reject(error);
|
||||
});
|
||||
|
|
@ -130,12 +107,12 @@ export const getExtensions: () => Promise<Extension[]> = async () => {
|
|||
} catch (error) {
|
||||
manualLogger.warn(
|
||||
`Redis Client wasn't able to connect, continuing without Redis (you won't be able to sync data between multiple plane live servers)`,
|
||||
error,
|
||||
error
|
||||
);
|
||||
}
|
||||
} else {
|
||||
manualLogger.warn(
|
||||
"Redis URL is not set, continuing without Redis (you won't be able to sync data between multiple plane live servers)",
|
||||
"Redis URL is not set, continuing without Redis (you won't be able to sync data between multiple plane live servers)"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,7 @@ export const errorHandler: ErrorRequestHandler = (err, _req, res) => {
|
|||
// Send the response
|
||||
res.json({
|
||||
error: {
|
||||
message:
|
||||
process.env.NODE_ENV === "production"
|
||||
? "An unexpected error occurred"
|
||||
: err.message,
|
||||
message: process.env.NODE_ENV === "production" ? "An unexpected error occurred" : err.message,
|
||||
...(process.env.NODE_ENV !== "production" && { stack: err.stack }),
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
import { getSchema } from "@tiptap/core";
|
||||
import { generateHTML, generateJSON } from "@tiptap/html";
|
||||
import { prosemirrorJSONToYDoc, yXmlFragmentToProseMirrorRootNode } from "y-prosemirror";
|
||||
import * as Y from "yjs"
|
||||
import * as Y from "yjs";
|
||||
// plane editor
|
||||
import { CoreEditorExtensionsWithoutProps, DocumentEditorExtensionsWithoutProps } from "@plane/editor/lib";
|
||||
|
||||
const DOCUMENT_EDITOR_EXTENSIONS = [
|
||||
...CoreEditorExtensionsWithoutProps,
|
||||
...DocumentEditorExtensionsWithoutProps,
|
||||
];
|
||||
const DOCUMENT_EDITOR_EXTENSIONS = [...CoreEditorExtensionsWithoutProps, ...DocumentEditorExtensionsWithoutProps];
|
||||
const documentEditorSchema = getSchema(DOCUMENT_EDITOR_EXTENSIONS);
|
||||
|
||||
export const getAllDocumentFormatsFromBinaryData = (description: Uint8Array): {
|
||||
export const getAllDocumentFormatsFromBinaryData = (
|
||||
description: Uint8Array
|
||||
): {
|
||||
contentBinaryEncoded: string;
|
||||
contentJSON: object;
|
||||
contentHTML: string;
|
||||
|
|
@ -22,10 +21,7 @@ export const getAllDocumentFormatsFromBinaryData = (description: Uint8Array): {
|
|||
Y.applyUpdate(yDoc, description);
|
||||
// convert to JSON
|
||||
const type = yDoc.getXmlFragment("default");
|
||||
const contentJSON = yXmlFragmentToProseMirrorRootNode(
|
||||
type,
|
||||
documentEditorSchema
|
||||
).toJSON();
|
||||
const contentJSON = yXmlFragmentToProseMirrorRootNode(type, documentEditorSchema).toJSON();
|
||||
// convert to HTML
|
||||
const contentHTML = generateHTML(contentJSON, DOCUMENT_EDITOR_EXTENSIONS);
|
||||
|
||||
|
|
@ -34,26 +30,21 @@ export const getAllDocumentFormatsFromBinaryData = (description: Uint8Array): {
|
|||
contentJSON,
|
||||
contentHTML,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const getBinaryDataFromHTMLString = (descriptionHTML: string): {
|
||||
contentBinary: Uint8Array
|
||||
export const getBinaryDataFromHTMLString = (
|
||||
descriptionHTML: string
|
||||
): {
|
||||
contentBinary: Uint8Array;
|
||||
} => {
|
||||
// convert HTML to JSON
|
||||
const contentJSON = generateJSON(
|
||||
descriptionHTML ?? "<p></p>",
|
||||
DOCUMENT_EDITOR_EXTENSIONS
|
||||
);
|
||||
const contentJSON = generateJSON(descriptionHTML ?? "<p></p>", DOCUMENT_EDITOR_EXTENSIONS);
|
||||
// convert JSON to Y.Doc format
|
||||
const transformedData = prosemirrorJSONToYDoc(
|
||||
documentEditorSchema,
|
||||
contentJSON,
|
||||
"default"
|
||||
);
|
||||
const transformedData = prosemirrorJSONToYDoc(documentEditorSchema, contentJSON, "default");
|
||||
// convert Y.Doc to Uint8Array format
|
||||
const encodedData = Y.encodeStateAsUpdate(transformedData);
|
||||
|
||||
return {
|
||||
contentBinary: encodedData
|
||||
}
|
||||
}
|
||||
contentBinary: encodedData,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@ import { v4 as uuidv4 } from "uuid";
|
|||
import { handleAuthentication } from "@/core/lib/authentication.js";
|
||||
// extensions
|
||||
import { getExtensions } from "@/core/extensions/index.js";
|
||||
import {
|
||||
DocumentCollaborativeEvents,
|
||||
TDocumentEventsServer,
|
||||
} from "@plane/editor/lib";
|
||||
import { DocumentCollaborativeEvents, TDocumentEventsServer } from "@plane/editor/lib";
|
||||
// editor types
|
||||
import { TUserDetails } from "@plane/editor";
|
||||
// types
|
||||
|
|
@ -61,8 +58,7 @@ export const getHocusPocusServer = async () => {
|
|||
},
|
||||
async onStateless({ payload, document }) {
|
||||
// broadcast the client event (derived from the server event) to all the clients so that they can update their state
|
||||
const response =
|
||||
DocumentCollaborativeEvents[payload as TDocumentEventsServer].client;
|
||||
const response = DocumentCollaborativeEvents[payload as TDocumentEventsServer].client;
|
||||
if (response) {
|
||||
document.broadcastStateless(response);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
// helpers
|
||||
import {
|
||||
getAllDocumentFormatsFromBinaryData,
|
||||
getBinaryDataFromHTMLString,
|
||||
} from "@/core/helpers/page.js";
|
||||
import { getAllDocumentFormatsFromBinaryData, getBinaryDataFromHTMLString } from "@/core/helpers/page.js";
|
||||
// services
|
||||
import { PageService } from "@/core/services/page.service.js";
|
||||
import { manualLogger } from "../helpers/logger.js";
|
||||
|
|
@ -12,20 +9,17 @@ export const updatePageDescription = async (
|
|||
params: URLSearchParams,
|
||||
pageId: string,
|
||||
updatedDescription: Uint8Array,
|
||||
cookie: string | undefined,
|
||||
cookie: string | undefined
|
||||
) => {
|
||||
if (!(updatedDescription instanceof Uint8Array)) {
|
||||
throw new Error(
|
||||
"Invalid updatedDescription: must be an instance of Uint8Array",
|
||||
);
|
||||
throw new Error("Invalid updatedDescription: must be an instance of Uint8Array");
|
||||
}
|
||||
|
||||
const workspaceSlug = params.get("workspaceSlug")?.toString();
|
||||
const projectId = params.get("projectId")?.toString();
|
||||
if (!workspaceSlug || !projectId || !cookie) return;
|
||||
|
||||
const { contentBinaryEncoded, contentHTML, contentJSON } =
|
||||
getAllDocumentFormatsFromBinaryData(updatedDescription);
|
||||
const { contentBinaryEncoded, contentHTML, contentJSON } = getAllDocumentFormatsFromBinaryData(updatedDescription);
|
||||
try {
|
||||
const payload = {
|
||||
description_binary: contentBinaryEncoded,
|
||||
|
|
@ -33,13 +27,7 @@ export const updatePageDescription = async (
|
|||
description: contentJSON,
|
||||
};
|
||||
|
||||
await pageService.updateDescription(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
pageId,
|
||||
payload,
|
||||
cookie,
|
||||
);
|
||||
await pageService.updateDescription(workspaceSlug, projectId, pageId, payload, cookie);
|
||||
} catch (error) {
|
||||
manualLogger.error("Update error:", error);
|
||||
throw error;
|
||||
|
|
@ -50,26 +38,16 @@ const fetchDescriptionHTMLAndTransform = async (
|
|||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
pageId: string,
|
||||
cookie: string,
|
||||
cookie: string
|
||||
) => {
|
||||
if (!workspaceSlug || !projectId || !cookie) return;
|
||||
|
||||
try {
|
||||
const pageDetails = await pageService.fetchDetails(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
pageId,
|
||||
cookie,
|
||||
);
|
||||
const { contentBinary } = getBinaryDataFromHTMLString(
|
||||
pageDetails.description_html ?? "<p></p>",
|
||||
);
|
||||
const pageDetails = await pageService.fetchDetails(workspaceSlug, projectId, pageId, cookie);
|
||||
const { contentBinary } = getBinaryDataFromHTMLString(pageDetails.description_html ?? "<p></p>");
|
||||
return contentBinary;
|
||||
} catch (error) {
|
||||
manualLogger.error(
|
||||
"Error while transforming from HTML to Uint8Array",
|
||||
error,
|
||||
);
|
||||
manualLogger.error("Error while transforming from HTML to Uint8Array", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
|
@ -77,28 +55,18 @@ const fetchDescriptionHTMLAndTransform = async (
|
|||
export const fetchPageDescriptionBinary = async (
|
||||
params: URLSearchParams,
|
||||
pageId: string,
|
||||
cookie: string | undefined,
|
||||
cookie: string | undefined
|
||||
) => {
|
||||
const workspaceSlug = params.get("workspaceSlug")?.toString();
|
||||
const projectId = params.get("projectId")?.toString();
|
||||
if (!workspaceSlug || !projectId || !cookie) return null;
|
||||
|
||||
try {
|
||||
const response = await pageService.fetchDescriptionBinary(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
pageId,
|
||||
cookie,
|
||||
);
|
||||
const response = await pageService.fetchDescriptionBinary(workspaceSlug, projectId, pageId, cookie);
|
||||
const binaryData = new Uint8Array(response);
|
||||
|
||||
if (binaryData.byteLength === 0) {
|
||||
const binary = await fetchDescriptionHTMLAndTransform(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
pageId,
|
||||
cookie,
|
||||
);
|
||||
const binary = await fetchDescriptionHTMLAndTransform(workspaceSlug, projectId, pageId, cookie);
|
||||
if (binary) {
|
||||
return binary;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,42 +8,26 @@ export class PageService extends APIService {
|
|||
super(API_BASE_URL);
|
||||
}
|
||||
|
||||
async fetchDetails(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
pageId: string,
|
||||
cookie: string
|
||||
): Promise<TPage> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/`,
|
||||
{
|
||||
async fetchDetails(workspaceSlug: string, projectId: string, pageId: string, cookie: string): Promise<TPage> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/`, {
|
||||
headers: {
|
||||
Cookie: cookie,
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async fetchDescriptionBinary(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
pageId: string,
|
||||
cookie: string
|
||||
): Promise<any> {
|
||||
return this.get(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/description/`,
|
||||
{
|
||||
async fetchDescriptionBinary(workspaceSlug: string, projectId: string, pageId: string, cookie: string): Promise<any> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/description/`, {
|
||||
headers: {
|
||||
"Content-Type": "application/octet-stream",
|
||||
Cookie: cookie,
|
||||
},
|
||||
responseType: "arraybuffer",
|
||||
}
|
||||
)
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
|
|
@ -61,15 +45,11 @@ export class PageService extends APIService {
|
|||
},
|
||||
cookie: string
|
||||
): Promise<any> {
|
||||
return this.patch(
|
||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/description/`,
|
||||
data,
|
||||
{
|
||||
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/description/`, data, {
|
||||
headers: {
|
||||
Cookie: cookie,
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error;
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export * from "../../ce/lib/fetch-document.js"
|
||||
export * from "../../ce/lib/fetch-document.js";
|
||||
|
|
|
|||
2
apps/live/src/ee/types/common.d.ts
vendored
2
apps/live/src/ee/types/common.d.ts
vendored
|
|
@ -1 +1 @@
|
|||
export * from "../../ce/types/common.js"
|
||||
export * from "../../ce/types/common.js";
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ export const IssueAppliedFilters: FC<TIssueAppliedFilters> = observer((props) =>
|
|||
|
||||
const updateRouteParams = useCallback(
|
||||
(key: keyof TIssueQueryFilters, value: string[]) => {
|
||||
const state = key === "state" ? value : issueFilters?.filters?.state ?? [];
|
||||
const priority = key === "priority" ? value : issueFilters?.filters?.priority ?? [];
|
||||
const labels = key === "labels" ? value : issueFilters?.filters?.labels ?? [];
|
||||
const state = key === "state" ? value : (issueFilters?.filters?.state ?? []);
|
||||
const priority = key === "priority" ? value : (issueFilters?.filters?.priority ?? []);
|
||||
const labels = key === "labels" ? value : (issueFilters?.filters?.labels ?? []);
|
||||
|
||||
let params: any = { board: activeLayout || "list" };
|
||||
if (priority.length > 0) params = { ...params, priority: priority.join(",") };
|
||||
|
|
|
|||
|
|
@ -76,8 +76,8 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
|
|||
const isSubGroup = !!subGroupId && subGroupId !== "null";
|
||||
|
||||
const issueIds = isSubGroup
|
||||
? (groupedIssueIds as TSubGroupedIssues)?.[groupId]?.[subGroupId] ?? []
|
||||
: (groupedIssueIds as TGroupedIssues)?.[groupId] ?? [];
|
||||
? ((groupedIssueIds as TSubGroupedIssues)?.[groupId]?.[subGroupId] ?? [])
|
||||
: ((groupedIssueIds as TGroupedIssues)?.[groupId] ?? []);
|
||||
|
||||
const groupIssueCount = getGroupIssueCount(groupId, subGroupId, false) ?? 0;
|
||||
const nextPageResults = getPaginationData(groupId, subGroupId)?.nextPageResults;
|
||||
|
|
|
|||
|
|
@ -133,12 +133,7 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
|
|||
if (subGroupByVisibilityToggle === false) return <></>;
|
||||
return (
|
||||
<div key={`${subGroupBy}_${group.id}`} className="flex w-[350px] flex-shrink-0 flex-col">
|
||||
<HeaderGroupByCard
|
||||
groupBy={groupBy}
|
||||
icon={group.icon}
|
||||
title={group.name}
|
||||
count={groupCount}
|
||||
/>
|
||||
<HeaderGroupByCard groupBy={groupBy} icon={group.icon} title={group.name} count={groupCount} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import { useParams, usePathname } from "next/navigation";
|
||||
import { ArrowUpToLine, Building, CreditCard, Users, Webhook } from "lucide-react";
|
||||
import { EUserPermissionsLevel, GROUPED_WORKSPACE_SETTINGS, WORKSPACE_SETTINGS_CATEGORIES, EUserPermissions, WORKSPACE_SETTINGS_CATEGORY } from "@plane/constants";
|
||||
import {
|
||||
EUserPermissionsLevel,
|
||||
GROUPED_WORKSPACE_SETTINGS,
|
||||
WORKSPACE_SETTINGS_CATEGORIES,
|
||||
EUserPermissions,
|
||||
WORKSPACE_SETTINGS_CATEGORY,
|
||||
} from "@plane/constants";
|
||||
import { EUserWorkspaceRoles } from "@plane/types";
|
||||
import { SettingsSidebar } from "@/components/settings";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const usePreloadResources = () => {
|
|||
`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/workspaces/?v=${Date.now()}`,
|
||||
];
|
||||
|
||||
urls.forEach(url => preloadItem(url));
|
||||
urls.forEach((url) => preloadItem(url));
|
||||
}, []);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export const metadata: Metadata = {
|
|||
robots: {
|
||||
index: true,
|
||||
follow: false,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default function SignUpLayout({ children }: { children: React.ReactNode }) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,5 @@ export const viewport: Viewport = {
|
|||
};
|
||||
|
||||
export default function HomeLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>{children}</>
|
||||
);
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,5 +4,9 @@ import packageJson from "package.json";
|
|||
|
||||
export const PlaneVersionNumber: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
return <span>{t("version")}: v{packageJson.version}</span>;
|
||||
return (
|
||||
<span>
|
||||
{t("version")}: v{packageJson.version}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,4 +2,3 @@ export * from "./provider";
|
|||
export * from "./issue-type-select";
|
||||
export * from "./additional-properties";
|
||||
export * from "./template-select";
|
||||
|
||||
|
|
|
|||
|
|
@ -5,13 +5,17 @@ export const getRelationActivityContent = (activity: TIssueActivity | undefined)
|
|||
|
||||
switch (activity.field) {
|
||||
case "blocking":
|
||||
return activity.old_value === "" ? `marked this work item is blocking work item ` : `removed the blocking work item `;
|
||||
return activity.old_value === ""
|
||||
? `marked this work item is blocking work item `
|
||||
: `removed the blocking work item `;
|
||||
case "blocked_by":
|
||||
return activity.old_value === ""
|
||||
? `marked this work item is being blocked by `
|
||||
: `removed this work item being blocked by work item `;
|
||||
case "duplicate":
|
||||
return activity.old_value === "" ? `marked this work item as duplicate of ` : `removed this work item as a duplicate of `;
|
||||
return activity.old_value === ""
|
||||
? `marked this work item as duplicate of `
|
||||
: `removed this work item as a duplicate of `;
|
||||
case "relates_to":
|
||||
return activity.old_value === "" ? `marked that this work item relates to ` : `removed the relation from `;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ export const OAuthOptions: React.FC<TOAuthOptionProps> = observer(() => {
|
|||
// hooks
|
||||
const { config } = useInstance();
|
||||
|
||||
const isOAuthEnabled = (config && (config?.is_google_enabled || config?.is_github_enabled || config?.is_gitlab_enabled)) || false;
|
||||
const isOAuthEnabled =
|
||||
(config && (config?.is_google_enabled || config?.is_github_enabled || config?.is_gitlab_enabled)) || false;
|
||||
|
||||
if (!isOAuthEnabled) return null;
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import { InsightTable } from "../insight-table";
|
|||
|
||||
const analyticsService = new AnalyticsService();
|
||||
|
||||
|
||||
declare module "@tanstack/react-table" {
|
||||
interface ColumnMeta<TData extends RowData, TValue> {
|
||||
export: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
import { getWeekOfMonth, isValid } from "date-fns";
|
||||
import { CHART_X_AXIS_DATE_PROPERTIES, ChartXAxisDateGrouping, TO_CAPITALIZE_PROPERTIES } from "@plane/constants";
|
||||
import { ChartXAxisProperty, TChart, TChartDatum } from "@plane/types";
|
||||
import { capitalizeFirstLetter, hexToHsl, hslToHex, renderFormattedDate, renderFormattedDateWithoutYear } from "@plane/utils";
|
||||
import {
|
||||
capitalizeFirstLetter,
|
||||
hexToHsl,
|
||||
hslToHex,
|
||||
renderFormattedDate,
|
||||
renderFormattedDateWithoutYear,
|
||||
} from "@plane/utils";
|
||||
//
|
||||
|
||||
const getDateGroupingName = (date: string, dateGrouping: ChartXAxisDateGrouping): string => {
|
||||
|
|
@ -61,7 +67,7 @@ export const parseChartData = (
|
|||
const updatedWidgetData: TChartDatum[] = widgetData.map((datum) => {
|
||||
const keys = Object.keys(datum);
|
||||
const missingKeys = allKeys.filter((key) => !keys.includes(key));
|
||||
const missingValues: Record<string, number> = Object.fromEntries(missingKeys.map(key => [key, 0]));
|
||||
const missingValues: Record<string, number> = Object.fromEntries(missingKeys.map((key) => [key, 0]));
|
||||
|
||||
if (xAxisProperty) {
|
||||
// capitalize first letter if xAxisProperty is in TO_CAPITALIZE_PROPERTIES and no groupByProperty is set
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@ import { EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constant
|
|||
// i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { EIssuesStoreType, IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
import {
|
||||
EIssuesStoreType,
|
||||
IIssueDisplayFilterOptions,
|
||||
IIssueDisplayProperties,
|
||||
IIssueFilterOptions,
|
||||
} from "@plane/types";
|
||||
// components
|
||||
import { isIssueFilterActive } from "@plane/utils";
|
||||
import { ArchiveTabsList } from "@/components/archives";
|
||||
|
|
@ -74,7 +79,11 @@ export const ArchivedIssuesHeader: FC = observer(() => {
|
|||
</div>
|
||||
{/* filter options */}
|
||||
<div className="flex items-center gap-2 px-8">
|
||||
<FiltersDropdown title={t("common.filters")} placement="bottom-end" isFiltersApplied={isIssueFilterActive(issueFilters)}>
|
||||
<FiltersDropdown
|
||||
title={t("common.filters")}
|
||||
placement="bottom-end"
|
||||
isFiltersApplied={isIssueFilterActive(issueFilters)}
|
||||
>
|
||||
<FilterSelection
|
||||
filters={issueFilters?.filters || {}}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,14 @@ import Link from "next/link";
|
|||
import { AlertCircle, X } from "lucide-react";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
import { convertBytesToSize, getFileExtension, getFileName, getFileURL, renderFormattedDate, truncateText } from "@plane/utils";
|
||||
import {
|
||||
convertBytesToSize,
|
||||
getFileExtension,
|
||||
getFileName,
|
||||
getFileURL,
|
||||
renderFormattedDate,
|
||||
truncateText,
|
||||
} from "@plane/utils";
|
||||
// icons
|
||||
//
|
||||
import { getFileIcon } from "@/components/icons";
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ import { EIssueLayoutTypes, EIssueFilterType, ISSUE_STORE_TO_FILTERS_MAP } from
|
|||
// i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { EIssuesStoreType, IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
import {
|
||||
EIssuesStoreType,
|
||||
IIssueDisplayFilterOptions,
|
||||
IIssueDisplayProperties,
|
||||
IIssueFilterOptions,
|
||||
} from "@plane/types";
|
||||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { isIssueFilterActive } from "@plane/utils";
|
||||
|
|
|
|||
|
|
@ -18,14 +18,7 @@ import { ArchiveIssueModal, DeleteIssueModal, IssueSubscription } from "@/compon
|
|||
// helpers
|
||||
// hooks
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import {
|
||||
useIssueDetail,
|
||||
useIssues,
|
||||
useProject,
|
||||
useProjectState,
|
||||
useUser,
|
||||
useUserPermissions,
|
||||
} from "@/hooks/store";
|
||||
import { useIssueDetail, useIssues, useProject, useProjectState, useUser, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
export * from "./blocks";
|
||||
export * from "./base-gantt-root";
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@ import { FC, useCallback, useEffect } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { ALL_ISSUES, EIssueLayoutTypes, EIssueFilterType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import {
|
||||
ALL_ISSUES,
|
||||
EIssueLayoutTypes,
|
||||
EIssueFilterType,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
} from "@plane/constants";
|
||||
import { EIssuesStoreType, IIssueDisplayFilterOptions } from "@plane/types";
|
||||
// hooks
|
||||
import { useIssues, useUserPermissions } from "@/hooks/store";
|
||||
|
|
|
|||
|
|
@ -12,7 +12,14 @@ import { useTranslation } from "@plane/i18n";
|
|||
import { EIssuesStoreType, TIssue, TWorkspaceDraftIssue } from "@plane/types";
|
||||
// hooks
|
||||
import { Button, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { convertWorkItemDataToSearchResponse, getUpdateFormDataForReset, cn, getTextContent, getChangedIssuefields, getTabIndex } from "@plane/utils";
|
||||
import {
|
||||
convertWorkItemDataToSearchResponse,
|
||||
getUpdateFormDataForReset,
|
||||
cn,
|
||||
getTextContent,
|
||||
getChangedIssuefields,
|
||||
getTabIndex,
|
||||
} from "@plane/utils";
|
||||
// components
|
||||
import {
|
||||
IssueDefaultProperties,
|
||||
|
|
|
|||
|
|
@ -137,7 +137,12 @@ export const IssueLabelSelect: React.FC<Props> = observer((props) => {
|
|||
/>
|
||||
</span>
|
||||
) : (
|
||||
<div className={cn("h-full flex items-center justify-center gap-1 rounded border-[0.5px] border-custom-border-300 px-2 py-1 text-xs hover:bg-custom-background-80", buttonClassName)}>
|
||||
<div
|
||||
className={cn(
|
||||
"h-full flex items-center justify-center gap-1 rounded border-[0.5px] border-custom-border-300 px-2 py-1 text-xs hover:bg-custom-background-80",
|
||||
buttonClassName
|
||||
)}
|
||||
>
|
||||
<Tag className="h-3 w-3 flex-shrink-0" />
|
||||
<span>{t("labels")}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export const WorkspaceDraftEmptyState: FC = observer(() => {
|
|||
onClick: () => {
|
||||
setIsDraftIssueModalOpen(true);
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,10 +3,7 @@ import { useParams } from "next/navigation";
|
|||
// PLane
|
||||
import { IBlockUpdateData, IBlockUpdateDependencyData, IModule } from "@plane/types";
|
||||
// components
|
||||
import {
|
||||
GanttChartRoot,
|
||||
ModuleGanttSidebar,
|
||||
} from "@/components/gantt-chart";
|
||||
import { GanttChartRoot, ModuleGanttSidebar } from "@/components/gantt-chart";
|
||||
import { ETimeLineTypeType, TimeLineTypeContext } from "@/components/gantt-chart/contexts";
|
||||
import { ModuleGanttBlock } from "@/components/modules";
|
||||
// hooks
|
||||
|
|
|
|||
|
|
@ -63,7 +63,9 @@ export const ProfileActivity = observer(() => {
|
|||
<div className="-mt-1 w-4/5 break-words">
|
||||
<p className="inline text-sm text-custom-text-200">
|
||||
<span className="font-medium text-custom-text-100">
|
||||
{currentUser?.id === activity.actor_detail?.id ? "You" : activity.actor_detail?.display_name}{" "}
|
||||
{currentUser?.id === activity.actor_detail?.id
|
||||
? "You"
|
||||
: activity.actor_detail?.display_name}{" "}
|
||||
</span>
|
||||
{activity.field ? (
|
||||
<ActivityMessage activity={activity} showIssue />
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@ import { EIssueLayoutTypes, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_PAGE } fr
|
|||
// i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { EIssuesStoreType, IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
import {
|
||||
EIssuesStoreType,
|
||||
IIssueDisplayFilterOptions,
|
||||
IIssueDisplayProperties,
|
||||
IIssueFilterOptions,
|
||||
} from "@plane/types";
|
||||
// components
|
||||
import { isIssueFilterActive } from "@plane/utils";
|
||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, LayoutSelection } from "@/components/issues";
|
||||
|
|
@ -107,7 +112,11 @@ export const ProfileIssuesFilter = observer(() => {
|
|||
selectedLayout={activeLayout}
|
||||
/>
|
||||
|
||||
<FiltersDropdown title={t("common.filters")} placement="bottom-end" isFiltersApplied={isIssueFilterActive(issueFilters)}>
|
||||
<FiltersDropdown
|
||||
title={t("common.filters")}
|
||||
placement="bottom-end"
|
||||
isFiltersApplied={isIssueFilterActive(issueFilters)}
|
||||
>
|
||||
<FilterSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.profile_issues[activeLayout] : undefined
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import { FC } from "react";
|
||||
import { TNotification } from "@plane/types";
|
||||
import { convertMinutesToHoursMinutesString, renderFormattedDate, sanitizeCommentForNotification, replaceUnderscoreIfSnakeCase, stripAndTruncateHTML } from "@plane/utils";
|
||||
import {
|
||||
convertMinutesToHoursMinutesString,
|
||||
renderFormattedDate,
|
||||
sanitizeCommentForNotification,
|
||||
replaceUnderscoreIfSnakeCase,
|
||||
stripAndTruncateHTML,
|
||||
} from "@plane/utils";
|
||||
// components
|
||||
// helpers
|
||||
import { LiteTextReadOnlyEditor } from "@/components/editor";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { observer } from "mobx-react";
|
|||
import { useTranslation } from "@plane/i18n";
|
||||
import { cn, getFileURL } from "@plane/utils";
|
||||
|
||||
|
||||
type Props = {
|
||||
logo: string | null | undefined;
|
||||
name: string | undefined;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export type TargetData = {
|
|||
parentId: string | null;
|
||||
isGroup: boolean;
|
||||
isChild: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* extracts the Payload and translates the instruction for the current dropTarget based on drag and drop payload
|
||||
|
|
@ -60,7 +60,6 @@ export const getCanDrop = (source: TDropTarget, favorite: IFavorite | undefined,
|
|||
// a favorite cannot be dropped on to itself
|
||||
if (sourceData.id === favorite?.id) return false;
|
||||
|
||||
|
||||
// if current dropTarget is a child and the favorite being dropped is a group then don't enable drop
|
||||
if (isCurrentChild && sourceData.isGroup) return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -112,7 +112,9 @@ export const GlobalViewsHeader: React.FC = observer(() => {
|
|||
<DefaultViewTab key={`${tab.key}-${index}`} tab={tab} />
|
||||
))}
|
||||
|
||||
{currentWorkspaceViews?.map((viewId) => <ViewTab key={viewId} viewId={viewId} />)}
|
||||
{currentWorkspaceViews?.map((viewId) => (
|
||||
<ViewTab key={viewId} viewId={viewId} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{isAuthorizedUser ? (
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import { start, done } from 'nprogress';
|
||||
import {
|
||||
AppProgressBar as AppProgressBarComponent,
|
||||
useRouter,
|
||||
} from './AppProgressBar';
|
||||
import withSuspense from './withSuspense';
|
||||
import { start, done } from "nprogress";
|
||||
import { AppProgressBar as AppProgressBarComponent, useRouter } from "./AppProgressBar";
|
||||
import withSuspense from "./withSuspense";
|
||||
|
||||
export interface NProgressOptions {
|
||||
minimum?: number;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
export function isSameURL(target: URL, current: URL) {
|
||||
const cleanTarget =
|
||||
target.protocol + '//' + target.host + target.pathname + target.search;
|
||||
const cleanCurrent =
|
||||
current.protocol + '//' + current.host + current.pathname + current.search;
|
||||
const cleanTarget = target.protocol + "//" + target.host + target.pathname + target.search;
|
||||
const cleanCurrent = current.protocol + "//" + current.host + current.pathname + current.search;
|
||||
|
||||
return cleanTarget === cleanCurrent;
|
||||
}
|
||||
|
||||
export function isSameURLWithoutSearch(target: URL, current: URL) {
|
||||
const cleanTarget = target.protocol + '//' + target.host + target.pathname;
|
||||
const cleanCurrent =
|
||||
current.protocol + '//' + current.host + current.pathname;
|
||||
const cleanTarget = target.protocol + "//" + target.host + target.pathname;
|
||||
const cleanCurrent = current.protocol + "//" + current.host + current.pathname;
|
||||
|
||||
return cleanTarget === cleanCurrent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
'use client'
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
// posthog
|
||||
import { usePostHog } from 'posthog-js/react';
|
||||
import { usePostHog } from "posthog-js/react";
|
||||
|
||||
export default function PostHogPageView(): null {
|
||||
const pathname = usePathname();
|
||||
|
|
@ -12,18 +12,15 @@ export default function PostHogPageView(): null {
|
|||
useEffect(() => {
|
||||
// Track pageviews
|
||||
if (pathname && posthog) {
|
||||
let url = window.origin + pathname
|
||||
let url = window.origin + pathname;
|
||||
if (searchParams.toString()) {
|
||||
url = url + `?${searchParams.toString()}`
|
||||
url = url + `?${searchParams.toString()}`;
|
||||
}
|
||||
posthog.capture(
|
||||
'$pageview',
|
||||
{
|
||||
'$current_url': url,
|
||||
posthog.capture("$pageview", {
|
||||
$current_url: url,
|
||||
});
|
||||
}
|
||||
)
|
||||
}
|
||||
}, [pathname, searchParams, posthog])
|
||||
}, [pathname, searchParams, posthog]);
|
||||
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,4 +17,6 @@ const initializeStore = () => {
|
|||
|
||||
export const store = initializeStore();
|
||||
|
||||
export const StoreProvider = ({ children }: { children: ReactElement }) => <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
|
||||
export const StoreProvider = ({ children }: { children: ReactElement }) => (
|
||||
<StoreContext.Provider value={store}>{children}</StoreContext.Provider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -44,22 +44,10 @@ declare interface SQLiteVFS {
|
|||
xClose(fileId: number): number | Promise<number>;
|
||||
|
||||
/** @see https://sqlite.org/c3ref/io_methods.html */
|
||||
xRead(
|
||||
fileId: number,
|
||||
pData: number,
|
||||
iAmt: number,
|
||||
iOffsetLo: number,
|
||||
iOffsetHi: number
|
||||
): number|Promise<number>;
|
||||
xRead(fileId: number, pData: number, iAmt: number, iOffsetLo: number, iOffsetHi: number): number | Promise<number>;
|
||||
|
||||
/** @see https://sqlite.org/c3ref/io_methods.html */
|
||||
xWrite(
|
||||
fileId: number,
|
||||
pData: number,
|
||||
iAmt: number,
|
||||
iOffsetLo: number,
|
||||
iOffsetHi: number
|
||||
): number|Promise<number>;
|
||||
xWrite(fileId: number, pData: number, iAmt: number, iOffsetLo: number, iOffsetHi: number): number | Promise<number>;
|
||||
|
||||
/** @see https://sqlite.org/c3ref/io_methods.html */
|
||||
xTruncate(fileId: number, iSizeLo: number, iSizeHi): number | Promise<number>;
|
||||
|
|
@ -68,10 +56,7 @@ declare interface SQLiteVFS {
|
|||
xSync(fileId: number, flags: number): number | Promise<number>;
|
||||
|
||||
/** @see https://sqlite.org/c3ref/io_methods.html */
|
||||
xFileSize(
|
||||
fileId: number,
|
||||
pSize64: number
|
||||
): number|Promise<number>;
|
||||
xFileSize(fileId: number, pSize64: number): number | Promise<number>;
|
||||
|
||||
/** @see https://sqlite.org/c3ref/io_methods.html */
|
||||
xLock(fileId: number, flags: number): number | Promise<number>;
|
||||
|
|
@ -80,40 +65,22 @@ declare interface SQLiteVFS {
|
|||
xUnlock(fileId: number, flags: number): number | Promise<number>;
|
||||
|
||||
/** @see https://sqlite.org/c3ref/io_methods.html */
|
||||
xCheckReservedLock(
|
||||
fileId: number,
|
||||
pResOut: number
|
||||
): number|Promise<number>;
|
||||
xCheckReservedLock(fileId: number, pResOut: number): number | Promise<number>;
|
||||
|
||||
/** @see https://sqlite.org/c3ref/io_methods.html */
|
||||
xFileControl(
|
||||
fileId: number,
|
||||
flags: number,
|
||||
pOut: number
|
||||
): number|Promise<number>;
|
||||
xFileControl(fileId: number, flags: number, pOut: number): number | Promise<number>;
|
||||
|
||||
/** @see https://sqlite.org/c3ref/io_methods.html */
|
||||
xDeviceCharacteristics(fileId: number): number | Promise<number>;
|
||||
|
||||
/** @see https://sqlite.org/c3ref/vfs.html */
|
||||
xOpen(
|
||||
pVfs: number,
|
||||
zName: number,
|
||||
pFile: number,
|
||||
flags: number,
|
||||
pOutFlags: number
|
||||
): number|Promise<number>;
|
||||
xOpen(pVfs: number, zName: number, pFile: number, flags: number, pOutFlags: number): number | Promise<number>;
|
||||
|
||||
/** @see https://sqlite.org/c3ref/vfs.html */
|
||||
xDelete(pVfs: number, zName: number, syncDir: number): number | Promise<number>;
|
||||
|
||||
/** @see https://sqlite.org/c3ref/vfs.html */
|
||||
xAccess(
|
||||
pVfs: number,
|
||||
zName: number,
|
||||
flags: number,
|
||||
pResOut: number
|
||||
): number|Promise<number>;
|
||||
xAccess(pVfs: number, zName: number, flags: number, pResOut: number): number | Promise<number>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -484,7 +451,8 @@ declare interface SQLiteAPI {
|
|||
pApp: number,
|
||||
xFunc?: (context: number, values: Uint32Array) => void | Promise<void>,
|
||||
xStep?: (context: number, values: Uint32Array) => void | Promise<void>,
|
||||
xFinal?: (context: number) => void|Promise<void>): number;
|
||||
xFinal?: (context: number) => void | Promise<void>
|
||||
): number;
|
||||
|
||||
/**
|
||||
* Get number of columns in current row of a prepared statement
|
||||
|
|
@ -543,7 +511,7 @@ declare interface SQLiteAPI {
|
|||
* @see https://www.sqlite.org/c3ref/libversion.html
|
||||
* @returns version number, e.g. 3035005
|
||||
*/
|
||||
libversion_number(): number
|
||||
libversion_number(): number;
|
||||
|
||||
/**
|
||||
* Set a usage limit on a connection.
|
||||
|
|
@ -553,10 +521,7 @@ declare interface SQLiteAPI {
|
|||
* @param newVal
|
||||
* @returns previous setting
|
||||
*/
|
||||
limit(
|
||||
db: number,
|
||||
id: number,
|
||||
newVal: number): number;
|
||||
limit(db: number, id: number, newVal: number): number;
|
||||
|
||||
/**
|
||||
* Opening a new database connection.
|
||||
|
|
@ -570,11 +535,7 @@ declare interface SQLiteAPI {
|
|||
* @param zVfs VFS name
|
||||
* @returns Promise-wrapped database pointer.
|
||||
*/
|
||||
open_v2(
|
||||
zFilename: string,
|
||||
iFlags?: number,
|
||||
zVfs?: string
|
||||
): Promise<number>;
|
||||
open_v2(zFilename: string, iFlags?: number, zVfs?: string): Promise<number>;
|
||||
|
||||
/**
|
||||
* Specify callback to be invoked between long-running queries
|
||||
|
|
@ -673,8 +634,16 @@ declare interface SQLiteAPI {
|
|||
*/
|
||||
set_authorizer(
|
||||
db: number,
|
||||
authFunction: (userData: any, iActionCode: number, param3: string|null, param4: string|null, param5: string|null, param6: string|null) => number|Promise<number>,
|
||||
userData: any): number;
|
||||
authFunction: (
|
||||
userData: any,
|
||||
iActionCode: number,
|
||||
param3: string | null,
|
||||
param4: string | null,
|
||||
param5: string | null,
|
||||
param6: string | null
|
||||
) => number | Promise<number>,
|
||||
userData: any
|
||||
): number;
|
||||
|
||||
/**
|
||||
* Get statement SQL
|
||||
|
|
@ -751,7 +720,8 @@ declare interface SQLiteAPI {
|
|||
*/
|
||||
update_hook(
|
||||
db: number,
|
||||
callback: (updateType: number, dbName: string|null, tblName: string|null, rowid: bigint) => void): void;
|
||||
callback: (updateType: number, dbName: string | null, tblName: string | null, rowid: bigint) => void
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Extract a value from `sqlite3_value`
|
||||
|
|
@ -839,7 +809,7 @@ declare interface SQLiteAPI {
|
|||
}
|
||||
|
||||
/** @ignore */
|
||||
declare module 'wa-sqlite/src/sqlite-constants.js' {
|
||||
declare module "wa-sqlite/src/sqlite-constants.js" {
|
||||
export const SQLITE_OK: 0;
|
||||
export const SQLITE_ERROR: 1;
|
||||
export const SQLITE_INTERNAL: 2;
|
||||
|
|
@ -1074,8 +1044,8 @@ declare module 'wa-sqlite/src/sqlite-constants.js' {
|
|||
export const SQLITE_PREPARE_NO_VTAB: 0x04;
|
||||
}
|
||||
|
||||
declare module 'wa-sqlite' {
|
||||
export * from 'wa-sqlite/src/sqlite-constants.js';
|
||||
declare module "wa-sqlite" {
|
||||
export * from "wa-sqlite/src/sqlite-constants.js";
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
|
|
@ -1094,20 +1064,20 @@ declare module 'wa-sqlite' {
|
|||
}
|
||||
|
||||
/** @ignore */
|
||||
declare module 'wa-sqlite/dist/wa-sqlite.mjs' {
|
||||
declare module "wa-sqlite/dist/wa-sqlite.mjs" {
|
||||
function ModuleFactory(config?: object): Promise<any>;
|
||||
export = ModuleFactory;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
declare module 'wa-sqlite/dist/wa-sqlite-async.mjs' {
|
||||
declare module "wa-sqlite/dist/wa-sqlite-async.mjs" {
|
||||
function ModuleFactory(config?: object): Promise<any>;
|
||||
export = ModuleFactory;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
declare module 'wa-sqlite/src/VFS.js' {
|
||||
export * from 'wa-sqlite/src/sqlite-constants.js';
|
||||
declare module "wa-sqlite/src/VFS.js" {
|
||||
export * from "wa-sqlite/src/sqlite-constants.js";
|
||||
|
||||
export class Base {
|
||||
mxPathName: number;
|
||||
|
|
@ -1122,20 +1092,28 @@ declare module 'wa-sqlite/src/VFS.js' {
|
|||
* @param {number} iOffset
|
||||
* @returns {number}
|
||||
*/
|
||||
xRead(fileId: number, pData: {
|
||||
xRead(
|
||||
fileId: number,
|
||||
pData: {
|
||||
size: number;
|
||||
value: Uint8Array;
|
||||
}, iOffset: number): number;
|
||||
},
|
||||
iOffset: number
|
||||
): number;
|
||||
/**
|
||||
* @param {number} fileId
|
||||
* @param {Uint8Array} pData
|
||||
* @param {number} iOffset
|
||||
* @returns {number}
|
||||
*/
|
||||
xWrite(fileId: number, pData: {
|
||||
xWrite(
|
||||
fileId: number,
|
||||
pData: {
|
||||
size: number;
|
||||
value: Uint8Array;
|
||||
}, iOffset: number): number;
|
||||
},
|
||||
iOffset: number
|
||||
): number;
|
||||
/**
|
||||
* @param {number} fileId
|
||||
* @param {number} iSize
|
||||
|
|
@ -1222,7 +1200,7 @@ declare module 'wa-sqlite/src/VFS.js' {
|
|||
}
|
||||
|
||||
/** @ignore */
|
||||
declare module 'wa-sqlite/src/examples/IndexedDbVFS.js' {
|
||||
declare module "wa-sqlite/src/examples/IndexedDbVFS.js" {
|
||||
import * as VFS from "wa-sqlite/src/VFS.js";
|
||||
export class IndexedDbVFS extends VFS.Base {
|
||||
/**
|
||||
|
|
@ -1275,7 +1253,7 @@ declare module 'wa-sqlite/src/examples/IndexedDbVFS.js' {
|
|||
}
|
||||
|
||||
/** @ignore */
|
||||
declare module 'wa-sqlite/src/examples/MemoryVFS.js' {
|
||||
declare module "wa-sqlite/src/examples/MemoryVFS.js" {
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import * as VFS from "wa-sqlite/src/VFS.js";
|
||||
/** @ignore */
|
||||
|
|
@ -1287,14 +1265,13 @@ declare module 'wa-sqlite/src/examples/MemoryVFS.js' {
|
|||
}
|
||||
|
||||
/** @ignore */
|
||||
declare module 'wa-sqlite/src/examples/MemoryAsyncVFS.js' {
|
||||
declare module "wa-sqlite/src/examples/MemoryAsyncVFS.js" {
|
||||
import { MemoryVFS } from "wa-sqlite/src/examples/MemoryVFS.js";
|
||||
export class MemoryAsyncVFS extends MemoryVFS {
|
||||
}
|
||||
export class MemoryAsyncVFS extends MemoryVFS {}
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
declare module 'wa-sqlite/src/examples/tag.js' {
|
||||
declare module "wa-sqlite/src/examples/tag.js" {
|
||||
/**
|
||||
* @ignore
|
||||
* Template tag builder. This function creates a tag with an API and
|
||||
|
|
|
|||
|
|
@ -2,7 +2,14 @@ import { action, makeObservable, runInAction } from "mobx";
|
|||
// base class
|
||||
// services
|
||||
// types
|
||||
import { TIssue, TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse, TBulkOperationsPayload } from "@plane/types";
|
||||
import {
|
||||
TIssue,
|
||||
TLoader,
|
||||
ViewFlags,
|
||||
IssuePaginationOptions,
|
||||
TIssuesResponse,
|
||||
TBulkOperationsPayload,
|
||||
} from "@plane/types";
|
||||
import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store";
|
||||
import { IIssueRootStore } from "../root.store";
|
||||
import { IDraftIssuesFilter } from "./filter.store";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
import { action, observable, makeObservable, computed, runInAction } from "mobx";
|
||||
// base class
|
||||
import { TIssue, TLoader, IssuePaginationOptions, TIssuesResponse, ViewFlags, TBulkOperationsPayload } from "@plane/types";
|
||||
import {
|
||||
TIssue,
|
||||
TLoader,
|
||||
IssuePaginationOptions,
|
||||
TIssuesResponse,
|
||||
ViewFlags,
|
||||
TBulkOperationsPayload,
|
||||
} from "@plane/types";
|
||||
import { UserService } from "@/services/user.service";
|
||||
|
||||
// services
|
||||
|
|
|
|||
|
|
@ -1,7 +1,16 @@
|
|||
import isEmpty from "lodash/isEmpty";
|
||||
import { autorun, makeObservable, observable } from "mobx";
|
||||
// types
|
||||
import { EIssueServiceType, ICycle, IIssueLabel, IModule, IProject, IState, IUserLite, TIssueServiceType } from "@plane/types";
|
||||
import {
|
||||
EIssueServiceType,
|
||||
ICycle,
|
||||
IIssueLabel,
|
||||
IModule,
|
||||
IProject,
|
||||
IState,
|
||||
IUserLite,
|
||||
TIssueServiceType,
|
||||
} from "@plane/types";
|
||||
// plane web store
|
||||
import { IProjectEpics, IProjectEpicsFilter, ProjectEpics, ProjectEpicsFilter } from "@/plane-web/store/issue/epic";
|
||||
import { IIssueDetail, IssueDetail } from "@/plane-web/store/issue/issue-details/root.store";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import
|
||||
isEmpty from "lodash/isEmpty";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
import set from "lodash/set";
|
||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||
// base class
|
||||
|
|
|
|||
|
|
@ -171,7 +171,6 @@ export class ProfileStore implements IUserProfileStore {
|
|||
runInAction(() => {
|
||||
this.mutateUserProfile({ ...dataToUpdate, is_onboarded: true });
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
runInAction(() => {
|
||||
this.error = {
|
||||
|
|
@ -181,7 +180,7 @@ export class ProfileStore implements IUserProfileStore {
|
|||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description updates the user tour completed status
|
||||
|
|
|
|||
|
|
@ -20,7 +20,12 @@ export interface IHomeStore {
|
|||
// actions
|
||||
toggleWidgetSettings: (value?: boolean) => void;
|
||||
fetchWidgets: (workspaceSlug: string) => Promise<void>;
|
||||
reorderWidget: (workspaceSlug: string, widgetKey: string, destinationId: string, edge: string | undefined) => Promise<void>;
|
||||
reorderWidget: (
|
||||
workspaceSlug: string,
|
||||
widgetKey: string,
|
||||
destinationId: string,
|
||||
edge: string | undefined
|
||||
) => Promise<void>;
|
||||
toggleWidget: (workspaceSlug: string, widgetKey: string, is_enabled: boolean) => void;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export * from "ce/components/issues/issue-layouts/quick-action-dropdowns"
|
||||
export * from "ce/components/issues/issue-layouts/quick-action-dropdowns";
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -21,7 +21,9 @@
|
|||
right: 0px;
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
box-shadow: 0 0 10px #3f76ff, 0 0 5px #3f76ff;
|
||||
box-shadow:
|
||||
0 0 10px #3f76ff,
|
||||
0 0 5px #3f76ff;
|
||||
opacity: 1;
|
||||
|
||||
-webkit-transform: rotate(3deg) translate(0px, -4px);
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export * from "./common"
|
||||
export * from "./common";
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@ export const SPACE_PASSWORD_CRITERIA = [
|
|||
{
|
||||
key: "min_8_char",
|
||||
label: "Min 8 characters",
|
||||
isCriteriaValid: (password: string) =>
|
||||
password.length >= PASSWORD_MIN_LENGTH,
|
||||
isCriteriaValid: (password: string) => password.length >= PASSWORD_MIN_LENGTH,
|
||||
},
|
||||
// {
|
||||
// key: "min_1_upper_case",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { ChartXAxisProperty, TChartColorScheme } from "@plane/types";
|
|||
export const LABEL_CLASSNAME = "uppercase text-custom-text-300/60 text-sm tracking-wide";
|
||||
export const AXIS_LABEL_CLASSNAME = "uppercase text-custom-text-300/60 text-sm tracking-wide";
|
||||
|
||||
|
||||
export enum ChartXAxisDateGrouping {
|
||||
DAY = "DAY",
|
||||
WEEK = "WEEK",
|
||||
|
|
@ -23,7 +22,6 @@ export const CHART_X_AXIS_DATE_PROPERTIES: ChartXAxisProperty[] = [
|
|||
ChartXAxisProperty.COMPLETED_AT,
|
||||
];
|
||||
|
||||
|
||||
export enum EChartModels {
|
||||
BASIC = "BASIC",
|
||||
STACKED = "STACKED",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,4 @@
|
|||
export type TIssueLayout =
|
||||
| "list"
|
||||
| "kanban"
|
||||
| "calendar"
|
||||
| "spreadsheet"
|
||||
| "gantt";
|
||||
export type TIssueLayout = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt";
|
||||
|
||||
export enum EIssueLayoutTypes {
|
||||
LIST = "list",
|
||||
|
|
|
|||
|
|
@ -1,22 +1,16 @@
|
|||
export const SITE_NAME =
|
||||
"Plane | Simple, extensible, open-source project management tool.";
|
||||
export const SITE_TITLE =
|
||||
"Plane | Simple, extensible, open-source project management tool.";
|
||||
export const SITE_NAME = "Plane | Simple, extensible, open-source project management tool.";
|
||||
export const SITE_TITLE = "Plane | Simple, extensible, open-source project management tool.";
|
||||
export const SITE_DESCRIPTION =
|
||||
"Open-source project management tool to manage work items, cycles, and product roadmaps easily";
|
||||
export const SITE_KEYWORDS =
|
||||
"software development, plan, ship, software, accelerate, code management, release management, project management, work items tracking, agile, scrum, kanban, collaboration";
|
||||
export const SITE_URL = "https://app.plane.so/";
|
||||
export const TWITTER_USER_NAME =
|
||||
"Plane | Simple, extensible, open-source project management tool.";
|
||||
export const TWITTER_USER_NAME = "Plane | Simple, extensible, open-source project management tool.";
|
||||
|
||||
// Plane Sites Metadata
|
||||
export const SPACE_SITE_NAME =
|
||||
"Plane Publish | Make your Plane boards and roadmaps pubic with just one-click. ";
|
||||
export const SPACE_SITE_TITLE =
|
||||
"Plane Publish | Make your Plane boards public with one-click";
|
||||
export const SPACE_SITE_DESCRIPTION =
|
||||
"Plane Publish is a customer feedback management tool built on top of plane.so";
|
||||
export const SPACE_SITE_NAME = "Plane Publish | Make your Plane boards and roadmaps pubic with just one-click. ";
|
||||
export const SPACE_SITE_TITLE = "Plane Publish | Make your Plane boards public with one-click";
|
||||
export const SPACE_SITE_DESCRIPTION = "Plane Publish is a customer feedback management tool built on top of plane.so";
|
||||
export const SPACE_SITE_KEYWORDS =
|
||||
"software development, customer feedback, software, accelerate, code management, release management, project management, work items tracking, agile, scrum, kanban, collaboration";
|
||||
export const SPACE_SITE_URL = "https://app.plane.so/";
|
||||
|
|
|
|||
|
|
@ -31,8 +31,7 @@ export const NOTIFICATION_TABS = [
|
|||
{
|
||||
i18n_label: "notification.tabs.all",
|
||||
value: ENotificationTab.ALL,
|
||||
count: (unReadNotification: TUnreadNotificationsCount) =>
|
||||
unReadNotification?.total_unread_notifications_count || 0,
|
||||
count: (unReadNotification: TUnreadNotificationsCount) => unReadNotification?.total_unread_notifications_count || 0,
|
||||
},
|
||||
{
|
||||
i18n_label: "notification.tabs.mentions",
|
||||
|
|
|
|||
|
|
@ -53,14 +53,7 @@ export const PROJECT_CREATE_TAB_INDICES = [
|
|||
"logo_props",
|
||||
];
|
||||
|
||||
export const PROJECT_CYCLE_TAB_INDICES = [
|
||||
"name",
|
||||
"description",
|
||||
"date_range",
|
||||
"cancel",
|
||||
"submit",
|
||||
"project_id",
|
||||
];
|
||||
export const PROJECT_CYCLE_TAB_INDICES = ["name", "description", "date_range", "cancel", "submit", "project_id"];
|
||||
|
||||
export const PROJECT_MODULE_TAB_INDICES = [
|
||||
"name",
|
||||
|
|
@ -73,21 +66,9 @@ export const PROJECT_MODULE_TAB_INDICES = [
|
|||
"submit",
|
||||
];
|
||||
|
||||
export const PROJECT_VIEW_TAB_INDICES = [
|
||||
"name",
|
||||
"description",
|
||||
"filters",
|
||||
"cancel",
|
||||
"submit",
|
||||
];
|
||||
export const PROJECT_VIEW_TAB_INDICES = ["name", "description", "filters", "cancel", "submit"];
|
||||
|
||||
export const PROJECT_PAGE_TAB_INDICES = [
|
||||
"name",
|
||||
"public",
|
||||
"private",
|
||||
"cancel",
|
||||
"submit",
|
||||
];
|
||||
export const PROJECT_PAGE_TAB_INDICES = ["name", "public", "private", "cancel", "submit"];
|
||||
|
||||
export enum ETabIndices {
|
||||
ISSUE_FORM = "issue-form",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,4 @@
|
|||
export const THEMES = [
|
||||
"light",
|
||||
"dark",
|
||||
"light-contrast",
|
||||
"dark-contrast",
|
||||
"custom",
|
||||
];
|
||||
export const THEMES = ["light", "dark", "light-contrast", "dark-contrast", "custom"];
|
||||
|
||||
export interface I_THEME_OPTION {
|
||||
key: string;
|
||||
|
|
|
|||
|
|
@ -52,11 +52,7 @@ export type TUserAllowedPermissions = {
|
|||
export const USER_ALLOWED_PERMISSIONS: TUserAllowedPermissions = {
|
||||
workspace: {
|
||||
dashboard: {
|
||||
read: [
|
||||
EUserPermissions.ADMIN,
|
||||
EUserPermissions.MEMBER,
|
||||
EUserPermissions.GUEST,
|
||||
],
|
||||
read: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
|
||||
},
|
||||
},
|
||||
project: {},
|
||||
|
|
|
|||
|
|
@ -12,4 +12,3 @@ import * as WebSocketDecorators from "./websocket";
|
|||
// Named namespace exports
|
||||
export const Rest = RestDecorators;
|
||||
export const WebSocketNS = WebSocketDecorators;
|
||||
|
||||
|
|
|
|||
|
|
@ -21,13 +21,13 @@ export function Controller(baseRoute: string = ""): ClassDecorator {
|
|||
* @returns Method decorator
|
||||
*/
|
||||
function createHttpMethodDecorator(
|
||||
method: RestMethod
|
||||
method: RestMethod,
|
||||
): (route: string) => MethodDecorator {
|
||||
return function (route: string): MethodDecorator {
|
||||
return function (
|
||||
target: object,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: PropertyDescriptor
|
||||
descriptor: PropertyDescriptor,
|
||||
) {
|
||||
Reflect.defineMetadata("method", method, target, propertyKey);
|
||||
Reflect.defineMetadata("route", route, target, propertyKey);
|
||||
|
|
|
|||
|
|
@ -10,12 +10,6 @@
|
|||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./src"
|
||||
],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"build",
|
||||
"node_modules"
|
||||
]
|
||||
"include": ["./src"],
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { defineConfig } from 'tsup';
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm', 'cjs'],
|
||||
entry: ["src/index.ts"],
|
||||
format: ["esm", "cjs"],
|
||||
dts: true,
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
external: ['express', 'ws'],
|
||||
external: ["express", "ws"],
|
||||
treeshake: true,
|
||||
});
|
||||
|
|
@ -55,7 +55,8 @@ export const EditorContainer: FC<EditorContainerProps> = (props) => {
|
|||
|
||||
// Check if its last node and add new node
|
||||
if (lastNode) {
|
||||
const isLastNodeEmptyParagraph = lastNode.type.name === CORE_EXTENSIONS.PARAGRAPH && lastNode.content.size === 0;
|
||||
const isLastNodeEmptyParagraph =
|
||||
lastNode.type.name === CORE_EXTENSIONS.PARAGRAPH && lastNode.content.size === 0;
|
||||
// Only insert a new paragraph if the last node is not an empty paragraph and not a doc node
|
||||
if (!isLastNodeEmptyParagraph && lastNode.type.name !== "doc") {
|
||||
const endPosition = editor?.state.doc.content.size;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { useState, useEffect, useCallback } from "react";
|
||||
|
||||
export const getValueFromLocalStorage = (key: string, defaultValue: any) => {
|
||||
if (typeof window === undefined || typeof window === "undefined")
|
||||
return defaultValue;
|
||||
if (typeof window === undefined || typeof window === "undefined") return defaultValue;
|
||||
try {
|
||||
const item = window.localStorage.getItem(key);
|
||||
return item ? JSON.parse(item) : defaultValue;
|
||||
|
|
@ -13,8 +12,7 @@ export const getValueFromLocalStorage = (key: string, defaultValue: any) => {
|
|||
};
|
||||
|
||||
export const setValueIntoLocalStorage = (key: string, value: any) => {
|
||||
if (typeof window === undefined || typeof window === "undefined")
|
||||
return false;
|
||||
if (typeof window === undefined || typeof window === "undefined") return false;
|
||||
try {
|
||||
window.localStorage.setItem(key, JSON.stringify(value));
|
||||
return true;
|
||||
|
|
@ -24,9 +22,7 @@ export const setValueIntoLocalStorage = (key: string, value: any) => {
|
|||
};
|
||||
|
||||
export const useLocalStorage = <T,>(key: string, initialValue: T) => {
|
||||
const [storedValue, setStoredValue] = useState<T | null>(() =>
|
||||
getValueFromLocalStorage(key, initialValue)
|
||||
);
|
||||
const [storedValue, setStoredValue] = useState<T | null>(() => getValueFromLocalStorage(key, initialValue));
|
||||
|
||||
const setValue = useCallback(
|
||||
(value: T) => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
This package provides a logger and a request logger utility built using [Winston](https://github.com/winstonjs/winston). It offers customizable log levels using env and supports structured logging for general application logs and HTTP requests.
|
||||
|
||||
## Features.
|
||||
|
||||
- Dynamic log level configuration using env.
|
||||
- Pre-configured winston logger for general usage (`logger`).
|
||||
- Request logger middleware that logs incoming request
|
||||
|
|
@ -10,7 +11,9 @@ This package provides a logger and a request logger utility built using [Winston
|
|||
## Usage
|
||||
|
||||
### Adding as a package
|
||||
|
||||
Add this package as a dependency in package.json
|
||||
|
||||
```typescript
|
||||
dependency: {
|
||||
...
|
||||
|
|
@ -20,11 +23,15 @@ dependency: {
|
|||
```
|
||||
|
||||
### Importing the Logger
|
||||
|
||||
```typescript
|
||||
import { logger, requestLogger } from '@plane/logger'
|
||||
import { logger, requestLogger } from "@plane/logger";
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
### `logger`: General Logger
|
||||
|
||||
Use this for general application logs.
|
||||
|
||||
```typescript
|
||||
|
|
@ -34,14 +41,16 @@ logger.error("This is an error");
|
|||
```
|
||||
|
||||
### `requestLogger`: Request Logger Middleware
|
||||
|
||||
Use this as a middleware for incoming requests
|
||||
|
||||
```typescript
|
||||
const app = express()
|
||||
app.use(requestLogger)
|
||||
const app = express();
|
||||
app.use(requestLogger);
|
||||
```
|
||||
|
||||
## Available Log Levels
|
||||
|
||||
- `error`
|
||||
- `warn`
|
||||
- `info` (default)
|
||||
|
|
@ -51,9 +60,11 @@ app.use(requestLogger)
|
|||
- `silly`
|
||||
|
||||
## Log file
|
||||
|
||||
- Log files are stored in logs folder of current working directory. Error logs are stored in files with format `error-%DATE%.log` and combined logs are stored with format `combined-%DATE%.log`.
|
||||
- Log files have a 7 day rotation period defined.
|
||||
|
||||
## Configuration
|
||||
|
||||
- By default, the log level is set to `info`.
|
||||
- You can specify a log level by adding a LOG_LEVEL in .env.
|
||||
|
|
@ -23,8 +23,7 @@ export const CustomYAxisTick = React.memo<any>(({ x, y, payload }: any) => (
|
|||
|
||||
CustomYAxisTick.displayName = "CustomYAxisTick";
|
||||
|
||||
export const CustomRadarAxisTick = React.memo<any>(
|
||||
({ x, y, payload, getLabel, cx, cy, offset = 16 }: any) => {
|
||||
export const CustomRadarAxisTick = React.memo<any>(({ x, y, payload, getLabel, cx, cy, offset = 16 }: any) => {
|
||||
// Calculate direction vector from center to tick
|
||||
const dx = x - cx;
|
||||
const dy = y - cy;
|
||||
|
|
@ -42,6 +41,5 @@ export const CustomRadarAxisTick = React.memo<any>(
|
|||
</text>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
CustomRadarAxisTick.displayName = "CustomRadarAxisTick";
|
||||
|
|
|
|||
|
|
@ -21,10 +21,7 @@ export class FileUploadService extends APIService {
|
|||
* @returns {Promise<void>} Promise resolving to void
|
||||
* @throws {Error} If the request fails
|
||||
*/
|
||||
async uploadFile(
|
||||
url: string,
|
||||
data: FormData,
|
||||
): Promise<void> {
|
||||
async uploadFile(url: string, data: FormData): Promise<void> {
|
||||
this.cancelSource = axios.CancelToken.source();
|
||||
return this.post(url, data, {
|
||||
headers: {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"extends": "@plane/typescript-config/react-library.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"lib": ["esnext", "dom"],
|
||||
"lib": ["esnext", "dom"]
|
||||
},
|
||||
"include": ["./src"],
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
export type TBaseActivity<
|
||||
TFieldKey extends string = string,
|
||||
TVerbKey extends string = string,
|
||||
> = {
|
||||
export type TBaseActivity<TFieldKey extends string = string, TVerbKey extends string = string> = {
|
||||
id: string;
|
||||
field: TFieldKey | undefined;
|
||||
epoch: number;
|
||||
|
|
@ -19,17 +16,14 @@ export type TBaseActivity<
|
|||
updated_at: string;
|
||||
};
|
||||
|
||||
export type TWorkspaceBaseActivity<
|
||||
K extends string = string,
|
||||
V extends string = string,
|
||||
> = TBaseActivity<K, V> & {
|
||||
export type TWorkspaceBaseActivity<K extends string = string, V extends string = string> = TBaseActivity<K, V> & {
|
||||
workspace: string;
|
||||
};
|
||||
|
||||
export type TProjectBaseActivity<
|
||||
K extends string = string,
|
||||
V extends string = string,
|
||||
> = TWorkspaceBaseActivity<K, V> & {
|
||||
export type TProjectBaseActivity<K extends string = string, V extends string = string> = TWorkspaceBaseActivity<
|
||||
K,
|
||||
V
|
||||
> & {
|
||||
project: string;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ export enum ChartYAxisMetric {
|
|||
BLOCKED_WORK_ITEM_COUNT = "BLOCKED_WORK_ITEM_COUNT",
|
||||
}
|
||||
|
||||
|
||||
export type TAnalyticsTabsBase = "overview" | "work-items";
|
||||
export type TAnalyticsGraphsBase = "projects" | "work-items" | "custom-work-items";
|
||||
export interface AnalyticsTab {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
export type TChartColorScheme = "modern" | "horizon" | "earthen";
|
||||
|
||||
export type TChartDatum = {
|
||||
|
|
@ -12,5 +10,3 @@ export type TChart = {
|
|||
data: TChartDatum[];
|
||||
schema: Record<string, string>;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
export type TCommandPaletteActionList = Record<
|
||||
string,
|
||||
{ title: string; description: string; action: () => void }
|
||||
>;
|
||||
export type TCommandPaletteActionList = Record<string, { title: string; description: string; action: () => void }>;
|
||||
|
||||
export type TCommandPaletteShortcutList = {
|
||||
key: string;
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ export type TInstanceAuthenticationMethodKeys =
|
|||
| "IS_GITHUB_ENABLED"
|
||||
| "IS_GITLAB_ENABLED";
|
||||
|
||||
export type TInstanceGoogleAuthenticationConfigurationKeys =
|
||||
| "GOOGLE_CLIENT_ID"
|
||||
| "GOOGLE_CLIENT_SECRET";
|
||||
export type TInstanceGoogleAuthenticationConfigurationKeys = "GOOGLE_CLIENT_ID" | "GOOGLE_CLIENT_SECRET";
|
||||
|
||||
export type TInstanceGithubAuthenticationConfigurationKeys =
|
||||
| "GITHUB_CLIENT_ID"
|
||||
|
|
@ -34,9 +32,7 @@ export type TInstanceAuthenticationConfigurationKeys =
|
|||
| TInstanceGithubAuthenticationConfigurationKeys
|
||||
| TInstanceGitlabAuthenticationConfigurationKeys;
|
||||
|
||||
export type TInstanceAuthenticationKeys =
|
||||
| TInstanceAuthenticationMethodKeys
|
||||
| TInstanceAuthenticationConfigurationKeys;
|
||||
export type TInstanceAuthenticationKeys = TInstanceAuthenticationMethodKeys | TInstanceAuthenticationConfigurationKeys;
|
||||
|
||||
export type TGetBaseAuthenticationModeProps = {
|
||||
disabled: boolean;
|
||||
|
|
|
|||
|
|
@ -7,13 +7,7 @@ export * from "./issue_relation";
|
|||
export * from "./issue_sub_issues";
|
||||
export * from "./activity/base";
|
||||
|
||||
|
||||
export type TLoader =
|
||||
| "init-loader"
|
||||
| "mutation"
|
||||
| "pagination"
|
||||
| "loaded"
|
||||
| undefined;
|
||||
export type TLoader = "init-loader" | "mutation" | "pagination" | "loaded" | undefined;
|
||||
|
||||
export type TGroupedIssues = {
|
||||
[group_id: string]: string[];
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export type TIssueAttachment = {
|
|||
};
|
||||
|
||||
export type TIssueAttachmentUploadResponse = TFileSignedURLResponse & {
|
||||
attachment: TIssueAttachment
|
||||
attachment: TIssueAttachment;
|
||||
};
|
||||
|
||||
export type TIssueAttachmentMap = {
|
||||
|
|
|
|||
|
|
@ -2,13 +2,7 @@ import type { TIssue } from "../issues/issue";
|
|||
import type { IIssueFilterOptions } from "../view-props";
|
||||
import type { ILinkDetails } from "../issues";
|
||||
|
||||
export type TModuleStatus =
|
||||
| "backlog"
|
||||
| "planned"
|
||||
| "in-progress"
|
||||
| "paused"
|
||||
| "completed"
|
||||
| "cancelled";
|
||||
export type TModuleStatus = "backlog" | "planned" | "in-progress" | "paused" | "completed" | "cancelled";
|
||||
|
||||
export type TModuleCompletionChartDistribution = {
|
||||
[key: string]: number | null;
|
||||
|
|
@ -114,9 +108,7 @@ export type ModuleLink = {
|
|||
url: string;
|
||||
};
|
||||
|
||||
export type SelectModuleType =
|
||||
| (IModule & { actionType: "edit" | "delete" | "create-issue" })
|
||||
| undefined;
|
||||
export type SelectModuleType = (IModule & { actionType: "edit" | "delete" | "create-issue" }) | undefined;
|
||||
|
||||
export type TModulePlotType = "burndown" | "points";
|
||||
|
||||
|
|
|
|||
|
|
@ -26,9 +26,4 @@ export interface IPragmaticDropPayload {
|
|||
self: TDropTarget & TDropTargetMiscellaneousData;
|
||||
}
|
||||
|
||||
export type InstructionType =
|
||||
| "reparent"
|
||||
| "reorder-above"
|
||||
| "reorder-below"
|
||||
| "make-child"
|
||||
| "instruction-blocked";
|
||||
export type InstructionType = "reparent" | "reorder-above" | "reorder-below" | "make-child" | "instruction-blocked";
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue