mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'develop' of github.com:makeplane/plane into update-file-uploads
This commit is contained in:
commit
d560d1f7df
1
.github/workflows/build-branch.yml
vendored
1
.github/workflows/build-branch.yml
vendored
@ -6,7 +6,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- preview
|
- preview
|
||||||
- develop
|
|
||||||
release:
|
release:
|
||||||
types: [released, prereleased]
|
types: [released, prereleased]
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ If you want more control over your data, prefer to self-host Plane, please refer
|
|||||||
|
|
||||||
| Installation Methods | Documentation Link |
|
| Installation Methods | Documentation Link |
|
||||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| Docker | [![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white)](https://docs.plane.so/docker-compose) |
|
| Docker | [![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white)](https://docs.plane.so/self-hosting/methods/docker-compose) |
|
||||||
| Kubernetes | [![Kubernetes](https://img.shields.io/badge/kubernetes-%23326ce5.svg?style=for-the-badge&logo=kubernetes&logoColor=white)](https://docs.plane.so/kubernetes) |
|
| Kubernetes | [![Kubernetes](https://img.shields.io/badge/kubernetes-%23326ce5.svg?style=for-the-badge&logo=kubernetes&logoColor=white)](https://docs.plane.so/kubernetes) |
|
||||||
|
|
||||||
`Instance admin` can configure instance settings using our [God-mode](https://docs.plane.so/instance-admin) feature.
|
`Instance admin` can configure instance settings using our [God-mode](https://docs.plane.so/instance-admin) feature.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "plane-api",
|
"name": "plane-api",
|
||||||
"version": "0.16.0"
|
"version": "0.17.0"
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,11 @@ x-app-env: &app-env
|
|||||||
- NGINX_PORT=${NGINX_PORT:-80}
|
- NGINX_PORT=${NGINX_PORT:-80}
|
||||||
- WEB_URL=${WEB_URL:-http://localhost}
|
- WEB_URL=${WEB_URL:-http://localhost}
|
||||||
- DEBUG=${DEBUG:-0}
|
- DEBUG=${DEBUG:-0}
|
||||||
- SENTRY_DSN=${SENTRY_DSN:-""}
|
- SENTRY_DSN=${SENTRY_DSN}
|
||||||
- SENTRY_ENVIRONMENT=${SENTRY_ENVIRONMENT:-"production"}
|
- SENTRY_ENVIRONMENT=${SENTRY_ENVIRONMENT:-"production"}
|
||||||
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-""}
|
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS}
|
||||||
# Gunicorn Workers
|
# Gunicorn Workers
|
||||||
- GUNICORN_WORKERS=${GUNICORN_WORKERS:-2}
|
- GUNICORN_WORKERS=${GUNICORN_WORKERS:-1}
|
||||||
#DB SETTINGS
|
#DB SETTINGS
|
||||||
- PGHOST=${PGHOST:-plane-db}
|
- PGHOST=${PGHOST:-plane-db}
|
||||||
- PGDATABASE=${PGDATABASE:-plane}
|
- PGDATABASE=${PGDATABASE:-plane}
|
||||||
@ -17,11 +17,11 @@ x-app-env: &app-env
|
|||||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-plane}
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-plane}
|
||||||
- POSTGRES_DB=${POSTGRES_DB:-plane}
|
- POSTGRES_DB=${POSTGRES_DB:-plane}
|
||||||
- PGDATA=${PGDATA:-/var/lib/postgresql/data}
|
- PGDATA=${PGDATA:-/var/lib/postgresql/data}
|
||||||
- DATABASE_URL=${DATABASE_URL:-postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${PGHOST}/${PGDATABASE}}
|
- DATABASE_URL=${DATABASE_URL:-postgresql://plane:plane@plane-db/plane}
|
||||||
# REDIS SETTINGS
|
# REDIS SETTINGS
|
||||||
- REDIS_HOST=${REDIS_HOST:-plane-redis}
|
- REDIS_HOST=${REDIS_HOST:-plane-redis}
|
||||||
- REDIS_PORT=${REDIS_PORT:-6379}
|
- REDIS_PORT=${REDIS_PORT:-6379}
|
||||||
- REDIS_URL=${REDIS_URL:-redis://${REDIS_HOST}:6379/}
|
- REDIS_URL=${REDIS_URL:-redis://plane-redis:6379/}
|
||||||
# Application secret
|
# Application secret
|
||||||
- SECRET_KEY=${SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5}
|
- SECRET_KEY=${SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5}
|
||||||
# DATA STORE SETTINGS
|
# DATA STORE SETTINGS
|
||||||
@ -39,7 +39,7 @@ x-app-env: &app-env
|
|||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
<<: *app-env
|
<<: *app-env
|
||||||
image: ${DOCKERHUB_USER:-makeplane}/plane-frontend:${APP_RELEASE:-latest}
|
image: ${DOCKERHUB_USER:-makeplane}/plane-frontend:${APP_RELEASE:-stable}
|
||||||
pull_policy: ${PULL_POLICY:-always}
|
pull_policy: ${PULL_POLICY:-always}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: /usr/local/bin/start.sh web/server.js web
|
command: /usr/local/bin/start.sh web/server.js web
|
||||||
@ -51,7 +51,7 @@ services:
|
|||||||
|
|
||||||
space:
|
space:
|
||||||
<<: *app-env
|
<<: *app-env
|
||||||
image: ${DOCKERHUB_USER:-makeplane}/plane-space:${APP_RELEASE:-latest}
|
image: ${DOCKERHUB_USER:-makeplane}/plane-space:${APP_RELEASE:-stable}
|
||||||
pull_policy: ${PULL_POLICY:-always}
|
pull_policy: ${PULL_POLICY:-always}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: /usr/local/bin/start.sh space/server.js space
|
command: /usr/local/bin/start.sh space/server.js space
|
||||||
@ -64,7 +64,7 @@ services:
|
|||||||
|
|
||||||
api:
|
api:
|
||||||
<<: *app-env
|
<<: *app-env
|
||||||
image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-latest}
|
image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-stable}
|
||||||
pull_policy: ${PULL_POLICY:-always}
|
pull_policy: ${PULL_POLICY:-always}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: ./bin/takeoff
|
command: ./bin/takeoff
|
||||||
@ -78,7 +78,7 @@ services:
|
|||||||
|
|
||||||
worker:
|
worker:
|
||||||
<<: *app-env
|
<<: *app-env
|
||||||
image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-latest}
|
image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-stable}
|
||||||
pull_policy: ${PULL_POLICY:-always}
|
pull_policy: ${PULL_POLICY:-always}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: ./bin/worker
|
command: ./bin/worker
|
||||||
@ -91,7 +91,7 @@ services:
|
|||||||
|
|
||||||
beat-worker:
|
beat-worker:
|
||||||
<<: *app-env
|
<<: *app-env
|
||||||
image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-latest}
|
image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-stable}
|
||||||
pull_policy: ${PULL_POLICY:-always}
|
pull_policy: ${PULL_POLICY:-always}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: ./bin/beat
|
command: ./bin/beat
|
||||||
@ -104,7 +104,7 @@ services:
|
|||||||
|
|
||||||
migrator:
|
migrator:
|
||||||
<<: *app-env
|
<<: *app-env
|
||||||
image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-latest}
|
image: ${DOCKERHUB_USER:-makeplane}/plane-backend:${APP_RELEASE:-stable}
|
||||||
pull_policy: ${PULL_POLICY:-always}
|
pull_policy: ${PULL_POLICY:-always}
|
||||||
restart: no
|
restart: no
|
||||||
command: >
|
command: >
|
||||||
@ -118,7 +118,7 @@ services:
|
|||||||
|
|
||||||
plane-db:
|
plane-db:
|
||||||
<<: *app-env
|
<<: *app-env
|
||||||
image: postgres:15.2-alpine
|
image: postgres:15.5-alpine
|
||||||
pull_policy: if_not_present
|
pull_policy: if_not_present
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: postgres -c 'max_connections=1000'
|
command: postgres -c 'max_connections=1000'
|
||||||
@ -126,7 +126,7 @@ services:
|
|||||||
- pgdata:/var/lib/postgresql/data
|
- pgdata:/var/lib/postgresql/data
|
||||||
plane-redis:
|
plane-redis:
|
||||||
<<: *app-env
|
<<: *app-env
|
||||||
image: redis:6.2.7-alpine
|
image: redis:7.2.4-alpine
|
||||||
pull_policy: if_not_present
|
pull_policy: if_not_present
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
@ -144,7 +144,7 @@ services:
|
|||||||
# Comment this if you already have a reverse proxy running
|
# Comment this if you already have a reverse proxy running
|
||||||
proxy:
|
proxy:
|
||||||
<<: *app-env
|
<<: *app-env
|
||||||
image: ${DOCKERHUB_USER:-makeplane}/plane-proxy:${APP_RELEASE:-latest}
|
image: ${DOCKERHUB_USER:-makeplane}/plane-proxy:${APP_RELEASE:-stable}
|
||||||
pull_policy: ${PULL_POLICY:-always}
|
pull_policy: ${PULL_POLICY:-always}
|
||||||
ports:
|
ports:
|
||||||
- ${NGINX_PORT}:80
|
- ${NGINX_PORT}:80
|
||||||
|
@ -17,16 +17,16 @@ function print_header() {
|
|||||||
clear
|
clear
|
||||||
|
|
||||||
cat <<"EOF"
|
cat <<"EOF"
|
||||||
---------------------------------------
|
--------------------------------------------
|
||||||
____ _
|
____ _ /////////
|
||||||
| _ \| | __ _ _ __ ___
|
| _ \| | __ _ _ __ ___ /////////
|
||||||
| |_) | |/ _` | '_ \ / _ \
|
| |_) | |/ _` | '_ \ / _ \ ///// /////
|
||||||
| __/| | (_| | | | | __/
|
| __/| | (_| | | | | __/ ///// /////
|
||||||
|_| |_|\__,_|_| |_|\___|
|
|_| |_|\__,_|_| |_|\___| ////
|
||||||
|
////
|
||||||
---------------------------------------
|
--------------------------------------------
|
||||||
Project management tool from the future
|
Project management tool from the future
|
||||||
---------------------------------------
|
--------------------------------------------
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ function buildLocalImage() {
|
|||||||
cd $PLANE_TEMP_CODE_DIR
|
cd $PLANE_TEMP_CODE_DIR
|
||||||
if [ "$BRANCH" == "master" ];
|
if [ "$BRANCH" == "master" ];
|
||||||
then
|
then
|
||||||
export APP_RELEASE=latest
|
export APP_RELEASE=stable
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docker compose -f build.yml build --no-cache >&2
|
docker compose -f build.yml build --no-cache >&2
|
||||||
@ -99,17 +99,17 @@ function download() {
|
|||||||
curl -H 'Cache-Control: no-cache, no-store' -s -o $PLANE_INSTALL_DIR/docker-compose.yaml https://raw.githubusercontent.com/makeplane/plane/$BRANCH/deploy/selfhost/docker-compose.yml?$(date +%s)
|
curl -H 'Cache-Control: no-cache, no-store' -s -o $PLANE_INSTALL_DIR/docker-compose.yaml https://raw.githubusercontent.com/makeplane/plane/$BRANCH/deploy/selfhost/docker-compose.yml?$(date +%s)
|
||||||
curl -H 'Cache-Control: no-cache, no-store' -s -o $PLANE_INSTALL_DIR/variables-upgrade.env https://raw.githubusercontent.com/makeplane/plane/$BRANCH/deploy/selfhost/variables.env?$(date +%s)
|
curl -H 'Cache-Control: no-cache, no-store' -s -o $PLANE_INSTALL_DIR/variables-upgrade.env https://raw.githubusercontent.com/makeplane/plane/$BRANCH/deploy/selfhost/variables.env?$(date +%s)
|
||||||
|
|
||||||
if [ -f "$PLANE_INSTALL_DIR/.env" ];
|
if [ -f "$DOCKER_ENV_PATH" ];
|
||||||
then
|
then
|
||||||
cp $PLANE_INSTALL_DIR/.env $PLANE_INSTALL_DIR/archive/$TS.env
|
cp $DOCKER_ENV_PATH $PLANE_INSTALL_DIR/archive/$TS.env
|
||||||
else
|
else
|
||||||
mv $PLANE_INSTALL_DIR/variables-upgrade.env $PLANE_INSTALL_DIR/.env
|
mv $PLANE_INSTALL_DIR/variables-upgrade.env $DOCKER_ENV_PATH
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$BRANCH" != "master" ];
|
if [ "$BRANCH" != "master" ];
|
||||||
then
|
then
|
||||||
cp $PLANE_INSTALL_DIR/docker-compose.yaml $PLANE_INSTALL_DIR/temp.yaml
|
cp $PLANE_INSTALL_DIR/docker-compose.yaml $PLANE_INSTALL_DIR/temp.yaml
|
||||||
sed -e 's@${APP_RELEASE:-latest}@'"$BRANCH"'@g' \
|
sed -e 's@${APP_RELEASE:-stable}@'"$BRANCH"'@g' \
|
||||||
$PLANE_INSTALL_DIR/temp.yaml > $PLANE_INSTALL_DIR/docker-compose.yaml
|
$PLANE_INSTALL_DIR/temp.yaml > $PLANE_INSTALL_DIR/docker-compose.yaml
|
||||||
|
|
||||||
rm $PLANE_INSTALL_DIR/temp.yaml
|
rm $PLANE_INSTALL_DIR/temp.yaml
|
||||||
@ -131,9 +131,9 @@ function download() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Latest version is now available for you to use"
|
echo "Most recent Stable version is now available for you to use"
|
||||||
echo ""
|
echo ""
|
||||||
echo "In case of Upgrade, your new setting file is availabe as 'variables-upgrade.env'. Please compare and set the required values in '.env 'file."
|
echo "In case of Upgrade, your new setting file is availabe as 'variables-upgrade.env'. Please compare and set the required values in 'plane.env 'file."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -144,7 +144,7 @@ function startServices() {
|
|||||||
if [ -n "$migrator_container_id" ]; then
|
if [ -n "$migrator_container_id" ]; then
|
||||||
local idx=0
|
local idx=0
|
||||||
while docker inspect --format='{{.State.Status}}' $migrator_container_id | grep -q "running"; do
|
while docker inspect --format='{{.State.Status}}' $migrator_container_id | grep -q "running"; do
|
||||||
local message=">>> Waiting for Data Migration to finish"
|
local message=">> Waiting for Data Migration to finish"
|
||||||
local dots=$(printf '%*s' $idx | tr ' ' '.')
|
local dots=$(printf '%*s' $idx | tr ' ' '.')
|
||||||
echo -ne "\r$message$dots"
|
echo -ne "\r$message$dots"
|
||||||
((idx++))
|
((idx++))
|
||||||
@ -152,13 +152,18 @@ function startServices() {
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
printf "\r\033[K"
|
printf "\r\033[K"
|
||||||
|
echo ""
|
||||||
|
echo " Data Migration completed successfully ✅"
|
||||||
|
|
||||||
# if migrator exit status is not 0, show error message and exit
|
# if migrator exit status is not 0, show error message and exit
|
||||||
if [ -n "$migrator_container_id" ]; then
|
if [ -n "$migrator_container_id" ]; then
|
||||||
local migrator_exit_code=$(docker inspect --format='{{.State.ExitCode}}' $migrator_container_id)
|
local migrator_exit_code=$(docker inspect --format='{{.State.ExitCode}}' $migrator_container_id)
|
||||||
if [ $migrator_exit_code -ne 0 ]; then
|
if [ $migrator_exit_code -ne 0 ]; then
|
||||||
echo "Plane Server failed to start ❌"
|
echo "Plane Server failed to start ❌"
|
||||||
stopServices
|
# stopServices
|
||||||
|
echo
|
||||||
|
echo "Please check the logs for the 'migrator' service and resolve the issue(s)."
|
||||||
|
echo "Stop the services by running the command: ./setup.sh stop"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -167,26 +172,35 @@ function startServices() {
|
|||||||
local idx2=0
|
local idx2=0
|
||||||
while ! docker logs $api_container_id 2>&1 | grep -m 1 -i "Application startup complete" | grep -q ".";
|
while ! docker logs $api_container_id 2>&1 | grep -m 1 -i "Application startup complete" | grep -q ".";
|
||||||
do
|
do
|
||||||
local message=">>> Waiting for API Service to Start"
|
local message=">> Waiting for API Service to Start"
|
||||||
local dots=$(printf '%*s' $idx2 | tr ' ' '.')
|
local dots=$(printf '%*s' $idx2 | tr ' ' '.')
|
||||||
echo -ne "\r$message$dots"
|
echo -ne "\r$message$dots"
|
||||||
((idx2++))
|
((idx2++))
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
printf "\r\033[K"
|
printf "\r\033[K"
|
||||||
|
echo " API Service started successfully ✅"
|
||||||
|
source "${DOCKER_ENV_PATH}"
|
||||||
|
echo " Plane Server started successfully ✅"
|
||||||
|
echo ""
|
||||||
|
echo " You can access the application at $WEB_URL"
|
||||||
|
echo ""
|
||||||
|
|
||||||
}
|
}
|
||||||
function stopServices() {
|
function stopServices() {
|
||||||
docker compose -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH down
|
docker compose -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH down
|
||||||
}
|
}
|
||||||
function restartServices() {
|
function restartServices() {
|
||||||
docker compose -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH restart
|
# docker compose -f $DOCKER_FILE_PATH --env-file=$DOCKER_ENV_PATH restart
|
||||||
|
stopServices
|
||||||
|
startServices
|
||||||
}
|
}
|
||||||
function upgrade() {
|
function upgrade() {
|
||||||
echo "***** STOPPING SERVICES ****"
|
echo "***** STOPPING SERVICES ****"
|
||||||
stopServices
|
stopServices
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "***** DOWNLOADING LATEST VERSION ****"
|
echo "***** DOWNLOADING STABLE VERSION ****"
|
||||||
download
|
download
|
||||||
|
|
||||||
echo "***** PLEASE VALIDATE AND START SERVICES ****"
|
echo "***** PLEASE VALIDATE AND START SERVICES ****"
|
||||||
@ -303,15 +317,15 @@ function askForAction() {
|
|||||||
elif [ "$ACTION" == "2" ] || [ "$DEFAULT_ACTION" == "start" ]
|
elif [ "$ACTION" == "2" ] || [ "$DEFAULT_ACTION" == "start" ]
|
||||||
then
|
then
|
||||||
startServices
|
startServices
|
||||||
askForAction
|
# askForAction
|
||||||
elif [ "$ACTION" == "3" ] || [ "$DEFAULT_ACTION" == "stop" ]
|
elif [ "$ACTION" == "3" ] || [ "$DEFAULT_ACTION" == "stop" ]
|
||||||
then
|
then
|
||||||
stopServices
|
stopServices
|
||||||
askForAction
|
# askForAction
|
||||||
elif [ "$ACTION" == "4" ] || [ "$DEFAULT_ACTION" == "restart" ]
|
elif [ "$ACTION" == "4" ] || [ "$DEFAULT_ACTION" == "restart" ]
|
||||||
then
|
then
|
||||||
restartServices
|
restartServices
|
||||||
askForAction
|
# askForAction
|
||||||
elif [ "$ACTION" == "5" ] || [ "$DEFAULT_ACTION" == "upgrade" ]
|
elif [ "$ACTION" == "5" ] || [ "$DEFAULT_ACTION" == "upgrade" ]
|
||||||
then
|
then
|
||||||
upgrade
|
upgrade
|
||||||
@ -343,7 +357,7 @@ fi
|
|||||||
|
|
||||||
if [ "$BRANCH" == "master" ];
|
if [ "$BRANCH" == "master" ];
|
||||||
then
|
then
|
||||||
export APP_RELEASE=latest
|
export APP_RELEASE=stable
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# REMOVE SPECIAL CHARACTERS FROM BRANCH NAME
|
# REMOVE SPECIAL CHARACTERS FROM BRANCH NAME
|
||||||
@ -354,7 +368,21 @@ fi
|
|||||||
mkdir -p $PLANE_INSTALL_DIR/archive
|
mkdir -p $PLANE_INSTALL_DIR/archive
|
||||||
|
|
||||||
DOCKER_FILE_PATH=$PLANE_INSTALL_DIR/docker-compose.yaml
|
DOCKER_FILE_PATH=$PLANE_INSTALL_DIR/docker-compose.yaml
|
||||||
DOCKER_ENV_PATH=$PLANE_INSTALL_DIR/.env
|
DOCKER_ENV_PATH=$PLANE_INSTALL_DIR/plane.env
|
||||||
|
|
||||||
|
# BACKWARD COMPATIBILITY
|
||||||
|
OLD_DOCKER_ENV_PATH=$PLANE_INSTALL_DIR/.env
|
||||||
|
if [ -f "$OLD_DOCKER_ENV_PATH" ];
|
||||||
|
then
|
||||||
|
mv "$OLD_DOCKER_ENV_PATH" "$DOCKER_ENV_PATH"
|
||||||
|
OS_NAME=$(uname)
|
||||||
|
if [ "$OS_NAME" == "Darwin" ];
|
||||||
|
then
|
||||||
|
sed -i '' -e 's@APP_RELEASE=latest@APP_RELEASE=stable@' "$DOCKER_ENV_PATH"
|
||||||
|
else
|
||||||
|
sed -i -e 's@APP_RELEASE=latest@APP_RELEASE=stable@' "$DOCKER_ENV_PATH"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
print_header
|
print_header
|
||||||
askForAction $@
|
askForAction $@
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
APP_RELEASE=latest
|
APP_RELEASE=stable
|
||||||
|
|
||||||
WEB_REPLICAS=1
|
WEB_REPLICAS=1
|
||||||
SPACE_REPLICAS=1
|
SPACE_REPLICAS=1
|
||||||
@ -41,4 +41,4 @@ BUCKET_NAME=uploads
|
|||||||
FILE_SIZE_LIMIT=5242880
|
FILE_SIZE_LIMIT=5242880
|
||||||
|
|
||||||
# Gunicorn Workers
|
# Gunicorn Workers
|
||||||
GUNICORN_WORKERS=2
|
GUNICORN_WORKERS=1
|
||||||
|
@ -39,7 +39,7 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location /${BUCKET_NAME}/ {
|
location /${BUCKET_NAME}/ {
|
||||||
proxy_pass http://plane-minio:9000/uploads/;
|
proxy_pass http://plane-minio:9000/${BUCKET_NAME}/;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"repository": "https://github.com/makeplane/plane.git",
|
"repository": "https://github.com/makeplane/plane.git",
|
||||||
"version": "0.16.0",
|
"version": "0.17.0",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@plane/editor-core",
|
"name": "@plane/editor-core",
|
||||||
"version": "0.16.0",
|
"version": "0.17.0",
|
||||||
"description": "Core Editor that powers Plane",
|
"description": "Core Editor that powers Plane",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./dist/index.mjs",
|
"main": "./dist/index.mjs",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Editor } from "@tiptap/react";
|
import { Editor } from "@tiptap/react";
|
||||||
import { FC, ReactNode } from "react";
|
import { FC, ReactNode } from "react";
|
||||||
|
import { cn } from "src/lib/utils";
|
||||||
|
|
||||||
interface EditorContainerProps {
|
interface EditorContainerProps {
|
||||||
editor: Editor | null;
|
editor: Editor | null;
|
||||||
@ -53,7 +54,7 @@ export const EditorContainer: FC<EditorContainerProps> = (props) => {
|
|||||||
onMouseLeave={() => {
|
onMouseLeave={() => {
|
||||||
hideDragHandle?.();
|
hideDragHandle?.();
|
||||||
}}
|
}}
|
||||||
className={`cursor-text ${editorClassNames}`}
|
className={cn(`cursor-text`, { "active-editor": editor?.isFocused && editor?.isEditable }, editorClassNames)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,7 +22,7 @@ export const Suggestion = (suggestions: IMentionSuggestion[]) => ({
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
popup = tippy("body", {
|
popup = tippy("body", {
|
||||||
getReferenceClientRect: props.clientRect,
|
getReferenceClientRect: props.clientRect,
|
||||||
appendTo: () => document.querySelector("#editor-container"),
|
appendTo: () => document.querySelector(".active-editor") ?? document.querySelector("#editor-container"),
|
||||||
content: reactRenderer.element,
|
content: reactRenderer.element,
|
||||||
showOnCreate: true,
|
showOnCreate: true,
|
||||||
interactive: true,
|
interactive: true,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@plane/document-editor",
|
"name": "@plane/document-editor",
|
||||||
"version": "0.16.0",
|
"version": "0.17.0",
|
||||||
"description": "Package that powers Plane's Pages Editor",
|
"description": "Package that powers Plane's Pages Editor",
|
||||||
"main": "./dist/index.mjs",
|
"main": "./dist/index.mjs",
|
||||||
"module": "./dist/index.mjs",
|
"module": "./dist/index.mjs",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@plane/editor-extensions",
|
"name": "@plane/editor-extensions",
|
||||||
"version": "0.16.0",
|
"version": "0.17.0",
|
||||||
"description": "Package that powers Plane's Editor with extensions",
|
"description": "Package that powers Plane's Editor with extensions",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./dist/index.mjs",
|
"main": "./dist/index.mjs",
|
||||||
|
@ -330,7 +330,7 @@ const renderItems = () => {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
popup = tippy("body", {
|
popup = tippy("body", {
|
||||||
getReferenceClientRect: props.clientRect,
|
getReferenceClientRect: props.clientRect,
|
||||||
appendTo: () => document.querySelector("#editor-container"),
|
appendTo: () => document.querySelector(".active-editor") ?? document.querySelector("#editor-container"),
|
||||||
content: component.element,
|
content: component.element,
|
||||||
showOnCreate: true,
|
showOnCreate: true,
|
||||||
interactive: true,
|
interactive: true,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@plane/lite-text-editor",
|
"name": "@plane/lite-text-editor",
|
||||||
"version": "0.16.0",
|
"version": "0.17.0",
|
||||||
"description": "Package that powers Plane's Comment Editor",
|
"description": "Package that powers Plane's Comment Editor",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./dist/index.mjs",
|
"main": "./dist/index.mjs",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@plane/rich-text-editor",
|
"name": "@plane/rich-text-editor",
|
||||||
"version": "0.16.0",
|
"version": "0.17.0",
|
||||||
"description": "Rich Text Editor that powers Plane",
|
"description": "Rich Text Editor that powers Plane",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./dist/index.mjs",
|
"main": "./dist/index.mjs",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "eslint-config-custom",
|
"name": "eslint-config-custom",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.16.0",
|
"version": "0.17.0",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {},
|
"devDependencies": {},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tailwind-config-custom",
|
"name": "tailwind-config-custom",
|
||||||
"version": "0.16.0",
|
"version": "0.17.0",
|
||||||
"description": "common tailwind configuration across monorepo",
|
"description": "common tailwind configuration across monorepo",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tsconfig",
|
"name": "tsconfig",
|
||||||
"version": "0.16.0",
|
"version": "0.17.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"files": [
|
"files": [
|
||||||
"base.json",
|
"base.json",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@plane/types",
|
"name": "@plane/types",
|
||||||
"version": "0.16.0",
|
"version": "0.17.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./src/index.d.ts"
|
"main": "./src/index.d.ts"
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"name": "@plane/ui",
|
"name": "@plane/ui",
|
||||||
"description": "UI components shared across multiple apps internally",
|
"description": "UI components shared across multiple apps internally",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.16.0",
|
"version": "0.17.0",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.mjs",
|
"module": "./dist/index.mjs",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
|
@ -91,7 +91,7 @@ const CustomSelect = (props: ICustomSelectProps) => {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Listbox.Options className="fixed z-10" onClick={() => closeDropdown()} static>
|
<Listbox.Options className="fixed z-20" onClick={() => closeDropdown()} static>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"my-1 overflow-y-scroll rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 text-xs shadow-custom-shadow-rg focus:outline-none min-w-[12rem] whitespace-nowrap",
|
"my-1 overflow-y-scroll rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 text-xs shadow-custom-shadow-rg focus:outline-none min-w-[12rem] whitespace-nowrap",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "space",
|
"name": "space",
|
||||||
"version": "0.16.0",
|
"version": "0.17.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "turbo run develop",
|
"dev": "turbo run develop",
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
// icons
|
|
||||||
import { Triangle } from "lucide-react";
|
|
||||||
import { IDefaultAnalyticsResponse, TStateGroups } from "@plane/types";
|
|
||||||
// types
|
// types
|
||||||
import { STATE_GROUPS } from "@/constants/state";
|
import { IDefaultAnalyticsResponse, TStateGroups } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
|
import { STATE_GROUPS } from "@/constants/state";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
defaultAnalytics: IDefaultAnalyticsResponse;
|
defaultAnalytics: IDefaultAnalyticsResponse;
|
||||||
@ -16,7 +14,7 @@ export const AnalyticsDemand: React.FC<Props> = ({ defaultAnalytics }) => (
|
|||||||
<h4 className="text-base font-medium text-custom-text-100">Total open tasks</h4>
|
<h4 className="text-base font-medium text-custom-text-100">Total open tasks</h4>
|
||||||
<h3 className="mt-1 text-xl font-semibold">{defaultAnalytics.open_issues}</h3>
|
<h3 className="mt-1 text-xl font-semibold">{defaultAnalytics.open_issues}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6 pb-2">
|
||||||
{defaultAnalytics?.open_issues_classified.map((group) => {
|
{defaultAnalytics?.open_issues_classified.map((group) => {
|
||||||
const percentage = ((group.state_count / defaultAnalytics.total_issues) * 100).toFixed(0);
|
const percentage = ((group.state_count / defaultAnalytics.total_issues) * 100).toFixed(0);
|
||||||
|
|
||||||
@ -50,14 +48,5 @@ export const AnalyticsDemand: React.FC<Props> = ({ defaultAnalytics }) => (
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="!mt-6 flex w-min items-center gap-2 whitespace-nowrap rounded-md border border-custom-border-200 bg-custom-background-80 p-2 text-xs">
|
|
||||||
<p className="flex items-center gap-1 text-custom-text-200">
|
|
||||||
<Triangle className="h-4 w-4" />
|
|
||||||
<span>Estimate Demand:</span>
|
|
||||||
</p>
|
|
||||||
<p className="font-medium">
|
|
||||||
{defaultAnalytics.open_estimate_sum}/{defaultAnalytics.total_estimate_sum}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -69,7 +69,9 @@ export const CommandPaletteHelpActions: React.FC<Props> = (props) => {
|
|||||||
<Command.Item
|
<Command.Item
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
closePalette();
|
closePalette();
|
||||||
(window as any)?.$crisp.push(["do", "chat:open"]);
|
if (window) {
|
||||||
|
window.$crisp.push(["do", "chat:show"]);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
className="focus:outline-none"
|
className="focus:outline-none"
|
||||||
>
|
>
|
||||||
|
@ -40,7 +40,7 @@ const issueService = new IssueService();
|
|||||||
|
|
||||||
export const CommandModal: React.FC = observer(() => {
|
export const CommandModal: React.FC = observer(() => {
|
||||||
// hooks
|
// hooks
|
||||||
const { getProjectById } = useProject();
|
const { getProjectById, workspaceProjectIds } = useProject();
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
// states
|
// states
|
||||||
const [placeholder, setPlaceholder] = useState("Type a command or search...");
|
const [placeholder, setPlaceholder] = useState("Type a command or search...");
|
||||||
@ -282,22 +282,24 @@ export const CommandModal: React.FC = observer(() => {
|
|||||||
setSearchTerm={(newSearchTerm) => setSearchTerm(newSearchTerm)}
|
setSearchTerm={(newSearchTerm) => setSearchTerm(newSearchTerm)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Command.Group heading="Issue">
|
{workspaceSlug && workspaceProjectIds && workspaceProjectIds.length > 0 && (
|
||||||
<Command.Item
|
<Command.Group heading="Issue">
|
||||||
onSelect={() => {
|
<Command.Item
|
||||||
closePalette();
|
onSelect={() => {
|
||||||
setTrackElement("Command Palette");
|
closePalette();
|
||||||
toggleCreateIssueModal(true);
|
setTrackElement("Command Palette");
|
||||||
}}
|
toggleCreateIssueModal(true);
|
||||||
className="focus:bg-custom-background-80"
|
}}
|
||||||
>
|
className="focus:bg-custom-background-80"
|
||||||
<div className="flex items-center gap-2 text-custom-text-200">
|
>
|
||||||
<LayersIcon className="h-3.5 w-3.5" />
|
<div className="flex items-center gap-2 text-custom-text-200">
|
||||||
Create new issue
|
<LayersIcon className="h-3.5 w-3.5" />
|
||||||
</div>
|
Create new issue
|
||||||
<kbd>C</kbd>
|
</div>
|
||||||
</Command.Item>
|
<kbd>C</kbd>
|
||||||
</Command.Group>
|
</Command.Item>
|
||||||
|
</Command.Group>
|
||||||
|
)}
|
||||||
|
|
||||||
{workspaceSlug && (
|
{workspaceSlug && (
|
||||||
<Command.Group heading="Project">
|
<Command.Group heading="Project">
|
||||||
|
@ -36,18 +36,18 @@ export const ShortcutsModal: FC<Props> = (props) => {
|
|||||||
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
|
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
<div className="fixed inset-0 z-20 h-full w-full overflow-y-auto">
|
<div className="fixed inset-0 z-20 overflow-y-auto">
|
||||||
<Transition.Child
|
<div className="my-10 flex items-center justify-center p-4 text-center sm:p-0 md:my-20">
|
||||||
as={Fragment}
|
<Transition.Child
|
||||||
enter="ease-out duration-300"
|
as={Fragment}
|
||||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
enter="ease-out duration-300"
|
||||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
leave="ease-in duration-200"
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leave="ease-in duration-200"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
>
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
<Dialog.Panel className="h-full w-full">
|
>
|
||||||
<div className="my-10 flex items-center justify-center p-4 text-center sm:p-0 md:my-20">
|
<Dialog.Panel className="relative flex h-full items-center justify-center">
|
||||||
<div className="flex h-[61vh] w-full flex-col space-y-4 overflow-hidden rounded-lg bg-custom-background-100 p-5 shadow-custom-shadow-md transition-all sm:w-[28rem]">
|
<div className="flex h-[61vh] w-full flex-col space-y-4 overflow-hidden rounded-lg bg-custom-background-100 p-5 shadow-custom-shadow-md transition-all sm:w-[28rem]">
|
||||||
<Dialog.Title as="h3" className="flex justify-between">
|
<Dialog.Title as="h3" className="flex justify-between">
|
||||||
<span className="text-lg font-medium">Keyboard shortcuts</span>
|
<span className="text-lg font-medium">Keyboard shortcuts</span>
|
||||||
@ -71,9 +71,9 @@ export const ShortcutsModal: FC<Props> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<ShortcutCommandsList searchQuery={query} />
|
<ShortcutCommandsList searchQuery={query} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Dialog.Panel>
|
||||||
</Dialog.Panel>
|
</Transition.Child>
|
||||||
</Transition.Child>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Transition.Root>
|
</Transition.Root>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { LayoutGrid, Zap } from "lucide-react";
|
import { Home, Zap } from "lucide-react";
|
||||||
// images
|
// images
|
||||||
import githubBlackImage from "/public/logos/github-black.png";
|
import githubBlackImage from "/public/logos/github-black.png";
|
||||||
import githubWhiteImage from "/public/logos/github-white.png";
|
import githubWhiteImage from "/public/logos/github-white.png";
|
||||||
@ -25,9 +25,7 @@ export const WorkspaceDashboardHeader = () => {
|
|||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<Breadcrumbs.BreadcrumbItem
|
<Breadcrumbs.BreadcrumbItem
|
||||||
type="text"
|
type="text"
|
||||||
link={
|
link={<BreadcrumbLink label="Home" icon={<Home className="h-4 w-4 text-custom-text-300" />} />}
|
||||||
<BreadcrumbLink label="Dashboard" icon={<LayoutGrid className="h-4 w-4 text-custom-text-300" />} />
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import omit from "lodash/omit";
|
import omit from "lodash/omit";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// hooks
|
// icons
|
||||||
import { Copy, Pencil, Trash2 } from "lucide-react";
|
import { Pencil, Trash2 } from "lucide-react";
|
||||||
|
// types
|
||||||
import { TIssue } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
|
// ui
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { CustomMenu } from "@plane/ui";
|
||||||
|
// components
|
||||||
import { CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
import { CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
|
||||||
|
// constant
|
||||||
import { EIssuesStoreType } from "@/constants/issue";
|
import { EIssuesStoreType } from "@/constants/issue";
|
||||||
import { EUserProjectRoles } from "@/constants/project";
|
import { EUserProjectRoles } from "@/constants/project";
|
||||||
|
// hooks
|
||||||
import { useEventTracker, useIssues, useUser } from "@/hooks/store";
|
import { useEventTracker, useIssues, useUser } from "@/hooks/store";
|
||||||
// ui
|
|
||||||
// components
|
|
||||||
// helpers
|
|
||||||
// types
|
// types
|
||||||
import { IQuickActionProps } from "../list/list-view-types";
|
import { IQuickActionProps } from "../list/list-view-types";
|
||||||
// constant
|
|
||||||
|
|
||||||
export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((props) => {
|
export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((props) => {
|
||||||
const { issue, handleDelete, handleUpdate, customActionButton, portalElement, readOnly = false } = props;
|
const { issue, handleDelete, handleUpdate, customActionButton, portalElement, readOnly = false } = props;
|
||||||
@ -89,19 +90,6 @@ export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
|
|||||||
</div>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
)}
|
)}
|
||||||
{isEditingAllowed && (
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
onClick={() => {
|
|
||||||
setTrackElement(activeLayout);
|
|
||||||
setCreateUpdateIssueModal(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Copy className="h-3 w-3" />
|
|
||||||
Make a copy
|
|
||||||
</div>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
)}
|
|
||||||
{isDeletingAllowed && (
|
{isDeletingAllowed && (
|
||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -84,7 +84,7 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
|||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
...changesMade,
|
...changesMade,
|
||||||
name: changesMade?.name && changesMade?.name?.trim() === "" ? changesMade.name?.trim() : "Untitled",
|
name: changesMade?.name && changesMade?.name?.trim() !== "" ? changesMade.name?.trim() : "Untitled",
|
||||||
};
|
};
|
||||||
|
|
||||||
await issueDraftService
|
await issueDraftService
|
||||||
|
@ -26,6 +26,7 @@ import { renderFormattedPayloadDate, getDate } from "@/helpers/date-time.helper"
|
|||||||
import { getChangedIssuefields } from "@/helpers/issue.helper";
|
import { getChangedIssuefields } from "@/helpers/issue.helper";
|
||||||
import { shouldRenderProject } from "@/helpers/project.helper";
|
import { shouldRenderProject } from "@/helpers/project.helper";
|
||||||
import { useApplication, useEstimate, useIssueDetail, useMention, useProject, useWorkspace } from "@/hooks/store";
|
import { useApplication, useEstimate, useIssueDetail, useMention, useProject, useWorkspace } from "@/hooks/store";
|
||||||
|
import { useProjectIssueProperties } from "@/hooks/use-project-issue-properties";
|
||||||
// services
|
// services
|
||||||
import { AIService } from "@/services/ai.service";
|
import { AIService } from "@/services/ai.service";
|
||||||
import { FileService } from "@/services/file.service";
|
import { FileService } from "@/services/file.service";
|
||||||
@ -121,6 +122,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
config: { envConfig },
|
config: { envConfig },
|
||||||
|
router: { projectId: routeProjectId },
|
||||||
} = useApplication();
|
} = useApplication();
|
||||||
const { getProjectById } = useProject();
|
const { getProjectById } = useProject();
|
||||||
const { areEstimatesEnabledForProject } = useEstimate();
|
const { areEstimatesEnabledForProject } = useEstimate();
|
||||||
@ -128,6 +130,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||||||
const {
|
const {
|
||||||
issue: { getIssueById },
|
issue: { getIssueById },
|
||||||
} = useIssueDetail();
|
} = useIssueDetail();
|
||||||
|
const { fetchCycles } = useProjectIssueProperties();
|
||||||
// form info
|
// form info
|
||||||
const {
|
const {
|
||||||
formState: { errors, isDirty, isSubmitting, dirtyFields },
|
formState: { errors, isDirty, isSubmitting, dirtyFields },
|
||||||
@ -160,6 +163,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||||||
parent_id: formData.parent_id,
|
parent_id: formData.parent_id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (projectId && routeProjectId !== projectId) fetchCycles(workspaceSlug, projectId);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [projectId]);
|
}, [projectId]);
|
||||||
|
|
||||||
|
@ -10,11 +10,11 @@ import {
|
|||||||
ContrastIcon,
|
ContrastIcon,
|
||||||
FileText,
|
FileText,
|
||||||
LayersIcon,
|
LayersIcon,
|
||||||
LayoutGrid,
|
|
||||||
PenSquare,
|
PenSquare,
|
||||||
Search,
|
Search,
|
||||||
Settings,
|
Settings,
|
||||||
Bell,
|
Bell,
|
||||||
|
Home,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { IWorkspace } from "@plane/types";
|
import { IWorkspace } from "@plane/types";
|
||||||
import { Avatar, DiceIcon, PhotoFilterIcon } from "@plane/ui";
|
import { Avatar, DiceIcon, PhotoFilterIcon } from "@plane/ui";
|
||||||
@ -26,8 +26,8 @@ import projectEmoji from "public/emoji/project-emoji.svg";
|
|||||||
|
|
||||||
const workspaceLinks = [
|
const workspaceLinks = [
|
||||||
{
|
{
|
||||||
Icon: LayoutGrid,
|
Icon: Home,
|
||||||
name: "Dashboard",
|
name: "Home",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Icon: BarChart2,
|
Icon: BarChart2,
|
||||||
|
@ -226,7 +226,7 @@ export const CreateProjectForm: FC<Props> = observer((props) => {
|
|||||||
control={control}
|
control={control}
|
||||||
name="name"
|
name="name"
|
||||||
rules={{
|
rules={{
|
||||||
required: "Title is required",
|
required: "Name is required",
|
||||||
maxLength: {
|
maxLength: {
|
||||||
value: 255,
|
value: 255,
|
||||||
message: "Title should be less than 255 characters",
|
message: "Title should be less than 255 characters",
|
||||||
@ -240,7 +240,7 @@ export const CreateProjectForm: FC<Props> = observer((props) => {
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={handleNameChange(onChange)}
|
onChange={handleNameChange(onChange)}
|
||||||
hasError={Boolean(errors.name)}
|
hasError={Boolean(errors.name)}
|
||||||
placeholder="Project title"
|
placeholder="Project name"
|
||||||
className="w-full focus:border-blue-400"
|
className="w-full focus:border-blue-400"
|
||||||
tabIndex={1}
|
tabIndex={1}
|
||||||
/>
|
/>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { FC, useEffect, useState } from "react";
|
import { FC, useEffect, useState } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
// icons
|
// icons
|
||||||
import { Lock } from "lucide-react";
|
import { Info, Lock } from "lucide-react";
|
||||||
import { IProject, IWorkspace } from "@plane/types";
|
import { IProject, IWorkspace } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import {
|
import {
|
||||||
@ -13,6 +13,7 @@ import {
|
|||||||
setToast,
|
setToast,
|
||||||
CustomEmojiIconPicker,
|
CustomEmojiIconPicker,
|
||||||
EmojiIconPickerTypes,
|
EmojiIconPickerTypes,
|
||||||
|
Tooltip,
|
||||||
} from "@plane/ui";
|
} from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ImagePickerPopover } from "@/components/core";
|
import { ImagePickerPopover } from "@/components/core";
|
||||||
@ -24,6 +25,7 @@ import { renderFormattedDate } from "@/helpers/date-time.helper";
|
|||||||
// hooks
|
// hooks
|
||||||
import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper";
|
import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper";
|
||||||
import { useEventTracker, useProject } from "@/hooks/store";
|
import { useEventTracker, useProject } from "@/hooks/store";
|
||||||
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// services
|
// services
|
||||||
import { ProjectService } from "@/services/project";
|
import { ProjectService } from "@/services/project";
|
||||||
// types
|
// types
|
||||||
@ -42,6 +44,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
// store hooks
|
// store hooks
|
||||||
const { captureProjectEvent } = useEventTracker();
|
const { captureProjectEvent } = useEventTracker();
|
||||||
const { updateProject } = useProject();
|
const { updateProject } = useProject();
|
||||||
|
const { isMobile } = usePlatformOS();
|
||||||
// form info
|
// form info
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -229,6 +232,9 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<span className="text-xs text-red-500">
|
||||||
|
<>{errors?.name?.message}</>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<h4 className="text-sm">Description</h4>
|
<h4 className="text-sm">Description</h4>
|
||||||
@ -249,39 +255,54 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full items-center justify-between gap-10">
|
<div className="flex w-full justify-between gap-10">
|
||||||
<div className="flex w-1/2 flex-col gap-1">
|
<div className="flex w-1/2 flex-col gap-1">
|
||||||
<h4 className="text-sm">Identifier</h4>
|
<h4 className="text-sm">Project ID</h4>
|
||||||
<Controller
|
<div className="relative">
|
||||||
control={control}
|
<Controller
|
||||||
name="identifier"
|
control={control}
|
||||||
rules={{
|
name="identifier"
|
||||||
required: "Identifier is required",
|
rules={{
|
||||||
validate: (value) => /^[A-Z0-9]+$/.test(value.toUpperCase()) || "Identifier must be in uppercase.",
|
required: "Project ID is required",
|
||||||
minLength: {
|
validate: (value) =>
|
||||||
value: 1,
|
/^[ÇŞĞIİÖÜA-Z0-9]+$/.test(value.toUpperCase()) ||
|
||||||
message: "Identifier must at least be of 1 character",
|
"Only Alphanumeric & Non-latin characters are allowed.",
|
||||||
},
|
minLength: {
|
||||||
maxLength: {
|
value: 1,
|
||||||
value: 12,
|
message: "Project ID must at least be of 1 character",
|
||||||
message: "Identifier must at most be of 5 characters",
|
},
|
||||||
},
|
maxLength: {
|
||||||
}}
|
value: 5,
|
||||||
render={({ field: { value, ref } }) => (
|
message: "Project ID must at most be of 5 characters",
|
||||||
<Input
|
},
|
||||||
id="identifier"
|
}}
|
||||||
name="identifier"
|
render={({ field: { value, ref } }) => (
|
||||||
type="text"
|
<Input
|
||||||
value={value}
|
id="identifier"
|
||||||
onChange={handleIdentifierChange}
|
name="identifier"
|
||||||
ref={ref}
|
type="text"
|
||||||
hasError={Boolean(errors.identifier)}
|
value={value}
|
||||||
placeholder="Enter identifier"
|
onChange={handleIdentifierChange}
|
||||||
className="w-full font-medium"
|
ref={ref}
|
||||||
disabled={!isAdmin}
|
hasError={Boolean(errors.identifier)}
|
||||||
/>
|
placeholder="Enter Project ID"
|
||||||
)}
|
className="w-full font-medium"
|
||||||
/>
|
disabled={!isAdmin}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
isMobile={isMobile}
|
||||||
|
tooltipContent="Helps you identify issues in the project uniquely, (e.g. APP-123). Max 5 characters."
|
||||||
|
className="text-sm"
|
||||||
|
position="right-top"
|
||||||
|
>
|
||||||
|
<Info className="absolute right-2 top-2.5 h-4 w-4 text-custom-text-400" />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-red-500">
|
||||||
|
<>{errors?.identifier?.message}</>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-1/2 flex-col gap-1">
|
<div className="flex w-1/2 flex-col gap-1">
|
||||||
<h4 className="text-sm">Network</h4>
|
<h4 className="text-sm">Network</h4>
|
||||||
|
89
web/hooks/use-project-issue-properties.ts
Normal file
89
web/hooks/use-project-issue-properties.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { useCycle, useEstimate, useLabel, useMember, useModule, useProjectState } from "./store";
|
||||||
|
|
||||||
|
export const useProjectIssueProperties = () => {
|
||||||
|
const { fetchProjectStates } = useProjectState();
|
||||||
|
const {
|
||||||
|
project: { fetchProjectMembers },
|
||||||
|
} = useMember();
|
||||||
|
const { fetchProjectLabels } = useLabel();
|
||||||
|
const { fetchAllCycles: fetchProjectAllCycles } = useCycle();
|
||||||
|
const { fetchModules: fetchProjectAllModules } = useModule();
|
||||||
|
const { fetchProjectEstimates } = useEstimate();
|
||||||
|
|
||||||
|
// fetching project states
|
||||||
|
const fetchStates = async (
|
||||||
|
workspaceSlug: string | string[] | undefined,
|
||||||
|
projectId: string | string[] | undefined
|
||||||
|
) => {
|
||||||
|
if (workspaceSlug && projectId) {
|
||||||
|
await fetchProjectStates(workspaceSlug.toString(), projectId.toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// fetching project members
|
||||||
|
const fetchMembers = async (
|
||||||
|
workspaceSlug: string | string[] | undefined,
|
||||||
|
projectId: string | string[] | undefined
|
||||||
|
) => {
|
||||||
|
if (workspaceSlug && projectId) {
|
||||||
|
await fetchProjectMembers(workspaceSlug.toString(), projectId.toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// fetching project labels
|
||||||
|
const fetchLabels = async (
|
||||||
|
workspaceSlug: string | string[] | undefined,
|
||||||
|
projectId: string | string[] | undefined
|
||||||
|
) => {
|
||||||
|
if (workspaceSlug && projectId) {
|
||||||
|
await fetchProjectLabels(workspaceSlug.toString(), projectId.toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// fetching project cycles
|
||||||
|
const fetchCycles = async (
|
||||||
|
workspaceSlug: string | string[] | undefined,
|
||||||
|
projectId: string | string[] | undefined
|
||||||
|
) => {
|
||||||
|
if (workspaceSlug && projectId) {
|
||||||
|
await fetchProjectAllCycles(workspaceSlug.toString(), projectId.toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// fetching project modules
|
||||||
|
const fetchModules = async (
|
||||||
|
workspaceSlug: string | string[] | undefined,
|
||||||
|
projectId: string | string[] | undefined
|
||||||
|
) => {
|
||||||
|
if (workspaceSlug && projectId) {
|
||||||
|
await fetchProjectAllModules(workspaceSlug.toString(), projectId.toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// fetching project estimates
|
||||||
|
const fetchEstimates = async (
|
||||||
|
workspaceSlug: string | string[] | undefined,
|
||||||
|
projectId: string | string[] | undefined
|
||||||
|
) => {
|
||||||
|
if (workspaceSlug && projectId) {
|
||||||
|
await fetchProjectEstimates(workspaceSlug.toString(), projectId.toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchAll = async (workspaceSlug: string | string[] | undefined, projectId: string | string[] | undefined) => {
|
||||||
|
if (workspaceSlug && projectId) {
|
||||||
|
await fetchStates(workspaceSlug, projectId);
|
||||||
|
await fetchMembers(workspaceSlug, projectId);
|
||||||
|
await fetchLabels(workspaceSlug, projectId);
|
||||||
|
await fetchCycles(workspaceSlug, projectId);
|
||||||
|
await fetchModules(workspaceSlug, projectId);
|
||||||
|
await fetchEstimates(workspaceSlug, projectId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
fetchAll,
|
||||||
|
fetchStates,
|
||||||
|
fetchMembers,
|
||||||
|
fetchLabels,
|
||||||
|
fetchCycles,
|
||||||
|
fetchModules,
|
||||||
|
fetchEstimates,
|
||||||
|
};
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "web",
|
"name": "web",
|
||||||
"version": "0.16.0",
|
"version": "0.17.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "turbo run develop",
|
"dev": "turbo run develop",
|
||||||
|
@ -14,7 +14,7 @@ import { NextPageWithLayout } from "@/lib/types";
|
|||||||
const WorkspacePage: NextPageWithLayout = observer(() => {
|
const WorkspacePage: NextPageWithLayout = observer(() => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
// derived values
|
// derived values
|
||||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Dashboard` : undefined;
|
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Home` : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -220,7 +220,7 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => {
|
|||||||
description="You can see here if someone invites you to a workspace."
|
description="You can see here if someone invites you to a workspace."
|
||||||
image={emptyInvitation}
|
image={emptyInvitation}
|
||||||
primaryButton={{
|
primaryButton={{
|
||||||
text: "Back to dashboard",
|
text: "Back to home",
|
||||||
onClick: () => router.push("/"),
|
onClick: () => router.push("/"),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -81,7 +81,7 @@ const WorkspaceInvitationPage: NextPageWithLayout = observer(() => {
|
|||||||
title={`You are already a member of ${invitationDetail.workspace.name}`}
|
title={`You are already a member of ${invitationDetail.workspace.name}`}
|
||||||
description="Your workspace is where you'll create projects, collaborate on your issues, and organize different streams of work in your Plane account."
|
description="Your workspace is where you'll create projects, collaborate on your issues, and organize different streams of work in your Plane account."
|
||||||
>
|
>
|
||||||
<EmptySpaceItem Icon={Boxes} title="Continue to Dashboard" href="/" />
|
<EmptySpaceItem Icon={Boxes} title="Continue to home" href="/" />
|
||||||
</EmptySpace>
|
</EmptySpace>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@ -105,7 +105,7 @@ const WorkspaceInvitationPage: NextPageWithLayout = observer(() => {
|
|||||||
{!currentUser ? (
|
{!currentUser ? (
|
||||||
<EmptySpaceItem Icon={User2} title="Sign in to continue" href="/" />
|
<EmptySpaceItem Icon={User2} title="Sign in to continue" href="/" />
|
||||||
) : (
|
) : (
|
||||||
<EmptySpaceItem Icon={Boxes} title="Continue to Dashboard" href="/" />
|
<EmptySpaceItem Icon={Boxes} title="Continue to home" href="/" />
|
||||||
)}
|
)}
|
||||||
<EmptySpaceItem Icon={Star} title="Star us on GitHub" href="https://github.com/makeplane" />
|
<EmptySpaceItem Icon={Star} title="Star us on GitHub" href="https://github.com/makeplane" />
|
||||||
<EmptySpaceItem
|
<EmptySpaceItem
|
||||||
|
@ -2733,7 +2733,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react@*", "@types/react@^18.2.42":
|
"@types/react@*", "@types/react@18.2.42", "@types/react@^18.2.42":
|
||||||
version "18.2.42"
|
version "18.2.42"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.42.tgz#6f6b11a904f6d96dda3c2920328a97011a00aba7"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.42.tgz#6f6b11a904f6d96dda3c2920328a97011a00aba7"
|
||||||
integrity sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA==
|
integrity sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA==
|
||||||
|
Loading…
Reference in New Issue
Block a user