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 []
|
||||
|
|
|
|||
|
|
@ -148,8 +148,8 @@ class ProjectMemberAdminSerializer(BaseSerializer):
|
|||
fields = "__all__"
|
||||
|
||||
|
||||
class ProjectMemberRoleSerializer(DynamicBaseSerializer):
|
||||
original_role = serializers.IntegerField(source='role', read_only=True)
|
||||
class ProjectMemberRoleSerializer(DynamicBaseSerializer):
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class Command(BaseCommand):
|
|||
project_count = int(input("Number of projects to be created: "))
|
||||
|
||||
for i in range(project_count):
|
||||
print(f"Please provide the following details for project {i+1}:")
|
||||
print(f"Please provide the following details for project {i + 1}:")
|
||||
issue_count = int(input("Number of issues to be created: "))
|
||||
cycle_count = int(input("Number of cycles to be created: "))
|
||||
module_count = int(input("Number of modules to be created: "))
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ def workspace(create_user):
|
|||
)
|
||||
|
||||
WorkspaceMember.objects.create(
|
||||
workspace=created_workspace, member=create_user, role=20
|
||||
)
|
||||
|
||||
workspace=created_workspace, member=create_user, role=20
|
||||
)
|
||||
|
||||
return created_workspace
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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
|
||||
|
|
@ -456,4 +466,4 @@ class TestMagicSignUp:
|
|||
assert User.objects.filter(email=email).exists()
|
||||
|
||||
# Check if user is authenticated
|
||||
assert "_auth_user_id" in django_client.session
|
||||
assert "_auth_user_id" in django_client.session
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
@ -76,4 +76,4 @@ class TestWorkspaceAPI:
|
|||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
# Optionally check the error message to confirm it's related to the duplicate slug
|
||||
assert "slug" in response.data
|
||||
assert "slug" in response.data
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -79,4 +78,4 @@ class ProjectMemberFactory(factory.django.DjangoModelFactory):
|
|||
member = factory.SubFactory(UserFactory)
|
||||
role = 20 # Admin role by default
|
||||
created_at = factory.LazyFunction(timezone.now)
|
||||
updated_at = factory.LazyFunction(timezone.now)
|
||||
updated_at = factory.LazyFunction(timezone.now)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -97,4 +104,4 @@ class TestHealthCheckSmoke:
|
|||
response = requests.get(f"{plane_server.url}/")
|
||||
|
||||
# Should be OK
|
||||
assert response.status_code == 200, "Health check endpoint should return 200 OK"
|
||||
assert response.status_code == 200, "Health check endpoint should return 200 OK"
|
||||
|
|
|
|||
|
|
@ -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,21 +27,18 @@ 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
|
||||
assert workspace_member.id is not None
|
||||
assert workspace_member.workspace == workspace
|
||||
assert workspace_member.member == create_user
|
||||
assert workspace_member.role == 20
|
||||
assert workspace_member.role == 20
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -68,4 +57,4 @@ class TestWorkspaceLiteSerializer:
|
|||
# Save should not update the read-only fields
|
||||
updated_workspace = serializer.save()
|
||||
assert updated_workspace.name == "Test Workspace"
|
||||
assert updated_workspace.slug == "test-workspace"
|
||||
assert updated_workspace.slug == "test-workspace"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -50,11 +50,11 @@ def paginate(base_queryset, queryset, cursor, on_result):
|
|||
paginated_data = queryset[start_index:end_index]
|
||||
|
||||
# Create the pagination info object
|
||||
prev_cursor = f"{page_size}:{cursor_object.current_page-1}:0"
|
||||
prev_cursor = f"{page_size}:{cursor_object.current_page - 1}:0"
|
||||
cursor = f"{page_size}:{cursor_object.current_page}:0"
|
||||
next_cursor = None
|
||||
if end_index < total_results:
|
||||
next_cursor = f"{page_size}:{cursor_object.current_page+1}:0"
|
||||
next_cursor = f"{page_size}:{cursor_object.current_page + 1}:0"
|
||||
|
||||
prev_page_results = False
|
||||
if cursor_object.current_page > 0:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -71,10 +55,10 @@ def main():
|
|||
|
||||
# Print command
|
||||
print(f"Running: {' '.join(cmd)}")
|
||||
|
||||
|
||||
# Execute command
|
||||
result = subprocess.run(cmd)
|
||||
|
||||
|
||||
# Check coverage thresholds if coverage is enabled
|
||||
if args.coverage:
|
||||
print("Checking coverage thresholds...")
|
||||
|
|
@ -83,9 +67,9 @@ def main():
|
|||
if coverage_result.returncode != 0:
|
||||
print("Coverage below threshold (90%)")
|
||||
sys.exit(coverage_result.returncode)
|
||||
|
||||
|
||||
sys.exit(result.returncode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -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}/`,
|
||||
{
|
||||
headers: {
|
||||
Cookie: cookie,
|
||||
},
|
||||
}
|
||||
)
|
||||
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/`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/octet-stream",
|
||||
Cookie: cookie,
|
||||
},
|
||||
responseType: "arraybuffer",
|
||||
}
|
||||
)
|
||||
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,
|
||||
{
|
||||
headers: {
|
||||
Cookie: cookie,
|
||||
},
|
||||
}
|
||||
)
|
||||
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>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -28,4 +28,4 @@ const ProjectArchivedCyclesPage = observer(() => {
|
|||
);
|
||||
});
|
||||
|
||||
export default ProjectArchivedCyclesPage;
|
||||
export default ProjectArchivedCyclesPage;
|
||||
|
|
|
|||
|
|
@ -28,4 +28,4 @@ const ProjectArchivedIssuesPage = observer(() => {
|
|||
);
|
||||
});
|
||||
|
||||
export default ProjectArchivedIssuesPage;
|
||||
export default ProjectArchivedIssuesPage;
|
||||
|
|
|
|||
|
|
@ -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}</>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export * from "./subscription-pill";
|
||||
export * from "./subscription-pill";
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export const ProductUpdatesHeader = observer(() => {
|
|||
<div className="flex gap-2 mx-6 my-4 items-center justify-between flex-shrink-0">
|
||||
<div className="flex w-full items-center">
|
||||
<div className="flex gap-2 text-xl font-medium">{t("whats_new")}</div>
|
||||
<div
|
||||
<div
|
||||
className={cn(
|
||||
"px-2 mx-2 py-0.5 text-center text-xs font-medium rounded-full bg-custom-primary-100/20 text-custom-primary-100"
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export * from "./root";
|
||||
export * from "./root";
|
||||
|
|
|
|||
|
|
@ -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 `;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
export type TRenderSettingsLink = (workspaceSlug: string, settingKey: string) => boolean;
|
||||
export const shouldRenderSettingLink: TRenderSettingsLink = (workspaceSlug, settingKey) => true;
|
||||
export const shouldRenderSettingLink: TRenderSettingsLink = (workspaceSlug, settingKey) => true;
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
export * from "./estimate.service";
|
||||
export * from "./view.service";
|
||||
export * from "./view.service";
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ import { IPartialProject, IProject } from "@plane/types";
|
|||
|
||||
export type TPartialProject = IPartialProject;
|
||||
|
||||
export type TProject = TPartialProject & IProject;
|
||||
export type TProject = TPartialProject & IProject;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -47,11 +47,11 @@ export const ProjectSelect: React.FC<Props> = observer((props) => {
|
|||
{value && value.length > 3
|
||||
? `3+ projects`
|
||||
: value && value.length > 0
|
||||
? projectIds
|
||||
?.filter((p) => value.includes(p))
|
||||
.map((p) => getProjectById(p)?.name)
|
||||
.join(", ")
|
||||
: "All projects"}
|
||||
? projectIds
|
||||
?.filter((p) => value.includes(p))
|
||||
.map((p) => getProjectById(p)?.name)
|
||||
.join(", ")
|
||||
: "All projects"}
|
||||
</div>
|
||||
}
|
||||
multiple
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -251,4 +251,4 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
|
|||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -60,4 +60,4 @@ export const ApiTokenListItem: React.FC<Props> = (props) => {
|
|||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
|
|||
EUserPermissionsLevel.PROJECT,
|
||||
workspaceSlug?.toString(),
|
||||
currentProjectDetails?.id
|
||||
);
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -163,4 +169,4 @@ export const generateExtendedColors = (baseColorSet: string[], targetCount: numb
|
|||
}
|
||||
|
||||
return colors.slice(0, targetCount);
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,51 +13,51 @@ export const ProductUpdatesFooter = () => {
|
|||
<div className="flex items-center justify-between flex-shrink-0 gap-4 m-6 mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<a
|
||||
href="https://go.plane.so/p-docs"
|
||||
target="_blank"
|
||||
className="text-sm text-custom-text-200 hover:text-custom-text-100 hover:underline underline-offset-1 outline-none"
|
||||
href="https://go.plane.so/p-docs"
|
||||
target="_blank"
|
||||
className="text-sm text-custom-text-200 hover:text-custom-text-100 hover:underline underline-offset-1 outline-none"
|
||||
>
|
||||
{t("docs")}
|
||||
</a>
|
||||
<svg viewBox="0 0 2 2" className="h-0.5 w-0.5 fill-current">
|
||||
<circle cx={1} cy={1} r={1} />
|
||||
</svg>
|
||||
<a
|
||||
href="https://go.plane.so/p-changelog"
|
||||
target="_blank"
|
||||
className="text-sm text-custom-text-200 hover:text-custom-text-100 hover:underline underline-offset-1 outline-none"
|
||||
</svg>
|
||||
<a
|
||||
href="https://go.plane.so/p-changelog"
|
||||
target="_blank"
|
||||
className="text-sm text-custom-text-200 hover:text-custom-text-100 hover:underline underline-offset-1 outline-none"
|
||||
>
|
||||
{t("full_changelog")}
|
||||
</a>
|
||||
<svg viewBox="0 0 2 2" className="h-0.5 w-0.5 fill-current">
|
||||
<circle cx={1} cy={1} r={1} />
|
||||
</svg>
|
||||
<a
|
||||
href="mailto:support@plane.so"
|
||||
target="_blank"
|
||||
className="text-sm text-custom-text-200 hover:text-custom-text-100 hover:underline underline-offset-1 outline-none"
|
||||
</svg>
|
||||
<a
|
||||
href="mailto:support@plane.so"
|
||||
target="_blank"
|
||||
className="text-sm text-custom-text-200 hover:text-custom-text-100 hover:underline underline-offset-1 outline-none"
|
||||
>
|
||||
{t("support")}
|
||||
</a>
|
||||
<svg viewBox="0 0 2 2" className="h-0.5 w-0.5 fill-current">
|
||||
<circle cx={1} cy={1} r={1} />
|
||||
</svg>
|
||||
</svg>
|
||||
<a
|
||||
href="https://go.plane.so/p-discord"
|
||||
target="_blank"
|
||||
className="text-sm text-custom-text-200 hover:text-custom-text-100 hover:underline underline-offset-1 outline-none"
|
||||
>
|
||||
Discord
|
||||
</a>
|
||||
</div>
|
||||
<a
|
||||
href="https://go.plane.so/p-discord"
|
||||
href="https://plane.so/pages"
|
||||
target="_blank"
|
||||
className="text-sm text-custom-text-200 hover:text-custom-text-100 hover:underline underline-offset-1 outline-none"
|
||||
className={cn(
|
||||
getButtonStyling("accent-primary", "sm"),
|
||||
"flex gap-1.5 items-center text-center font-medium hover:underline underline-offset-2 outline-none"
|
||||
)}
|
||||
>
|
||||
Discord
|
||||
</a>
|
||||
</div>
|
||||
<a
|
||||
href="https://plane.so/pages"
|
||||
target="_blank"
|
||||
className={cn(
|
||||
getButtonStyling("accent-primary", "sm"),
|
||||
"flex gap-1.5 items-center text-center font-medium hover:underline underline-offset-2 outline-none"
|
||||
)}
|
||||
>
|
||||
<Image src={PlaneLogo} alt="Plane" width={12} height={12} />
|
||||
{t("powered_by_plane_pages")}
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -58,11 +58,10 @@ export const getCanDrop = (source: TDropTarget, favorite: IFavorite | undefined,
|
|||
if (!sourceData) return false;
|
||||
|
||||
// a favorite cannot be dropped on to itself
|
||||
if (sourceData.id === favorite?.id ) return false;
|
||||
|
||||
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;
|
||||
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ export * from "./favorite-folder";
|
|||
export * from "./favorite-items";
|
||||
export * from "./favorites-menu";
|
||||
export * from "./favorites.helpers";
|
||||
export * from "./new-fav-folder";
|
||||
export * from "./new-fav-folder";
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -59,4 +59,4 @@ declare var _modRename;
|
|||
|
||||
declare var _jsAuth;
|
||||
|
||||
declare var _jsProgress;
|
||||
declare var _jsProgress;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* each element converted to a byte); SQLite always returns blob data as
|
||||
* `Uint8Array`
|
||||
*/
|
||||
type SQLiteCompatibleType = number|string|Uint8Array|Array<number>|bigint|null;
|
||||
type SQLiteCompatibleType = number | string | Uint8Array | Array<number> | bigint | null;
|
||||
|
||||
/**
|
||||
* SQLite Virtual File System object
|
||||
|
|
@ -37,83 +37,50 @@ declare interface SQLiteVFS {
|
|||
/** Maximum length of a file path in UTF-8 bytes (default 64) */
|
||||
mxPathName?: number;
|
||||
|
||||
close(): void|Promise<void>;
|
||||
isReady(): boolean|Promise<boolean>;
|
||||
close(): void | Promise<void>;
|
||||
isReady(): boolean | Promise<boolean>;
|
||||
|
||||
/** @see https://sqlite.org/c3ref/io_methods.html */
|
||||
xClose(fileId: number): number|Promise<number>;
|
||||
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>;
|
||||
xTruncate(fileId: number, iSizeLo: number, iSizeHi): number | Promise<number>;
|
||||
|
||||
/** @see https://sqlite.org/c3ref/io_methods.html */
|
||||
xSync(fileId: number, flags: number): number|Promise<number>;
|
||||
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>;
|
||||
xLock(fileId: number, flags: number): number | Promise<number>;
|
||||
|
||||
/** @see https://sqlite.org/c3ref/io_methods.html */
|
||||
xUnlock(fileId: number, flags: number): number|Promise<number>;
|
||||
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>;
|
||||
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>;
|
||||
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>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -217,7 +184,7 @@ declare interface SQLiteAPI {
|
|||
*/
|
||||
bind_collection(
|
||||
stmt: number,
|
||||
bindings: {[index: string]: SQLiteCompatibleType|null}|Array<SQLiteCompatibleType|null>
|
||||
bindings: { [index: string]: SQLiteCompatibleType | null } | Array<SQLiteCompatibleType | null>
|
||||
): number;
|
||||
|
||||
/**
|
||||
|
|
@ -230,7 +197,7 @@ declare interface SQLiteAPI {
|
|||
* @param value
|
||||
* @returns `SQLITE_OK` (throws exception on error)
|
||||
*/
|
||||
bind(stmt: number, i: number, value: SQLiteCompatibleType|null): number;
|
||||
bind(stmt: number, i: number, value: SQLiteCompatibleType | null): number;
|
||||
|
||||
/**
|
||||
* Bind blob to prepared statement parameter
|
||||
|
|
@ -242,7 +209,7 @@ declare interface SQLiteAPI {
|
|||
* @param value
|
||||
* @returns `SQLITE_OK` (throws exception on error)
|
||||
*/
|
||||
bind_blob(stmt: number, i: number, value: Uint8Array|Array<number>): number;
|
||||
bind_blob(stmt: number, i: number, value: Uint8Array | Array<number>): number;
|
||||
|
||||
/**
|
||||
* Bind number to prepared statement parameter
|
||||
|
|
@ -254,9 +221,9 @@ declare interface SQLiteAPI {
|
|||
* @param value
|
||||
* @returns `SQLITE_OK` (throws exception on error)
|
||||
*/
|
||||
bind_double(stmt: number, i: number, value: number): number;
|
||||
bind_double(stmt: number, i: number, value: number): number;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Bind number to prepared statement parameter
|
||||
*
|
||||
* Note that binding indices begin with 1.
|
||||
|
|
@ -268,7 +235,7 @@ declare interface SQLiteAPI {
|
|||
*/
|
||||
bind_int(stmt: number, i: number, value: number): number;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Bind number to prepared statement parameter
|
||||
*
|
||||
* Note that binding indices begin with 1.
|
||||
|
|
@ -278,9 +245,9 @@ declare interface SQLiteAPI {
|
|||
* @param value
|
||||
* @returns `SQLITE_OK` (throws exception on error)
|
||||
*/
|
||||
bind_int64(stmt: number, i: number, value: bigint): number;
|
||||
bind_int64(stmt: number, i: number, value: bigint): number;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Bind null to prepared statement
|
||||
*
|
||||
* Note that binding indices begin with 1.
|
||||
|
|
@ -310,7 +277,7 @@ declare interface SQLiteAPI {
|
|||
*/
|
||||
bind_parameter_name(stmt: number, i: number): string;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Bind string to prepared statement
|
||||
*
|
||||
* Note that binding indices begin with 1.
|
||||
|
|
@ -419,7 +386,7 @@ declare interface SQLiteAPI {
|
|||
*/
|
||||
column_int64(stmt: number, i: number): bigint;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Get a column name for a prepared statement
|
||||
* @see https://www.sqlite.org/c3ref/column_blob.html
|
||||
* @param stmt prepared statement pointer
|
||||
|
|
@ -482,9 +449,10 @@ declare interface SQLiteAPI {
|
|||
nArg: number,
|
||||
eTextRep: number,
|
||||
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;
|
||||
xFunc?: (context: number, values: Uint32Array) => void | Promise<void>,
|
||||
xStep?: (context: number, values: Uint32Array) => void | Promise<void>,
|
||||
xFinal?: (context: number) => void | Promise<void>
|
||||
): number;
|
||||
|
||||
/**
|
||||
* Get number of columns in current row of a prepared statement
|
||||
|
|
@ -509,7 +477,7 @@ declare interface SQLiteAPI {
|
|||
exec(
|
||||
db: number,
|
||||
zSQL: string,
|
||||
callback?: (row: Array<SQLiteCompatibleType|null>, columns: string[]) => void
|
||||
callback?: (row: Array<SQLiteCompatibleType | null>, columns: string[]) => void
|
||||
): Promise<number>;
|
||||
|
||||
/**
|
||||
|
|
@ -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
|
||||
|
|
@ -588,7 +549,7 @@ declare interface SQLiteAPI {
|
|||
* @param handler
|
||||
* @param userData
|
||||
*/
|
||||
progress_handler(db: number, nProgressOps: number, handler: (userData: any) => number|Promise<number>, userData);
|
||||
progress_handler(db: number, nProgressOps: number, handler: (userData: any) => number | Promise<number>, userData);
|
||||
|
||||
/**
|
||||
* Reset a prepared statement object
|
||||
|
|
@ -603,7 +564,7 @@ declare interface SQLiteAPI {
|
|||
* @param context context pointer
|
||||
* @param value
|
||||
*/
|
||||
result(context: number, value: (SQLiteCompatibleType|number[])|null): void;
|
||||
result(context: number, value: (SQLiteCompatibleType | number[]) | null): void;
|
||||
|
||||
/**
|
||||
* Set the result of a function or vtable column
|
||||
|
|
@ -611,7 +572,7 @@ declare interface SQLiteAPI {
|
|||
* @param context context pointer
|
||||
* @param value
|
||||
*/
|
||||
result_blob(context: number, value: Uint8Array|number[]): void;
|
||||
result_blob(context: number, value: Uint8Array | number[]): void;
|
||||
|
||||
/**
|
||||
* Set the result of a function or vtable column
|
||||
|
|
@ -650,19 +611,19 @@ declare interface SQLiteAPI {
|
|||
* @param context context pointer
|
||||
* @param value
|
||||
*/
|
||||
result_text(context: number, value: string): void;
|
||||
result_text(context: number, value: string): void;
|
||||
|
||||
/**
|
||||
* Get all column data for a row from a prepared statement step
|
||||
*
|
||||
* This convenience function will return a copy of any blob, unlike
|
||||
* {@link column_blob} which returns a value referencing volatile WASM
|
||||
* memory with short validity. Like {@link column}, it will return a
|
||||
* BigInt for integers outside the safe integer bounds for Number.
|
||||
* @param stmt prepared statement pointer
|
||||
* @returns row data
|
||||
*/
|
||||
row(stmt: number): Array<SQLiteCompatibleType|null>;
|
||||
/**
|
||||
* Get all column data for a row from a prepared statement step
|
||||
*
|
||||
* This convenience function will return a copy of any blob, unlike
|
||||
* {@link column_blob} which returns a value referencing volatile WASM
|
||||
* memory with short validity. Like {@link column}, it will return a
|
||||
* BigInt for integers outside the safe integer bounds for Number.
|
||||
* @param stmt prepared statement pointer
|
||||
* @returns row data
|
||||
*/
|
||||
row(stmt: number): Array<SQLiteCompatibleType | null>;
|
||||
|
||||
/**
|
||||
* Register a callback function that is invoked to authorize certain SQL statement actions.
|
||||
|
|
@ -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
|
||||
|
|
@ -733,7 +702,7 @@ declare interface SQLiteAPI {
|
|||
*/
|
||||
step(stmt: number): Promise<number>;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Register an update hook
|
||||
*
|
||||
* The callback is invoked whenever a row is updated, inserted, or deleted
|
||||
|
|
@ -749,9 +718,10 @@ declare interface SQLiteAPI {
|
|||
* @param db database pointer
|
||||
* @param callback
|
||||
*/
|
||||
update_hook(
|
||||
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`
|
||||
|
|
@ -809,7 +779,7 @@ declare interface SQLiteAPI {
|
|||
* @param pValue `sqlite3_value` pointer
|
||||
* @returns value
|
||||
*/
|
||||
value_int64(pValue: number): bigint;
|
||||
value_int64(pValue: number): bigint;
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
@ -1088,26 +1058,26 @@ declare module 'wa-sqlite' {
|
|||
export function Factory(Module: any): SQLiteAPI;
|
||||
|
||||
export class SQLiteError extends Error {
|
||||
constructor(message: any, code: any);
|
||||
code: any;
|
||||
constructor(message: any, code: any);
|
||||
code: any;
|
||||
}
|
||||
}
|
||||
|
||||
/** @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";
|
||||
|
|
|
|||
|
|
@ -830,7 +830,7 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
|
|||
updates: { id: string; start_date?: string; target_date?: string }[],
|
||||
projectId?: string
|
||||
) {
|
||||
if(!projectId) return;
|
||||
if (!projectId) return;
|
||||
const issueDatesBeforeChange: { id: string; start_date?: string; target_date?: string }[] = [];
|
||||
try {
|
||||
const getIssueById = this.rootIssueStore.issues.getIssueById;
|
||||
|
|
|
|||
|
|
@ -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/active-cycles/workspace-active-cycles-upgrade";
|
||||
export * from "ce/components/active-cycles/workspace-active-cycles-upgrade";
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export * from "ce/components/breadcrumbs";
|
||||
export * from "ce/components/breadcrumbs";
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export * from "ce/components/issues/bulk-operations/index";
|
||||
export * from "ce/components/issues/bulk-operations/index";
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export * from "ce/components/issues/issue-layouts/quick-action-dropdowns"
|
||||
export * from "ce/components/issues/issue-layouts/quick-action-dropdowns";
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export * from "ce/components/sidebar";
|
||||
export * from "ce/components/sidebar";
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export * from "./root";
|
||||
export * from "./root";
|
||||
|
|
|
|||
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",
|
||||
|
|
@ -39,88 +37,88 @@ export const CHART_COLOR_PALETTES: {
|
|||
light: string[];
|
||||
dark: string[];
|
||||
}[] = [
|
||||
{
|
||||
key: "modern",
|
||||
i18n_label: "dashboards.widget.color_palettes.modern",
|
||||
light: [
|
||||
"#6172E8",
|
||||
"#8B6EDB",
|
||||
"#E05F99",
|
||||
"#29A383",
|
||||
"#CB8A37",
|
||||
"#3AA7C1",
|
||||
"#F1B24A",
|
||||
"#E84855",
|
||||
"#50C799",
|
||||
"#B35F9E",
|
||||
],
|
||||
dark: [
|
||||
"#6B7CDE",
|
||||
"#8E9DE6",
|
||||
"#D45D9E",
|
||||
"#2EAF85",
|
||||
"#D4A246",
|
||||
"#29A7C1",
|
||||
"#B89F6A",
|
||||
"#D15D64",
|
||||
"#4ED079",
|
||||
"#A169A4",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "horizon",
|
||||
i18n_label: "dashboards.widget.color_palettes.horizon",
|
||||
light: [
|
||||
"#E76E50",
|
||||
"#289D90",
|
||||
"#F3A362",
|
||||
"#E9C368",
|
||||
"#264753",
|
||||
"#8A6FA0",
|
||||
"#5B9EE5",
|
||||
"#7CC474",
|
||||
"#BA7DB5",
|
||||
"#CF8640",
|
||||
],
|
||||
dark: [
|
||||
"#E05A3A",
|
||||
"#1D8A7E",
|
||||
"#D98B4D",
|
||||
"#D1AC50",
|
||||
"#3A6B7C",
|
||||
"#7D6297",
|
||||
"#4D8ACD",
|
||||
"#569C64",
|
||||
"#C16A8C",
|
||||
"#B77436",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "earthen",
|
||||
i18n_label: "dashboards.widget.color_palettes.earthen",
|
||||
light: [
|
||||
"#386641",
|
||||
"#6A994E",
|
||||
"#A7C957",
|
||||
"#E97F4E",
|
||||
"#BC4749",
|
||||
"#9E2A2B",
|
||||
"#80CED1",
|
||||
"#5C3E79",
|
||||
"#526EAB",
|
||||
"#6B5B95",
|
||||
],
|
||||
dark: [
|
||||
"#497752",
|
||||
"#7BAA5F",
|
||||
"#B8DA68",
|
||||
"#FA905F",
|
||||
"#CD585A",
|
||||
"#AF3B3C",
|
||||
"#91DFE2",
|
||||
"#6D4F8A",
|
||||
"#637FBC",
|
||||
"#7C6CA6",
|
||||
],
|
||||
},
|
||||
];
|
||||
{
|
||||
key: "modern",
|
||||
i18n_label: "dashboards.widget.color_palettes.modern",
|
||||
light: [
|
||||
"#6172E8",
|
||||
"#8B6EDB",
|
||||
"#E05F99",
|
||||
"#29A383",
|
||||
"#CB8A37",
|
||||
"#3AA7C1",
|
||||
"#F1B24A",
|
||||
"#E84855",
|
||||
"#50C799",
|
||||
"#B35F9E",
|
||||
],
|
||||
dark: [
|
||||
"#6B7CDE",
|
||||
"#8E9DE6",
|
||||
"#D45D9E",
|
||||
"#2EAF85",
|
||||
"#D4A246",
|
||||
"#29A7C1",
|
||||
"#B89F6A",
|
||||
"#D15D64",
|
||||
"#4ED079",
|
||||
"#A169A4",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "horizon",
|
||||
i18n_label: "dashboards.widget.color_palettes.horizon",
|
||||
light: [
|
||||
"#E76E50",
|
||||
"#289D90",
|
||||
"#F3A362",
|
||||
"#E9C368",
|
||||
"#264753",
|
||||
"#8A6FA0",
|
||||
"#5B9EE5",
|
||||
"#7CC474",
|
||||
"#BA7DB5",
|
||||
"#CF8640",
|
||||
],
|
||||
dark: [
|
||||
"#E05A3A",
|
||||
"#1D8A7E",
|
||||
"#D98B4D",
|
||||
"#D1AC50",
|
||||
"#3A6B7C",
|
||||
"#7D6297",
|
||||
"#4D8ACD",
|
||||
"#569C64",
|
||||
"#C16A8C",
|
||||
"#B77436",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "earthen",
|
||||
i18n_label: "dashboards.widget.color_palettes.earthen",
|
||||
light: [
|
||||
"#386641",
|
||||
"#6A994E",
|
||||
"#A7C957",
|
||||
"#E97F4E",
|
||||
"#BC4749",
|
||||
"#9E2A2B",
|
||||
"#80CED1",
|
||||
"#5C3E79",
|
||||
"#526EAB",
|
||||
"#6B5B95",
|
||||
],
|
||||
dark: [
|
||||
"#497752",
|
||||
"#7BAA5F",
|
||||
"#B8DA68",
|
||||
"#FA905F",
|
||||
"#CD585A",
|
||||
"#AF3B3C",
|
||||
"#91DFE2",
|
||||
"#6D4F8A",
|
||||
"#637FBC",
|
||||
"#7C6CA6",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
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