From c7f10909144014d88adb93498b20203e3a4bf739 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Wed, 3 May 2023 13:36:55 +0530 Subject: [PATCH 01/87] fix: docker setup (#987) * removing dependencies from .env * dev: Passing the arguments from docker compose to DockerWeb in nextjs to define base environment variables * dev: removed env from docker-compose and taking the env from shell * dev: Updated docker file and used console in signin to test the env from docker * dev: Docker setting env variables via shell * removed env variables and args * Update Dockerfile.web * Update Dockerfile.web * Update signin.tsx * . * . * dev: Added BASE_URL from docker * dev: Updated docker config * dev: scripts for replacing variable during runtime * dev: entrypoint script * dev: update replace env script and update docker entrypoint command for frontend * dev: update replace env script to not update process.env * dev: update docker file to add missing variables as well * fix: updated docker compose yml and web * dev: create start script to run docker and update script for replacing variables * dev: update setup script and env example script to create variables in the root of the project * . * dev: update docker compose hub * dev: update docker compose hub command * dev: update docker compose yml and env example * dev: update docker compose hub * dev: single docker --------- Co-authored-by: Narayana Co-authored-by: gurusainath --- apps/app/.env.example => .env.example | 10 +++- .github/workflows/push-image-backend.yml | 45 +++++++++++++----- .github/workflows/push-image-frontend.yml | 39 +++++++++++---- Dockerfile | 20 +++++++- apiserver/.env.example | 28 ----------- apps/app/Dockerfile.web | 27 +++++++++-- apps/app/next.config.js | 6 ++- apps/app/package.json | 1 + apps/app/pages/signin.tsx | 2 - docker-compose-hub.yml | 58 +++++++++++++++++++---- docker-compose.yml | 57 ++++++++++++++++++---- nginx/supervisor.conf | 12 ++++- replace-env-vars.sh | 17 +++++++ setup.sh | 8 ++-- start.sh | 9 ++++ turbo.json | 2 + yarn.lock | 5 ++ 17 files changed, 266 insertions(+), 80 deletions(-) rename apps/app/.env.example => .env.example (66%) delete mode 100644 apiserver/.env.example create mode 100644 replace-env-vars.sh create mode 100644 start.sh diff --git a/apps/app/.env.example b/.env.example similarity index 66% rename from apps/app/.env.example rename to .env.example index 9e41ba88d..118a94883 100644 --- a/apps/app/.env.example +++ b/.env.example @@ -1,5 +1,4 @@ # Replace with your instance Public IP -# NEXT_PUBLIC_API_BASE_URL = "http://localhost" NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS= NEXT_PUBLIC_GOOGLE_CLIENTID="" NEXT_PUBLIC_GITHUB_APP_NAME="" @@ -10,3 +9,12 @@ NEXT_PUBLIC_ENABLE_SENTRY=0 NEXT_PUBLIC_ENABLE_SESSION_RECORDER=0 NEXT_PUBLIC_TRACK_EVENTS=0 NEXT_PUBLIC_SLACK_CLIENT_ID="" +EMAIL_HOST="" +EMAIL_HOST_USER="" +EMAIL_HOST_PASSWORD="" +AWS_REGION="" +AWS_ACCESS_KEY_ID="" +AWS_SECRET_ACCESS_KEY="" +AWS_S3_BUCKET_NAME="" +OPENAI_API_KEY="" +GPT_ENGINE="" \ No newline at end of file diff --git a/.github/workflows/push-image-backend.yml b/.github/workflows/push-image-backend.yml index abb833922..95d93f813 100644 --- a/.github/workflows/push-image-backend.yml +++ b/.github/workflows/push-image-backend.yml @@ -1,4 +1,4 @@ -name: Build Api Server Docker Image +name: Build and Push Backend Docker Image on: push: @@ -10,11 +10,8 @@ on: jobs: build_push_backend: - name: Build Api Server Docker Image + name: Build and Push Api Server Docker Image runs-on: ubuntu-20.04 - permissions: - contents: read - packages: write steps: - name: Check out the repo @@ -28,20 +25,33 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2.5.0 - - name: Login to Github Container Registry + - name: Login to GitHub Container Registry uses: docker/login-action@v2.1.0 with: registry: "ghcr.io" username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker - id: meta + - name: Login to Docker Hub + uses: docker/login-action@v2.1.0 + with: + registry: "registry.hub.docker.com" + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker (Docker Hub) + id: ghmeta + uses: docker/metadata-action@v4.3.0 + with: + images: makeplane/plane-backend + + - name: Extract metadata (tags, labels) for Docker (Github) + id: dkrmeta uses: docker/metadata-action@v4.3.0 with: images: ghcr.io/${{ github.repository }}-backend - - name: Build Api Server + - name: Build and Push to GitHub Container Registry uses: docker/build-push-action@v4.0.0 with: context: ./apiserver @@ -50,5 +60,18 @@ jobs: push: true cache-from: type=gha cache-to: type=gha - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + tags: ${{ steps.ghmeta.outputs.tags }} + labels: ${{ steps.ghmeta.outputs.labels }} + + - name: Build and Push to Docker Hub + uses: docker/build-push-action@v4.0.0 + with: + context: ./apiserver + file: ./apiserver/Dockerfile.api + platforms: linux/arm64,linux/amd64 + push: true + cache-from: type=gha + cache-to: type=gha + tags: ${{ steps.dkrmeta.outputs.tags }} + labels: ${{ steps.dkrmeta.outputs.labels }} + diff --git a/.github/workflows/push-image-frontend.yml b/.github/workflows/push-image-frontend.yml index c6a3bf1b8..cbd742511 100644 --- a/.github/workflows/push-image-frontend.yml +++ b/.github/workflows/push-image-frontend.yml @@ -1,4 +1,4 @@ -name: Build Frontend Docker Image +name: Build and Push Frontend Docker Image on: push: @@ -12,9 +12,6 @@ jobs: build_push_frontend: name: Build Frontend Docker Image runs-on: ubuntu-20.04 - permissions: - contents: read - packages: write steps: - name: Check out the repo @@ -35,13 +32,26 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker + - name: Login to Docker Hub + uses: docker/login-action@v2.1.0 + with: + registry: "registry.hub.docker.com" + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker (Docker Hub) + id: ghmeta + uses: docker/metadata-action@v4.3.0 + with: + images: makeplane/plane-frontend + + - name: Extract metadata (tags, labels) for Docker (Github) id: meta uses: docker/metadata-action@v4.3.0 with: images: ghcr.io/${{ github.repository }}-frontend - - name: Build Frontend Server + - name: Build and Push to GitHub Container Registry uses: docker/build-push-action@v4.0.0 with: context: . @@ -50,5 +60,18 @@ jobs: push: true cache-from: type=gha cache-to: type=gha - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + tags: ${{ steps.ghmeta.outputs.tags }} + labels: ${{ steps.ghmeta.outputs.labels }} + + - name: Build and Push to Docker Container Registry + uses: docker/build-push-action@v4.0.0 + with: + context: . + file: ./apps/app/Dockerfile.web + platforms: linux/arm64,linux/amd64 + push: true + cache-from: type=gha + cache-to: type=gha + tags: ${{ steps.dkrmeta.outputs.tags }} + labels: ${{ steps.dkrmeta.outputs.labels }} + diff --git a/Dockerfile b/Dockerfile index 094d628e3..9d0ff559d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,7 @@ RUN apk add --no-cache libc6-compat RUN apk update # Set working directory WORKDIR /app +ENV NEXT_PUBLIC_API_BASE_URL=http://NEXT_PUBLIC_API_BASE_URL_PLACEHOLDER RUN yarn global add turbo COPY . . @@ -16,7 +17,7 @@ FROM node:18-alpine AS installer RUN apk add --no-cache libc6-compat RUN apk update WORKDIR /app - +ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 # First install the dependencies (as they change less often) COPY .gitignore .gitignore COPY --from=builder /app/out/json/ . @@ -26,9 +27,16 @@ RUN yarn install # Build the project COPY --from=builder /app/out/full/ . COPY turbo.json turbo.json +COPY replace-env-vars.sh /usr/local/bin/ +USER root +RUN chmod +x /usr/local/bin/replace-env-vars.sh RUN yarn turbo run build --filter=app +ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \ + BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL + +RUN /usr/local/bin/replace-env-vars.sh http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER ${NEXT_PUBLIC_WEBAPP_URL} FROM python:3.11.1-alpine3.17 AS backend @@ -108,6 +116,16 @@ COPY nginx/nginx-single-docker-image.conf /etc/nginx/http.d/default.conf COPY nginx/supervisor.conf /code/supervisor.conf +ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 +ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \ + BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL + +USER root +COPY replace-env-vars.sh /usr/local/bin/ +COPY start.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/replace-env-vars.sh +RUN chmod +x /usr/local/bin/start.sh + CMD ["supervisord","-c","/code/supervisor.conf"] diff --git a/apiserver/.env.example b/apiserver/.env.example deleted file mode 100644 index 8a7c76ffa..000000000 --- a/apiserver/.env.example +++ /dev/null @@ -1,28 +0,0 @@ -DJANGO_SETTINGS_MODULE="plane.settings.production" -# Database -DATABASE_URL=postgres://plane:xyzzyspoon@db:5432/plane -# Cache -REDIS_URL=redis://redis:6379/ -# SMTP -EMAIL_HOST="" -EMAIL_HOST_USER="" -EMAIL_HOST_PASSWORD="" -EMAIL_PORT="587" -EMAIL_USE_TLS="1" -EMAIL_FROM="Team Plane " -# AWS -AWS_REGION="" -AWS_ACCESS_KEY_ID="" -AWS_SECRET_ACCESS_KEY="" -AWS_S3_BUCKET_NAME="" -AWS_S3_ENDPOINT_URL="" -# FE -WEB_URL="localhost/" -# OAUTH -GITHUB_CLIENT_SECRET="" -# Flags -DISABLE_COLLECTSTATIC=1 -DOCKERIZED=1 -# GPT Envs -OPENAI_API_KEY=0 -GPT_ENGINE=0 diff --git a/apps/app/Dockerfile.web b/apps/app/Dockerfile.web index 11bf98bd4..57654a4e9 100644 --- a/apps/app/Dockerfile.web +++ b/apps/app/Dockerfile.web @@ -3,6 +3,7 @@ RUN apk add --no-cache libc6-compat RUN apk update # Set working directory WORKDIR /app +ENV NEXT_PUBLIC_API_BASE_URL=http://NEXT_PUBLIC_API_BASE_URL_PLACEHOLDER RUN yarn global add turbo COPY . . @@ -12,10 +13,10 @@ RUN turbo prune --scope=app --docker # Add lockfile and package.json's of isolated subworkspace FROM node:18-alpine AS installer - RUN apk add --no-cache libc6-compat RUN apk update WORKDIR /app +ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 # First install the dependencies (as they change less often) COPY .gitignore .gitignore @@ -26,9 +27,17 @@ RUN yarn install # Build the project COPY --from=builder /app/out/full/ . COPY turbo.json turbo.json +COPY replace-env-vars.sh /usr/local/bin/ +USER root +RUN chmod +x /usr/local/bin/replace-env-vars.sh RUN yarn turbo run build --filter=app +ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \ + BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL + +RUN /usr/local/bin/replace-env-vars.sh http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER ${NEXT_PUBLIC_WEBAPP_URL} + FROM node:18-alpine AS runner WORKDIR /app @@ -43,8 +52,20 @@ COPY --from=installer /app/apps/app/package.json . # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=installer --chown=captain:plane /app/apps/app/.next/standalone ./ -# COPY --from=installer --chown=captain:plane /app/apps/app/.next/standalone/node_modules ./apps/app/node_modules -COPY --from=installer --chown=captain:plane /app/apps/app/.next/static ./apps/app/.next/static + +COPY --from=installer --chown=captain:plane /app/apps/app/.next ./apps/app/.next + +ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 +ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \ + BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL + +USER root +COPY replace-env-vars.sh /usr/local/bin/ +COPY start.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/replace-env-vars.sh +RUN chmod +x /usr/local/bin/start.sh + +USER captain ENV NEXT_TELEMETRY_DISABLED 1 diff --git a/apps/app/next.config.js b/apps/app/next.config.js index b3c67eedd..876694142 100644 --- a/apps/app/next.config.js +++ b/apps/app/next.config.js @@ -1,6 +1,10 @@ +require("dotenv").config({ path: ".env" }); + const { withSentryConfig } = require("@sentry/nextjs"); const path = require("path"); -const extraImageDomains = (process.env.NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS ?? "").split(",").filter((domain) => domain.length > 0); +const extraImageDomains = (process.env.NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS ?? "") + .split(",") + .filter((domain) => domain.length > 0); const nextConfig = { reactStrictMode: false, diff --git a/apps/app/package.json b/apps/app/package.json index d3c414210..187e356ff 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -23,6 +23,7 @@ "@types/react-datepicker": "^4.8.0", "axios": "^1.1.3", "cmdk": "^0.2.0", + "dotenv": "^16.0.3", "js-cookie": "^3.0.1", "lodash.debounce": "^4.0.8", "next": "12.3.2", diff --git a/apps/app/pages/signin.tsx b/apps/app/pages/signin.tsx index 3dee9a9e6..ee2469b86 100644 --- a/apps/app/pages/signin.tsx +++ b/apps/app/pages/signin.tsx @@ -1,8 +1,6 @@ import React, { useCallback, useState } from "react"; - import { useRouter } from "next/router"; import Image from "next/image"; - // hooks import useUser from "hooks/use-user"; import useToast from "hooks/use-toast"; diff --git a/docker-compose-hub.yml b/docker-compose-hub.yml index 3f67ef48b..b36d50267 100644 --- a/docker-compose-hub.yml +++ b/docker-compose-hub.yml @@ -35,24 +35,46 @@ services: - redisdata:/data plane-web: container_name: planefrontend - image: makeplane/plane-frontend:0.5-dev + image: makeplane/plane-frontend:0.6 restart: always - command: node apps/app/server.js - env_file: - - ./apps/app/.env + command: [ "/usr/local/bin/start.sh" ] + environment: + NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL} + NEXT_PUBLIC_GOOGLE_CLIENTID: 0 + NEXT_PUBLIC_GITHUB_APP_NAME: 0 + NEXT_PUBLIC_GITHUB_ID: 0 + NEXT_PUBLIC_SENTRY_DSN: 0 + NEXT_PUBLIC_ENABLE_OAUTH: 0 + NEXT_PUBLIC_ENABLE_SENTRY: 0 ports: - 3000:3000 plane-api: container_name: planebackend - image: makeplane/plane-backend:0.5-dev + image: makeplane/plane-backend:0.6 build: context: ./apiserver dockerfile: Dockerfile.api restart: always ports: - 8000:8000 - env_file: - - ./apiserver/.env + environment: + DJANGO_SETTINGS_MODULE: plane.settings.production + DATABASE_URL: postgres://plane:xyzzyspoon@db:5432/plane + REDIS_URL: redis://redis:6379/ + EMAIL_HOST: ${EMAIL_HOST} + EMAIL_HOST_USER: ${EMAIL_HOST_USER} + EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD} + AWS_REGION: ${AWS_REGION} + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} + AWS_S3_BUCKET_NAME: ${AWS_S3_BUCKET_NAME} + WEB_URL: localhost/ + GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} + DISABLE_COLLECTSTATIC: 1 + DOCKERIZED: 1 + OPENAI_API_KEY: 0 + GPT_ENGINE: 0 + SECRET_KEY: ${SECRET_KEY} depends_on: - db - redis @@ -62,7 +84,7 @@ services: - redis:redis plane-worker: container_name: planerqworker - image: makeplane/plane-worker:0.5-dev + image: makeplane/plane-worker:0.6 depends_on: - redis - db @@ -71,8 +93,24 @@ services: links: - redis:redis - db:db - env_file: - - ./apiserver/.env + environment: + DJANGO_SETTINGS_MODULE: plane.settings.production + DATABASE_URL: postgres://plane:xyzzyspoon@db:5432/plane + REDIS_URL: redis://redis:6379/ + EMAIL_HOST: ${EMAIL_HOST} + EMAIL_HOST_USER: ${EMAIL_HOST_USER} + EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD} + AWS_REGION: ${AWS_REGION} + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} + AWS_S3_BUCKET_NAME: ${AWS_S3_BUCKET_NAME} + WEB_URL: localhost/ + GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} + DISABLE_COLLECTSTATIC: 1 + DOCKERIZED: 1 + OPENAI_API_KEY: 0 + GPT_ENGINE: 0 + SECRET_KEY: ${SECRET_KEY} volumes: pgdata: redisdata: diff --git a/docker-compose.yml b/docker-compose.yml index 8d05e03cd..7adcdd54a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,12 +38,21 @@ services: build: context: . dockerfile: ./apps/app/Dockerfile.web - restart: always - command: node apps/app/server.js - env_file: - - ./apps/app/.env + args: + NEXT_PUBLIC_API_BASE_URL: http://localhost:8000 + command: [ "/usr/local/bin/start.sh" ] ports: - 3000:3000 + environment: + NEXT_PUBLIC_API_BASE_URL: http://localhost1234 + NEXT_PUBLIC_GOOGLE_CLIENTID: "0" + NEXT_PUBLIC_GITHUB_APP_NAME: "0" + NEXT_PUBLIC_GITHUB_ID: "0" + NEXT_PUBLIC_SENTRY_DSN: "0" + NEXT_PUBLIC_ENABLE_OAUTH: "0" + NEXT_PUBLIC_ENABLE_SENTRY: "0" + NEXT_PUBLIC_ENABLE_SESSION_RECORDER: "0" + NEXT_PUBLIC_TRACK_EVENTS: "0" plane-api: container_name: planebackend build: @@ -52,8 +61,24 @@ services: restart: always ports: - 8000:8000 - env_file: - - ./apiserver/.env + environment: + DJANGO_SETTINGS_MODULE: plane.settings.production + DATABASE_URL: postgres://plane:xyzzyspoon@db:5432/plane + REDIS_URL: redis://redis:6379/ + EMAIL_HOST: + EMAIL_HOST_USER: + EMAIL_HOST_PASSWORD: + AWS_REGION: + AWS_ACCESS_KEY_ID: + AWS_SECRET_ACCESS_KEY: + AWS_S3_BUCKET_NAME: + WEB_URL: localhost/ + GITHUB_CLIENT_SECRET: + DISABLE_COLLECTSTATIC: 1 + DOCKERIZED: 1 + OPENAI_API_KEY: 0 + GPT_ENGINE: 0 + SECRET_KEY: ${SECRET_KEY} depends_on: - db - redis @@ -74,8 +99,24 @@ services: links: - redis:redis - db:db - env_file: - - ./apiserver/.env + environment: + DJANGO_SETTINGS_MODULE: plane.settings.production + DATABASE_URL: postgres://plane:xyzzyspoon@db:5432/plane + REDIS_URL: redis://redis:6379/ + EMAIL_HOST: + EMAIL_HOST_USER: + EMAIL_HOST_PASSWORD: + AWS_REGION: + AWS_ACCESS_KEY_ID: + AWS_SECRET_ACCESS_KEY: + AWS_S3_BUCKET_NAME: + WEB_URL: localhost/ + GITHUB_CLIENT_SECRET: + DISABLE_COLLECTSTATIC: 1 + DOCKERIZED: 1 + OPENAI_API_KEY: 0 + GPT_ENGINE: 0 + SECRET_KEY: asdasdasdsd volumes: pgdata: redisdata: diff --git a/nginx/supervisor.conf b/nginx/supervisor.conf index db615812e..54b4ca04d 100644 --- a/nginx/supervisor.conf +++ b/nginx/supervisor.conf @@ -2,7 +2,7 @@ nodaemon=true [program:node] -command=node /app/apps/app/server.js +command=sh /usr/local/bin/start.sh autostart=true autorestart=true stderr_logfile=/var/log/node.err.log @@ -21,4 +21,12 @@ command=nginx -g "daemon off;" autostart=true autorestart=true stderr_logfile=/var/log/nginx.err.log -stdout_logfile=/var/log/nginx.out.log \ No newline at end of file +stdout_logfile=/var/log/nginx.out.log + +[program:worker] +directory=/code +command=sh bin/worker +autostart=true +autorestart=true +stderr_logfile=/var/log/worker.err.log +stdout_logfile=/var/log/worker.out.log \ No newline at end of file diff --git a/replace-env-vars.sh b/replace-env-vars.sh new file mode 100644 index 000000000..fe7acc698 --- /dev/null +++ b/replace-env-vars.sh @@ -0,0 +1,17 @@ +#!/bin/sh +FROM=$1 +TO=$2 + +if [ "${FROM}" = "${TO}" ]; then + echo "Nothing to replace, the value is already set to ${TO}." + + exit 0 +fi + +# Only peform action if $FROM and $TO are different. +echo "Replacing all statically built instances of $FROM with this string $TO ." + +find apps/app/.next -type f | +while read file; do + sed -i "s|$FROM|$TO|g" "$file" +done \ No newline at end of file diff --git a/setup.sh b/setup.sh index de95db2f8..e7f9a52dd 100755 --- a/setup.sh +++ b/setup.sh @@ -1,9 +1,7 @@ #!/bin/bash -cp ./apiserver/.env.example ./apiserver/.env -# Generating App environmental variables -cp ./apps/app/.env.example ./apps/app/.env +cp ./.env.example ./.env -echo -e "\nNEXT_PUBLIC_API_BASE_URL=http://$1" >> ./apps/app/.env +echo -e "\nNEXT_PUBLIC_API_BASE_URL=http://$1" >> ./.env export LC_ALL=C export LC_CTYPE=C -echo -e "\nSECRET_KEY=\"$(tr -dc 'a-z0-9!@#$%^&*(-_=+)' < /dev/urandom | head -c50)\"" >> ./apiserver/.env +echo -e "\nSECRET_KEY=\"$(tr -dc 'a-z0-9!@#$%^&*(-_=+)' < /dev/urandom | head -c50)\"" >> ./.env diff --git a/start.sh b/start.sh new file mode 100644 index 000000000..b8ef2736b --- /dev/null +++ b/start.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -x + +# Replace the statically built BUILT_NEXT_PUBLIC_WEBAPP_URL with run-time NEXT_PUBLIC_WEBAPP_URL +# NOTE: if these values are the same, this will be skipped. +/usr/local/bin/replace-env-vars.sh "$BUILT_NEXT_PUBLIC_API_BASE_URL" "$NEXT_PUBLIC_API_BASE_URL" + +echo "Starting Plane Frontend.." +node apps/app/server.js \ No newline at end of file diff --git a/turbo.json b/turbo.json index 12476ceb9..1eba6fc9a 100644 --- a/turbo.json +++ b/turbo.json @@ -4,6 +4,7 @@ "NEXT_PUBLIC_GITHUB_ID", "NEXT_PUBLIC_GOOGLE_CLIENTID", "NEXT_PUBLIC_API_BASE_URL", + "API_BASE_URL", "NEXT_PUBLIC_SENTRY_DSN", "SENTRY_AUTH_TOKEN", "NEXT_PUBLIC_SENTRY_ENVIRONMENT", @@ -17,6 +18,7 @@ "NEXT_PUBLIC_CRISP_ID", "NEXT_PUBLIC_ENABLE_SESSION_RECORDER", "NEXT_PUBLIC_SESSION_RECORDER_KEY", + "NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS", "NEXT_PUBLIC_SLACK_CLIENT_ID", "NEXT_PUBLIC_SLACK_CLIENT_SECRET" ], diff --git a/yarn.lock b/yarn.lock index a81d16621..65c491ddc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4229,6 +4229,11 @@ dot-case@^3.0.4: no-case "^3.0.4" tslib "^2.0.3" +dotenv@^16.0.3: + version "16.0.3" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" + integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== + ejs@^3.1.6: version "3.1.8" resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz" From 849e2d658adc94e2903bd4dc69f019221d903760 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Wed, 3 May 2023 16:39:13 +0530 Subject: [PATCH 02/87] dev:: update docker file for frontend (#998) --- apps/app/Dockerfile.web | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/Dockerfile.web b/apps/app/Dockerfile.web index 57654a4e9..0b3e45f7a 100644 --- a/apps/app/Dockerfile.web +++ b/apps/app/Dockerfile.web @@ -36,7 +36,7 @@ RUN yarn turbo run build --filter=app ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \ BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL -RUN /usr/local/bin/replace-env-vars.sh http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER ${NEXT_PUBLIC_WEBAPP_URL} +RUN /usr/local/bin/replace-env-vars.sh http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER ${NEXT_PUBLIC_API_BASE_URL} FROM node:18-alpine AS runner WORKDIR /app From baa9c30449f7cd906e3d0edd2ecb93e2b6972897 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Wed, 3 May 2023 16:48:04 +0530 Subject: [PATCH 03/87] fix: single docker file (#999) --- Dockerfile | 2 +- start.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9d0ff559d..cb7ef6887 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,7 +36,7 @@ RUN yarn turbo run build --filter=app ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \ BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL -RUN /usr/local/bin/replace-env-vars.sh http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER ${NEXT_PUBLIC_WEBAPP_URL} +RUN /usr/local/bin/replace-env-vars.sh http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER ${NEXT_PUBLIC_API_BASE_URL} FROM python:3.11.1-alpine3.17 AS backend diff --git a/start.sh b/start.sh index b8ef2736b..173e333a4 100644 --- a/start.sh +++ b/start.sh @@ -1,7 +1,7 @@ #!/bin/sh set -x -# Replace the statically built BUILT_NEXT_PUBLIC_WEBAPP_URL with run-time NEXT_PUBLIC_WEBAPP_URL +# Replace the statically built BUILT_NEXT_PUBLIC_API_BASE_URL with run-time NEXT_PUBLIC_API_BASE_URL # NOTE: if these values are the same, this will be skipped. /usr/local/bin/replace-env-vars.sh "$BUILT_NEXT_PUBLIC_API_BASE_URL" "$NEXT_PUBLIC_API_BASE_URL" From c3387ba974a1ee5650a6ca3ee4cc79e18cb50c5b Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Wed, 3 May 2023 21:31:06 +0530 Subject: [PATCH 04/87] fix: environment variables for the services (#1001) --- docker-compose-hub.yml | 8 ++++---- docker-compose.yml | 44 +++++++++++++++++++++--------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docker-compose-hub.yml b/docker-compose-hub.yml index b36d50267..435e47b29 100644 --- a/docker-compose-hub.yml +++ b/docker-compose-hub.yml @@ -72,8 +72,8 @@ services: GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} DISABLE_COLLECTSTATIC: 1 DOCKERIZED: 1 - OPENAI_API_KEY: 0 - GPT_ENGINE: 0 + OPENAI_API_KEY: ${OPENAI_API_KEY} + GPT_ENGINE: ${GPT_ENGINE} SECRET_KEY: ${SECRET_KEY} depends_on: - db @@ -108,8 +108,8 @@ services: GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} DISABLE_COLLECTSTATIC: 1 DOCKERIZED: 1 - OPENAI_API_KEY: 0 - GPT_ENGINE: 0 + OPENAI_API_KEY: ${OPENAI_API_KEY} + GPT_ENGINE: ${GPT_ENGINE} SECRET_KEY: ${SECRET_KEY} volumes: pgdata: diff --git a/docker-compose.yml b/docker-compose.yml index 7adcdd54a..e4086acb2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -44,7 +44,7 @@ services: ports: - 3000:3000 environment: - NEXT_PUBLIC_API_BASE_URL: http://localhost1234 + NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL} NEXT_PUBLIC_GOOGLE_CLIENTID: "0" NEXT_PUBLIC_GITHUB_APP_NAME: "0" NEXT_PUBLIC_GITHUB_ID: "0" @@ -65,19 +65,19 @@ services: DJANGO_SETTINGS_MODULE: plane.settings.production DATABASE_URL: postgres://plane:xyzzyspoon@db:5432/plane REDIS_URL: redis://redis:6379/ - EMAIL_HOST: - EMAIL_HOST_USER: - EMAIL_HOST_PASSWORD: - AWS_REGION: - AWS_ACCESS_KEY_ID: - AWS_SECRET_ACCESS_KEY: - AWS_S3_BUCKET_NAME: + EMAIL_HOST: ${EMAIL_HOST} + EMAIL_HOST_USER: ${EMAIL_HOST_USER} + EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD} + AWS_REGION: ${AWS_REGION} + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} + AWS_S3_BUCKET_NAME: ${AWS_S3_BUCKET_NAME} WEB_URL: localhost/ - GITHUB_CLIENT_SECRET: + GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} DISABLE_COLLECTSTATIC: 1 DOCKERIZED: 1 - OPENAI_API_KEY: 0 - GPT_ENGINE: 0 + OPENAI_API_KEY: ${OPENAI_API_KEY} + GPT_ENGINE: ${GPT_ENGINE} SECRET_KEY: ${SECRET_KEY} depends_on: - db @@ -103,20 +103,20 @@ services: DJANGO_SETTINGS_MODULE: plane.settings.production DATABASE_URL: postgres://plane:xyzzyspoon@db:5432/plane REDIS_URL: redis://redis:6379/ - EMAIL_HOST: - EMAIL_HOST_USER: - EMAIL_HOST_PASSWORD: - AWS_REGION: - AWS_ACCESS_KEY_ID: - AWS_SECRET_ACCESS_KEY: - AWS_S3_BUCKET_NAME: + EMAIL_HOST: ${EMAIL_HOST} + EMAIL_HOST_USER: ${EMAIL_HOST_USER} + EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD} + AWS_REGION: ${AWS_REGION} + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} + AWS_S3_BUCKET_NAME: ${AWS_S3_BUCKET_NAME} WEB_URL: localhost/ - GITHUB_CLIENT_SECRET: + GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} DISABLE_COLLECTSTATIC: 1 DOCKERIZED: 1 - OPENAI_API_KEY: 0 - GPT_ENGINE: 0 - SECRET_KEY: asdasdasdsd + OPENAI_API_KEY: ${OPENAI_API_KEY} + GPT_ENGINE: ${GPT_ENGINE} + SECRET_KEY: ${SECRET_KEY} volumes: pgdata: redisdata: From a2825208b87e2132c4f31496b070e13600a0c801 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Wed, 3 May 2023 23:53:53 +0530 Subject: [PATCH 05/87] docs: update self hosting section in readme (#1002) docs: update self hosting section in readme --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 102739e4e..13888d544 100644 --- a/README.md +++ b/README.md @@ -58,11 +58,18 @@ cd plane > If running in a cloud env replace localhost with public facing IP address of the VM +- Export Environment Variables + +```bash +set -a +source .env +set +a +``` - Run Docker compose up ```bash -docker-compose up +docker-compose -f docker-compose-hub.yml up ``` You can use the default email and password for your first login `captain@plane.so` and `password123`. From e0bec31586f82746ae971632167ce19b3c5416b3 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Fri, 5 May 2023 15:11:45 +0530 Subject: [PATCH 06/87] feat: workspace detail for imports (#1011) --- apiserver/plane/api/serializers/importer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apiserver/plane/api/serializers/importer.py b/apiserver/plane/api/serializers/importer.py index fcc7da6ce..8997f6392 100644 --- a/apiserver/plane/api/serializers/importer.py +++ b/apiserver/plane/api/serializers/importer.py @@ -2,12 +2,14 @@ from .base import BaseSerializer from .user import UserLiteSerializer from .project import ProjectLiteSerializer +from .workspace import WorkspaceLiteSerializer from plane.db.models import Importer class ImporterSerializer(BaseSerializer): initiated_by_detail = UserLiteSerializer(source="initiated_by", read_only=True) project_detail = ProjectLiteSerializer(source="project", read_only=True) + workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True) class Meta: model = Importer From 5b0dc43bae452ba13b478fb5248cd359b2bd4533 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Fri, 5 May 2023 15:12:01 +0530 Subject: [PATCH 07/87] docs: update readme (#1010) docs: update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 13888d544..a7a23d7c5 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@

-Meet Plane. An open-source software development tool to manage issues, sprints, and product roadmaps with peace of mind 🧘‍♀️. +Meet [Plane](https://plane.so). An open-source software development tool to manage issues, sprints, and product roadmaps with peace of mind 🧘‍♀️. > Plane is still in its early days, not everything will be perfect yet, and hiccups may happen. Please let us know of any suggestions, ideas, or bugs that you encounter on our [Discord](https://discord.com/invite/A92xrEGCge) or GitHub issues, and we will use your feedback to improve on our upcoming releases. From 336220bd9841c47584b9ab67a2b6570bce73d1dc Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Fri, 5 May 2023 15:12:22 +0530 Subject: [PATCH 08/87] feat: return workspace and project details in estimate endpoints (#1009) --- apiserver/plane/api/serializers/estimate.py | 6 ++++++ apiserver/plane/api/views/estimate.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apiserver/plane/api/serializers/estimate.py b/apiserver/plane/api/serializers/estimate.py index 360275562..3cb0e4713 100644 --- a/apiserver/plane/api/serializers/estimate.py +++ b/apiserver/plane/api/serializers/estimate.py @@ -2,9 +2,13 @@ from .base import BaseSerializer from plane.db.models import Estimate, EstimatePoint +from plane.api.serializers import WorkspaceLiteSerializer, ProjectLiteSerializer class EstimateSerializer(BaseSerializer): + workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace") + project_detail = ProjectLiteSerializer(read_only=True, source="project") + class Meta: model = Estimate fields = "__all__" @@ -27,6 +31,8 @@ class EstimatePointSerializer(BaseSerializer): class EstimateReadSerializer(BaseSerializer): points = EstimatePointSerializer(read_only=True, many=True) + workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace") + project_detail = ProjectLiteSerializer(read_only=True, source="project") class Meta: model = Estimate diff --git a/apiserver/plane/api/views/estimate.py b/apiserver/plane/api/views/estimate.py index e878ccafc..49c3ba2d0 100644 --- a/apiserver/plane/api/views/estimate.py +++ b/apiserver/plane/api/views/estimate.py @@ -53,7 +53,7 @@ class BulkEstimatePointEndpoint(BaseViewSet): try: estimates = Estimate.objects.filter( workspace__slug=slug, project_id=project_id - ).prefetch_related("points") + ).prefetch_related("points").select_related("workspace", "project") serializer = EstimateReadSerializer(estimates, many=True) return Response(serializer.data, status=status.HTTP_200_OK) except Exception as e: From 1bf1b63fffa86524538542e303246d715f5b394d Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Fri, 5 May 2023 15:12:38 +0530 Subject: [PATCH 09/87] fix: estimate points update (#1003) * fix: estimate points hub * fix: estimate points update * fix: estimate points bulk_update --- apiserver/plane/api/views/estimate.py | 4 ++-- apiserver/plane/db/models/estimate.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apiserver/plane/api/views/estimate.py b/apiserver/plane/api/views/estimate.py index 49c3ba2d0..68de54d7a 100644 --- a/apiserver/plane/api/views/estimate.py +++ b/apiserver/plane/api/views/estimate.py @@ -57,7 +57,7 @@ class BulkEstimatePointEndpoint(BaseViewSet): serializer = EstimateReadSerializer(estimates, many=True) return Response(serializer.data, status=status.HTTP_200_OK) except Exception as e: - print(e) + capture_exception(e) return Response( {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, @@ -211,7 +211,7 @@ class BulkEstimatePointEndpoint(BaseViewSet): try: EstimatePoint.objects.bulk_update( - updated_estimate_points, ["value"], batch_size=10 + updated_estimate_points, ["value"], batch_size=10, ) except IntegrityError as e: return Response( diff --git a/apiserver/plane/db/models/estimate.py b/apiserver/plane/db/models/estimate.py index f163a1407..d95a86316 100644 --- a/apiserver/plane/db/models/estimate.py +++ b/apiserver/plane/db/models/estimate.py @@ -39,7 +39,6 @@ class EstimatePoint(ProjectBaseModel): return f"{self.estimate.name} <{self.key}> <{self.value}>" class Meta: - unique_together = ["value", "estimate"] verbose_name = "Estimate Point" verbose_name_plural = "Estimate Points" db_table = "estimate_points" From 993cf3faba468bf1220b07474283596713840ca0 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Fri, 5 May 2023 15:13:03 +0530 Subject: [PATCH 10/87] chore: add assignee avatar in cycle endpoint (#996) * chore: add assignee avatar in cycle endpoint * dev: update the structure to return avatar and firstname * dev: return distinct users * dev: update the structure to return id * dev: update the prefetch queryset to distinct by id * dev: remove id from distinct * dev: add unique condition --- apiserver/plane/api/serializers/cycle.py | 19 +++++++++++++++++ apiserver/plane/api/views/cycle.py | 27 +++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/apiserver/plane/api/serializers/cycle.py b/apiserver/plane/api/serializers/cycle.py index 5c06a28e7..d6d281357 100644 --- a/apiserver/plane/api/serializers/cycle.py +++ b/apiserver/plane/api/serializers/cycle.py @@ -19,10 +19,29 @@ class CycleSerializer(BaseSerializer): started_issues = serializers.IntegerField(read_only=True) unstarted_issues = serializers.IntegerField(read_only=True) backlog_issues = serializers.IntegerField(read_only=True) + assignees = serializers.SerializerMethodField() workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace") project_detail = ProjectLiteSerializer(read_only=True, source="project") + def get_assignees(self, obj): + members = [ + { + "avatar": assignee.avatar, + "first_name": assignee.first_name, + "id": assignee.id, + } + for issue_cycle in obj.issue_cycle.all() + for assignee in issue_cycle.issue.assignees.all() + ] + # Use a set comprehension to return only the unique objects + unique_objects = {frozenset(item.items()) for item in members} + + # Convert the set back to a list of dictionaries + unique_list = [dict(item) for item in unique_objects] + + return unique_list + class Meta: model = Cycle fields = "__all__" diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index 9265aca00..36c54c54c 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -30,6 +30,7 @@ from plane.db.models import ( CycleFavorite, IssueLink, IssueAttachment, + User, ) from plane.bgtasks.issue_activites_task import issue_activity from plane.utils.grouper import group_results @@ -501,6 +502,12 @@ class CurrentUpcomingCyclesEndpoint(BaseAPIView): filter=Q(issue_cycle__issue__state__group="backlog"), ) ) + .prefetch_related( + Prefetch( + "issue_cycle__issue__assignees", + queryset=User.objects.only("avatar", "first_name", "id").distinct(), + ) + ) .order_by("name", "-is_favorite") ) @@ -545,6 +552,12 @@ class CurrentUpcomingCyclesEndpoint(BaseAPIView): filter=Q(issue_cycle__issue__state__group="backlog"), ) ) + .prefetch_related( + Prefetch( + "issue_cycle__issue__assignees", + queryset=User.objects.only("avatar", "first_name", "id").distinct(), + ) + ) .order_by("name", "-is_favorite") ) @@ -557,7 +570,7 @@ class CurrentUpcomingCyclesEndpoint(BaseAPIView): ) except Exception as e: - capture_exception(e) + print(e) return Response( {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, @@ -618,6 +631,12 @@ class CompletedCyclesEndpoint(BaseAPIView): filter=Q(issue_cycle__issue__state__group="backlog"), ) ) + .prefetch_related( + Prefetch( + "issue_cycle__issue__assignees", + queryset=User.objects.only("avatar", "first_name", "id").distinct(), + ) + ) .order_by("name", "-is_favorite") ) @@ -693,6 +712,12 @@ class DraftCyclesEndpoint(BaseAPIView): filter=Q(issue_cycle__issue__state__group="backlog"), ) ) + .prefetch_related( + Prefetch( + "issue_cycle__issue__assignees", + queryset=User.objects.only("avatar", "first_name", "id").distinct(), + ) + ) .order_by("name", "-is_favorite") ) From fd96c54b43beb2b3d4e00fd0eb7c4401e43a1f5b Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Fri, 5 May 2023 15:13:22 +0530 Subject: [PATCH 11/87] fix: cycle date check endpoint for updation (#1006) * fix: cycle date check endpoint for updation * dev: update the cycle date check endpoint to exclude current cycle when updating --- apiserver/plane/api/views/cycle.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index 36c54c54c..f61a93487 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -414,10 +414,11 @@ class CycleDateCheckEndpoint(BaseAPIView): try: start_date = request.data.get("start_date", False) end_date = request.data.get("end_date", False) + cycle_id = request.data.get("cycle_id", False) if not start_date or not end_date: return Response( - {"error": "Start date and end date both are required"}, + {"error": "Start date and end date are required"}, status=status.HTTP_400_BAD_REQUEST, ) @@ -429,6 +430,11 @@ class CycleDateCheckEndpoint(BaseAPIView): project_id=project_id, ) + if cycle_id: + cycles = cycles.filter( + ~Q(pk=cycle_id), + ) + if cycles.exists(): return Response( { From b34cf0c47180afdb95e357d706595bc842556075 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 5 May 2023 15:45:10 +0530 Subject: [PATCH 12/87] fix: ai button not working on creating a page block (#1013) * fix: ai button not working on creating page block * fix: build error --- .../components/core/gpt-assistant-modal.tsx | 4 +- .../pages/create-update-block-inline.tsx | 167 ++++++++++-------- .../components/pages/single-page-block.tsx | 16 +- .../projects/[projectId]/pages/[pageId].tsx | 1 - 4 files changed, 97 insertions(+), 91 deletions(-) diff --git a/apps/app/components/core/gpt-assistant-modal.tsx b/apps/app/components/core/gpt-assistant-modal.tsx index 37104e30f..653ea3e01 100644 --- a/apps/app/components/core/gpt-assistant-modal.tsx +++ b/apps/app/components/core/gpt-assistant-modal.tsx @@ -121,7 +121,7 @@ export const GptAssistantModal: React.FC = ({ return (
@@ -138,7 +138,7 @@ export const GptAssistantModal: React.FC = ({
)} {response !== "" && ( -
+
Response: ${response}

`} diff --git a/apps/app/components/pages/create-update-block-inline.tsx b/apps/app/components/pages/create-update-block-inline.tsx index 4b8fbcc99..832aa03df 100644 --- a/apps/app/components/pages/create-update-block-inline.tsx +++ b/apps/app/components/pages/create-update-block-inline.tsx @@ -15,8 +15,10 @@ import issuesService from "services/issues.service"; import aiService from "services/ai.service"; // hooks import useToast from "hooks/use-toast"; +// components +import { GptAssistantModal } from "components/core"; // ui -import { Input, Loader, PrimaryButton, SecondaryButton, TextArea } from "components/ui"; +import { Input, Loader, PrimaryButton, SecondaryButton } from "components/ui"; // types import { IPageBlock } from "types"; // fetch-keys @@ -25,9 +27,9 @@ import { PAGE_BLOCKS_LIST } from "constants/fetch-keys"; type Props = { handleClose: () => void; data?: IPageBlock; + handleAiAssistance?: (response: string) => void; setIsSyncing?: React.Dispatch>; focus?: keyof IPageBlock; - setGptAssistantModal: () => void; }; const defaultValues = { @@ -48,11 +50,12 @@ const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor export const CreateUpdateBlockInline: React.FC = ({ handleClose, data, + handleAiAssistance, setIsSyncing, focus, - setGptAssistantModal, }) => { const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false); + const [gptAssistantModal, setGptAssistantModal] = useState(false); const router = useRouter(); const { workspaceSlug, projectId, pageId } = router.query; @@ -230,87 +233,101 @@ export const CreateUpdateBlockInline: React.FC = ({ }, [createPageBlock, updatePageBlock, data, handleSubmit]); return ( -
-
-
- -
-
- ( - setValue("description", jsonValue)} - onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} - placeholder="Write something..." - customClassName="text-sm" - noBorder - borderOnFocus={false} - /> - )} - /> -
- - {data && ( + /> +
+ + - )} +
-
-
- Cancel - - {data - ? isSubmitting - ? "Updating..." - : "Update block" - : isSubmitting - ? "Adding..." - : "Add block"} - -
-
+
+ Cancel + + {data + ? isSubmitting + ? "Updating..." + : "Update block" + : isSubmitting + ? "Adding..." + : "Add block"} + +
+ + setGptAssistantModal(false)} + inset="top-8 left-0" + content={watch("description_html")} + htmlContent={watch("description_html")} + onResponse={(response) => { + if (data && handleAiAssistance) handleAiAssistance(response); + else { + setValue("description", {}); + setValue("description_html", `${watch("description_html")}

${response}

`); + } + }} + projectId={projectId?.toString() ?? ""} + /> +
); }; diff --git a/apps/app/components/pages/single-page-block.tsx b/apps/app/components/pages/single-page-block.tsx index 34b7f6583..ff3811f66 100644 --- a/apps/app/components/pages/single-page-block.tsx +++ b/apps/app/components/pages/single-page-block.tsx @@ -2,12 +2,11 @@ import { useEffect, useState, useRef } from "react"; import { useRouter } from "next/router"; import Link from "next/link"; -import dynamic from "next/dynamic"; import { mutate } from "swr"; // react-hook-form -import { Controller, useForm } from "react-hook-form"; +import { useForm } from "react-hook-form"; // react-beautiful-dnd import { Draggable } from "react-beautiful-dnd"; // services @@ -21,7 +20,7 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector"; import { GptAssistantModal } from "components/core"; import { CreateUpdateBlockInline } from "components/pages"; // ui -import { CustomMenu, Loader } from "components/ui"; +import { CustomMenu } from "components/ui"; // icons import { LayerDiagonalIcon } from "components/icons"; import { ArrowPathIcon, LinkIcon } from "@heroicons/react/20/solid"; @@ -46,15 +45,6 @@ type Props = { index: number; }; -const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { - ssr: false, - loading: () => ( - - - - ), -}); - export const SinglePageBlock: React.FC = ({ block, projectDetails, index }) => { const [isSyncing, setIsSyncing] = useState(false); const [createBlockForm, setCreateBlockForm] = useState(false); @@ -291,7 +281,7 @@ export const SinglePageBlock: React.FC = ({ block, projectDetails, index {...provided.dragHandleProps} > setGptAssistantModal((prev) => !prev)} + handleAiAssistance={handleAiAssistance} handleClose={() => setCreateBlockForm(false)} data={block} setIsSyncing={setIsSyncing} diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index dbdd3c57f..9d2382d21 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -543,7 +543,6 @@ const SinglePage: NextPage = () => { setCreateBlockForm(false)} focus="name" - setGptAssistantModal={() => {}} />
)} From 93c105c49508edf59b72a752a48b2aa8027d802e Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 5 May 2023 15:45:38 +0530 Subject: [PATCH 13/87] chore: track events for estimates and importers (#1012) --- apps/app/components/integration/guide.tsx | 8 ++- .../components/integration/single-import.tsx | 13 ++-- apps/app/services/estimates.service.ts | 24 +++++-- .../services/integration/github.service.ts | 12 +++- apps/app/services/integration/index.ts | 12 +++- apps/app/services/integration/jira.service.ts | 10 ++- apps/app/services/track-event.service.ts | 70 ++++++++++++++++++- apps/app/types/estimate.d.ts | 2 + 8 files changed, 133 insertions(+), 18 deletions(-) diff --git a/apps/app/components/integration/guide.tsx b/apps/app/components/integration/guide.tsx index e61d3ded5..06f13b752 100644 --- a/apps/app/components/integration/guide.tsx +++ b/apps/app/components/integration/guide.tsx @@ -63,7 +63,11 @@ const IntegrationGuide = () => { services. This tool will guide you to relocate the issue to Plane. - +
Read More @@ -124,7 +128,7 @@ const IntegrationGuide = () => { {importerServices ? ( importerServices.length > 0 ? (
-
+
{importerServices.map((service) => ( void; }; -const importersList: { [key: string]: string } = { - github: "GitHub", -}; - export const SingleImport: React.FC = ({ service, refreshing, handleDelete }) => (

- Import from {importersList[service.service]} to{" "} - {service.project_detail.name} + Import from{" "} + + {IMPORTERS_EXPORTERS_LIST.find((i) => i.provider === service.service)?.title} + {" "} + to {service.project_detail.name} { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/`, data) - .then((response) => response?.data) + .then((response) => { + if (trackEvent) + trackEventServices.trackIssueEstimateEvent(response?.data, "ESTIMATE_CREATE"); + return response?.data; + }) .catch((error) => { throw error?.response; }); @@ -32,7 +40,11 @@ class ProjectEstimateServices extends APIService { `/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`, data ) - .then((response) => response?.data) + .then((response) => { + if (trackEvent) + trackEventServices.trackIssueEstimateEvent(response?.data, "ESTIMATE_UPDATE"); + return response?.data; + }) .catch((error) => { throw error?.response?.data; }); @@ -64,7 +76,11 @@ class ProjectEstimateServices extends APIService { return this.delete( `/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/` ) - .then((response) => response?.data) + .then((response) => { + if (trackEvent) + trackEventServices.trackIssueEstimateEvent(response?.data, "ESTIMATE_DELETE"); + return response?.data; + }) .catch((error) => { throw error?.response?.data; }); diff --git a/apps/app/services/integration/github.service.ts b/apps/app/services/integration/github.service.ts index 641d2cd2c..101e7ac67 100644 --- a/apps/app/services/integration/github.service.ts +++ b/apps/app/services/integration/github.service.ts @@ -1,10 +1,14 @@ import APIService from "services/api.service"; +import trackEventServices from "services/track-event.service"; + import { IGithubRepoInfo, IGithubServiceImportFormData } from "types"; const { NEXT_PUBLIC_API_BASE_URL } = process.env; -const integrationServiceType: string = "github"; +const trackEvent = + process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; +const integrationServiceType: string = "github"; class GithubIntegrationService extends APIService { constructor() { super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); @@ -41,7 +45,11 @@ class GithubIntegrationService extends APIService { `/api/workspaces/${workspaceSlug}/projects/importers/${integrationServiceType}/`, data ) - .then((response) => response?.data) + .then((response) => { + if (trackEvent) + trackEventServices.trackImporterEvent(response?.data, "GITHUB_IMPORTER_CREATE"); + return response?.data; + }) .catch((error) => { throw error?.response?.data; }); diff --git a/apps/app/services/integration/index.ts b/apps/app/services/integration/index.ts index 425087085..51ecd33c1 100644 --- a/apps/app/services/integration/index.ts +++ b/apps/app/services/integration/index.ts @@ -1,9 +1,14 @@ import APIService from "services/api.service"; +import trackEventServices from "services/track-event.service"; + // types import { IAppIntegration, IImporterService, IWorkspaceIntegration } from "types"; const { NEXT_PUBLIC_API_BASE_URL } = process.env; +const trackEvent = + process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; + class IntegrationService extends APIService { constructor() { super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); @@ -49,7 +54,12 @@ class IntegrationService extends APIService { importerId: string ): Promise { return this.delete(`/api/workspaces/${workspaceSlug}/importers/${service}/${importerId}/`) - .then((res) => res?.data) + .then((response) => { + const eventName = service === "github" ? "GITHUB_IMPORTER_DELETE" : "JIRA_IMPORTER_DELETE"; + + if (trackEvent) trackEventServices.trackImporterEvent(response?.data, eventName); + return response?.data; + }) .catch((error) => { throw error?.response?.data; }); diff --git a/apps/app/services/integration/jira.service.ts b/apps/app/services/integration/jira.service.ts index 20ad8166a..456530308 100644 --- a/apps/app/services/integration/jira.service.ts +++ b/apps/app/services/integration/jira.service.ts @@ -1,10 +1,14 @@ import APIService from "services/api.service"; +import trackEventServices from "services/track-event.service"; // types import { IJiraMetadata, IJiraResponse, IJiraImporterForm } from "types"; const { NEXT_PUBLIC_API_BASE_URL } = process.env; +const trackEvent = + process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; + class JiraImportedService extends APIService { constructor() { super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); @@ -22,7 +26,11 @@ class JiraImportedService extends APIService { async createJiraImporter(workspaceSlug: string, data: IJiraImporterForm): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/importers/jira/`, data) - .then((response) => response?.data) + .then((response) => { + if (trackEvent) + trackEventServices.trackImporterEvent(response?.data, "JIRA_IMPORTER_CREATE"); + return response?.data; + }) .catch((error) => { throw error?.response?.data; }); diff --git a/apps/app/services/track-event.service.ts b/apps/app/services/track-event.service.ts index 5d7ed32f1..54de7119b 100644 --- a/apps/app/services/track-event.service.ts +++ b/apps/app/services/track-event.service.ts @@ -7,6 +7,7 @@ const trackEvent = // types import type { ICycle, + IEstimate, IGptResponse, IIssue, IIssueComment, @@ -45,7 +46,10 @@ type PagesEventType = "PAGE_CREATE" | "PAGE_UPDATE" | "PAGE_DELETE"; type ViewEventType = "VIEW_CREATE" | "VIEW_UPDATE" | "VIEW_DELETE"; -type IssueCommentType = "ISSUE_COMMENT_CREATE" | "ISSUE_COMMENT_UPDATE" | "ISSUE_COMMENT_DELETE"; +type IssueCommentEventType = + | "ISSUE_COMMENT_CREATE" + | "ISSUE_COMMENT_UPDATE" + | "ISSUE_COMMENT_DELETE"; export type MiscellaneousEventType = | "TOGGLE_CYCLE_ON" @@ -73,6 +77,13 @@ type IssueLabelEventType = "ISSUE_LABEL_CREATE" | "ISSUE_LABEL_UPDATE" | "ISSUE_ type GptEventType = "ASK_GPT" | "USE_GPT_RESPONSE_IN_ISSUE" | "USE_GPT_RESPONSE_IN_PAGE_BLOCK"; +type IssueEstimateEventType = "ESTIMATE_CREATE" | "ESTIMATE_UPDATE" | "ESTIMATE_DELETE"; + +type ImporterEventType = + | "GITHUB_IMPORTER_CREATE" + | "GITHUB_IMPORTER_DELETE" + | "JIRA_IMPORTER_CREATE" + | "JIRA_IMPORTER_DELETE"; class TrackEventServices extends APIService { constructor() { super("/"); @@ -209,7 +220,7 @@ class TrackEventServices extends APIService { async trackIssueCommentEvent( data: Partial | any, - eventName: IssueCommentType + eventName: IssueCommentEventType ): Promise { let payload: any; if (eventName !== "ISSUE_COMMENT_DELETE") @@ -549,6 +560,61 @@ class TrackEventServices extends APIService { }, }); } + + async trackIssueEstimateEvent( + data: { estimate: IEstimate }, + eventName: IssueEstimateEventType + ): Promise { + let payload: any; + if (eventName === "ESTIMATE_DELETE") payload = data; + else + payload = { + workspaceId: data?.estimate?.workspace_detail?.id, + workspaceName: data?.estimate?.workspace_detail?.name, + workspaceSlug: data?.estimate?.workspace_detail?.slug, + projectId: data?.estimate?.project_detail?.id, + projectName: data?.estimate?.project_detail?.name, + projectIdentifier: data?.estimate?.project_detail?.identifier, + estimateId: data.estimate?.id, + }; + + return this.request({ + url: "/api/track-event", + method: "POST", + data: { + eventName, + extra: { + ...payload, + }, + }, + }); + } + + async trackImporterEvent(data: any, eventName: ImporterEventType): Promise { + let payload: any; + if (eventName === "GITHUB_IMPORTER_DELETE" || eventName === "JIRA_IMPORTER_DELETE") + payload = data; + else + payload = { + workspaceId: data?.workspace_detail?.id, + workspaceName: data?.workspace_detail?.name, + workspaceSlug: data?.workspace_detail?.slug, + projectId: data?.project_detail?.id, + projectName: data?.project_detail?.name, + projectIdentifier: data?.project_detail?.identifier, + }; + + return this.request({ + url: "/api/track-event", + method: "POST", + data: { + eventName, + extra: { + ...payload, + }, + }, + }); + } } const trackEventServices = new TrackEventServices(); diff --git a/apps/app/types/estimate.d.ts b/apps/app/types/estimate.d.ts index 6d14a20ca..32925c793 100644 --- a/apps/app/types/estimate.d.ts +++ b/apps/app/types/estimate.d.ts @@ -8,7 +8,9 @@ export interface IEstimate { updated_by: string; points: IEstimatePoint[]; project: string; + project_detail: IProject; workspace: string; + workspace_detail: IWorkspace; } export interface IEstimatePoint { From 86cb23777ea262b98d9540e64dc6a8ee1fb25e2e Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 5 May 2023 15:45:53 +0530 Subject: [PATCH 14/87] fix: bug fixes (#1000) * fix: issue sidebar cycle and module dropdown fix * style: my issue page * style: date picker theming * fix: cycle modal * style: date picker * fix: info icon fix * feat: integration banner * feat: project integration banner * fix: module card progress bar fix * style: integration banner * style: workspace sidebar * fix: cycle date checker * fix: calendar page view dropdown --- .../components/core/issues-view-filter.tsx | 2 +- .../cycles/completed-cycles-list.tsx | 6 +- apps/app/components/cycles/form.tsx | 122 +----------------- apps/app/components/cycles/modal.tsx | 76 ++++++++++- apps/app/components/cycles/sidebar.tsx | 12 +- .../cycles/transfer-issues-modal.tsx | 6 +- .../app/components/cycles/transfer-issues.tsx | 2 +- .../app/components/icons/exclamation-icon.tsx | 22 ++-- apps/app/components/issues/activity.tsx | 22 ++++ apps/app/components/issues/attachments.tsx | 2 +- .../components/issues/my-issues-list-item.tsx | 24 ++-- .../issues/sidebar-select/cycle.tsx | 2 +- .../issues/sidebar-select/module.tsx | 2 +- apps/app/components/modules/sidebar.tsx | 12 +- .../components/modules/single-module-card.tsx | 3 +- .../pages/single-page-detailed-item.tsx | 2 +- .../pages/single-page-list-item.tsx | 2 +- .../project/single-sidebar-project.tsx | 4 +- .../app/components/workspace/sidebar-menu.tsx | 4 +- .../pages/[workspaceSlug]/me/my-issues.tsx | 46 ++++--- .../[projectId]/settings/integrations.tsx | 13 +- .../[workspaceSlug]/settings/integrations.tsx | 14 +- apps/app/pages/_app.tsx | 1 + apps/app/styles/react-datepicker.css | 118 +++++++++++++++++ 24 files changed, 332 insertions(+), 187 deletions(-) create mode 100644 apps/app/styles/react-datepicker.css diff --git a/apps/app/components/core/issues-view-filter.tsx b/apps/app/components/core/issues-view-filter.tsx index 5d6c909fe..6868cf7b0 100644 --- a/apps/app/components/core/issues-view-filter.tsx +++ b/apps/app/components/core/issues-view-filter.tsx @@ -134,7 +134,7 @@ export const IssuesFilterView: React.FC = () => { leaveFrom="opacity-100 translate-y-0" leaveTo="opacity-0 translate-y-1" > - +
{issueView !== "calendar" && ( diff --git a/apps/app/components/cycles/completed-cycles-list.tsx b/apps/app/components/cycles/completed-cycles-list.tsx index bf1971368..6729ceeeb 100644 --- a/apps/app/components/cycles/completed-cycles-list.tsx +++ b/apps/app/components/cycles/completed-cycles-list.tsx @@ -65,7 +65,11 @@ export const CompletedCyclesList: React.FC = ({ completedCycles.completed_cycles.length > 0 ? (
- + Completed cycles are not editable.
diff --git a/apps/app/components/cycles/form.tsx b/apps/app/components/cycles/form.tsx index f977bc74b..a0bd781ce 100644 --- a/apps/app/components/cycles/form.tsx +++ b/apps/app/components/cycles/form.tsx @@ -1,21 +1,10 @@ -import { useEffect, useState } from "react"; - -import { useRouter } from "next/router"; +import { useEffect } from "react"; // react-hook-form import { Controller, useForm } from "react-hook-form"; -// services -import cyclesService from "services/cycles.service"; -// hooks -import useToast from "hooks/use-toast"; + // ui import { DateSelect, Input, PrimaryButton, SecondaryButton, TextArea } from "components/ui"; -// helpers -import { - getDateRangeStatus, - isDateGreaterThanToday, - isDateRangeValid, -} from "helpers/date-time.helper"; // types import { ICycle } from "types"; @@ -34,13 +23,6 @@ const defaultValues: Partial = { }; export const CycleForm: React.FC = ({ handleFormSubmit, handleClose, status, data }) => { - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - - const { setToastAlert } = useToast(); - - const [isDateValid, setIsDateValid] = useState(true); - const { register, formState: { errors, isSubmitting }, @@ -60,43 +42,6 @@ export const CycleForm: React.FC = ({ handleFormSubmit, handleClose, stat }); }; - const cycleStatus = - data?.start_date && data?.end_date ? getDateRangeStatus(data?.start_date, data?.end_date) : ""; - - const dateChecker = async (payload: any) => { - if (isDateGreaterThanToday(payload.end_date)) { - await cyclesService - .cycleDateCheck(workspaceSlug as string, projectId as string, payload) - .then((res) => { - if (res.status) { - setIsDateValid(true); - } else { - setIsDateValid(false); - setToastAlert({ - type: "error", - title: "Error!", - message: - "You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates", - }); - } - }) - .catch((err) => { - console.log(err); - }); - } else { - setIsDateValid(false); - setToastAlert({ - type: "error", - title: "Error!", - message: "Unable to create cycle in past date. Please enter a valid date.", - }); - } - }; - - const checkEmptyDate = - (watch("start_date") === "" && watch("end_date") === "") || - (!watch("start_date") && !watch("end_date")); - useEffect(() => { reset({ ...defaultValues, @@ -147,30 +92,7 @@ export const CycleForm: React.FC = ({ handleFormSubmit, handleClose, stat control={control} name="start_date" render={({ field: { value, onChange } }) => ( - { - onChange(val); - if (val && watch("end_date")) { - if (isDateRangeValid(val, `${watch("end_date")}`)) { - cycleStatus != "current" && - dateChecker({ - start_date: val, - end_date: watch("end_date"), - }); - } else { - setIsDateValid(false); - setToastAlert({ - type: "error", - title: "Error!", - message: - "The date you have entered is invalid. Please check and enter a valid date.", - }); - } - } - }} - /> + onChange(val)} /> )} />
@@ -179,30 +101,7 @@ export const CycleForm: React.FC = ({ handleFormSubmit, handleClose, stat control={control} name="end_date" render={({ field: { value, onChange } }) => ( - { - onChange(val); - if (watch("start_date") && val) { - if (isDateRangeValid(`${watch("start_date")}`, val)) { - cycleStatus != "current" && - dateChecker({ - start_date: watch("start_date"), - end_date: val, - }); - } else { - setIsDateValid(false); - setToastAlert({ - type: "error", - title: "Error!", - message: - "The date you have entered is invalid. Please check and enter a valid date.", - }); - } - } - }} - /> + onChange(val)} /> )} />
@@ -211,18 +110,7 @@ export const CycleForm: React.FC = ({ handleFormSubmit, handleClose, stat
Cancel - + {status ? isSubmitting ? "Updating Cycle..." diff --git a/apps/app/components/cycles/modal.tsx b/apps/app/components/cycles/modal.tsx index cd439c372..968c6f46e 100644 --- a/apps/app/components/cycles/modal.tsx +++ b/apps/app/components/cycles/modal.tsx @@ -13,7 +13,7 @@ import useToast from "hooks/use-toast"; // components import { CycleForm } from "components/cycles"; // helper -import { getDateRangeStatus } from "helpers/date-time.helper"; +import { getDateRangeStatus, isDateGreaterThanToday } from "helpers/date-time.helper"; // types import type { ICycle } from "types"; // fetch keys @@ -128,6 +128,21 @@ export const CreateUpdateCycleModal: React.FC = ({ }); }; + const dateChecker = async (payload: any) => { + try { + const res = await cycleService.cycleDateCheck( + workspaceSlug as string, + projectId as string, + payload + ); + console.log(res); + return res.status; + } catch (err) { + console.log(err); + return false; + } + }; + const handleFormSubmit = async (formData: Partial) => { if (!workspaceSlug || !projectId) return; @@ -135,8 +150,63 @@ export const CreateUpdateCycleModal: React.FC = ({ ...formData, }; - if (!data) await createCycle(payload); - else await updateCycle(data.id, payload); + if (payload.start_date && payload.end_date) { + if (!isDateGreaterThanToday(payload.end_date)) { + setToastAlert({ + type: "error", + title: "Error!", + message: "Unable to create cycle in past date. Please enter a valid date.", + }); + return; + } + + const isDateValid = await dateChecker({ + start_date: payload.start_date, + end_date: payload.end_date, + }); + + if (data?.start_date && data?.end_date) { + const isDateValidForExistingCycle = await dateChecker({ + start_date: payload.start_date, + end_date: payload.end_date, + cycle_id: data.id, + }); + + if (isDateValidForExistingCycle) { + await updateCycle(data.id, payload); + return; + } else { + setToastAlert({ + type: "error", + title: "Error!", + message: + "You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates", + }); + return; + } + } + + if (isDateValid) { + if (data) { + await updateCycle(data.id, payload); + } else { + await createCycle(payload); + } + } else { + setToastAlert({ + type: "error", + title: "Error!", + message: + "You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates", + }); + } + } else { + if (data) { + await updateCycle(data.id, payload); + } else { + await createCycle(payload); + } + } }; return ( diff --git a/apps/app/components/cycles/sidebar.tsx b/apps/app/components/cycles/sidebar.tsx index 863da7934..91e592e7d 100644 --- a/apps/app/components/cycles/sidebar.tsx +++ b/apps/app/components/cycles/sidebar.tsx @@ -370,7 +370,11 @@ export const CycleDetailsSidebar: React.FC = ({ ) : (
- + {cycleStatus === "upcoming" ? "Cycle is yet to start." @@ -444,7 +448,11 @@ export const CycleDetailsSidebar: React.FC = ({ ) : (
- + No issues found. Please add issue. diff --git a/apps/app/components/cycles/transfer-issues-modal.tsx b/apps/app/components/cycles/transfer-issues-modal.tsx index 366ef67f7..c857e154e 100644 --- a/apps/app/components/cycles/transfer-issues-modal.tsx +++ b/apps/app/components/cycles/transfer-issues-modal.tsx @@ -148,7 +148,11 @@ export const TransferIssuesModal: React.FC = ({ isOpen, handleClose }) => )) ) : (
- + You don’t have any current cycle. Please create one to transfer the issues. diff --git a/apps/app/components/cycles/transfer-issues.tsx b/apps/app/components/cycles/transfer-issues.tsx index 067147bed..77b1f59f1 100644 --- a/apps/app/components/cycles/transfer-issues.tsx +++ b/apps/app/components/cycles/transfer-issues.tsx @@ -39,7 +39,7 @@ export const TransferIssues: React.FC = ({ handleClick }) => { return (
- + Completed cycles are not editable.
diff --git a/apps/app/components/icons/exclamation-icon.tsx b/apps/app/components/icons/exclamation-icon.tsx index d904263f5..243329647 100644 --- a/apps/app/components/icons/exclamation-icon.tsx +++ b/apps/app/components/icons/exclamation-icon.tsx @@ -3,14 +3,14 @@ import React from "react"; import type { Props } from "./types"; export const ExclamationIcon: React.FC = ({ width, height, className }) => ( - - - - ); + + + +); diff --git a/apps/app/components/issues/activity.tsx b/apps/app/components/issues/activity.tsx index b3e419e17..87c3cf5b6 100644 --- a/apps/app/components/issues/activity.tsx +++ b/apps/app/components/issues/activity.tsx @@ -231,6 +231,16 @@ export const IssueActivitySection: React.FC = () => { action = `${activityItem.verb} the`; } else if (activityItem.field === "estimate") { action = "updated the"; + } else if (activityItem.field === "cycles") { + action = + activityItem.new_value && activityItem.new_value !== "" + ? "set the cycle to" + : "removed the cycle"; + } else if (activityItem.field === "modules") { + action = + activityItem.new_value && activityItem.new_value !== "" + ? "set the module to" + : "removed the module"; } // for values that are after the action clause let value: any = activityItem.new_value ? activityItem.new_value : activityItem.old_value; @@ -282,6 +292,18 @@ export const IssueActivitySection: React.FC = () => { value = "description"; } else if (activityItem.field === "attachment") { value = "attachment"; + } else if (activityItem.field === "cycles") { + const cycles = + activityItem.new_value && activityItem.new_value !== "" + ? activityItem.new_value + : activityItem.old_value; + value = cycles ? addSpaceIfCamelCase(cycles) : "None"; + } else if (activityItem.field === "modules") { + const modules = + activityItem.new_value && activityItem.new_value !== "" + ? activityItem.new_value + : activityItem.old_value; + value = modules ? addSpaceIfCamelCase(modules) : "None"; } else if (activityItem.field === "link") { value = "link"; } else if (activityItem.field === "estimate_point") { diff --git a/apps/app/components/issues/attachments.tsx b/apps/app/components/issues/attachments.tsx index 13fc0d972..7f4be47dd 100644 --- a/apps/app/components/issues/attachments.tsx +++ b/apps/app/components/issues/attachments.tsx @@ -82,7 +82,7 @@ export const IssueAttachments = () => { } uploaded on ${renderLongDateFormat(file.updated_at)}`} > - +
diff --git a/apps/app/components/issues/my-issues-list-item.tsx b/apps/app/components/issues/my-issues-list-item.tsx index ebf063719..648cb4bbe 100644 --- a/apps/app/components/issues/my-issues-list-item.tsx +++ b/apps/app/components/issues/my-issues-list-item.tsx @@ -82,7 +82,7 @@ export const MyIssuesListItem: React.FC = ({ issue, properties, projectId const isNotAllowed = false; return ( -
+
@@ -91,13 +91,13 @@ export const MyIssuesListItem: React.FC = ({ issue, properties, projectId tooltipHeading="Issue ID" tooltipContent={`${issue.project_detail?.identifier}-${issue.sequence_id}`} > - + {issue.project_detail?.identifier}-{issue.sequence_id} )} - + {truncateText(issue.name, 50)} @@ -127,7 +127,7 @@ export const MyIssuesListItem: React.FC = ({ issue, properties, projectId /> )} {properties.sub_issue_count && ( -
+
{issue?.sub_issues_count} {issue?.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
)} @@ -136,10 +136,10 @@ export const MyIssuesListItem: React.FC = ({ issue, properties, projectId {issue.label_details.map((label) => ( = ({ issue, properties, projectId )} {properties.link && ( -
+
-
- +
+ {issue.link_count}
)} {properties.attachment_count && ( -
+
-
- +
+ {issue.attachment_count}
diff --git a/apps/app/components/issues/sidebar-select/cycle.tsx b/apps/app/components/issues/sidebar-select/cycle.tsx index c412c9fda..7db9cd906 100644 --- a/apps/app/components/issues/sidebar-select/cycle.tsx +++ b/apps/app/components/issues/sidebar-select/cycle.tsx @@ -78,7 +78,7 @@ export const SidebarCycleSelect: React.FC = ({ } - value={issueCycle?.cycle_detail.id} + value={issueCycle ? issueCycle.cycle_detail.id : null} onChange={(value: any) => { !value ? removeIssueFromCycle(issueCycle?.id ?? "", issueCycle?.cycle ?? "") diff --git a/apps/app/components/issues/sidebar-select/module.tsx b/apps/app/components/issues/sidebar-select/module.tsx index 282ccb533..d1b0f999b 100644 --- a/apps/app/components/issues/sidebar-select/module.tsx +++ b/apps/app/components/issues/sidebar-select/module.tsx @@ -82,7 +82,7 @@ export const SidebarModuleSelect: React.FC = ({ } - value={issueModule?.module_detail?.id} + value={issueModule ? issueModule.module_detail?.id : null} onChange={(value: any) => { !value ? removeIssueFromModule(issueModule?.id ?? "", issueModule?.module ?? "") diff --git a/apps/app/components/modules/sidebar.tsx b/apps/app/components/modules/sidebar.tsx index 3b3076e05..e5a932608 100644 --- a/apps/app/components/modules/sidebar.tsx +++ b/apps/app/components/modules/sidebar.tsx @@ -416,7 +416,11 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen, ) : (
- + Invalid date. Please enter valid date. @@ -488,7 +492,11 @@ export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen, ) : (
- + No issues found. Please add issue. diff --git a/apps/app/components/modules/single-module-card.tsx b/apps/app/components/modules/single-module-card.tsx index f7192de6f..c4a580db5 100644 --- a/apps/app/components/modules/single-module-card.tsx +++ b/apps/app/components/modules/single-module-card.tsx @@ -44,7 +44,8 @@ export const SingleModuleCard: React.FC = ({ module, handleEditModule }) const { setToastAlert } = useToast(); - const completionPercentage = (module.completed_issues / module.total_issues) * 100; + const completionPercentage = + ((module.completed_issues + module.cancelled_issues) / module.total_issues) * 100; const handleDeleteModule = () => { if (!module) return; diff --git a/apps/app/components/pages/single-page-detailed-item.tsx b/apps/app/components/pages/single-page-detailed-item.tsx index 9c025f21d..4be70384e 100644 --- a/apps/app/components/pages/single-page-detailed-item.tsx +++ b/apps/app/components/pages/single-page-detailed-item.tsx @@ -162,7 +162,7 @@ export const SinglePageDetailedItem: React.FC = ({ } on ${renderLongDateFormat(`${page.created_at}`)}`} > - + diff --git a/apps/app/components/pages/single-page-list-item.tsx b/apps/app/components/pages/single-page-list-item.tsx index bb9589502..876d94102 100644 --- a/apps/app/components/pages/single-page-list-item.tsx +++ b/apps/app/components/pages/single-page-list-item.tsx @@ -161,7 +161,7 @@ export const SinglePageListItem: React.FC = ({ } on ${renderLongDateFormat(`${page.created_at}`)}`} > - + diff --git a/apps/app/components/project/single-sidebar-project.tsx b/apps/app/components/project/single-sidebar-project.tsx index a3f265032..4ff2b2cd3 100644 --- a/apps/app/components/project/single-sidebar-project.tsx +++ b/apps/app/components/project/single-sidebar-project.tsx @@ -172,8 +172,8 @@ export const SingleSidebarProject: React.FC = ({
diff --git a/apps/app/components/workspace/sidebar-menu.tsx b/apps/app/components/workspace/sidebar-menu.tsx index 58712a30b..8e5bc65ab 100644 --- a/apps/app/components/workspace/sidebar-menu.tsx +++ b/apps/app/components/workspace/sidebar-menu.tsx @@ -47,8 +47,8 @@ export const WorkspaceSidebarMenu: React.FC = () => { ? router.asPath === link.href : router.asPath.includes(link.href) ) - ? "bg-brand-base text-brand-base" - : "text-brand-secondary hover:bg-brand-surface-1 hover:text-brand-secondary focus:bg-brand-base focus:text-brand-secondary" + ? "bg-brand-surface-2 text-brand-base" + : "text-brand-secondary hover:bg-brand-surface-2 hover:text-brand-secondary focus:bg-brand-surface-2 focus:text-brand-secondary" } group flex w-full items-center gap-3 rounded-md p-2 text-sm font-medium outline-none ${ sidebarCollapse ? "justify-center" : "" }`} diff --git a/apps/app/pages/[workspaceSlug]/me/my-issues.tsx b/apps/app/pages/[workspaceSlug]/me/my-issues.tsx index 8e48db44a..610502b08 100644 --- a/apps/app/pages/[workspaceSlug]/me/my-issues.tsx +++ b/apps/app/pages/[workspaceSlug]/me/my-issues.tsx @@ -52,7 +52,7 @@ const MyIssuesPage: NextPage = () => { <> View @@ -69,29 +69,27 @@ const MyIssuesPage: NextPage = () => { leaveTo="opacity-0 translate-y-1" > -
-
-

Properties

-
- {Object.keys(properties).map((key) => { - if (key === "estimate") return null; +
+

Properties

+
+ {Object.keys(properties).map((key) => { + if (key === "estimate") return null; - return ( - - ); - })} -
+ return ( + + ); + })}
@@ -107,7 +105,7 @@ const MyIssuesPage: NextPage = () => { document.dispatchEvent(e); }} > - + Add Issue
diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx index 101044f5c..7d8592400 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx @@ -16,6 +16,7 @@ import { EmptySpace, EmptySpaceItem, Loader } from "components/ui"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // icons import { PlusIcon, PuzzlePieceIcon } from "@heroicons/react/24/outline"; +import { ExclamationIcon } from "components/icons"; // types import { IProject } from "types"; import type { NextPage } from "next"; @@ -56,7 +57,17 @@ const ProjectIntegrations: NextPage = () => { {workspaceIntegrations ? ( workspaceIntegrations.length > 0 ? (
-

Integrations

+
+

Integrations

+
+ +

+ Integrations and importers are only available on the cloud version. We plan to + open-source our SDKs in the near future so that the community can request or + contribute integrations as needed. +

+
+
{workspaceIntegrations.map((integration) => ( { } >
-

Integrations

+
+

Integrations

+
+ +

+ Integrations and importers are only available on the cloud version. We plan to + open-source our SDKs in the near future so that the community can request or + contribute integrations as needed. +

+
+
{appIntegrations ? ( appIntegrations.map((integration) => ( diff --git a/apps/app/pages/_app.tsx b/apps/app/pages/_app.tsx index 6f372b4e3..0f50b3b8c 100644 --- a/apps/app/pages/_app.tsx +++ b/apps/app/pages/_app.tsx @@ -8,6 +8,7 @@ import "styles/globals.css"; import "styles/editor.css"; import "styles/command-pallette.css"; import "styles/nprogress.css"; +import "styles/react-datepicker.css"; // router import Router from "next/router"; diff --git a/apps/app/styles/react-datepicker.css b/apps/app/styles/react-datepicker.css new file mode 100644 index 000000000..3c5f9a5ca --- /dev/null +++ b/apps/app/styles/react-datepicker.css @@ -0,0 +1,118 @@ +.react-datepicker-wrapper input::placeholder { + color: rgba(var(--color-text-secondary)); + opacity: 1; +} + +.react-datepicker-wrapper input:-ms-input-placeholder { + color: rgba(var(--color-text-secondary)); +} + +.react-datepicker-wrapper .react-datepicker__close-icon::after { + background: transparent; + color: rgba(var(--color-text-secondary)); +} + +.react-datepicker-popper { + z-index: 30 !important; +} + +.react-datepicker-wrapper { + position: relative; + background-color: rgba(var(--color-bg-base)) !important; +} + +.react-datepicker { + font-family: "Inter" !important; + border: none !important; + background-color: rgba(var(--color-bg-base)) !important; +} + +.react-datepicker__month-container { + width: 300px; + background-color: rgba(var(--color-bg-base)) !important; + color: rgba(var(--color-text-base)) !important; + border-radius: 10px !important; + /* border: 1px solid rgba(var(--color-border)) !important; */ +} + +.react-datepicker__header { + border-radius: 10px !important; + background-color: rgba(var(--color-bg-base)) !important; + border: none !important; +} + +.react-datepicker__navigation { + line-height: 0.78; +} + +.react-datepicker__triangle { + border-color: rgba(var(--color-bg-base)) transparent transparent transparent !important; +} + +.react-datepicker__triangle:before { + border-bottom-color: rgba(var(--color-border)) !important; +} +.react-datepicker__triangle:after { + border-bottom-color: rgba(var(--color-bg-base)) !important; +} + +.react-datepicker__current-month { + font-weight: 500 !important; + color: rgba(var(--color-text-base)) !important; +} + +.react-datepicker__month { + border-collapse: collapse; + color: rgba(var(--color-text-base)) !important; +} + +.react-datepicker__day-names { + margin-top: 10px; + margin-left: 14px; + width: 280px; + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 0; +} + +.react-datepicker__day-name { + color: rgba(var(--color-text-base)) !important; +} + +.react-datepicker__week { + display: grid; + grid-template-columns: repeat(7, 1fr); + margin-left: 8px; +} + +.react-datepicker__day { + color: rgba(var(--color-text-base)) !important; +} + +.react-datepicker__day { + border-radius: 50% !important; + transition: all 0.15s ease-in-out; +} + +.react-datepicker__day:hover { + background-color: rgba(var(--color-bg-surface-2)) !important; + color: rgba(var(--color-text-base)) !important; +} + +.react-datepicker__day--selected { + background-color: #216ba5 !important; + color: white !important; +} + +.react-datepicker__day--today { + font-weight: 800; +} + +.react-datepicker__day--highlighted { + background-color: rgba(var(--color-bg-surface-2)) !important; +} + +.react-datepicker__day--keyboard-selected { + background-color: #216ba5 !important; + color: white !important; +} From 443878994a0b13585f4ed9439b1dec8d64044802 Mon Sep 17 00:00:00 2001 From: Rhea Jain <65884341+rhea0110@users.noreply.github.com> Date: Fri, 5 May 2023 15:46:05 +0530 Subject: [PATCH 15/87] fix: breadcrumbs and tab updated (#1007) --- apps/app/components/command-palette/command-pallette.tsx | 4 ++-- apps/app/layouts/settings-navbar.tsx | 2 +- .../[workspaceSlug]/projects/[projectId]/settings/index.tsx | 2 +- apps/app/pages/[workspaceSlug]/settings/billing.tsx | 2 +- apps/app/pages/[workspaceSlug]/settings/import-export.tsx | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/app/components/command-palette/command-pallette.tsx b/apps/app/components/command-palette/command-pallette.tsx index 9d23d84e2..bed84f5ad 100644 --- a/apps/app/components/command-palette/command-pallette.tsx +++ b/apps/app/components/command-palette/command-pallette.tsx @@ -821,7 +821,7 @@ export const CommandPalette: React.FC = () => { >
- Billings and Plans + Billing and Plans
{ >
- Import/Export + Import/ Export
diff --git a/apps/app/layouts/settings-navbar.tsx b/apps/app/layouts/settings-navbar.tsx index 027f87f61..fb720acf8 100644 --- a/apps/app/layouts/settings-navbar.tsx +++ b/apps/app/layouts/settings-navbar.tsx @@ -30,7 +30,7 @@ const SettingsNavbar: React.FC = ({ profilePage = false }) => { href: `/${workspaceSlug}/settings/integrations`, }, { - label: "Import/Export", + label: "Import/ Export", href: `/${workspaceSlug}/settings/import-export`, }, ]; diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx index 1996904a8..aebcc5690 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx @@ -222,7 +222,7 @@ const GeneralSettings: NextPage = () => {

Cover Photo

-

+

Select your cover photo from the given library.

diff --git a/apps/app/pages/[workspaceSlug]/settings/billing.tsx b/apps/app/pages/[workspaceSlug]/settings/billing.tsx index acdd0b9e2..8b191316f 100644 --- a/apps/app/pages/[workspaceSlug]/settings/billing.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/billing.tsx @@ -34,7 +34,7 @@ const BillingSettings: NextPage = () => { title={`${activeWorkspace?.name ?? "Workspace"}`} link={`/${workspaceSlug}`} /> - + } > diff --git a/apps/app/pages/[workspaceSlug]/settings/import-export.tsx b/apps/app/pages/[workspaceSlug]/settings/import-export.tsx index 41ef3177b..3a8fa340f 100644 --- a/apps/app/pages/[workspaceSlug]/settings/import-export.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/import-export.tsx @@ -18,7 +18,7 @@ const ImportExport: NextPage = () => { breadcrumbs={ - + } > From a1de3f581fc4b804f41ca72d74de34b79102964a Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 5 May 2023 17:07:29 +0530 Subject: [PATCH 16/87] fix: layout height and overflow (#1004) * fix: kanban height issue * dev: Layout fixes * dev: layout changes * fix: layout overflow settings and fixed header * style: filters padding fixed * fix: hide filters if none are applied --------- Co-authored-by: gurusainath --- .../components/core/board-view/all-boards.tsx | 2 +- .../core/board-view/single-issue.tsx | 4 +- .../core/calendar-view/calendar.tsx | 2 +- apps/app/components/core/issues-view.tsx | 66 ++-- .../core/list-view/single-issue.tsx | 4 +- .../integration/github/select-repository.tsx | 2 +- .../components/issues/my-issues-list-item.tsx | 8 +- apps/app/components/project/index.ts | 1 + .../components/project/settings-header.tsx | 13 + apps/app/components/workspace/index.ts | 1 + .../components/workspace/settings-header.tsx | 13 + apps/app/layouts/app-layout/app-header.tsx | 4 +- apps/app/layouts/app-layout/app-sidebar.tsx | 28 +- .../project-authorization-wrapper.tsx | 43 +-- .../workspace-authorization-wrapper.tsx | 47 +-- apps/app/pages/[workspaceSlug]/index.tsx | 2 +- .../pages/[workspaceSlug]/me/my-issues.tsx | 84 ++--- .../[workspaceSlug]/me/profile/activity.tsx | 37 ++- .../[workspaceSlug]/me/profile/index.tsx | 271 ++++++++-------- .../projects/[projectId]/cycles/index.tsx | 2 +- .../projects/[projectId]/issues/[issueId].tsx | 1 - .../projects/[projectId]/modules/index.tsx | 4 +- .../projects/[projectId]/pages/[pageId].tsx | 2 +- .../projects/[projectId]/pages/index.tsx | 4 +- .../projects/[projectId]/settings/control.tsx | 4 +- .../[projectId]/settings/estimates.tsx | 122 +++---- .../[projectId]/settings/features.tsx | 96 +++--- .../projects/[projectId]/settings/index.tsx | 5 +- .../[projectId]/settings/integrations.tsx | 97 +++--- .../projects/[projectId]/settings/labels.tsx | 124 +++---- .../projects/[projectId]/settings/members.tsx | 230 ++++++------- .../projects/[projectId]/settings/states.tsx | 144 +++++---- .../projects/[projectId]/views/index.tsx | 2 +- .../pages/[workspaceSlug]/projects/index.tsx | 4 +- .../[workspaceSlug]/settings/billing.tsx | 52 +-- .../settings/import-export.tsx | 6 +- .../pages/[workspaceSlug]/settings/index.tsx | 303 +++++++++--------- .../[workspaceSlug]/settings/integrations.tsx | 52 +-- .../[workspaceSlug]/settings/members.tsx | 224 ++++++------- apps/app/styles/globals.css | 1 + apps/app/tailwind.config.js | 4 - 41 files changed, 1080 insertions(+), 1035 deletions(-) create mode 100644 apps/app/components/project/settings-header.tsx create mode 100644 apps/app/components/workspace/settings-header.tsx diff --git a/apps/app/components/core/board-view/all-boards.tsx b/apps/app/components/core/board-view/all-boards.tsx index 69346cd18..495cee0a5 100644 --- a/apps/app/components/core/board-view/all-boards.tsx +++ b/apps/app/components/core/board-view/all-boards.tsx @@ -44,7 +44,7 @@ export const AllBoards: React.FC = ({ return ( <> {groupedByIssues ? ( -
+
{Object.keys(groupedByIssues).map((singleGroup, index) => { const currentState = selectedGroup === "state" ? states?.find((s) => s.id === singleGroup) : null; diff --git a/apps/app/components/core/board-view/single-issue.tsx b/apps/app/components/core/board-view/single-issue.tsx index 8007f3216..e82b9897c 100644 --- a/apps/app/components/core/board-view/single-issue.tsx +++ b/apps/app/components/core/board-view/single-issue.tsx @@ -392,7 +392,7 @@ export const SingleBoardIssue: React.FC = ({
- + {issue.link_count}
@@ -402,7 +402,7 @@ export const SingleBoardIssue: React.FC = ({
- + {issue.attachment_count}
diff --git a/apps/app/components/core/calendar-view/calendar.tsx b/apps/app/components/core/calendar-view/calendar.tsx index dd814ed09..9dfdac64e 100644 --- a/apps/app/components/core/calendar-view/calendar.tsx +++ b/apps/app/components/core/calendar-view/calendar.tsx @@ -229,7 +229,7 @@ export const CalendarView: React.FC = ({ addIssueToDate }) => { return calendarIssues ? ( -
+
diff --git a/apps/app/components/core/issues-view.tsx b/apps/app/components/core/issues-view.tsx index 497d7fba4..9baa0318d 100644 --- a/apps/app/components/core/issues-view.tsx +++ b/apps/app/components/core/issues-view.tsx @@ -353,7 +353,7 @@ export const IssuesView: React.FC = ({ console.log(e); }); }, - [workspaceSlug, projectId, cycleId, params] + [workspaceSlug, projectId, cycleId, params, selectedGroup, setToastAlert] ); const removeIssueFromModule = useCallback( @@ -396,7 +396,7 @@ export const IssuesView: React.FC = ({ console.log(e); }); }, - [workspaceSlug, projectId, moduleId, params] + [workspaceSlug, projectId, moduleId, params, selectedGroup, setToastAlert] ); const handleTrashBox = useCallback( @@ -442,39 +442,35 @@ export const IssuesView: React.FC = ({ handleClose={() => setTransferIssuesModal(false)} isOpen={transferIssuesModal} /> - <> -
- - {areFiltersApplied && ( - { - if (viewId) { - setFilters({}, true); - setToastAlert({ - title: "View updated", - message: "Your view has been updated", - type: "success", - }); - } else - setCreateViewModal({ - query: filters, - }); - }} - className="flex items-center gap-2 text-sm" - > - {!viewId && } - {viewId ? "Update" : "Save"} view - - )} -
- {areFiltersApplied && ( -
- )} - + {areFiltersApplied && ( + <> +
+ + {areFiltersApplied && ( + { + if (viewId) { + setFilters({}, true); + setToastAlert({ + title: "View updated", + message: "Your view has been updated", + type: "success", + }); + } else + setCreateViewModal({ + query: filters, + }); + }} + className="flex items-center gap-2 text-sm" + > + {!viewId && } + {viewId ? "Update" : "Save"} view + + )} +
+ {
} + + )} diff --git a/apps/app/components/core/list-view/single-issue.tsx b/apps/app/components/core/list-view/single-issue.tsx index 3f794c20c..dbcb87451 100644 --- a/apps/app/components/core/list-view/single-issue.tsx +++ b/apps/app/components/core/list-view/single-issue.tsx @@ -314,7 +314,7 @@ export const SingleListIssue: React.FC = ({
- + {issue.link_count}
@@ -324,7 +324,7 @@ export const SingleListIssue: React.FC = ({
- + {issue.attachment_count}
diff --git a/apps/app/components/integration/github/select-repository.tsx b/apps/app/components/integration/github/select-repository.tsx index b1781b70e..69040c2d0 100644 --- a/apps/app/components/integration/github/select-repository.tsx +++ b/apps/app/components/integration/github/select-repository.tsx @@ -81,7 +81,7 @@ export const SelectRepository: React.FC = ({ {userRepositories && options.length < totalCount && (
); }; diff --git a/apps/app/layouts/auth-layout/project-authorization-wrapper.tsx b/apps/app/layouts/auth-layout/project-authorization-wrapper.tsx index 1ad2d6868..61da09887 100644 --- a/apps/app/layouts/auth-layout/project-authorization-wrapper.tsx +++ b/apps/app/layouts/auth-layout/project-authorization-wrapper.tsx @@ -11,7 +11,6 @@ import useIssuesView from "hooks/use-issues-view"; import Container from "layouts/container"; import AppHeader from "layouts/app-layout/app-header"; import AppSidebar from "layouts/app-layout/app-sidebar"; -import SettingsNavbar from "layouts/settings-navbar"; // components import { NotAuthorizedView, JoinProject } from "components/auth-screens"; import { CommandPalette } from "components/command-palette"; @@ -30,7 +29,6 @@ type Meta = { type Props = { meta?: Meta; children: React.ReactNode; - noPadding?: boolean; noHeader?: boolean; bg?: "primary" | "secondary"; breadcrumbs?: JSX.Element; @@ -47,7 +45,6 @@ export const ProjectAuthorizationWrapper: React.FC = (props) => ( const ProjectAuthorizationWrapped: React.FC = ({ meta, children, - noPadding = false, noHeader = false, bg = "primary", breadcrumbs, @@ -68,8 +65,9 @@ const ProjectAuthorizationWrapped: React.FC = ({ return ( -
+
+ {loading ? (
@@ -107,7 +105,15 @@ const ProjectAuthorizationWrapped: React.FC = ({ type="project" /> ) : ( -
+
{!noHeader && ( = ({ setToggleSidebar={setToggleSidebar} /> )} -
- {settingsLayout && ( -
-
-

Project Settings

-

- This information will be displayed to every member of the project. -

-
- -
- )} - {children} +
+
+ {children} +
)} diff --git a/apps/app/layouts/auth-layout/workspace-authorization-wrapper.tsx b/apps/app/layouts/auth-layout/workspace-authorization-wrapper.tsx index 608760d6f..90dcbcf13 100644 --- a/apps/app/layouts/auth-layout/workspace-authorization-wrapper.tsx +++ b/apps/app/layouts/auth-layout/workspace-authorization-wrapper.tsx @@ -32,25 +32,21 @@ type Meta = { type Props = { meta?: Meta; children: React.ReactNode; - noPadding?: boolean; noHeader?: boolean; bg?: "primary" | "secondary"; breadcrumbs?: JSX.Element; left?: JSX.Element; right?: JSX.Element; - profilePage?: boolean; }; export const WorkspaceAuthorizationLayout: React.FC = ({ meta, children, - noPadding = false, noHeader = false, bg = "primary", breadcrumbs, left, right, - profilePage = false, }) => { const [toggleSidebar, setToggleSidebar] = useState(false); @@ -101,7 +97,7 @@ export const WorkspaceAuthorizationLayout: React.FC = ({ -
+
{settingsLayout && (memberType?.isGuest || memberType?.isViewer) ? ( = ({ type="workspace" /> ) : ( -
+
{!noHeader && ( = ({ setToggleSidebar={setToggleSidebar} /> )} -
- {(settingsLayout || profilePage) && ( -
-
-

- {profilePage ? "Profile" : "Workspace"} Settings -

-

- {profilePage - ? "This information will be visible to only you." - : "This information will be displayed to every member of the workspace."} -

-
- -
- )} - {children} +
+
+ {children} +
)} diff --git a/apps/app/pages/[workspaceSlug]/index.tsx b/apps/app/pages/[workspaceSlug]/index.tsx index 5edef60eb..fd3ca88d3 100644 --- a/apps/app/pages/[workspaceSlug]/index.tsx +++ b/apps/app/pages/[workspaceSlug]/index.tsx @@ -45,7 +45,7 @@ const WorkspacePage: NextPage = () => { isOpen={isProductUpdatesModalOpen} setIsOpen={setIsProductUpdatesModalOpen} /> -
+
{ } - noPadding right={
{myIssues && myIssues.length > 0 && ( @@ -115,55 +114,42 @@ const MyIssuesPage: NextPage = () => { {myIssues ? ( <> {myIssues.length > 0 ? ( -
- - {({ open }) => ( -
-
- -
- - - -

My Issues

- - {myIssues.length} - -
-
-
- - - {myIssues.map((issue: IIssue) => ( - - ))} - - + + {({ open }) => ( +
+
+ +
+

My Issues

+ + {myIssues.length} + +
+
- )} - -
+ + + {myIssues.map((issue: IIssue) => ( + + ))} + + +
+ )} +
) : (
{ const { data: userActivity } = useSWR(USER_ACTIVITY, () => userService.getUserActivity()); @@ -25,20 +26,30 @@ const ProfileActivity = () => { } - profilePage > - {userActivity ? ( - userActivity.results.length > 0 ? ( - - ) : null - ) : ( - - - - - - - )} +
+
+
+

Profile Settings

+

+ This information will be visible to only you. +

+
+ +
+ {userActivity ? ( + userActivity.results.length > 0 ? ( + + ) : null + ) : ( + + + + + + + )} +
); }; diff --git a/apps/app/pages/[workspaceSlug]/me/profile/index.tsx b/apps/app/pages/[workspaceSlug]/me/profile/index.tsx index 6487fbbd8..5fbf3ef2a 100644 --- a/apps/app/pages/[workspaceSlug]/me/profile/index.tsx +++ b/apps/app/pages/[workspaceSlug]/me/profile/index.tsx @@ -24,6 +24,7 @@ import type { NextPage } from "next"; import type { IUser } from "types"; // constants import { USER_ROLES } from "constants/workspace"; +import SettingsNavbar from "layouts/settings-navbar"; const defaultValues: Partial = { avatar: "", @@ -130,7 +131,6 @@ const Profile: NextPage = () => { } - profilePage > { userImage /> {myProfile ? ( -
-
-
-

Profile Picture

-

- Max file size is 5MB. Supported file types are .jpg and .png. +

+
+
+

Profile Settings

+

+ This information will be visible to only you.

-
-
- -
- { - setIsImageUploadModalOpen(true); - }} - > - Upload - - {myProfile.avatar && myProfile.avatar !== "" && ( - handleDelete(myProfile.avatar, true)} - loading={isRemoving} + +
+
+
+
+

Profile Picture

+

+ Max file size is 5MB. Supported file types are .jpg and .png. +

+
+
+
+ +
+ { + setIsImageUploadModalOpen(true); + }} > - {isRemoving ? "Removing..." : "Remove"} - - )} + Upload + + {myProfile.avatar && myProfile.avatar !== "" && ( + handleDelete(myProfile.avatar, true)} + loading={isRemoving} + > + {isRemoving ? "Removing..." : "Remove"} + + )} +
-
-
-
-

Full Name

-

- This name will be reflected on all the projects you are working on. -

+
+
+

Full Name

+

+ This name will be reflected on all the projects you are working on. +

+
+
+ + +
-
- - +
+
+

Email

+

+ The email address that you are using. +

+
+
+ +
-
-
-
-

Email

-

The email address that you are using.

+
+
+

Role

+

Add your role.

+
+
+ ( + + {USER_ROLES.map((item) => ( + + {item.label} + + ))} + + )} + /> +
-
- +
+
+

Theme

+

+ Select or customize your interface color scheme. +

+
+
+ +
-
-
-
-

Role

-

Add your role.

+
+ + {isSubmitting ? "Updating..." : "Update profile"} +
-
- ( - - {USER_ROLES.map((item) => ( - - {item.label} - - ))} - - )} - /> -
-
-
-
-

Theme

-

- Select or customize your interface color scheme. -

-
-
- -
-
-
- - {isSubmitting ? "Updating..." : "Update profile"} -
) : ( diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx index 55351b42d..cf4b8b109 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx @@ -124,7 +124,7 @@ const ProjectCycles: NextPage = () => { handleClose={() => setCreateUpdateCycleModal(false)} data={selectedCycle} /> -
+
{currentAndUpcomingCycles && currentAndUpcomingCycles.current_cycle.length > 0 && (

Current Cycle

diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index 85ea7551c..1d73b2a75 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -122,7 +122,6 @@ const IssueDetailsPage: NextPage = () => { return ( { document.dispatchEvent(e); }} > - + Add Module } @@ -89,7 +89,7 @@ const ProjectModules: NextPage = () => { /> {modules ? ( modules.length > 0 ? ( -
+

Modules

diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index 9d2382d21..fb82b7364 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -312,7 +312,7 @@ const SinglePage: NextPage = () => { } > {pageDetails ? ( -
+
+
+ ) ) : ( -
- { - setEstimateToUpdate(undefined); - setEstimateFormOpen(true); - }} - /> -
- ) - ) : ( - - - - - - - )} + + + + + + + )} +
); diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx index 3b5a09e6c..9df67612b 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx @@ -22,6 +22,7 @@ import { IProject } from "types"; import type { NextPage } from "next"; // fetch-keys import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; +import { SettingsHeader } from "components/project"; const featuresList = [ { @@ -134,54 +135,57 @@ const FeaturesSettings: NextPage = () => { } > -
-

Features

-
- {featuresList.map((feature) => ( -
-
- {feature.icon} -
-

{feature.title}

-

{feature.description}

+
+ +
+

Features

+
+ {featuresList.map((feature) => ( +
+
+ {feature.icon} +
+

{feature.title}

+

{feature.description}

+
+ { + trackEventServices.trackMiscellaneousEvent( + { + workspaceId: (projectDetails?.workspace as any)?.id, + workspaceSlug, + projectId, + projectIdentifier: projectDetails?.identifier, + projectName: projectDetails?.name, + }, + !projectDetails?.[feature.property as keyof IProject] + ? getEventType(feature.title, true) + : getEventType(feature.title, false) + ); + handleSubmit({ + [feature.property]: !projectDetails?.[feature.property as keyof IProject], + }); + }} + size="lg" + />
- { - trackEventServices.trackMiscellaneousEvent( - { - workspaceId: (projectDetails?.workspace as any)?.id, - workspaceSlug, - projectId, - projectIdentifier: projectDetails?.identifier, - projectName: projectDetails?.name, - }, - !projectDetails?.[feature.property as keyof IProject] - ? getEventType(feature.title, true) - : getEventType(feature.title, false) - ); - handleSubmit({ - [feature.property]: !projectDetails?.[feature.property as keyof IProject], - }); - }} - size="lg" - /> -
- ))} -
-
-
+ ))} +
+
+ +
); }; diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx index aebcc5690..684465f8d 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx @@ -12,7 +12,7 @@ import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; // services import projectService from "services/project.service"; // components -import { DeleteProjectModal } from "components/project"; +import { DeleteProjectModal, SettingsHeader } from "components/project"; import { ImagePickerPopover } from "components/core"; import EmojiIconPicker from "components/emoji-icon-picker"; // hooks @@ -151,7 +151,8 @@ const GeneralSettings: NextPage = () => { router.push(`/${workspaceSlug}/projects`); }} /> - + +
diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx index 7d8592400..5f5077a0e 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx @@ -10,7 +10,7 @@ import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import IntegrationService from "services/integration"; import projectService from "services/project.service"; // components -import { SingleIntegration } from "components/project"; +import { SettingsHeader, SingleIntegration } from "components/project"; // ui import { EmptySpace, EmptySpaceItem, Loader } from "components/ui"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; @@ -54,54 +54,61 @@ const ProjectIntegrations: NextPage = () => { } > - {workspaceIntegrations ? ( - workspaceIntegrations.length > 0 ? ( -
-
-

Integrations

-
- -

- Integrations and importers are only available on the cloud version. We plan to - open-source our SDKs in the near future so that the community can request or - contribute integrations as needed. -

+
+ + {workspaceIntegrations ? ( + workspaceIntegrations.length > 0 ? ( +
+
+

Integrations

+
+ +

+ Integrations and importers are only available on the cloud version. We plan to + open-source our SDKs in the near future so that the community can request or + contribute integrations as needed. +

+
-
-
- {workspaceIntegrations.map((integration) => ( - + {workspaceIntegrations.map((integration) => ( + + ))} +
+
+ ) : ( +
+ + { + router.push(`/${workspaceSlug}/settings/integrations`); + }} /> - ))} +
- + ) ) : ( -
- - { - router.push(`/${workspaceSlug}/settings/integrations`); - }} - /> - -
- ) - ) : ( - - - - - - - )} + + + + + + + )} +
); }; diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx index ab7f2e0bc..ef88e4912 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx @@ -7,8 +7,6 @@ import useSWR from "swr"; // services import projectService from "services/project.service"; import issuesService from "services/issues.service"; -// lib -import { requiredAdmin } from "lib/auth"; // layouts import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; // components @@ -24,10 +22,11 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // icons import { PlusIcon } from "@heroicons/react/24/outline"; // types -import { IIssueLabels, UserAuth } from "types"; -import type { GetServerSidePropsContext, NextPage } from "next"; +import { IIssueLabels } from "types"; +import type { NextPage } from "next"; // fetch-keys import { PROJECT_DETAILS, PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; +import { SettingsHeader } from "components/project"; const LabelsSettings: NextPage = () => { // create/edit label form @@ -103,39 +102,57 @@ const LabelsSettings: NextPage = () => { } > -
-
-

Labels

-

Manage the labels of this project.

- - - - New label - - -
-
- {labelForm && ( - - )} - <> - {issueLabels ? ( - issueLabels.map((label) => { - const children = issueLabels?.filter((l) => l.parent === label.id); +
+ +
+
+

Labels

+

Manage the labels of this project.

+ + + + New label + + +
+
+ {labelForm && ( + + )} + <> + {issueLabels ? ( + issueLabels.map((label) => { + const children = issueLabels?.filter((l) => l.parent === label.id); - if (children && children.length === 0) { - if (!label.parent) + if (children && children.length === 0) { + if (!label.parent) + return ( + addLabelToGroup(label)} + editLabel={(label) => { + editLabel(label); + scrollToRef.current?.scrollIntoView({ + behavior: "smooth", + }); + }} + handleLabelDelete={handleLabelDelete} + /> + ); + } else return ( - addLabelToGroup(label)} + labelChildren={children} + addLabelToGroup={addLabelToGroup} editLabel={(label) => { editLabel(label); scrollToRef.current?.scrollIntoView({ @@ -145,34 +162,19 @@ const LabelsSettings: NextPage = () => { handleLabelDelete={handleLabelDelete} /> ); - } else - return ( - { - editLabel(label); - scrollToRef.current?.scrollIntoView({ - behavior: "smooth", - }); - }} - handleLabelDelete={handleLabelDelete} - /> - ); - }) - ) : ( - - - - - - - )} - -
-
+ }) + ) : ( + + + + + + + )} + +
+
+
); diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx index d7fb1d824..88d03e538 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx @@ -27,6 +27,7 @@ import type { NextPage } from "next"; import { PROJECT_INVITATIONS, PROJECT_MEMBERS, WORKSPACE_DETAILS } from "constants/fetch-keys"; // constants import { ROLE } from "constants/workspace"; +import { SettingsHeader } from "components/project"; const MembersSettings: NextPage = () => { const [inviteModal, setInviteModal] = useState(false); @@ -141,120 +142,123 @@ const MembersSettings: NextPage = () => { } > -
-
-

Members

- -
- {!projectMembers || !projectInvitations ? ( - - - - - - - ) : ( -
- {members.length > 0 - ? members.map((member) => ( -
-
-
- {member.avatar && member.avatar !== "" ? ( - {member.first_name} - ) : member.first_name !== "" ? ( - member.first_name.charAt(0) - ) : ( - member.email.charAt(0) - )} -
-
-

- {member.first_name} {member.last_name} -

-

{member.email}

-
-
-
- {!member.member && ( -
- Pending -
- )} - { - if (!activeWorkspace || !projectDetails) return; - - projectService - .updateProjectMember( - activeWorkspace.slug, - projectDetails.id, - member.id, - { - role: value, - } - ) - .then((res) => { - setToastAlert({ - type: "success", - message: "Member role updated successfully.", - title: "Success", - }); - mutateMembers( - (prevData: any) => - prevData.map((m: any) => - m.id === member.id ? { ...m, ...res, role: value } : m - ), - false - ); - }) - .catch((err) => { - console.log(err); - }); - }} - position="right" - > - {Object.keys(ROLE).map((key) => ( - - <>{ROLE[parseInt(key) as keyof typeof ROLE]} - - ))} - - - { - if (member.member) setSelectedRemoveMember(member.id); - else setSelectedInviteRemoveMember(member.id); - }} - > - - - Remove member - - - -
-
- )) - : null} +
+ +
+
+

Members

+
- )} -
+ {!projectMembers || !projectInvitations ? ( + + + + + + + ) : ( +
+ {members.length > 0 + ? members.map((member) => ( +
+
+
+ {member.avatar && member.avatar !== "" ? ( + {member.first_name} + ) : member.first_name !== "" ? ( + member.first_name.charAt(0) + ) : ( + member.email.charAt(0) + )} +
+
+

+ {member.first_name} {member.last_name} +

+

{member.email}

+
+
+
+ {!member.member && ( +
+ Pending +
+ )} + { + if (!activeWorkspace || !projectDetails) return; + + projectService + .updateProjectMember( + activeWorkspace.slug, + projectDetails.id, + member.id, + { + role: value, + } + ) + .then((res) => { + setToastAlert({ + type: "success", + message: "Member role updated successfully.", + title: "Success", + }); + mutateMembers( + (prevData: any) => + prevData.map((m: any) => + m.id === member.id ? { ...m, ...res, role: value } : m + ), + false + ); + }) + .catch((err) => { + console.log(err); + }); + }} + position="right" + > + {Object.keys(ROLE).map((key) => ( + + <>{ROLE[parseInt(key) as keyof typeof ROLE]} + + ))} + + + { + if (member.member) setSelectedRemoveMember(member.id); + else setSelectedInviteRemoveMember(member.id); + }} + > + + + Remove member + + + +
+
+ )) + : null} +
+ )} +
+
); diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx index b8040c473..39d83616d 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx @@ -28,6 +28,7 @@ import { getStatesList, orderStateGroups } from "helpers/state.helper"; import type { NextPage } from "next"; // fetch-keys import { STATES_LIST } from "constants/fetch-keys"; +import { SettingsHeader } from "components/project"; const StatesSettings: NextPage = () => { const [activeGroup, setActiveGroup] = useState(null); @@ -66,79 +67,82 @@ const StatesSettings: NextPage = () => { } > -
-
-

States

-

Manage the states of this project.

-
-
- {states && projectDetails ? ( - Object.keys(orderedStateGroups).map((key) => { - if (orderedStateGroups[key].length !== 0) - return ( -
-
-

{key}

- -
-
- {key === activeGroup && ( - { - setActiveGroup(null); - setSelectedState(null); - }} - data={null} - selectedGroup={key as keyof StateGroup} - /> - )} - {orderedStateGroups[key].map((state, index) => - state.id !== selectedState ? ( - setSelectedState(state.id)} - handleDeleteState={() => setSelectDeleteState(state.id)} +
+ +
+
+

States

+

Manage the states of this project.

+
+
+ {states && projectDetails ? ( + Object.keys(orderedStateGroups).map((key) => { + if (orderedStateGroups[key].length !== 0) + return ( +
+
+

{key}

+ +
+
+ {key === activeGroup && ( + { + setActiveGroup(null); + setSelectedState(null); + }} + data={null} + selectedGroup={key as keyof StateGroup} /> - ) : ( -
- { - setActiveGroup(null); - setSelectedState(null); - }} - data={ - statesList?.find((state) => state.id === selectedState) ?? null - } - selectedGroup={key as keyof StateGroup} + )} + {orderedStateGroups[key].map((state, index) => + state.id !== selectedState ? ( + setSelectedState(state.id)} + handleDeleteState={() => setSelectDeleteState(state.id)} /> -
- ) - )} + ) : ( +
+ { + setActiveGroup(null); + setSelectedState(null); + }} + data={ + statesList?.find((state) => state.id === selectedState) ?? null + } + selectedGroup={key as keyof StateGroup} + /> +
+ ) + )} +
-
- ); - }) - ) : ( - - - - - - - )} + ); + }) + ) : ( + + + + + + + )} +
diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx index 06599742b..79e72b85e 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx @@ -97,7 +97,7 @@ const ProjectViews: NextPage = () => { /> {views ? ( views.length > 0 ? ( -
+

Views

{views.map((view) => ( diff --git a/apps/app/pages/[workspaceSlug]/projects/index.tsx b/apps/app/pages/[workspaceSlug]/projects/index.tsx index 9cb78563f..a38c037e5 100644 --- a/apps/app/pages/[workspaceSlug]/projects/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/index.tsx @@ -83,7 +83,7 @@ const ProjectsPage: NextPage = () => { data={projects?.find((item) => item.id === deleteProject) ?? null} /> {projects ? ( - <> +
{projects.length === 0 ? ( { ))}
)} - +
) : ( diff --git a/apps/app/pages/[workspaceSlug]/settings/billing.tsx b/apps/app/pages/[workspaceSlug]/settings/billing.tsx index 8b191316f..0e5cc69f4 100644 --- a/apps/app/pages/[workspaceSlug]/settings/billing.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/billing.tsx @@ -8,6 +8,7 @@ import useSWR from "swr"; import workspaceService from "services/workspace.service"; // layouts import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; +import { SettingsHeader } from "components/workspace"; // ui import { SecondaryButton } from "components/ui"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; @@ -38,33 +39,36 @@ const BillingSettings: NextPage = () => { } > -
-
-

Billing & Plans

-

[Free launch preview] plan Pro

-
-
+
); }; diff --git a/apps/app/pages/[workspaceSlug]/settings/import-export.tsx b/apps/app/pages/[workspaceSlug]/settings/import-export.tsx index 3a8fa340f..821ceee7a 100644 --- a/apps/app/pages/[workspaceSlug]/settings/import-export.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/import-export.tsx @@ -2,6 +2,7 @@ import { useRouter } from "next/router"; // layouts import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; +import { SettingsHeader } from "components/workspace"; // components import IntegrationGuide from "components/integration/guide"; // ui @@ -22,7 +23,10 @@ const ImportExport: NextPage = () => { } > - +
+ + +
); }; diff --git a/apps/app/pages/[workspaceSlug]/settings/index.tsx b/apps/app/pages/[workspaceSlug]/settings/index.tsx index b0558b131..045e9a893 100644 --- a/apps/app/pages/[workspaceSlug]/settings/index.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/index.tsx @@ -14,9 +14,10 @@ import fileService from "services/file.service"; import useToast from "hooks/use-toast"; // layouts import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; +import SettingsNavbar from "layouts/settings-navbar"; // components import { ImageUploadModal } from "components/core"; -import { DeleteWorkspaceModal } from "components/workspace"; +import { DeleteWorkspaceModal, SettingsHeader } from "components/workspace"; // ui import { Spinner, Input, CustomSelect, SecondaryButton, DangerButton } from "components/ui"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; @@ -172,163 +173,167 @@ const WorkspaceSettings: NextPage = () => { }} data={activeWorkspace ?? null} /> - {activeWorkspace ? ( -
-
-
-

Logo

-

- Max file size is 5MB. Supported file types are .jpg and .png. -

-
-
-
- -
- { - setIsImageUploadModalOpen(true); - }} - > - {isImageUploading ? "Uploading..." : "Upload"} - - {activeWorkspace.logo && activeWorkspace.logo !== "" && ( - handleDelete(activeWorkspace.logo)}> - {isImageRemoving ? "Removing..." : "Remove"} - - )} +
+ + {activeWorkspace ? ( +
+
+
+

Logo

+

+ Max file size is 5MB. Supported file types are .jpg and .png. +

+
+
+
+ +
+ { + setIsImageUploadModalOpen(true); + }} + > + {isImageUploading ? "Uploading..." : "Upload"} + + {activeWorkspace.logo && activeWorkspace.logo !== "" && ( + handleDelete(activeWorkspace.logo)}> + {isImageRemoving ? "Removing..." : "Remove"} + + )} +
-
-
-
-

URL

-

Your workspace URL.

+
+
+

URL

+

Your workspace URL.

+
+
+ + + copyTextToClipboard( + `${typeof window !== "undefined" && window.location.origin}/${ + activeWorkspace.slug + }` + ).then(() => { + setToastAlert({ + type: "success", + title: "Link Copied!", + message: "Workspace link copied to clipboard.", + }); + }) + } + outline + > + + +
-
- - - copyTextToClipboard( - `${typeof window !== "undefined" && window.location.origin}/${ - activeWorkspace.slug - }` - ).then(() => { - setToastAlert({ - type: "success", - title: "Link Copied!", - message: "Workspace link copied to clipboard.", - }); - }) - } - outline - > - +
+
+

Name

+

Give a name to your workspace.

+
+
+ +
+
+
+
+

Company Size

+

How big is your company?

+
+
+ ( + + {COMPANY_SIZE?.map((item) => ( + + {item.label} + + ))} + + )} + /> +
+
+
+ + {isSubmitting ? "Updating..." : "Update Workspace"}
-
-
-
-

Name

-

Give a name to your workspace.

-
-
- +
+
+

Danger Zone

+

+ The danger zone of the workspace delete page is a critical area that requires + careful consideration and attention. When deleting a workspace, all of the data + and resources within that workspace will be permanently removed and cannot be + recovered. +

+
+
+ setIsOpen(true)} outline> + Delete the workspace + +
-
-
-

Company Size

-

How big is your company?

-
-
- ( - - {COMPANY_SIZE?.map((item) => ( - - {item.label} - - ))} - - )} - /> -
+ ) : ( +
+
-
- - {isSubmitting ? "Updating..." : "Update Workspace"} - -
-
-
-

Danger Zone

-

- The danger zone of the workspace delete page is a critical area that requires - careful consideration and attention. When deleting a workspace, all of the data and - resources within that workspace will be permanently removed and cannot be recovered. -

-
-
- setIsOpen(true)} outline> - Delete the workspace - -
-
-
- ) : ( -
- -
- )} + )} +
); }; diff --git a/apps/app/pages/[workspaceSlug]/settings/integrations.tsx b/apps/app/pages/[workspaceSlug]/settings/integrations.tsx index 915a2af98..de3aa1533 100644 --- a/apps/app/pages/[workspaceSlug]/settings/integrations.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/integrations.tsx @@ -9,6 +9,7 @@ import workspaceService from "services/workspace.service"; import IntegrationService from "services/integration"; // layouts import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; +import { SettingsHeader } from "components/workspace"; // components import { SingleIntegrationCard } from "components/integration"; // ui @@ -46,31 +47,34 @@ const WorkspaceIntegrations: NextPage = () => { } > -
-
-

Integrations

-
- -

- Integrations and importers are only available on the cloud version. We plan to - open-source our SDKs in the near future so that the community can request or - contribute integrations as needed. -

+
+ +
+
+

Integrations

+
+ +

+ Integrations and importers are only available on the cloud version. We plan to + open-source our SDKs in the near future so that the community can request or + contribute integrations as needed. +

+
-
-
- {appIntegrations ? ( - appIntegrations.map((integration) => ( - - )) - ) : ( - - - - - )} -
-
+
+ {appIntegrations ? ( + appIntegrations.map((integration) => ( + + )) + ) : ( + + + + + )} +
+ +
); }; diff --git a/apps/app/pages/[workspaceSlug]/settings/members.tsx b/apps/app/pages/[workspaceSlug]/settings/members.tsx index 98d4eaaf2..05ae6249d 100644 --- a/apps/app/pages/[workspaceSlug]/settings/members.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/members.tsx @@ -11,6 +11,7 @@ import useToast from "hooks/use-toast"; import workspaceService from "services/workspace.service"; // layouts import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; +import { SettingsHeader } from "components/workspace"; // components import ConfirmWorkspaceMemberRemove from "components/workspace/confirm-workspace-member-remove"; import SendWorkspaceInvitationModal from "components/workspace/send-workspace-invitation-modal"; @@ -137,117 +138,120 @@ const MembersSettings: NextPage = () => { } > -
-
-

Members

- -
- {!workspaceMembers || !workspaceInvitations ? ( - - - - - - - ) : ( -
- {members.length > 0 - ? members.map((member) => ( -
-
-
- {member.avatar && member.avatar !== "" ? ( - {member.first_name} - ) : member.first_name !== "" ? ( - member.first_name.charAt(0) - ) : ( - member.email.charAt(0) - )} -
-
-

- {member.first_name} {member.last_name} -

-

{member.email}

-
-
-
- {!member?.status && ( -
-

Pending

-
- )} - { - workspaceService - .updateWorkspaceMember(activeWorkspace?.slug as string, member.id, { - role: value, - }) - .then(() => { - mutateMembers( - (prevData) => - prevData?.map((m) => - m.id === member.id ? { ...m, role: value } : m - ), - false - ); - setToastAlert({ - title: "Success", - type: "success", - message: "Member role updated successfully.", - }); - }) - .catch(() => { - setToastAlert({ - title: "Error", - type: "error", - message: "An error occurred while updating member role.", - }); - }); - }} - position="right" - > - {Object.keys(ROLE).map((key) => ( - - <>{ROLE[parseInt(key) as keyof typeof ROLE]} - - ))} - - - { - if (member.member) { - setSelectedRemoveMember(member.id); - } else { - setSelectedInviteRemoveMember(member.id); - } - }} - > - Remove member - - -
-
- )) - : null} +
+ +
+
+

Members

+
- )} -
+ {!workspaceMembers || !workspaceInvitations ? ( + + + + + + + ) : ( +
+ {members.length > 0 + ? members.map((member) => ( +
+
+
+ {member.avatar && member.avatar !== "" ? ( + {member.first_name} + ) : member.first_name !== "" ? ( + member.first_name.charAt(0) + ) : ( + member.email.charAt(0) + )} +
+
+

+ {member.first_name} {member.last_name} +

+

{member.email}

+
+
+
+ {!member?.status && ( +
+

Pending

+
+ )} + { + workspaceService + .updateWorkspaceMember(activeWorkspace?.slug as string, member.id, { + role: value, + }) + .then(() => { + mutateMembers( + (prevData) => + prevData?.map((m) => + m.id === member.id ? { ...m, role: value } : m + ), + false + ); + setToastAlert({ + title: "Success", + type: "success", + message: "Member role updated successfully.", + }); + }) + .catch(() => { + setToastAlert({ + title: "Error", + type: "error", + message: "An error occurred while updating member role.", + }); + }); + }} + position="right" + > + {Object.keys(ROLE).map((key) => ( + + <>{ROLE[parseInt(key) as keyof typeof ROLE]} + + ))} + + + { + if (member.member) { + setSelectedRemoveMember(member.id); + } else { + setSelectedInviteRemoveMember(member.id); + } + }} + > + Remove member + + +
+
+ )) + : null} +
+ )} +
+
); diff --git a/apps/app/styles/globals.css b/apps/app/styles/globals.css index 8ca808544..4bdd47dbe 100644 --- a/apps/app/styles/globals.css +++ b/apps/app/styles/globals.css @@ -112,6 +112,7 @@ body { .horizontal-scroll-enable::-webkit-scrollbar { display: block; height: 7px; + width: 0; } .horizontal-scroll-enable::-webkit-scrollbar-track { diff --git a/apps/app/tailwind.config.js b/apps/app/tailwind.config.js index 9dde3bb74..3e2a2da4b 100644 --- a/apps/app/tailwind.config.js +++ b/apps/app/tailwind.config.js @@ -12,10 +12,6 @@ module.exports = { theme: { extend: { colors: { - theme: "#3f76ff", - "hover-gray": "#f5f5f5", - primary: "#f9fafb", // gray-50 - secondary: "white", brand: { accent: withOpacity("--color-accent"), base: withOpacity("--color-bg-base"), From a69593a9e8432f595a59cbbd3c07101eae5cb0e9 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Fri, 5 May 2023 18:01:58 +0530 Subject: [PATCH 17/87] fix: handled token expiry validation (#1016) --- apps/app/services/api.service.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/app/services/api.service.ts b/apps/app/services/api.service.ts index a625c0b37..958205005 100644 --- a/apps/app/services/api.service.ts +++ b/apps/app/services/api.service.ts @@ -1,6 +1,21 @@ import axios from "axios"; import Cookies from "js-cookie"; +const unAuthorizedStatus = [401]; +axios.interceptors.response.use( + (response) => response, + (error) => { + const { status }: any = error.response; + if (unAuthorizedStatus.includes(status)) { + Cookies.remove("refreshToken", { path: "/" }); + Cookies.remove("accessToken", { path: "/" }); + console.log("window.location.href", window.location.pathname); + if (window.location.pathname != "/signin") window.location.href = "/signin"; + } + return Promise.reject(error); + } +); + abstract class APIService { protected baseURL: string; protected headers: any = {}; From 4884ecd6689d196d7004c698b72ed3c73185ba1f Mon Sep 17 00:00:00 2001 From: vamsi Date: Fri, 5 May 2023 19:48:38 +0530 Subject: [PATCH 18/87] dev: migrations for estimate point values --- .../0030_alter_estimatepoint_unique_together.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 apiserver/plane/db/migrations/0030_alter_estimatepoint_unique_together.py diff --git a/apiserver/plane/db/migrations/0030_alter_estimatepoint_unique_together.py b/apiserver/plane/db/migrations/0030_alter_estimatepoint_unique_together.py new file mode 100644 index 000000000..bfc1da530 --- /dev/null +++ b/apiserver/plane/db/migrations/0030_alter_estimatepoint_unique_together.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.18 on 2023-05-05 14:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0029_auto_20230502_0126'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='estimatepoint', + unique_together=set(), + ), + ] From df96d40cfa71f62df37b766e5884ef701fc67112 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Thu, 11 May 2023 02:15:39 +0530 Subject: [PATCH 19/87] fix: views issues mutation, sidebar link highlight (#1025) * fix: views issues mutation, sidebar link highlight * fix: show only specific states when type filter is set * fix: delete comment mutation * style: bulk delete issues modal * fix: project settings features mutation --- .../core/board-view/single-board.tsx | 2 +- .../core/bulk-delete-issues-modal.tsx | 12 ++-- .../{filter-list.tsx => filters-list.tsx} | 0 .../components/core/image-upload-modal.tsx | 19 +++--- apps/app/components/core/index.ts | 2 +- .../components/core/list-view/single-list.tsx | 5 +- .../integration/jira/give-details.tsx | 1 + apps/app/components/issues/activity.tsx | 3 + apps/app/components/issues/sidebar.tsx | 5 +- .../app/components/issues/sub-issues-list.tsx | 2 +- .../components/pages/single-page-block.tsx | 8 +-- apps/app/components/project/sidebar-list.tsx | 4 +- apps/app/components/ui/custom-menu.tsx | 12 ++-- .../components/ui/custom-search-select.tsx | 6 +- apps/app/components/ui/custom-select.tsx | 4 ++ apps/app/components/ui/empty-space.tsx | 11 ++-- .../app/components/workspace/sidebar-menu.tsx | 6 +- apps/app/constants/fetch-keys.ts | 10 ++- apps/app/helpers/array.helper.ts | 2 + apps/app/hooks/use-issues-view.tsx | 33 +++++++++- apps/app/hooks/use-projects.tsx | 8 ++- apps/app/layouts/settings-navbar.tsx | 8 ++- .../projects/[projectId]/issues/[issueId].tsx | 2 +- .../projects/[projectId]/pages/[pageId].tsx | 2 +- .../[projectId]/settings/features.tsx | 62 ++++++++++++++----- .../projects/[projectId]/settings/index.tsx | 2 +- .../projects/[projectId]/views/[viewId].tsx | 2 +- .../projects/[projectId]/views/index.tsx | 2 +- .../pages/[workspaceSlug]/projects/index.tsx | 4 +- 29 files changed, 165 insertions(+), 74 deletions(-) rename apps/app/components/core/{filter-list.tsx => filters-list.tsx} (100%) diff --git a/apps/app/components/core/board-view/single-board.tsx b/apps/app/components/core/board-view/single-board.tsx index 76226f0cf..5d33b15bf 100644 --- a/apps/app/components/core/board-view/single-board.tsx +++ b/apps/app/components/core/board-view/single-board.tsx @@ -167,7 +167,7 @@ export const SingleBoard: React.FC = ({ Add Issue } - optionsPosition="left" + position="left" noBorder > diff --git a/apps/app/components/core/bulk-delete-issues-modal.tsx b/apps/app/components/core/bulk-delete-issues-modal.tsx index bd5e18e57..7fe2181f6 100644 --- a/apps/app/components/core/bulk-delete-issues-modal.tsx +++ b/apps/app/components/core/bulk-delete-issues-modal.tsx @@ -121,7 +121,7 @@ export const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen }) => leaveFrom="opacity-100 scale-100" leaveTo="opacity-0 scale-95" > - + { @@ -149,7 +149,7 @@ export const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen }) => {filteredIssues.length > 0 ? (
  • @@ -158,15 +158,15 @@ export const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen }) => Select issues to delete
  • )} -