Merge branch 'develop' of https://github.com/makeplane/plane into chore/event-improvements

This commit is contained in:
LAKHAN BAHETI 2024-03-06 13:42:59 +05:30
commit cd8de9b46a
28 changed files with 386 additions and 154 deletions

View File

@ -1,27 +1,19 @@
name: Build Pull Request Contents name: Build and Lint on Pull Request
on: on:
workflow_dispatch:
pull_request: pull_request:
types: ["opened", "synchronize"] types: ["opened", "synchronize"]
jobs: jobs:
build-pull-request-contents: get-changed-files:
name: Build Pull Request Contents runs-on: ubuntu-latest
runs-on: ubuntu-20.04 outputs:
permissions: apiserver_changed: ${{ steps.changed-files.outputs.apiserver_any_changed }}
pull-requests: read web_changed: ${{ steps.changed-files.outputs.web_any_changed }}
space_changed: ${{ steps.changed-files.outputs.deploy_any_changed }}
steps: steps:
- name: Checkout Repository to Actions - uses: actions/checkout@v3
uses: actions/checkout@v3.3.0
with:
token: ${{ secrets.ACCESS_TOKEN }}
- name: Setup Node.js 18.x
uses: actions/setup-node@v2
with:
node-version: 18.x
- name: Get changed files - name: Get changed files
id: changed-files id: changed-files
uses: tj-actions/changed-files@v41 uses: tj-actions/changed-files@v41
@ -31,17 +23,82 @@ jobs:
- apiserver/** - apiserver/**
web: web:
- web/** - web/**
- packages/**
- 'package.json'
- 'yarn.lock'
- 'tsconfig.json'
- 'turbo.json'
deploy: deploy:
- space/** - space/**
- packages/**
- 'package.json'
- 'yarn.lock'
- 'tsconfig.json'
- 'turbo.json'
- name: Build Plane's Main App lint-apiserver:
if: steps.changed-files.outputs.web_any_changed == 'true' needs: get-changed-files
run: | runs-on: ubuntu-latest
yarn if: needs.get-changed-files.outputs.apiserver_changed == 'true'
yarn build --filter=web steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x' # Specify the Python version you need
- name: Install Pylint
run: python -m pip install ruff
- name: Install Apiserver Dependencies
run: cd apiserver && pip install -r requirements.txt
- name: Lint apiserver
run: ruff check --fix apiserver
- name: Build Plane's Deploy App lint-web:
if: steps.changed-files.outputs.deploy_any_changed == 'true' needs: get-changed-files
run: | if: needs.get-changed-files.outputs.web_changed == 'true'
yarn runs-on: ubuntu-latest
yarn build --filter=space steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 18.x
- run: yarn install
- run: yarn lint --filter=web
lint-space:
needs: get-changed-files
if: needs.get-changed-files.outputs.space_changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 18.x
- run: yarn install
- run: yarn lint --filter=space
build-web:
needs: lint-web
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 18.x
- run: yarn install
- run: yarn build --filter=web
build-space:
needs: lint-space
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 18.x
- run: yarn install
- run: yarn build --filter=space

45
.github/workflows/check-version.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Version Change Before Release
on:
pull_request:
branches:
- master
jobs:
check-version:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Get PR Branch version
run: echo "PR_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV
- name: Fetch base branch
run: git fetch origin master:master
- name: Get Master Branch version
run: |
git checkout master
echo "MASTER_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV
- name: Get master branch version and compare
run: |
echo "Comparing versions: PR version is $PR_VERSION, Master version is $MASTER_VERSION"
if [ "$PR_VERSION" == "$MASTER_VERSION" ]; then
echo "Version in PR branch is the same as in master. Failing the CI."
exit 1
else
echo "Version check passed. Versions are different."
fi
env:
PR_VERSION: ${{ env.PR_VERSION }}
MASTER_VERSION: ${{ env.MASTER_VERSION }}

View File

@ -0,0 +1,73 @@
name: Feature Preview
on:
workflow_dispatch:
inputs:
web-build:
required: true
type: boolean
default: true
space-build:
required: true
type: boolean
default: false
jobs:
feature-deploy:
name: Feature Deploy
runs-on: ubuntu-latest
env:
KUBE_CONFIG_FILE: ${{ secrets.KUBE_CONFIG }}
BUILD_WEB: ${{ (github.event.inputs.web-build == '' && true) || github.event.inputs.web-build }}
BUILD_SPACE: ${{ (github.event.inputs.space-build == '' && false) || github.event.inputs.space-build }}
steps:
- name: Tailscale
uses: tailscale/github-action@v2
with:
oauth-client-id: ${{ secrets.TAILSCALE_OAUTH_CLIENT_ID }}
oauth-secret: ${{ secrets.TAILSCALE_OAUTH_SECRET }}
tags: tag:ci
- name: Kubectl Setup
run: |
curl -LO "https://dl.k8s.io/release/${{secrets.KUBE_VERSION}}/bin/linux/amd64/kubectl"
chmod +x kubectl
mkdir -p ~/.kube
echo "$KUBE_CONFIG_FILE" > ~/.kube/config
chmod 600 ~/.kube/config
- name: HELM Setup
run: |
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh
- name: App Deploy
run: |
helm --kube-insecure-skip-tls-verify repo add feature-preview ${{ secrets.FEATURE_PREVIEW_HELM_CHART_URL }}
GIT_BRANCH=${{ github.ref_name }}
APP_NAMESPACE=${{ secrets.FEATURE_PREVIEW_NAMESPACE }}
METADATA=$(helm install feature-preview/${{ secrets.FEATURE_PREVIEW_HELM_CHART_NAME }} \
--kube-insecure-skip-tls-verify \
--generate-name \
--namespace $APP_NAMESPACE \
--set shared_config.git_repo=${{ github.repositoryUrl }} \
--set shared_config.git_branch="$GIT_BRANCH" \
--set web.enabled=${{ env.BUILD_WEB }} \
--set space.enabled=${{ env.BUILD_SPACE }} \
--output json \
--timeout 1000s)
APP_NAME=$(echo $METADATA | jq -r '.name')
INGRESS_HOSTNAME=$(kubectl get ingress -n feature-builds --insecure-skip-tls-verify \
-o jsonpath='{.items[?(@.metadata.annotations.meta\.helm\.sh\/release-name=="'$APP_NAME'")]}' | \
jq -r '.spec.rules[0].host')
echo "****************************************"
echo "APP NAME ::: $APP_NAME"
echo "INGRESS HOSTNAME ::: $INGRESS_HOSTNAME"
echo "****************************************"

151
README.md
View File

@ -7,7 +7,7 @@
</p> </p>
<h3 align="center"><b>Plane</b></h3> <h3 align="center"><b>Plane</b></h3>
<p align="center"><b>Flexible, extensible open-source project management</b></p> <p align="center"><b>Open-source project management that unlocks customer value.</b></p>
<p align="center"> <p align="center">
<a href="https://discord.com/invite/A92xrEGCge"> <a href="https://discord.com/invite/A92xrEGCge">
@ -16,6 +16,13 @@
<img alt="Commit activity per month" src="https://img.shields.io/github/commit-activity/m/makeplane/plane?style=for-the-badge" /> <img alt="Commit activity per month" src="https://img.shields.io/github/commit-activity/m/makeplane/plane?style=for-the-badge" />
</p> </p>
<p align="center">
<a href="http://www.plane.so"><b>Website</b></a>
<a href="https://github.com/makeplane/plane/releases"><b>Releases</b></a>
<a href="https://twitter.com/planepowers"><b>Twitter</b></a>
<a href="https://docs.plane.so/"><b>Documentation</b></a>
</p>
<p> <p>
<a href="https://app.plane.so/#gh-light-mode-only" target="_blank"> <a href="https://app.plane.so/#gh-light-mode-only" target="_blank">
<img <img
@ -33,56 +40,93 @@
</a> </a>
</p> </p>
Meet [Plane](https://plane.so). 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. > Plane is still in its early days, not everything will be perfect yet, and hiccups may happen. Please let us know of any suggestions, ideas, or bugs that you encounter on our [Discord](https://discord.com/invite/A92xrEGCge) or GitHub issues, and we will use your feedback to improve on our upcoming releases.
The easiest way to get started with Plane is by creating a [Plane Cloud](https://app.plane.so) account. Plane Cloud offers a hosted solution for Plane. If you prefer to self-host Plane, please refer to our [deployment documentation](https://docs.plane.so/docker-compose).
## ⚡️ Contributors Quick Start
### Prerequisite ## ⚡ Installation
Development system must have docker engine installed and running. The easiest way to get started with Plane is by creating a [Plane Cloud](https://app.plane.so) account where we offer a hosted solution for users.
### Steps If you want more control over your data prefer to self-host Plane, please refer to our [deployment documentation](https://docs.plane.so/docker-compose).
Setting up local environment is extremely easy and straight forward. Follow the below step and you will be ready to contribute | Installation Methods | Documentation Link |
|-----------------|----------------------------------------------------------------------------------------------------------|
| Docker | [![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white)](https://docs.plane.so/docker-compose) |
| Kubernetes | [![Kubernetes](https://img.shields.io/badge/kubernetes-%23326ce5.svg?style=for-the-badge&logo=kubernetes&logoColor=white)](https://docs.plane.so/kubernetes) |
1. Clone the code locally using `git clone https://github.com/makeplane/plane.git` `Instance admin` can configure instance settings using our [God-mode](https://docs.plane.so/instance-admin) feature.
1. Switch to the code folder `cd plane`
1. Create your feature or fix branch you plan to work on using `git checkout -b <feature-branch-name>`
1. Open terminal and run `./setup.sh`
1. Open the code on VSCode or similar equivalent IDE
1. Review the `.env` files available in various folders. Visit [Environment Setup](./ENV_SETUP.md) to know about various environment variables used in system
1. Run the docker command to initiate various services `docker compose -f docker-compose-local.yml up -d`
You are ready to make changes to the code. Do not forget to refresh the browser (in case id does not auto-reload)
Thats it!
## 🍙 Self Hosting
For self hosting environment setup, visit the [Self Hosting](https://docs.plane.so/docker-compose) documentation page
## 🚀 Features ## 🚀 Features
- **Issue Planning and Tracking**: Quickly create issues and add details using a powerful rich text editor that supports file uploads. Add sub-properties and references to issues for better organization and tracking. - **Issues**: Quickly create issues and add details using a powerful, rich text editor that supports file uploads. Add sub-properties and references to problems for better organization and tracking.
- **Issue Attachments**: Collaborate effectively by attaching files to issues, making it easy for your team to find and share important project-related documents.
- **Layouts**: Customize your project view with your preferred layout - choose from List, Kanban, or Calendar to visualize your project in a way that makes sense to you. - **Cycles**
- **Cycles**: Plan sprints with Cycles to keep your team on track and productive. Gain insights into your project's progress with burn-down charts and other useful features. Keep up your team's momentum with Cycles. Gain insights into your project's progress with burn-down charts and other valuable features.
- **Modules**: Break down your large projects into smaller, more manageable modules. Assign modules between teams to easily track and plan your project's progress.
- **Modules**: Break down your large projects into smaller, more manageable modules. Assign modules between teams to track and plan your project's progress easily.
- **Views**: Create custom filters to display only the issues that matter to you. Save and share your filters in just a few clicks. - **Views**: Create custom filters to display only the issues that matter to you. Save and share your filters in just a few clicks.
- **Pages**: Plane pages function as an AI-powered notepad, allowing you to easily document issues, cycle plans, and module details, and then synchronize them with your issues.
- **Command K**: Enjoy a better user experience with the new Command + K menu. Easily manage and navigate through your projects from one convenient location. - **Pages**: Plane pages, equipped with AI and a rich text editor, let you jot down your thoughts on the fly. Format your text, upload images, hyperlink, or sync your existing ideas into an actionable item or issue.
- **GitHub Sync**: Streamline your planning process by syncing your GitHub issues with Plane. Keep all your issues in one place for better tracking and collaboration.
- **Analytics**: Get insights into all your Plane data in real-time. Visualize issue data to spot trends, remove blockers, and progress your work.
- **Drive** (*coming soon*): The drive helps you share documents, images, videos, or any other files that make sense to you or your team and align on the problem/solution.
## 🛠️ Contributors Quick Start
> Development system must have docker engine installed and running.
Setting up local environment is extremely easy and straight forward. Follow the below step and you will be ready to contribute
1. Clone the code locally using:
```
git clone https://github.com/makeplane/plane.git
```
2. Switch to the code folder:
```
cd plane
```
3. Create your feature or fix branch you plan to work on using:
```
git checkout -b <feature-branch-name>
```
4. Open terminal and run:
```
./setup.sh
```
5. Open the code on VSCode or similar equivalent IDE.
6. Review the `.env` files available in various folders.
Visit [Environment Setup](./ENV_SETUP.md) to know about various environment variables used in system.
7. Run the docker command to initiate services:
```
docker compose -f docker-compose-local.yml up -d
```
You are ready to make changes to the code. Do not forget to refresh the browser (in case it does not auto-reload).
Thats it!
## ❤️ Community
The Plane community can be found on [GitHub Discussions](https://github.com/orgs/makeplane/discussions), and our [Discord server](https://discord.com/invite/A92xrEGCge). Our [Code of conduct](https://github.com/makeplane/plane/blob/master/CODE_OF_CONDUCT.md) applies to all Plane community chanels.
Ask questions, report bugs, join discussions, voice ideas, make feature requests, or share your projects.
### Repo Activity
![Plane Repo Activity](https://repobeats.axiom.co/api/embed/2523c6ed2f77c082b7908c33e2ab208981d76c39.svg "Repobeats analytics image")
## 📸 Screenshots ## 📸 Screenshots
<p> <p>
<a href="https://plane.so" target="_blank"> <a href="https://plane.so" target="_blank">
<img <img
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_views_dark_mode.webp" src="https://ik.imagekit.io/w2okwbtu2/Issues_rNZjrGgFl.png?updatedAt=1709298765880"
alt="Plane Views" alt="Plane Views"
width="100%" width="100%"
/> />
@ -91,8 +135,7 @@ For self hosting environment setup, visit the [Self Hosting](https://docs.plane.
<p> <p>
<a href="https://plane.so" target="_blank"> <a href="https://plane.so" target="_blank">
<img <img
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_issue_detail_dark_mode.webp" src="https://ik.imagekit.io/w2okwbtu2/Cycles_jCDhqmTl9.png?updatedAt=1709298780697"
alt="Plane Issue Details"
width="100%" width="100%"
/> />
</a> </a>
@ -100,7 +143,7 @@ For self hosting environment setup, visit the [Self Hosting](https://docs.plane.
<p> <p>
<a href="https://plane.so" target="_blank"> <a href="https://plane.so" target="_blank">
<img <img
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_cycles_modules_dark_mode.webp" src="https://ik.imagekit.io/w2okwbtu2/Modules_PSCVsbSfI.png?updatedAt=1709298796783"
alt="Plane Cycles and Modules" alt="Plane Cycles and Modules"
width="100%" width="100%"
/> />
@ -109,7 +152,7 @@ For self hosting environment setup, visit the [Self Hosting](https://docs.plane.
<p> <p>
<a href="https://plane.so" target="_blank"> <a href="https://plane.so" target="_blank">
<img <img
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_analytics_dark_mode.webp" src="https://ik.imagekit.io/w2okwbtu2/Views_uxXsRatS4.png?updatedAt=1709298834522"
alt="Plane Analytics" alt="Plane Analytics"
width="100%" width="100%"
/> />
@ -118,7 +161,7 @@ For self hosting environment setup, visit the [Self Hosting](https://docs.plane.
<p> <p>
<a href="https://plane.so" target="_blank"> <a href="https://plane.so" target="_blank">
<img <img
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_pages_dark_mode.webp" src="https://ik.imagekit.io/w2okwbtu2/Analytics_0o22gLRtp.png?updatedAt=1709298834389"
alt="Plane Pages" alt="Plane Pages"
width="100%" width="100%"
/> />
@ -128,7 +171,7 @@ For self hosting environment setup, visit the [Self Hosting](https://docs.plane.
<p> <p>
<a href="https://plane.so" target="_blank"> <a href="https://plane.so" target="_blank">
<img <img
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_commad_k_dark_mode.webp" src="https://ik.imagekit.io/w2okwbtu2/Drive_LlfeY4xn3.png?updatedAt=1709298837917"
alt="Plane Command Menu" alt="Plane Command Menu"
width="100%" width="100%"
/> />
@ -136,20 +179,22 @@ For self hosting environment setup, visit the [Self Hosting](https://docs.plane.
</p> </p>
</p> </p>
## 📚Documentation
For full documentation, visit [docs.plane.so](https://docs.plane.so/)
To see how to Contribute, visit [here](https://github.com/makeplane/plane/blob/master/CONTRIBUTING.md).
## ❤️ Community
The Plane community can be found on GitHub Discussions, where you can ask questions, voice ideas, and share your projects.
To chat with other community members you can join the [Plane Discord](https://discord.com/invite/A92xrEGCge).
Our [Code of Conduct](https://github.com/makeplane/plane/blob/master/CODE_OF_CONDUCT.md) applies to all Plane community channels.
## ⛓️ Security ## ⛓️ Security
If you believe you have found a security vulnerability in Plane, we encourage you to responsibly disclose this and not open a public issue. We will investigate all legitimate reports. Email engineering@plane.so to disclose any security vulnerabilities. If you believe you have found a security vulnerability in Plane, we encourage you to responsibly disclose this and not open a public issue. We will investigate all legitimate reports.
Email squawk@plane.so to disclose any security vulnerabilities.
## ❤️ Contribute
There are many ways to contribute to Plane, including:
- Submitting [bugs](https://github.com/makeplane/plane/issues/new?assignees=srinivaspendem%2Cpushya22&labels=%F0%9F%90%9Bbug&projects=&template=--bug-report.yaml&title=%5Bbug%5D%3A+) and [feature requests](https://github.com/makeplane/plane/issues/new?assignees=srinivaspendem%2Cpushya22&labels=%E2%9C%A8feature&projects=&template=--feature-request.yaml&title=%5Bfeature%5D%3A+) for various components.
- Reviewing [the documentation](https://docs.plane.so/) and submitting [pull requests](https://github.com/makeplane/plane), from fixing typos to adding new features.
- Speaking or writing about Plane or any other ecosystem integration and [letting us know](https://discord.com/invite/A92xrEGCge)!
- Upvoting [popular feature requests](https://github.com/makeplane/plane/issues) to show your support.
### We couldn't have done this without you.
<a href="https://github.com/makeplane/plane/graphs/contributors">
<img src="https://contrib.rocks/image?repo=makeplane/plane" />
</a>

View File

@ -716,9 +716,9 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
# Validation check if the issue already exists # Validation check if the issue already exists
if ( if (
str(request.data.get("external_id")) request.data.get("external_id")
and (issue_comment.external_id != str(request.data.get("external_id"))) and (issue_comment.external_id != str(request.data.get("external_id")))
and Issue.objects.filter( and IssueComment.objects.filter(
project_id=project_id, project_id=project_id,
workspace__slug=slug, workspace__slug=slug,
external_source=request.data.get( external_source=request.data.get(

View File

@ -40,9 +40,11 @@ export const LinkEditView = ({
const [positionRef, setPositionRef] = useState({ from: from, to: to }); const [positionRef, setPositionRef] = useState({ from: from, to: to });
const [localUrl, setLocalUrl] = useState(viewProps.url); const [localUrl, setLocalUrl] = useState(viewProps.url);
const linkRemoved = useRef<Boolean>(); const linkRemoved = useRef<boolean>();
const getText = (from: number, to: number) => { const getText = (from: number, to: number) => {
if (to >= editor.state.doc.content.size) return "";
const text = editor.state.doc.textBetween(from, to, "\n"); const text = editor.state.doc.textBetween(from, to, "\n");
return text; return text;
}; };
@ -72,10 +74,12 @@ export const LinkEditView = ({
const url = isValidUrl(localUrl) ? localUrl : viewProps.url; const url = isValidUrl(localUrl) ? localUrl : viewProps.url;
if (to >= editor.state.doc.content.size) return;
editor.view.dispatch(editor.state.tr.removeMark(from, to, editor.schema.marks.link)); editor.view.dispatch(editor.state.tr.removeMark(from, to, editor.schema.marks.link));
editor.view.dispatch(editor.state.tr.addMark(from, to, editor.schema.marks.link.create({ href: url }))); editor.view.dispatch(editor.state.tr.addMark(from, to, editor.schema.marks.link.create({ href: url })));
}, },
[localUrl] [localUrl, editor, from, to, viewProps.url]
); );
const handleUpdateText = (text: string) => { const handleUpdateText = (text: string) => {

View File

@ -27,6 +27,7 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
noBorder = false, noBorder = false,
noChevron = false, noChevron = false,
optionsClassName = "", optionsClassName = "",
menuItemsClassName = "",
verticalEllipsis = false, verticalEllipsis = false,
portalElement, portalElement,
menuButtonOnClick, menuButtonOnClick,
@ -70,7 +71,7 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
useOutsideClickDetector(dropdownRef, closeDropdown); useOutsideClickDetector(dropdownRef, closeDropdown);
let menuItems = ( let menuItems = (
<Menu.Items className="fixed z-10" static> <Menu.Items className={cn("fixed z-10", menuItemsClassName)} static>
<div <div
className={cn( className={cn(
"my-1 overflow-y-scroll rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 text-xs shadow-custom-shadow-rg focus:outline-none min-w-[12rem] whitespace-nowrap", "my-1 overflow-y-scroll rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 text-xs shadow-custom-shadow-rg focus:outline-none min-w-[12rem] whitespace-nowrap",

View File

@ -24,6 +24,7 @@ export interface ICustomMenuDropdownProps extends IDropdownProps {
noBorder?: boolean; noBorder?: boolean;
verticalEllipsis?: boolean; verticalEllipsis?: boolean;
menuButtonOnClick?: (...args: any) => void; menuButtonOnClick?: (...args: any) => void;
menuItemsClassName?: string;
onMenuClose?: () => void; onMenuClose?: () => void;
closeOnSelect?: boolean; closeOnSelect?: boolean;
portalElement?: Element | null; portalElement?: Element | null;

View File

@ -58,7 +58,7 @@ const BorderButton = (props: ButtonProps) => {
high: "bg-orange-500/20 text-orange-950 border-orange-500", high: "bg-orange-500/20 text-orange-950 border-orange-500",
medium: "bg-yellow-500/20 text-yellow-950 border-yellow-500", medium: "bg-yellow-500/20 text-yellow-950 border-yellow-500",
low: "bg-custom-primary-100/20 text-custom-primary-950 border-custom-primary-100", low: "bg-custom-primary-100/20 text-custom-primary-950 border-custom-primary-100",
none: "bg-custom-background-80 border-custom-border-300", none: "hover:bg-custom-background-80 border-custom-border-300",
}; };
return ( return (
@ -197,7 +197,7 @@ const TransparentButton = (props: ButtonProps) => {
high: "text-orange-950", high: "text-orange-950",
medium: "text-yellow-950", medium: "text-yellow-950",
low: "text-blue-950", low: "text-blue-950",
none: "", none: "hover:text-custom-text-300",
}; };
return ( return (

View File

@ -131,6 +131,8 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
const handleInboxIssueNavigation = useCallback( const handleInboxIssueNavigation = useCallback(
(direction: "next" | "prev") => { (direction: "next" | "prev") => {
if (!inboxIssues || !inboxIssueId) return; if (!inboxIssues || !inboxIssueId) return;
const activeElement = document.activeElement as HTMLElement;
if (activeElement && (activeElement.classList.contains("tiptap") || activeElement.id === "title-input")) return;
const nextIssueIndex = const nextIssueIndex =
direction === "next" direction === "next"
? (currentIssueIndex + 1) % inboxIssues.length ? (currentIssueIndex + 1) % inboxIssues.length

View File

@ -50,7 +50,7 @@ export const IssueCycleSelect: React.FC<TIssueCycleSelect> = observer((props) =>
buttonVariant="transparent-with-text" buttonVariant="transparent-with-text"
className="w-full group" className="w-full group"
buttonContainerClassName="w-full text-left" buttonContainerClassName="w-full text-left"
buttonClassName={`text-sm ${issue?.cycle_id ? "" : "text-custom-text-400"}`} buttonClassName={`text-sm justify-between ${issue?.cycle_id ? "" : "text-custom-text-400"}`}
placeholder="No cycle" placeholder="No cycle"
hideIcon hideIcon
dropdownArrow dropdownArrow

View File

@ -74,7 +74,7 @@ export const IssueCommentCreate: FC<TIssueCommentCreate> = (props) => {
return ( return (
<div <div
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey && !isEmpty) { if (e.key === "Enter" && !e.shiftKey && !isEmpty && !isSubmitting) {
handleSubmit(onSubmit)(e); handleSubmit(onSubmit)(e);
} }
}} }}

View File

@ -291,27 +291,29 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
</Droppable> </Droppable>
</div> </div>
<KanBanView <div className="w-max h-max">
issuesMap={issueMap} <KanBanView
issueIds={issueIds} issuesMap={issueMap}
displayProperties={displayProperties} issueIds={issueIds}
sub_group_by={sub_group_by} displayProperties={displayProperties}
group_by={group_by} sub_group_by={sub_group_by}
handleIssues={handleIssues} group_by={group_by}
quickActions={renderQuickActions} handleIssues={handleIssues}
handleKanbanFilters={handleKanbanFilters} quickActions={renderQuickActions}
kanbanFilters={kanbanFilters} handleKanbanFilters={handleKanbanFilters}
enableQuickIssueCreate={enableQuickAdd} kanbanFilters={kanbanFilters}
showEmptyGroup={userDisplayFilters?.show_empty_groups ?? true} enableQuickIssueCreate={enableQuickAdd}
quickAddCallback={issues?.quickAddIssue} showEmptyGroup={userDisplayFilters?.show_empty_groups ?? true}
viewId={viewId} quickAddCallback={issues?.quickAddIssue}
disableIssueCreation={!enableIssueCreation || !isEditingAllowed || isCompletedCycle} viewId={viewId}
canEditProperties={canEditProperties} disableIssueCreation={!enableIssueCreation || !isEditingAllowed || isCompletedCycle}
storeType={storeType} canEditProperties={canEditProperties}
addIssuesToView={addIssuesToView} storeType={storeType}
scrollableContainerRef={scrollableContainerRef} addIssuesToView={addIssuesToView}
isDragStarted={isDragStarted} scrollableContainerRef={scrollableContainerRef}
/> isDragStarted={isDragStarted}
/>
</div>
</DragDropContext> </DragDropContext>
</div> </div>
</div> </div>

View File

@ -141,7 +141,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = memo((props) => {
> >
<div <div
className={cn( className={cn(
"rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 text-sm transition-all hover:border-custom-border-400", "rounded border-[0.5px] w-full border-custom-border-200 bg-custom-background-100 text-sm transition-all hover:border-custom-border-400",
{ "hover:cursor-grab": !isDragDisabled }, { "hover:cursor-grab": !isDragDisabled },
{ "border-custom-primary-100": snapshot.isDragging }, { "border-custom-primary-100": snapshot.isDragging },
{ "border border-custom-primary-70 hover:border-custom-primary-70": peekIssueId === issue.id } { "border border-custom-primary-70 hover:border-custom-primary-70": peekIssueId === issue.id }

View File

@ -101,20 +101,27 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
const groupList = showEmptyGroup ? list : groupWithIssues; const groupList = showEmptyGroup ? list : groupWithIssues;
const visibilityGroupBy = (_list: IGroupByColumn) => const visibilityGroupBy = (_list: IGroupByColumn) => {
sub_group_by ? false : kanbanFilters?.group_by.includes(_list.id) ? true : false; if (sub_group_by) {
if (kanbanFilters?.sub_group_by.includes(_list.id)) return true;
return false;
} else {
if (kanbanFilters?.group_by.includes(_list.id)) return true;
return false;
}
};
const isGroupByCreatedBy = group_by === "created_by"; const isGroupByCreatedBy = group_by === "created_by";
return ( return (
<div className={`relative w-full flex gap-3 ${sub_group_by ? "h-full" : "h-full"}`}> <div className={`relative w-full flex gap-2 ${sub_group_by ? "h-full" : "h-full"}`}>
{groupList && {groupList &&
groupList.length > 0 && groupList.length > 0 &&
groupList.map((_list: IGroupByColumn) => { groupList.map((_list: IGroupByColumn) => {
const groupByVisibilityToggle = visibilityGroupBy(_list); const groupByVisibilityToggle = visibilityGroupBy(_list);
return ( return (
<div className={`relative flex flex-shrink-0 flex-col group ${groupByVisibilityToggle ? `` : `w-[340px]`}`}> <div className={`relative flex flex-shrink-0 flex-col group ${groupByVisibilityToggle ? `` : `w-[350px]`}`}>
{sub_group_by === null && ( {sub_group_by === null && (
<div className="flex-shrink-0 sticky top-0 z-[2] w-full bg-custom-background-90 py-1"> <div className="flex-shrink-0 sticky top-0 z-[2] w-full bg-custom-background-90 py-1">
<HeaderGroupByCard <HeaderGroupByCard

View File

@ -106,13 +106,21 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
{icon ? icon : <Circle width={14} strokeWidth={2} />} {icon ? icon : <Circle width={14} strokeWidth={2} />}
</div> </div>
<div className={`flex items-center gap-1 ${verticalAlignPosition ? `flex-col` : `w-full flex-row`}`}> <div
className={`relative overflow-hidden flex items-center gap-1 ${
verticalAlignPosition ? `flex-col` : `w-full flex-row`
}`}
>
<div <div
className={`line-clamp-1 font-medium text-custom-text-100 ${verticalAlignPosition ? `vertical-lr` : ``}`} className={`inline-block truncate line-clamp-1 font-medium text-custom-text-100 overflow-hidden ${
verticalAlignPosition ? `vertical-lr max-h-[400px]` : ``
}`}
> >
{title} {title}
</div> </div>
<div className={`text-sm font-medium text-custom-text-300 ${verticalAlignPosition ? `` : `pl-2`}`}> <div
className={`flex-shrink-0 text-sm font-medium text-custom-text-300 ${verticalAlignPosition ? `` : `pl-2`}`}
>
{count || 0} {count || 0}
</div> </div>
</div> </div>

View File

@ -123,9 +123,7 @@ export const KanbanGroup = (props: IKanbanGroup) => {
<Droppable droppableId={`${groupId}__${sub_group_id}`}> <Droppable droppableId={`${groupId}__${sub_group_id}`}>
{(provided: any, snapshot: any) => ( {(provided: any, snapshot: any) => (
<div <div
className={`relative h-full w-full transition-all ${ className={`relative h-full transition-all ${snapshot.isDraggingOver ? `bg-custom-background-80` : ``}`}
snapshot.isDraggingOver ? `bg-custom-background-80` : ``
}`}
{...provided.droppableProps} {...provided.droppableProps}
ref={provided.innerRef} ref={provided.innerRef}
> >

View File

@ -30,6 +30,15 @@ interface ISubGroupSwimlaneHeader {
kanbanFilters: TIssueKanbanFilters; kanbanFilters: TIssueKanbanFilters;
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void; handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
} }
const getSubGroupHeaderIssuesCount = (issueIds: TSubGroupedIssues, groupById: string) => {
let headerCount = 0;
Object.keys(issueIds).map((groupState) => {
headerCount = headerCount + (issueIds?.[groupState]?.[groupById]?.length || 0);
});
return headerCount;
};
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
issueIds, issueIds,
sub_group_by, sub_group_by,
@ -38,18 +47,18 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
kanbanFilters, kanbanFilters,
handleKanbanFilters, handleKanbanFilters,
}) => ( }) => (
<div className="relative flex h-max min-h-full w-full items-center"> <div className="relative flex gap-2 h-max min-h-full w-full items-center">
{list && {list &&
list.length > 0 && list.length > 0 &&
list.map((_list: IGroupByColumn) => ( list.map((_list: IGroupByColumn) => (
<div key={`${sub_group_by}_${_list.id}`} className="flex w-[340px] flex-shrink-0 flex-col"> <div key={`${sub_group_by}_${_list.id}`} className="flex w-[350px] flex-shrink-0 flex-col">
<HeaderGroupByCard <HeaderGroupByCard
sub_group_by={sub_group_by} sub_group_by={sub_group_by}
group_by={group_by} group_by={group_by}
column_id={_list.id} column_id={_list.id}
icon={_list.icon} icon={_list.icon}
title={_list.name} title={_list.name}
count={(issueIds as TGroupedIssues)?.[_list.id]?.length || 0} count={getSubGroupHeaderIssuesCount(issueIds as TSubGroupedIssues, _list?.id)}
kanbanFilters={kanbanFilters} kanbanFilters={kanbanFilters}
handleKanbanFilters={handleKanbanFilters} handleKanbanFilters={handleKanbanFilters}
issuePayload={_list.payload} issuePayload={_list.payload}

View File

@ -96,6 +96,7 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
storeType={EIssuesStoreType.PROJECT} storeType={EIssuesStoreType.PROJECT}
/> />
<CustomMenu <CustomMenu
menuItemsClassName="z-[14]"
placement="bottom-start" placement="bottom-start"
customButton={customActionButton} customButton={customActionButton}
portalElement={portalElement} portalElement={portalElement}

View File

@ -56,6 +56,7 @@ export const ArchivedIssueQuickActions: React.FC<IQuickActionProps> = (props) =>
onSubmit={handleDelete} onSubmit={handleDelete}
/> />
<CustomMenu <CustomMenu
menuItemsClassName="z-[14]"
placement="bottom-start" placement="bottom-start"
customButton={customActionButton} customButton={customActionButton}
portalElement={portalElement} portalElement={portalElement}

View File

@ -106,6 +106,7 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
storeType={EIssuesStoreType.CYCLE} storeType={EIssuesStoreType.CYCLE}
/> />
<CustomMenu <CustomMenu
menuItemsClassName="z-[14]"
placement="bottom-start" placement="bottom-start"
customButton={customActionButton} customButton={customActionButton}
portalElement={portalElement} portalElement={portalElement}

View File

@ -106,6 +106,7 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
storeType={EIssuesStoreType.MODULE} storeType={EIssuesStoreType.MODULE}
/> />
<CustomMenu <CustomMenu
menuItemsClassName="z-[14]"
placement="bottom-start" placement="bottom-start"
customButton={customActionButton} customButton={customActionButton}
portalElement={portalElement} portalElement={portalElement}

View File

@ -108,6 +108,7 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
isDraft={isDraftIssue} isDraft={isDraftIssue}
/> />
<CustomMenu <CustomMenu
menuItemsClassName="z-[14]"
placement="bottom-start" placement="bottom-start"
customButton={customActionButton} customButton={customActionButton}
portalElement={portalElement} portalElement={portalElement}

View File

@ -100,8 +100,9 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
const fetchIssueDetail = async (issueId: string | undefined) => { const fetchIssueDetail = async (issueId: string | undefined) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
if (!projectId || issueId === undefined) { if (!projectId || issueId === undefined) {
setDescription("<p></p>"); setDescription(data?.description_html || "<p></p>");
return; return;
} }
const response = await fetchIssue(workspaceSlug, projectId, issueId, isDraft ? "DRAFT" : "DEFAULT"); const response = await fetchIssue(workspaceSlug, projectId, issueId, isDraft ? "DRAFT" : "DEFAULT");

View File

@ -368,12 +368,6 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
</div> </div>
</div> </div>
{moduleDetails.description && (
<span className="w-full whitespace-normal break-words py-2.5 text-sm leading-5 text-custom-text-200">
{moduleDetails.description}
</span>
)}
<div className="flex flex-col gap-5 pb-6 pt-2.5"> <div className="flex flex-col gap-5 pb-6 pt-2.5">
<div className="flex items-center justify-start gap-1"> <div className="flex items-center justify-start gap-1">
<div className="flex w-2/5 items-center justify-start gap-2 text-custom-text-300"> <div className="flex w-2/5 items-center justify-start gap-2 text-custom-text-300">

View File

@ -58,6 +58,7 @@ export const WorkspaceSidebarQuickAction = observer(() => {
const draftIssues = storedValue ?? {}; const draftIssues = storedValue ?? {};
if (workspaceSlug && draftIssues[workspaceSlug]) delete draftIssues[workspaceSlug]; if (workspaceSlug && draftIssues[workspaceSlug]) delete draftIssues[workspaceSlug];
setValue(draftIssues); setValue(draftIssues);
return Promise.resolve();
}; };
return ( return (
@ -66,7 +67,7 @@ export const WorkspaceSidebarQuickAction = observer(() => {
isOpen={isDraftIssueModalOpen} isOpen={isDraftIssueModalOpen}
onClose={() => setIsDraftIssueModalOpen(false)} onClose={() => setIsDraftIssueModalOpen(false)}
data={workspaceDraftIssue ?? {}} data={workspaceDraftIssue ?? {}}
// storeType={storeType} onSubmit={() => removeWorkspaceDraftIssue()}
isDraft={true} isDraft={true}
/> />

View File

@ -6,11 +6,6 @@ import { CommandPalette } from "components/command-palette";
import { AppSidebar } from "./sidebar"; import { AppSidebar } from "./sidebar";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// FIXME: remove this later
import { useIssues } from "hooks/store/use-issues";
import { EIssuesStoreType } from "constants/issue";
import useSWR from "swr";
export interface IAppLayout { export interface IAppLayout {
children: ReactNode; children: ReactNode;
header: ReactNode; header: ReactNode;
@ -20,22 +15,6 @@ export interface IAppLayout {
export const AppLayout: FC<IAppLayout> = observer((props) => { export const AppLayout: FC<IAppLayout> = observer((props) => {
const { children, header, withProjectWrapper = false } = props; const { children, header, withProjectWrapper = false } = props;
const workspaceSlug = "plane-demo";
const projectId = "b16907a9-a55f-4f5b-b05e-7065a0869ba6";
const { issues, issuesFilter } = useIssues(EIssuesStoreType.ARCHIVED);
useSWR(
workspaceSlug && projectId ? `PROJECT_ARCHIVED_ISSUES_V3_${workspaceSlug}_${projectId}` : null,
async () => {
if (workspaceSlug && projectId) {
await issuesFilter?.fetchFilters(workspaceSlug, projectId);
// await issues?.fetchIssues(workspaceSlug, projectId, issues?.groupedIssueIds ? "mutation" : "init-loader");
}
},
{ revalidateIfStale: false, revalidateOnFocus: false }
);
return ( return (
<> <>
<CommandPalette /> <CommandPalette />

View File

@ -148,7 +148,7 @@ export class ProjectStore implements IProjectStore {
projects = sortBy(projects, "created_at"); projects = sortBy(projects, "created_at");
const projectIds = projects const projectIds = projects
.filter((project) => project.workspace === currentWorkspace.id && project.is_favorite) .filter((project) => project.workspace === currentWorkspace.id && project.is_member && project.is_favorite)
.map((project) => project.id); .map((project) => project.id);
return projectIds; return projectIds;
} }