Compare commits

..
Author SHA1 Message Date
sriram veeraghantaandGitHub 002fb4547b Merge pull request #4107 from makeplane/preview
release: v0.17-dev
2024-04-02 20:07:48 +05:30
sriram veeraghanta 204e4a8c6d fix: nextjs config sentry 2024-04-02 16:45:18 +05:30
Prateek ShouryaandGitHub bd470f7a90 chore: add Get started button to redirect to /god-mode to set up instance. (#4105) 2024-04-02 14:05:12 +05:30
Anmol Singh BhatiaandGitHub 84e73291cc fix: draft issue title logic (#4104) 2024-04-01 17:53:40 +05:30
SamandGitHub 1423955a34 docs: fix incorrect Docker guide link (#4098) 2024-04-01 16:13:36 +05:30
Prateek ShouryaandGitHub a771bab64c fix: display error message in project settings page. (#4099)
* chore: update `project title` to `project name` in create project modal.
2024-04-01 13:48:12 +05:30
M. PalanikannanandGitHub fafdac68f0 [WEB-876] fix: Multiple editor instances cause weird issues with Tippy (#4092)
* fix: tippy rendered properly when there are multiple editor instances

* enhancement: using cn to merge classes conditionally
2024-04-01 12:04:01 +05:30
Anmol Singh BhatiaandGitHub 3a466cfe40 [WEB-838] fix: cycle dropdown fetch (#4076)
* fix: workspace issue properties fetch

* fix: issue modal cycle fetch issue

* fix: issue modal cycle fetch issue

* chore: project issue properties hook updated
2024-03-28 20:29:35 +05:30
Manish GuptaandGitHub 86715f5b20 Chore: self host enhancements (#4091)
* latest changed to stable and .env changed to plane.env

* env changes

* script fix

* env changes

* sed fix

* codacy proposed fixes
2024-03-28 15:53:37 +05:30
Prateek ShouryaandGitHub 97fb08928f [WEB-834] fix: close shortcut modle on outside click. (#4078) 2024-03-28 15:04:56 +05:30
Prateek ShouryaandGitHub 8bdf8fa24f [WEB-836] fix: color picker overlapping with themes dropdown issue. (#4079) 2024-03-28 15:04:48 +05:30
Prateek ShouryaandGitHub f1530688f8 [WEB-843] fix: chat with us option not woking in command k modal. (#4080) 2024-03-28 15:04:41 +05:30
Prateek ShouryaandGitHub 265ce7778a [WEB-840] chore: remove make a copy option from draft issue quick action menu. (#4081) 2024-03-28 15:04:26 +05:30
Prateek ShouryaandGitHub 79d227b906 [WEB-839] chore: limit project identifier to 5 letters in project settings page. (#4082) 2024-03-28 15:04:16 +05:30
Prateek ShouryaandGitHub 5d4b3cea4c [WEB-833] chore: remove create a new issue option from command k modal if no projects are there. (#4083) 2024-03-28 14:32:16 +05:30
Prateek ShouryaandGitHub 2c510a7cb9 [WEB-852] chore: remove Estimate Demand data from the anaylytics. (#4084) 2024-03-28 14:31:52 +05:30
Prateek ShouryaandGitHub 478535422b [WEB-851] chore: update all instances of Dashboard with Home. (#4085) 2024-03-28 14:31:28 +05:30
sriram veeraghanta 35ca495f40 chore: remove develop builds on push 2024-03-28 14:05:04 +05:30
sriram veeraghanta 68fa7a485b chore: apiserver version changes 2024-03-28 13:59:52 +05:30
sriram veeraghanta ecfd3b68e3 chore: package version upgrade 2024-03-28 13:31:45 +05:30
Anmol Singh BhatiaandGitHub 9249e6d5b9 [WEB-849] fix: issue detail identifier and workspace settings (#4073)
* fix: issue detail identifier

* fix: workspace settings user role validation
2024-03-26 20:38:54 +05:30
NikhilandGitHub 30cee78170 dev: fix api security error (#4072) 2024-03-26 20:38:25 +05:30
NikhilandGitHub 4c46b075b4 fix: module endpoint validations (#4071) 2024-03-26 19:48:52 +05:30
sriram veeraghanta f3d9053d81 fix: unsafe external link fixes 2024-03-26 19:43:22 +05:30
sriram veeraghanta a136931168 fix: codeql workflow push and pull request chanages 2024-03-26 19:40:48 +05:30
sriram veeraghanta 5235b78cb8 fix: sentry config fixes 2024-03-26 19:24:42 +05:30
Bavisetti NarayanandGitHub 1c3619a4d6 chore: workspace estimate project filter (#4070) 2024-03-26 16:59:23 +05:30
Anmol Singh BhatiaandGitHub f3fd48dd43 chore: image picker popover improvement (#4068) 2024-03-26 16:30:57 +05:30
sriram veeraghanta c6f36a056b fix: adding workflow dispatch to codeql 2024-03-26 15:55:47 +05:30
Anmol Singh BhatiaandGitHub 51683e6b2f fix: active cycle stats empty state (#4067) 2024-03-26 15:53:52 +05:30
NikhilandGitHub fb189ca447 fix: cycle issue subquery filter when adding multiple assignees (#4066) 2024-03-26 15:49:44 +05:30
ea728a385f [WEB-831] fix: sentry issue and code refactor (#4063)
* chore: unnecessary console log removed

* chore: in_use sentry issue resolved

* chore: detail sentry issue resolved

* fix: updated project logo validation in project icon

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>
2024-03-26 15:13:26 +05:30
NikhilandGitHub 7452e40134 fix: optimize cycles endpoint for sql (#4065) 2024-03-26 15:08:00 +05:30
Bavisetti NarayanandGitHub 599809dd2e chore: removed project from workspace estimate (#4064) 2024-03-26 14:59:28 +05:30
sriram veeraghantaandGitHub c1b1ba35c1 Merge pull request #3878 from makeplane/preview
release: v0.16-dev
2024-03-05 20:04:08 +05:30
sriram veeraghantaandGitHub 4566d6e80c Merge pull request #3697 from makeplane/preview
release: 0.15.4-dev
2024-02-19 19:30:06 +05:30
sriram veeraghantaandGitHub e8d359e625 Merge pull request #3674 from makeplane/preview
fix: build branch docker images push on release
2024-02-15 14:35:32 +05:30
sriram veeraghantaandGitHub 351eba8d61 Merge pull request #3671 from makeplane/preview
release: peek overview issue description initial load bug (#3670)
2024-02-15 03:25:30 +05:30
sriram veeraghantaandGitHub 1e27e37b51 Merge pull request #3666 from makeplane/preview
release: v0.15.2-dev
2024-02-14 19:41:55 +05:30
sriram veeraghantaandGitHub 7df2e9cf11 Merge pull request #3632 from makeplane/preview
release: v0.15.1-dev
2024-02-12 20:59:56 +05:30
sriram veeraghantaandGitHub c6e3f1b932 Merge pull request #3535 from makeplane/preview
release: 0.15-dev
2024-02-01 15:01:49 +05:30
71 changed files with 2445 additions and 1757 deletions
-1
View File
@@ -6,7 +6,6 @@ on:
branches:
- master
- preview
- develop
release:
types: [released, prereleased]
+2 -1
View File
@@ -1,8 +1,9 @@
name: "CodeQL"
on:
workflow_dispatch:
push:
branches: ["master"]
branches: ["develop", "preview", "master"]
pull_request:
branches: ["develop", "preview", "master"]
schedule:
+1 -1
View File
@@ -52,7 +52,7 @@ If you want more control over your data, prefer to self-host Plane, please refer
| Installation Methods | Documentation Link |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Docker | [![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white)](https://docs.plane.so/docker-compose) |
| Docker | [![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white)](https://docs.plane.so/self-hosting/methods/docker-compose) |
| Kubernetes | [![Kubernetes](https://img.shields.io/badge/kubernetes-%23326ce5.svg?style=for-the-badge&logo=kubernetes&logoColor=white)](https://docs.plane.so/kubernetes) |
`Instance admin` can configure instance settings using our [God-mode](https://docs.plane.so/instance-admin) feature.
+1 -1
View File
@@ -1,4 +1,4 @@
{
"name": "plane-api",
"version": "0.16.0"
"version": "0.17.0"
}
+16 -15
View File
@@ -1,32 +1,33 @@
from lxml import html
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
# Django imports
from django.utils import timezone
from django.core.validators import URLValidator
from django.core.exceptions import ValidationError
from lxml import html
# Third party imports
from rest_framework import serializers
# Module imports
from plane.db.models import (
User,
Issue,
State,
IssueActivity,
IssueAssignee,
Label,
IssueAttachment,
IssueComment,
IssueLabel,
IssueLink,
IssueComment,
IssueAttachment,
IssueActivity,
Label,
ProjectMember,
State,
User,
)
from .base import BaseSerializer
from .cycle import CycleSerializer, CycleLiteSerializer
from .module import ModuleSerializer, ModuleLiteSerializer
from .user import UserLiteSerializer
from .cycle import CycleLiteSerializer, CycleSerializer
from .module import ModuleLiteSerializer, ModuleSerializer
from .state import StateLiteSerializer
from .user import UserLiteSerializer
class IssueSerializer(BaseSerializer):
@@ -79,7 +80,7 @@ class IssueSerializer(BaseSerializer):
data["description_html"] = parsed_str
except Exception as e:
raise serializers.ValidationError(f"Invalid HTML: {str(e)}")
raise serializers.ValidationError("Invalid HTML passed")
# Validate assignees are from project
if data.get("assignees", []):
@@ -294,7 +295,7 @@ class IssueLinkSerializer(BaseSerializer):
raise serializers.ValidationError("Invalid URL format.")
# Check URL scheme
if not value.startswith(('http://', 'https://')):
if not value.startswith(("http://", "https://")):
raise serializers.ValidationError("Invalid URL scheme.")
return value
@@ -366,7 +367,7 @@ class IssueCommentSerializer(BaseSerializer):
data["comment_html"] = parsed_str
except Exception as e:
raise serializers.ValidationError(f"Invalid HTML: {str(e)}")
raise serializers.ValidationError("Invalid HTML passed")
return data
+16 -14
View File
@@ -1,31 +1,33 @@
from django.urls import path
from plane.app.views import (
ExportWorkspaceUserActivityEndpoint,
TeamMemberViewSet,
UserLastProjectWithWorkspaceEndpoint,
UserWorkspaceInvitationsViewSet,
WorkSpaceAvailabilityCheckEndpoint,
WorkspaceCyclesEndpoint,
WorkspaceEstimatesEndpoint,
WorkspaceInvitationsViewset,
WorkSpaceViewSet,
WorkspaceJoinEndpoint,
WorkspaceLabelsEndpoint,
WorkSpaceMemberViewSet,
WorkspaceInvitationsViewset,
WorkspaceMemberUserEndpoint,
WorkspaceMemberUserViewsEndpoint,
WorkSpaceMemberViewSet,
WorkspaceModulesEndpoint,
WorkspaceProjectMemberEndpoint,
WorkspaceStatesEndpoint,
WorkSpaceAvailabilityCheckEndpoint,
TeamMemberViewSet,
UserLastProjectWithWorkspaceEndpoint,
WorkspaceThemeViewSet,
WorkspaceUserProfileStatsEndpoint,
WorkspaceUserActivityEndpoint,
WorkspaceUserProfileEndpoint,
WorkspaceUserProfileIssuesEndpoint,
WorkspaceUserProfileStatsEndpoint,
WorkspaceLabelsEndpoint,
WorkspaceProjectMemberEndpoint,
WorkspaceUserPropertiesEndpoint,
WorkSpaceViewSet,
WorkspaceStatesEndpoint,
WorkspaceEstimatesEndpoint,
ExportWorkspaceUserActivityEndpoint,
WorkspaceModulesEndpoint,
WorkspaceCyclesEndpoint,
)
urlpatterns = [
path(
"workspace-slug-check/",
+1 -1
View File
@@ -38,7 +38,7 @@ from .workspace.base import (
WorkSpaceAvailabilityCheckEndpoint,
UserWorkspaceDashboardEndpoint,
WorkspaceThemeViewSet,
ExportWorkspaceUserActivityEndpoint,
ExportWorkspaceUserActivityEndpoint
)
from .workspace.member import (
+83 -111
View File
@@ -1,56 +1,56 @@
# Python imports
import json
# Django imports
from django.db.models import (
Func,
F,
Q,
Exists,
OuterRef,
Count,
Prefetch,
Case,
When,
Value,
CharField,
Subquery,
IntegerField,
)
from django.utils import timezone
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField
from django.db.models import UUIDField
# Django imports
from django.db.models import (
Case,
CharField,
Count,
Exists,
F,
Func,
OuterRef,
Prefetch,
Q,
UUIDField,
Value,
When,
)
from django.db.models.functions import Coalesce
from django.utils import timezone
from rest_framework import status
# Third party imports
from rest_framework.response import Response
from rest_framework import status
# Module imports
from .. import BaseViewSet, BaseAPIView, WebhookMixin
from plane.app.serializers import (
CycleSerializer,
CycleFavoriteSerializer,
CycleWriteSerializer,
CycleUserPropertiesSerializer,
)
from plane.app.permissions import (
ProjectEntityPermission,
ProjectLitePermission,
)
from plane.db.models import (
User,
Cycle,
CycleIssue,
Issue,
CycleFavorite,
Label,
CycleUserProperties,
from plane.app.serializers import (
CycleFavoriteSerializer,
CycleSerializer,
CycleUserPropertiesSerializer,
CycleWriteSerializer,
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import (
Cycle,
CycleFavorite,
CycleIssue,
CycleUserProperties,
Issue,
Label,
User,
)
from plane.utils.analytics_plot import burndown_plot
# Module imports
from .. import BaseAPIView, BaseViewSet, WebhookMixin
class CycleViewSet(WebhookMixin, BaseViewSet):
serializer_class = CycleSerializer
@@ -73,60 +73,6 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
cancelled_issues = (
Issue.issue_objects.filter(
state__group="cancelled",
issue_cycle__cycle_id=OuterRef("pk"),
)
.values("issue_cycle__cycle_id")
.annotate(cnt=Count("pk"))
.values("cnt")
)
completed_issues = (
Issue.issue_objects.filter(
state__group="completed",
issue_cycle__cycle_id=OuterRef("pk"),
)
.values("issue_cycle__cycle_id")
.annotate(cnt=Count("pk"))
.values("cnt")
)
started_issues = (
Issue.issue_objects.filter(
state__group="started",
issue_cycle__cycle_id=OuterRef("pk"),
)
.values("issue_cycle__cycle_id")
.annotate(cnt=Count("pk"))
.values("cnt")
)
unstarted_issues = (
Issue.issue_objects.filter(
state__group="unstarted",
issue_cycle__cycle_id=OuterRef("pk"),
)
.values("issue_cycle__cycle_id")
.annotate(cnt=Count("pk"))
.values("cnt")
)
backlog_issues = (
Issue.issue_objects.filter(
state__group="backlog",
issue_cycle__cycle_id=OuterRef("pk"),
)
.values("issue_cycle__cycle_id")
.annotate(cnt=Count("pk"))
.values("cnt")
)
total_issues = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=OuterRef("pk"),
)
.values("issue_cycle__cycle_id")
.annotate(cnt=Count("pk"))
.values("cnt")
)
return self.filter_queryset(
super()
.get_queryset()
@@ -156,39 +102,68 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
)
.annotate(is_favorite=Exists(favorite_subquery))
.annotate(
completed_issues=Coalesce(
Subquery(completed_issues[:1]),
Value(0, output_field=IntegerField()),
total_issues=Count(
"issue_cycle__issue__id",
distinct=True,
filter=Q(
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
cancelled_issues=Coalesce(
Subquery(cancelled_issues[:1]),
Value(0, output_field=IntegerField()),
completed_issues=Count(
"issue_cycle__issue__id",
distinct=True,
filter=Q(
issue_cycle__issue__state__group="completed",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
started_issues=Coalesce(
Subquery(started_issues[:1]),
Value(0, output_field=IntegerField()),
cancelled_issues=Count(
"issue_cycle__issue__id",
distinct=True,
filter=Q(
issue_cycle__issue__state__group="cancelled",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
unstarted_issues=Coalesce(
Subquery(unstarted_issues[:1]),
Value(0, output_field=IntegerField()),
started_issues=Count(
"issue_cycle__issue__id",
distinct=True,
filter=Q(
issue_cycle__issue__state__group="started",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
backlog_issues=Coalesce(
Subquery(backlog_issues[:1]),
Value(0, output_field=IntegerField()),
unstarted_issues=Count(
"issue_cycle__issue__id",
distinct=True,
filter=Q(
issue_cycle__issue__state__group="unstarted",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
total_issues=Coalesce(
Subquery(total_issues[:1]),
Value(0, output_field=IntegerField()),
backlog_issues=Count(
"issue_cycle__issue__id",
distinct=True,
filter=Q(
issue_cycle__issue__state__group="backlog",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
@@ -217,9 +192,6 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
distinct=True,
filter=~Q(
issue_cycle__issue__assignees__id__isnull=True
)
& Q(
issue_cycle__issue__assignees__member_project__is_active=True
),
),
Value([], output_field=ArrayField(UUIDField())),
@@ -384,8 +356,8 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
"external_id",
"progress_snapshot",
# meta fields
"total_issues",
"is_favorite",
"total_issues",
"cancelled_issues",
"completed_issues",
"started_issues",
+40 -35
View File
@@ -1,54 +1,57 @@
# Python imports
import json
# Django Imports
from django.utils import timezone
from django.db.models import (
Prefetch,
F,
OuterRef,
Exists,
Count,
Q,
Func,
Subquery,
IntegerField,
)
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField
from django.db.models import Value, UUIDField
from django.db.models import (
Count,
Exists,
F,
Func,
IntegerField,
OuterRef,
Prefetch,
Q,
Subquery,
UUIDField,
Value,
)
from django.db.models.functions import Coalesce
# Django Imports
from django.utils import timezone
from rest_framework import status
# Third party imports
from rest_framework.response import Response
from rest_framework import status
# Module imports
from .. import BaseViewSet, BaseAPIView, WebhookMixin
from plane.app.serializers import (
ModuleWriteSerializer,
ModuleSerializer,
ModuleLinkSerializer,
ModuleFavoriteSerializer,
ModuleUserPropertiesSerializer,
ModuleDetailSerializer,
)
from plane.app.permissions import (
ProjectEntityPermission,
ProjectLitePermission,
)
from plane.db.models import (
Module,
ModuleIssue,
Project,
Issue,
ModuleLink,
ModuleFavorite,
ModuleUserProperties,
from plane.app.serializers import (
ModuleDetailSerializer,
ModuleFavoriteSerializer,
ModuleLinkSerializer,
ModuleSerializer,
ModuleUserPropertiesSerializer,
ModuleWriteSerializer,
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import (
Issue,
Module,
ModuleFavorite,
ModuleIssue,
ModuleLink,
ModuleUserProperties,
Project,
)
from plane.utils.analytics_plot import burndown_plot
# Module imports
from .. import BaseAPIView, BaseViewSet, WebhookMixin
class ModuleViewSet(WebhookMixin, BaseViewSet):
model = Module
@@ -392,9 +395,11 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
"completion_chart": {},
}
if queryset.first().start_date and queryset.first().target_date:
# Fetch the modules
modules = queryset.first()
if modules and modules.start_date and modules.target_date:
data["distribution"]["completion_chart"] = burndown_plot(
queryset=queryset.first(),
queryset=modules,
slug=slug,
project_id=project_id,
module_id=pk,
@@ -3,15 +3,10 @@ from rest_framework import status
from rest_framework.response import Response
# Module imports
from plane.app.permissions import WorkspaceEntityPermission
from plane.app.serializers import WorkspaceEstimateSerializer
from plane.app.views.base import BaseAPIView
from plane.db.models import Project, Estimate
from plane.app.permissions import WorkspaceEntityPermission
# Django imports
from django.db.models import (
Prefetch,
)
from plane.db.models import Estimate, Project
from plane.utils.cache import cache_response
@@ -25,15 +20,11 @@ class WorkspaceEstimatesEndpoint(BaseAPIView):
estimate_ids = Project.objects.filter(
workspace__slug=slug, estimate__isnull=False
).values_list("estimate_id", flat=True)
estimates = Estimate.objects.filter(
pk__in=estimate_ids
).prefetch_related(
Prefetch(
"points",
queryset=Project.objects.select_related(
"estimate", "workspace", "project"
),
)
estimates = (
Estimate.objects.filter(pk__in=estimate_ids, workspace__slug=slug)
.prefetch_related("points")
.select_related("workspace", "project")
)
serializer = WorkspaceEstimateSerializer(estimates, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
@@ -1,311 +0,0 @@
# Python imports
import io
import json
import logging
import zipfile
# Third party imports
import boto3
from botocore.client import Config
from celery import shared_task
# Django imports
from django.conf import settings
from django.core.mail import EmailMultiAlternatives, get_connection
from django.core.serializers.json import DjangoJSONEncoder
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.html import strip_tags
# Module imports
from plane.db.models import (
APIToken,
CommentReaction,
Cycle,
CycleFavorite,
CycleIssue,
CycleUserProperties,
Estimate,
EstimatePoint,
FileAsset,
Inbox,
InboxIssue,
Issue,
IssueActivity,
IssueAssignee,
IssueAttachment,
IssueComment,
IssueLabel,
IssueLink,
IssueMention,
IssueProperty,
IssueReaction,
IssueRelation,
IssueSequence,
IssueSubscriber,
IssueView,
IssueViewFavorite,
IssueVote,
Label,
Module,
ModuleFavorite,
ModuleIssue,
ModuleLink,
ModuleMember,
ModuleUserProperties,
Notification,
Page,
PageFavorite,
PageLabel,
PageLog,
Project,
ProjectDeployBoard,
ProjectFavorite,
ProjectIdentifier,
ProjectMember,
ProjectMemberInvite,
ProjectPublicMember,
State,
User,
UserNotificationPreference,
Webhook,
Workspace,
WorkspaceMember,
WorkspaceMemberInvite,
WorkspaceTheme,
WorkspaceUserProperties,
)
from plane.license.utils.instance_value import get_email_configuration
from plane.utils.exception_logger import log_exception
def create_zip_file(files):
# Create zip
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zipf:
for file in files:
filename = file.get("filename")
file_content = file.get("data")
zipf.writestr(filename, file_content)
zip_buffer.seek(0)
return zip_buffer
def upload_to_s3(zip_file, workspace_id, slug):
# Upload the zip to s3
file_name = f"{workspace_id}/export-{slug}-{timezone.now()}.zip"
expires_in = 7 * 24 * 60 * 60
if settings.USE_MINIO:
s3 = boto3.client(
"s3",
endpoint_url=settings.AWS_S3_ENDPOINT_URL,
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
config=Config(signature_version="s3v4"),
)
s3.upload_fileobj(
zip_file,
settings.AWS_STORAGE_BUCKET_NAME,
file_name,
ExtraArgs={"ACL": "public-read", "ContentType": "application/zip"},
)
presigned_url = s3.generate_presigned_url(
"get_object",
Params={
"Bucket": settings.AWS_STORAGE_BUCKET_NAME,
"Key": file_name,
},
ExpiresIn=expires_in,
)
# Create the new url with updated domain and protocol
presigned_url = presigned_url.replace(
f"{settings.AWS_S3_ENDPOINT_URL}/{settings.AWS_STORAGE_BUCKET_NAME}/",
f"{settings.AWS_S3_URL_PROTOCOL}//{settings.AWS_S3_CUSTOM_DOMAIN}/",
)
else:
s3 = boto3.client(
"s3",
region_name=settings.AWS_REGION,
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
config=Config(signature_version="s3v4"),
)
s3.upload_fileobj(
zip_file,
settings.AWS_STORAGE_BUCKET_NAME,
file_name,
ExtraArgs={"ACL": "public-read", "ContentType": "application/zip"},
)
presigned_url = s3.generate_presigned_url(
"get_object",
Params={
"Bucket": settings.AWS_STORAGE_BUCKET_NAME,
"Key": file_name,
},
ExpiresIn=expires_in,
)
return presigned_url
@shared_task
def workspace_export(workspace_id, email):
# Get the workspace
workspace = Workspace.objects.get(pk=workspace_id)
slug = workspace.slug
# Store all files
files = []
# Users that need to be exported
emails = WorkspaceMember.objects.filter(
workspace_id=workspace_id
).values_list("member__email", flat=True)
users = User.objects.filter(email__in=emails).values()
users_json = json.dumps(list(users), cls=DjangoJSONEncoder)
files.append({"filename": "users.json", "data": users_json})
workspace = list(Workspace.objects.filter(pk=workspace_id).values())
workspace_json = json.dumps(workspace, cls=DjangoJSONEncoder)
files.append({"filename": "workspaces.json", "data": workspace_json})
models = {
# Workspace
WorkspaceMemberInvite: "workspace_member_invites.json",
WorkspaceMember: "workspace_members.json",
WorkspaceTheme: "workspace_themes.json",
WorkspaceUserProperties: "workspace_user_properties.json",
# Projects
Project: "projects.json",
ProjectDeployBoard: "project_deploy_boards.json",
ProjectFavorite: "project_favorites.json",
ProjectIdentifier: "project_identifier.json",
ProjectMember: "project_members.json",
ProjectMemberInvite: "project_member_invites.json",
ProjectPublicMember: "project_public_members.json",
# APIToken
APIToken: "api_tokens.json",
# Assets
FileAsset: "file_assets.json",
# States
State: "states.json",
# Issues
Issue: "issues.json",
IssueAssignee: "issue_assignees.json",
Label: "labels.json",
IssueLabel: "issue_labels.json",
IssueLink: "issue_links.json",
IssueMention: "issue_mention.json",
IssueVote: "issue_votes.json",
IssueSubscriber: "issue_subscribers.json",
IssueProperty: "issue_properties.json",
IssueSequence: "issue_sequences.json",
IssueReaction: "issue_reactions.json",
IssueRelation: "issue_relations.json",
IssueAttachment: "issue_attachments.json",
IssueActivity: "issue_activities.json",
CommentReaction: "comment_reactions.json",
IssueComment: "issue_comments.json",
# Cycles
Cycle: "cycles.json",
CycleIssue: "cycle_issues.json",
CycleFavorite: "cycle_favorites.json",
CycleUserProperties: "cycle_user_properties.json",
# Modules
Module: "modules.json",
ModuleIssue: "module_issues.json",
ModuleFavorite: "module_favorites.json",
ModuleLink: "module_links.json",
ModuleMember: "module_members.json",
ModuleUserProperties: "module_user_properties.json",
# Page
Page: "pages.json",
PageLog: "page_logs.json",
PageLabel: "page_labels.json",
PageFavorite: "page_favorites.json",
# Estimate
Estimate: "estimates.json",
EstimatePoint: "estimate_points.json",
# Webhook
Webhook: "webhooks.json",
# Views
IssueView: "views.json",
IssueViewFavorite: "view_favorites.json",
# Notification
Notification: "notifications.json",
UserNotificationPreference: "user_notification_preferences.json",
# Inbox
Inbox: "inboxes.json",
InboxIssue: "inbox_issues.json",
}
# Loop through the models
for model in models:
file_name = models[model]
files.append(
{
"filename": file_name,
"data": json.dumps(
list(
model.objects.filter(
workspace_id=workspace_id
).values()
),
cls=DjangoJSONEncoder,
),
}
)
# Create zip
zip_buffer = create_zip_file(files)
# Get the presigned url
url = upload_to_s3(
workspace_id=workspace_id, slug=slug, zip_file=zip_buffer
)
# Send mail
try:
(
EMAIL_HOST,
EMAIL_HOST_USER,
EMAIL_HOST_PASSWORD,
EMAIL_PORT,
EMAIL_USE_TLS,
EMAIL_FROM,
) = get_email_configuration()
# Send the mail
subject = "Your Plane Export Link"
context = {"url": url, "email": email}
html_content = render_to_string(
"emails/exports/workspace_exports.html", context
)
text_content = strip_tags(html_content)
connection = get_connection(
host=EMAIL_HOST,
port=int(EMAIL_PORT),
username=EMAIL_HOST_USER,
password=EMAIL_HOST_PASSWORD,
use_tls=EMAIL_USE_TLS == "1",
)
msg = EmailMultiAlternatives(
subject=subject,
body=text_content,
from_email=EMAIL_FROM,
to=[email],
connection=connection,
)
msg.attach_alternative(html_content, "text/html")
msg.send()
logging.getLogger("plane").info("Email sent successfully.")
return
except Exception as e:
log_exception(e)
return
@@ -1,475 +0,0 @@
# Python imports
import json
# Third party imports
from celery import shared_task
from plane.db.models import (
APIToken,
CommentReaction,
Cycle,
CycleFavorite,
CycleIssue,
CycleUserProperties,
Estimate,
EstimatePoint,
FileAsset,
Inbox,
InboxIssue,
Issue,
IssueActivity,
IssueAssignee,
IssueAttachment,
IssueComment,
IssueLabel,
IssueLink,
IssueMention,
IssueProperty,
IssueReaction,
IssueRelation,
IssueSequence,
IssueSubscriber,
IssueView,
IssueViewFavorite,
IssueVote,
Label,
Module,
ModuleFavorite,
ModuleIssue,
ModuleLink,
ModuleMember,
ModuleUserProperties,
Notification,
Page,
PageFavorite,
PageLabel,
PageLog,
Project,
ProjectDeployBoard,
ProjectFavorite,
ProjectIdentifier,
ProjectMember,
ProjectMemberInvite,
ProjectPublicMember,
State,
User,
UserNotificationPreference,
Webhook,
Workspace,
WorkspaceMember,
WorkspaceMemberInvite,
WorkspaceTheme,
WorkspaceUserProperties,
)
from plane.utils.exception_logger import log_exception
def replace_model_data(fields, model_data, user_maps):
for field in fields:
model_data[field] = user_maps.get(
model_data.get(field), model_data.get(field)
)
return model_data
def data_transformer(model, model_data, user_maps):
# model name mapper
mapper = {
"workspace": [
"created_by_id",
"updated_by_id",
"owner_id",
],
"workspacememberinvite": [
"created_by_id",
"updated_by_id",
],
"workspacetheme": [
"created_by_id",
"updated_by_id",
],
"workspacemember": [
"created_by_id",
"updated_by_id",
"member_id",
],
"workspaceuserproperties": [
"created_by_id",
"updated_by_id",
"user_id",
],
"project": [
"created_by_id",
"updated_by_id",
"default_assignee_id",
"project_lead_id",
],
"projectdeployboard": [
"created_by_id",
"updated_by_id",
],
"projectfavorite": [
"user_id",
"created_by_id",
"updated_by_id",
],
"projectmember": [
"created_by_id",
"updated_by_id",
"member_id",
],
"projectidentifier": [
"created_by_id",
"updated_by_id",
],
"projectmemberinvite": [
"created_by_id",
"updated_by_id",
],
"projectpublicmember": [
"created_by_id",
"updated_by_id",
"member_id",
],
"state": [
"created_by_id",
"updated_by_id",
],
"label": [
"created_by_id",
"updated_by_id",
],
"estimate": [
"created_by_id",
"updated_by_id",
],
"estimatepoint": [
"created_by_id",
"updated_by_id",
],
"issue": [
"created_by_id",
"updated_by_id",
],
"issuecomment": [
"created_by_id",
"updated_by_id",
"actor_id",
],
"issueassignee": [
"created_by_id",
"updated_by_id",
"assignee_id",
],
"issuelabel": [
"created_by_id",
"updated_by_id",
],
"issuelink": [
"created_by_id",
"updated_by_id",
],
"issuemention": [
"created_by_id",
"updated_by_id",
"mention_id",
],
"issuevote": [
"created_by_id",
"updated_by_id",
"actor_id",
],
"issuesubscriber": [
"created_by_id",
"updated_by_id",
"subscriber_id",
],
"issueproperty": [
"created_by_id",
"updated_by_id",
"user_id",
],
"issuesequence": [
"created_by_id",
"updated_by_id",
],
"issuereaction": [
"created_by_id",
"updated_by_id",
"actor_id",
],
"issuerelation": [
"created_by_id",
"updated_by_id",
],
"issueattachment": [
"created_by_id",
"updated_by_id",
],
"issueactivity": [
"created_by_id",
"updated_by_id",
"actor_id",
],
"apitoken": [
"created_by_id",
"updated_by_id",
"user_id",
],
"fileasset": [
"created_by_id",
"updated_by_id",
],
"commentreaction": [
"created_by_id",
"updated_by_id",
"actor_id",
],
"cycle": [
"created_by_id",
"updated_by_id",
"owned_by_id",
],
"cycleissue": [
"created_by_id",
"updated_by_id",
],
"cyclefavorite": [
"user_id",
"created_by_id",
"updated_by_id",
],
"cycleuserproperties": [
"user_id",
"created_by_id",
"updated_by_id",
],
"module": [
"created_by_id",
"updated_by_id",
"lead_id",
],
"moduleissue": [
"created_by_id",
"updated_by_id",
],
"modulefavorite": [
"user_id",
"created_by_id",
"updated_by_id",
],
"modulelink": [
"created_by_id",
"updated_by_id",
],
"modulemember": [
"created_by_id",
"updated_by_id",
"member_id",
],
"moduleuserproperties": [
"user_id",
"created_by_id",
"updated_by_id",
],
"page": [
"created_by_id",
"updated_by_id",
"owned_by_id",
],
"pagelog": [
"created_by_id",
"updated_by_id",
],
"pagelabel": [
"created_by_id",
"updated_by_id",
],
"pagefavorite": [
"user_id",
"created_by_id",
"updated_by_id",
],
"webhook": [
"created_by_id",
"updated_by_id",
],
"issueview": [
"created_by_id",
"updated_by_id",
],
"issueviewfavorite": [
"user_id",
"created_by_id",
"updated_by_id",
],
"notification": [
"created_by_id",
"updated_by_id",
"triggered_by_id",
"receiver_id",
],
"usernotificationpreference": [
"user_id",
"created_by_id",
"updated_by_id",
],
"inbox": [
"created_by_id",
"updated_by_id",
],
"inboxissue": [
"created_by_id",
"updated_by_id",
],
}
# Get all the fields for the current model
fields = mapper[model._meta.model_name]
if fields:
model_data = replace_model_data(
fields=fields, model_data=model_data, user_maps=user_maps
)
# return modified model data
return model_data
# return model data
return model_data
@shared_task
def workspace_import(workspace_data):
try:
# Create Users
users = json.loads(workspace_data.get("users.json"))
# get user emails
user_emails = [user.get("email") for user in users]
imported_users = {user.get("email"): user.get("id") for user in users}
existing_users = User.objects.filter(email__in=user_emails).values(
"id", "email"
)
user_maps = {
imported_users.get(exuser.get("email")): str(exuser.get("id"))
for exuser in existing_users
if imported_users.get(exuser.get("email"))
}
User.objects.bulk_create(
[User(**user) for user in (users)],
batch_size=100,
ignore_conflicts=True,
)
# Workspaces
workspaces = workspace_data.get("workspaces.json")
Workspace.objects.bulk_create(
[
Workspace(
**data_transformer(
model=Workspace,
model_data=workspace,
user_maps=user_maps,
)
)
for workspace in json.loads(workspaces)
],
batch_size=100,
ignore_conflicts=True,
)
models = {
# Workspace
WorkspaceMemberInvite: "workspace_member_invites.json",
WorkspaceMember: "workspace_members.json",
WorkspaceTheme: "workspace_themes.json",
WorkspaceUserProperties: "workspace_user_properties.json",
# Projects
Project: "projects.json",
ProjectDeployBoard: "project_deploy_boards.json",
ProjectFavorite: "project_favorites.json",
ProjectIdentifier: "project_identifier.json",
ProjectMember: "project_members.json",
ProjectMemberInvite: "project_member_invites.json",
ProjectPublicMember: "project_public_members.json",
# States
State: "states.json",
# Labels
Label: "labels.json",
# Estimate
Estimate: "estimates.json",
EstimatePoint: "estimate_points.json",
# Issues
Issue: "issues.json",
IssueComment: "issue_comments.json",
IssueAssignee: "issue_assignees.json",
IssueLabel: "issue_labels.json",
IssueLink: "issue_links.json",
IssueMention: "issue_mention.json",
IssueVote: "issue_votes.json",
IssueSubscriber: "issue_subscribers.json",
IssueProperty: "issue_properties.json",
IssueSequence: "issue_sequences.json",
IssueReaction: "issue_reactions.json",
IssueRelation: "issue_relations.json",
IssueAttachment: "issue_attachments.json",
IssueActivity: "issue_activities.json",
# APIToken
APIToken: "api_tokens.json",
# Assets
FileAsset: "file_assets.json",
CommentReaction: "comment_reactions.json",
# Cycles
Cycle: "cycles.json",
CycleIssue: "cycle_issues.json",
CycleFavorite: "cycle_favorites.json",
CycleUserProperties: "cycle_user_properties.json",
# Modules
Module: "modules.json",
ModuleIssue: "module_issues.json",
ModuleFavorite: "module_favorites.json",
ModuleLink: "module_links.json",
ModuleMember: "module_members.json",
ModuleUserProperties: "module_user_properties",
# Page
Page: "pages.json",
PageLog: "page_logs.json",
PageLabel: "page_labels.json",
PageFavorite: "page_favorites.json",
# Webhook
Webhook: "webhooks.json",
# Views
IssueView: "views.json",
IssueViewFavorite: "view_favorites.json",
# Notification
Notification: "notifications.json",
UserNotificationPreference: "user_notification_preferences.json",
# Inbox
Inbox: "inboxes.json",
InboxIssue: "inbox_issues.json",
}
for model in models:
file_name = models[model]
data = workspace_data.get(file_name)
# Loop through all the models and create the records accordingly
model.objects.bulk_create(
[
model(
**data_transformer(
model=model,
model_data=model_data,
user_maps=user_maps,
)
)
for model_data in json.loads(data)
],
batch_size=100,
ignore_conflicts=True,
)
return
except Exception as e:
log_exception(e)
return
+1 -1
View File
@@ -1,6 +1,6 @@
# Django imports
from django.conf import settings
from django.db import models
from django.conf import settings
# Module imports
from . import ProjectBaseModel
+2 -1
View File
@@ -1,11 +1,12 @@
# Django imports
from django.db import models
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
# Module imports
from . import BaseModel
ROLE_CHOICES = (
(20, "Owner"),
(15, "Admin"),
@@ -4,6 +4,4 @@ from .instance import (
InstanceConfigurationEndpoint,
InstanceAdminSignInEndpoint,
SignUpScreenVisitedEndpoint,
ExportWorkspaceEndpoint,
ImportWorkspaceEndpoint,
)
+11 -98
View File
@@ -1,39 +1,33 @@
# Python imports
import uuid
import zipfile
# Django imports
from django.contrib.auth.hashers import make_password
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.utils import timezone
from django.contrib.auth.hashers import make_password
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
# Third party imports
from rest_framework import status
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from rest_framework_simplejwt.tokens import RefreshToken
# Module imports
from plane.app.serializers.workspace import WorkspaceLiteSerializer
from plane.app.views import BaseAPIView
from plane.bgtasks.workspace_export_task import workspace_export
from plane.bgtasks.workspace_import_task import workspace_import
from plane.db.models import User, Workspace
from plane.license.models import Instance, InstanceAdmin, InstanceConfiguration
from plane.license.api.serializers import (
InstanceSerializer,
InstanceAdminSerializer,
InstanceConfigurationSerializer,
)
from plane.license.api.permissions import (
InstanceAdminPermission,
)
from plane.license.api.serializers import (
InstanceAdminSerializer,
InstanceConfigurationSerializer,
InstanceSerializer,
)
from plane.license.models import Instance, InstanceAdmin, InstanceConfiguration
from plane.db.models import User
from plane.license.utils.encryption import encrypt_data
from plane.utils.cache import cache_response, invalidate_cache
class InstanceEndpoint(BaseAPIView):
def get_permissions(self):
if self.request.method == "PATCH":
@@ -278,84 +272,3 @@ class SignUpScreenVisitedEndpoint(BaseAPIView):
instance.is_signup_screen_visited = True
instance.save()
return Response(status=status.HTTP_204_NO_CONTENT)
class InstanceWorkspacesEndpoint(BaseAPIView):
permission_classes = [
InstanceAdminPermission,
]
def get(self, request):
workspaces = Workspace.objects.all()
serializer = WorkspaceLiteSerializer(workspaces, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
class ExportWorkspaceEndpoint(BaseAPIView):
permission_classes = [
InstanceAdminPermission,
]
def post(self, request):
workspace_id = request.data.get("workspace_id", False)
if not workspace_id:
return Response(
{"error": "Workspace ID is required"},
status=status.HTTP_400_BAD_REQUEST,
)
workspace_export.delay(
workspace_id=workspace_id,
email=request.user.email,
)
return Response(
{
"message": "An email will be sent to download the exports when they are ready"
},
status=status.HTTP_200_OK,
)
class ImportWorkspaceEndpoint(BaseAPIView):
parser_classes = (
MultiPartParser,
FormParser,
JSONParser,
)
permission_classes = [
InstanceAdminPermission,
]
def post(self, request):
file_obj = request.FILES.get("zip_file")
if file_obj is None:
return Response(
"No file uploaded.", status=status.HTTP_400_BAD_REQUEST
)
# Ensure the uploaded file is a ZIP file
if not zipfile.is_zipfile(file_obj):
return Response(
"Uploaded file is not a valid zip file.",
status=status.HTTP_400_BAD_REQUEST,
)
# Reading contents of the ZIP file
file_contents = {}
with zipfile.ZipFile(file_obj, "r") as zip_ref:
for file_name in zip_ref.namelist():
with zip_ref.open(file_name) as file:
# Assuming the file content is text. Use file.read() for binary content.
content = file.read().decode("utf-8")
file_contents[file_name] = content
workspace_import.delay(workspace_data=file_contents)
return Response(
{"message": "Files processed.", "file_count": len(file_contents)},
status=status.HTTP_200_OK,
)
+3 -15
View File
@@ -1,12 +1,10 @@
from django.urls import path
from plane.license.api.views import (
ExportWorkspaceEndpoint,
ImportWorkspaceEndpoint,
InstanceAdminEndpoint,
InstanceAdminSignInEndpoint,
InstanceConfigurationEndpoint,
InstanceEndpoint,
InstanceAdminEndpoint,
InstanceConfigurationEndpoint,
InstanceAdminSignInEndpoint,
SignUpScreenVisitedEndpoint,
)
@@ -41,14 +39,4 @@ urlpatterns = [
SignUpScreenVisitedEndpoint.as_view(),
name="instance-sign-up",
),
path(
"export-workspace/",
ExportWorkspaceEndpoint.as_view(),
name="workspace-exports",
),
path(
"import-workspace/",
ImportWorkspaceEndpoint.as_view(),
name="workspace-imports",
),
]
@@ -1,5 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
Hey there,<br />
Your requested url {{ url }}
</html>
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -5,11 +5,11 @@ x-app-env: &app-env
- NGINX_PORT=${NGINX_PORT:-80}
- WEB_URL=${WEB_URL:-http://localhost}
- DEBUG=${DEBUG:-0}
- SENTRY_DSN=${SENTRY_DSN:-""}
- SENTRY_DSN=${SENTRY_DSN}
- SENTRY_ENVIRONMENT=${SENTRY_ENVIRONMENT:-"production"}
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-""}
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS}
# Gunicorn Workers
- GUNICORN_WORKERS=${GUNICORN_WORKERS:-2}
- GUNICORN_WORKERS=${GUNICORN_WORKERS:-1}
#DB SETTINGS
- PGHOST=${PGHOST:-plane-db}
- PGDATABASE=${PGDATABASE:-plane}
@@ -17,11 +17,11 @@ x-app-env: &app-env
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-plane}
- POSTGRES_DB=${POSTGRES_DB:-plane}
- PGDATA=${PGDATA:-/var/lib/postgresql/data}
- DATABASE_URL=${DATABASE_URL:-postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${PGHOST}/${PGDATABASE}}
- DATABASE_URL=${DATABASE_URL:-postgresql://plane:plane@plane-db/plane}
# REDIS SETTINGS
- REDIS_HOST=${REDIS_HOST:-plane-redis}
- REDIS_PORT=${REDIS_PORT:-6379}
- REDIS_URL=${REDIS_URL:-redis://${REDIS_HOST}:6379/}
- REDIS_URL=${REDIS_URL:-redis://plane-redis:6379/}
# Application secret
- SECRET_KEY=${SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5}
# DATA STORE SETTINGS
@@ -39,7 +39,7 @@ x-app-env: &app-env
services:
web:
<<: *app-env
image: ${DOCKERHUB_USER:-makeplane}/plane-frontend:${APP_RELEASE:-latest}
image: ${DOCKERHUB_USER:-makeplane}/plane-frontend:${APP_RELEASE:-stable}
pull_policy: ${PULL_POLICY:-always}
restart: unless-stopped
command: /usr/local/bin/start.sh web/server.js web
@@ -51,7 +51,7 @@ services:
space:
<<: *app-env
image: ${DOCKERHUB_USER:-makeplane}/plane-space:${APP_RELEASE:-latest}
image: ${DOCKERHUB_USER:-makeplane}/plane-space:${APP_RELEASE:-stable}
pull_policy: ${PULL_POLICY:-always}
restart: unless-stopped
command: /usr/local/bin/start.sh space/server.js space
@@ -64,7 +64,7 @@ services:
api:
<<: *app-env
image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-latest}
image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-stable}
pull_policy: ${PULL_POLICY:-always}
restart: unless-stopped
command: ./bin/takeoff
@@ -78,7 +78,7 @@ services:
worker:
<<: *app-env
image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-latest}
image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-stable}
pull_policy: ${PULL_POLICY:-always}
restart: unless-stopped
command: ./bin/worker
@@ -91,7 +91,7 @@ services:
beat-worker:
<<: *app-env
image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-latest}
image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-stable}
pull_policy: ${PULL_POLICY:-always}
restart: unless-stopped
command: ./bin/beat
@@ -104,7 +104,7 @@ services:
migrator:
<<: *app-env
image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-latest}
image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-stable}
pull_policy: ${PULL_POLICY:-always}
restart: no
command: >
@@ -118,7 +118,7 @@ services:
plane-db:
<<: *app-env
image: postgres:15.2-alpine
image: postgres:15.5-alpine
pull_policy: if_not_present
restart: unless-stopped
command: postgres -c 'max_connections=1000'
@@ -126,7 +126,7 @@ services:
- pgdata:/var/lib/postgresql/data
plane-redis:
<<: *app-env
image: redis:6.2.7-alpine
image: redis:7.2.4-alpine
pull_policy: if_not_present
restart: unless-stopped
volumes:
@@ -144,7 +144,7 @@ services:
# Comment this if you already have a reverse proxy running
proxy:
<<: *app-env
image: ${DOCKERHUB_USER:-makeplane}/plane-proxy:${APP_RELEASE:-latest}
image: ${DOCKERHUB_USER:-makeplane}/plane-proxy:${APP_RELEASE:-stable}
pull_policy: ${PULL_POLICY:-always}
ports:
- ${NGINX_PORT}:80
+54 -26
View File
@@ -17,16 +17,16 @@ function print_header() {
clear
cat <<"EOF"
---------------------------------------
____ _
| _ \| | __ _ _ __ ___
| |_) | |/ _` | '_ \ / _ \
| __/| | (_| | | | | __/
|_| |_|\__,_|_| |_|\___|
---------------------------------------
--------------------------------------------
____ _ /////////
| _ \| | __ _ _ __ ___ /////////
| |_) | |/ _` | '_ \ / _ \ ///// /////
| __/| | (_| | | | | __/ ///// /////
|_| |_|\__,_|_| |_|\___| ////
////
--------------------------------------------
Project management tool from the future
---------------------------------------
--------------------------------------------
EOF
}
@@ -66,7 +66,7 @@ function buildLocalImage() {
cd $PLANE_TEMP_CODE_DIR
if [ "$BRANCH" == "master" ];
then
export APP_RELEASE=latest
export APP_RELEASE=stable
fi
docker compose -f build.yml build --no-cache >&2
@@ -99,17 +99,17 @@ function download() {
curl -H 'Cache-Control: no-cache, no-store' -s -o $PLANE_INSTALL_DIR/docker-compose.yaml https://raw.githubusercontent.com/makeplane/plane/$BRANCH/deploy/selfhost/docker-compose.yml?$(date +%s)
curl -H 'Cache-Control: no-cache, no-store' -s -o $PLANE_INSTALL_DIR/variables-upgrade.env https://raw.githubusercontent.com/makeplane/plane/$BRANCH/deploy/selfhost/variables.env?$(date +%s)
if [ -f "$PLANE_INSTALL_DIR/.env" ];
if [ -f "$DOCKER_ENV_PATH" ];
then
cp $PLANE_INSTALL_DIR/.env $PLANE_INSTALL_DIR/archive/$TS.env
cp $DOCKER_ENV_PATH $PLANE_INSTALL_DIR/archive/$TS.env
else
mv $PLANE_INSTALL_DIR/variables-upgrade.env $PLANE_INSTALL_DIR/.env
mv $PLANE_INSTALL_DIR/variables-upgrade.env $DOCKER_ENV_PATH
fi
if [ "$BRANCH" != "master" ];
then
cp $PLANE_INSTALL_DIR/docker-compose.yaml $PLANE_INSTALL_DIR/temp.yaml
sed -e 's@${APP_RELEASE:-latest}@'"$BRANCH"'@g' \
sed -e 's@${APP_RELEASE:-stable}@'"$BRANCH"'@g' \
$PLANE_INSTALL_DIR/temp.yaml > $PLANE_INSTALL_DIR/docker-compose.yaml
rm $PLANE_INSTALL_DIR/temp.yaml
@@ -131,9 +131,9 @@ function download() {
fi
echo ""
echo "Latest version is now available for you to use"
echo "Most recent Stable version is now available for you to use"
echo ""
echo "In case of Upgrade, your new setting file is availabe as 'variables-upgrade.env'. Please compare and set the required values in '.env 'file."
echo "In case of Upgrade, your new setting file is availabe as 'variables-upgrade.env'. Please compare and set the required values in 'plane.env 'file."
echo ""
}
@@ -144,7 +144,7 @@ function startServices() {
if [ -n "$migrator_container_id" ]; then
local idx=0
while docker inspect --format='{{.State.Status}}' $migrator_container_id | grep -q "running"; do
local message=">>> Waiting for Data Migration to finish"
local message=">> Waiting for Data Migration to finish"
local dots=$(printf '%*s' $idx | tr ' ' '.')
echo -ne "\r$message$dots"
((idx++))
@@ -152,13 +152,18 @@ function startServices() {
done
fi
printf "\r\033[K"
echo ""
echo " Data Migration completed successfully ✅"
# if migrator exit status is not 0, show error message and exit
if [ -n "$migrator_container_id" ]; then
local migrator_exit_code=$(docker inspect --format='{{.State.ExitCode}}' $migrator_container_id)
if [ $migrator_exit_code -ne 0 ]; then
echo "Plane Server failed to start ❌"
stopServices
# stopServices
echo
echo "Please check the logs for the 'migrator' service and resolve the issue(s)."
echo "Stop the services by running the command: ./setup.sh stop"
exit 1
fi
fi
@@ -167,26 +172,35 @@ function startServices() {
local idx2=0
while ! docker logs $api_container_id 2>&1 | grep -m 1 -i "Application startup complete" | grep -q ".";
do
local message=">>> Waiting for API Service to Start"
local message=">> Waiting for API Service to Start"
local dots=$(printf '%*s' $idx2 | tr ' ' '.')
echo -ne "\r$message$dots"
((idx2++))
sleep 1
done
printf "\r\033[K"
echo " API Service started successfully ✅"
source "${DOCKER_ENV_PATH}"
echo " Plane Server started successfully ✅"
echo ""
echo " You can access the application at $WEB_URL"
echo ""
}
function stopServices() {
docker compose -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH down
}
function restartServices() {
docker compose -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH restart
# docker compose -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH restart
stopServices
startServices
}
function upgrade() {
echo "***** STOPPING SERVICES ****"
stopServices
echo
echo "***** DOWNLOADING LATEST VERSION ****"
echo "***** DOWNLOADING STABLE VERSION ****"
download
echo "***** PLEASE VALIDATE AND START SERVICES ****"
@@ -303,15 +317,15 @@ function askForAction() {
elif [ "$ACTION" == "2" ] || [ "$DEFAULT_ACTION" == "start" ]
then
startServices
askForAction
# askForAction
elif [ "$ACTION" == "3" ] || [ "$DEFAULT_ACTION" == "stop" ]
then
stopServices
askForAction
# askForAction
elif [ "$ACTION" == "4" ] || [ "$DEFAULT_ACTION" == "restart" ]
then
restartServices
askForAction
# askForAction
elif [ "$ACTION" == "5" ] || [ "$DEFAULT_ACTION" == "upgrade" ]
then
upgrade
@@ -343,7 +357,7 @@ fi
if [ "$BRANCH" == "master" ];
then
export APP_RELEASE=latest
export APP_RELEASE=stable
fi
# REMOVE SPECIAL CHARACTERS FROM BRANCH NAME
@@ -354,7 +368,21 @@ fi
mkdir -p $PLANE_INSTALL_DIR/archive
DOCKER_FILE_PATH=$PLANE_INSTALL_DIR/docker-compose.yaml
DOCKER_ENV_PATH=$PLANE_INSTALL_DIR/.env
DOCKER_ENV_PATH=$PLANE_INSTALL_DIR/plane.env
# BACKWARD COMPATIBILITY
OLD_DOCKER_ENV_PATH=$PLANE_INSTALL_DIR/.env
if [ -f "$OLD_DOCKER_ENV_PATH" ];
then
mv "$OLD_DOCKER_ENV_PATH" "$DOCKER_ENV_PATH"
OS_NAME=$(uname)
if [ "$OS_NAME" == "Darwin" ];
then
sed -i '' -e 's@APP_RELEASE=latest@APP_RELEASE=stable@' "$DOCKER_ENV_PATH"
else
sed -i -e 's@APP_RELEASE=latest@APP_RELEASE=stable@' "$DOCKER_ENV_PATH"
fi
fi
print_header
askForAction $@
+4 -4
View File
@@ -1,4 +1,4 @@
APP_RELEASE=latest
APP_RELEASE=stable
WEB_REPLICAS=1
SPACE_REPLICAS=1
@@ -18,12 +18,12 @@ POSTGRES_USER=plane
POSTGRES_PASSWORD=plane
POSTGRES_DB=plane
PGDATA=/var/lib/postgresql/data
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${PGHOST}/${PGDATABASE}
DATABASE_URL=
# REDIS SETTINGS
REDIS_HOST=plane-redis
REDIS_PORT=6379
REDIS_URL=redis://${REDIS_HOST}:6379/
REDIS_URL=
# Secret Key
SECRET_KEY=60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5
@@ -41,4 +41,4 @@ BUCKET_NAME=uploads
FILE_SIZE_LIMIT=5242880
# Gunicorn Workers
GUNICORN_WORKERS=2
GUNICORN_WORKERS=1
+1 -1
View File
@@ -30,7 +30,7 @@ http {
}
location /${BUCKET_NAME}/ {
proxy_pass http://plane-minio:9000/uploads/;
proxy_pass http://plane-minio:9000/${BUCKET_NAME}/;
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"repository": "https://github.com/makeplane/plane.git",
"version": "0.16.0",
"version": "0.17.0",
"license": "AGPL-3.0",
"private": true,
"workspaces": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/editor-core",
"version": "0.16.0",
"version": "0.17.0",
"description": "Core Editor that powers Plane",
"private": true,
"main": "./dist/index.mjs",
@@ -1,5 +1,6 @@
import { Editor } from "@tiptap/react";
import { FC, ReactNode } from "react";
import { cn } from "src/lib/utils";
interface EditorContainerProps {
editor: Editor | null;
@@ -53,7 +54,7 @@ export const EditorContainer: FC<EditorContainerProps> = (props) => {
onMouseLeave={() => {
hideDragHandle?.();
}}
className={`cursor-text ${editorClassNames}`}
className={cn(`cursor-text`, { "active-editor": editor?.isFocused && editor?.isEditable }, editorClassNames)}
>
{children}
</div>
@@ -22,7 +22,7 @@ export const Suggestion = (suggestions: IMentionSuggestion[]) => ({
// @ts-ignore
popup = tippy("body", {
getReferenceClientRect: props.clientRect,
appendTo: () => document.querySelector("#editor-container"),
appendTo: () => document.querySelector(".active-editor") ?? document.querySelector("#editor-container"),
content: reactRenderer.element,
showOnCreate: true,
interactive: true,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/document-editor",
"version": "0.16.0",
"version": "0.17.0",
"description": "Package that powers Plane's Pages Editor",
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/editor-extensions",
"version": "0.16.0",
"version": "0.17.0",
"description": "Package that powers Plane's Editor with extensions",
"private": true,
"main": "./dist/index.mjs",
@@ -330,7 +330,7 @@ const renderItems = () => {
// @ts-ignore
popup = tippy("body", {
getReferenceClientRect: props.clientRect,
appendTo: () => document.querySelector("#editor-container"),
appendTo: () => document.querySelector(".active-editor") ?? document.querySelector("#editor-container"),
content: component.element,
showOnCreate: true,
interactive: true,
@@ -1,6 +1,6 @@
{
"name": "@plane/lite-text-editor",
"version": "0.16.0",
"version": "0.17.0",
"description": "Package that powers Plane's Comment Editor",
"private": true,
"main": "./dist/index.mjs",
@@ -1,6 +1,6 @@
{
"name": "@plane/rich-text-editor",
"version": "0.16.0",
"version": "0.17.0",
"description": "Rich Text Editor that powers Plane",
"private": true,
"main": "./dist/index.mjs",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "eslint-config-custom",
"private": true,
"version": "0.16.0",
"version": "0.17.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "tailwind-config-custom",
"version": "0.16.0",
"version": "0.17.0",
"description": "common tailwind configuration across monorepo",
"main": "index.js",
"private": true,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "tsconfig",
"version": "0.16.0",
"version": "0.17.0",
"private": true,
"files": [
"base.json",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/types",
"version": "0.16.0",
"version": "0.17.0",
"private": true,
"main": "./src/index.d.ts"
}
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "@plane/ui",
"description": "UI components shared across multiple apps internally",
"private": true,
"version": "0.16.0",
"version": "0.17.0",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
+1 -1
View File
@@ -91,7 +91,7 @@ const CustomSelect = (props: ICustomSelectProps) => {
)}
</>
{isOpen && (
<Listbox.Options className="fixed z-10" onClick={() => closeDropdown()} static>
<Listbox.Options className="fixed z-20" onClick={() => closeDropdown()} static>
<div
className={cn(
"my-1 overflow-y-scroll rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 text-xs shadow-custom-shadow-rg focus:outline-none min-w-[12rem] whitespace-nowrap",
+6 -2
View File
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/** @type {import('next').NextConfig} */
require("dotenv").config({ path: ".env" });
const { withSentryConfig } = require("@sentry/nextjs");
@@ -26,8 +27,11 @@ const nextConfig = {
output: "standalone",
};
if (parseInt(process.env.NEXT_PUBLIC_ENABLE_SENTRY || "0")) {
module.exports = withSentryConfig(nextConfig, { silent: true }, { hideSourceMaps: true });
if (parseInt(process.env.NEXT_PUBLIC_ENABLE_SENTRY || "0", 10)) {
module.exports = withSentryConfig(nextConfig,
{ silent: true, authToken: process.env.SENTRY_AUTH_TOKEN },
{ hideSourceMaps: true }
);
} else {
module.exports = nextConfig;
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "space",
"version": "0.16.0",
"version": "0.17.0",
"private": true,
"scripts": {
"dev": "turbo run develop",
@@ -22,7 +22,7 @@
"@plane/rich-text-editor": "*",
"@plane/types": "*",
"@plane/ui": "*",
"@sentry/nextjs": "^7.85.0",
"@sentry/nextjs": "^7.108.0",
"axios": "^1.3.4",
"clsx": "^2.0.0",
"dotenv": "^16.3.1",
+6 -18
View File
@@ -17,37 +17,25 @@
"NEXT_PUBLIC_POSTHOG_KEY",
"NEXT_PUBLIC_POSTHOG_HOST",
"NEXT_PUBLIC_POSTHOG_DEBUG",
"JITSU_TRACKER_ACCESS_KEY",
"JITSU_TRACKER_HOST"
"SENTRY_AUTH_TOKEN"
],
"pipeline": {
"build": {
"dependsOn": [
"^build"
],
"outputs": [
".next/**",
"dist/**"
]
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
},
"develop": {
"cache": false,
"persistent": true,
"dependsOn": [
"^build"
]
"dependsOn": ["^build"]
},
"dev": {
"cache": false,
"persistent": true,
"dependsOn": [
"^build"
]
"dependsOn": ["^build"]
},
"test": {
"dependsOn": [
"^build"
],
"dependsOn": ["^build"],
"outputs": []
},
"lint": {
@@ -1,9 +1,7 @@
// icons
import { Triangle } from "lucide-react";
import { IDefaultAnalyticsResponse, TStateGroups } from "@plane/types";
// types
import { STATE_GROUPS } from "@/constants/state";
import { IDefaultAnalyticsResponse, TStateGroups } from "@plane/types";
// constants
import { STATE_GROUPS } from "@/constants/state";
type Props = {
defaultAnalytics: IDefaultAnalyticsResponse;
@@ -16,7 +14,7 @@ export const AnalyticsDemand: React.FC<Props> = ({ defaultAnalytics }) => (
<h4 className="text-base font-medium text-custom-text-100">Total open tasks</h4>
<h3 className="mt-1 text-xl font-semibold">{defaultAnalytics.open_issues}</h3>
</div>
<div className="space-y-6">
<div className="space-y-6 pb-2">
{defaultAnalytics?.open_issues_classified.map((group) => {
const percentage = ((group.state_count / defaultAnalytics.total_issues) * 100).toFixed(0);
@@ -50,14 +48,5 @@ export const AnalyticsDemand: React.FC<Props> = ({ defaultAnalytics }) => (
);
})}
</div>
<div className="!mt-6 flex w-min items-center gap-2 whitespace-nowrap rounded-md border border-custom-border-200 bg-custom-background-80 p-2 text-xs">
<p className="flex items-center gap-1 text-custom-text-200">
<Triangle className="h-4 w-4" />
<span>Estimate Demand:</span>
</p>
<p className="font-medium">
{defaultAnalytics.open_estimate_sum}/{defaultAnalytics.total_estimate_sum}
</p>
</div>
</div>
);
@@ -69,7 +69,9 @@ export const CommandPaletteHelpActions: React.FC<Props> = (props) => {
<Command.Item
onSelect={() => {
closePalette();
(window as any)?.$crisp.push(["do", "chat:open"]);
if (window) {
window.$crisp.push(["do", "chat:show"]);
}
}}
className="focus:outline-none"
>
@@ -40,7 +40,7 @@ const issueService = new IssueService();
export const CommandModal: React.FC = observer(() => {
// hooks
const { getProjectById } = useProject();
const { getProjectById, workspaceProjectIds } = useProject();
const { isMobile } = usePlatformOS();
// states
const [placeholder, setPlaceholder] = useState("Type a command or search...");
@@ -282,22 +282,24 @@ export const CommandModal: React.FC = observer(() => {
setSearchTerm={(newSearchTerm) => setSearchTerm(newSearchTerm)}
/>
)}
<Command.Group heading="Issue">
<Command.Item
onSelect={() => {
closePalette();
setTrackElement("Command Palette");
toggleCreateIssueModal(true);
}}
className="focus:bg-custom-background-80"
>
<div className="flex items-center gap-2 text-custom-text-200">
<LayersIcon className="h-3.5 w-3.5" />
Create new issue
</div>
<kbd>C</kbd>
</Command.Item>
</Command.Group>
{workspaceSlug && workspaceProjectIds && workspaceProjectIds.length > 0 && (
<Command.Group heading="Issue">
<Command.Item
onSelect={() => {
closePalette();
setTrackElement("Command Palette");
toggleCreateIssueModal(true);
}}
className="focus:bg-custom-background-80"
>
<div className="flex items-center gap-2 text-custom-text-200">
<LayersIcon className="h-3.5 w-3.5" />
Create new issue
</div>
<kbd>C</kbd>
</Command.Item>
</Command.Group>
)}
{workspaceSlug && (
<Command.Group heading="Project">
@@ -113,8 +113,6 @@ export const CommandPalette: FC = observer(() => {
const canPerformWorkspaceCreateActions = useCallback(
(showToast: boolean = true) => {
const isAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
console.log("currentWorkspaceRole", currentWorkspaceRole);
console.log("isAllowed", isAllowed);
if (!isAllowed && showToast)
setToast({
type: TOAST_TYPE.ERROR,
@@ -36,18 +36,18 @@ export const ShortcutsModal: FC<Props> = (props) => {
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 h-full w-full overflow-y-auto">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="h-full w-full">
<div className="my-10 flex items-center justify-center p-4 text-center sm:p-0 md:my-20">
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="my-10 flex items-center justify-center p-4 text-center sm:p-0 md:my-20">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative flex h-full items-center justify-center">
<div className="flex h-[61vh] w-full flex-col space-y-4 overflow-hidden rounded-lg bg-custom-background-100 p-5 shadow-custom-shadow-md transition-all sm:w-[28rem]">
<Dialog.Title as="h3" className="flex justify-between">
<span className="text-lg font-medium">Keyboard shortcuts</span>
@@ -71,9 +71,9 @@ export const ShortcutsModal: FC<Props> = (props) => {
</div>
<ShortcutCommandsList searchQuery={query} />
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
+1 -1
View File
@@ -144,7 +144,7 @@ export const ImagePickerPopover: React.FC<Props> = observer((props) => {
useEffect(() => {
if (!unsplashImages || value !== null) return;
onChange(unsplashImages[0].urls.regular);
onChange(unsplashImages[0]?.urls.regular);
}, [value, onChange, unsplashImages]);
const handleClose = () => {
@@ -201,7 +201,7 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
as="div"
className="flex h-52 w-full flex-col gap-1 overflow-y-auto text-custom-text-200 vertical-scrollbar scrollbar-sm"
>
{cycleIssues.length > 0 ? (
{cycle?.distribution?.assignees && cycle.distribution.assignees.length > 0 ? (
cycle.distribution?.assignees?.map((assignee, index) => {
if (assignee.assignee_id)
return (
@@ -246,8 +246,8 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
as="div"
className="flex h-52 w-full flex-col gap-1 overflow-y-auto text-custom-text-200 vertical-scrollbar scrollbar-sm"
>
{cycleIssues.length > 0 ? (
cycle.distribution?.labels?.map((label, index) => (
{cycle?.distribution?.labels && cycle.distribution.labels.length > 0 ? (
cycle.distribution.labels?.map((label, index) => (
<SingleProgressStats
key={label.label_id ?? `no-label-${index}`}
title={
+2 -2
View File
@@ -56,7 +56,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err.detail ?? "Error in creating cycle. Please try again.",
message: err?.detail ?? "Error in creating cycle. Please try again.",
});
captureCycleEvent({
eventName: CYCLE_CREATED,
@@ -90,7 +90,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err.detail ?? "Error in updating cycle. Please try again.",
message: err?.detail ?? "Error in updating cycle. Please try again.",
});
});
};
@@ -1,6 +1,6 @@
import Image from "next/image";
import { useTheme } from "next-themes";
import { LayoutGrid, Zap } from "lucide-react";
import { Home, Zap } from "lucide-react";
// images
import githubBlackImage from "/public/logos/github-black.png";
import githubWhiteImage from "/public/logos/github-white.png";
@@ -25,9 +25,7 @@ export const WorkspaceDashboardHeader = () => {
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink label="Dashboard" icon={<LayoutGrid className="h-4 w-4 text-custom-text-300" />} />
}
link={<BreadcrumbLink label="Home" icon={<Home className="h-4 w-4 text-custom-text-300" />} />}
/>
</Breadcrumbs>
</div>
@@ -1,6 +1,11 @@
import React from "react";
import Image from "next/image";
import Link from "next/link";
import { useTheme } from "next-themes";
// icons
import { UserCog2 } from "lucide-react";
// ui
import { getButtonStyling } from "@plane/ui";
// images
import instanceNotReady from "public/instance/plane-instance-not-ready.webp";
import PlaneBlackLogo from "public/plane-logos/black-horizontal-with-blue-logo.svg";
@@ -25,6 +30,10 @@ export const InstanceNotReady = () => {
<div className="flex w-full flex-col items-center gap-5 py-12 pb-20">
<h3 className="text-2xl font-medium">Your Plane instance isn{"'"}t ready yet</h3>
<p className="text-sm">Ask your Instance Admin to complete set-up first.</p>
<Link href="/god-mode" className={`${getButtonStyling("primary", "md")} mt-4`}>
<UserCog2 className="h-3.5 w-3.5" />
Get started
</Link>
</div>
</div>
</div>
@@ -73,7 +73,7 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
setToast({
title: "Error",
type: TOAST_TYPE.ERROR,
message: err.detail ?? "Failed to perform this action",
message: err?.detail ?? "Failed to perform this action",
});
});
}
@@ -143,7 +143,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
setToast({
title: "Error",
type: TOAST_TYPE.ERROR,
message: err.detail ?? "Failed to perform this action",
message: err?.detail ?? "Failed to perform this action",
});
});
}
@@ -1,20 +1,21 @@
import { useState } from "react";
import omit from "lodash/omit";
import { observer } from "mobx-react";
// hooks
import { Copy, Pencil, Trash2 } from "lucide-react";
// icons
import { Pencil, Trash2 } from "lucide-react";
// types
import { TIssue } from "@plane/types";
// ui
import { CustomMenu } from "@plane/ui";
// components
import { CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
// constant
import { EIssuesStoreType } from "@/constants/issue";
import { EUserProjectRoles } from "@/constants/project";
// hooks
import { useEventTracker, useIssues, useUser } from "@/hooks/store";
// ui
// components
// helpers
// types
import { IQuickActionProps } from "../list/list-view-types";
// constant
export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((props) => {
const { issue, handleDelete, handleUpdate, customActionButton, portalElement, readOnly = false } = props;
@@ -89,19 +90,6 @@ export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
</div>
</CustomMenu.MenuItem>
)}
{isEditingAllowed && (
<CustomMenu.MenuItem
onClick={() => {
setTrackElement(activeLayout);
setCreateUpdateIssueModal(true);
}}
>
<div className="flex items-center gap-2">
<Copy className="h-3 w-3" />
Make a copy
</div>
</CustomMenu.MenuItem>
)}
{isDeletingAllowed && (
<CustomMenu.MenuItem
onClick={() => {
@@ -84,7 +84,7 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
const payload = {
...changesMade,
name: changesMade?.name && changesMade?.name?.trim() === "" ? changesMade.name?.trim() : "Untitled",
name: changesMade?.name && changesMade?.name?.trim() !== "" ? changesMade.name?.trim() : "Untitled",
};
await issueDraftService
@@ -26,6 +26,7 @@ import { renderFormattedPayloadDate, getDate } from "@/helpers/date-time.helper"
import { getChangedIssuefields } from "@/helpers/issue.helper";
import { shouldRenderProject } from "@/helpers/project.helper";
import { useApplication, useEstimate, useIssueDetail, useMention, useProject, useWorkspace } from "@/hooks/store";
import { useProjectIssueProperties } from "@/hooks/use-project-issue-properties";
// services
import { AIService } from "@/services/ai.service";
import { FileService } from "@/services/file.service";
@@ -121,6 +122,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
// store hooks
const {
config: { envConfig },
router: { projectId: routeProjectId },
} = useApplication();
const { getProjectById } = useProject();
const { areEstimatesEnabledForProject } = useEstimate();
@@ -128,6 +130,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
const {
issue: { getIssueById },
} = useIssueDetail();
const { fetchCycles } = useProjectIssueProperties();
// form info
const {
formState: { errors, isDirty, isSubmitting, dirtyFields },
@@ -160,6 +163,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
parent_id: formData.parent_id,
});
}
if (projectId && routeProjectId !== projectId) fetchCycles(workspaceSlug, projectId);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [projectId]);
@@ -1,4 +1,5 @@
import React from "react";
import { observer } from "mobx-react";
import { RefreshCw } from "lucide-react";
import { TIssue } from "@plane/types";
// types
@@ -9,7 +10,7 @@ type Props = {
issueDetail?: TIssue;
};
export const IssueUpdateStatus: React.FC<Props> = (props) => {
export const IssueUpdateStatus: React.FC<Props> = observer((props) => {
const { isSubmitting, issueDetail } = props;
// hooks
const { getProjectById } = useProject();
@@ -33,4 +34,4 @@ export const IssueUpdateStatus: React.FC<Props> = (props) => {
</div>
</>
);
};
});
+2 -2
View File
@@ -68,7 +68,7 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err.detail ?? "Module could not be created. Please try again.",
message: err?.detail ?? "Module could not be created. Please try again.",
});
captureModuleEvent({
eventName: MODULE_CREATED,
@@ -99,7 +99,7 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err.detail ?? "Module could not be updated. Please try again.",
message: err?.detail ?? "Module could not be updated. Please try again.",
});
captureModuleEvent({
eventName: MODULE_UPDATED,
@@ -10,11 +10,11 @@ import {
ContrastIcon,
FileText,
LayersIcon,
LayoutGrid,
PenSquare,
Search,
Settings,
Bell,
Home,
} from "lucide-react";
import { IWorkspace } from "@plane/types";
import { Avatar, DiceIcon, PhotoFilterIcon } from "@plane/ui";
@@ -26,8 +26,8 @@ import projectEmoji from "public/emoji/project-emoji.svg";
const workspaceLinks = [
{
Icon: LayoutGrid,
name: "Dashboard",
Icon: Home,
name: "Home",
},
{
Icon: BarChart2,
@@ -209,8 +209,10 @@ export const CreateProjectForm: FC<Props> = observer((props) => {
[val.type]: logoValue,
});
}}
defaultIconColor={value.in_use === "icon" ? value.icon?.color : undefined}
defaultOpen={value.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON}
defaultIconColor={value.in_use && value.in_use === "icon" ? value.icon?.color : undefined}
defaultOpen={
value.in_use && value.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON
}
/>
)}
/>
@@ -224,7 +226,7 @@ export const CreateProjectForm: FC<Props> = observer((props) => {
control={control}
name="name"
rules={{
required: "Title is required",
required: "Name is required",
maxLength: {
value: 255,
message: "Title should be less than 255 characters",
@@ -238,7 +240,7 @@ export const CreateProjectForm: FC<Props> = observer((props) => {
value={value}
onChange={handleNameChange(onChange)}
hasError={Boolean(errors.name)}
placeholder="Project title"
placeholder="Project name"
className="w-full focus:border-blue-400"
tabIndex={1}
/>
+58 -35
View File
@@ -1,7 +1,7 @@
import { FC, useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
// icons
import { Lock } from "lucide-react";
import { Info, Lock } from "lucide-react";
import { IProject, IWorkspace } from "@plane/types";
// ui
import {
@@ -13,6 +13,7 @@ import {
setToast,
CustomEmojiIconPicker,
EmojiIconPickerTypes,
Tooltip,
} from "@plane/ui";
// components
import { ImagePickerPopover } from "@/components/core";
@@ -24,6 +25,7 @@ import { renderFormattedDate } from "@/helpers/date-time.helper";
// hooks
import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper";
import { useEventTracker, useProject } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
// services
import { ProjectService } from "@/services/project";
// types
@@ -42,6 +44,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
// store hooks
const { captureProjectEvent } = useEventTracker();
const { updateProject } = useProject();
const { isMobile } = usePlatformOS();
// form info
const {
handleSubmit,
@@ -166,8 +169,10 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
[val.type]: logoValue,
});
}}
defaultIconColor={value.in_use === "icon" ? value.icon?.color : undefined}
defaultOpen={value.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON}
defaultIconColor={value?.in_use && value.in_use === "icon" ? value?.icon?.color : undefined}
defaultOpen={
value.in_use && value.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON
}
disabled={!isAdmin}
/>
)}
@@ -227,6 +232,9 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
/>
)}
/>
<span className="text-xs text-red-500">
<>{errors?.name?.message}</>
</span>
</div>
<div className="flex flex-col gap-1">
<h4 className="text-sm">Description</h4>
@@ -247,39 +255,54 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
)}
/>
</div>
<div className="flex w-full items-center justify-between gap-10">
<div className="flex w-full justify-between gap-10">
<div className="flex w-1/2 flex-col gap-1">
<h4 className="text-sm">Identifier</h4>
<Controller
control={control}
name="identifier"
rules={{
required: "Identifier is required",
validate: (value) => /^[A-Z0-9]+$/.test(value.toUpperCase()) || "Identifier must be in uppercase.",
minLength: {
value: 1,
message: "Identifier must at least be of 1 character",
},
maxLength: {
value: 12,
message: "Identifier must at most be of 5 characters",
},
}}
render={({ field: { value, ref } }) => (
<Input
id="identifier"
name="identifier"
type="text"
value={value}
onChange={handleIdentifierChange}
ref={ref}
hasError={Boolean(errors.identifier)}
placeholder="Enter identifier"
className="w-full font-medium"
disabled={!isAdmin}
/>
)}
/>
<h4 className="text-sm">Project ID</h4>
<div className="relative">
<Controller
control={control}
name="identifier"
rules={{
required: "Project ID is required",
validate: (value) =>
/^[ÇŞĞIİÖÜA-Z0-9]+$/.test(value.toUpperCase()) ||
"Only Alphanumeric & Non-latin characters are allowed.",
minLength: {
value: 1,
message: "Project ID must at least be of 1 character",
},
maxLength: {
value: 5,
message: "Project ID must at most be of 5 characters",
},
}}
render={({ field: { value, ref } }) => (
<Input
id="identifier"
name="identifier"
type="text"
value={value}
onChange={handleIdentifierChange}
ref={ref}
hasError={Boolean(errors.identifier)}
placeholder="Enter Project ID"
className="w-full font-medium"
disabled={!isAdmin}
/>
)}
/>
<Tooltip
isMobile={isMobile}
tooltipContent="Helps you identify issues in the project uniquely, (e.g. APP-123). Max 5 characters."
className="text-sm"
position="right-top"
>
<Info className="absolute right-2 top-2.5 h-4 w-4 text-custom-text-400" />
</Tooltip>
</div>
<span className="text-xs text-red-500">
<>{errors?.identifier?.message}</>
</span>
</div>
<div className="flex w-1/2 flex-col gap-1">
<h4 className="text-sm">Network</h4>
+3 -3
View File
@@ -11,7 +11,7 @@ type Props = {
export const ProjectLogo: React.FC<Props> = (props) => {
const { className, logo } = props;
if (logo && logo.in_use === "icon" && logo.icon)
if (logo?.in_use === "icon" && logo?.icon)
return (
<span
style={{
@@ -23,12 +23,12 @@ export const ProjectLogo: React.FC<Props> = (props) => {
</span>
);
if (logo && logo.in_use === "emoji" && logo.emoji)
if (logo?.in_use === "emoji" && logo?.emoji)
return (
<span className={cn("text-base", className)}>
{logo.emoji.value?.split("-").map((emoji) => String.fromCodePoint(parseInt(emoji, 10)))}
</span>
);
return <span />;
return <></>;
};
+1 -1
View File
@@ -54,7 +54,7 @@ export const CreateUpdateProjectViewModal: FC<Props> = observer((props) => {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err.detail ?? "Something went wrong. Please try again.",
message: err?.detail ?? "Something went wrong. Please try again.",
})
);
};
+89
View File
@@ -0,0 +1,89 @@
import { useCycle, useEstimate, useLabel, useMember, useModule, useProjectState } from "./store";
export const useProjectIssueProperties = () => {
const { fetchProjectStates } = useProjectState();
const {
project: { fetchProjectMembers },
} = useMember();
const { fetchProjectLabels } = useLabel();
const { fetchAllCycles: fetchProjectAllCycles } = useCycle();
const { fetchModules: fetchProjectAllModules } = useModule();
const { fetchProjectEstimates } = useEstimate();
// fetching project states
const fetchStates = async (
workspaceSlug: string | string[] | undefined,
projectId: string | string[] | undefined
) => {
if (workspaceSlug && projectId) {
await fetchProjectStates(workspaceSlug.toString(), projectId.toString());
}
};
// fetching project members
const fetchMembers = async (
workspaceSlug: string | string[] | undefined,
projectId: string | string[] | undefined
) => {
if (workspaceSlug && projectId) {
await fetchProjectMembers(workspaceSlug.toString(), projectId.toString());
}
};
// fetching project labels
const fetchLabels = async (
workspaceSlug: string | string[] | undefined,
projectId: string | string[] | undefined
) => {
if (workspaceSlug && projectId) {
await fetchProjectLabels(workspaceSlug.toString(), projectId.toString());
}
};
// fetching project cycles
const fetchCycles = async (
workspaceSlug: string | string[] | undefined,
projectId: string | string[] | undefined
) => {
if (workspaceSlug && projectId) {
await fetchProjectAllCycles(workspaceSlug.toString(), projectId.toString());
}
};
// fetching project modules
const fetchModules = async (
workspaceSlug: string | string[] | undefined,
projectId: string | string[] | undefined
) => {
if (workspaceSlug && projectId) {
await fetchProjectAllModules(workspaceSlug.toString(), projectId.toString());
}
};
// fetching project estimates
const fetchEstimates = async (
workspaceSlug: string | string[] | undefined,
projectId: string | string[] | undefined
) => {
if (workspaceSlug && projectId) {
await fetchProjectEstimates(workspaceSlug.toString(), projectId.toString());
}
};
const fetchAll = async (workspaceSlug: string | string[] | undefined, projectId: string | string[] | undefined) => {
if (workspaceSlug && projectId) {
await fetchStates(workspaceSlug, projectId);
await fetchMembers(workspaceSlug, projectId);
await fetchLabels(workspaceSlug, projectId);
await fetchCycles(workspaceSlug, projectId);
await fetchModules(workspaceSlug, projectId);
await fetchEstimates(workspaceSlug, projectId);
}
};
return {
fetchAll,
fetchStates,
fetchMembers,
fetchLabels,
fetchCycles,
fetchModules,
fetchEstimates,
};
};
@@ -1,4 +1,5 @@
import React from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { useRouter } from "next/router";
// hooks
@@ -6,7 +7,7 @@ import { EUserWorkspaceRoles, WORKSPACE_SETTINGS_LINKS } from "@/constants/works
import { useUser } from "@/hooks/store";
// constants
export const WorkspaceSettingsSidebar = () => {
export const WorkspaceSettingsSidebar = observer(() => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
@@ -44,4 +45,4 @@ export const WorkspaceSettingsSidebar = () => {
</div>
</div>
);
};
});
+6 -2
View File
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
require("dotenv").config({ path: ".env" });
const { withSentryConfig } = require("@sentry/nextjs");
@@ -24,8 +25,11 @@ const nextConfig = {
output: "standalone",
};
if (parseInt(process.env.NEXT_PUBLIC_ENABLE_SENTRY || "0")) {
module.exports = withSentryConfig(nextConfig, { silent: true }, { hideSourceMaps: true });
if (parseInt(process.env.NEXT_PUBLIC_ENABLE_SENTRY || "0", 10)) {
module.exports = withSentryConfig(nextConfig,
{ silent: true, authToken: process.env.SENTRY_AUTH_TOKEN },
{ hideSourceMaps: true }
);
} else {
module.exports = nextConfig;
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "web",
"version": "0.16.0",
"version": "0.17.0",
"private": true,
"scripts": {
"dev": "turbo run develop",
@@ -28,7 +28,7 @@
"@plane/types": "*",
"@plane/ui": "*",
"@popperjs/core": "^2.11.8",
"@sentry/nextjs": "^7.85.0",
"@sentry/nextjs": "^7.108.0",
"axios": "^1.1.3",
"clsx": "^2.0.0",
"cmdk": "^0.2.0",
+1 -1
View File
@@ -14,7 +14,7 @@ import { NextPageWithLayout } from "@/lib/types";
const WorkspacePage: NextPageWithLayout = observer(() => {
const { currentWorkspace } = useWorkspace();
// derived values
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Dashboard` : undefined;
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Home` : undefined;
return (
<>
+1 -1
View File
@@ -220,7 +220,7 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => {
description="You can see here if someone invites you to a workspace."
image={emptyInvitation}
primaryButton={{
text: "Back to dashboard",
text: "Back to home",
onClick: () => router.push("/"),
}}
/>
+2 -2
View File
@@ -81,7 +81,7 @@ const WorkspaceInvitationPage: NextPageWithLayout = observer(() => {
title={`You are already a member of ${invitationDetail.workspace.name}`}
description="Your workspace is where you'll create projects, collaborate on your issues, and organize different streams of work in your Plane account."
>
<EmptySpaceItem Icon={Boxes} title="Continue to Dashboard" href="/" />
<EmptySpaceItem Icon={Boxes} title="Continue to home" href="/" />
</EmptySpace>
</>
) : (
@@ -105,7 +105,7 @@ const WorkspaceInvitationPage: NextPageWithLayout = observer(() => {
{!currentUser ? (
<EmptySpaceItem Icon={User2} title="Sign in to continue" href="/" />
) : (
<EmptySpaceItem Icon={Boxes} title="Continue to Dashboard" href="/" />
<EmptySpaceItem Icon={Boxes} title="Continue to home" href="/" />
)}
<EmptySpaceItem Icon={Star} title="Star us on GitHub" href="https://github.com/makeplane" />
<EmptySpaceItem
+101 -90
View File
@@ -2149,35 +2149,46 @@
dependencies:
"@daybrush/utils" "^1.4.0"
"@sentry-internal/feedback@7.91.0":
version "7.91.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-7.91.0.tgz#be09e5aec2959fcf503e2cf78496d5e2d263bc5a"
integrity sha512-SJKTSaz68F5YIwF79EttBm915M2LnacgZMYRnRumyTmMKnebGhYQLwWbZdpaDvOa1U18dgRajDX8Qed/8A3tXw==
"@sentry-internal/feedback@7.108.0":
version "7.108.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-7.108.0.tgz#7033352abd304f1383ec47640e056a0dfd5132b7"
integrity sha512-8JcgZEnk1uWrXJhsd3iRvFtEiVeaWOEhN0NZwhwQXHfvODqep6JtrkY1yCIyxbpA37aZmrPc2JhyotRERGfUjg==
dependencies:
"@sentry/core" "7.91.0"
"@sentry/types" "7.91.0"
"@sentry/utils" "7.91.0"
"@sentry/core" "7.108.0"
"@sentry/types" "7.108.0"
"@sentry/utils" "7.108.0"
"@sentry-internal/tracing@7.91.0":
version "7.91.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.91.0.tgz#fbb6e1e3383e1eeee08633384e004da73ac1c37d"
integrity sha512-JH5y6gs6BS0its7WF2DhySu7nkhPDfZcdpAXldxzIlJpqFkuwQKLU5nkYJpiIyZz1NHYYtW5aum2bV2oCOdDRA==
"@sentry-internal/replay-canvas@7.108.0":
version "7.108.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-7.108.0.tgz#641133c19c0e1c423617b8d791f53d6cd0b0a862"
integrity sha512-R5tvjGqWUV5vSk0N1eBgVW7wIADinrkfDEBZ9FyKP2mXHBobsyNGt30heJDEqYmVqluRqjU2NuIRapsnnrpGnA==
dependencies:
"@sentry/core" "7.91.0"
"@sentry/types" "7.91.0"
"@sentry/utils" "7.91.0"
"@sentry/core" "7.108.0"
"@sentry/replay" "7.108.0"
"@sentry/types" "7.108.0"
"@sentry/utils" "7.108.0"
"@sentry/browser@7.91.0":
version "7.91.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.91.0.tgz#de3b9ae3ca7716a35cfabc97ac376944a67e6e34"
integrity sha512-lJv3x/xekzC/biiyAsVCioq2XnKNOZhI6jY3ZzLJZClYV8eKRi7D3KCsHRvMiCdGak1d/6sVp8F4NYY+YiWy1Q==
"@sentry-internal/tracing@7.108.0":
version "7.108.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.108.0.tgz#d1e660701fb860cfae72b6ebfa8fb267533421fa"
integrity sha512-zuK5XsTsb+U+hgn3SPetYDAogrXsM16U/LLoMW7+TlC6UjlHGYQvmX3o+M2vntejoU1QZS8m1bCAZSMWEypAEw==
dependencies:
"@sentry-internal/feedback" "7.91.0"
"@sentry-internal/tracing" "7.91.0"
"@sentry/core" "7.91.0"
"@sentry/replay" "7.91.0"
"@sentry/types" "7.91.0"
"@sentry/utils" "7.91.0"
"@sentry/core" "7.108.0"
"@sentry/types" "7.108.0"
"@sentry/utils" "7.108.0"
"@sentry/browser@7.108.0":
version "7.108.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.108.0.tgz#b95810bb6572b63781f253615896f5afb1a3a5c0"
integrity sha512-FNpzsdTvGvdHJMUelqEouUXMZU7jC+dpN7CdT6IoHVVFEkoAgrjMVUhXZoQ/dmCkdKWHmFSQhJ8Fm6V+e9Aq0A==
dependencies:
"@sentry-internal/feedback" "7.108.0"
"@sentry-internal/replay-canvas" "7.108.0"
"@sentry-internal/tracing" "7.108.0"
"@sentry/core" "7.108.0"
"@sentry/replay" "7.108.0"
"@sentry/types" "7.108.0"
"@sentry/utils" "7.108.0"
"@sentry/cli@^1.77.1":
version "1.77.1"
@@ -2191,95 +2202,95 @@
proxy-from-env "^1.1.0"
which "^2.0.2"
"@sentry/core@7.91.0":
version "7.91.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.91.0.tgz#229334d7f03dd5d90a17495e61ce4215ab730b2a"
integrity sha512-tu+gYq4JrTdrR+YSh5IVHF0fJi/Pi9y0HZ5H9HnYy+UMcXIotxf6hIEaC6ZKGeLWkGXffz2gKpQLe/g6vy/lPA==
"@sentry/core@7.108.0":
version "7.108.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.108.0.tgz#a27e8d6f85f59c5730ce86071474f15ac899fde0"
integrity sha512-I/VNZCFgLASxHZaD0EtxZRM34WG9w2gozqgrKGNMzAymwmQ3K9g/1qmBy4e6iS3YRptb7J5UhQkZQHrcwBbjWQ==
dependencies:
"@sentry/types" "7.91.0"
"@sentry/utils" "7.91.0"
"@sentry/types" "7.108.0"
"@sentry/utils" "7.108.0"
"@sentry/integrations@7.91.0":
version "7.91.0"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.91.0.tgz#b0496c9e404783bc433b1d2464d8f9aa180ebc8e"
integrity sha512-LGRfb+WfG3FaWHtDnJIhtupweat0imCQr2z/5SSbQKzqxHhtlaEU+9IExBmBdzq90n4lRBaVQHA3zGuU02uOhg==
"@sentry/integrations@7.108.0":
version "7.108.0"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.108.0.tgz#307c61966208f2a126c1a93e712277c8f86a3d3c"
integrity sha512-b/WbK1f3x2rQ4aJJSA4VSwpBXrXFm1Nzrca3Y9qW0MI1wjZEYsDDrh9m6ulLdVBl4YDc2VqYp1COwU/NjuHlog==
dependencies:
"@sentry/core" "7.91.0"
"@sentry/types" "7.91.0"
"@sentry/utils" "7.91.0"
"@sentry/core" "7.108.0"
"@sentry/types" "7.108.0"
"@sentry/utils" "7.108.0"
localforage "^1.8.1"
"@sentry/nextjs@^7.85.0":
version "7.91.0"
resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-7.91.0.tgz#42eb3af10ff230e8a3fe9f0e50cdbac94b7d290e"
integrity sha512-wE83+OTEH4yYnDrhMw9eVEARSfZc6xY5qJb9xyYm5rW3+gVjNQZQaUY+wkM61Xdo0T35BN+7U4T88HbwzGeMqA==
"@sentry/nextjs@^7.108.0":
version "7.108.0"
resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-7.108.0.tgz#31d903d75bbf4b4530046360daff4d51dcf62f53"
integrity sha512-etBrMSLRbNAzozetBeL6D+lR9lRAyHmV7NUBGCX9lQvgmcdxkQa15EX8pIKjsMejZ8xAZNsqYVIByIs67A77rg==
dependencies:
"@rollup/plugin-commonjs" "24.0.0"
"@sentry/core" "7.91.0"
"@sentry/integrations" "7.91.0"
"@sentry/node" "7.91.0"
"@sentry/react" "7.91.0"
"@sentry/types" "7.91.0"
"@sentry/utils" "7.91.0"
"@sentry/vercel-edge" "7.91.0"
"@sentry/core" "7.108.0"
"@sentry/integrations" "7.108.0"
"@sentry/node" "7.108.0"
"@sentry/react" "7.108.0"
"@sentry/types" "7.108.0"
"@sentry/utils" "7.108.0"
"@sentry/vercel-edge" "7.108.0"
"@sentry/webpack-plugin" "1.21.0"
chalk "3.0.0"
resolve "1.22.8"
rollup "2.78.0"
stacktrace-parser "^0.1.10"
"@sentry/node@7.91.0":
version "7.91.0"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.91.0.tgz#26bf13c3daf988f9725afd1a3cc38ba2ff90d62a"
integrity sha512-hTIfSQxD7L+AKIqyjoq8CWBRkEQrrMZmA3GSZgPI5JFWBHgO0HBo5TH/8TU81oEJh6kqqHAl2ObMhmcnaFqlzg==
"@sentry/node@7.108.0":
version "7.108.0"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.108.0.tgz#ed113dc1d39aaae32f7e9f681dcea41cf28eb5f1"
integrity sha512-pMxc9txnDDkU4Z8k2Uw/DPSLPehNtWV3mjJ3+my0AMORGYrXLkJI93tddlE5z/7k+GEJdj1HsOLgxUN0OU+HGA==
dependencies:
"@sentry-internal/tracing" "7.91.0"
"@sentry/core" "7.91.0"
"@sentry/types" "7.91.0"
"@sentry/utils" "7.91.0"
https-proxy-agent "^5.0.0"
"@sentry-internal/tracing" "7.108.0"
"@sentry/core" "7.108.0"
"@sentry/types" "7.108.0"
"@sentry/utils" "7.108.0"
"@sentry/react@7.91.0":
version "7.91.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.91.0.tgz#620e6ce9452af025d2cc1b2eca3dd1dd730dc439"
integrity sha512-7JH2rWaX3WKHHvBcZQ4f/KnkYIXTf7hMojRFncUwPocdtDlhJw/JUvjAYNpEysixXIgsMes3B32lmtZjGjRhwQ==
"@sentry/react@7.108.0":
version "7.108.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.108.0.tgz#26a50324f6d7a9576f3753c099e7bcd8def94f3c"
integrity sha512-C60arh5/gtO42eMU9l34aWlKDLZUO+1j1goaEf/XRSwUcyJS9tbJrs+mT4nbKxUsEG714It2gRbfSEvh1eXmCg==
dependencies:
"@sentry/browser" "7.91.0"
"@sentry/types" "7.91.0"
"@sentry/utils" "7.91.0"
"@sentry/browser" "7.108.0"
"@sentry/core" "7.108.0"
"@sentry/types" "7.108.0"
"@sentry/utils" "7.108.0"
hoist-non-react-statics "^3.3.2"
"@sentry/replay@7.91.0":
version "7.91.0"
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.91.0.tgz#95077868aee3c3cc670affe13156434f858e1755"
integrity sha512-XwbesnLLNtaVXKtDoyBB96GxJuhGi9zy3a662Ba/McmumCnkXrMQYpQPh08U7MgkTyDRgjDwm7PXDhiKpcb03g==
"@sentry/replay@7.108.0":
version "7.108.0"
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.108.0.tgz#baa679bd19b4e3729e607d3f84cff5048aeb3415"
integrity sha512-jo8fDOzcZJclP1+4n9jUtVxTlBFT9hXwxhAMrhrt70FV/nfmCtYQMD3bzIj79nwbhUtFP6pN39JH1o7Xqt1hxQ==
dependencies:
"@sentry-internal/tracing" "7.91.0"
"@sentry/core" "7.91.0"
"@sentry/types" "7.91.0"
"@sentry/utils" "7.91.0"
"@sentry-internal/tracing" "7.108.0"
"@sentry/core" "7.108.0"
"@sentry/types" "7.108.0"
"@sentry/utils" "7.108.0"
"@sentry/types@7.91.0":
version "7.91.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.91.0.tgz#5b68954e08986fecb0d4bef168df58eef62c32c7"
integrity sha512-bcQnb7J3P3equbCUc+sPuHog2Y47yGD2sCkzmnZBjvBT0Z1B4f36fI/5WjyZhTjLSiOdg3F2otwvikbMjmBDew==
"@sentry/types@7.108.0":
version "7.108.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.108.0.tgz#5ceb959c4dabe511fc441fec8c2465f2d624900f"
integrity sha512-bKtHITmBN3kqtqE5eVvL8mY8znM05vEodENwRpcm6TSrrBjC2RnwNWVwGstYDdHpNfFuKwC8mLY9bgMJcENo8g==
"@sentry/utils@7.91.0":
version "7.91.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.91.0.tgz#3b1a94c053c885877908cd3e1365e3d23e21a73f"
integrity sha512-fvxjrEbk6T6Otu++Ax9ntlQ0sGRiwSC179w68aC3u26Wr30FAIRKqHTCCdc2jyWk7Gd9uWRT/cq+g8NG/8BfSg==
"@sentry/utils@7.108.0":
version "7.108.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.108.0.tgz#0231042956aed2ef35809891592238530349dfd9"
integrity sha512-a45yEFD5qtgZaIFRAcFkG8C8lnDzn6t4LfLXuV4OafGAy/3ZAN3XN8wDnrruHkiUezSSANGsLg3bXaLW/JLvJw==
dependencies:
"@sentry/types" "7.91.0"
"@sentry/types" "7.108.0"
"@sentry/vercel-edge@7.91.0":
version "7.91.0"
resolved "https://registry.yarnpkg.com/@sentry/vercel-edge/-/vercel-edge-7.91.0.tgz#df67ee39d10570b71eccf831a181c064974d62b1"
integrity sha512-CounqhXPwFh67zf6L/q4ACBHHqknT6YY9LdgIAnUd0GGgHzrJPyKcthvh8Je4lNdpo5LFg2gnR+6g6JS8DDYDQ==
"@sentry/vercel-edge@7.108.0":
version "7.108.0"
resolved "https://registry.yarnpkg.com/@sentry/vercel-edge/-/vercel-edge-7.108.0.tgz#c5ca35094bc46029ec5a72f7ee09fd3705582baa"
integrity sha512-dUuUEswaVIzsJnzTfaJxrvkfOowrlJxxHo2AybPDym2rob7CdaLdDJIYJa83X7QeAKMkTgLny/gYSQYC0E4UyA==
dependencies:
"@sentry-internal/tracing" "7.91.0"
"@sentry/core" "7.91.0"
"@sentry/types" "7.91.0"
"@sentry/utils" "7.91.0"
"@sentry-internal/tracing" "7.108.0"
"@sentry/core" "7.108.0"
"@sentry/types" "7.108.0"
"@sentry/utils" "7.108.0"
"@sentry/webpack-plugin@1.21.0":
version "1.21.0"
@@ -2722,7 +2733,7 @@
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^18.2.42":
"@types/react@*", "@types/react@18.2.42", "@types/react@^18.2.42":
version "18.2.42"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.42.tgz#6f6b11a904f6d96dda3c2920328a97011a00aba7"
integrity sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA==