diff --git a/.env.example b/.env.example index 90070de19..71a9074a6 100644 --- a/.env.example +++ b/.env.example @@ -1,14 +1,12 @@ # Database Settings -PGUSER="plane" -PGPASSWORD="plane" -PGHOST="plane-db" -PGDATABASE="plane" -DATABASE_URL=postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}/${PGDATABASE} +POSTGRES_USER="plane" +POSTGRES_PASSWORD="plane" +POSTGRES_DB="plane" +PGDATA="/var/lib/postgresql/data" # Redis Settings REDIS_HOST="plane-redis" REDIS_PORT="6379" -REDIS_URL="redis://${REDIS_HOST}:6379/" # AWS Settings AWS_REGION="" diff --git a/.github/ISSUE_TEMPLATE/--bug-report.yaml b/.github/ISSUE_TEMPLATE/--bug-report.yaml index 4240c10c5..5d19be11c 100644 --- a/.github/ISSUE_TEMPLATE/--bug-report.yaml +++ b/.github/ISSUE_TEMPLATE/--bug-report.yaml @@ -1,7 +1,8 @@ name: Bug report description: Create a bug report to help us improve Plane title: "[bug]: " -labels: [bug, need testing] +labels: [🐛bug] +assignees: [srinivaspendem, pushya-plane] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/--feature-request.yaml b/.github/ISSUE_TEMPLATE/--feature-request.yaml index b7ba11679..941fbef87 100644 --- a/.github/ISSUE_TEMPLATE/--feature-request.yaml +++ b/.github/ISSUE_TEMPLATE/--feature-request.yaml @@ -1,7 +1,8 @@ name: Feature request description: Suggest a feature to improve Plane title: "[feature]: " -labels: [feature] +labels: [✨feature] +assignees: [srinivaspendem, pushya-plane] body: - type: markdown attributes: diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml index db65fbc2c..38694a62e 100644 --- a/.github/workflows/build-branch.yml +++ b/.github/workflows/build-branch.yml @@ -1,61 +1,30 @@ name: Branch Build on: - pull_request: - types: - - closed + workflow_dispatch: + inputs: + branch_name: + description: "Branch Name" + required: true + default: "preview" + push: branches: - master - preview - - qa - develop - - release-* release: types: [released, prereleased] env: - TARGET_BRANCH: ${{ github.event.pull_request.base.ref || github.event.release.target_commitish }} + TARGET_BRANCH: ${{ inputs.branch_name || github.ref_name || github.event.release.target_commitish }} jobs: branch_build_setup: - if: ${{ (github.event_name == 'pull_request' && github.event.action =='closed' && github.event.pull_request.merged == true) || github.event_name == 'release' }} name: Build-Push Web/Space/API/Proxy Docker Image runs-on: ubuntu-20.04 - steps: - name: Check out the repo uses: actions/checkout@v3.3.0 - - - name: Uploading Proxy Source - uses: actions/upload-artifact@v3 - with: - name: proxy-src-code - path: ./nginx - - name: Uploading Backend Source - uses: actions/upload-artifact@v3 - with: - name: backend-src-code - path: ./apiserver - - name: Uploading Web Source - uses: actions/upload-artifact@v3 - with: - name: web-src-code - path: | - ./ - !./apiserver - !./nginx - !./deploy - !./space - - name: Uploading Space Source - uses: actions/upload-artifact@v3 - with: - name: space-src-code - path: | - ./ - !./apiserver - !./nginx - !./deploy - !./web outputs: gh_branch_name: ${{ env.TARGET_BRANCH }} @@ -63,33 +32,38 @@ jobs: runs-on: ubuntu-20.04 needs: [branch_build_setup] env: - FRONTEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend${{ secrets.DOCKER_REPO_SUFFIX || '' }}:${{ needs.branch_build_setup.outputs.gh_branch_name }} + FRONTEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:${{ needs.branch_build_setup.outputs.gh_branch_name }} steps: - - name: Set Frontend Docker Tag + - name: Set Frontend Docker Tag run: | if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend${{ secrets.DOCKER_REPO_SUFFIX || '' }}:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend${{ secrets.DOCKER_REPO_SUFFIX || '' }}:${{ github.event.release.tag_name }} + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:${{ github.event.release.tag_name }} elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend${{ secrets.DOCKER_REPO_SUFFIX || '' }}:stable + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:stable else TAG=${{ env.FRONTEND_TAG }} fi echo "FRONTEND_TAG=${TAG}" >> $GITHUB_ENV + - name: Docker Setup QEMU + uses: docker/setup-qemu-action@v3.0.0 + - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2.5.0 + uses: docker/setup-buildx-action@v3.0.0 + with: + platforms: linux/amd64,linux/arm64 + buildkitd-flags: "--allow-insecure-entitlement security.insecure" - name: Login to Docker Hub - uses: docker/login-action@v2.1.0 + uses: docker/login-action@v3.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Downloading Web Source Code - uses: actions/download-artifact@v3 - with: - name: web-src-code + + - name: Check out the repo + uses: actions/checkout@v4.1.1 - name: Build and Push Frontend to Docker Container Registry - uses: docker/build-push-action@v4.0.0 + uses: docker/build-push-action@v5.1.0 with: context: . file: ./web/Dockerfile.web @@ -105,33 +79,39 @@ jobs: runs-on: ubuntu-20.04 needs: [branch_build_setup] env: - SPACE_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-space${{ secrets.DOCKER_REPO_SUFFIX || '' }}:${{ needs.branch_build_setup.outputs.gh_branch_name }} + SPACE_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-space:${{ needs.branch_build_setup.outputs.gh_branch_name }} steps: - - name: Set Space Docker Tag + - name: Set Space Docker Tag run: | if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space${{ secrets.DOCKER_REPO_SUFFIX || '' }}:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-space${{ secrets.DOCKER_REPO_SUFFIX || '' }}:${{ github.event.release.tag_name }} + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-space:${{ github.event.release.tag_name }} elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space${{ secrets.DOCKER_REPO_SUFFIX || '' }}:stable + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space:stable else TAG=${{ env.SPACE_TAG }} fi echo "SPACE_TAG=${TAG}" >> $GITHUB_ENV + + - name: Docker Setup QEMU + uses: docker/setup-qemu-action@v3.0.0 + - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2.5.0 + uses: docker/setup-buildx-action@v3.0.0 + with: + platforms: linux/amd64,linux/arm64 + buildkitd-flags: "--allow-insecure-entitlement security.insecure" - name: Login to Docker Hub - uses: docker/login-action@v2.1.0 + uses: docker/login-action@v3.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Downloading Space Source Code - uses: actions/download-artifact@v3 - with: - name: space-src-code + + - name: Check out the repo + uses: actions/checkout@v4.1.1 - name: Build and Push Space to Docker Hub - uses: docker/build-push-action@v4.0.0 + uses: docker/build-push-action@v5.1.0 with: context: . file: ./space/Dockerfile.space @@ -147,36 +127,42 @@ jobs: runs-on: ubuntu-20.04 needs: [branch_build_setup] env: - BACKEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-backend${{ secrets.DOCKER_REPO_SUFFIX || '' }}:${{ needs.branch_build_setup.outputs.gh_branch_name }} + BACKEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:${{ needs.branch_build_setup.outputs.gh_branch_name }} steps: - - name: Set Backend Docker Tag + - name: Set Backend Docker Tag run: | if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend${{ secrets.DOCKER_REPO_SUFFIX || '' }}:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-backend${{ secrets.DOCKER_REPO_SUFFIX || '' }}:${{ github.event.release.tag_name }} + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:${{ github.event.release.tag_name }} elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend${{ secrets.DOCKER_REPO_SUFFIX || '' }}:stable + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:stable else TAG=${{ env.BACKEND_TAG }} fi echo "BACKEND_TAG=${TAG}" >> $GITHUB_ENV + + - name: Docker Setup QEMU + uses: docker/setup-qemu-action@v3.0.0 + - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2.5.0 + uses: docker/setup-buildx-action@v3.0.0 + with: + platforms: linux/amd64,linux/arm64 + buildkitd-flags: "--allow-insecure-entitlement security.insecure" - name: Login to Docker Hub - uses: docker/login-action@v2.1.0 + uses: docker/login-action@v3.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Downloading Backend Source Code - uses: actions/download-artifact@v3 - with: - name: backend-src-code + + - name: Check out the repo + uses: actions/checkout@v4.1.1 - name: Build and Push Backend to Docker Hub - uses: docker/build-push-action@v4.0.0 + uses: docker/build-push-action@v5.1.0 with: - context: . - file: ./Dockerfile.api + context: ./apiserver + file: ./apiserver/Dockerfile.api platforms: linux/amd64 push: true tags: ${{ env.BACKEND_TAG }} @@ -189,37 +175,42 @@ jobs: runs-on: ubuntu-20.04 needs: [branch_build_setup] env: - PROXY_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy${{ secrets.DOCKER_REPO_SUFFIX || '' }}:${{ needs.branch_build_setup.outputs.gh_branch_name }} + PROXY_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:${{ needs.branch_build_setup.outputs.gh_branch_name }} steps: - - name: Set Proxy Docker Tag + - name: Set Proxy Docker Tag run: | if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy${{ secrets.DOCKER_REPO_SUFFIX || '' }}:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy${{ secrets.DOCKER_REPO_SUFFIX || '' }}:${{ github.event.release.tag_name }} + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:${{ github.event.release.tag_name }} elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then - TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy${{ secrets.DOCKER_REPO_SUFFIX || '' }}:stable + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:stable else TAG=${{ env.PROXY_TAG }} fi echo "PROXY_TAG=${TAG}" >> $GITHUB_ENV + + - name: Docker Setup QEMU + uses: docker/setup-qemu-action@v3.0.0 + - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2.5.0 + uses: docker/setup-buildx-action@v3.0.0 + with: + platforms: linux/amd64,linux/arm64 + buildkitd-flags: "--allow-insecure-entitlement security.insecure" - name: Login to Docker Hub - uses: docker/login-action@v2.1.0 + uses: docker/login-action@v3.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Downloading Proxy Source Code - uses: actions/download-artifact@v3 - with: - name: proxy-src-code + - name: Check out the repo + uses: actions/checkout@v4.1.1 - name: Build and Push Plane-Proxy to Docker Hub - uses: docker/build-push-action@v4.0.0 + uses: docker/build-push-action@v5.1.0 with: - context: . - file: ./Dockerfile + context: ./nginx + file: ./nginx/Dockerfile platforms: linux/amd64 tags: ${{ env.PROXY_TAG }} push: true diff --git a/.github/workflows/build-test-pull-request.yml b/.github/workflows/build-test-pull-request.yml index fd5d5ad03..296e965d7 100644 --- a/.github/workflows/build-test-pull-request.yml +++ b/.github/workflows/build-test-pull-request.yml @@ -25,7 +25,7 @@ jobs: - name: Get changed files id: changed-files - uses: tj-actions/changed-files@v38 + uses: tj-actions/changed-files@v41 with: files_yaml: | apiserver: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 29fbde453..9f6ab1bfb 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,10 +2,10 @@ name: "CodeQL" on: push: - branches: [ 'develop', 'hot-fix', 'stage-release' ] + branches: [ 'develop', 'preview', 'master' ] pull_request: # The branches below must be a subset of the branches above - branches: [ 'develop' ] + branches: [ 'develop', 'preview', 'master' ] schedule: - cron: '53 19 * * 5' diff --git a/.github/workflows/create-sync-pr.yml b/.github/workflows/create-sync-pr.yml index 5b5f958d3..47a85f3ba 100644 --- a/.github/workflows/create-sync-pr.yml +++ b/.github/workflows/create-sync-pr.yml @@ -1,25 +1,23 @@ name: Create Sync Action on: - pull_request: + workflow_dispatch: + push: branches: - - preview - types: - - closed -env: - SOURCE_BRANCH_NAME: ${{github.event.pull_request.base.ref}} + - preview + +env: + SOURCE_BRANCH_NAME: ${{ github.ref_name }} jobs: - create_pr: - # Only run the job when a PR is merged - if: github.event.pull_request.merged == true + sync_changes: runs-on: ubuntu-latest permissions: pull-requests: write contents: read steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v4.1.1 with: persist-credentials: false fetch-depth: 0 @@ -43,4 +41,4 @@ jobs: git checkout $SOURCE_BRANCH git remote add target-origin "https://$GH_TOKEN@github.com/$TARGET_REPO.git" - git push target-origin $SOURCE_BRANCH:$TARGET_BRANCH \ No newline at end of file + git push target-origin $SOURCE_BRANCH:$TARGET_BRANCH diff --git a/README.md b/README.md index 5b96dbf6c..b509fd6f6 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Meet [Plane](https://plane.so). An open-source software development tool to mana > Plane is still in its early days, not everything will be perfect yet, and hiccups may happen. Please let us know of any suggestions, ideas, or bugs that you encounter on our [Discord](https://discord.com/invite/A92xrEGCge) or GitHub issues, and we will use your feedback to improve on our upcoming releases. -The easiest way to get started with Plane is by creating a [Plane Cloud](https://app.plane.so) account. Plane Cloud offers a hosted solution for Plane. If you prefer to self-host Plane, please refer to our [deployment documentation](https://docs.plane.so/self-hosting/docker-compose). +The easiest way to get started with Plane is by creating a [Plane Cloud](https://app.plane.so) account. Plane Cloud offers a hosted solution for Plane. If you prefer to self-host Plane, please refer to our [deployment documentation](https://docs.plane.so/docker-compose). ## ⚡️ Contributors Quick Start @@ -63,7 +63,7 @@ Thats it! ## 🍙 Self Hosting -For self hosting environment setup, visit the [Self Hosting](https://docs.plane.so/self-hosting/docker-compose) documentation page +For self hosting environment setup, visit the [Self Hosting](https://docs.plane.so/docker-compose) documentation page ## 🚀 Features diff --git a/apiserver/.env.example b/apiserver/.env.example index 37178b398..42b0e32e5 100644 --- a/apiserver/.env.example +++ b/apiserver/.env.example @@ -8,11 +8,11 @@ SENTRY_DSN="" SENTRY_ENVIRONMENT="development" # Database Settings -PGUSER="plane" -PGPASSWORD="plane" -PGHOST="plane-db" -PGDATABASE="plane" -DATABASE_URL=postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}/${PGDATABASE} +POSTGRES_USER="plane" +POSTGRES_PASSWORD="plane" +POSTGRES_HOST="plane-db" +POSTGRES_DB="plane" +DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}/${POSTGRES_DB} # Oauth variables GOOGLE_CLIENT_ID="" @@ -39,9 +39,6 @@ OPENAI_API_BASE="https://api.openai.com/v1" # deprecated OPENAI_API_KEY="sk-" # deprecated GPT_ENGINE="gpt-3.5-turbo" # deprecated -# Github -GITHUB_CLIENT_SECRET="" # For fetching release notes - # Settings related to Docker DOCKERIZED=1 # deprecated diff --git a/apiserver/Dockerfile.dev b/apiserver/Dockerfile.dev index cb2d1ca28..bd6684fd5 100644 --- a/apiserver/Dockerfile.dev +++ b/apiserver/Dockerfile.dev @@ -33,15 +33,10 @@ RUN pip install -r requirements/local.txt --compile --no-cache-dir RUN addgroup -S plane && \ adduser -S captain -G plane -RUN chown captain.plane /code +COPY . . -USER captain - -# Add in Django deps and generate Django's static files - -USER root - -# RUN chmod +x ./bin/takeoff ./bin/worker ./bin/beat +RUN chown -R captain.plane /code +RUN chmod -R +x /code/bin RUN chmod -R 777 /code USER captain diff --git a/apiserver/back_migration.py b/apiserver/back_migration.py index c04ee7771..a0e45416a 100644 --- a/apiserver/back_migration.py +++ b/apiserver/back_migration.py @@ -26,7 +26,9 @@ def update_description(): updated_issues.append(issue) Issue.objects.bulk_update( - updated_issues, ["description_html", "description_stripped"], batch_size=100 + updated_issues, + ["description_html", "description_stripped"], + batch_size=100, ) print("Success") except Exception as e: @@ -40,7 +42,9 @@ def update_comments(): updated_issue_comments = [] for issue_comment in issue_comments: - issue_comment.comment_html = f"
{issue_comment.comment_stripped}
" + issue_comment.comment_html = ( + f"{issue_comment.comment_stripped}
" + ) updated_issue_comments.append(issue_comment) IssueComment.objects.bulk_update( @@ -99,7 +103,9 @@ def updated_issue_sort_order(): issue.sort_order = issue.sequence_id * random.randint(100, 500) updated_issues.append(issue) - Issue.objects.bulk_update(updated_issues, ["sort_order"], batch_size=100) + Issue.objects.bulk_update( + updated_issues, ["sort_order"], batch_size=100 + ) print("Success") except Exception as e: print(e) @@ -137,7 +143,9 @@ def update_project_cover_images(): project.cover_image = project_cover_images[random.randint(0, 19)] updated_projects.append(project) - Project.objects.bulk_update(updated_projects, ["cover_image"], batch_size=100) + Project.objects.bulk_update( + updated_projects, ["cover_image"], batch_size=100 + ) print("Success") except Exception as e: print(e) @@ -186,7 +194,9 @@ def update_label_color(): def create_slack_integration(): try: - _ = Integration.objects.create(provider="slack", network=2, title="Slack") + _ = Integration.objects.create( + provider="slack", network=2, title="Slack" + ) print("Success") except Exception as e: print(e) @@ -212,12 +222,16 @@ def update_integration_verified(): def update_start_date(): try: - issues = Issue.objects.filter(state__group__in=["started", "completed"]) + issues = Issue.objects.filter( + state__group__in=["started", "completed"] + ) updated_issues = [] for issue in issues: issue.start_date = issue.created_at.date() updated_issues.append(issue) - Issue.objects.bulk_update(updated_issues, ["start_date"], batch_size=500) + Issue.objects.bulk_update( + updated_issues, ["start_date"], batch_size=500 + ) print("Success") except Exception as e: print(e) diff --git a/apiserver/bin/beat b/apiserver/bin/beat old mode 100644 new mode 100755 index 45d357442..3a9602a9e --- a/apiserver/bin/beat +++ b/apiserver/bin/beat @@ -2,4 +2,7 @@ set -e python manage.py wait_for_db +# Wait for migrations +python manage.py wait_for_migrations +# Run the processes celery -A plane beat -l info \ No newline at end of file diff --git a/apiserver/bin/takeoff b/apiserver/bin/takeoff index 0ec2e495c..efea53f87 100755 --- a/apiserver/bin/takeoff +++ b/apiserver/bin/takeoff @@ -1,7 +1,8 @@ #!/bin/bash set -e python manage.py wait_for_db -python manage.py migrate +# Wait for migrations +python manage.py wait_for_migrations # Create the default bucket #!/bin/bash diff --git a/apiserver/bin/takeoff.local b/apiserver/bin/takeoff.local index b89c20874..8f62370ec 100755 --- a/apiserver/bin/takeoff.local +++ b/apiserver/bin/takeoff.local @@ -1,7 +1,8 @@ #!/bin/bash set -e python manage.py wait_for_db -python manage.py migrate +# Wait for migrations +python manage.py wait_for_migrations # Create the default bucket #!/bin/bash diff --git a/apiserver/bin/worker b/apiserver/bin/worker index 9d2da1254..a70b5f77c 100755 --- a/apiserver/bin/worker +++ b/apiserver/bin/worker @@ -2,4 +2,7 @@ set -e python manage.py wait_for_db +# Wait for migrations +python manage.py wait_for_migrations +# Run the processes celery -A plane worker -l info \ No newline at end of file diff --git a/apiserver/manage.py b/apiserver/manage.py index 837297219..744086783 100644 --- a/apiserver/manage.py +++ b/apiserver/manage.py @@ -2,10 +2,10 @@ import os import sys -if __name__ == '__main__': +if __name__ == "__main__": os.environ.setdefault( - 'DJANGO_SETTINGS_MODULE', - 'plane.settings.production') + "DJANGO_SETTINGS_MODULE", "plane.settings.production" + ) try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/apiserver/package.json b/apiserver/package.json index a317b4776..120314ed3 100644 --- a/apiserver/package.json +++ b/apiserver/package.json @@ -1,4 +1,4 @@ { "name": "plane-api", - "version": "0.14.0" + "version": "0.15.0" } diff --git a/apiserver/plane/__init__.py b/apiserver/plane/__init__.py index fb989c4e6..53f4ccb1d 100644 --- a/apiserver/plane/__init__.py +++ b/apiserver/plane/__init__.py @@ -1,3 +1,3 @@ from .celery import app as celery_app -__all__ = ('celery_app',) +__all__ = ("celery_app",) diff --git a/apiserver/plane/analytics/apps.py b/apiserver/plane/analytics/apps.py index 353779983..52a59f313 100644 --- a/apiserver/plane/analytics/apps.py +++ b/apiserver/plane/analytics/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class AnalyticsConfig(AppConfig): - name = 'plane.analytics' + name = "plane.analytics" diff --git a/apiserver/plane/api/apps.py b/apiserver/plane/api/apps.py index 292ad9344..6ba36e7e5 100644 --- a/apiserver/plane/api/apps.py +++ b/apiserver/plane/api/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class ApiConfig(AppConfig): - name = "plane.api" \ No newline at end of file + name = "plane.api" diff --git a/apiserver/plane/api/middleware/api_authentication.py b/apiserver/plane/api/middleware/api_authentication.py index 1b2c03318..893df7f84 100644 --- a/apiserver/plane/api/middleware/api_authentication.py +++ b/apiserver/plane/api/middleware/api_authentication.py @@ -25,7 +25,10 @@ class APIKeyAuthentication(authentication.BaseAuthentication): def validate_api_token(self, token): try: api_token = APIToken.objects.get( - Q(Q(expired_at__gt=timezone.now()) | Q(expired_at__isnull=True)), + Q( + Q(expired_at__gt=timezone.now()) + | Q(expired_at__isnull=True) + ), token=token, is_active=True, ) @@ -44,4 +47,4 @@ class APIKeyAuthentication(authentication.BaseAuthentication): # Validate the API token user, token = self.validate_api_token(token) - return user, token \ No newline at end of file + return user, token diff --git a/apiserver/plane/api/rate_limit.py b/apiserver/plane/api/rate_limit.py index f91e2d65d..b62936d8e 100644 --- a/apiserver/plane/api/rate_limit.py +++ b/apiserver/plane/api/rate_limit.py @@ -1,17 +1,18 @@ from rest_framework.throttling import SimpleRateThrottle + class ApiKeyRateThrottle(SimpleRateThrottle): - scope = 'api_key' - rate = '60/minute' + scope = "api_key" + rate = "60/minute" def get_cache_key(self, request, view): # Retrieve the API key from the request header - api_key = request.headers.get('X-Api-Key') + api_key = request.headers.get("X-Api-Key") if not api_key: return None # Allow the request if there's no API key # Use the API key as part of the cache key - return f'{self.scope}:{api_key}' + return f"{self.scope}:{api_key}" def allow_request(self, request, view): allowed = super().allow_request(request, view) @@ -24,7 +25,7 @@ class ApiKeyRateThrottle(SimpleRateThrottle): # Remove old histories while history and history[-1] <= now - self.duration: history.pop() - + # Calculate the requests num_requests = len(history) @@ -35,7 +36,7 @@ class ApiKeyRateThrottle(SimpleRateThrottle): reset_time = int(now + self.duration) # Add headers - request.META['X-RateLimit-Remaining'] = max(0, available) - request.META['X-RateLimit-Reset'] = reset_time + request.META["X-RateLimit-Remaining"] = max(0, available) + request.META["X-RateLimit-Reset"] = reset_time - return allowed \ No newline at end of file + return allowed diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index 1fd1bce78..10b0182d6 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -13,5 +13,9 @@ from .issue import ( ) from .state import StateLiteSerializer, StateSerializer from .cycle import CycleSerializer, CycleIssueSerializer, CycleLiteSerializer -from .module import ModuleSerializer, ModuleIssueSerializer, ModuleLiteSerializer -from .inbox import InboxIssueSerializer \ No newline at end of file +from .module import ( + ModuleSerializer, + ModuleIssueSerializer, + ModuleLiteSerializer, +) +from .inbox import InboxIssueSerializer diff --git a/apiserver/plane/api/serializers/base.py b/apiserver/plane/api/serializers/base.py index b96422501..da8b96964 100644 --- a/apiserver/plane/api/serializers/base.py +++ b/apiserver/plane/api/serializers/base.py @@ -97,9 +97,11 @@ class BaseSerializer(serializers.ModelSerializer): exp_serializer = expansion[expand]( getattr(instance, expand) ) - response[expand] = exp_serializer.data + response[expand] = exp_serializer.data else: # You might need to handle this case differently - response[expand] = getattr(instance, f"{expand}_id", None) + response[expand] = getattr( + instance, f"{expand}_id", None + ) - return response \ No newline at end of file + return response diff --git a/apiserver/plane/api/serializers/cycle.py b/apiserver/plane/api/serializers/cycle.py index eaff8181a..6fc73a4bc 100644 --- a/apiserver/plane/api/serializers/cycle.py +++ b/apiserver/plane/api/serializers/cycle.py @@ -23,7 +23,9 @@ class CycleSerializer(BaseSerializer): and data.get("end_date", None) is not None and data.get("start_date", None) > data.get("end_date", None) ): - raise serializers.ValidationError("Start date cannot exceed end date") + raise serializers.ValidationError( + "Start date cannot exceed end date" + ) return data class Meta: @@ -55,7 +57,6 @@ class CycleIssueSerializer(BaseSerializer): class CycleLiteSerializer(BaseSerializer): - class Meta: model = Cycle - fields = "__all__" \ No newline at end of file + fields = "__all__" diff --git a/apiserver/plane/api/serializers/inbox.py b/apiserver/plane/api/serializers/inbox.py index 17ae8c1ed..78bb74d13 100644 --- a/apiserver/plane/api/serializers/inbox.py +++ b/apiserver/plane/api/serializers/inbox.py @@ -2,8 +2,8 @@ from .base import BaseSerializer from plane.db.models import InboxIssue -class InboxIssueSerializer(BaseSerializer): +class InboxIssueSerializer(BaseSerializer): class Meta: model = InboxIssue fields = "__all__" @@ -16,4 +16,4 @@ class InboxIssueSerializer(BaseSerializer): "updated_by", "created_at", "updated_at", - ] \ No newline at end of file + ] diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index 75396e9bb..4c8d6e815 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -27,6 +27,7 @@ from .module import ModuleSerializer, ModuleLiteSerializer from .user import UserLiteSerializer from .state import StateLiteSerializer + class IssueSerializer(BaseSerializer): assignees = serializers.ListField( child=serializers.PrimaryKeyRelatedField( @@ -66,14 +67,16 @@ class IssueSerializer(BaseSerializer): and data.get("target_date", None) is not None and data.get("start_date", None) > data.get("target_date", None) ): - raise serializers.ValidationError("Start date cannot exceed target date") - + raise serializers.ValidationError( + "Start date cannot exceed target date" + ) + try: - if(data.get("description_html", None) is not None): + if data.get("description_html", None) is not None: parsed = html.fromstring(data["description_html"]) - parsed_str = html.tostring(parsed, encoding='unicode') + parsed_str = html.tostring(parsed, encoding="unicode") data["description_html"] = parsed_str - + except Exception as e: raise serializers.ValidationError(f"Invalid HTML: {str(e)}") @@ -96,7 +99,8 @@ class IssueSerializer(BaseSerializer): if ( data.get("state") and not State.objects.filter( - project_id=self.context.get("project_id"), pk=data.get("state").id + project_id=self.context.get("project_id"), + pk=data.get("state").id, ).exists() ): raise serializers.ValidationError( @@ -107,7 +111,8 @@ class IssueSerializer(BaseSerializer): if ( data.get("parent") and not Issue.objects.filter( - workspace_id=self.context.get("workspace_id"), pk=data.get("parent").id + workspace_id=self.context.get("workspace_id"), + pk=data.get("parent").id, ).exists() ): raise serializers.ValidationError( @@ -238,9 +243,13 @@ class IssueSerializer(BaseSerializer): ] if "labels" in self.fields: if "labels" in self.expand: - data["labels"] = LabelSerializer(instance.labels.all(), many=True).data + data["labels"] = LabelSerializer( + instance.labels.all(), many=True + ).data else: - data["labels"] = [str(label.id) for label in instance.labels.all()] + data["labels"] = [ + str(label.id) for label in instance.labels.all() + ] return data @@ -278,7 +287,8 @@ class IssueLinkSerializer(BaseSerializer): # Validation if url already exists def create(self, validated_data): if IssueLink.objects.filter( - url=validated_data.get("url"), issue_id=validated_data.get("issue_id") + url=validated_data.get("url"), + issue_id=validated_data.get("issue_id"), ).exists(): raise serializers.ValidationError( {"error": "URL already exists for this Issue"} @@ -324,11 +334,11 @@ class IssueCommentSerializer(BaseSerializer): def validate(self, data): try: - if(data.get("comment_html", None) is not None): + if data.get("comment_html", None) is not None: parsed = html.fromstring(data["comment_html"]) - parsed_str = html.tostring(parsed, encoding='unicode') + parsed_str = html.tostring(parsed, encoding="unicode") data["comment_html"] = parsed_str - + except Exception as e: raise serializers.ValidationError(f"Invalid HTML: {str(e)}") return data @@ -362,7 +372,6 @@ class ModuleIssueSerializer(BaseSerializer): class LabelLiteSerializer(BaseSerializer): - class Meta: model = Label fields = [ diff --git a/apiserver/plane/api/serializers/module.py b/apiserver/plane/api/serializers/module.py index a96a9b54d..01a201064 100644 --- a/apiserver/plane/api/serializers/module.py +++ b/apiserver/plane/api/serializers/module.py @@ -52,7 +52,9 @@ class ModuleSerializer(BaseSerializer): and data.get("target_date", None) is not None and data.get("start_date", None) > data.get("target_date", None) ): - raise serializers.ValidationError("Start date cannot exceed target date") + raise serializers.ValidationError( + "Start date cannot exceed target date" + ) if data.get("members", []): data["members"] = ProjectMember.objects.filter( @@ -146,16 +148,16 @@ class ModuleLinkSerializer(BaseSerializer): # Validation if url already exists def create(self, validated_data): if ModuleLink.objects.filter( - url=validated_data.get("url"), module_id=validated_data.get("module_id") + url=validated_data.get("url"), + module_id=validated_data.get("module_id"), ).exists(): raise serializers.ValidationError( {"error": "URL already exists for this Issue"} ) return ModuleLink.objects.create(**validated_data) - + class ModuleLiteSerializer(BaseSerializer): - class Meta: model = Module - fields = "__all__" \ No newline at end of file + fields = "__all__" diff --git a/apiserver/plane/api/serializers/project.py b/apiserver/plane/api/serializers/project.py index c394a080d..342cc1a81 100644 --- a/apiserver/plane/api/serializers/project.py +++ b/apiserver/plane/api/serializers/project.py @@ -2,12 +2,17 @@ from rest_framework import serializers # Module imports -from plane.db.models import Project, ProjectIdentifier, WorkspaceMember, State, Estimate +from plane.db.models import ( + Project, + ProjectIdentifier, + WorkspaceMember, + State, + Estimate, +) from .base import BaseSerializer class ProjectSerializer(BaseSerializer): - total_members = serializers.IntegerField(read_only=True) total_cycles = serializers.IntegerField(read_only=True) total_modules = serializers.IntegerField(read_only=True) @@ -21,7 +26,7 @@ class ProjectSerializer(BaseSerializer): fields = "__all__" read_only_fields = [ "id", - 'emoji', + "emoji", "workspace", "created_at", "updated_at", @@ -59,12 +64,16 @@ class ProjectSerializer(BaseSerializer): def create(self, validated_data): identifier = validated_data.get("identifier", "").strip().upper() if identifier == "": - raise serializers.ValidationError(detail="Project Identifier is required") + raise serializers.ValidationError( + detail="Project Identifier is required" + ) if ProjectIdentifier.objects.filter( name=identifier, workspace_id=self.context["workspace_id"] ).exists(): - raise serializers.ValidationError(detail="Project Identifier is taken") + raise serializers.ValidationError( + detail="Project Identifier is taken" + ) project = Project.objects.create( **validated_data, workspace_id=self.context["workspace_id"] @@ -89,4 +98,4 @@ class ProjectLiteSerializer(BaseSerializer): "emoji", "description", ] - read_only_fields = fields \ No newline at end of file + read_only_fields = fields diff --git a/apiserver/plane/api/serializers/state.py b/apiserver/plane/api/serializers/state.py index 9d08193d8..1649a7bcf 100644 --- a/apiserver/plane/api/serializers/state.py +++ b/apiserver/plane/api/serializers/state.py @@ -7,9 +7,9 @@ class StateSerializer(BaseSerializer): def validate(self, data): # If the default is being provided then make all other states default False if data.get("default", False): - State.objects.filter(project_id=self.context.get("project_id")).update( - default=False - ) + State.objects.filter( + project_id=self.context.get("project_id") + ).update(default=False) return data class Meta: @@ -35,4 +35,4 @@ class StateLiteSerializer(BaseSerializer): "color", "group", ] - read_only_fields = fields \ No newline at end of file + read_only_fields = fields diff --git a/apiserver/plane/api/serializers/user.py b/apiserver/plane/api/serializers/user.py index 42b6c3967..fe50021b5 100644 --- a/apiserver/plane/api/serializers/user.py +++ b/apiserver/plane/api/serializers/user.py @@ -13,4 +13,4 @@ class UserLiteSerializer(BaseSerializer): "avatar", "display_name", ] - read_only_fields = fields \ No newline at end of file + read_only_fields = fields diff --git a/apiserver/plane/api/serializers/workspace.py b/apiserver/plane/api/serializers/workspace.py index c4c5caceb..a47de3d31 100644 --- a/apiserver/plane/api/serializers/workspace.py +++ b/apiserver/plane/api/serializers/workspace.py @@ -5,6 +5,7 @@ from .base import BaseSerializer class WorkspaceLiteSerializer(BaseSerializer): """Lite serializer with only required fields""" + class Meta: model = Workspace fields = [ @@ -12,4 +13,4 @@ class WorkspaceLiteSerializer(BaseSerializer): "slug", "id", ] - read_only_fields = fields \ No newline at end of file + read_only_fields = fields diff --git a/apiserver/plane/api/urls/__init__.py b/apiserver/plane/api/urls/__init__.py index a5ef0f5f1..84927439e 100644 --- a/apiserver/plane/api/urls/__init__.py +++ b/apiserver/plane/api/urls/__init__.py @@ -12,4 +12,4 @@ urlpatterns = [ *cycle_patterns, *module_patterns, *inbox_patterns, -] \ No newline at end of file +] diff --git a/apiserver/plane/api/urls/cycle.py b/apiserver/plane/api/urls/cycle.py index f557f8af0..593e501bf 100644 --- a/apiserver/plane/api/urls/cycle.py +++ b/apiserver/plane/api/urls/cycle.py @@ -32,4 +32,4 @@ urlpatterns = [ TransferCycleIssueAPIEndpoint.as_view(), name="transfer-issues", ), -] \ No newline at end of file +] diff --git a/apiserver/plane/api/urls/inbox.py b/apiserver/plane/api/urls/inbox.py index 3a2a57786..95eb68f3f 100644 --- a/apiserver/plane/api/urls/inbox.py +++ b/apiserver/plane/api/urls/inbox.py @@ -14,4 +14,4 @@ urlpatterns = [ InboxIssueAPIEndpoint.as_view(), name="inbox-issue", ), -] \ No newline at end of file +] diff --git a/apiserver/plane/api/urls/module.py b/apiserver/plane/api/urls/module.py index 7117a9e8b..4309f44e9 100644 --- a/apiserver/plane/api/urls/module.py +++ b/apiserver/plane/api/urls/module.py @@ -23,4 +23,4 @@ urlpatterns = [ ModuleIssueAPIEndpoint.as_view(), name="module-issues", ), -] \ No newline at end of file +] diff --git a/apiserver/plane/api/urls/project.py b/apiserver/plane/api/urls/project.py index c73e84c89..1ed450c86 100644 --- a/apiserver/plane/api/urls/project.py +++ b/apiserver/plane/api/urls/project.py @@ -3,7 +3,7 @@ from django.urls import path from plane.api.views import ProjectAPIEndpoint urlpatterns = [ - path( + path( "workspaces/
+
+
+
+ |
+
+ + {{ issue.issue_identifier }} updates + ++ {{workspace}}/{{project}}/{{issue.issue_identifier}}: {{ issue.name }} + + |
+
+ {{summary}} + + {{ data.0.actor_detail.first_name}} + {{data.0.actor_detail.last_name}} + . +
+ {% else %} ++ {{summary}} + + {{ data.0.actor_detail.first_name}} + {{data.0.actor_detail.last_name }} + and others. +
+ {% endif %} + + + + {% for update in data %} {% if update.changes.name %} + ++ The issue title has been updated to {{ issue.name}} +
+ {% endif %} + + {% if data %} ++ Updates +
++ Comments +
+ + {% for comment in comments %} +
+ {% if comment.actor_detail.avatar_url %}
+
+ {% else %}
+
|
+
+
|
+
+
+ This email was sent to
+ {{ receiver.email }}.
+ If you'd rather not receive this kind of email,
+ you can unsubscribe to the issue
+ or
+ manage your email preferences.
+
+
+
+ |
+
LinkInputView
; diff --git a/packages/editor/document-editor/src/ui/components/links/link-preview.tsx b/packages/editor/document-editor/src/ui/components/links/link-preview.tsx new file mode 100644 index 000000000..ff3fd0263 --- /dev/null +++ b/packages/editor/document-editor/src/ui/components/links/link-preview.tsx @@ -0,0 +1,52 @@ +import { Copy, GlobeIcon, Link2Off, PencilIcon } from "lucide-react"; +import { LinkViewProps } from "./link-view"; + +export const LinkPreview = ({ + viewProps, + switchView, +}: { + viewProps: LinkViewProps; + switchView: (view: "LinkPreview" | "LinkEditView" | "LinkInputView") => void; +}) => { + const { editor, from, to, url } = viewProps; + + const removeLink = () => { + editor.view.dispatch(editor.state.tr.removeMark(from, to, editor.schema.marks.link)); + viewProps.onActionCompleteHandler({ + title: "Link successfully removed", + message: "The link was removed from the text.", + type: "success", + }); + viewProps.closeLinkView(); + }; + + const copyLinkToClipboard = () => { + navigator.clipboard.writeText(url); + viewProps.onActionCompleteHandler({ + title: "Link successfully copied", + message: "The link was copied to the clipboard.", + type: "success", + }); + viewProps.closeLinkView(); + }; + + return ( +{url.length > 40 ? url.slice(0, 40) + "..." : url}
+#issue_
" - ) - .run(); - }, +) => [ + SlashCommand(uploadFile, setIsSubmitting), + DragAndDrop(setHideDragHandle), + Placeholder.configure({ + placeholder: ({ node }) => { + if (node.type.name === "heading") { + return `Heading ${node.attrs.level}`; + } + if (node.type.name === "image" || node.type.name === "table") { + return ""; + } + + return "Press '/' for commands..."; }, - ]; + includeChildren: true, + }), + IssueWidgetPlaceholder(), +]; - return [ - SlashCommand(uploadFile, setIsSubmitting, additionalOptions), - DragAndDrop, - Placeholder.configure({ - placeholder: ({ node }) => { - if (node.type.name === "heading") { - return `Heading ${node.attrs.level}`; - } - if (node.type.name === "image" || node.type.name === "table") { - return ""; - } - - return "Press '/' for commands..."; - }, - includeChildren: true, - }), - IssueWidgetExtension({ issueEmbedConfig }), - IssueSuggestions(issueEmbedConfig ? issueEmbedConfig.issues : []), - ]; -}; diff --git a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/index.tsx b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/index.tsx index acc6213c2..35a09bcc2 100644 --- a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/index.tsx +++ b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/index.tsx @@ -24,7 +24,7 @@ export const IssueSuggestions = (suggestions: any[]) => { title: suggestion.name, priority: suggestion.priority.toString(), identifier: `${suggestion.project_detail.identifier}-${suggestion.sequence_id}`, - state: suggestion.state_detail.name, + state: suggestion.state_detail && suggestion.state_detail.name ? suggestion.state_detail.name : "Todo", command: ({ editor, range }) => { editor .chain() diff --git a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-extension.tsx b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-extension.tsx index 75d977e49..96a5c1325 100644 --- a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-extension.tsx +++ b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-extension.tsx @@ -9,6 +9,8 @@ export const IssueEmbedSuggestions = Extension.create({ addOptions() { return { suggestion: { + char: "#issue_", + allowSpaces: true, command: ({ editor, range, props }: { editor: Editor; range: Range; props: any }) => { props.command({ editor, range }); }, @@ -18,11 +20,8 @@ export const IssueEmbedSuggestions = Extension.create({ addProseMirrorPlugins() { return [ Suggestion({ - char: "#issue_", pluginKey: new PluginKey("issue-embed-suggestions"), editor: this.editor, - allowSpaces: true, - ...this.options.suggestion, }), ]; diff --git a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx index 0a166c3e3..869c7a8c6 100644 --- a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx +++ b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx @@ -53,7 +53,7 @@ const IssueSuggestionList = ({ const commandListContainer = useRef= NextPage
& {
- getLayout?: (page: ReactElement) => ReactNode;
-};
-
export interface IAppConfig {
email_password_login: boolean;
file_size_limit: number;
- google_client_id: string | null;
github_app_name: string | null;
github_client_id: string | null;
- magic_login: boolean;
- slack_client_id: string | null;
- posthog_api_key: string | null;
- posthog_host: string | null;
+ google_client_id: string | null;
has_openai_configured: boolean;
has_unsplash_configured: boolean;
- is_self_managed: boolean;
+ is_smtp_configured: boolean;
+ magic_login: boolean;
+ posthog_api_key: string | null;
+ posthog_host: string | null;
+ slack_client_id: string | null;
}
diff --git a/web/types/auth.d.ts b/packages/types/src/auth.d.ts
similarity index 100%
rename from web/types/auth.d.ts
rename to packages/types/src/auth.d.ts
diff --git a/web/types/calendar.ts b/packages/types/src/calendar.d.ts
similarity index 100%
rename from web/types/calendar.ts
rename to packages/types/src/calendar.d.ts
diff --git a/web/types/cycles.d.ts b/packages/types/src/cycles.d.ts
similarity index 82%
rename from web/types/cycles.d.ts
rename to packages/types/src/cycles.d.ts
index 4f243deeb..12cbab4c6 100644
--- a/web/types/cycles.d.ts
+++ b/packages/types/src/cycles.d.ts
@@ -1,4 +1,11 @@
-import type { IUser, IIssue, IProjectLite, IWorkspaceLite, IIssueFilterOptions, IUserLite } from "types";
+import type {
+ IUser,
+ TIssue,
+ IProjectLite,
+ IWorkspaceLite,
+ IIssueFilterOptions,
+ IUserLite,
+} from "@plane/types";
export type TCycleView = "all" | "active" | "upcoming" | "completed" | "draft";
@@ -23,7 +30,7 @@ export interface ICycle {
is_favorite: boolean;
issue: string;
name: string;
- owned_by: IUser;
+ owned_by: string;
project: string;
project_detail: IProjectLite;
status: TCycleGroups;
@@ -54,7 +61,7 @@ export type TAssigneesDistribution = {
};
export type TCompletionChartDistribution = {
- [key: string]: number;
+ [key: string]: number | null;
};
export type TLabelsDistribution = {
@@ -68,7 +75,7 @@ export type TLabelsDistribution = {
export interface CycleIssueResponse {
id: string;
- issue_detail: IIssue;
+ issue_detail: TIssue;
created_at: Date;
updated_at: Date;
created_by: string;
@@ -80,9 +87,13 @@ export interface CycleIssueResponse {
sub_issues_count: number;
}
-export type SelectCycleType = (ICycle & { actionType: "edit" | "delete" | "create-issue" }) | undefined;
+export type SelectCycleType =
+ | (ICycle & { actionType: "edit" | "delete" | "create-issue" })
+ | undefined;
-export type SelectIssue = (IIssue & { actionType: "edit" | "delete" | "create" }) | null;
+export type SelectIssue =
+ | (TIssue & { actionType: "edit" | "delete" | "create" })
+ | null;
export type CycleDateCheckData = {
start_date: string;
diff --git a/packages/types/src/dashboard.d.ts b/packages/types/src/dashboard.d.ts
new file mode 100644
index 000000000..31751c0d0
--- /dev/null
+++ b/packages/types/src/dashboard.d.ts
@@ -0,0 +1,175 @@
+import { IIssueActivity, TIssuePriorities } from "./issues";
+import { TIssue } from "./issues/issue";
+import { TIssueRelationTypes } from "./issues/issue_relation";
+import { TStateGroups } from "./state";
+
+export type TWidgetKeys =
+ | "overview_stats"
+ | "assigned_issues"
+ | "created_issues"
+ | "issues_by_state_groups"
+ | "issues_by_priority"
+ | "recent_activity"
+ | "recent_projects"
+ | "recent_collaborators";
+
+export type TIssuesListTypes = "upcoming" | "overdue" | "completed";
+
+export type TDurationFilterOptions =
+ | "today"
+ | "this_week"
+ | "this_month"
+ | "this_year";
+
+// widget filters
+export type TAssignedIssuesWidgetFilters = {
+ target_date?: TDurationFilterOptions;
+ tab?: TIssuesListTypes;
+};
+
+export type TCreatedIssuesWidgetFilters = {
+ target_date?: TDurationFilterOptions;
+ tab?: TIssuesListTypes;
+};
+
+export type TIssuesByStateGroupsWidgetFilters = {
+ target_date?: TDurationFilterOptions;
+};
+
+export type TIssuesByPriorityWidgetFilters = {
+ target_date?: TDurationFilterOptions;
+};
+
+export type TWidgetFiltersFormData =
+ | {
+ widgetKey: "assigned_issues";
+ filters: Partial Or continue with
+ Get back to your issues, projects and workspaces. +
+ + + > + ); +}); diff --git a/web/components/account/sign-in-forms/forgot-password-popover.tsx b/web/components/account/sign-in-forms/forgot-password-popover.tsx new file mode 100644 index 000000000..d652e51f1 --- /dev/null +++ b/web/components/account/sign-in-forms/forgot-password-popover.tsx @@ -0,0 +1,54 @@ +import { Fragment, useState } from "react"; +import { usePopper } from "react-popper"; +import { Popover } from "@headlessui/react"; +import { X } from "lucide-react"; + +export const ForgotPasswordPopover = () => { + // popper-js refs + const [referenceElement, setReferenceElement] = useState+ We see that your god hasn{"'"}t enabled SMTP, we will not be able to send a password reset link +
++
If you{"'"}d like to do away with codes, set a password here.
- -- When you click the button above, you agree with our{" "} - - terms and conditions of service. - -
- )} > ); diff --git a/web/components/account/sign-in-forms/email-form.tsx b/web/components/account/sign-up-forms/email.tsx similarity index 76% rename from web/components/account/sign-in-forms/email-form.tsx rename to web/components/account/sign-up-forms/email.tsx index 6b6071475..0d5861b4e 100644 --- a/web/components/account/sign-in-forms/email-form.tsx +++ b/web/components/account/sign-up-forms/email.tsx @@ -1,6 +1,7 @@ -import React, { useEffect } from "react"; +import React from "react"; import { Controller, useForm } from "react-hook-form"; import { XCircle } from "lucide-react"; +import { observer } from "mobx-react-lite"; // services import { AuthService } from "services/auth.service"; // hooks @@ -10,12 +11,10 @@ import { Button, Input } from "@plane/ui"; // helpers import { checkEmailValidity } from "helpers/string.helper"; // types -import { IEmailCheckData } from "types/auth"; -// constants -import { ESignInSteps } from "components/account"; +import { IEmailCheckData } from "@plane/types"; type Props = { - handleStepChange: (step: ESignInSteps) => void; + onSubmit: () => void; updateEmail: (email: string) => void; }; @@ -25,16 +24,14 @@ type TEmailFormValues = { const authService = new AuthService(); -export const EmailForm: React.FC
+ Let{"'"}s set a password so
+
+ you can do away with codes.
+
+ This password will continue to be your account{"'"}s password. +
++ Create or join a workspace. Start with your e-mail. +
++ This password will continue to be your account{"'"}s password. +
When you click the button above, you agree with our{" "}
@@ -141,4 +154,4 @@ export const SelfHostedSignInForm: React.FC
+ Already using Plane?{" "} + + Sign in + +
+ > + )} + > + ); +}); diff --git a/web/components/account/sign-up-forms/unique-code.tsx b/web/components/account/sign-up-forms/unique-code.tsx new file mode 100644 index 000000000..7764b627e --- /dev/null +++ b/web/components/account/sign-up-forms/unique-code.tsx @@ -0,0 +1,215 @@ +import React, { useState } from "react"; +import Link from "next/link"; +import { Controller, useForm } from "react-hook-form"; +import { XCircle } from "lucide-react"; +// services +import { AuthService } from "services/auth.service"; +import { UserService } from "services/user.service"; +// hooks +import useToast from "hooks/use-toast"; +import useTimer from "hooks/use-timer"; +// ui +import { Button, Input } from "@plane/ui"; +// helpers +import { checkEmailValidity } from "helpers/string.helper"; +// types +import { IEmailCheckData, IMagicSignInData } from "@plane/types"; + +type Props = { + email: string; + handleEmailClear: () => void; + onSubmit: (isPasswordAutoset: boolean) => Promise
+ Paste the code you got at
+
+ {email} below.
+
+ When you click the button above, you agree with our{" "} + + terms and conditions of service. + +
+{truncateText(project.name, 20)}
- ({project.identifier}) - -{truncateText(project.name, 20)}
+ ({project.identifier}) +
- You have signed in as {user.email}.
+ You have signed in as {currentUser.email}.
Sign in
{" "}
@@ -58,4 +59,4 @@ export const NotAuthorizedView: React.FC
{state.name}