mirror of
https://github.com/makeplane/plane.git
synced 2026-02-24 04:00:14 +01:00
Merge pull request #179 from makeplane/preview
release: UI changes and Bugfixes
This commit is contained in:
176
.github/workflows/build-branch-ee.yml
vendored
176
.github/workflows/build-branch-ee.yml
vendored
@@ -18,17 +18,17 @@ jobs:
|
||||
name: Build-Push Web/Space/API/Proxy Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
gh_branch_name: ${{ steps.set_env_variables.outputs.TARGET_BRANCH }}
|
||||
gh_branch_name: ${{ steps.set_env_variables.outputs.TARGET_BRANCH }}
|
||||
gh_buildx_driver: ${{ steps.set_env_variables.outputs.BUILDX_DRIVER }}
|
||||
gh_buildx_version: ${{ steps.set_env_variables.outputs.BUILDX_VERSION }}
|
||||
gh_buildx_platforms: ${{ steps.set_env_variables.outputs.BUILDX_PLATFORMS }}
|
||||
gh_buildx_endpoint: ${{ steps.set_env_variables.outputs.BUILDX_ENDPOINT }}
|
||||
build_frontend: ${{ steps.changed_files.outputs.frontend_any_changed }}
|
||||
build_web: ${{ steps.changed_files.outputs.web_any_changed }}
|
||||
build_space: ${{ steps.changed_files.outputs.space_any_changed }}
|
||||
build_backend: ${{ steps.changed_files.outputs.backend_any_changed }}
|
||||
build_proxy: ${{ steps.changed_files.outputs.proxy_any_changed }}
|
||||
docker_upload_to_s3: ${{ steps.set_env_variables.outputs.docker_upload_to_s3 }}
|
||||
docker_tar_suffix: ${{ steps.set_env_variables.outputs.docker_tar_suffix }}
|
||||
artifact_upload_to_s3: ${{ steps.set_env_variables.outputs.artifact_upload_to_s3 }}
|
||||
artifact_s3_suffix: ${{ steps.set_env_variables.outputs.artifact_s3_suffix }}
|
||||
|
||||
steps:
|
||||
- id: set_env_variables
|
||||
@@ -45,17 +45,21 @@ jobs:
|
||||
echo "BUILDX_PLATFORMS=linux/amd64" >> $GITHUB_OUTPUT
|
||||
echo "BUILDX_ENDPOINT=" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
echo "TARGET_BRANCH=${{ env.TARGET_BRANCH }}" >> $GITHUB_OUTPUT
|
||||
BR_NAME=$( echo "${{ env.TARGET_BRANCH }}" | tr / -)
|
||||
echo "TARGET_BRANCH=$BR_NAME" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ "${{ github.event_name }}" == "release" ]; then
|
||||
echo "docker_upload_to_s3=true" >> $GITHUB_OUTPUT
|
||||
echo "docker_tar_suffix=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT
|
||||
echo "artifact_upload_to_s3=true" >> $GITHUB_OUTPUT
|
||||
echo "artifact_s3_suffix=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
|
||||
echo "docker_upload_to_s3=true" >> $GITHUB_OUTPUT
|
||||
echo "docker_tar_suffix=latest" >> $GITHUB_OUTPUT
|
||||
echo "artifact_upload_to_s3=true" >> $GITHUB_OUTPUT
|
||||
echo "artifact_s3_suffix=latest" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ env.TARGET_BRANCH }}" == "preview" ] || [ "${{ env.TARGET_BRANCH }}" == "develop" ]; then
|
||||
echo "artifact_upload_to_s3=true" >> $GITHUB_OUTPUT
|
||||
echo "artifact_s3_suffix=${{ env.TARGET_BRANCH }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "docker_upload_to_s3=false" >> $GITHUB_OUTPUT
|
||||
echo "docker_tar_suffix=${{ env.TARGET_BRANCH }}" >> $GITHUB_OUTPUT
|
||||
echo "artifact_upload_to_s3=false" >> $GITHUB_OUTPUT
|
||||
echo "artifact_s3_suffix=$BR_NAME" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- id: checkout_files
|
||||
@@ -67,7 +71,7 @@ jobs:
|
||||
uses: tj-actions/changed-files@v42
|
||||
with:
|
||||
files_yaml: |
|
||||
frontend:
|
||||
web:
|
||||
- web/**
|
||||
- packages/**
|
||||
- 'package.json'
|
||||
@@ -86,28 +90,33 @@ jobs:
|
||||
proxy:
|
||||
- nginx/**
|
||||
|
||||
branch_build_push_frontend:
|
||||
if: ${{ needs.branch_build_setup.outputs.build_frontend == 'true' || github.event_name == 'release' || needs.branch_build_setup.outputs.gh_branch_name == 'master' }}
|
||||
branch_build_push_web:
|
||||
if: ${{ needs.branch_build_setup.outputs.build_web == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'release' || needs.branch_build_setup.outputs.gh_branch_name == 'master' }}
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [branch_build_setup]
|
||||
env:
|
||||
FRONTEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-ee:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
WEB_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/web-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
|
||||
BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
|
||||
BUILDX_PLATFORMS: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
|
||||
BUILDX_ENDPOINT: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }}
|
||||
steps:
|
||||
- name: Set Frontend Docker Tag
|
||||
- name: Set Web Docker Tag
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "release" ]; then
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-ee:stable,${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-ee:${{ github.event.release.tag_name }}
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/web-enterprise:stable
|
||||
TAG=${TAG},${{ secrets.DOCKERHUB_USERNAME }}/web-enterprise:${{ github.event.release.tag_name }}
|
||||
TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/web-enterprise:stable
|
||||
TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/web-enterprise:${{ github.event.release.tag_name }}
|
||||
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-ee:latest
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/web-enterprise:latest
|
||||
TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/web-enterprise:latest
|
||||
else
|
||||
TAG=${{ env.FRONTEND_TAG }}
|
||||
TAG=${{ env.WEB_TAG }}
|
||||
TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/web-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
fi
|
||||
echo "FRONTEND_TAG=${TAG}" >> $GITHUB_ENV
|
||||
echo "WEB_TAG=${TAG}" >> $GITHUB_ENV
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
@@ -115,6 +124,13 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.HARBOR_USERNAME }}
|
||||
password: ${{ secrets.HARBOR_TOKEN }}
|
||||
registry: ${{ vars.HARBOR_REGISTRY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
@@ -125,13 +141,13 @@ jobs:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build and Push Frontend to Docker Container Registry
|
||||
- name: Build and Push Web to Docker Container Registry
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
with:
|
||||
context: .
|
||||
file: ./web/Dockerfile.web
|
||||
platforms: ${{ env.BUILDX_PLATFORMS }}
|
||||
tags: ${{ env.FRONTEND_TAG }}
|
||||
tags: ${{ env.WEB_TAG }}
|
||||
push: true
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
@@ -139,11 +155,11 @@ jobs:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
branch_build_push_space:
|
||||
if: ${{ needs.branch_build_setup.outputs.build_space == 'true' || github.event_name == 'release' || needs.branch_build_setup.outputs.gh_branch_name == 'master' }}
|
||||
if: ${{ needs.branch_build_setup.outputs.build_space == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'release' || needs.branch_build_setup.outputs.gh_branch_name == 'master' }}
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [branch_build_setup]
|
||||
env:
|
||||
SPACE_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-space-ee:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
SPACE_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/space-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
|
||||
BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
|
||||
@@ -153,11 +169,16 @@ jobs:
|
||||
- name: Set Space Docker Tag
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "release" ]; then
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space-ee:stable,${{ secrets.DOCKERHUB_USERNAME }}/plane-space-ee:${{ github.event.release.tag_name }}
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/space-enterprise:stable
|
||||
TAG=${TAG},${{ secrets.DOCKERHUB_USERNAME }}/space-enterprise:${{ github.event.release.tag_name }}
|
||||
TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/space-enterprise:stable
|
||||
TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/space-enterprise:${{ github.event.release.tag_name }}
|
||||
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space-ee:latest
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/space-enterprise:latest
|
||||
TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/space-enterprise:latest
|
||||
else
|
||||
TAG=${{ env.SPACE_TAG }}
|
||||
TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/space-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
fi
|
||||
echo "SPACE_TAG=${TAG}" >> $GITHUB_ENV
|
||||
|
||||
@@ -167,6 +188,13 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.HARBOR_USERNAME }}
|
||||
password: ${{ secrets.HARBOR_TOKEN }}
|
||||
registry: ${{ vars.HARBOR_REGISTRY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
@@ -191,11 +219,11 @@ jobs:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
branch_build_push_backend:
|
||||
if: ${{ needs.branch_build_setup.outputs.build_backend == 'true' || github.event_name == 'release' || needs.branch_build_setup.outputs.gh_branch_name == 'master' }}
|
||||
if: ${{ needs.branch_build_setup.outputs.build_backend == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'release' || needs.branch_build_setup.outputs.gh_branch_name == 'master' }}
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [branch_build_setup]
|
||||
env:
|
||||
BACKEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-ee:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
BACKEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/backend-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
|
||||
BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
|
||||
@@ -205,11 +233,16 @@ jobs:
|
||||
- name: Set Backend Docker Tag
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "release" ]; then
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-ee:stable,${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-ee:${{ github.event.release.tag_name }}
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/backend-enterprise:stable
|
||||
TAG=${TAG},${{ secrets.DOCKERHUB_USERNAME }}/backend-enterprise:${{ github.event.release.tag_name }}
|
||||
TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/backend-enterprise:stable
|
||||
TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/backend-enterprise:${{ github.event.release.tag_name }}
|
||||
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-ee:latest
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/backend-enterprise:latest
|
||||
TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/backend-enterprise:latest
|
||||
else
|
||||
TAG=${{ env.BACKEND_TAG }}
|
||||
TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/backend-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
fi
|
||||
echo "BACKEND_TAG=${TAG}" >> $GITHUB_ENV
|
||||
|
||||
@@ -219,6 +252,13 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.HARBOR_USERNAME }}
|
||||
password: ${{ secrets.HARBOR_TOKEN }}
|
||||
registry: ${{ vars.HARBOR_REGISTRY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
@@ -243,11 +283,11 @@ jobs:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
branch_build_push_proxy:
|
||||
if: ${{ needs.branch_build_setup.outputs.build_proxy == 'true' || github.event_name == 'release' || needs.branch_build_setup.outputs.gh_branch_name == 'master' }}
|
||||
if: ${{ needs.branch_build_setup.outputs.build_proxy == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'release' || needs.branch_build_setup.outputs.gh_branch_name == 'master' }}
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [branch_build_setup]
|
||||
env:
|
||||
PROXY_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-ee:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
PROXY_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/proxy-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
|
||||
BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
|
||||
@@ -257,11 +297,16 @@ jobs:
|
||||
- name: Set Proxy Docker Tag
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "release" ]; then
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-ee:stable,${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-ee:${{ github.event.release.tag_name }}
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/proxy-enterprise:stable
|
||||
TAG=${TAG},${{ secrets.DOCKERHUB_USERNAME }}/proxy-enterprise:${{ github.event.release.tag_name }}
|
||||
TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/proxy-enterprise:stable
|
||||
TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/proxy-enterprise:${{ github.event.release.tag_name }}
|
||||
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-ee:latest
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/proxy-enterprise:latest
|
||||
TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/proxy-enterprise:latest
|
||||
else
|
||||
TAG=${{ env.PROXY_TAG }}
|
||||
TAG=${TAG},${{ vars.HARBOR_REGISTRY }}/${{ vars.HARBOR_PROJECT }}/proxy-enterprise:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
fi
|
||||
echo "PROXY_TAG=${TAG}" >> $GITHUB_ENV
|
||||
|
||||
@@ -271,6 +316,13 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.HARBOR_USERNAME }}
|
||||
password: ${{ secrets.HARBOR_TOKEN }}
|
||||
registry: ${{ vars.HARBOR_REGISTRY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
@@ -294,47 +346,45 @@ jobs:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
convert_docker_to_tar:
|
||||
if: ${{ needs.branch_build_setup.outputs.docker_upload_to_s3 == 'true' }}
|
||||
name: Convert Docker TAR to S3 Bucket
|
||||
upload_artifacts_s3:
|
||||
if: ${{ needs.branch_build_setup.outputs.artifact_upload_to_s3 == 'true' }}
|
||||
name: Upload artifacts to S3 Bucket
|
||||
runs-on: ubuntu-latest
|
||||
needs: [branch_build_setup, branch_build_push_frontend, branch_build_push_space, branch_build_push_backend, branch_build_push_proxy]
|
||||
needs:
|
||||
[
|
||||
branch_build_setup,
|
||||
branch_build_push_web,
|
||||
branch_build_push_space,
|
||||
branch_build_push_backend,
|
||||
branch_build_push_proxy,
|
||||
]
|
||||
container:
|
||||
image: docker:20.10.7
|
||||
credentials:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
env:
|
||||
TAR_SUFFIX: ${{ needs.branch_build_setup.outputs.docker_tar_suffix }}
|
||||
FRONTEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-ee:${{ needs.branch_build_setup.outputs.docker_tar_suffix }}
|
||||
SPACE_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-space-ee:${{ needs.branch_build_setup.outputs.docker_tar_suffix }}
|
||||
BACKEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-ee:${{ needs.branch_build_setup.outputs.docker_tar_suffix }}
|
||||
PROXY_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-ee:${{ needs.branch_build_setup.outputs.docker_tar_suffix }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.PLANE_DOCKER_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.PLANE_DOCKER_SECRET_ACCESS_KEY }}
|
||||
ARTIFACT_SUFFIX: ${{ needs.branch_build_setup.outputs.artifact_s3_suffix }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.SELF_HOST_BUCKET_ACCESS_KEY }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.SELF_HOST_BUCKET_SECRET_KEY }}
|
||||
TARGET_BRANCH: ${{ github.ref_name || github.event.release.target_commitish }}
|
||||
steps:
|
||||
- name: Save Image as Tar
|
||||
- id: checkout_files
|
||||
name: Checkout Files
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Upload artifacts
|
||||
run: |
|
||||
apk update
|
||||
apk add --no-cache aws-cli
|
||||
|
||||
mkdir -p ~/${{ env.TAR_SUFFIX }}
|
||||
mkdir -p ~/${{ env.ARTIFACT_SUFFIX }}
|
||||
|
||||
echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
|
||||
cp deploy/cli-install/variables.env ~/${{ env.ARTIFACT_SUFFIX }}/variables.env
|
||||
cp deploy/cli-install/Caddyfile ~/${{ env.ARTIFACT_SUFFIX }}/Caddyfile
|
||||
sed -e 's@${APP_RELEASE_VERSION}@'${{ env.ARTIFACT_SUFFIX }}'@' deploy/cli-install/docker-compose.yml > ~/${{ env.ARTIFACT_SUFFIX }}/docker-compose.yml
|
||||
sed -e 's@${APP_RELEASE_VERSION}@'${{ env.ARTIFACT_SUFFIX }}'@' deploy/cli-install/docker-compose-caddy.yml > ~/${{ env.ARTIFACT_SUFFIX }}/docker-compose-caddy.yml
|
||||
|
||||
docker pull ${{ env.FRONTEND_TAG }}
|
||||
docker save -o ~/${{ env.TAR_SUFFIX }}/plane-frontend-ee.tar ${{ env.FRONTEND_TAG }}
|
||||
aws s3 cp ~/${{ env.ARTIFACT_SUFFIX }} s3://${{ vars.SELF_HOST_BUCKET_NAME }}/plane-enterprise/${{ env.ARTIFACT_SUFFIX }} --recursive
|
||||
|
||||
docker pull ${{ env.SPACE_TAG }}
|
||||
docker save -o ~/${{ env.TAR_SUFFIX }}/plane-space-ee.tar ${{ env.SPACE_TAG }}
|
||||
|
||||
docker pull ${{ env.BACKEND_TAG }}
|
||||
docker save -o ~/${{ env.TAR_SUFFIX }}/plane-backend-ee.tar ${{ env.BACKEND_TAG }}
|
||||
|
||||
docker pull ${{ env.PROXY_TAG }}
|
||||
docker save -o ~/${{ env.TAR_SUFFIX }}/plane-proxy-ee.tar ${{ env.PROXY_TAG }}
|
||||
|
||||
aws s3 cp ~/${{ env.TAR_SUFFIX }} s3://${{ secrets.PLANE_DOCKER_BUCKET }}/plane-ee/${{ env.TAR_SUFFIX }} --recursive
|
||||
|
||||
rm -rf ~/${{ env.TAR_SUFFIX }}
|
||||
|
||||
rm -rf ~/${{ env.ARTIFACT_SUFFIX }}
|
||||
|
||||
18
deploy/cli-install/Caddyfile
Normal file
18
deploy/cli-install/Caddyfile
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
email {$CERT_EMAIL}
|
||||
{$CERT_ACME_DNS}
|
||||
}
|
||||
|
||||
{$APP_PROTOCOL}://{$DOMAIN_NAME} {
|
||||
request_body {
|
||||
max_size {$FILE_SIZE_LIMIT}
|
||||
}
|
||||
|
||||
reverse_proxy /spaces/* space:3000
|
||||
|
||||
reverse_proxy /api/* api:8000
|
||||
|
||||
reverse_proxy /{$BUCKET_NAME}/* plane-minio:9000
|
||||
|
||||
reverse_proxy /* web:3000
|
||||
}
|
||||
161
deploy/cli-install/docker-compose-caddy.yml
Normal file
161
deploy/cli-install/docker-compose-caddy.yml
Normal file
@@ -0,0 +1,161 @@
|
||||
x-proxy-env: &proxy-env
|
||||
environment:
|
||||
- DOMAIN_NAME=${DOMAIN_NAME:-localhost}
|
||||
- CERT_EMAIL=${CERT_EMAIL:-admin@localhost}
|
||||
- APP_PROTOCOL=${APP_PROTOCOL:-http}
|
||||
- CERT_ACME_DNS=${CERT_ACME_DNS:-}
|
||||
- BUCKET_NAME=${BUCKET_NAME:-uploads}
|
||||
- FILE_SIZE_LIMIT=${FILE_SIZE_LIMIT:-5242880}
|
||||
- LISTEN_HTTP_PORT=${LISTEN_HTTP_PORT:-80}
|
||||
- LISTEN_HTTPS_PORT=${LISTEN_HTTPS_PORT:-443}
|
||||
|
||||
x-app-env: &app-env
|
||||
environment:
|
||||
- NGINX_PORT=${NGINX_PORT:-80}
|
||||
- WEB_URL=${WEB_URL:-http://localhost}
|
||||
- DEBUG=${DEBUG:-0}
|
||||
- SENTRY_DSN=${SENTRY_DSN:-""}
|
||||
- SENTRY_ENVIRONMENT=${SENTRY_ENVIRONMENT:-"production"}
|
||||
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-}
|
||||
# Gunicorn Workers
|
||||
- GUNICORN_WORKERS=${GUNICORN_WORKERS:-2}
|
||||
#DB SETTINGS
|
||||
- PGHOST=${PGHOST:-plane-db}
|
||||
- PGDATABASE=${PGDATABASE:-plane}
|
||||
- POSTGRES_USER=${POSTGRES_USER:-plane}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-plane}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-plane}
|
||||
- PGDATA=${PGDATA:-/var/lib/postgresql/data}
|
||||
- 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://plane-redis:6379/}
|
||||
# Application secret
|
||||
- SECRET_KEY=${SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5}
|
||||
# DATA STORE SETTINGS
|
||||
- USE_MINIO=${USE_MINIO:-1}
|
||||
- AWS_REGION=${AWS_REGION:-""}
|
||||
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-"access-key"}
|
||||
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-"secret-key"}
|
||||
- AWS_S3_ENDPOINT_URL=${AWS_S3_ENDPOINT_URL:-http://plane-minio:9000}
|
||||
- AWS_S3_BUCKET_NAME=${AWS_S3_BUCKET_NAME:-uploads}
|
||||
- MINIO_ROOT_USER=${MINIO_ROOT_USER:-"access-key"}
|
||||
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:-"secret-key"}
|
||||
- BUCKET_NAME=${BUCKET_NAME:-uploads}
|
||||
- FILE_SIZE_LIMIT=${FILE_SIZE_LIMIT:-5242880}
|
||||
|
||||
services:
|
||||
web:
|
||||
<<: *app-env
|
||||
image: registry.plane.tools/plane-enterprise/web-enterprise:${APP_RELEASE_VERSION}
|
||||
restart: unless-stopped
|
||||
command: /usr/local/bin/start.sh web/server.js web
|
||||
deploy:
|
||||
replicas: ${WEB_REPLICAS:-1}
|
||||
depends_on:
|
||||
- api
|
||||
- worker
|
||||
|
||||
space:
|
||||
<<: *app-env
|
||||
image: registry.plane.tools/plane-enterprise/space-enterprise:${APP_RELEASE_VERSION}
|
||||
restart: unless-stopped
|
||||
command: /usr/local/bin/start.sh space/server.js space
|
||||
deploy:
|
||||
replicas: ${SPACE_REPLICAS:-1}
|
||||
depends_on:
|
||||
- api
|
||||
- worker
|
||||
- web
|
||||
|
||||
api:
|
||||
<<: *app-env
|
||||
image: registry.plane.tools/plane-enterprise/backend-enterprise:${APP_RELEASE_VERSION}
|
||||
restart: unless-stopped
|
||||
command: ./bin/takeoff
|
||||
deploy:
|
||||
replicas: ${API_REPLICAS:-1}
|
||||
# volumes:
|
||||
# - ${INSTALL_DIR}/logs/api:/code/plane/logs
|
||||
depends_on:
|
||||
- plane-db
|
||||
- plane-redis
|
||||
|
||||
worker:
|
||||
<<: *app-env
|
||||
image: registry.plane.tools/plane-enterprise/backend-enterprise:${APP_RELEASE_VERSION}
|
||||
restart: unless-stopped
|
||||
command: ./bin/worker
|
||||
# volumes:
|
||||
# - ${INSTALL_DIR}/logs/worker:/code/plane/logs
|
||||
depends_on:
|
||||
- api
|
||||
- plane-db
|
||||
- plane-redis
|
||||
|
||||
beat-worker:
|
||||
<<: *app-env
|
||||
image: registry.plane.tools/plane-enterprise/backend-enterprise:${APP_RELEASE_VERSION}
|
||||
restart: unless-stopped
|
||||
command: ./bin/beat
|
||||
# volumes:
|
||||
# - ${INSTALL_DIR}/logs/beat-worker:/code/plane/logs
|
||||
depends_on:
|
||||
- api
|
||||
- plane-db
|
||||
- plane-redis
|
||||
|
||||
migrator:
|
||||
<<: *app-env
|
||||
image: registry.plane.tools/plane-enterprise/backend-enterprise:${APP_RELEASE_VERSION}
|
||||
restart: no
|
||||
command: >
|
||||
sh -c "python manage.py wait_for_db &&
|
||||
python manage.py migrate"
|
||||
# volumes:
|
||||
# - ${INSTALL_DIR}/logs/migrator:/code/plane/logs
|
||||
depends_on:
|
||||
- plane-db
|
||||
- plane-redis
|
||||
|
||||
plane-db:
|
||||
<<: *app-env
|
||||
image: postgres:15.5-alpine
|
||||
restart: unless-stopped
|
||||
command: postgres -c 'max_connections=1000'
|
||||
volumes:
|
||||
- ${INSTALL_DIR}/data/db:/var/lib/postgresql/data
|
||||
|
||||
plane-redis:
|
||||
<<: *app-env
|
||||
image: redis:7.2.4-alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ${INSTALL_DIR}/data/redis:/data
|
||||
|
||||
plane-minio:
|
||||
<<: *app-env
|
||||
image: minio/minio:latest
|
||||
restart: unless-stopped
|
||||
command: server /export --console-address ":9090"
|
||||
volumes:
|
||||
- ${INSTALL_DIR}/data/minio/uploads:/export
|
||||
- ${INSTALL_DIR}/data/minio/data:/data
|
||||
|
||||
# Comment this if you already have a reverse proxy running
|
||||
proxy:
|
||||
<<: *proxy-env
|
||||
image: makeplane/caddy:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- ${LISTEN_HTTP_PORT:-80}:80
|
||||
- ${LISTEN_HTTPS_PORT:-443}:443
|
||||
volumes:
|
||||
- ${INSTALL_DIR}/Caddyfile:/etc/caddy/Caddyfile
|
||||
- ${INSTALL_DIR}/caddy/config:/config
|
||||
- ${INSTALL_DIR}/caddy/data:/data
|
||||
depends_on:
|
||||
- web
|
||||
- api
|
||||
- space
|
||||
156
deploy/cli-install/docker-compose.yml
Normal file
156
deploy/cli-install/docker-compose.yml
Normal file
@@ -0,0 +1,156 @@
|
||||
# version: "3.8"
|
||||
|
||||
x-app-env: &app-env
|
||||
environment:
|
||||
- NGINX_PORT=${NGINX_PORT:-80}
|
||||
- WEB_URL=${WEB_URL:-http://localhost}
|
||||
- DEBUG=${DEBUG:-0}
|
||||
- SENTRY_DSN=${SENTRY_DSN:-""}
|
||||
- SENTRY_ENVIRONMENT=${SENTRY_ENVIRONMENT:-"production"}
|
||||
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-}
|
||||
# Gunicorn Workers
|
||||
- GUNICORN_WORKERS=${GUNICORN_WORKERS:-2}
|
||||
#DB SETTINGS
|
||||
- PGHOST=${PGHOST:-plane-db}
|
||||
- PGDATABASE=${PGDATABASE:-plane}
|
||||
- POSTGRES_USER=${POSTGRES_USER:-plane}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-plane}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-plane}
|
||||
- PGDATA=${PGDATA:-/var/lib/postgresql/data}
|
||||
- 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://plane-redis:6379/}
|
||||
# Application secret
|
||||
- SECRET_KEY=${SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5}
|
||||
# DATA STORE SETTINGS
|
||||
- USE_MINIO=${USE_MINIO:-1}
|
||||
- AWS_REGION=${AWS_REGION:-""}
|
||||
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-"access-key"}
|
||||
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-"secret-key"}
|
||||
- AWS_S3_ENDPOINT_URL=${AWS_S3_ENDPOINT_URL:-http://plane-minio:9000}
|
||||
- AWS_S3_BUCKET_NAME=${AWS_S3_BUCKET_NAME:-uploads}
|
||||
- MINIO_ROOT_USER=${MINIO_ROOT_USER:-"access-key"}
|
||||
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:-"secret-key"}
|
||||
- BUCKET_NAME=${BUCKET_NAME:-uploads}
|
||||
- FILE_SIZE_LIMIT=${FILE_SIZE_LIMIT:-5242880}
|
||||
|
||||
services:
|
||||
web:
|
||||
<<: *app-env
|
||||
image: registry.plane.tools/plane-enterprise/web-enterprise:${APP_RELEASE_VERSION}
|
||||
pull_policy: if_not_present
|
||||
restart: unless-stopped
|
||||
command: /usr/local/bin/start.sh web/server.js web
|
||||
deploy:
|
||||
replicas: ${WEB_REPLICAS:-1}
|
||||
depends_on:
|
||||
- api
|
||||
- worker
|
||||
|
||||
space:
|
||||
<<: *app-env
|
||||
image: registry.plane.tools/plane-enterprise/space-enterprise:${APP_RELEASE_VERSION}
|
||||
pull_policy: if_not_present
|
||||
restart: unless-stopped
|
||||
command: /usr/local/bin/start.sh space/server.js space
|
||||
deploy:
|
||||
replicas: ${SPACE_REPLICAS:-1}
|
||||
depends_on:
|
||||
- api
|
||||
- worker
|
||||
- web
|
||||
|
||||
api:
|
||||
<<: *app-env
|
||||
image: registry.plane.tools/plane-enterprise/backend-enterprise:${APP_RELEASE_VERSION}
|
||||
pull_policy: if_not_present
|
||||
restart: unless-stopped
|
||||
command: ./bin/takeoff
|
||||
deploy:
|
||||
replicas: ${API_REPLICAS:-1}
|
||||
# volumes:
|
||||
# - ${INSTALL_DIR}/logs/api:/code/plane/logs
|
||||
depends_on:
|
||||
- plane-db
|
||||
- plane-redis
|
||||
|
||||
worker:
|
||||
<<: *app-env
|
||||
image: registry.plane.tools/plane-enterprise/backend-enterprise:${APP_RELEASE_VERSION}
|
||||
pull_policy: if_not_present
|
||||
restart: unless-stopped
|
||||
command: ./bin/worker
|
||||
# volumes:
|
||||
# - ${INSTALL_DIR}/logs/worker:/code/plane/logs
|
||||
depends_on:
|
||||
- api
|
||||
- plane-db
|
||||
- plane-redis
|
||||
|
||||
beat-worker:
|
||||
<<: *app-env
|
||||
image: registry.plane.tools/plane-enterprise/backend-enterprise:${APP_RELEASE_VERSION}
|
||||
pull_policy: if_not_present
|
||||
restart: unless-stopped
|
||||
command: ./bin/beat
|
||||
# volumes:
|
||||
# - ${INSTALL_DIR}/logs/beat-worker:/code/plane/logs
|
||||
depends_on:
|
||||
- api
|
||||
- plane-db
|
||||
- plane-redis
|
||||
|
||||
migrator:
|
||||
<<: *app-env
|
||||
image: registry.plane.tools/plane-enterprise/backend-enterprise:${APP_RELEASE_VERSION}
|
||||
pull_policy: if_not_present
|
||||
restart: no
|
||||
command: >
|
||||
sh -c "python manage.py wait_for_db &&
|
||||
python manage.py migrate"
|
||||
# volumes:
|
||||
# - ${INSTALL_DIR}/logs/migrator:/code/plane/logs
|
||||
depends_on:
|
||||
- plane-db
|
||||
- plane-redis
|
||||
|
||||
plane-db:
|
||||
<<: *app-env
|
||||
image: postgres:15.5-alpine
|
||||
pull_policy: if_not_present
|
||||
restart: unless-stopped
|
||||
command: postgres -c 'max_connections=1000'
|
||||
volumes:
|
||||
- ${INSTALL_DIR}/data/db:/var/lib/postgresql/data
|
||||
plane-redis:
|
||||
<<: *app-env
|
||||
image: redis:7.2.4-alpine
|
||||
pull_policy: if_not_present
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ${INSTALL_DIR}/data/redis:/data
|
||||
|
||||
plane-minio:
|
||||
<<: *app-env
|
||||
image: minio/minio:latest
|
||||
pull_policy: if_not_present
|
||||
restart: unless-stopped
|
||||
command: server /export --console-address ":9090"
|
||||
volumes:
|
||||
- ${INSTALL_DIR}/data/minio/uploads:/export
|
||||
- ${INSTALL_DIR}/data/minio/data:/data
|
||||
|
||||
# Comment this if you already have a reverse proxy running
|
||||
proxy:
|
||||
<<: *app-env
|
||||
image: registry.plane.tools/plane-enterprise/proxy-enterprise:${APP_RELEASE_VERSION}
|
||||
pull_policy: if_not_present
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- ${NGINX_PORT}:80
|
||||
depends_on:
|
||||
- web
|
||||
- api
|
||||
- space
|
||||
56
deploy/cli-install/variables.env
Normal file
56
deploy/cli-install/variables.env
Normal file
@@ -0,0 +1,56 @@
|
||||
INSTALL_DIR=/opt/plane
|
||||
|
||||
WEB_REPLICAS=1
|
||||
SPACE_REPLICAS=1
|
||||
API_REPLICAS=1
|
||||
|
||||
NGINX_PORT=80
|
||||
LISTEN_HTTP_PORT=80
|
||||
LISTEN_HTTPS_PORT=443
|
||||
|
||||
APP_PROTOCOL=http
|
||||
|
||||
# If SSL Cert to be generated, set CERT_EMAIL and APP_PROTOCOL to https
|
||||
CERT_EMAIL=admin@localhost
|
||||
|
||||
# For DNS Challenge based certificate generation, set the CERT_ACME_DNS
|
||||
# CERT_ACME_DNS=acme_dns CERT_DNS_PROVIDER CERT_DNS_PROVIDER_API_KEY
|
||||
CERT_ACME_DNS=
|
||||
|
||||
WEB_URL=http://localhost
|
||||
DEBUG=0
|
||||
SENTRY_DSN=
|
||||
SENTRY_ENVIRONMENT=production
|
||||
CORS_ALLOWED_ORIGINS=http://localhost
|
||||
|
||||
#DB SETTINGS
|
||||
PGHOST=plane-db
|
||||
PGDATABASE=plane
|
||||
POSTGRES_USER=plane
|
||||
POSTGRES_PASSWORD=plane
|
||||
POSTGRES_DB=plane
|
||||
PGDATA=/var/lib/postgresql/data
|
||||
DATABASE_URL=
|
||||
|
||||
# REDIS SETTINGS
|
||||
REDIS_HOST=plane-redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_URL=
|
||||
|
||||
# Secret Key
|
||||
SECRET_KEY=60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5
|
||||
|
||||
# DATA STORE SETTINGS
|
||||
USE_MINIO=1
|
||||
AWS_REGION=
|
||||
AWS_ACCESS_KEY_ID=access-key
|
||||
AWS_SECRET_ACCESS_KEY=secret-key
|
||||
AWS_S3_ENDPOINT_URL=http://plane-minio:9000
|
||||
AWS_S3_BUCKET_NAME=uploads
|
||||
MINIO_ROOT_USER=access-key
|
||||
MINIO_ROOT_PASSWORD=secret-key
|
||||
BUCKET_NAME=uploads
|
||||
FILE_SIZE_LIMIT=5242880
|
||||
|
||||
# Gunicorn Workers
|
||||
GUNICORN_WORKERS=2
|
||||
@@ -15,7 +15,7 @@ const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>((props, re
|
||||
// refs
|
||||
const textAreaRef = useRef<any>(ref);
|
||||
// auto re-size
|
||||
useAutoResizeTextArea(textAreaRef);
|
||||
useAutoResizeTextArea(textAreaRef, value);
|
||||
|
||||
return (
|
||||
<textarea
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
import { useEffect } from "react";
|
||||
import { useLayoutEffect } from "react";
|
||||
|
||||
export const useAutoResizeTextArea = (textAreaRef: React.RefObject<HTMLTextAreaElement>) => {
|
||||
useEffect(() => {
|
||||
export const useAutoResizeTextArea = (
|
||||
textAreaRef: React.RefObject<HTMLTextAreaElement>,
|
||||
value: string | number | readonly string[]
|
||||
) => {
|
||||
useLayoutEffect(() => {
|
||||
const textArea = textAreaRef.current;
|
||||
if (!textArea) return;
|
||||
|
||||
const resizeTextArea = () => {
|
||||
textArea.style.height = "auto";
|
||||
const computedHeight = textArea.scrollHeight + "px";
|
||||
textArea.style.height = computedHeight;
|
||||
};
|
||||
|
||||
const handleInput = () => resizeTextArea();
|
||||
|
||||
// resize on mount
|
||||
resizeTextArea();
|
||||
|
||||
textArea.addEventListener("input", handleInput);
|
||||
return () => {
|
||||
textArea.removeEventListener("input", handleInput);
|
||||
};
|
||||
}, [textAreaRef]);
|
||||
// We need to reset the height momentarily to get the correct scrollHeight for the textarea
|
||||
textArea.style.height = "0px";
|
||||
const scrollHeight = textArea.scrollHeight;
|
||||
textArea.style.height = scrollHeight + "px";
|
||||
}, [textAreaRef, value]);
|
||||
};
|
||||
|
||||
@@ -178,7 +178,9 @@ export const CommandModal: React.FC = observer(() => {
|
||||
return 0;
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
// when search is empty and page is undefined
|
||||
// when search term is not empty, esc should clear the search term
|
||||
if (e.key === "Escape" && searchTerm) setSearchTerm("");
|
||||
|
||||
// when user tries to close the modal with esc
|
||||
if (e.key === "Escape" && !page && !searchTerm) closePalette();
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import Image from "next/image";
|
||||
// headless ui
|
||||
import { Tab } from "@headlessui/react";
|
||||
import {
|
||||
IIssueFilterOptions,
|
||||
IIssueFilters,
|
||||
IModule,
|
||||
TAssigneesDistribution,
|
||||
TCompletionChartDistribution,
|
||||
@@ -37,6 +39,9 @@ type Props = {
|
||||
roundedTab?: boolean;
|
||||
noBackground?: boolean;
|
||||
isPeekView?: boolean;
|
||||
isCompleted?: boolean;
|
||||
filters?: IIssueFilters | undefined;
|
||||
handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string | string[]) => void;
|
||||
};
|
||||
|
||||
export const SidebarProgressStats: React.FC<Props> = ({
|
||||
@@ -47,6 +52,9 @@ export const SidebarProgressStats: React.FC<Props> = ({
|
||||
roundedTab,
|
||||
noBackground,
|
||||
isPeekView = false,
|
||||
isCompleted = false,
|
||||
filters,
|
||||
handleFiltersUpdate,
|
||||
}) => {
|
||||
const { storedValue: tab, setValue: setTab } = useLocalStorage("tab", "Assignees");
|
||||
|
||||
@@ -145,20 +153,11 @@ export const SidebarProgressStats: React.FC<Props> = ({
|
||||
}
|
||||
completed={assignee.completed_issues}
|
||||
total={assignee.total_issues}
|
||||
{...(!isPeekView && {
|
||||
onClick: () => {
|
||||
// TODO: set filters here
|
||||
// if (filters?.assignees?.includes(assignee.assignee_id ?? ""))
|
||||
// setFilters({
|
||||
// assignees: filters?.assignees?.filter((a) => a !== assignee.assignee_id),
|
||||
// });
|
||||
// else
|
||||
// setFilters({
|
||||
// assignees: [...(filters?.assignees ?? []), assignee.assignee_id ?? ""],
|
||||
// });
|
||||
},
|
||||
// selected: filters?.assignees?.includes(assignee.assignee_id ?? ""),
|
||||
})}
|
||||
{...(!isPeekView &&
|
||||
!isCompleted && {
|
||||
onClick: () => handleFiltersUpdate("assignees", assignee.assignee_id ?? ""),
|
||||
selected: filters?.filters?.assignees?.includes(assignee.assignee_id ?? ""),
|
||||
})}
|
||||
/>
|
||||
);
|
||||
else
|
||||
@@ -192,35 +191,52 @@ export const SidebarProgressStats: React.FC<Props> = ({
|
||||
className="flex w-full flex-col gap-1.5 overflow-y-auto pt-3.5 vertical-scrollbar scrollbar-sm"
|
||||
>
|
||||
{distribution && distribution?.labels.length > 0 ? (
|
||||
distribution.labels.map((label, index) => (
|
||||
<SingleProgressStats
|
||||
key={label.label_id ?? `no-label-${index}`}
|
||||
title={
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className="block h-3 w-3 rounded-full"
|
||||
style={{
|
||||
backgroundColor: label.color ?? "transparent",
|
||||
}}
|
||||
/>
|
||||
<span className="text-xs">{label.label_name ?? "No labels"}</span>
|
||||
</div>
|
||||
}
|
||||
completed={label.completed_issues}
|
||||
total={label.total_issues}
|
||||
{...(!isPeekView && {
|
||||
// TODO: set filters here
|
||||
onClick: () => {
|
||||
// if (filters.labels?.includes(label.label_id ?? ""))
|
||||
// setFilters({
|
||||
// labels: filters?.labels?.filter((l) => l !== label.label_id),
|
||||
// });
|
||||
// else setFilters({ labels: [...(filters?.labels ?? []), label.label_id ?? ""] });
|
||||
},
|
||||
// selected: filters?.labels?.includes(label.label_id ?? ""),
|
||||
})}
|
||||
/>
|
||||
))
|
||||
distribution.labels.map((label, index) => {
|
||||
if (label.label_id) {
|
||||
return (
|
||||
<SingleProgressStats
|
||||
key={label.label_id}
|
||||
title={
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className="block h-3 w-3 rounded-full"
|
||||
style={{
|
||||
backgroundColor: label.color ?? "transparent",
|
||||
}}
|
||||
/>
|
||||
<span className="text-xs">{label.label_name ?? "No labels"}</span>
|
||||
</div>
|
||||
}
|
||||
completed={label.completed_issues}
|
||||
total={label.total_issues}
|
||||
{...(!isPeekView &&
|
||||
!isCompleted && {
|
||||
onClick: () => handleFiltersUpdate("labels", label.label_id ?? ""),
|
||||
selected: filters?.filters?.labels?.includes(label.label_id ?? `no-label-${index}`),
|
||||
})}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<SingleProgressStats
|
||||
key={`no-label-${index}`}
|
||||
title={
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className="block h-3 w-3 rounded-full"
|
||||
style={{
|
||||
backgroundColor: label.color ?? "transparent",
|
||||
}}
|
||||
/>
|
||||
<span className="text-xs">{label.label_name ?? "No labels"}</span>
|
||||
</div>
|
||||
}
|
||||
completed={label.completed_issues}
|
||||
total={label.total_issues}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})
|
||||
) : (
|
||||
<div className="flex h-full flex-col items-center justify-center gap-2">
|
||||
<div className="flex h-20 w-20 items-center justify-center rounded-full bg-custom-background-80">
|
||||
|
||||
@@ -235,7 +235,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = observer((props) => {
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="absolute right-4 bottom-3.5 z-[5] flex items-center gap-1.5">
|
||||
<div className="absolute right-4 bottom-3.5 flex items-center gap-1.5">
|
||||
{isEditingAllowed && (
|
||||
<FavoriteStar
|
||||
onClick={(e) => {
|
||||
|
||||
@@ -202,7 +202,7 @@ export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
|
||||
<span className="h-6 w-52 flex-shrink-0" />
|
||||
</div>
|
||||
</Link>
|
||||
<div className="absolute right-5 bottom-8 z-[5] flex items-center gap-1.5">
|
||||
<div className="absolute right-5 bottom-8 flex items-center gap-1.5">
|
||||
<div className="relative flex w-full flex-shrink-0 items-center justify-between gap-2.5 md:w-auto md:flex-shrink-0 md:justify-end">
|
||||
{currentCycle && (
|
||||
<div
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
// types
|
||||
import { ICycle } from "@plane/types";
|
||||
import { ICycle, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
import { Avatar, ArchiveIcon, CustomMenu, Loader, LayersIcon, TOAST_TYPE, setToast, TextArea } from "@plane/ui";
|
||||
// components
|
||||
@@ -27,12 +27,13 @@ import { DateRangeDropdown } from "@/components/dropdowns";
|
||||
// constants
|
||||
import { CYCLE_STATUS } from "@/constants/cycle";
|
||||
import { CYCLE_UPDATED } from "@/constants/event-tracker";
|
||||
import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue";
|
||||
import { EUserWorkspaceRoles } from "@/constants/workspace";
|
||||
// helpers
|
||||
import { findHowManyDaysLeft, getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
import { copyUrlToClipboard } from "@/helpers/string.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useCycle, useUser, useMember } from "@/hooks/store";
|
||||
import { useEventTracker, useCycle, useUser, useMember, useIssues } from "@/hooks/store";
|
||||
// services
|
||||
import { CycleService } from "@/services/cycle.service";
|
||||
|
||||
@@ -191,25 +192,36 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: refactor this
|
||||
// const handleFiltersUpdate = useCallback(
|
||||
// (key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||
// if (!workspaceSlug || !projectId) return;
|
||||
// const newValues = issueFilters?.filters?.[key] ?? [];
|
||||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.CYCLE);
|
||||
|
||||
// if (Array.isArray(value)) {
|
||||
// value.forEach((val) => {
|
||||
// if (!newValues.includes(val)) newValues.push(val);
|
||||
// });
|
||||
// } else {
|
||||
// if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||
// else newValues.push(value);
|
||||
// }
|
||||
const handleFiltersUpdate = useCallback(
|
||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
const newValues = issueFilters?.filters?.[key] ?? [];
|
||||
|
||||
// updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, { [key]: newValues }, cycleId);
|
||||
// },
|
||||
// [workspaceSlug, projectId, cycleId, issueFilters, updateFilters]
|
||||
// );
|
||||
if (Array.isArray(value)) {
|
||||
// this validation is majorly for the filter start_date, target_date custom
|
||||
value.forEach((val) => {
|
||||
if (!newValues.includes(val)) newValues.push(val);
|
||||
else newValues.splice(newValues.indexOf(val), 1);
|
||||
});
|
||||
} else {
|
||||
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||
else newValues.push(value);
|
||||
}
|
||||
|
||||
updateFilters(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
EIssueFilterType.FILTERS,
|
||||
{ [key]: newValues },
|
||||
cycleId
|
||||
);
|
||||
},
|
||||
[workspaceSlug, projectId, cycleId, issueFilters, updateFilters]
|
||||
);
|
||||
|
||||
const cycleStatus = cycleDetails?.status?.toLocaleLowerCase();
|
||||
const isCompleted = cycleStatus === "completed";
|
||||
@@ -251,8 +263,8 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
? "0 Issue"
|
||||
: `${cycleDetails.progress_snapshot.completed_issues}/${cycleDetails.progress_snapshot.total_issues}`
|
||||
: cycleDetails.total_issues === 0
|
||||
? "0 Issue"
|
||||
: `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`;
|
||||
? "0 Issue"
|
||||
: `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`;
|
||||
|
||||
const daysLeft = findHowManyDaysLeft(cycleDetails.end_date);
|
||||
|
||||
@@ -551,6 +563,9 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
}}
|
||||
totalIssues={cycleDetails.progress_snapshot.total_issues}
|
||||
isPeekView={Boolean(peekCycle)}
|
||||
isCompleted={isCompleted}
|
||||
filters={issueFilters}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -570,6 +585,9 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
}}
|
||||
totalIssues={cycleDetails.total_issues}
|
||||
isPeekView={Boolean(peekCycle)}
|
||||
isCompleted={isCompleted}
|
||||
filters={issueFilters}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -20,8 +20,8 @@ import { useLabel, useMember, useUser, useIssues } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
const GLOBAL_VIEW_LAYOUTS = [
|
||||
{ key: "list", title: "List", link: "/workspace-views", icon: List },
|
||||
{ key: "spreadsheet", title: "Spreadsheet", link: "/workspace-views/all-issues", icon: Sheet },
|
||||
{ key: "list", title: "List", link: "workspace-views", icon: List },
|
||||
{ key: "spreadsheet", title: "Spreadsheet", link: "workspace-views/all-issues", icon: Sheet },
|
||||
];
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -26,7 +26,7 @@ export const FilterState: React.FC<Props> = observer((props) => {
|
||||
const sortedOptions = useMemo(() => {
|
||||
const filteredOptions = (states ?? []).filter((s) => s.name.toLowerCase().includes(searchQuery.toLowerCase()));
|
||||
|
||||
return sortBy(filteredOptions, [(s) => !(appliedFilters ?? []).includes(s.id), (s) => s.name.toLowerCase()]);
|
||||
return sortBy(filteredOptions, [(s) => !(appliedFilters ?? []).includes(s.id)]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchQuery]);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { Avatar, Loader } from "@plane/ui";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||
import { useMember } from "@/hooks/store";
|
||||
import { useMember, useUser } from "@/hooks/store";
|
||||
// components
|
||||
// ui
|
||||
|
||||
@@ -22,19 +22,22 @@ export const FilterLead: React.FC<Props> = observer((props: Props) => {
|
||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
|
||||
const sortedOptions = useMemo(() => {
|
||||
const filteredOptions = (memberIds || []).filter(
|
||||
(memberId) => getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
const filteredOptions = (memberIds || []).filter((memberId) =>
|
||||
getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
return sortBy(filteredOptions, [
|
||||
(memberId) => !(appliedFilters ?? []).includes(memberId),
|
||||
(memberId) => memberId !== currentUser?.id,
|
||||
(memberId) => getUserDetails(memberId)?.display_name.toLowerCase(),
|
||||
]);
|
||||
}, [appliedFilters, getUserDetails, memberIds, , searchQuery]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchQuery]);
|
||||
|
||||
const handleViewToggle = () => {
|
||||
if (!sortedOptions) return;
|
||||
@@ -65,7 +68,7 @@ export const FilterLead: React.FC<Props> = observer((props: Props) => {
|
||||
isChecked={appliedFilters?.includes(member.id) ? true : false}
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={<Avatar name={member.display_name} src={member.avatar} showTooltip={false} size="md" />}
|
||||
title={member.display_name}
|
||||
title={currentUser?.id === member.id ? "You" : member?.display_name}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { Avatar, Loader } from "@plane/ui";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||
import { useMember } from "@/hooks/store";
|
||||
import { useMember, useUser } from "@/hooks/store";
|
||||
// components
|
||||
// ui
|
||||
|
||||
@@ -22,16 +22,18 @@ export const FilterMembers: React.FC<Props> = observer((props: Props) => {
|
||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
|
||||
const sortedOptions = useMemo(() => {
|
||||
const filteredOptions = (memberIds || []).filter(
|
||||
(memberId) => getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
const filteredOptions = (memberIds || []).filter((memberId) =>
|
||||
getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
return sortBy(filteredOptions, [
|
||||
(memberId) => !(appliedFilters ?? []).includes(memberId),
|
||||
(memberId) => memberId !== currentUser?.id,
|
||||
(memberId) => getUserDetails(memberId)?.display_name.toLowerCase(),
|
||||
]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -66,7 +68,7 @@ export const FilterMembers: React.FC<Props> = observer((props: Props) => {
|
||||
isChecked={appliedFilters?.includes(member.id) ? true : false}
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={<Avatar name={member.display_name} src={member.avatar} showTooltip={false} size="md" />}
|
||||
title={member.display_name}
|
||||
title={currentUser?.id === member.id ? "You" : member?.display_name}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -227,7 +227,7 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="absolute right-4 bottom-3.5 z-[5] flex items-center gap-1.5">
|
||||
<div className="absolute right-4 bottom-3.5 flex items-center gap-1.5">
|
||||
{isEditingAllowed && (
|
||||
<FavoriteStar
|
||||
onClick={(e) => {
|
||||
|
||||
@@ -175,7 +175,7 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
|
||||
<span className="h-6 w-52 flex-shrink-0" />
|
||||
</div>
|
||||
</Link>
|
||||
<div className="absolute right-5 bottom-8 z-[5] flex items-center gap-1.5">
|
||||
<div className="absolute right-5 bottom-8 flex items-center gap-1.5">
|
||||
<div className="flex flex-shrink-0 items-center justify-center">
|
||||
{moduleStatus && (
|
||||
<span
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/router";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
UserCircle2,
|
||||
} from "lucide-react";
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
import { ILinkDetails, IModule, ModuleLink } from "@plane/types";
|
||||
import { IIssueFilterOptions, ILinkDetails, IModule, ModuleLink } from "@plane/types";
|
||||
// ui
|
||||
import {
|
||||
CustomMenu,
|
||||
@@ -41,13 +41,14 @@ import {
|
||||
MODULE_LINK_UPDATED,
|
||||
MODULE_UPDATED,
|
||||
} from "@/constants/event-tracker";
|
||||
import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue";
|
||||
import { MODULE_STATUS } from "@/constants/module";
|
||||
import { EUserProjectRoles } from "@/constants/project";
|
||||
// helpers
|
||||
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
import { copyUrlToClipboard } from "@/helpers/string.helper";
|
||||
// hooks
|
||||
import { useModule, useUser, useEventTracker } from "@/hooks/store";
|
||||
import { useModule, useUser, useEventTracker, useIssues } from "@/hooks/store";
|
||||
// types
|
||||
|
||||
const defaultValues: Partial<IModule> = {
|
||||
@@ -82,6 +83,9 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
const { getModuleById, updateModuleDetails, createModuleLink, updateModuleLink, deleteModuleLink, restoreModule } =
|
||||
useModule();
|
||||
const { setTrackElement, captureModuleEvent, captureEvent } = useEventTracker();
|
||||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.MODULE);
|
||||
const moduleDetails = getModuleById(moduleId);
|
||||
|
||||
const moduleState = moduleDetails?.status?.toLocaleLowerCase();
|
||||
@@ -245,6 +249,33 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
});
|
||||
}, [moduleDetails, reset]);
|
||||
|
||||
const handleFiltersUpdate = useCallback(
|
||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
const newValues = issueFilters?.filters?.[key] ?? [];
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
// this validation is majorly for the filter start_date, target_date custom
|
||||
value.forEach((val) => {
|
||||
if (!newValues.includes(val)) newValues.push(val);
|
||||
else newValues.splice(newValues.indexOf(val), 1);
|
||||
});
|
||||
} else {
|
||||
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||
else newValues.push(value);
|
||||
}
|
||||
|
||||
updateFilters(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
EIssueFilterType.FILTERS,
|
||||
{ [key]: newValues },
|
||||
moduleId
|
||||
);
|
||||
},
|
||||
[workspaceSlug, projectId, moduleId, issueFilters, updateFilters]
|
||||
);
|
||||
|
||||
const startDate = getDate(moduleDetails?.start_date);
|
||||
const endDate = getDate(moduleDetails?.target_date);
|
||||
const isStartValid = startDate && startDate <= new Date();
|
||||
@@ -599,6 +630,8 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
totalIssues={moduleDetails.total_issues}
|
||||
module={moduleDetails}
|
||||
isPeekView={Boolean(peekModule)}
|
||||
filters={issueFilters}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -10,7 +10,7 @@ export const OutlineHeading1 = ({ marking, onClick }: HeadingProps) => (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="ml-4 cursor-pointer text-sm font-medium text-custom-text-400 hover:text-custom-primary-100 max-md:ml-2.5"
|
||||
className="text-sm text-left font-medium text-custom-text-300 hover:text-custom-primary-100"
|
||||
>
|
||||
{marking.text}
|
||||
</button>
|
||||
@@ -20,7 +20,7 @@ export const OutlineHeading2 = ({ marking, onClick }: HeadingProps) => (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="ml-6 cursor-pointer text-xs font-medium text-custom-text-400 hover:text-custom-primary-100"
|
||||
className="ml-2 text-xs text-left font-medium text-custom-text-300 hover:text-custom-primary-100"
|
||||
>
|
||||
{marking.text}
|
||||
</button>
|
||||
@@ -30,7 +30,7 @@ export const OutlineHeading3 = ({ marking, onClick }: HeadingProps) => (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="ml-8 cursor-pointer text-xs font-medium text-custom-text-400 hover:text-custom-primary-100"
|
||||
className="ml-4 text-xs text-left font-medium text-custom-text-300 hover:text-custom-primary-100"
|
||||
>
|
||||
{marking.text}
|
||||
</button>
|
||||
|
||||
@@ -22,12 +22,19 @@ export const PageEditorTitle: React.FC<Props> = observer((props) => {
|
||||
return (
|
||||
<>
|
||||
{readOnly ? (
|
||||
<h6 className="-mt-2 break-words bg-transparent text-4xl font-bold">{title}</h6>
|
||||
<h6
|
||||
className="break-words bg-transparent text-4xl font-bold"
|
||||
style={{
|
||||
lineHeight: "1.2",
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</h6>
|
||||
) : (
|
||||
<>
|
||||
<TextArea
|
||||
onChange={(e) => updateTitle(e.target.value)}
|
||||
className="-mt-2 w-full bg-custom-background text-4xl font-bold outline-none p-0 border-none resize-none rounded-none"
|
||||
className="w-full bg-custom-background text-4xl font-bold outline-none p-0 border-none resize-none rounded-none"
|
||||
style={{
|
||||
lineHeight: "1.2",
|
||||
}}
|
||||
|
||||
@@ -51,7 +51,7 @@ export const PageListBlock: FC<TPageListBlock> = observer((props) => {
|
||||
>
|
||||
{/* page title */}
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={name}>
|
||||
<h5 className="text-base font-semibold truncate">{name}</h5>
|
||||
<h5 className="text-base font-medium truncate">{name}</h5>
|
||||
</Tooltip>
|
||||
|
||||
{/* page properties */}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Avatar, Loader } from "@plane/ui";
|
||||
// components
|
||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store";
|
||||
import { useMember, useUser } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
appliedFilters: string[] | null;
|
||||
@@ -22,6 +22,7 @@ export const FilterCreatedBy: React.FC<Props> = observer((props: Props) => {
|
||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
|
||||
@@ -32,9 +33,11 @@ export const FilterCreatedBy: React.FC<Props> = observer((props: Props) => {
|
||||
|
||||
return sortBy(filteredOptions, [
|
||||
(memberId) => !(appliedFilters ?? []).includes(memberId),
|
||||
(memberId) => memberId !== currentUser?.id,
|
||||
(memberId) => getUserDetails(memberId)?.display_name.toLowerCase(),
|
||||
]);
|
||||
}, [appliedFilters, getUserDetails, memberIds, searchQuery]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchQuery]);
|
||||
|
||||
const handleViewToggle = () => {
|
||||
if (!sortedOptions) return;
|
||||
@@ -65,7 +68,7 @@ export const FilterCreatedBy: React.FC<Props> = observer((props: Props) => {
|
||||
isChecked={appliedFilters?.includes(member.id) ? true : false}
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={<Avatar name={member.display_name} src={member.avatar} showTooltip={false} size="md" />}
|
||||
title={member.display_name}
|
||||
title={currentUser?.id === member.id ? "You" : member?.display_name}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { Avatar, Loader } from "@plane/ui";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||
import { useMember } from "@/hooks/store";
|
||||
import { useMember, useUser } from "@/hooks/store";
|
||||
// components
|
||||
// ui
|
||||
|
||||
@@ -22,16 +22,18 @@ export const FilterLead: React.FC<Props> = observer((props: Props) => {
|
||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
|
||||
const sortedOptions = useMemo(() => {
|
||||
const filteredOptions = (memberIds || []).filter(
|
||||
(memberId) => getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
const filteredOptions = (memberIds || []).filter((memberId) =>
|
||||
getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
return sortBy(filteredOptions, [
|
||||
(memberId) => !(appliedFilters ?? []).includes(memberId),
|
||||
(memberId) => memberId !== currentUser?.id,
|
||||
(memberId) => getUserDetails(memberId)?.display_name.toLowerCase(),
|
||||
]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -66,7 +68,7 @@ export const FilterLead: React.FC<Props> = observer((props: Props) => {
|
||||
isChecked={appliedFilters?.includes(member.id) ? true : false}
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={<Avatar name={member.display_name} src={member.avatar} showTooltip={false} size="md" />}
|
||||
title={member.display_name}
|
||||
title={currentUser?.id === member.id ? "You" : member?.display_name}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { Avatar, Loader } from "@plane/ui";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||
import { useMember } from "@/hooks/store";
|
||||
import { useMember, useUser } from "@/hooks/store";
|
||||
// components
|
||||
// ui
|
||||
|
||||
@@ -22,16 +22,18 @@ export const FilterMembers: React.FC<Props> = observer((props: Props) => {
|
||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
|
||||
const sortedOptions = useMemo(() => {
|
||||
const filteredOptions = (memberIds || []).filter(
|
||||
(memberId) => getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
const filteredOptions = (memberIds || []).filter((memberId) =>
|
||||
getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
return sortBy(filteredOptions, [
|
||||
(memberId) => !(appliedFilters ?? []).includes(memberId),
|
||||
(memberId) => memberId !== currentUser?.id,
|
||||
(memberId) => getUserDetails(memberId)?.display_name.toLowerCase(),
|
||||
]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -66,7 +68,7 @@ export const FilterMembers: React.FC<Props> = observer((props: Props) => {
|
||||
isChecked={appliedFilters?.includes(member.id) ? true : false}
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={<Avatar name={member.display_name} src={member.avatar} showTooltip={false} size="md" />}
|
||||
title={member.display_name}
|
||||
title={currentUser?.id === member.id ? "You" : member?.display_name}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -93,29 +93,24 @@ export const CYCLE_STATUS: {
|
||||
|
||||
export const CYCLE_STATE_GROUPS_DETAILS = [
|
||||
{
|
||||
key: "backlog_issues",
|
||||
title: "Backlog",
|
||||
color: "#F0F0F3",
|
||||
},
|
||||
{
|
||||
key: "unstarted_issues",
|
||||
title: "Unstarted",
|
||||
color: "#FB923C",
|
||||
key: "completed_issues",
|
||||
title: "Completed",
|
||||
color: "#6490FE",
|
||||
},
|
||||
{
|
||||
key: "started_issues",
|
||||
title: "Started",
|
||||
color: "#FFC53D",
|
||||
color: "#FDD97F",
|
||||
},
|
||||
{
|
||||
key: "completed_issues",
|
||||
title: "Completed",
|
||||
color: "#d687ff",
|
||||
key: "unstarted_issues",
|
||||
title: "Unstarted",
|
||||
color: "#FEB055",
|
||||
},
|
||||
{
|
||||
key: "cancelled_issues",
|
||||
title: "Cancelled",
|
||||
color: "#ef4444",
|
||||
key: "backlog_issues",
|
||||
title: "Backlog",
|
||||
color: "#F0F0F3",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups"],
|
||||
values: ["show_empty_groups", "sub_issue"],
|
||||
},
|
||||
},
|
||||
kanban: {
|
||||
|
||||
Reference in New Issue
Block a user