chore: format files using prettier (#7364)

* chore: format files using prettier

* chore: api server files formatted
This commit is contained in:
sriram veeraghanta 2025-07-08 20:41:11 +05:30 committed by GitHub
parent 0225d806cc
commit 6ce700fd5d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
149 changed files with 1518 additions and 919 deletions

View file

@ -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

View file

@ -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"]

View file

@ -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 []

View file

@ -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

View file

@ -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": (

View file

@ -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()]

View file

@ -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")),

View file

@ -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(

View file

@ -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: "))

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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
)

View file

@ -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:

View file

@ -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):

View file

@ -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()

View file

@ -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.`);
}
};

View file

@ -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.`);
}
};

View file

@ -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)"
);
}

View file

@ -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 }),
},
});

View file

@ -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,
};
};

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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;

View file

@ -1 +1 @@
export * from "../../ce/lib/fetch-document.js"
export * from "../../ce/lib/fetch-document.js";

View file

@ -1 +1 @@
export * from "../../ce/types/common.js"
export * from "../../ce/types/common.js";

View file

@ -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(",") };

View file

@ -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;

View file

@ -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>
);
})}

View file

@ -28,4 +28,4 @@ const ProjectArchivedCyclesPage = observer(() => {
);
});
export default ProjectArchivedCyclesPage;
export default ProjectArchivedCyclesPage;

View file

@ -28,4 +28,4 @@ const ProjectArchivedIssuesPage = observer(() => {
);
});
export default ProjectArchivedIssuesPage;
export default ProjectArchivedIssuesPage;

View file

@ -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";

View file

@ -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));
}, []);
};

View file

@ -5,7 +5,7 @@ export const metadata: Metadata = {
robots: {
index: true,
follow: false,
}
},
};
export default function SignUpLayout({ children }: { children: React.ReactNode }) {

View file

@ -15,7 +15,5 @@ export const viewport: Viewport = {
};
export default function HomeLayout({ children }: { children: React.ReactNode }) {
return (
<>{children}</>
);
return <>{children}</>;
}

View file

@ -1 +1 @@
export * from "./subscription-pill";
export * from "./subscription-pill";

View file

@ -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"
)}

View file

@ -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>
);
};

View file

@ -1 +1 @@
export * from "./root";
export * from "./root";

View file

@ -2,4 +2,3 @@ export * from "./provider";
export * from "./issue-type-select";
export * from "./additional-properties";
export * from "./template-select";

View file

@ -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 `;
}

View file

@ -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;

View file

@ -1,2 +1,2 @@
export * from "./estimate.service";
export * from "./view.service";
export * from "./view.service";

View file

@ -2,4 +2,4 @@ import { IPartialProject, IProject } from "@plane/types";
export type TPartialProject = IPartialProject;
export type TProject = TPartialProject & IProject;
export type TProject = TPartialProject & IProject;

View file

@ -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;

View file

@ -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

View file

@ -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: {

View file

@ -251,4 +251,4 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
</div>
</form>
);
};
};

View file

@ -60,4 +60,4 @@ export const ApiTokenListItem: React.FC<Props> = (props) => {
</div>
</>
);
};
};

View file

@ -39,7 +39,7 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
EUserPermissionsLevel.PROJECT,
workspaceSlug?.toString(),
currentProjectDetails?.id
);
);
return (
<>

View file

@ -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);
};
};

View file

@ -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>

View file

@ -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}

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -1,3 +1,2 @@
export * from "./blocks";
export * from "./base-gantt-root";

View file

@ -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";

View file

@ -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,

View file

@ -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>

View file

@ -43,7 +43,7 @@ export const WorkspaceDraftEmptyState: FC = observer(() => {
onClick: () => {
setIsDraftIssueModalOpen(true);
},
disabled: !canPerformEmptyStateActions
disabled: !canPerformEmptyStateActions,
}}
/>
</div>

View file

@ -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

View file

@ -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 />

View file

@ -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

View file

@ -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";

View file

@ -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;

View file

@ -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;
};
};

View file

@ -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";

View file

@ -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 ? (

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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>
);

View file

@ -59,4 +59,4 @@ declare var _modRename;
declare var _jsAuth;
declare var _jsProgress;
declare var _jsProgress;

View file

@ -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

View file

@ -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";

View file

@ -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;

View file

@ -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

View file

@ -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";

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -1 +1 @@
export * from "ce/components/active-cycles/workspace-active-cycles-upgrade";
export * from "ce/components/active-cycles/workspace-active-cycles-upgrade";

View file

@ -1 +1 @@
export * from "ce/components/breadcrumbs";
export * from "ce/components/breadcrumbs";

View file

@ -1 +1 @@
export * from "ce/components/issues/bulk-operations/index";
export * from "ce/components/issues/bulk-operations/index";

View file

@ -1 +1 @@
export * from "ce/components/issues/issue-layouts/quick-action-dropdowns"
export * from "ce/components/issues/issue-layouts/quick-action-dropdowns";

View file

@ -1 +1 @@
export * from "ce/components/sidebar";
export * from "ce/components/sidebar";

View file

@ -1 +1 @@
export * from "./root";
export * from "./root";

File diff suppressed because one or more lines are too long

View file

@ -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);

View file

@ -1 +1 @@
export * from "./common"
export * from "./common";

View file

@ -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",

View file

@ -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",
],
},
];

View file

@ -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