├── apiserver
├── plane
│ ├── api
│ │ ├── __init__.py
│ │ ├── apps.py
│ │ ├── serializers
│ │ │ ├── base.py
│ │ │ ├── integration
│ │ │ │ ├── __init__.py
│ │ │ │ ├── slack.py
│ │ │ │ └── base.py
│ │ │ ├── api_token.py
│ │ │ ├── asset.py
│ │ │ ├── notification.py
│ │ │ ├── importer.py
│ │ │ ├── state.py
│ │ │ └── analytic.py
│ │ ├── permissions
│ │ │ └── __init__.py
│ │ └── views
│ │ │ ├── integration
│ │ │ └── __init__.py
│ │ │ └── release.py
│ ├── db
│ │ ├── __init__.py
│ │ ├── management
│ │ │ ├── __init__.py
│ │ │ └── commands
│ │ │ │ ├── __init__.py
│ │ │ │ └── wait_for_db.py
│ │ ├── migrations
│ │ │ ├── __init__.py
│ │ │ ├── 0017_alter_workspace_unique_together.py
│ │ │ ├── 0030_alter_estimatepoint_unique_together.py
│ │ │ ├── 0008_label_colour.py
│ │ │ ├── 0004_alter_state_sequence.py
│ │ │ ├── 0014_alter_workspacememberinvite_unique_together.py
│ │ │ ├── 0036_alter_workspace_organization_size.py
│ │ │ ├── 0026_alter_projectmember_view_props.py
│ │ │ ├── 0006_alter_cycle_status.py
│ │ │ ├── 0007_label_parent.py
│ │ │ ├── 0032_auto_20230520_2015.py
│ │ │ ├── 0005_auto_20221114_2127.py
│ │ │ ├── 0019_auto_20230131_0049.py
│ │ │ ├── 0003_auto_20221109_2320.py
│ │ │ ├── 0009_auto_20221208_0310.py
│ │ │ ├── 0015_auto_20230107_1636.py
│ │ │ ├── 0016_auto_20230107_1735.py
│ │ │ ├── 0034_auto_20230628_1046.py
│ │ │ ├── 0010_auto_20221213_0037.py
│ │ │ └── 0013_auto_20230107_0041.py
│ │ ├── apps.py
│ │ └── models
│ │ │ ├── integration
│ │ │ ├── __init__.py
│ │ │ └── slack.py
│ │ │ ├── analytic.py
│ │ │ ├── social_connection.py
│ │ │ └── api_token.py
│ ├── static
│ │ ├── humans.txt
│ │ ├── css
│ │ │ └── style.css
│ │ └── js
│ │ │ └── script.js
│ ├── utils
│ │ ├── __init__.py
│ │ ├── importers
│ │ │ └── __init__.py
│ │ ├── integrations
│ │ │ └── __init__.py
│ │ ├── markdown.py
│ │ ├── ip_address.py
│ │ ├── html_processor.py
│ │ ├── issue_search.py
│ │ └── imports.py
│ ├── web
│ │ ├── __init__.py
│ │ ├── views.py
│ │ ├── apps.py
│ │ └── urls.py
│ ├── analytics
│ │ ├── __init__.py
│ │ └── apps.py
│ ├── bgtasks
│ │ ├── __init__.py
│ │ ├── apps.py
│ │ └── user_welcome_task.py
│ ├── middleware
│ │ ├── __init__.py
│ │ ├── apps.py
│ │ └── user_middleware.py
│ ├── settings
│ │ ├── __init__.py
│ │ ├── redis.py
│ │ └── test.py
│ ├── tests
│ │ ├── api
│ │ │ ├── __init__.py
│ │ │ ├── test_asset.py
│ │ │ ├── test_cycle.py
│ │ │ ├── test_issue.py
│ │ │ ├── test_people.py
│ │ │ ├── test_shortcut.py
│ │ │ ├── test_state.py
│ │ │ ├── test_project.py
│ │ │ ├── test_view.py
│ │ │ ├── test_oauth.py
│ │ │ ├── test_auth_extended.py
│ │ │ └── base.py
│ │ ├── __init__.py
│ │ └── apps.py
│ ├── __init__.py
│ ├── wsgi.py
│ ├── asgi.py
│ ├── urls.py
│ └── celery.py
├── runtime.txt
├── requirements
│ ├── local.txt
│ ├── test.txt
│ ├── production.txt
│ └── base.txt
├── bin
│ ├── beat
│ ├── worker
│ ├── takeoff
│ └── user_script.py
├── gunicorn.config.py
├── requirements.txt
├── templates
│ ├── about.html
│ ├── index.html
│ ├── emails
│ │ ├── auth
│ │ │ ├── email_verification.html
│ │ │ └── forgot_password.html
│ │ └── exports
│ │ │ └── analytics.html
│ └── admin
│ │ └── base_site.html
├── Procfile
└── manage.py
├── apps
└── app
│ ├── components
│ ├── gantt-chart
│ │ ├── index.ts
│ │ ├── views
│ │ │ └── index.ts
│ │ ├── hooks
│ │ │ └── index.tsx
│ │ ├── root.tsx
│ │ └── types
│ │ │ └── index.ts
│ ├── integration
│ │ ├── slack
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── github
│ │ │ ├── index.ts
│ │ │ ├── auth.tsx
│ │ │ └── import-confirm.tsx
│ │ └── jira
│ │ │ └── index.ts
│ ├── auth-screens
│ │ ├── project
│ │ │ └── index.ts
│ │ ├── workspace
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── onboarding
│ │ ├── tour
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── issues
│ │ ├── comment
│ │ │ └── index.ts
│ │ ├── view-select
│ │ │ └── index.ts
│ │ ├── select
│ │ │ └── index.ts
│ │ ├── sidebar-select
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── modules
│ │ ├── select
│ │ │ └── index.ts
│ │ ├── sidebar-select
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── core
│ │ ├── list-view
│ │ │ └── index.ts
│ │ ├── theme
│ │ │ └── index.ts
│ │ ├── sidebar
│ │ │ └── index.ts
│ │ ├── board-view
│ │ │ └── index.ts
│ │ ├── calendar-view
│ │ │ └── index.ts
│ │ ├── filters
│ │ │ └── index.ts
│ │ ├── spreadsheet-view
│ │ │ └── index.ts
│ │ ├── modals
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ └── gantt-chart-view
│ │ │ └── index.tsx
│ ├── ui
│ │ ├── buttons
│ │ │ ├── index.ts
│ │ │ ├── type.d.ts
│ │ │ ├── secondary-button.tsx
│ │ │ ├── primary-button.tsx
│ │ │ └── danger-button.tsx
│ │ ├── graphs
│ │ │ ├── index.ts
│ │ │ ├── types.d.ts
│ │ │ ├── pie-graph.tsx
│ │ │ ├── scatter-plot-graph.tsx
│ │ │ └── line-graph.tsx
│ │ ├── dropdowns
│ │ │ ├── index.ts
│ │ │ └── types.d.ts
│ │ ├── icon.tsx
│ │ ├── text-area
│ │ │ └── types.d.ts
│ │ ├── input
│ │ │ └── types.d.ts
│ │ ├── loader.tsx
│ │ ├── index.ts
│ │ ├── integration-and-import-export-banner.tsx
│ │ ├── labels-list.tsx
│ │ └── linear-progress-indicator.tsx
│ ├── analytics
│ │ ├── select
│ │ │ ├── index.ts
│ │ │ └── y-axis.tsx
│ │ ├── index.ts
│ │ ├── scope-and-demand
│ │ │ └── index.ts
│ │ └── custom-analytics
│ │ │ └── index.ts
│ ├── automation
│ │ └── index.ts
│ ├── pages
│ │ ├── pages-list
│ │ │ ├── types.ts
│ │ │ ├── index.ts
│ │ │ ├── all-pages-list.tsx
│ │ │ ├── favorite-pages-list.tsx
│ │ │ ├── my-pages-list.tsx
│ │ │ └── other-pages-list.tsx
│ │ └── index.ts
│ ├── estimates
│ │ └── index.tsx
│ ├── notifications
│ │ └── index.ts
│ ├── icons
│ │ ├── types.d.ts
│ │ ├── css-file-icon.tsx
│ │ ├── csv-file-icon.tsx
│ │ ├── doc-file-icon.tsx
│ │ ├── img-file-icon.tsx
│ │ ├── jpg-file-icon.tsx
│ │ ├── png-file-icon.tsx
│ │ ├── svg-file-icon.tsx
│ │ ├── txt-file-icon.tsx
│ │ ├── js-file-icon.tsx
│ │ ├── pdf-file-icon.tsx
│ │ ├── html-file-icon.tsx
│ │ ├── audio-file-icon.tsx
│ │ ├── figma-file-icon.tsx
│ │ ├── sheet-file-icon.tsx
│ │ ├── video-file-icon.tsx
│ │ ├── default-file-icon.tsx
│ │ ├── cmd-icon.tsx
│ │ ├── backlog-state-icon.tsx
│ │ ├── completed-cycle-icon.tsx
│ │ ├── upcoming-cycle-icon.tsx
│ │ ├── heartbeat-icon.tsx
│ │ ├── priority-icon.tsx
│ │ ├── single-comment-icon.tsx
│ │ ├── current-cycle-icon.tsx
│ │ ├── state-group-icon.tsx
│ │ ├── bolt-icon.tsx
│ │ ├── pencil-scribble-icon.tsx
│ │ ├── contrast-icon.tsx
│ │ ├── comment-icon.tsx
│ │ ├── cycle-icon.tsx
│ │ ├── plus-icon.tsx
│ │ ├── view-list-icon.tsx
│ │ ├── check.tsx
│ │ └── x-mark-icon.tsx
│ ├── states
│ │ └── index.ts
│ ├── cycles
│ │ ├── cycles-list
│ │ │ ├── index.ts
│ │ │ ├── all-cycles-list.tsx
│ │ │ ├── draft-cycles-list.tsx
│ │ │ ├── upcoming-cycles-list.tsx
│ │ │ └── completed-cycles-list.tsx
│ │ └── index.ts
│ ├── account
│ │ └── index.ts
│ ├── views
│ │ └── index.ts
│ ├── labels
│ │ └── index.ts
│ ├── command-palette
│ │ └── index.ts
│ ├── project
│ │ ├── index.ts
│ │ └── settings-header.tsx
│ ├── emoji-icon-picker
│ │ ├── types.d.ts
│ │ └── helpers.ts
│ ├── inbox
│ │ └── index.ts
│ ├── workspace
│ │ ├── index.ts
│ │ └── settings-header.tsx
│ ├── search-listbox
│ │ └── types.d.ts
│ ├── dnd
│ │ └── StrictModeDroppable.tsx
│ └── rich-text-editor
│ │ └── toolbar
│ │ └── index.tsx
│ ├── public
│ ├── user.png
│ ├── services
│ │ ├── jira.png
│ │ ├── slack.png
│ │ └── github.png
│ ├── favicon
│ │ ├── favicon.ico
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── apple-touch-icon.png
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ └── site.webmanifest
│ ├── attachment
│ │ ├── css-icon.png
│ │ ├── csv-icon.png
│ │ ├── doc-icon.png
│ │ ├── img-icon.png
│ │ ├── jpg-icon.png
│ │ ├── js-icon.png
│ │ ├── pdf-icon.png
│ │ ├── png-icon.png
│ │ ├── svg-icon.png
│ │ ├── txt-icon.png
│ │ ├── audio-icon.png
│ │ ├── excel-icon.png
│ │ ├── figma-icon.png
│ │ ├── html-icon.png
│ │ ├── video-icon.png
│ │ └── default-icon.png
│ ├── logos
│ │ ├── github-black.png
│ │ ├── github-square.png
│ │ └── github-white.png
│ ├── plane-logos
│ │ └── blue-without-text.png
│ ├── site.webmanifest.json
│ ├── mac-command.svg
│ └── empty-state
│ │ └── issue-archive.svg
│ ├── types
│ ├── waitlist.d.ts
│ ├── calendar.ts
│ ├── ai.d.ts
│ ├── importer
│ │ ├── github-importer.d.ts
│ │ └── index.ts
│ ├── state.d.ts
│ ├── estimate.d.ts
│ ├── index.d.ts
│ └── views.d.ts
│ ├── .prettierrc
│ ├── sentry.properties
│ ├── postcss.config.js
│ ├── layouts
│ ├── auth-layout
│ │ ├── index.ts
│ │ └── user-authorization-wrapper.tsx
│ ├── default-layout
│ │ └── index.tsx
│ └── app-layout
│ │ └── app-header.tsx
│ ├── .eslintrc.js
│ ├── next-env.d.ts
│ ├── Dockerfile.dev
│ ├── constants
│ ├── state.ts
│ ├── module.ts
│ ├── seo-variables.ts
│ ├── calendar.ts
│ ├── graph.ts
│ ├── project.ts
│ ├── inbox.ts
│ ├── due-dates.ts
│ └── themes.ts
│ ├── tsconfig.json
│ ├── hooks
│ ├── use-theme.tsx
│ ├── use-toast.tsx
│ ├── use-timer.tsx
│ ├── use-debounce.tsx
│ ├── use-outside-click-detector.tsx
│ ├── use-issues.tsx
│ ├── use-workspaces.tsx
│ ├── gantt-chart
│ │ ├── issue-view.tsx
│ │ ├── cycle-issues-view.tsx
│ │ ├── module-issues-view.tsx
│ │ └── view-issues-view.tsx
│ ├── use-reload-confirmation.tsx
│ ├── use-project-details.tsx
│ ├── use-sub-issue.tsx
│ └── use-workspace-details.tsx
│ ├── pages
│ ├── error.tsx
│ └── api
│ │ ├── slack-redirect.ts
│ │ ├── unsplash.ts
│ │ └── track-event.ts
│ ├── helpers
│ ├── common.helper.ts
│ ├── emoji.helper.ts
│ ├── color.helper.ts
│ ├── attachment.helper.ts
│ ├── state.helper.ts
│ └── graph.helper.ts
│ ├── lib
│ ├── redirect.ts
│ └── cookie.ts
│ ├── services
│ ├── web-waitlist.service.ts
│ └── ai.service.ts
│ ├── sentry.client.config.js
│ ├── sentry.server.config.js
│ ├── sentry.edge.config.js
│ ├── styles
│ ├── nprogress.css
│ └── command-pallette.css
│ ├── manifest.json
│ └── next.config.js
├── heroku.yml
├── .dockerignore
├── packages
├── ui
│ ├── button
│ │ └── index.tsx
│ ├── tsconfig.json
│ ├── package.json
│ └── index.tsx
├── tsconfig
│ ├── package.json
│ ├── react-library.json
│ ├── base.json
│ └── nextjs.json
└── eslint-config-custom
│ ├── package.json
│ └── index.js
├── deploy
└── heroku
│ └── Dockerfile
├── nginx
├── env.sh
├── Dockerfile
├── nginx.conf.template
├── nginx-single-docker-image.conf
└── supervisor.conf
├── .eslintrc.js
├── .github
└── ISSUE_TEMPLATE
│ ├── config.yaml
│ └── --feature-request.yaml
├── start.sh
├── replace-env-vars.sh
├── setup.sh
├── package.json
└── .gitignore
/apiserver/plane/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/plane/db/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/plane/static/humans.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/plane/utils/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/plane/web/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/plane/analytics/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/plane/bgtasks/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/plane/middleware/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/plane/settings/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/plane/static/css/style.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/plane/static/js/script.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/plane/tests/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/runtime.txt:
--------------------------------------------------------------------------------
1 | python-3.11.4
--------------------------------------------------------------------------------
/apiserver/plane/db/management/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/plane/utils/importers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/plane/utils/integrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/plane/db/management/commands/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apiserver/plane/tests/__init__.py:
--------------------------------------------------------------------------------
1 | from .api import *
--------------------------------------------------------------------------------
/apps/app/components/gantt-chart/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./root";
2 |
--------------------------------------------------------------------------------
/heroku.yml:
--------------------------------------------------------------------------------
1 | build:
2 | docker:
3 | web: deploy/heroku/Dockerfile
--------------------------------------------------------------------------------
/apiserver/plane/tests/api/test_asset.py:
--------------------------------------------------------------------------------
1 | # TODO: Tests for File Asset Uploads
--------------------------------------------------------------------------------
/apiserver/plane/tests/api/test_cycle.py:
--------------------------------------------------------------------------------
1 | # TODO: Write Test for Cycle Endpoints
--------------------------------------------------------------------------------
/apiserver/plane/tests/api/test_issue.py:
--------------------------------------------------------------------------------
1 | # TODO: Write Test for Issue Endpoints
--------------------------------------------------------------------------------
/apiserver/plane/tests/api/test_people.py:
--------------------------------------------------------------------------------
1 | # TODO: Write Test for people Endpoint
--------------------------------------------------------------------------------
/apiserver/plane/tests/api/test_shortcut.py:
--------------------------------------------------------------------------------
1 | # TODO: Write Test for shortcuts
--------------------------------------------------------------------------------
/apiserver/plane/tests/api/test_state.py:
--------------------------------------------------------------------------------
1 | # TODO: Wrote test for state endpoints
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | *.pyc
3 | .env
4 | venv
5 | node_modules
6 | npm-debug.log
--------------------------------------------------------------------------------
/apiserver/plane/tests/api/test_project.py:
--------------------------------------------------------------------------------
1 | # TODO: Write Tests for project endpoints
--------------------------------------------------------------------------------
/apiserver/plane/tests/api/test_view.py:
--------------------------------------------------------------------------------
1 | # TODO: Write test for view endpoints
2 |
--------------------------------------------------------------------------------
/apiserver/requirements/local.txt:
--------------------------------------------------------------------------------
1 | -r base.txt
2 |
3 | django-debug-toolbar==4.1.0
--------------------------------------------------------------------------------
/apps/app/components/integration/slack/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./select-channel";
--------------------------------------------------------------------------------
/apiserver/plane/tests/api/test_oauth.py:
--------------------------------------------------------------------------------
1 | #TODO: Tests for OAuth Authentication Endpoint
--------------------------------------------------------------------------------
/apiserver/plane/utils/markdown.py:
--------------------------------------------------------------------------------
1 | import mistune
2 |
3 | markdown = mistune.Markdown()
--------------------------------------------------------------------------------
/apiserver/requirements/test.txt:
--------------------------------------------------------------------------------
1 | -r base.txt
2 |
3 | pytest==7.1.2
4 | coverage==6.5.0
--------------------------------------------------------------------------------
/apps/app/components/auth-screens/project/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./join-project";
2 |
--------------------------------------------------------------------------------
/apps/app/components/auth-screens/workspace/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./not-a-member";
2 |
--------------------------------------------------------------------------------
/apiserver/plane/tests/api/test_auth_extended.py:
--------------------------------------------------------------------------------
1 | #TODO: Tests for ChangePassword and other Endpoints
--------------------------------------------------------------------------------
/apiserver/plane/web/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render
2 |
3 | # Create your views here.
4 |
--------------------------------------------------------------------------------
/apps/app/components/onboarding/tour/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./root";
2 | export * from "./sidebar";
3 |
--------------------------------------------------------------------------------
/apps/app/public/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/user.png
--------------------------------------------------------------------------------
/apps/app/types/waitlist.d.ts:
--------------------------------------------------------------------------------
1 | export interface IWebWaitListResponse {
2 | status: string;
3 | }
4 |
--------------------------------------------------------------------------------
/apiserver/plane/__init__.py:
--------------------------------------------------------------------------------
1 | from .celery import app as celery_app
2 |
3 | __all__ = ('celery_app',)
4 |
--------------------------------------------------------------------------------
/apps/app/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "tabWidth": 2,
4 | "trailingComma": "es5"
5 | }
6 |
--------------------------------------------------------------------------------
/packages/ui/button/index.tsx:
--------------------------------------------------------------------------------
1 | export const Button = () => {
2 | return button ;
3 | };
4 |
--------------------------------------------------------------------------------
/apiserver/bin/beat:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | python manage.py wait_for_db
5 | celery -A plane beat -l info
--------------------------------------------------------------------------------
/apiserver/bin/worker:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | python manage.py wait_for_db
5 | celery -A plane worker -l info
--------------------------------------------------------------------------------
/apps/app/components/issues/comment/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./add-comment";
2 | export * from "./comment-card";
3 |
--------------------------------------------------------------------------------
/apps/app/sentry.properties:
--------------------------------------------------------------------------------
1 | defaults.url=https://sentry.io/
2 | defaults.org=plane
3 | defaults.project=plane-web
4 |
--------------------------------------------------------------------------------
/apps/app/types/calendar.ts:
--------------------------------------------------------------------------------
1 | export interface ICalendarRange {
2 | startDate: Date;
3 | endDate: Date;
4 | }
5 |
--------------------------------------------------------------------------------
/apps/app/public/services/jira.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/services/jira.png
--------------------------------------------------------------------------------
/apps/app/public/services/slack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/services/slack.png
--------------------------------------------------------------------------------
/deploy/heroku/Dockerfile:
--------------------------------------------------------------------------------
1 | # Deploy the Plane image
2 | FROM makeplane/plane
3 |
4 | LABEL maintainer="engineering@plane.so"
--------------------------------------------------------------------------------
/apps/app/public/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/favicon/favicon.ico
--------------------------------------------------------------------------------
/apps/app/public/services/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/services/github.png
--------------------------------------------------------------------------------
/apiserver/plane/db/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class DbConfig(AppConfig):
5 | name = "plane.db"
6 |
--------------------------------------------------------------------------------
/apps/app/components/modules/select/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./lead";
2 | export * from "./members";
3 | export * from "./status";
4 |
--------------------------------------------------------------------------------
/apps/app/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/apps/app/public/attachment/css-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/attachment/css-icon.png
--------------------------------------------------------------------------------
/apps/app/public/attachment/csv-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/attachment/csv-icon.png
--------------------------------------------------------------------------------
/apps/app/public/attachment/doc-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/attachment/doc-icon.png
--------------------------------------------------------------------------------
/apps/app/public/attachment/img-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/attachment/img-icon.png
--------------------------------------------------------------------------------
/apps/app/public/attachment/jpg-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/attachment/jpg-icon.png
--------------------------------------------------------------------------------
/apps/app/public/attachment/js-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/attachment/js-icon.png
--------------------------------------------------------------------------------
/apps/app/public/attachment/pdf-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/attachment/pdf-icon.png
--------------------------------------------------------------------------------
/apps/app/public/attachment/png-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/attachment/png-icon.png
--------------------------------------------------------------------------------
/apps/app/public/attachment/svg-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/attachment/svg-icon.png
--------------------------------------------------------------------------------
/apps/app/public/attachment/txt-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/attachment/txt-icon.png
--------------------------------------------------------------------------------
/apps/app/public/logos/github-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/logos/github-black.png
--------------------------------------------------------------------------------
/apps/app/public/logos/github-square.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/logos/github-square.png
--------------------------------------------------------------------------------
/apps/app/public/logos/github-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/logos/github-white.png
--------------------------------------------------------------------------------
/nginx/env.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | envsubst < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
4 | exec nginx -g 'daemon off;'
5 |
--------------------------------------------------------------------------------
/apiserver/plane/api/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class ApiConfig(AppConfig):
5 | name = "plane.api"
6 |
--------------------------------------------------------------------------------
/apiserver/plane/tests/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class ApiConfig(AppConfig):
5 | name = "plane.tests"
6 |
--------------------------------------------------------------------------------
/apiserver/plane/web/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class WebConfig(AppConfig):
5 | name = 'plane.web'
6 |
--------------------------------------------------------------------------------
/apps/app/public/attachment/audio-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/attachment/audio-icon.png
--------------------------------------------------------------------------------
/apps/app/public/attachment/excel-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/attachment/excel-icon.png
--------------------------------------------------------------------------------
/apps/app/public/attachment/figma-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/attachment/figma-icon.png
--------------------------------------------------------------------------------
/apps/app/public/attachment/html-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/attachment/html-icon.png
--------------------------------------------------------------------------------
/apps/app/public/attachment/video-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/attachment/video-icon.png
--------------------------------------------------------------------------------
/apps/app/public/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/apps/app/public/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/apps/app/public/attachment/default-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/attachment/default-icon.png
--------------------------------------------------------------------------------
/apps/app/public/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/apiserver/plane/bgtasks/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class BgtasksConfig(AppConfig):
5 | name = 'plane.bgtasks'
6 |
--------------------------------------------------------------------------------
/apps/app/components/auth-screens/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./project";
2 | export * from "./workspace";
3 | export * from "./not-authorized-view";
4 |
--------------------------------------------------------------------------------
/apps/app/components/core/list-view/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./all-lists";
2 | export * from "./single-issue";
3 | export * from "./single-list";
4 |
--------------------------------------------------------------------------------
/apps/app/layouts/auth-layout/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./project-authorization-wrapper";
2 | export * from "./workspace-authorization-wrapper";
3 |
--------------------------------------------------------------------------------
/apiserver/plane/analytics/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class AnalyticsConfig(AppConfig):
5 | name = 'plane.analytics'
6 |
--------------------------------------------------------------------------------
/apiserver/plane/middleware/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class Middleware(AppConfig):
5 | name = 'plane.middleware'
6 |
--------------------------------------------------------------------------------
/apps/app/public/plane-logos/blue-without-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/plane-logos/blue-without-text.png
--------------------------------------------------------------------------------
/apps/app/components/ui/buttons/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./danger-button";
2 | export * from "./primary-button";
3 | export * from "./secondary-button";
4 |
--------------------------------------------------------------------------------
/apps/app/public/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/apps/app/public/favicon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krshrimali/plane/develop/apps/app/public/favicon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/apps/app/components/core/theme/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./color-picker-input";
2 | export * from "./custom-theme-selector";
3 | export * from "./theme-switch";
4 |
--------------------------------------------------------------------------------
/apps/app/components/modules/sidebar-select/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./select-lead";
2 | export * from "./select-members";
3 | export * from "./select-status";
4 |
--------------------------------------------------------------------------------
/apps/app/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ["custom"],
4 | rules: {
5 | "@next/next/no-img-element": "off",
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/apps/app/components/analytics/select/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./project";
2 | export * from "./segment";
3 | export * from "./x-axis";
4 | export * from "./y-axis";
5 |
--------------------------------------------------------------------------------
/apps/app/components/core/sidebar/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./links-list";
2 | export * from "./sidebar-progress-stats";
3 | export * from "./single-progress-stats";
4 |
--------------------------------------------------------------------------------
/apps/app/components/automation/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./auto-close-automation";
2 | export * from "./auto-archive-automation";
3 | export * from "./select-month-modal";
4 |
--------------------------------------------------------------------------------
/apps/app/components/pages/pages-list/types.ts:
--------------------------------------------------------------------------------
1 | import { TPageViewProps } from "types";
2 |
3 | export type TPagesListProps = {
4 | viewType: TPageViewProps;
5 | };
6 |
--------------------------------------------------------------------------------
/apps/app/components/estimates/index.tsx:
--------------------------------------------------------------------------------
1 | export * from "./create-update-estimate-modal";
2 | export * from "./single-estimate";
3 | export * from "./delete-estimate-modal";
4 |
--------------------------------------------------------------------------------
/apps/app/components/notifications/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./notification-card";
2 | export * from "./notification-popover";
3 | export * from "./select-snooze-till-modal";
4 |
--------------------------------------------------------------------------------
/apps/app/components/icons/types.d.ts:
--------------------------------------------------------------------------------
1 | export type Props = {
2 | className?: string;
3 | width?: string | number;
4 | height?: string | number;
5 | color?: string;
6 | };
7 |
--------------------------------------------------------------------------------
/apiserver/gunicorn.config.py:
--------------------------------------------------------------------------------
1 | from psycogreen.gevent import patch_psycopg
2 |
3 |
4 | def post_fork(server, worker):
5 | patch_psycopg()
6 | worker.log.info("Made Psycopg2 Green")
--------------------------------------------------------------------------------
/apiserver/requirements.txt:
--------------------------------------------------------------------------------
1 | # This file is here because many Platforms as a Service look for
2 | # requirements.txt in the root directory of a project.
3 | -r requirements/production.txt
--------------------------------------------------------------------------------
/apps/app/components/analytics/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./custom-analytics";
2 | export * from "./scope-and-demand";
3 | export * from "./select";
4 | export * from "./project-modal";
5 |
--------------------------------------------------------------------------------
/apps/app/components/core/board-view/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./all-boards";
2 | export * from "./board-header";
3 | export * from "./single-board";
4 | export * from "./single-issue";
5 |
--------------------------------------------------------------------------------
/apps/app/components/core/calendar-view/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./calendar-header";
2 | export * from "./calendar";
3 | export * from "./single-date";
4 | export * from "./single-issue";
5 |
--------------------------------------------------------------------------------
/apiserver/templates/about.html:
--------------------------------------------------------------------------------
1 |
2 | {% extends 'base.html' %}
3 | {% load static %}
4 |
5 |
6 | {% block content %}
7 |
Hello from plane!
8 | Made with Django
9 | {% endblock content %}
--------------------------------------------------------------------------------
/apiserver/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %} {% load static %} {% block content %}
2 |
3 |
Hello from plane!
4 |
5 | {% endblock content %}
--------------------------------------------------------------------------------
/apps/app/components/states/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./create-update-state-inline";
2 | export * from "./create-state-modal";
3 | export * from "./delete-state-modal";
4 | export * from "./single-state";
5 |
--------------------------------------------------------------------------------
/apiserver/plane/api/serializers/base.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 |
3 |
4 | class BaseSerializer(serializers.ModelSerializer):
5 | id = serializers.PrimaryKeyRelatedField(read_only=True)
6 |
--------------------------------------------------------------------------------
/apps/app/components/core/filters/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./due-date-filter-modal";
2 | export * from "./due-date-filter-select";
3 | export * from "./filters-list";
4 | export * from "./issues-view-filter";
5 |
--------------------------------------------------------------------------------
/apps/app/components/core/spreadsheet-view/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./spreadsheet-view";
2 | export * from "./single-issue";
3 | export * from "./spreadsheet-columns";
4 | export * from "./spreadsheet-issues";
5 |
--------------------------------------------------------------------------------
/apps/app/components/onboarding/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./tour";
2 | export * from "./invite-members";
3 | export * from "./join-workspaces";
4 | export * from "./user-details";
5 | export * from "./workspace";
6 |
--------------------------------------------------------------------------------
/apps/app/components/cycles/cycles-list/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./all-cycles-list";
2 | export * from "./completed-cycles-list";
3 | export * from "./draft-cycles-list";
4 | export * from "./upcoming-cycles-list";
5 |
--------------------------------------------------------------------------------
/apps/app/components/ui/graphs/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./bar-graph";
2 | export * from "./calendar-graph";
3 | export * from "./line-graph";
4 | export * from "./pie-graph";
5 | export * from "./scatter-plot-graph";
6 |
--------------------------------------------------------------------------------
/apiserver/plane/web/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from django.views.generic import TemplateView
3 |
4 | urlpatterns = [
5 | path('about/', TemplateView.as_view(template_name='about.html'))
6 |
7 | ]
8 |
--------------------------------------------------------------------------------
/apps/app/components/ui/dropdowns/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./context-menu";
2 | export * from "./custom-menu";
3 | export * from "./custom-search-select";
4 | export * from "./custom-select";
5 | export * from "./types.d";
6 |
--------------------------------------------------------------------------------
/apps/app/components/ui/graphs/types.d.ts:
--------------------------------------------------------------------------------
1 | import { Theme, Margin } from "@nivo/core";
2 |
3 | export type TGraph = {
4 | height?: string;
5 | width?: string;
6 | margin?: Partial;
7 | theme?: Theme;
8 | };
9 |
--------------------------------------------------------------------------------
/packages/tsconfig/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tsconfig",
3 | "version": "0.0.0",
4 | "private": true,
5 | "files": [
6 | "base.json",
7 | "nextjs.json",
8 | "react-library.json"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/apps/app/components/analytics/scope-and-demand/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./demand";
2 | export * from "./leaderboard";
3 | export * from "./scope-and-demand";
4 | export * from "./scope";
5 | export * from "./year-wise-issues";
6 |
--------------------------------------------------------------------------------
/apps/app/components/issues/view-select/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./assignee";
2 | export * from "./due-date";
3 | export * from "./estimate";
4 | export * from "./priority";
5 | export * from "./state";
6 | export * from "./label";
7 |
--------------------------------------------------------------------------------
/apiserver/plane/db/models/integration/__init__.py:
--------------------------------------------------------------------------------
1 | from .base import Integration, WorkspaceIntegration
2 | from .github import GithubRepository, GithubRepositorySync, GithubIssueSync, GithubCommentSync
3 | from .slack import SlackProjectSync
--------------------------------------------------------------------------------
/apps/app/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/apps/app/components/account/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./email-code-form";
2 | export * from "./email-password-form";
3 | export * from "./email-reset-password-form";
4 | export * from "./github-login-button";
5 | export * from "./google-login";
6 |
--------------------------------------------------------------------------------
/apps/app/components/views/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./delete-view-modal";
2 | export * from "./form";
3 | export * from "./gantt-chart";
4 | export * from "./modal";
5 | export * from "./select-filters";
6 | export * from "./single-view-item";
7 |
--------------------------------------------------------------------------------
/packages/ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig/nextjs.json",
3 | "include": ["."],
4 | "exclude": ["dist", "build", "node_modules"],
5 | "compilerOptions": {
6 | "jsx": "react-jsx",
7 | "lib": ["DOM"]
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apps/app/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine
2 | RUN apk add --no-cache libc6-compat
3 | # Set working directory
4 | WORKDIR /app
5 |
6 |
7 | COPY . .
8 | RUN yarn global add turbo
9 | RUN yarn install
10 | EXPOSE 3000
11 | CMD ["yarn","dev"]
12 |
--------------------------------------------------------------------------------
/apps/app/components/core/modals/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./bulk-delete-issues-modal";
2 | export * from "./existing-issues-list-modal";
3 | export * from "./gpt-assistant-modal";
4 | export * from "./image-upload-modal";
5 | export * from "./link-modal";
6 |
--------------------------------------------------------------------------------
/apps/app/components/issues/select/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./assignee";
2 | export * from "./date";
3 | export * from "./estimate";
4 | export * from "./label";
5 | export * from "./priority";
6 | export * from "./project";
7 | export * from "./state";
8 |
--------------------------------------------------------------------------------
/apps/app/constants/state.ts:
--------------------------------------------------------------------------------
1 | export const STATE_GROUP_COLORS: {
2 | [key: string]: string;
3 | } = {
4 | backlog: "#ced4da",
5 | unstarted: "#26b5ce",
6 | started: "#f7ae59",
7 | cancelled: "#d687ff",
8 | completed: "#09a953",
9 | };
10 |
--------------------------------------------------------------------------------
/apiserver/plane/api/permissions/__init__.py:
--------------------------------------------------------------------------------
1 | from .workspace import WorkSpaceBasePermission, WorkSpaceAdminPermission, WorkspaceEntityPermission
2 | from .project import ProjectBasePermission, ProjectEntityPermission, ProjectMemberPermission, ProjectLitePermission
3 |
--------------------------------------------------------------------------------
/apps/app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tsconfig/nextjs.json",
3 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
4 | "exclude": ["node_modules"],
5 | "compilerOptions": {
6 | "baseUrl": ".",
7 | "jsx": "preserve"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | // This tells ESLint to load the config from the package `eslint-config-custom`
4 | extends: ["custom"],
5 | settings: {
6 | next: {
7 | rootDir: ["apps/*"],
8 | },
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/apps/app/components/analytics/custom-analytics/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./graph";
2 | export * from "./create-update-analytics-modal";
3 | export * from "./custom-analytics";
4 | export * from "./select-bar";
5 | export * from "./sidebar";
6 | export * from "./table";
7 |
--------------------------------------------------------------------------------
/apps/app/components/pages/pages-list/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./all-pages-list";
2 | export * from "./favorite-pages-list";
3 | export * from "./my-pages-list";
4 | export * from "./other-pages-list";
5 | export * from "./recent-pages-list";
6 | export * from "./types";
7 |
--------------------------------------------------------------------------------
/apiserver/requirements/production.txt:
--------------------------------------------------------------------------------
1 | -r base.txt
2 |
3 | dj-database-url==2.0.0
4 | gunicorn==20.1.0
5 | whitenoise==6.5.0
6 | django-storages==1.13.2
7 | boto3==1.27.0
8 | django-anymail==10.0
9 | django-debug-toolbar==4.1.0
10 | gevent==23.7.0
11 | psycogreen==1.0.2
--------------------------------------------------------------------------------
/apiserver/plane/utils/ip_address.py:
--------------------------------------------------------------------------------
1 | def get_client_ip(request):
2 | x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
3 | if x_forwarded_for:
4 | ip = x_forwarded_for.split(',')[0]
5 | else:
6 | ip = request.META.get('REMOTE_ADDR')
7 | return ip
--------------------------------------------------------------------------------
/apps/app/components/labels/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./create-label-modal";
2 | export * from "./create-update-label-inline";
3 | export * from "./delete-label-modal";
4 | export * from "./labels-list-modal";
5 | export * from "./single-label-group";
6 | export * from "./single-label";
7 |
--------------------------------------------------------------------------------
/apps/app/types/ai.d.ts:
--------------------------------------------------------------------------------
1 | import { IProjectLite, IWorkspaceLite } from "types";
2 |
3 | export interface IGptResponse {
4 | response: string;
5 | response_html: string;
6 | count: number;
7 | project_detail: IProjectLite;
8 | workspace_detail: IWorkspaceLite;
9 | }
10 |
--------------------------------------------------------------------------------
/apiserver/Procfile:
--------------------------------------------------------------------------------
1 | web: gunicorn -w 4 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:$PORT --config gunicorn.config.py --max-requests 10000 --max-requests-jitter 1000 --access-logfile -
2 | worker: celery -A plane worker -l info
3 | beat: celery -A plane beat -l INFO
--------------------------------------------------------------------------------
/apps/app/components/gantt-chart/views/index.ts:
--------------------------------------------------------------------------------
1 | // export * from "./hours-view";
2 | // export * from "./day-view";
3 | export * from "./week-view";
4 | export * from "./bi-week-view";
5 | export * from "./month-view";
6 | export * from "./quater-view";
7 | export * from "./year-view";
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yaml:
--------------------------------------------------------------------------------
1 | contact_links:
2 | - name: Help and support
3 | about: Reach out to us on our Discord server or GitHub discussions.
4 | - name: Dedicated support
5 | url: mailto:support@plane.so
6 | about: Write to us if you'd like dedicated support using Plane
7 |
--------------------------------------------------------------------------------
/apps/app/components/command-palette/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./command-pallette";
2 | export * from "./shortcuts-modal";
3 | export * from "./change-issue-state";
4 | export * from "./change-issue-priority";
5 | export * from "./change-issue-assignee";
6 | export * from "./change-interface-theme";
7 |
--------------------------------------------------------------------------------
/apps/app/hooks/use-theme.tsx:
--------------------------------------------------------------------------------
1 | import { useContext } from "react";
2 | import { themeContext } from "contexts/theme.context";
3 |
4 | const useTheme = () => {
5 | const themeContextData = useContext(themeContext);
6 | return themeContextData;
7 | };
8 |
9 | export default useTheme;
10 |
--------------------------------------------------------------------------------
/apps/app/hooks/use-toast.tsx:
--------------------------------------------------------------------------------
1 | import { useContext } from "react";
2 | import { toastContext } from "contexts/toast.context";
3 |
4 | const useToast = () => {
5 | const toastContextData = useContext(toastContext);
6 | return toastContextData;
7 | };
8 |
9 | export default useToast;
10 |
--------------------------------------------------------------------------------
/apps/app/public/favicon/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/packages/tsconfig/react-library.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "React Library",
4 | "extends": "./base.json",
5 | "compilerOptions": {
6 | "jsx": "react",
7 | "lib": ["ES2015"],
8 | "module": "ESNext",
9 | "target": "es6"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/nginx/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:1.25.0-alpine
2 |
3 | RUN rm /etc/nginx/conf.d/default.conf
4 | COPY nginx.conf.template /etc/nginx/nginx.conf.template
5 |
6 | COPY ./env.sh /docker-entrypoint.sh
7 |
8 | RUN chmod +x /docker-entrypoint.sh
9 | # Update all environment variables
10 | CMD ["/docker-entrypoint.sh"]
11 |
--------------------------------------------------------------------------------
/apps/app/components/ui/buttons/type.d.ts:
--------------------------------------------------------------------------------
1 | export type ButtonProps = {
2 | children: React.ReactNode;
3 | className?: string;
4 | onClick?: () => void;
5 | type?: "button" | "submit" | "reset";
6 | disabled?: boolean;
7 | loading?: boolean;
8 | size?: "sm" | "md" | "lg";
9 | outline?: boolean;
10 | };
11 |
--------------------------------------------------------------------------------
/apps/app/components/project/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./create-project-modal";
2 | export * from "./delete-project-modal";
3 | export * from "./sidebar-list";
4 | export * from "./settings-header"
5 | export * from "./single-integration-card";
6 | export * from "./single-project-card";
7 | export * from "./single-sidebar-project";
8 |
--------------------------------------------------------------------------------
/apiserver/templates/emails/auth/email_verification.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dear {{first_name}},
5 | Welcome! Your account has been created.
6 | Verify your email by clicking on the link below
7 | {{verification_url}}
8 | successfully.
9 |
10 |
11 |
--------------------------------------------------------------------------------
/apiserver/bin/takeoff:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | python manage.py wait_for_db
4 | python manage.py migrate
5 |
6 | # Create a Default User
7 | python bin/user_script.py
8 |
9 | exec gunicorn -w 8 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile -
10 |
--------------------------------------------------------------------------------
/apps/app/components/emoji-icon-picker/types.d.ts:
--------------------------------------------------------------------------------
1 | export type Props = {
2 | label: string | React.ReactNode;
3 | value: any;
4 | onChange: (
5 | data:
6 | | string
7 | | {
8 | name: string;
9 | color: string;
10 | }
11 | ) => void;
12 | onIconColorChange?: (data: any) => void;
13 | };
14 |
--------------------------------------------------------------------------------
/apiserver/plane/api/serializers/integration/__init__.py:
--------------------------------------------------------------------------------
1 | from .base import IntegrationSerializer, WorkspaceIntegrationSerializer
2 | from .github import (
3 | GithubRepositorySerializer,
4 | GithubRepositorySyncSerializer,
5 | GithubIssueSyncSerializer,
6 | GithubCommentSyncSerializer,
7 | )
8 | from .slack import SlackProjectSyncSerializer
--------------------------------------------------------------------------------
/apps/app/components/integration/index.ts:
--------------------------------------------------------------------------------
1 | // layout
2 | export * from "./delete-import-modal";
3 | export * from "./guide";
4 | export * from "./single-import";
5 | export * from "./single-integration-card";
6 |
7 | // github
8 | export * from "./github";
9 | // jira
10 | export * from "./jira";
11 | // slack
12 | export * from "./slack";
13 |
--------------------------------------------------------------------------------
/apps/app/components/issues/sidebar-select/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./assignee";
2 | export * from "./blocked";
3 | export * from "./blocker";
4 | export * from "./cycle";
5 | export * from "./estimate";
6 | export * from "./label";
7 | export * from "./module";
8 | export * from "./parent";
9 | export * from "./priority";
10 | export * from "./state";
11 |
--------------------------------------------------------------------------------
/apps/app/components/modules/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./select";
2 | export * from "./sidebar-select";
3 | export * from "./delete-module-modal";
4 | export * from "./form";
5 | export * from "./gantt-chart";
6 | export * from "./modal";
7 | export * from "./modules-list-gantt-chart";
8 | export * from "./sidebar";
9 | export * from "./single-module-card";
10 |
--------------------------------------------------------------------------------
/apps/app/components/ui/icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | type Props = {
4 | iconName: string;
5 | className?: string;
6 | };
7 |
8 | export const Icon: React.FC = ({ iconName, className = "" }) => (
9 |
10 | {iconName}
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -x
3 |
4 | # Replace the statically built BUILT_NEXT_PUBLIC_API_BASE_URL with run-time NEXT_PUBLIC_API_BASE_URL
5 | # NOTE: if these values are the same, this will be skipped.
6 | /usr/local/bin/replace-env-vars.sh "$BUILT_NEXT_PUBLIC_API_BASE_URL" "$NEXT_PUBLIC_API_BASE_URL"
7 |
8 | echo "Starting Plane Frontend.."
9 | node apps/app/server.js
--------------------------------------------------------------------------------
/apps/app/components/integration/github/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./auth";
2 | export * from "./import-configure";
3 | export * from "./import-confirm";
4 | export * from "./import-data";
5 | export * from "./import-users";
6 | export * from "./repo-details";
7 | export * from "./root";
8 | export * from "./select-repository";
9 | export * from "./single-user-select";
10 |
--------------------------------------------------------------------------------
/apiserver/plane/api/views/integration/__init__.py:
--------------------------------------------------------------------------------
1 | from .base import IntegrationViewSet, WorkspaceIntegrationViewSet
2 | from .github import (
3 | GithubRepositorySyncViewSet,
4 | GithubIssueSyncViewSet,
5 | BulkCreateGithubIssueSyncEndpoint,
6 | GithubCommentSyncViewSet,
7 | GithubRepositoriesEndpoint,
8 | )
9 | from .slack import SlackProjectSyncViewSet
10 |
--------------------------------------------------------------------------------
/apps/app/components/icons/css-file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import CssFileIcon from "public/attachment/css-icon.png";
6 |
7 | export const CssIcon: React.FC = ({ width, height }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/apps/app/components/icons/csv-file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import CSVFileIcon from "public/attachment/csv-icon.png";
6 |
7 | export const CsvIcon: React.FC = ({ width , height }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/apps/app/components/icons/doc-file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import DocFileIcon from "public/attachment/doc-icon.png";
6 |
7 | export const DocIcon: React.FC = ({ width , height }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/apps/app/components/icons/img-file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import ImgFileIcon from "public/attachment/img-icon.png";
6 |
7 | export const ImgIcon: React.FC = ({ width , height }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/apps/app/components/icons/jpg-file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import JpgFileIcon from "public/attachment/jpg-icon.png";
6 |
7 | export const JpgIcon: React.FC = ({ width, height }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/apps/app/components/icons/png-file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import PngFileIcon from "public/attachment/png-icon.png";
6 |
7 | export const PngIcon: React.FC = ({ width, height }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/apps/app/components/icons/svg-file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import SvgFileIcon from "public/attachment/svg-icon.png";
6 |
7 | export const SvgIcon: React.FC = ({ width, height }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/apps/app/components/icons/txt-file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import TxtFileIcon from "public/attachment/txt-icon.png";
6 |
7 | export const TxtIcon: React.FC = ({ width, height }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/apps/app/components/icons/js-file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import JsFileIcon from "public/attachment/js-icon.png";
6 |
7 | export const JavaScriptIcon: React.FC = ({ width, height }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/apps/app/components/icons/pdf-file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import PDFFileIcon from "public/attachment/pdf-icon.png";
6 |
7 | export const PdfIcon: React.FC = ({ width , height }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/apps/app/components/icons/html-file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import HtmlFileIcon from "public/attachment/html-icon.png";
6 |
7 | export const HtmlIcon: React.FC = ({ width, height }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/apps/app/components/icons/audio-file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import AudioFileIcon from "public/attachment/audio-icon.png";
6 |
7 | export const AudioIcon: React.FC = ({ width, height }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/apps/app/components/icons/figma-file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import FigmaFileIcon from "public/attachment/figma-icon.png";
6 |
7 | export const FigmaIcon: React.FC = ({ width , height }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/apps/app/components/icons/sheet-file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import SheetFileIcon from "public/attachment/excel-icon.png";
6 |
7 | export const SheetIcon: React.FC = ({ width, height }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/apps/app/components/icons/video-file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import VideoFileIcon from "public/attachment/video-icon.png";
6 |
7 | export const VideoIcon: React.FC = ({ width, height }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/apps/app/layouts/default-layout/index.tsx:
--------------------------------------------------------------------------------
1 | type Props = {
2 | children: React.ReactNode;
3 | gradient?: boolean;
4 | };
5 |
6 | const DefaultLayout: React.FC = ({ children, gradient = false }) => (
7 |
8 | {children}
9 |
10 | );
11 |
12 | export default DefaultLayout;
13 |
--------------------------------------------------------------------------------
/apiserver/plane/api/serializers/api_token.py:
--------------------------------------------------------------------------------
1 | from .base import BaseSerializer
2 | from plane.db.models import APIToken
3 |
4 |
5 | class APITokenSerializer(BaseSerializer):
6 | class Meta:
7 | model = APIToken
8 | fields = [
9 | "label",
10 | "user",
11 | "user_type",
12 | "workspace",
13 | "created_at",
14 | ]
15 |
--------------------------------------------------------------------------------
/apps/app/components/icons/default-file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import DefaultFileIcon from "public/attachment/default-icon.png";
6 |
7 | export const DefaultIcon: React.FC = ({ width, height }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/apiserver/plane/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for plane project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | """
7 |
8 | import os
9 |
10 | from django.core.wsgi import get_wsgi_application
11 |
12 | os.environ.setdefault('DJANGO_SETTINGS_MODULE',
13 | 'plane.settings.production')
14 |
15 | application = get_wsgi_application()
16 |
--------------------------------------------------------------------------------
/apps/app/components/icons/cmd-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Image from "next/image";
3 |
4 | import type { Props } from "./types";
5 | import CMDIcon from "public/mac-command.svg";
6 |
7 | export const MacCommandIcon: React.FC = ({ width = "14", height = "14" }) => (
8 |
9 | );
10 |
11 | export default MacCommandIcon;
12 |
--------------------------------------------------------------------------------
/apps/app/pages/error.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | // layouts
4 | import DefaultLayout from "layouts/default-layout";
5 | // types
6 | import type { NextPage } from "next";
7 |
8 | const ErrorPage: NextPage = () => (
9 |
10 |
11 |
Error!
12 |
13 |
14 | );
15 |
16 | export default ErrorPage;
17 |
--------------------------------------------------------------------------------
/apiserver/plane/api/serializers/asset.py:
--------------------------------------------------------------------------------
1 | from .base import BaseSerializer
2 | from plane.db.models import FileAsset
3 |
4 |
5 | class FileAssetSerializer(BaseSerializer):
6 | class Meta:
7 | model = FileAsset
8 | fields = "__all__"
9 | read_only_fields = [
10 | "created_by",
11 | "updated_by",
12 | "created_at",
13 | "updated_at",
14 | ]
15 |
--------------------------------------------------------------------------------
/apiserver/plane/api/serializers/notification.py:
--------------------------------------------------------------------------------
1 | # Module imports
2 | from .base import BaseSerializer
3 | from .user import UserLiteSerializer
4 | from plane.db.models import Notification
5 |
6 | class NotificationSerializer(BaseSerializer):
7 | triggered_by_details = UserLiteSerializer(read_only=True, source="triggered_by")
8 |
9 | class Meta:
10 | model = Notification
11 | fields = "__all__"
12 |
13 |
--------------------------------------------------------------------------------
/replace-env-vars.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | FROM=$1
3 | TO=$2
4 |
5 | if [ "${FROM}" = "${TO}" ]; then
6 | echo "Nothing to replace, the value is already set to ${TO}."
7 |
8 | exit 0
9 | fi
10 |
11 | # Only peform action if $FROM and $TO are different.
12 | echo "Replacing all statically built instances of $FROM with this string $TO ."
13 |
14 | grep -R -la "${FROM}" apps/app/.next | xargs -I{} sed -i "s|$FROM|$TO|g" "{}"
15 |
--------------------------------------------------------------------------------
/apps/app/components/inbox/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./accept-issue-modal";
2 | export * from "./decline-issue-modal";
3 | export * from "./delete-issue-modal";
4 | export * from "./filters-dropdown";
5 | export * from "./filters-list";
6 | export * from "./inbox-action-headers";
7 | export * from "./inbox-issue-card";
8 | export * from "./inbox-main-content";
9 | export * from "./issues-list-sidebar";
10 | export * from "./select-duplicate";
11 |
--------------------------------------------------------------------------------
/apps/app/components/pages/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./pages-list";
2 | export * from "./create-update-block-inline";
3 | export * from "./create-update-page-modal";
4 | export * from "./delete-page-modal";
5 | export * from "./page-form";
6 | export * from "./pages-view";
7 | export * from "./single-page-block";
8 | export * from "./single-page-detailed-item";
9 | export * from "./single-page-list-item";
10 | export * from "./create-block";
11 |
--------------------------------------------------------------------------------
/apps/app/components/core/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./board-view";
2 | export * from "./calendar-view";
3 | export * from "./filters";
4 | export * from "./gantt-chart-view";
5 | export * from "./list-view";
6 | export * from "./modals";
7 | export * from "./spreadsheet-view";
8 | export * from "./theme";
9 | export * from "./sidebar";
10 | export * from "./issues-view";
11 | export * from "./image-picker-popover";
12 | export * from "./feeds";
13 |
--------------------------------------------------------------------------------
/apps/app/constants/module.ts:
--------------------------------------------------------------------------------
1 | export const MODULE_STATUS = [
2 | { label: "Backlog", value: "backlog", color: "#5e6ad2" },
3 | { label: "Planned", value: "planned", color: "#26b5ce" },
4 | { label: "In Progress", value: "in-progress", color: "#f2c94c" },
5 | { label: "Paused", value: "paused", color: "#ff6900" },
6 | { label: "Completed", value: "completed", color: "#4cb782" },
7 | { label: "Cancelled", value: "cancelled", color: "#cc1d10" },
8 | ];
9 |
--------------------------------------------------------------------------------
/apps/app/components/gantt-chart/hooks/index.tsx:
--------------------------------------------------------------------------------
1 | import { useContext } from "react";
2 | // types
3 | import { ChartContextReducer } from "../types";
4 | // context
5 | import { ChartContext } from "../contexts";
6 |
7 | export const useChart = (): ChartContextReducer => {
8 | const context = useContext(ChartContext);
9 |
10 | if (!context) {
11 | throw new Error("useChart must be used within a GanttChart");
12 | }
13 |
14 | return context;
15 | };
16 |
--------------------------------------------------------------------------------
/apiserver/plane/api/serializers/integration/slack.py:
--------------------------------------------------------------------------------
1 | # Module imports
2 | from plane.api.serializers import BaseSerializer
3 | from plane.db.models import SlackProjectSync
4 |
5 |
6 | class SlackProjectSyncSerializer(BaseSerializer):
7 | class Meta:
8 | model = SlackProjectSync
9 | fields = "__all__"
10 | read_only_fields = [
11 | "project",
12 | "workspace",
13 | "workspace_integration",
14 | ]
15 |
--------------------------------------------------------------------------------
/apps/app/components/workspace/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./activity-graph";
2 | export * from "./completed-issues-graph";
3 | export * from "./create-workspace-form";
4 | export * from "./delete-workspace-modal";
5 | export * from "./help-section";
6 | export * from "./issues-list";
7 | export * from "./issues-pie-chart";
8 | export * from "./issues-stats";
9 | export * from "./settings-header";
10 | export * from "./sidebar-dropdown";
11 | export * from "./sidebar-menu";
12 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0017_alter_workspace_unique_together.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.16 on 2023-01-07 17:49
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('db', '0016_auto_20230107_1735'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterUniqueTogether(
14 | name='workspace',
15 | unique_together=set(),
16 | ),
17 | ]
18 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0030_alter_estimatepoint_unique_together.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.18 on 2023-05-05 14:17
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('db', '0029_auto_20230502_0126'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterUniqueTogether(
14 | name='estimatepoint',
15 | unique_together=set(),
16 | ),
17 | ]
18 |
--------------------------------------------------------------------------------
/apps/app/components/project/settings-header.tsx:
--------------------------------------------------------------------------------
1 | import SettingsNavbar from "layouts/settings-navbar";
2 |
3 | export const SettingsHeader = () => (
4 |
5 |
6 |
Project Settings
7 |
8 | This information will be displayed to every member of the project.
9 |
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/apps/app/components/workspace/settings-header.tsx:
--------------------------------------------------------------------------------
1 | import SettingsNavbar from "layouts/settings-navbar";
2 |
3 | export const SettingsHeader = () => (
4 |
5 |
6 |
Workspace Settings
7 |
8 | This information will be displayed to every member of the workspace.
9 |
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cp ./.env.example ./.env
3 |
4 | # Export for tr error in mac
5 | export LC_ALL=C
6 | export LC_CTYPE=C
7 |
8 |
9 | # Generate the NEXT_PUBLIC_API_BASE_URL with given IP
10 | echo -e "\nNEXT_PUBLIC_API_BASE_URL=$1" >> ./.env
11 |
12 | # Generate the SECRET_KEY that will be used by django
13 | echo -e "SECRET_KEY=\"$(tr -dc 'a-z0-9' < /dev/urandom | head -c50)\"" >> ./.env
14 |
15 | # WEB_URL for email redirection and image saving
16 | echo -e "WEB_URL=$1" >> ./.env
17 |
--------------------------------------------------------------------------------
/apps/app/components/ui/dropdowns/types.d.ts:
--------------------------------------------------------------------------------
1 | export type DropdownProps = {
2 | buttonClassName?: string;
3 | className?: string;
4 | customButton?: JSX.Element;
5 | disabled?: boolean;
6 | input?: boolean;
7 | label?: string | JSX.Element;
8 | maxHeight?: "sm" | "rg" | "md" | "lg";
9 | noChevron?: boolean;
10 | optionsClassName?: string;
11 | position?: "right" | "left";
12 | selfPositioned?: boolean;
13 | verticalPosition?: "top" | "bottom";
14 | width?: "auto" | string;
15 | };
16 |
--------------------------------------------------------------------------------
/apps/app/hooks/use-timer.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const TIMER = 30;
4 |
5 | const useTimer = (initialValue: number = TIMER) => {
6 | const [timer, setTimer] = useState(initialValue);
7 |
8 | useEffect(() => {
9 | const interval = setInterval(() => {
10 | setTimer((prev) => prev - 1);
11 | }, 1000);
12 |
13 | return () => clearInterval(interval);
14 | }, []);
15 |
16 | return { timer, setTimer };
17 | };
18 |
19 | export default useTimer;
20 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0008_label_colour.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.14 on 2022-11-29 19:15
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('db', '0007_label_parent'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='label',
15 | name='colour',
16 | field=models.CharField(blank=True, max_length=255),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/apps/app/helpers/common.helper.ts:
--------------------------------------------------------------------------------
1 | export const debounce = (func: any, wait: number, immediate: boolean = false) => {
2 | let timeout: any;
3 |
4 | return function executedFunction(...args: any) {
5 | const later = () => {
6 | timeout = null;
7 | if (!immediate) func(...args);
8 | };
9 |
10 | const callNow = immediate && !timeout;
11 |
12 | clearTimeout(timeout);
13 |
14 | timeout = setTimeout(later, wait);
15 |
16 | if (callNow) func(...args);
17 | };
18 | };
19 |
--------------------------------------------------------------------------------
/apps/app/lib/redirect.ts:
--------------------------------------------------------------------------------
1 | import Router from "next/router";
2 | import type { NextPageContext } from "next";
3 |
4 | const redirect = (context: NextPageContext, target: any) => {
5 | if (context.res) {
6 | // server
7 | // 303: "See other"
8 | context.res.writeHead(301, { Location: target });
9 | context.res.end();
10 | } else {
11 | // In the browser, we just pretend like this never even happened ;)
12 | Router.push(target);
13 | }
14 | };
15 |
16 | export default redirect;
17 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0004_alter_state_sequence.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.14 on 2022-11-10 19:46
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('db', '0003_auto_20221109_2320'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='state',
15 | name='sequence',
16 | field=models.FloatField(default=65535),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0014_alter_workspacememberinvite_unique_together.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.16 on 2023-01-07 05:41
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('db', '0013_auto_20230107_0041'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterUniqueTogether(
14 | name='workspacememberinvite',
15 | unique_together={('email', 'workspace')},
16 | ),
17 | ]
18 |
--------------------------------------------------------------------------------
/apps/app/components/search-listbox/types.d.ts:
--------------------------------------------------------------------------------
1 | type Value = any;
2 |
3 | export type Props = {
4 | title: string;
5 | multiple?: boolean;
6 | options?: Array<{ display: string; element?: JSX.Element; value: Value }>;
7 | onChange: (value: Value) => void;
8 | value: Value;
9 | icon?: JSX.Element;
10 | buttonClassName?: string;
11 | optionsClassName?: string;
12 | width?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
13 | optionsFontsize?: "sm" | "md" | "lg" | "xl" | "2xl";
14 | assignee?: boolean;
15 | };
16 |
--------------------------------------------------------------------------------
/packages/eslint-config-custom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eslint-config-custom",
3 | "version": "0.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "dependencies": {
7 | "eslint": "^7.23.0",
8 | "eslint-config-next": "13.0.0",
9 | "eslint-config-prettier": "^8.3.0",
10 | "eslint-plugin-react": "7.31.8",
11 | "eslint-config-turbo": "latest"
12 | },
13 | "devDependencies": {
14 | "typescript": "^4.7.4"
15 | },
16 | "publishConfig": {
17 | "access": "public"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/apps/app/public/site.webmanifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Plane",
3 | "short_name": "Plane",
4 | "description": "Plane helps you plan your issues, cycles, and product modules.",
5 | "start_url": ".",
6 | "display": "standalone",
7 | "background_color": "#f9fafb",
8 | "theme_color": "#3f76ff",
9 | "icons": [
10 | { "src": "/favicon/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
11 | { "src": "/favicon/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/apps/app/hooks/use-debounce.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const useDebounce = (value: any, milliSeconds: number) => {
4 | const [debouncedValue, setDebouncedValue] = useState(value);
5 |
6 | useEffect(() => {
7 | const handler = setTimeout(() => {
8 | setDebouncedValue(value);
9 | }, milliSeconds);
10 |
11 | return () => {
12 | clearTimeout(handler);
13 | };
14 | }, [value, milliSeconds]);
15 |
16 | return debouncedValue;
17 | };
18 |
19 | export default useDebounce;
20 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0036_alter_workspace_organization_size.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.19 on 2023-07-05 07:59
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('db', '0035_auto_20230704_2225'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='workspace',
15 | name='organization_size',
16 | field=models.CharField(max_length=20),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/apiserver/plane/asgi.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from channels.routing import ProtocolTypeRouter
4 | from django.core.asgi import get_asgi_application
5 |
6 | django_asgi_app = get_asgi_application()
7 |
8 |
9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
10 | # Initialize Django ASGI application early to ensure the AppRegistry
11 | # is populated before importing code that may import ORM models.
12 |
13 |
14 | application = ProtocolTypeRouter(
15 | {
16 | "http": get_asgi_application(),
17 | }
18 | )
19 |
--------------------------------------------------------------------------------
/apps/app/lib/cookie.ts:
--------------------------------------------------------------------------------
1 | export const convertCookieStringToObject = (cookieHeader: string | undefined) => {
2 | const list: any = {};
3 | if (!cookieHeader) return list;
4 |
5 | cookieHeader.split(`;`).forEach(function (cookie) {
6 | // eslint-disable-next-line prefer-const
7 | let [name, ...rest] = cookie.split(`=`);
8 | name = name?.trim();
9 | if (!name) return;
10 | const value = rest.join(`=`).trim();
11 | if (!value) return;
12 | list[name] = decodeURIComponent(value);
13 | });
14 |
15 | return list;
16 | };
17 |
--------------------------------------------------------------------------------
/nginx/nginx.conf.template:
--------------------------------------------------------------------------------
1 | events { }
2 |
3 |
4 | http {
5 | sendfile on;
6 |
7 | server {
8 | listen 80;
9 | root /www/data/;
10 | access_log /var/log/nginx/access.log;
11 |
12 | client_max_body_size ${FILE_SIZE_LIMIT};
13 |
14 | location / {
15 | proxy_pass http://planefrontend:3000/;
16 | }
17 |
18 | location /api/ {
19 | proxy_pass http://planebackend:8000/api/;
20 | }
21 |
22 | location /${BUCKET_NAME}/ {
23 | proxy_pass http://plane-minio:9000/uploads/;
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "repository": "https://github.com/makeplane/plane.git",
3 | "license": "MIT",
4 | "private": true,
5 | "workspaces": [
6 | "apps/*",
7 | "packages/*"
8 | ],
9 | "scripts": {
10 | "build": "turbo run build",
11 | "dev": "turbo run dev",
12 | "start": "turbo run start",
13 | "lint": "turbo run lint",
14 | "clean": "turbo run clean"
15 | },
16 | "devDependencies": {
17 | "eslint-config-custom": "*",
18 | "prettier": "latest",
19 | "turbo": "latest"
20 | },
21 | "packageManager": "yarn@1.22.19"
22 | }
23 |
--------------------------------------------------------------------------------
/apps/app/components/ui/text-area/types.d.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import type { UseFormRegister, RegisterOptions, FieldError } from "react-hook-form";
3 |
4 | export interface Props extends React.ComponentPropsWithoutRef<"textarea"> {
5 | label?: string;
6 | value?: string | number | readonly string[];
7 | name: string;
8 | register?: UseFormRegister;
9 | mode?: "primary" | "transparent" | "secondary" | "disabled";
10 | validations?: RegisterOptions;
11 | error?: FieldError | Merge>;
12 | noPadding?: boolean;
13 | }
14 |
--------------------------------------------------------------------------------
/apps/app/components/ui/input/types.d.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import type { UseFormRegister, RegisterOptions } from "react-hook-form";
3 |
4 | export interface Props extends React.ComponentPropsWithoutRef<"input"> {
5 | label?: string;
6 | name: string;
7 | value?: string | number | readonly string[];
8 | mode?: "primary" | "transparent" | "trueTransparent" | "secondary" | "disabled";
9 | register?: UseFormRegister;
10 | validations?: RegisterOptions;
11 | error?: any;
12 | className?: string;
13 | size?: "rg" | "lg";
14 | fullWidth?: boolean;
15 | }
16 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0026_alter_projectmember_view_props.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.18 on 2023-04-04 21:50
2 |
3 | from django.db import migrations, models
4 | import plane.db.models.project
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('db', '0025_auto_20230331_0203'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='projectmember',
16 | name='view_props',
17 | field=models.JSONField(default=plane.db.models.project.get_default_props),
18 | ),
19 | ]
--------------------------------------------------------------------------------
/apps/app/components/cycles/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./cycles-list";
2 | export * from "./active-cycle-details";
3 | export * from "./active-cycle-stats";
4 | export * from "./cycles-list-gantt-chart";
5 | export * from "./cycles-view";
6 | export * from "./delete-cycle-modal";
7 | export * from "./form";
8 | export * from "./gantt-chart";
9 | export * from "./modal";
10 | export * from "./select";
11 | export * from "./sidebar";
12 | export * from "./single-cycle-card";
13 | export * from "./single-cycle-list";
14 | export * from "./transfer-issues-modal";
15 | export * from "./transfer-issues";
16 |
--------------------------------------------------------------------------------
/apps/app/helpers/emoji.helper.ts:
--------------------------------------------------------------------------------
1 | export const getRandomEmoji = () => {
2 | const emojis = [
3 | "8986",
4 | "9200",
5 | "128204",
6 | "127773",
7 | "127891",
8 | "127947",
9 | "128076",
10 | "128077",
11 | "128187",
12 | "128188",
13 | "128512",
14 | "128522",
15 | "128578",
16 | ];
17 |
18 | return emojis[Math.floor(Math.random() * emojis.length)];
19 | };
20 |
21 | export const renderEmoji = (emoji: string) => {
22 | if (!emoji) return;
23 |
24 | return isNaN(parseInt(emoji)) ? emoji : String.fromCodePoint(parseInt(emoji));
25 | };
26 |
--------------------------------------------------------------------------------
/packages/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ui",
3 | "version": "0.0.0",
4 | "main": "./index.tsx",
5 | "types": "./index.tsx",
6 | "license": "MIT",
7 | "scripts": {
8 | "lint": "eslint *.ts*"
9 | },
10 | "devDependencies": {
11 | "@types/react": "^18.0.17",
12 | "@types/react-dom": "^18.0.6",
13 | "@typescript-eslint/eslint-plugin": "^5.51.0",
14 | "classnames": "^2.3.2",
15 | "eslint": "^7.32.0",
16 | "eslint-config-custom": "*",
17 | "next": "12.3.2",
18 | "react": "^18.2.0",
19 | "tsconfig": "*",
20 | "typescript": "4.7.4"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/apiserver/templates/admin/base_site.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/base.html" %}{% load i18n %}
2 |
3 | {% block title %}{{ title }} | {% trans 'plane Admin' %} {% endblock %}
4 |
5 | {% block branding %}
6 |
7 |
20 | {% trans 'Plane Django Admin' %}
21 |
22 |
23 | {% endblock %}{% block nav-global %}{% endblock %}
24 |
--------------------------------------------------------------------------------
/apiserver/templates/emails/exports/analytics.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hey there,
4 | Your requested data export from Plane Analytics is now ready. The information has been compiled into a CSV format for your convenience.
5 | Please find the attachment and download the CSV file. This file can easily be imported into any spreadsheet program for further analysis.
6 | If you require any assistance or have any questions, please do not hesitate to contact us.
7 | Thank you
8 |
9 |
--------------------------------------------------------------------------------
/apps/app/components/icons/backlog-state-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import type { Props } from "./types";
4 |
5 | export const BacklogStateIcon: React.FC = ({
6 | width = "20",
7 | height = "20",
8 | className,
9 | color = "rgb(var(--color-text-200))",
10 | }) => (
11 |
19 |
20 |
21 | );
22 |
--------------------------------------------------------------------------------
/apps/app/components/icons/completed-cycle-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import type { Props } from "./types";
4 |
5 | export const CompletedCycleIcon: React.FC = ({
6 | width = "24",
7 | height = "24",
8 | className,
9 | color = "black",
10 | }) => (
11 |
12 |
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/apps/app/hooks/use-outside-click-detector.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 |
3 | const useOutsideClickDetector = (ref: React.RefObject, callback: () => void) => {
4 | const handleClick = (event: MouseEvent) => {
5 | if (ref.current && !ref.current.contains(event.target as Node)) {
6 | callback();
7 | }
8 | };
9 |
10 | useEffect(() => {
11 | document.addEventListener("click", handleClick);
12 |
13 | return () => {
14 | document.removeEventListener("click", handleClick);
15 | };
16 | });
17 | };
18 |
19 | export default useOutsideClickDetector;
20 |
--------------------------------------------------------------------------------
/packages/ui/index.tsx:
--------------------------------------------------------------------------------
1 | // import * as React from "react";
2 | // components
3 | // export * from "./breadcrumbs";
4 | // export * from "./button";
5 | // export * from "./custom-listbox";
6 | // export * from "./custom-menu";
7 | // export * from "./custom-select";
8 | // export * from "./empty-space";
9 | // export * from "./header-button";
10 | // export * from "./input";
11 | // export * from "./loader";
12 | // export * from "./outline-button";
13 | // export * from "./select";
14 | // export * from "./spinner";
15 | // export * from "./text-area";
16 | // export * from "./tooltip";
17 | export * from "./button";
18 |
--------------------------------------------------------------------------------
/apps/app/components/issues/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./comment";
2 | export * from "./sidebar-select";
3 | export * from "./view-select";
4 | export * from "./activity";
5 | export * from "./delete-issue-modal";
6 | export * from "./description-form";
7 | export * from "./form";
8 | export * from "./main-content";
9 | export * from "./modal";
10 | export * from "./my-issues-list-item";
11 | export * from "./parent-issues-list-modal";
12 | export * from "./sidebar";
13 | export * from "./sub-issues-list";
14 | export * from "./attachment-upload";
15 | export * from "./attachments";
16 | export * from "./delete-attachment-modal";
17 |
--------------------------------------------------------------------------------
/apps/app/helpers/color.helper.ts:
--------------------------------------------------------------------------------
1 | export type TRgb = { r: number; g: number; b: number };
2 |
3 | export const hexToRgb = (hex: string): TRgb => {
4 | const r = parseInt(hex.slice(1, 3), 16);
5 | const g = parseInt(hex.slice(3, 5), 16);
6 | const b = parseInt(hex.slice(5, 7), 16);
7 |
8 | return { r, g, b };
9 | };
10 |
11 | export const rgbToHex = (rgb: TRgb): string => {
12 | const { r, g, b } = rgb;
13 |
14 | const hexR = r.toString(16).padStart(2, "0");
15 | const hexG = g.toString(16).padStart(2, "0");
16 | const hexB = b.toString(16).padStart(2, "0");
17 |
18 | return `#${hexR}${hexG}${hexB}`;
19 | };
20 |
--------------------------------------------------------------------------------
/packages/tsconfig/base.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Default",
4 | "compilerOptions": {
5 | "composite": false,
6 | "declaration": true,
7 | "declarationMap": true,
8 | "esModuleInterop": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "inlineSources": false,
11 | "isolatedModules": true,
12 | "moduleResolution": "node",
13 | "noUnusedLocals": false,
14 | "noUnusedParameters": false,
15 | "preserveWatchOutput": true,
16 | "skipLibCheck": true,
17 | "strict": true
18 | },
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0006_alter_cycle_status.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.14 on 2022-11-16 14:54
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('db', '0005_auto_20221114_2127'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='cycle',
15 | name='status',
16 | field=models.CharField(choices=[('draft', 'Draft'), ('started', 'Started'), ('completed', 'Completed')], default='draft', max_length=255, verbose_name='Cycle Status'),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0007_label_parent.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.14 on 2022-11-28 20:00
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('db', '0006_alter_cycle_status'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='label',
16 | name='parent',
17 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_label', to='db.label'),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/apiserver/plane/utils/html_processor.py:
--------------------------------------------------------------------------------
1 | from io import StringIO
2 | from html.parser import HTMLParser
3 |
4 | class MLStripper(HTMLParser):
5 | """
6 | Markup Language Stripper
7 | """
8 | def __init__(self):
9 | super().__init__()
10 | self.reset()
11 | self.strict = False
12 | self.convert_charrefs= True
13 | self.text = StringIO()
14 |
15 | def handle_data(self, d):
16 | self.text.write(d)
17 |
18 | def get_data(self):
19 | return self.text.getvalue()
20 |
21 | def strip_tags(html):
22 | s = MLStripper()
23 | s.feed(html)
24 | return s.get_data()
25 |
--------------------------------------------------------------------------------
/apiserver/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == '__main__':
6 | os.environ.setdefault(
7 | 'DJANGO_SETTINGS_MODULE',
8 | 'plane.settings.production')
9 | try:
10 | from django.core.management import execute_from_command_line
11 | except ImportError as exc:
12 | raise ImportError(
13 | "Couldn't import Django. Are you sure it's installed and "
14 | "available on your PYTHONPATH environment variable? Did you "
15 | "forget to activate a virtual environment?"
16 | ) from exc
17 | execute_from_command_line(sys.argv)
18 |
--------------------------------------------------------------------------------
/apiserver/plane/api/serializers/integration/base.py:
--------------------------------------------------------------------------------
1 | # Module imports
2 | from plane.api.serializers import BaseSerializer
3 | from plane.db.models import Integration, WorkspaceIntegration
4 |
5 |
6 | class IntegrationSerializer(BaseSerializer):
7 | class Meta:
8 | model = Integration
9 | fields = "__all__"
10 | read_only_fields = [
11 | "verified",
12 | ]
13 |
14 |
15 | class WorkspaceIntegrationSerializer(BaseSerializer):
16 | integration_detail = IntegrationSerializer(read_only=True, source="integration")
17 |
18 | class Meta:
19 | model = WorkspaceIntegration
20 | fields = "__all__"
21 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0032_auto_20230520_2015.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.19 on 2023-05-20 14:45
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('db', '0031_analyticview'),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameField(
14 | model_name='project',
15 | old_name='icon',
16 | new_name='emoji',
17 | ),
18 | migrations.AddField(
19 | model_name='project',
20 | name='icon_prop',
21 | field=models.JSONField(null=True),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/apps/app/components/ui/loader.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | type Props = {
4 | children: React.ReactNode;
5 | className?: string;
6 | };
7 |
8 | const Loader = ({ children, className = "" }: Props) => (
9 |
10 | {children}
11 |
12 | );
13 |
14 | type ItemProps = {
15 | height?: string;
16 | width?: string;
17 | };
18 |
19 | const Item: React.FC = ({ height = "auto", width = "auto" }) => (
20 |
21 | );
22 |
23 | Loader.Item = Item;
24 |
25 | export { Loader };
26 |
--------------------------------------------------------------------------------
/apiserver/plane/api/serializers/importer.py:
--------------------------------------------------------------------------------
1 | # Module imports
2 | from .base import BaseSerializer
3 | from .user import UserLiteSerializer
4 | from .project import ProjectLiteSerializer
5 | from .workspace import WorkspaceLiteSerializer
6 | from plane.db.models import Importer
7 |
8 |
9 | class ImporterSerializer(BaseSerializer):
10 | initiated_by_detail = UserLiteSerializer(source="initiated_by", read_only=True)
11 | project_detail = ProjectLiteSerializer(source="project", read_only=True)
12 | workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
13 |
14 | class Meta:
15 | model = Importer
16 | fields = "__all__"
17 |
--------------------------------------------------------------------------------
/apps/app/helpers/attachment.helper.ts:
--------------------------------------------------------------------------------
1 | export const getFileExtension = (filename: string) =>
2 | filename.slice(((filename.lastIndexOf(".") - 1) >>> 0) + 2);
3 |
4 | export const getFileName = (fileName: string) => {
5 | const dotIndex = fileName.lastIndexOf(".");
6 |
7 | const nameWithoutExtension = fileName.substring(0, dotIndex);
8 |
9 | return nameWithoutExtension;
10 | };
11 |
12 | export const convertBytesToSize = (bytes: number) => {
13 | let size;
14 |
15 | if (bytes < 1024 * 1024) {
16 | size = Math.round(bytes / 1024) + " KB";
17 | } else {
18 | size = Math.round(bytes / (1024 * 1024)) + " MB";
19 | }
20 |
21 | return size;
22 | };
23 |
--------------------------------------------------------------------------------
/apiserver/plane/utils/issue_search.py:
--------------------------------------------------------------------------------
1 | # Python imports
2 | import re
3 |
4 | # Django imports
5 | from django.db.models import Q
6 |
7 | # Module imports
8 | from plane.db.models import Issue
9 |
10 |
11 | def search_issues(query, queryset):
12 | fields = ["name", "sequence_id"]
13 | q = Q()
14 | for field in fields:
15 | if field == "sequence_id":
16 | sequences = re.findall(r"\d+\.\d+|\d+", query)
17 | for sequence_id in sequences:
18 | q |= Q(**{"sequence_id": sequence_id})
19 | else:
20 | q |= Q(**{f"{field}__icontains": query})
21 | return queryset.filter(
22 | q,
23 | ).distinct()
24 |
--------------------------------------------------------------------------------
/packages/tsconfig/nextjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Next.js",
4 | "extends": "./base.json",
5 | "compilerOptions": {
6 | "target": "es5",
7 | "lib": ["dom", "dom.iterable", "esnext"],
8 | "allowJs": true,
9 | "skipLibCheck": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "noEmit": true,
13 | "incremental": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "jsx": "preserve"
19 | },
20 | "include": ["src", "next-env.d.ts"],
21 | "exclude": ["node_modules"]
22 | }
23 |
--------------------------------------------------------------------------------
/apps/app/components/icons/upcoming-cycle-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import type { Props } from "./types";
4 |
5 | export const UpcomingCycleIcon: React.FC = ({
6 | width = "24",
7 | height = "24",
8 | className,
9 | color = "black",
10 | }) => (
11 |
12 |
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/apps/app/components/icons/heartbeat-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import type { Props } from "./types";
4 |
5 | export const HeartbeatIcon: React.FC = ({
6 | width = "24",
7 | height = "24",
8 | color = "rgb(var(--color-text-200))",
9 | className,
10 | }) => (
11 |
19 |
26 |
27 | );
28 |
--------------------------------------------------------------------------------
/apps/app/components/ui/graphs/pie-graph.tsx:
--------------------------------------------------------------------------------
1 | // nivo
2 | import { PieSvgProps, ResponsivePie } from "@nivo/pie";
3 | // types
4 | import { TGraph } from "./types";
5 | // constants
6 | import { CHARTS_THEME, DEFAULT_MARGIN } from "constants/graph";
7 |
8 | export const PieGraph: React.FC, "height" | "width">> = ({
9 | height = "400px",
10 | width = "100%",
11 | margin,
12 | theme,
13 | ...rest
14 | }) => (
15 |
16 |
22 |
23 | );
24 |
--------------------------------------------------------------------------------
/apps/app/hooks/use-issues.tsx:
--------------------------------------------------------------------------------
1 | import useSWR from "swr";
2 | // services
3 | import userService from "services/user.service";
4 | // types
5 | import type { IIssue } from "types";
6 | // fetch-keys
7 | import { USER_ISSUE } from "constants/fetch-keys";
8 |
9 | const useIssues = (workspaceSlug: string | undefined) => {
10 | // API Fetching
11 | const { data: myIssues, mutate: mutateMyIssues } = useSWR(
12 | workspaceSlug ? USER_ISSUE(workspaceSlug as string) : null,
13 | workspaceSlug ? () => userService.userIssues(workspaceSlug as string) : null
14 | );
15 |
16 | return {
17 | myIssues: myIssues,
18 | mutateMyIssues,
19 | };
20 | };
21 |
22 | export default useIssues;
23 |
--------------------------------------------------------------------------------
/packages/eslint-config-custom/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["next", "turbo", "prettier"],
3 | parser: "@typescript-eslint/parser",
4 | plugins: ["react", "@typescript-eslint"],
5 | settings: {
6 | next: {
7 | rootDir: ["app/", "docs/", "packages/*/"],
8 | },
9 | },
10 | rules: {
11 | "@next/next/no-html-link-for-pages": "off",
12 | "react/jsx-key": "off",
13 | "prefer-const": "error",
14 | "no-irregular-whitespace": "error",
15 | "no-trailing-spaces": "error",
16 | "no-duplicate-imports": "error",
17 | "arrow-body-style": ["error", "as-needed"],
18 | "react/self-closing-comp": ["error", { component: true, html: true }],
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/apps/app/helpers/state.helper.ts:
--------------------------------------------------------------------------------
1 | // types
2 | import { IState, IStateResponse } from "types";
3 |
4 | export const orderStateGroups = (unorderedStateGroups: IStateResponse) =>
5 | Object.assign(
6 | { backlog: [], unstarted: [], started: [], completed: [], cancelled: [] },
7 | unorderedStateGroups
8 | );
9 |
10 | export const getStatesList = (stateGroups: any): IState[] => {
11 | // order the unordered state groups first
12 | const orderedStateGroups = orderStateGroups(stateGroups);
13 |
14 | // extract states from the groups and return them
15 | return Object.keys(orderedStateGroups)
16 | .map((group) => [...orderedStateGroups[group].map((state: IState) => state)])
17 | .flat();
18 | };
19 |
--------------------------------------------------------------------------------
/apps/app/services/web-waitlist.service.ts:
--------------------------------------------------------------------------------
1 | // services
2 | import APIService from "services/api.service";
3 |
4 | // types
5 | import { IWebWaitListResponse } from "types";
6 |
7 | class WebWailtListServices extends APIService {
8 | constructor() {
9 | const origin = typeof window !== "undefined" ? window.location.origin || "" : "";
10 | super(origin);
11 | }
12 |
13 | async create({ email }: { email: string }): Promise {
14 | return this.post(`/api/web-waitlist`, { email: email })
15 | .then((response) => response?.data)
16 | .catch((error) => {
17 | throw error?.response;
18 | });
19 | }
20 | }
21 |
22 | export default new WebWailtListServices();
23 |
--------------------------------------------------------------------------------
/apps/app/components/dnd/StrictModeDroppable.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 |
3 | // react beautiful dnd
4 | import { Droppable, DroppableProps } from "react-beautiful-dnd";
5 |
6 | const StrictModeDroppable = ({ children, ...props }: DroppableProps) => {
7 | const [enabled, setEnabled] = useState(false);
8 |
9 | useEffect(() => {
10 | const animation = requestAnimationFrame(() => setEnabled(true));
11 |
12 | return () => {
13 | cancelAnimationFrame(animation);
14 | setEnabled(false);
15 | };
16 | }, []);
17 |
18 | if (!enabled) return null;
19 |
20 | return {children} ;
21 | };
22 |
23 | export default StrictModeDroppable;
24 |
--------------------------------------------------------------------------------
/apps/app/constants/seo-variables.ts:
--------------------------------------------------------------------------------
1 | export const SITE_NAME = "Plane | Simple, extensible, open-source project management tool.";
2 | export const SITE_TITLE = "Plane | Simple, extensible, open-source project management tool.";
3 | export const SITE_DESCRIPTION =
4 | "Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.";
5 | export const SITE_KEYWORDS =
6 | "software development, plan, ship, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration";
7 | export const SITE_URL = "https://app.plane.so/";
8 | export const TWITTER_USER_NAME = "Plane | Simple, extensible, open-source project management tool.";
9 |
--------------------------------------------------------------------------------
/apiserver/plane/urls.py:
--------------------------------------------------------------------------------
1 | """plane URL Configuration
2 |
3 | """
4 |
5 | # from django.contrib import admin
6 | from django.urls import path, include, re_path
7 | from django.views.generic import TemplateView
8 |
9 | from django.conf import settings
10 |
11 | # from django.conf.urls.static import static
12 |
13 | urlpatterns = [
14 | # path("admin/", admin.site.urls),
15 | path("", TemplateView.as_view(template_name="index.html")),
16 | path("api/", include("plane.api.urls")),
17 | path("", include("plane.web.urls")),
18 | ]
19 |
20 |
21 | if settings.DEBUG:
22 | import debug_toolbar
23 |
24 | urlpatterns = [
25 | re_path(r"^__debug__/", include(debug_toolbar.urls)),
26 | ] + urlpatterns
27 |
--------------------------------------------------------------------------------
/apps/app/components/ui/graphs/scatter-plot-graph.tsx:
--------------------------------------------------------------------------------
1 | // nivo
2 | import { ResponsiveScatterPlot, ScatterPlotSvgProps } from "@nivo/scatterplot";
3 | // types
4 | import { TGraph } from "./types";
5 | // constants
6 | import { CHARTS_THEME, DEFAULT_MARGIN } from "constants/graph";
7 |
8 | export const ScatterPlotGraph: React.FC<
9 | TGraph & Omit, "height" | "width">
10 | > = ({ height = "400px", width = "100%", margin, theme, ...rest }) => (
11 |
12 |
18 |
19 | );
20 |
--------------------------------------------------------------------------------
/apiserver/plane/settings/redis.py:
--------------------------------------------------------------------------------
1 | import os
2 | import redis
3 | from django.conf import settings
4 | from urllib.parse import urlparse
5 |
6 |
7 | def redis_instance():
8 | # connect to redis
9 | if (
10 | settings.DOCKERIZED
11 | or os.environ.get("DJANGO_SETTINGS_MODULE", "plane.settings.production")
12 | == "plane.settings.local"
13 | ):
14 | ri = redis.Redis.from_url(settings.REDIS_URL, db=0)
15 | else:
16 | url = urlparse(settings.REDIS_URL)
17 | ri = redis.Redis(
18 | host=url.hostname,
19 | port=url.port,
20 | password=url.password,
21 | ssl=True,
22 | ssl_cert_reqs=None,
23 | )
24 |
25 | return ri
26 |
--------------------------------------------------------------------------------
/nginx/nginx-single-docker-image.conf:
--------------------------------------------------------------------------------
1 | upstream plane {
2 | server localhost:80;
3 | }
4 |
5 | error_log /var/log/nginx/error.log;
6 |
7 | server {
8 | listen 80;
9 | root /www/data/;
10 | access_log /var/log/nginx/access.log;
11 | location / {
12 | proxy_pass http://localhost:3000/;
13 | proxy_set_header Host $host;
14 | proxy_set_header X-Real-IP $remote_addr;
15 | }
16 | location /api/ {
17 | proxy_pass http://localhost:8000/api/;
18 | proxy_set_header Host $host;
19 | proxy_set_header X-Real-IP $remote_addr;
20 | }
21 | error_page 500 502 503 504 /50x.html;
22 | location = /50x.html {
23 | root /usr/share/nginx/html;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/apiserver/templates/emails/auth/forgot_password.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dear {{first_name}},
7 | We received a request to reset your password for your Plane account.
8 |
9 | To proceed with resetting your password, please click on the link below:
10 |
11 | {{forgot_password_url}}
12 |
13 | If you didn't request to reset your password, please ignore this email. Your account will remain secure.
14 |
15 | If you have any questions or need further assistance, please contact our support team.
16 |
17 | Thank you for using Plane.
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0005_auto_20221114_2127.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.14 on 2022-11-14 15:57
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('db', '0004_alter_state_sequence'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='cycle',
15 | name='end_date',
16 | field=models.DateField(blank=True, null=True, verbose_name='End Date'),
17 | ),
18 | migrations.AlterField(
19 | model_name='cycle',
20 | name='start_date',
21 | field=models.DateField(blank=True, null=True, verbose_name='Start Date'),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/apps/app/constants/calendar.ts:
--------------------------------------------------------------------------------
1 | export const MONTHS_LIST = [
2 | { value: 1, label: "January" },
3 | { value: 2, label: "February" },
4 | { value: 3, label: "March" },
5 | { value: 4, label: "April" },
6 | { value: 5, label: "May" },
7 | { value: 6, label: "June" },
8 | { value: 7, label: "July" },
9 | { value: 8, label: "August" },
10 | { value: 9, label: "September" },
11 | { value: 10, label: "October" },
12 | { value: 11, label: "November" },
13 | { value: 12, label: "December" },
14 | ];
15 |
16 | export const YEARS_LIST = [
17 | { value: "2021", label: "2021" },
18 | { value: "2022", label: "2022" },
19 | { value: "2023", label: "2023" },
20 | { value: "2024", label: "2024" },
21 | { value: "2025", label: "2025" },
22 | ];
23 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0019_auto_20230131_0049.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.16 on 2023-01-30 19:19
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('db', '0018_auto_20230130_0119'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='issueactivity',
15 | name='new_value',
16 | field=models.TextField(blank=True, null=True, verbose_name='New Value'),
17 | ),
18 | migrations.AlterField(
19 | model_name='issueactivity',
20 | name='old_value',
21 | field=models.TextField(blank=True, null=True, verbose_name='Old Value'),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/apps/app/helpers/graph.helper.ts:
--------------------------------------------------------------------------------
1 | export const generateYAxisTickValues = (data: number[]) => {
2 | if (!data || !Array.isArray(data) || data.length === 0) return [];
3 |
4 | const minValue = 0;
5 | const maxValue = Math.max(...data);
6 |
7 | const valueRange = maxValue - minValue;
8 |
9 | let tickInterval = 1;
10 |
11 | if (valueRange < 10) tickInterval = 1;
12 | else if (valueRange < 20) tickInterval = 2;
13 | else if (valueRange < 50) tickInterval = 5;
14 | else tickInterval = (Math.ceil(valueRange / 100) * 100) / 10;
15 |
16 | const tickValues: number[] = [];
17 | let tickValue = minValue;
18 | while (tickValue <= maxValue) {
19 | tickValues.push(tickValue);
20 | tickValue += tickInterval;
21 | }
22 |
23 | return tickValues;
24 | };
25 |
--------------------------------------------------------------------------------
/apps/app/types/importer/github-importer.d.ts:
--------------------------------------------------------------------------------
1 | export interface IGithubServiceImportFormData {
2 | metadata: {
3 | owner: string;
4 | name: string;
5 | repository_id: number;
6 | url: string;
7 | };
8 | data: {
9 | users: {
10 | username: string;
11 | import: boolean | "invite" | "map";
12 | email: string;
13 | }[];
14 | };
15 | config: {
16 | sync: boolean;
17 | };
18 | project_id: string;
19 | }
20 |
21 | export interface IGithubRepoCollaborator {
22 | avatar_url: string;
23 | html_url: string;
24 | id: number;
25 | login: string;
26 | url: string;
27 | }
28 |
29 | export interface IGithubRepoInfo {
30 | issue_count: number;
31 | labels: number;
32 | collaborators: IGithubRepoCollaborator[];
33 | }
34 |
--------------------------------------------------------------------------------
/apiserver/plane/db/management/commands/wait_for_db.py:
--------------------------------------------------------------------------------
1 | import time
2 | from django.db import connections
3 | from django.db.utils import OperationalError
4 | from django.core.management import BaseCommand
5 |
6 | class Command(BaseCommand):
7 | """Django command to pause execution until db is available"""
8 |
9 | def handle(self, *args, **options):
10 | self.stdout.write('Waiting for database...')
11 | db_conn = None
12 | while not db_conn:
13 | try:
14 | db_conn = connections['default']
15 | except OperationalError:
16 | self.stdout.write('Database unavailable, waititng 1 second...')
17 | time.sleep(1)
18 |
19 | self.stdout.write(self.style.SUCCESS('Database available!'))
20 |
--------------------------------------------------------------------------------
/apiserver/bin/user_script.py:
--------------------------------------------------------------------------------
1 | import os, sys
2 | import uuid
3 |
4 | sys.path.append("/code")
5 |
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
7 | import django
8 |
9 | django.setup()
10 |
11 | from plane.db.models import User
12 |
13 |
14 | def populate():
15 | default_email = os.environ.get("DEFAULT_EMAIL", "captain@plane.so")
16 | default_password = os.environ.get("DEFAULT_PASSWORD", "password123")
17 |
18 | if not User.objects.filter(email=default_email).exists():
19 | user = User.objects.create(email=default_email, username=uuid.uuid4().hex)
20 | user.set_password(default_password)
21 | user.save()
22 | print("User created")
23 |
24 | print("Success")
25 |
26 |
27 | if __name__ == "__main__":
28 | populate()
29 |
--------------------------------------------------------------------------------
/apiserver/plane/api/views/release.py:
--------------------------------------------------------------------------------
1 | # Third party imports
2 | from rest_framework.response import Response
3 | from rest_framework import status
4 | from sentry_sdk import capture_exception
5 |
6 | # Module imports
7 | from .base import BaseAPIView
8 | from plane.utils.integrations.github import get_release_notes
9 |
10 |
11 | class ReleaseNotesEndpoint(BaseAPIView):
12 | def get(self, request):
13 | try:
14 | release_notes = get_release_notes()
15 | return Response(release_notes, status=status.HTTP_200_OK)
16 | except Exception as e:
17 | capture_exception(e)
18 | return Response(
19 | {"error": "Something went wrong please try again later"},
20 | status=status.HTTP_400_BAD_REQUEST,
21 | )
22 |
--------------------------------------------------------------------------------
/apiserver/plane/utils/imports.py:
--------------------------------------------------------------------------------
1 | import pkgutil
2 | import six
3 |
4 |
5 | def import_submodules(context, root_module, path):
6 | """
7 | Import all submodules and register them in the ``context`` namespace.
8 | >>> import_submodules(locals(), __name__, __path__)
9 | """
10 | for loader, module_name, is_pkg in pkgutil.walk_packages(
11 | path,
12 | root_module +
13 | '.'):
14 | # this causes a Runtime error with model conflicts
15 | # module = loader.find_module(module_name).load_module(module_name)
16 | module = __import__(module_name, globals(), locals(), ['__name__'])
17 | for k, v in six.iteritems(vars(module)):
18 | if not k.startswith('_'):
19 | context[k] = v
20 | context[module_name] = module
--------------------------------------------------------------------------------
/apps/app/hooks/use-workspaces.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router";
2 |
3 | import useSWR from "swr";
4 |
5 | // services
6 | import workspaceService from "services/workspace.service";
7 | // fetch-keys
8 | import { USER_WORKSPACES } from "constants/fetch-keys";
9 |
10 | const useWorkspaces = () => {
11 | // router
12 | const router = useRouter();
13 | const { workspaceSlug } = router.query;
14 | // API to fetch user information
15 | const { data, error, mutate } = useSWR(USER_WORKSPACES, () => workspaceService.userWorkspaces());
16 | // active workspace
17 | const activeWorkspace = data?.find((w) => w.slug === workspaceSlug);
18 |
19 | return {
20 | workspaces: data,
21 | error,
22 | activeWorkspace,
23 | mutateWorkspaces: mutate,
24 | };
25 | };
26 |
27 | export default useWorkspaces;
28 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0003_auto_20221109_2320.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.14 on 2022-11-09 17:50
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('db', '0002_auto_20221104_2239'),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterField(
16 | model_name='issueproperty',
17 | name='user',
18 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issue_property_user', to=settings.AUTH_USER_MODEL),
19 | ),
20 | migrations.AlterUniqueTogether(
21 | name='issueproperty',
22 | unique_together={('user', 'project')},
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0009_auto_20221208_0310.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.14 on 2022-12-13 17:58
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('db', '0008_label_colour'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='projectmember',
15 | name='view_props',
16 | field=models.JSONField(null=True),
17 | ),
18 | migrations.AddField(
19 | model_name='state',
20 | name='group',
21 | field=models.CharField(choices=[('backlog', 'Backlog'), ('unstarted', 'Unstarted'), ('started', 'Started'), ('completed', 'Completed'), ('cancelled', 'Cancelled')], default='backlog', max_length=20),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/apps/app/components/analytics/select/y-axis.tsx:
--------------------------------------------------------------------------------
1 | // ui
2 | import { CustomSelect } from "components/ui";
3 | // types
4 | import { TYAxisValues } from "types";
5 | // constants
6 | import { ANALYTICS_Y_AXIS_VALUES } from "constants/analytics";
7 |
8 | type Props = {
9 | value: TYAxisValues;
10 | onChange: () => void;
11 | };
12 |
13 | export const SelectYAxis: React.FC = ({ value, onChange }) => (
14 | {ANALYTICS_Y_AXIS_VALUES.find((v) => v.value === value)?.label ?? "None"}}
17 | onChange={onChange}
18 | width="w-full"
19 | >
20 | {ANALYTICS_Y_AXIS_VALUES.map((item) => (
21 |
22 | {item.label}
23 |
24 | ))}
25 |
26 | );
27 |
--------------------------------------------------------------------------------
/apps/app/sentry.client.config.js:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry on the browser.
2 | // The config you add here will be used whenever a page is visited.
3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
4 |
5 | import * as Sentry from "@sentry/nextjs";
6 |
7 | const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN;
8 |
9 | Sentry.init({
10 | dsn: SENTRY_DSN,
11 | environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT || "development",
12 | // Adjust this value in production, or use tracesSampler for greater control
13 | tracesSampleRate: 1.0,
14 | // ...
15 | // Note: if you want to override the automatic release value, do not set a
16 | // `release` value here - use the environment variable `SENTRY_RELEASE`, so
17 | // that it will also get attached to your source maps
18 | });
19 |
--------------------------------------------------------------------------------
/apps/app/pages/api/slack-redirect.ts:
--------------------------------------------------------------------------------
1 | // pages/api/slack/authorize.js
2 | import axios from "axios";
3 | import { NextApiRequest, NextApiResponse } from "next";
4 |
5 | export default async function handleSlackAuthorize(req: NextApiRequest, res: NextApiResponse) {
6 | const { code } = req.body;
7 |
8 | if (!code || code === "") return res.status(400).json({ message: "Code is required" });
9 |
10 | const response = await axios({
11 | method: "post",
12 | url: "https://slack.com/api/oauth.v2.access",
13 | params: {
14 | client_id: process.env.NEXT_PUBLIC_SLACK_CLIENT_ID,
15 | client_secret: process.env.NEXT_PUBLIC_SLACK_CLIENT_SECRET,
16 | code,
17 | },
18 | });
19 |
20 | // if (response?.data?.ok)
21 | res.status(200).json(response.data);
22 | // else res.status(404).json(response.data);
23 | }
24 |
--------------------------------------------------------------------------------
/apps/app/sentry.server.config.js:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry on the server.
2 | // The config you add here will be used whenever the server handles a request.
3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
4 |
5 | import * as Sentry from "@sentry/nextjs";
6 |
7 | const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN;
8 |
9 | Sentry.init({
10 | dsn: SENTRY_DSN,
11 | environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT || "development",
12 | // Adjust this value in production, or use tracesSampler for greater control
13 | tracesSampleRate: 1.0,
14 | // ...
15 | // Note: if you want to override the automatic release value, do not set a
16 | // `release` value here - use the environment variable `SENTRY_RELEASE`, so
17 | // that it will also get attached to your source maps
18 | });
19 |
--------------------------------------------------------------------------------
/apps/app/hooks/gantt-chart/issue-view.tsx:
--------------------------------------------------------------------------------
1 | import useSWR from "swr";
2 |
3 | // services
4 | import issuesService from "services/issues.service";
5 | // fetch-keys
6 | import { PROJECT_ISSUES_LIST_WITH_PARAMS } from "constants/fetch-keys";
7 |
8 | const useGanttChartIssues = (workspaceSlug: string | undefined, projectId: string | undefined) => {
9 | // all issues under the workspace and project
10 | const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
11 | workspaceSlug && projectId ? PROJECT_ISSUES_LIST_WITH_PARAMS(projectId) : null,
12 | workspaceSlug && projectId
13 | ? () => issuesService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString())
14 | : null
15 | );
16 |
17 | return {
18 | ganttIssues,
19 | mutateGanttIssues,
20 | };
21 | };
22 |
23 | export default useGanttChartIssues;
24 |
--------------------------------------------------------------------------------
/apiserver/plane/db/models/analytic.py:
--------------------------------------------------------------------------------
1 | # Django models
2 | from django.db import models
3 | from django.conf import settings
4 |
5 | from .base import BaseModel
6 |
7 |
8 | class AnalyticView(BaseModel):
9 | workspace = models.ForeignKey(
10 | "db.Workspace", related_name="analytics", on_delete=models.CASCADE
11 | )
12 | name = models.CharField(max_length=255)
13 | description = models.TextField(blank=True)
14 | query = models.JSONField()
15 | query_dict = models.JSONField(default=dict)
16 |
17 | class Meta:
18 | verbose_name = "Analytic"
19 | verbose_name_plural = "Analytics"
20 | db_table = "analytic_views"
21 | ordering = ("-created_at",)
22 |
23 | def __str__(self):
24 | """Return name of the analytic view"""
25 | return f"{self.name} <{self.workspace.name}>"
26 |
--------------------------------------------------------------------------------
/apps/app/constants/graph.ts:
--------------------------------------------------------------------------------
1 | // nivo
2 | import { Theme } from "@nivo/core";
3 |
4 | export const CHARTS_THEME: Theme = {
5 | background: "transparent",
6 | textColor: "rgb(var(--color-text-200))",
7 | axis: {
8 | domain: {
9 | line: {
10 | stroke: "rgb(var(--color-background-80))",
11 | strokeWidth: 0.5,
12 | },
13 | },
14 | },
15 | tooltip: {
16 | container: {
17 | background: "rgb(var(--color-background-80))",
18 | color: "rgb(var(--color-text-200))",
19 | fontSize: "0.8rem",
20 | border: "1px solid rgb(var(--color-border-300))",
21 | },
22 | },
23 | grid: {
24 | line: {
25 | stroke: "rgb(var(--color-border-100))",
26 | },
27 | },
28 | };
29 |
30 | export const DEFAULT_MARGIN = {
31 | top: 50,
32 | right: 50,
33 | bottom: 50,
34 | left: 50,
35 | };
36 |
--------------------------------------------------------------------------------
/apps/app/components/core/gantt-chart-view/index.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router";
2 |
3 | // components
4 | import { CycleIssuesGanttChartView } from "components/cycles";
5 | import { IssueGanttChartView } from "components/issues/gantt-chart";
6 | import { ModuleIssuesGanttChartView } from "components/modules";
7 | import { ViewIssuesGanttChartView } from "components/views";
8 |
9 | export const GanttChartView = () => {
10 | const router = useRouter();
11 | const { cycleId, moduleId, viewId } = router.query;
12 |
13 | return (
14 | <>
15 | {cycleId ? (
16 |
17 | ) : moduleId ? (
18 |
19 | ) : viewId ? (
20 |
21 | ) : (
22 |
23 | )}
24 | >
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/apps/app/sentry.edge.config.js:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry on the server.
2 | // The config you add here will be used whenever middleware or an Edge route handles a request.
3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
4 |
5 | import * as Sentry from "@sentry/nextjs";
6 |
7 | const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN;
8 |
9 | Sentry.init({
10 | dsn: SENTRY_DSN,
11 | environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT || "development",
12 | // Adjust this value in production, or use tracesSampler for greater control
13 | tracesSampleRate: 1.0,
14 | // ...
15 | // Note: if you want to override the automatic release value, do not set a
16 | // `release` value here - use the environment variable `SENTRY_RELEASE`, so
17 | // that it will also get attached to your source maps
18 | });
19 |
--------------------------------------------------------------------------------
/apps/app/styles/nprogress.css:
--------------------------------------------------------------------------------
1 | #nprogress {
2 | pointer-events: none;
3 | }
4 |
5 | #nprogress .bar {
6 | background: #3f76ff;
7 |
8 | position: fixed;
9 | z-index: 1031;
10 | top: 0;
11 | left: 0;
12 |
13 | width: 100%;
14 | height: 0.2rem;
15 | }
16 |
17 | /* for blur effect */
18 | #nprogress .peg {
19 | display: block;
20 | position: absolute;
21 | right: 0px;
22 | width: 100px;
23 | height: 100%;
24 | box-shadow: 0 0 10px #3f76ff, 0 0 5px #3f76ff;
25 | opacity: 1;
26 |
27 | -webkit-transform: rotate(3deg) translate(0px, -4px);
28 | -ms-transform: rotate(3deg) translate(0px, -4px);
29 | transform: rotate(3deg) translate(0px, -4px);
30 | }
31 |
32 | .nprogress-custom-parent {
33 | overflow: hidden;
34 | position: relative;
35 | }
36 |
37 | .nprogress-custom-parent #nprogress .bar {
38 | position: absolute;
39 | }
40 |
--------------------------------------------------------------------------------
/apps/app/types/importer/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./github-importer";
2 | export * from "./jira-importer";
3 |
4 | import { IProjectLite } from "types/projects";
5 | // types
6 | import { IUserLite } from "types/users";
7 |
8 | export interface IImporterService {
9 | created_at: string;
10 | config: {
11 | sync: boolean;
12 | };
13 | created_by: string | null;
14 | data: {
15 | users: [];
16 | };
17 | id: string;
18 | initiated_by: string;
19 | initiated_by_detail: IUserLite;
20 | metadata: {
21 | name: string;
22 | owner: string;
23 | repository_id: number;
24 | url: string;
25 | };
26 | project: string;
27 | project_detail: IProjectLite;
28 | service: string;
29 | status: "processing" | "completed" | "failed";
30 | updated_at: string;
31 | updated_by: string;
32 | token: string;
33 | workspace: string;
34 | }
35 |
--------------------------------------------------------------------------------
/apps/app/pages/api/unsplash.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 |
3 | // TODO: remove NEXT_PUBLIC_ prefix from env variable
4 | const unsplashKey = process.env.NEXT_PUBLIC_UNSPLASH_ACCESS;
5 |
6 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
7 | const { query, page, per_page = 20 } = req.query;
8 |
9 | const url = query
10 | ? `https://api.unsplash.com/search/photos/?client_id=${unsplashKey}&query=${query}&page=${page}&per_page=${per_page}`
11 | : `https://api.unsplash.com/photos/?client_id=${unsplashKey}&page=${page}&per_page=${per_page}`;
12 |
13 | const response = await fetch(url, {
14 | method: "GET",
15 | headers: {
16 | "Content-Type": "application/json",
17 | },
18 | });
19 |
20 | const data = await response.json();
21 |
22 | res.status(200).json(data);
23 | }
24 |
--------------------------------------------------------------------------------
/nginx/supervisor.conf:
--------------------------------------------------------------------------------
1 | [supervisord] ## This is the main process for the Supervisor
2 | nodaemon=true
3 |
4 | [program:node]
5 | command=sh /usr/local/bin/start.sh
6 | autostart=true
7 | autorestart=true
8 | stderr_logfile=/var/log/node.err.log
9 | stdout_logfile=/var/log/node.out.log
10 |
11 | [program:python]
12 | directory=/code
13 | command=sh bin/takeoff
14 | autostart=true
15 | autorestart=true
16 | stderr_logfile=/var/log/python.err.log
17 | stdout_logfile=/var/log/python.out.log
18 |
19 | [program:nginx]
20 | command=nginx -g "daemon off;"
21 | autostart=true
22 | autorestart=true
23 | stderr_logfile=/var/log/nginx.err.log
24 | stdout_logfile=/var/log/nginx.out.log
25 |
26 | [program:worker]
27 | directory=/code
28 | command=sh bin/worker
29 | autostart=true
30 | autorestart=true
31 | stderr_logfile=/var/log/worker.err.log
32 | stdout_logfile=/var/log/worker.out.log
--------------------------------------------------------------------------------
/apiserver/requirements/base.txt:
--------------------------------------------------------------------------------
1 | # base requirements
2 |
3 | Django==4.2.3
4 | django-braces==1.15.0
5 | django-taggit==4.0.0
6 | psycopg==3.1.9
7 | django-oauth-toolkit==2.3.0
8 | mistune==3.0.1
9 | djangorestframework==3.14.0
10 | redis==4.6.0
11 | django-nested-admin==4.0.2
12 | django-cors-headers==4.1.0
13 | whitenoise==6.5.0
14 | django-allauth==0.54.0
15 | faker==18.11.2
16 | django-filter==23.2
17 | jsonmodels==2.6.0
18 | djangorestframework-simplejwt==5.2.2
19 | sentry-sdk==1.27.0
20 | django-s3-storage==0.14.0
21 | django-crum==0.7.9
22 | django-guardian==2.4.0
23 | dj_rest_auth==2.2.5
24 | google-auth==2.21.0
25 | google-api-python-client==2.92.0
26 | django-redis==5.3.0
27 | uvicorn==0.22.0
28 | channels==4.0.0
29 | openai==0.27.8
30 | slack-sdk==3.21.3
31 | celery==5.3.1
32 | django_celery_beat==2.5.0
33 | psycopg-binary==3.1.9
34 | psycopg-c==3.1.9
35 | scout-apm==2.26.1
--------------------------------------------------------------------------------
/apps/app/public/mac-command.svg:
--------------------------------------------------------------------------------
1 |
2 | mac-command
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0015_auto_20230107_1636.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.16 on 2023-01-07 11:06
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('db', '0014_alter_workspacememberinvite_unique_together'),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameField(
14 | model_name='issuecomment',
15 | old_name='comment',
16 | new_name='comment_stripped',
17 | ),
18 | migrations.AddField(
19 | model_name='issuecomment',
20 | name='comment_html',
21 | field=models.TextField(blank=True),
22 | ),
23 | migrations.AddField(
24 | model_name='issuecomment',
25 | name='comment_json',
26 | field=models.JSONField(blank=True, null=True),
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/apps/app/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "theme_color": "#3579f6",
3 | "background_color": "#ffffff",
4 | "display": "standalone",
5 | "scope": "/",
6 | "start_url": "/",
7 | "name": "Plane | Accelerate software development with peace.",
8 | "short_name": "Plane",
9 | "description": "Plane accelerated the software development by order of magnitude for agencies and product companies.",
10 | "icons": [
11 | {
12 | "src": "/icon-192x192.png",
13 | "sizes": "192x192",
14 | "type": "image/png"
15 | },
16 | {
17 | "src": "/icon-256x256.png",
18 | "sizes": "256x256",
19 | "type": "image/png"
20 | },
21 | {
22 | "src": "/icon-384x384.png",
23 | "sizes": "384x384",
24 | "type": "image/png"
25 | },
26 | {
27 | "src": "/icon-512x512.png",
28 | "sizes": "512x512",
29 | "type": "image/png"
30 | }
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/apps/app/types/state.d.ts:
--------------------------------------------------------------------------------
1 | import { IProject, IProjectLite, IWorkspaceLite } from "types";
2 |
3 | export type TStateGroup = "backlog" | "unstarted" | "started" | "completed" | "cancelled";
4 |
5 | export interface IState {
6 | readonly id: string;
7 | color: string;
8 | readonly created_at: Date;
9 | readonly created_by: string;
10 | default: boolean;
11 | description: string;
12 | group: TStateGroup;
13 | name: string;
14 | project: string;
15 | readonly project_detail: IProjectLite;
16 | sequence: number;
17 | readonly slug: string;
18 | readonly updated_at: Date;
19 | readonly updated_by: string;
20 | workspace: string;
21 | workspace_detail: IWorkspaceLite;
22 | }
23 |
24 | export interface IStateLite {
25 | color: string;
26 | group: TStateGroup;
27 | id: string;
28 | name: string;
29 | }
30 |
31 | export interface IStateResponse {
32 | [key: string]: IState[];
33 | }
34 |
--------------------------------------------------------------------------------
/apiserver/plane/api/serializers/state.py:
--------------------------------------------------------------------------------
1 | # Module imports
2 | from .base import BaseSerializer
3 | from .workspace import WorkspaceLiteSerializer
4 | from .project import ProjectLiteSerializer
5 |
6 | from plane.db.models import State
7 |
8 |
9 | class StateSerializer(BaseSerializer):
10 | workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
11 | project_detail = ProjectLiteSerializer(read_only=True, source="project")
12 |
13 | class Meta:
14 | model = State
15 | fields = "__all__"
16 | read_only_fields = [
17 | "workspace",
18 | "project",
19 | ]
20 |
21 |
22 | class StateLiteSerializer(BaseSerializer):
23 | class Meta:
24 | model = State
25 | fields = [
26 | "id",
27 | "name",
28 | "color",
29 | "group",
30 | ]
31 | read_only_fields = fields
32 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0016_auto_20230107_1735.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.16 on 2023-01-07 12:05
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 | import plane.db.models.asset
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('db', '0015_auto_20230107_1636'),
12 | ]
13 |
14 | operations = [
15 | migrations.AddField(
16 | model_name='fileasset',
17 | name='workspace',
18 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assets', to='db.workspace'),
19 | ),
20 | migrations.AlterField(
21 | model_name='fileasset',
22 | name='asset',
23 | field=models.FileField(upload_to=plane.db.models.asset.get_upload_path, validators=[plane.db.models.asset.file_size]),
24 | ),
25 | ]
26 |
--------------------------------------------------------------------------------
/apps/app/types/estimate.d.ts:
--------------------------------------------------------------------------------
1 | export interface IEstimate {
2 | id: string;
3 | created_at: Date;
4 | updated_at: Date;
5 | name: string;
6 | description: string;
7 | created_by: string;
8 | updated_by: string;
9 | points: IEstimatePoint[];
10 | project: string;
11 | project_detail: IProject;
12 | workspace: string;
13 | workspace_detail: IWorkspace;
14 | }
15 |
16 | export interface IEstimatePoint {
17 | id: string;
18 | created_at: string;
19 | created_by: string;
20 | description: string;
21 | estimate: string;
22 | key: number;
23 | project: string;
24 | updated_at: string;
25 | updated_by: string;
26 | value: string;
27 | workspace: string;
28 | }
29 |
30 | export interface IEstimateFormData {
31 | estimate: {
32 | name: string;
33 | description: string;
34 | };
35 | estimate_points: {
36 | id?: string;
37 | key: number;
38 | value: string;
39 | }[];
40 | }
41 |
--------------------------------------------------------------------------------
/apps/app/hooks/use-reload-confirmation.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useState } from "react";
2 |
3 | const useReloadConfirmations = (message?: string) => {
4 | const [showAlert, setShowAlert] = useState(false);
5 |
6 | const handleBeforeUnload = useCallback(
7 | (event: BeforeUnloadEvent) => {
8 | event.preventDefault();
9 | event.returnValue = "";
10 | return message ?? "Are you sure you want to leave?";
11 | },
12 | [message]
13 | );
14 |
15 | useEffect(() => {
16 | if (!showAlert) {
17 | window.removeEventListener("beforeunload", handleBeforeUnload);
18 | return;
19 | }
20 |
21 | window.addEventListener("beforeunload", handleBeforeUnload);
22 | return () => window.removeEventListener("beforeunload", handleBeforeUnload);
23 | }, [handleBeforeUnload, showAlert]);
24 |
25 | return { setShowAlert };
26 | };
27 |
28 | export default useReloadConfirmations;
29 |
--------------------------------------------------------------------------------
/apps/app/components/ui/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./buttons";
2 | export * from "./dropdowns";
3 | export * from "./graphs";
4 | export * from "./input";
5 | export * from "./text-area";
6 | export * from "./avatar";
7 | export * from "./date";
8 | export * from "./datepicker";
9 | export * from "./empty-space";
10 | export * from "./empty-state";
11 | export * from "./icon";
12 | export * from "./labels-list";
13 | export * from "./linear-progress-indicator";
14 | export * from "./loader";
15 | export * from "./multi-level-dropdown";
16 | export * from "./multi-level-select";
17 | export * from "./progress-bar";
18 | export * from "./spinner";
19 | export * from "./tooltip";
20 | export * from "./toggle-switch";
21 | export * from "./markdown-to-component";
22 | export * from "./product-updates-modal";
23 | export * from "./integration-and-import-export-banner";
24 | export * from "./range-datepicker";
25 | export * from "./circular-progress";
26 |
--------------------------------------------------------------------------------
/apps/app/components/integration/github/auth.tsx:
--------------------------------------------------------------------------------
1 | // hooks
2 | import useIntegrationPopup from "hooks/use-integration-popup";
3 | // ui
4 | import { PrimaryButton } from "components/ui";
5 | // types
6 | import { IWorkspaceIntegration } from "types";
7 |
8 | type Props = {
9 | workspaceIntegration: false | IWorkspaceIntegration | undefined;
10 | provider: string | undefined;
11 | };
12 |
13 | export const GithubAuth: React.FC = ({ workspaceIntegration, provider }) => {
14 | const { startAuth, isConnecting } = useIntegrationPopup(provider);
15 |
16 | return (
17 |
18 | {workspaceIntegration && workspaceIntegration?.id ? (
19 |
Successfully Connected
20 | ) : (
21 |
22 | {isConnecting ? "Connecting..." : "Connect"}
23 |
24 | )}
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/apps/app/components/ui/integration-and-import-export-banner.tsx:
--------------------------------------------------------------------------------
1 | import { ExclamationIcon } from "components/icons";
2 |
3 | type Props = {
4 | bannerName: string;
5 | };
6 |
7 | export const IntegrationAndImportExportBanner: React.FC = ({ bannerName }) => (
8 |
9 |
{bannerName}
10 |
11 |
12 |
13 | Integrations and importers are only available on the cloud version. We plan to open-source
14 | our SDKs in the near future so that the community can request or contribute integrations as
15 | needed.
16 |
17 |
18 |
19 | );
20 |
--------------------------------------------------------------------------------
/apps/app/components/pages/pages-list/all-pages-list.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router";
2 |
3 | import useSWR from "swr";
4 |
5 | // services
6 | import pagesService from "services/pages.service";
7 | // components
8 | import { PagesView } from "components/pages";
9 | // types
10 | import { TPagesListProps } from "./types";
11 | // fetch-keys
12 | import { ALL_PAGES_LIST } from "constants/fetch-keys";
13 |
14 | export const AllPagesList: React.FC = ({ viewType }) => {
15 | const router = useRouter();
16 | const { workspaceSlug, projectId } = router.query;
17 |
18 | const { data: pages } = useSWR(
19 | workspaceSlug && projectId ? ALL_PAGES_LIST(projectId as string) : null,
20 | workspaceSlug && projectId
21 | ? () => pagesService.getPagesWithParams(workspaceSlug as string, projectId as string, "all")
22 | : null
23 | );
24 |
25 | return ;
26 | };
27 |
--------------------------------------------------------------------------------
/apps/app/hooks/use-project-details.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router";
2 |
3 | import useSWR from "swr";
4 |
5 | // services
6 | import projectService from "services/project.service";
7 | // fetch-keys
8 | import { PROJECT_DETAILS } from "constants/fetch-keys";
9 |
10 | const useProjectDetails = () => {
11 | const router = useRouter();
12 | const { workspaceSlug, projectId } = router.query;
13 |
14 | const {
15 | data: projectDetails,
16 | error: projectDetailsError,
17 | mutate: mutateProjectDetails,
18 | } = useSWR(
19 | workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
20 | workspaceSlug && projectId
21 | ? () => projectService.getProject(workspaceSlug as string, projectId as string)
22 | : null
23 | );
24 |
25 | return {
26 | projectDetails,
27 | projectDetailsError,
28 | mutateProjectDetails,
29 | };
30 | };
31 |
32 | export default useProjectDetails;
33 |
--------------------------------------------------------------------------------
/apps/app/components/icons/priority-icon.tsx:
--------------------------------------------------------------------------------
1 | export const getPriorityIcon = (priority: string | null, className?: string) => {
2 | if (!className || className === "") className = "text-xs flex items-center";
3 |
4 | priority = priority?.toLowerCase() ?? null;
5 |
6 | switch (priority) {
7 | case "urgent":
8 | return error ;
9 | case "high":
10 | return signal_cellular_alt ;
11 | case "medium":
12 | return (
13 | signal_cellular_alt_2_bar
14 | );
15 | case "low":
16 | return (
17 | signal_cellular_alt_1_bar
18 | );
19 | default:
20 | return block ;
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/apps/app/components/emoji-icon-picker/helpers.ts:
--------------------------------------------------------------------------------
1 | export const saveRecentEmoji = (emoji: string) => {
2 | const recentEmojis = localStorage.getItem("recentEmojis");
3 | if (recentEmojis) {
4 | const recentEmojisArray = recentEmojis.split(",");
5 | if (recentEmojisArray.includes(emoji)) {
6 | const index = recentEmojisArray.indexOf(emoji);
7 | recentEmojisArray.splice(index, 1);
8 | }
9 | recentEmojisArray.unshift(emoji);
10 | if (recentEmojisArray.length > 18) {
11 | recentEmojisArray.pop();
12 | }
13 | localStorage.setItem("recentEmojis", recentEmojisArray.join(","));
14 | } else {
15 | localStorage.setItem("recentEmojis", emoji);
16 | }
17 | };
18 |
19 | export const getRecentEmojis = () => {
20 | const recentEmojis = localStorage.getItem("recentEmojis");
21 | if (recentEmojis) {
22 | const recentEmojisArray = recentEmojis.split(",");
23 | return recentEmojisArray;
24 | }
25 | return [];
26 | };
27 |
--------------------------------------------------------------------------------
/apps/app/constants/project.ts:
--------------------------------------------------------------------------------
1 | export const NETWORK_CHOICES = { "0": "Secret", "2": "Public" };
2 |
3 | export const GROUP_CHOICES = {
4 | backlog: "Backlog",
5 | unstarted: "Unstarted",
6 | started: "Started",
7 | completed: "Completed",
8 | cancelled: "Cancelled",
9 | };
10 |
11 | export const PRIORITIES = ["urgent", "high", "medium", "low", null];
12 |
13 | export const MONTHS = [
14 | "January",
15 | "February",
16 | "March",
17 | "April",
18 | "May",
19 | "June",
20 | "July",
21 | "August",
22 | "September",
23 | "October",
24 | "November",
25 | "December",
26 | ];
27 |
28 | export const DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
29 |
30 | export const PROJECT_AUTOMATION_MONTHS = [
31 | { label: "1 Month", value: 1 },
32 | { label: "3 Months", value: 3 },
33 | { label: "6 Months", value: 6 },
34 | { label: "9 Months", value: 9 },
35 | { label: "12 Months", value: 12 },
36 | ];
37 |
--------------------------------------------------------------------------------
/apps/app/types/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from "./users";
2 | export * from "./workspace";
3 | export * from "./cycles";
4 | export * from "./projects";
5 | export * from "./state";
6 | export * from "./invitation";
7 | export * from "./issues";
8 | export * from "./modules";
9 | export * from "./views";
10 | export * from "./integration";
11 | export * from "./pages";
12 | export * from "./ai";
13 | export * from "./estimate";
14 | export * from "./importer";
15 | export * from "./inbox";
16 | export * from "./analytics";
17 | export * from "./calendar";
18 | export * from "./notifications";
19 | export * from "./waitlist";
20 |
21 | export type NestedKeyOf = {
22 | [Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object
23 | ? ObjectType[Key] extends { pop: any; push: any }
24 | ? `${Key}`
25 | : `${Key}` | `${Key}.${NestedKeyOf}`
26 | : `${Key}`;
27 | }[keyof ObjectType & (string | number)];
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/--feature-request.yaml:
--------------------------------------------------------------------------------
1 | name: Feature request
2 | description: Suggest a feature to improve Plane
3 | title: "[feature]: "
4 | labels: [feature]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thank you for taking the time to request a feature for Plane
10 | - type: checkboxes
11 | attributes:
12 | label: Is there an existing issue for this?
13 | description: Please search to see if an issue related to this feature request already exists
14 | options:
15 | - label: I have searched the existing issues
16 | required: true
17 | - type: textarea
18 | attributes:
19 | label: Summary
20 | description: One paragraph description of the feature
21 | validations:
22 | required: true
23 | - type: textarea
24 | attributes:
25 | label: Why should this be worked on?
26 | description: A concise description of the problems or use cases for this feature request
27 | validations:
28 | required: true
--------------------------------------------------------------------------------
/apps/app/components/icons/single-comment-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import type { Props } from "./types";
4 |
5 | export const SingleCommentCard: React.FC = ({
6 | width = "24",
7 | height = "24",
8 | className,
9 | color,
10 | }) => (
11 |
19 |
23 |
24 | );
25 |
--------------------------------------------------------------------------------
/apps/app/public/empty-state/issue-archive.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/apps/app/styles/command-pallette.css:
--------------------------------------------------------------------------------
1 | [cmdk-group]:not(:first-child) {
2 | margin-top: 0.5rem;
3 | }
4 |
5 | [cmdk-group-heading] {
6 | color: rgb(var(--color-text-secondary));
7 | font-size: 0.75rem;
8 | margin: 0 0 0.25rem 0.25rem;
9 | }
10 |
11 | [cmdk-item] {
12 | display: flex;
13 | align-items: center;
14 | justify-content: space-between;
15 | border-radius: 0.375rem;
16 | padding: 0.5rem;
17 | font-size: 0.825rem;
18 | line-height: 1.25rem;
19 | cursor: pointer;
20 | }
21 |
22 | [cmdk-item] kbd {
23 | height: 1.25rem;
24 | width: 1.25rem;
25 | display: grid;
26 | place-items: center;
27 | font-size: 0.75rem;
28 | line-height: 1rem;
29 | border-radius: 0.25rem;
30 | background-color: rgba(var(--color-background-100));
31 | }
32 |
33 | [cmdk-item]:hover {
34 | background-color: rgba(var(--color-background-80));
35 | }
36 |
37 | [cmdk-item][aria-selected="true"] {
38 | background-color: rgba(var(--color-background-80));
39 | }
40 |
--------------------------------------------------------------------------------
/apps/app/components/cycles/cycles-list/all-cycles-list.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router";
2 |
3 | import useSWR from "swr";
4 |
5 | // services
6 | import cyclesService from "services/cycles.service";
7 | // components
8 | import { CyclesView } from "components/cycles";
9 | // fetch-keys
10 | import { CYCLES_LIST } from "constants/fetch-keys";
11 |
12 | type Props = {
13 | viewType: string | null;
14 | };
15 |
16 | export const AllCyclesList: React.FC = ({ viewType }) => {
17 | const router = useRouter();
18 | const { workspaceSlug, projectId } = router.query;
19 |
20 | const { data: allCyclesList } = useSWR(
21 | workspaceSlug && projectId ? CYCLES_LIST(projectId.toString()) : null,
22 | workspaceSlug && projectId
23 | ? () =>
24 | cyclesService.getCyclesWithParams(workspaceSlug.toString(), projectId.toString(), "all")
25 | : null
26 | );
27 |
28 | return ;
29 | };
30 |
--------------------------------------------------------------------------------
/apps/app/components/pages/pages-list/favorite-pages-list.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router";
2 |
3 | import useSWR from "swr";
4 |
5 | // services
6 | import pagesService from "services/pages.service";
7 | // components
8 | import { PagesView } from "components/pages";
9 | // types
10 | import { TPagesListProps } from "./types";
11 | // fetch-keys
12 | import { FAVORITE_PAGES_LIST } from "constants/fetch-keys";
13 |
14 | export const FavoritePagesList: React.FC = ({ viewType }) => {
15 | const router = useRouter();
16 | const { workspaceSlug, projectId } = router.query;
17 |
18 | const { data: pages } = useSWR(
19 | workspaceSlug && projectId ? FAVORITE_PAGES_LIST(projectId as string) : null,
20 | workspaceSlug && projectId
21 | ? () =>
22 | pagesService.getPagesWithParams(workspaceSlug as string, projectId as string, "favorite")
23 | : null
24 | );
25 |
26 | return ;
27 | };
28 |
--------------------------------------------------------------------------------
/apps/app/components/ui/labels-list.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | type IssueLabelsListProps = {
4 | labels?: (string | undefined)[];
5 | length?: number;
6 | showLength?: boolean;
7 | };
8 |
9 | export const IssueLabelsList: React.FC = ({
10 | labels,
11 | length = 5,
12 | showLength = true,
13 | }) => (
14 | <>
15 | {labels && (
16 | <>
17 | {labels.slice(0, length).map((color, index) => (
18 |
19 |
26 |
27 | ))}
28 | {labels.length > length ? +{labels.length - length} : null}
29 | >
30 | )}
31 | >
32 | );
33 |
--------------------------------------------------------------------------------
/apps/app/types/views.d.ts:
--------------------------------------------------------------------------------
1 | export interface IView {
2 | id: string;
3 | access: string;
4 | created_at: Date;
5 | updated_at: Date;
6 | is_favorite: boolean;
7 | created_by: string;
8 | updated_by: string;
9 | name: string;
10 | description: string;
11 | query: IQuery;
12 | query_data: IQuery;
13 | project: string;
14 | workspace: string;
15 | }
16 |
17 | export interface IQuery {
18 | state: string[] | null;
19 | parent: string[] | null;
20 | priority: string[] | null;
21 | labels: string[] | null;
22 | assignees: string[] | null;
23 | created_by: string[] | null;
24 | name: string | null;
25 | created_at: [
26 | {
27 | datetime: string;
28 | timeline: "before";
29 | },
30 | {
31 | datetime: string;
32 | timeline: "after";
33 | }
34 | ];
35 | updated_at: string[] | null;
36 | start_date: string[] | null;
37 | target_date: string[] | null;
38 | completed_at: string[] | null;
39 | type: string;
40 | }
41 |
--------------------------------------------------------------------------------
/apps/app/components/cycles/cycles-list/draft-cycles-list.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router";
2 |
3 | import useSWR from "swr";
4 |
5 | // services
6 | import cyclesService from "services/cycles.service";
7 | // components
8 | import { CyclesView } from "components/cycles";
9 | // fetch-keys
10 | import { DRAFT_CYCLES_LIST } from "constants/fetch-keys";
11 |
12 | type Props = {
13 | viewType: string | null;
14 | };
15 |
16 | export const DraftCyclesList: React.FC = ({ viewType }) => {
17 | const router = useRouter();
18 | const { workspaceSlug, projectId } = router.query;
19 |
20 | const { data: draftCyclesList } = useSWR(
21 | workspaceSlug && projectId ? DRAFT_CYCLES_LIST(projectId.toString()) : null,
22 | workspaceSlug && projectId
23 | ? () =>
24 | cyclesService.getCyclesWithParams(workspaceSlug.toString(), projectId.toString(), "draft")
25 | : null
26 | );
27 |
28 | return ;
29 | };
30 |
--------------------------------------------------------------------------------
/apps/app/components/icons/current-cycle-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import type { Props } from "./types";
4 |
5 | export const CurrentCycleIcon: React.FC = ({
6 | width = "24",
7 | height = "24",
8 | className,
9 | color = "black",
10 | }) => (
11 |
12 |
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/apps/app/components/integration/jira/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./root";
2 | export * from "./give-details";
3 | export * from "./jira-project-detail";
4 | export * from "./import-users";
5 | export * from "./confirm-import";
6 |
7 | import { IJiraImporterForm } from "types";
8 |
9 | export type TJiraIntegrationSteps =
10 | | "import-configure"
11 | | "display-import-data"
12 | | "select-import-data"
13 | | "import-users"
14 | | "import-confirmation";
15 |
16 | export interface IJiraIntegrationData {
17 | state: TJiraIntegrationSteps;
18 | }
19 |
20 | export const jiraFormDefaultValues: IJiraImporterForm = {
21 | metadata: {
22 | cloud_hostname: "",
23 | api_token: "",
24 | project_key: "",
25 | email: "",
26 | },
27 | config: {
28 | epics_to_modules: false,
29 | },
30 | data: {
31 | users: [],
32 | invite_users: true,
33 | total_issues: 0,
34 | total_labels: 0,
35 | total_modules: 0,
36 | total_states: 0,
37 | },
38 | project_id: "",
39 | };
40 |
--------------------------------------------------------------------------------
/apiserver/plane/celery.py:
--------------------------------------------------------------------------------
1 | import os
2 | from celery import Celery
3 | from plane.settings.redis import redis_instance
4 | from celery.schedules import crontab
5 |
6 | # Set the default Django settings module for the 'celery' program.
7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
8 |
9 | ri = redis_instance()
10 |
11 | app = Celery("plane")
12 |
13 | # Using a string here means the worker will not have to
14 | # pickle the object when using Windows.
15 | app.config_from_object("django.conf:settings", namespace="CELERY")
16 |
17 | app.conf.beat_schedule = {
18 | # Executes every day at 12 AM
19 | "check-every-day-to-archive-and-close": {
20 | "task": "plane.bgtasks.issue_automation_task.archive_and_close_old_issues",
21 | "schedule": crontab(hour=0, minute=0),
22 | },
23 | }
24 |
25 | # Load task modules from all registered Django app configs.
26 | app.autodiscover_tasks()
27 |
28 | app.conf.beat_scheduler = 'django_celery_beat.schedulers.DatabaseScheduler'
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
3 |
4 | ### NextJS ###
5 | # Dependencies
6 | /node_modules
7 | /.pnp
8 | .pnp.js
9 |
10 | # Testing
11 | /coverage
12 |
13 | # Next.js
14 | /.next/
15 | /out/
16 |
17 | # Production
18 | /build
19 |
20 | # Misc
21 | .DS_Store
22 | *.pem
23 | .history
24 |
25 | # Debug
26 | npm-debug.log*
27 | yarn-debug.log*
28 | yarn-error.log*
29 | .pnpm-debug.log*
30 |
31 | # Local env files
32 | .env
33 | .env.local
34 | .env.development.local
35 | .env.test.local
36 | .env.production.local
37 |
38 | # Vercel
39 | .vercel
40 |
41 | # Turborepo
42 | .turbo
43 |
44 | ## Django ##
45 | venv
46 | .venv
47 | *.pyc
48 | staticfiles
49 | mediafiles
50 | .env
51 | .DS_Store
52 |
53 | node_modules/
54 | assets/dist/
55 | npm-debug.log
56 | yarn-error.log
57 |
58 | # Editor directories and files
59 | .idea
60 | *.suo
61 | *.ntvs*
62 | *.njsproj
63 | *.sln
64 | package-lock.json
65 | .vscode
66 |
67 | # Sentry
68 | .sentryclirc
69 |
70 | # lock files
71 | package-lock.json
72 | pnpm-lock.yaml
73 | pnpm-workspace.yaml
--------------------------------------------------------------------------------
/apps/app/components/pages/pages-list/my-pages-list.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router";
2 |
3 | import useSWR from "swr";
4 |
5 | // services
6 | import pagesService from "services/pages.service";
7 | // components
8 | import { PagesView } from "components/pages";
9 | // types
10 | import { TPagesListProps } from "./types";
11 | // fetch-keys
12 | import { MY_PAGES_LIST } from "constants/fetch-keys";
13 |
14 | export const MyPagesList: React.FC = ({ viewType }) => {
15 | const router = useRouter();
16 | const { workspaceSlug, projectId } = router.query;
17 |
18 | const { data: pages } = useSWR(
19 | workspaceSlug && projectId ? MY_PAGES_LIST(projectId as string) : null,
20 | workspaceSlug && projectId
21 | ? () =>
22 | pagesService.getPagesWithParams(
23 | workspaceSlug as string,
24 | projectId as string,
25 | "created_by_me"
26 | )
27 | : null
28 | );
29 |
30 | return ;
31 | };
32 |
--------------------------------------------------------------------------------
/apps/app/components/icons/state-group-icon.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BacklogStateIcon,
3 | CancelledStateIcon,
4 | CompletedStateIcon,
5 | StartedStateIcon,
6 | UnstartedStateIcon,
7 | } from "components/icons";
8 |
9 | export const getStateGroupIcon = (
10 | stateGroup: "backlog" | "unstarted" | "started" | "completed" | "cancelled",
11 | width = "20",
12 | height = "20",
13 | color?: string
14 | ) => {
15 | switch (stateGroup) {
16 | case "backlog":
17 | return ;
18 | case "unstarted":
19 | return ;
20 | case "started":
21 | return ;
22 | case "completed":
23 | return ;
24 | case "cancelled":
25 | return ;
26 | default:
27 | return <>>;
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/apps/app/hooks/gantt-chart/cycle-issues-view.tsx:
--------------------------------------------------------------------------------
1 | import useSWR from "swr";
2 |
3 | // services
4 | import cyclesService from "services/cycles.service";
5 | // fetch-keys
6 | import { CYCLE_ISSUES_WITH_PARAMS } from "constants/fetch-keys";
7 |
8 | const useGanttChartCycleIssues = (
9 | workspaceSlug: string | undefined,
10 | projectId: string | undefined,
11 | cycleId: string | undefined
12 | ) => {
13 | // all issues under the workspace and project
14 | const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
15 | workspaceSlug && projectId && cycleId ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString()) : null,
16 | workspaceSlug && projectId && cycleId
17 | ? () =>
18 | cyclesService.getCycleIssuesWithParams(
19 | workspaceSlug.toString(),
20 | projectId.toString(),
21 | cycleId.toString()
22 | )
23 | : null
24 | );
25 |
26 | return {
27 | ganttIssues,
28 | mutateGanttIssues,
29 | };
30 | };
31 |
32 | export default useGanttChartCycleIssues;
33 |
--------------------------------------------------------------------------------
/apps/app/components/gantt-chart/root.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from "react";
2 | // components
3 | import { ChartViewRoot } from "./chart";
4 | // context
5 | import { ChartContextProvider } from "./contexts";
6 |
7 | type GanttChartRootProps = {
8 | border?: boolean;
9 | title: null | string;
10 | loaderTitle: string;
11 | blocks: any;
12 | blockUpdateHandler: (data: any) => void;
13 | sidebarBlockRender: FC;
14 | blockRender: FC;
15 | };
16 |
17 | export const GanttChartRoot: FC = ({
18 | border = true,
19 | title = null,
20 | blocks,
21 | loaderTitle = "blocks",
22 | blockUpdateHandler,
23 | sidebarBlockRender,
24 | blockRender,
25 | }) => (
26 |
27 |
36 |
37 | );
38 |
--------------------------------------------------------------------------------
/apps/app/components/pages/pages-list/other-pages-list.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router";
2 |
3 | import useSWR from "swr";
4 |
5 | // services
6 | import pagesService from "services/pages.service";
7 | // components
8 | import { PagesView } from "components/pages";
9 | // types
10 | import { TPagesListProps } from "./types";
11 | // fetch-keys
12 | import { OTHER_PAGES_LIST } from "constants/fetch-keys";
13 |
14 | export const OtherPagesList: React.FC = ({ viewType }) => {
15 | const router = useRouter();
16 | const { workspaceSlug, projectId } = router.query;
17 |
18 | const { data: pages } = useSWR(
19 | workspaceSlug && projectId ? OTHER_PAGES_LIST(projectId as string) : null,
20 | workspaceSlug && projectId
21 | ? () =>
22 | pagesService.getPagesWithParams(
23 | workspaceSlug as string,
24 | projectId as string,
25 | "created_by_other"
26 | )
27 | : null
28 | );
29 |
30 | return ;
31 | };
32 |
--------------------------------------------------------------------------------
/apps/app/components/icons/bolt-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import type { Props } from "./types";
4 |
5 | export const BoltIcon: React.FC = ({ width = "24", height = "24", className }) => (
6 |
14 |
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/apps/app/hooks/gantt-chart/module-issues-view.tsx:
--------------------------------------------------------------------------------
1 | import useSWR from "swr";
2 |
3 | // services
4 | import modulesService from "services/modules.service";
5 | // fetch-keys
6 | import { MODULE_ISSUES_WITH_PARAMS } from "constants/fetch-keys";
7 |
8 | const useGanttChartModuleIssues = (
9 | workspaceSlug: string | undefined,
10 | projectId: string | undefined,
11 | moduleId: string | undefined
12 | ) => {
13 | // all issues under the workspace and project
14 | const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
15 | workspaceSlug && projectId && moduleId ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString()) : null,
16 | workspaceSlug && projectId && moduleId
17 | ? () =>
18 | modulesService.getModuleIssuesWithParams(
19 | workspaceSlug.toString(),
20 | projectId.toString(),
21 | moduleId.toString()
22 | )
23 | : null
24 | );
25 |
26 | return {
27 | ganttIssues,
28 | mutateGanttIssues,
29 | };
30 | };
31 |
32 | export default useGanttChartModuleIssues;
33 |
--------------------------------------------------------------------------------
/apps/app/components/rich-text-editor/toolbar/index.tsx:
--------------------------------------------------------------------------------
1 | // buttons
2 | import {
3 | ToggleBoldButton,
4 | ToggleItalicButton,
5 | ToggleUnderlineButton,
6 | ToggleStrikeButton,
7 | ToggleOrderedListButton,
8 | ToggleBulletListButton,
9 | RedoButton,
10 | UndoButton,
11 | } from "@remirror/react";
12 | // headings
13 | import HeadingControls from "./heading-controls";
14 |
15 | export const RichTextToolbar: React.FC = () => (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | );
36 |
--------------------------------------------------------------------------------
/apps/app/pages/api/track-event.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 |
3 | // jitsu
4 | import { createClient } from "@jitsu/nextjs";
5 | import { convertCookieStringToObject } from "lib/cookie";
6 |
7 | const jitsu = createClient({
8 | key: process.env.TRACKER_ACCESS_KEY || "",
9 | tracking_host: "https://t.jitsu.com",
10 | });
11 |
12 | export default async function handler(req: NextApiRequest, res: NextApiResponse) {
13 | const { eventName, user, extra } = req.body;
14 |
15 | if (!eventName) {
16 | return res.status(400).json({ message: "Bad request" });
17 | }
18 |
19 | if (!user) return res.status(401).json({ message: "Unauthorized" });
20 |
21 | // TODO: cache user info
22 |
23 | jitsu
24 | .id({
25 | id: user.id,
26 | email: user.email,
27 | first_name: user.first_name,
28 | last_name: user.last_name,
29 | })
30 | .then(() => {
31 | jitsu.track(eventName, {
32 | ...extra,
33 | });
34 | });
35 |
36 | res.status(200).json({ message: "success" });
37 | }
38 |
--------------------------------------------------------------------------------
/apiserver/plane/tests/api/base.py:
--------------------------------------------------------------------------------
1 | # Third party imports
2 | from rest_framework.test import APITestCase, APIClient
3 |
4 | # Module imports
5 | from plane.db.models import User
6 | from plane.api.views.authentication import get_tokens_for_user
7 |
8 |
9 | class BaseAPITest(APITestCase):
10 | def setUp(self):
11 | self.client = APIClient(HTTP_USER_AGENT="plane/test", REMOTE_ADDR="10.10.10.10")
12 |
13 |
14 | class AuthenticatedAPITest(BaseAPITest):
15 | def setUp(self):
16 | super().setUp()
17 |
18 | ## Create Dummy User
19 | self.email = "user@plane.so"
20 | user = User.objects.create(email=self.email)
21 | user.set_password("user@123")
22 | user.save()
23 |
24 | # Set user
25 | self.user = user
26 |
27 | # Set Up User ID
28 | self.user_id = user.id
29 |
30 | access_token, _ = get_tokens_for_user(user)
31 | self.access_token = access_token
32 |
33 | # Set Up Authentication Token
34 | self.client.credentials(HTTP_AUTHORIZATION="Bearer " + access_token)
35 |
--------------------------------------------------------------------------------
/apps/app/components/integration/github/import-confirm.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from "react";
2 |
3 | // react-hook-form
4 | import { UseFormWatch } from "react-hook-form";
5 | // ui
6 | import { PrimaryButton, SecondaryButton } from "components/ui";
7 | // types
8 | import { TFormValues, TIntegrationSteps } from "components/integration";
9 |
10 | type Props = {
11 | handleStepChange: (value: TIntegrationSteps) => void;
12 | watch: UseFormWatch;
13 | };
14 |
15 | export const GithubImportConfirm: FC = ({ handleStepChange, watch }) => (
16 |
17 |
18 | You are about to import issues from {watch("github").full_name}. Click on {'"'}Confirm &
19 | Import{'" '}
20 | to complete the process.
21 |
22 |
23 |
handleStepChange("import-users")}>Back
24 |
Confirm & Import
25 |
26 |
27 | );
28 |
--------------------------------------------------------------------------------
/apps/app/components/ui/buttons/secondary-button.tsx:
--------------------------------------------------------------------------------
1 | // types
2 | import { ButtonProps } from "./type";
3 |
4 | export const SecondaryButton: React.FC = ({
5 | children,
6 | className = "",
7 | onClick,
8 | type = "button",
9 | disabled = false,
10 | loading = false,
11 | size = "sm",
12 | outline = false,
13 | }) => (
14 |
30 | {children}
31 |
32 | );
33 |
--------------------------------------------------------------------------------
/apps/app/hooks/use-sub-issue.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | import { useRouter } from "next/router";
4 |
5 | import useSWR from "swr";
6 |
7 | // services
8 | import issuesService from "services/issues.service";
9 | // types
10 | import { ISubIssueResponse } from "types";
11 | // fetch-keys
12 | import { SUB_ISSUES } from "constants/fetch-keys";
13 |
14 | const useSubIssue = (issueId: string, isExpanded: boolean) => {
15 | const router = useRouter();
16 | const { workspaceSlug, projectId } = router.query;
17 |
18 | const shouldFetch = workspaceSlug && projectId && issueId && isExpanded;
19 |
20 | const { data: subIssuesResponse, isLoading } = useSWR(
21 | shouldFetch ? SUB_ISSUES(issueId as string) : null,
22 | shouldFetch
23 | ? () =>
24 | issuesService.subIssues(workspaceSlug as string, projectId as string, issueId as string)
25 | : null
26 | );
27 |
28 | return {
29 | subIssues: subIssuesResponse?.sub_issues ?? [],
30 | isLoading,
31 | };
32 | };
33 |
34 | export default useSubIssue;
35 |
--------------------------------------------------------------------------------
/apps/app/components/cycles/cycles-list/upcoming-cycles-list.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router";
2 |
3 | import useSWR from "swr";
4 |
5 | // services
6 | import cyclesService from "services/cycles.service";
7 | // components
8 | import { CyclesView } from "components/cycles";
9 | // fetch-keys
10 | import { UPCOMING_CYCLES_LIST } from "constants/fetch-keys";
11 |
12 | type Props = {
13 | viewType: string | null;
14 | };
15 |
16 | export const UpcomingCyclesList: React.FC = ({ viewType }) => {
17 | const router = useRouter();
18 | const { workspaceSlug, projectId } = router.query;
19 |
20 | const { data: upcomingCyclesList } = useSWR(
21 | workspaceSlug && projectId ? UPCOMING_CYCLES_LIST(projectId.toString()) : null,
22 | workspaceSlug && projectId
23 | ? () =>
24 | cyclesService.getCyclesWithParams(
25 | workspaceSlug.toString(),
26 | projectId.toString(),
27 | "upcoming"
28 | )
29 | : null
30 | );
31 |
32 | return ;
33 | };
34 |
--------------------------------------------------------------------------------
/apps/app/components/cycles/cycles-list/completed-cycles-list.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router";
2 |
3 | import useSWR from "swr";
4 |
5 | // services
6 | import cyclesService from "services/cycles.service";
7 | // components
8 | import { CyclesView } from "components/cycles";
9 | // fetch-keys
10 | import { COMPLETED_CYCLES_LIST } from "constants/fetch-keys";
11 |
12 | type Props = {
13 | viewType: string | null;
14 | };
15 |
16 | export const CompletedCyclesList: React.FC = ({ viewType }) => {
17 | const router = useRouter();
18 | const { workspaceSlug, projectId } = router.query;
19 |
20 | const { data: completedCyclesList } = useSWR(
21 | workspaceSlug && projectId ? COMPLETED_CYCLES_LIST(projectId.toString()) : null,
22 | workspaceSlug && projectId
23 | ? () =>
24 | cyclesService.getCyclesWithParams(
25 | workspaceSlug.toString(),
26 | projectId.toString(),
27 | "completed"
28 | )
29 | : null
30 | );
31 |
32 | return ;
33 | };
34 |
--------------------------------------------------------------------------------
/apps/app/components/ui/buttons/primary-button.tsx:
--------------------------------------------------------------------------------
1 | // types
2 | import { ButtonProps } from "./type";
3 |
4 | export const PrimaryButton: React.FC = ({
5 | children,
6 | className = "",
7 | onClick,
8 | type = "button",
9 | disabled = false,
10 | loading = false,
11 | size = "sm",
12 | outline = false,
13 | }) => (
14 |
30 | {children}
31 |
32 | );
33 |
--------------------------------------------------------------------------------
/apps/app/components/ui/graphs/line-graph.tsx:
--------------------------------------------------------------------------------
1 | // nivo
2 | import { ResponsiveLine, LineSvgProps } from "@nivo/line";
3 | // helpers
4 | import { generateYAxisTickValues } from "helpers/graph.helper";
5 | // types
6 | import { TGraph } from "./types";
7 | // constants
8 | import { CHARTS_THEME, DEFAULT_MARGIN } from "constants/graph";
9 |
10 | type Props = {
11 | customYAxisTickValues?: number[];
12 | };
13 |
14 | export const LineGraph: React.FC = ({
15 | customYAxisTickValues,
16 | height = "400px",
17 | width = "100%",
18 | margin,
19 | theme,
20 | ...rest
21 | }) => (
22 |
23 |
36 |
37 | );
38 |
--------------------------------------------------------------------------------
/apps/app/components/icons/pencil-scribble-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import type { Props } from "./types";
4 |
5 | export const PencilScribbleIcon: React.FC = ({
6 | width = "20",
7 | height = "20",
8 | className,
9 | color = "#000000",
10 | }) => (
11 |
18 |
22 |
23 | );
24 |
--------------------------------------------------------------------------------
/apps/app/components/icons/contrast-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import type { Props } from "./types";
4 |
5 | export const ContrastIcon: React.FC = ({
6 | width = "24",
7 | height = "24",
8 | color = "rgb(var(--color-text-200))",
9 | className,
10 | }) => (
11 |
19 |
20 |
24 |
25 | );
26 |
--------------------------------------------------------------------------------
/apps/app/components/icons/comment-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import type { Props } from "./types";
4 |
5 | export const CommentIcon: React.FC = ({ width = "24", height = "24", className }) => (
6 |
14 |
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/apps/app/constants/inbox.ts:
--------------------------------------------------------------------------------
1 | export const INBOX_STATUS = [
2 | {
3 | key: "pending",
4 | label: "Pending",
5 | value: -2,
6 | textColor: "text-yellow-500",
7 | bgColor: "bg-yellow-500/10",
8 | borderColor: "border-yellow-500",
9 | },
10 | {
11 | key: "declined",
12 | label: "Declined",
13 | value: -1,
14 | textColor: "text-red-500",
15 | bgColor: "bg-red-500/10",
16 | borderColor: "border-red-500",
17 | },
18 | {
19 | key: "snoozed",
20 | label: "Snoozed",
21 | value: 0,
22 | textColor: "text-custom-text-200",
23 | bgColor: "bg-gray-500/10",
24 | borderColor: "border-gray-500",
25 | },
26 | {
27 | key: "accepted",
28 | label: "Accepted",
29 | value: 1,
30 | textColor: "text-green-500",
31 | bgColor: "bg-green-500/10",
32 | borderColor: "border-green-500",
33 | },
34 | {
35 | key: "duplicate",
36 | label: "Duplicate",
37 | value: 2,
38 | textColor: "text-custom-text-200",
39 | bgColor: "bg-gray-500/10",
40 | borderColor: "border-gray-500",
41 | },
42 | ];
43 |
44 | export const INBOX_ISSUE_SOURCE = "in-app";
45 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0034_auto_20230628_1046.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.19 on 2023-06-28 05:16
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('db', '0033_auto_20230618_2125'),
10 | ]
11 |
12 | operations = [
13 | migrations.RemoveField(
14 | model_name='timelineissue',
15 | name='created_by',
16 | ),
17 | migrations.RemoveField(
18 | model_name='timelineissue',
19 | name='issue',
20 | ),
21 | migrations.RemoveField(
22 | model_name='timelineissue',
23 | name='project',
24 | ),
25 | migrations.RemoveField(
26 | model_name='timelineissue',
27 | name='updated_by',
28 | ),
29 | migrations.RemoveField(
30 | model_name='timelineissue',
31 | name='workspace',
32 | ),
33 | migrations.DeleteModel(
34 | name='Shortcut',
35 | ),
36 | migrations.DeleteModel(
37 | name='TimelineIssue',
38 | ),
39 | ]
40 |
--------------------------------------------------------------------------------
/apps/app/components/gantt-chart/types/index.ts:
--------------------------------------------------------------------------------
1 | // context types
2 | export type allViewsType = {
3 | key: string;
4 | title: string;
5 | data: Object | null;
6 | };
7 |
8 | export interface ChartContextData {
9 | allViews: allViewsType[];
10 | currentView: "hours" | "day" | "week" | "bi_week" | "month" | "quarter" | "year";
11 | currentViewData: any;
12 | renderView: any;
13 | }
14 |
15 | export type ChartContextActionPayload = {
16 | type: "CURRENT_VIEW" | "CURRENT_VIEW_DATA" | "PARTIAL_UPDATE" | "RENDER_VIEW";
17 | payload: any;
18 | };
19 |
20 | export interface ChartContextReducer extends ChartContextData {
21 | dispatch: (action: ChartContextActionPayload) => void;
22 | }
23 |
24 | // chart render types
25 | export interface WeekMonthDataType {
26 | key: number;
27 | shortTitle: string;
28 | title: string;
29 | }
30 |
31 | export interface ChartDataType {
32 | key: string;
33 | title: string;
34 | data: ChartDataTypeData;
35 | }
36 |
37 | export interface ChartDataTypeData {
38 | startDate: Date;
39 | currentDate: Date;
40 | endDate: Date;
41 | approxFilterRange: number;
42 | width: number;
43 | }
44 |
--------------------------------------------------------------------------------
/apps/app/next.config.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config({ path: ".env" });
2 |
3 | const { withSentryConfig } = require("@sentry/nextjs");
4 | const path = require("path");
5 | const extraImageDomains = (process.env.NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS ?? "")
6 | .split(",")
7 | .filter((domain) => domain.length > 0);
8 |
9 | const nextConfig = {
10 | reactStrictMode: false,
11 | swcMinify: true,
12 | images: {
13 | domains: [
14 | "vinci-web.s3.amazonaws.com",
15 | "planefs-staging.s3.ap-south-1.amazonaws.com",
16 | "planefs.s3.amazonaws.com",
17 | "images.unsplash.com",
18 | "avatars.githubusercontent.com",
19 | "localhost",
20 | ...extraImageDomains,
21 | ],
22 | },
23 | output: "standalone",
24 | experimental: {
25 | // this includes files from the monorepo base two directories up
26 | outputFileTracingRoot: path.join(__dirname, "../../"),
27 | },
28 | };
29 |
30 | if (parseInt(process.env.NEXT_PUBLIC_ENABLE_SENTRY || "0")) {
31 | module.exports = withSentryConfig(nextConfig, { silent: true }, { hideSourceMaps: true });
32 | } else {
33 | module.exports = nextConfig;
34 | }
35 |
--------------------------------------------------------------------------------
/apiserver/plane/db/models/integration/slack.py:
--------------------------------------------------------------------------------
1 | # Python imports
2 | import uuid
3 |
4 | # Django imports
5 | from django.db import models
6 |
7 | # Module imports
8 | from plane.db.models import ProjectBaseModel
9 |
10 |
11 | class SlackProjectSync(ProjectBaseModel):
12 | access_token = models.CharField(max_length=300)
13 | scopes = models.TextField()
14 | bot_user_id = models.CharField(max_length=50)
15 | webhook_url = models.URLField(max_length=1000)
16 | data = models.JSONField(default=dict)
17 | team_id = models.CharField(max_length=30)
18 | team_name = models.CharField(max_length=300)
19 | workspace_integration = models.ForeignKey(
20 | "db.WorkspaceIntegration", related_name="slack_syncs", on_delete=models.CASCADE
21 | )
22 |
23 | def __str__(self):
24 | """Return the repo name"""
25 | return f"{self.project.name}"
26 |
27 | class Meta:
28 | unique_together = ["team_id", "project"]
29 | verbose_name = "Slack Project Sync"
30 | verbose_name_plural = "Slack Project Syncs"
31 | db_table = "slack_project_syncs"
32 | ordering = ("-created_at",)
33 |
--------------------------------------------------------------------------------
/apps/app/components/ui/buttons/danger-button.tsx:
--------------------------------------------------------------------------------
1 | // types
2 | import { ButtonProps } from "./type";
3 |
4 | export const DangerButton: React.FC = ({
5 | children,
6 | className = "",
7 | onClick,
8 | type = "button",
9 | disabled = false,
10 | loading = false,
11 | size = "sm",
12 | outline = false,
13 | }) => (
14 |
34 | {children}
35 |
36 | );
37 |
--------------------------------------------------------------------------------
/apiserver/plane/settings/test.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from .common import * # noqa
4 |
5 | DEBUG = True
6 |
7 | INSTALLED_APPS.append("plane.tests")
8 |
9 | if os.environ.get('GITHUB_WORKFLOW'):
10 | DATABASES = {
11 | 'default': {
12 | 'ENGINE': 'django.db.backends.postgresql',
13 | 'NAME': 'github_actions',
14 | 'USER': 'postgres',
15 | 'PASSWORD': 'postgres',
16 | 'HOST': '127.0.0.1',
17 | 'PORT': '5432',
18 | }
19 | }
20 | else:
21 | DATABASES = {
22 | 'default': {
23 | 'ENGINE': 'django.db.backends.postgresql',
24 | 'NAME': 'plane_test',
25 | 'USER': 'postgres',
26 | 'PASSWORD': 'password123',
27 | 'HOST': '127.0.0.1',
28 | 'PORT': '5432',
29 | }
30 | }
31 |
32 | REDIS_HOST = "localhost"
33 | REDIS_PORT = 6379
34 | REDIS_URL = False
35 |
36 | RQ_QUEUES = {
37 | "default": {
38 | "HOST": "localhost",
39 | "PORT": 6379,
40 | "DB": 0,
41 | "DEFAULT_TIMEOUT": 360,
42 | },
43 | }
44 |
45 | WEB_URL = "http://localhost:3000"
46 |
--------------------------------------------------------------------------------
/apps/app/constants/due-dates.ts:
--------------------------------------------------------------------------------
1 | // helper
2 | import { renderDateFormat } from "helpers/date-time.helper";
3 |
4 | export const DUE_DATES = [
5 | {
6 | name: "Last week",
7 | value: [
8 | `${renderDateFormat(new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000))};after`,
9 | `${renderDateFormat(new Date())};before`,
10 | ],
11 | },
12 | {
13 | name: "2 weeks from now",
14 | value: [
15 | `${renderDateFormat(new Date())};after`,
16 | `${renderDateFormat(new Date(new Date().getTime() + 14 * 24 * 60 * 60 * 1000))};before`,
17 | ],
18 | },
19 | {
20 | name: "1 month from now",
21 | value: [
22 | `${renderDateFormat(new Date())};after`,
23 | `${renderDateFormat(
24 | new Date(new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate())
25 | )};before`,
26 | ],
27 | },
28 | {
29 | name: "2 months from now",
30 | value: [
31 | `${renderDateFormat(new Date())};after`,
32 | `${renderDateFormat(
33 | new Date(new Date().getFullYear(), new Date().getMonth() + 2, new Date().getDate())
34 | )};before`,
35 | ],
36 | },
37 | ];
38 |
--------------------------------------------------------------------------------
/apps/app/layouts/auth-layout/user-authorization-wrapper.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router";
2 |
3 | import useSWR from "swr";
4 |
5 | // services
6 | import userService from "services/user.service";
7 | // ui
8 | import { Spinner } from "components/ui";
9 | // fetch-keys
10 | import { CURRENT_USER } from "constants/fetch-keys";
11 |
12 | type Props = {
13 | children: React.ReactNode;
14 | };
15 |
16 | export const UserAuthorizationLayout: React.FC = ({ children }) => {
17 | const router = useRouter();
18 |
19 | const { data: currentUser, error } = useSWR(CURRENT_USER, () => userService.currentUser());
20 |
21 | if (!currentUser && !error) {
22 | return (
23 |
24 |
25 |
Loading your profile...
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | if (error) {
33 | const redirectTo = router.asPath;
34 |
35 | router.push(`/?next=${redirectTo}`);
36 | return null;
37 | }
38 |
39 | return <>{children}>;
40 | };
41 |
--------------------------------------------------------------------------------
/apps/app/hooks/gantt-chart/view-issues-view.tsx:
--------------------------------------------------------------------------------
1 | import useSWR from "swr";
2 |
3 | // services
4 | import issuesService from "services/issues.service";
5 | // hooks
6 | import useIssuesView from "hooks/use-issues-view";
7 | // fetch-keys
8 | import { VIEW_ISSUES } from "constants/fetch-keys";
9 |
10 | const useGanttChartViewIssues = (
11 | workspaceSlug: string | undefined,
12 | projectId: string | undefined,
13 | viewId: string | undefined
14 | ) => {
15 | const { params } = useIssuesView();
16 | const { order_by, group_by, ...viewGanttParams } = params;
17 |
18 | // all issues under the view
19 | const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
20 | workspaceSlug && projectId && viewId ? VIEW_ISSUES(viewId.toString(), viewGanttParams) : null,
21 | workspaceSlug && projectId && viewId
22 | ? () =>
23 | issuesService.getIssuesWithParams(
24 | workspaceSlug.toString(),
25 | projectId.toString(),
26 | viewGanttParams
27 | )
28 | : null
29 | );
30 |
31 | return {
32 | ganttIssues,
33 | mutateGanttIssues,
34 | };
35 | };
36 |
37 | export default useGanttChartViewIssues;
38 |
--------------------------------------------------------------------------------
/apps/app/components/icons/cycle-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import type { Props } from "./types";
4 |
5 | export const CyclesIcon: React.FC = ({
6 | width = "24",
7 | height = "24",
8 | className,
9 | color = "rgb(var(--color-text-200))",
10 | }) => (
11 |
19 |
26 |
33 |
34 | );
35 |
--------------------------------------------------------------------------------
/apiserver/plane/middleware/user_middleware.py:
--------------------------------------------------------------------------------
1 | import jwt
2 | import pytz
3 | from django.conf import settings
4 | from django.utils import timezone
5 | from plane.db.models import User
6 |
7 |
8 | class UserMiddleware(object):
9 |
10 | def __init__(self, get_response):
11 | self.get_response = get_response
12 |
13 | def __call__(self, request):
14 |
15 | try:
16 | if request.headers.get("Authorization"):
17 | authorization_header = request.headers.get("Authorization")
18 | access_token = authorization_header.split(" ")[1]
19 | decoded = jwt.decode(
20 | access_token, settings.SECRET_KEY, algorithms=["HS256"]
21 | )
22 | id = decoded['user_id']
23 | user = User.objects.get(id=id)
24 | user.last_active = timezone.now()
25 | user.token_updated_at = None
26 | user.save()
27 | timezone.activate(pytz.timezone(user.user_timezone))
28 | except Exception as e:
29 | print(e)
30 |
31 | response = self.get_response(request)
32 |
33 | return response
34 |
--------------------------------------------------------------------------------
/apps/app/components/icons/plus-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import type { Props } from "./types";
4 |
5 | export const PlusIcon: React.FC = ({ width = "24", height = "24", className }) => (
6 |
14 |
18 |
19 | );
20 |
--------------------------------------------------------------------------------
/apps/app/components/icons/view-list-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import type { Props } from "./types";
4 |
5 | export const ViewListIcon: React.FC = ({
6 | width = "24",
7 | height = "24",
8 | className,
9 | color = "rgb(var(--color-text-200))",
10 | }) => (
11 |
19 |
23 |
24 | );
25 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0010_auto_20221213_0037.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.14 on 2022-12-13 18:18
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('db', '0009_auto_20221208_0310'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='projectidentifier',
16 | name='workspace',
17 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='project_identifiers', to='db.workspace'),
18 | ),
19 | migrations.AlterField(
20 | model_name='project',
21 | name='identifier',
22 | field=models.CharField(max_length=5, verbose_name='Project Identifier'),
23 | ),
24 | migrations.AlterUniqueTogether(
25 | name='project',
26 | unique_together={('name', 'workspace'), ('identifier', 'workspace')},
27 | ),
28 | migrations.AlterUniqueTogether(
29 | name='projectidentifier',
30 | unique_together={('name', 'workspace')},
31 | ),
32 | ]
33 |
--------------------------------------------------------------------------------
/apps/app/components/icons/check.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import type { Props } from "./types";
4 |
5 | export const CheckIcon: React.FC = ({
6 | width = "24",
7 | height = "24",
8 | color = "rgb(var(--color-text-200))",
9 | className,
10 | }) => (
11 |
19 |
23 |
24 | );
25 |
--------------------------------------------------------------------------------
/apps/app/components/ui/linear-progress-indicator.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Tooltip } from "./tooltip";
3 |
4 | type Props = {
5 | data: any;
6 | noTooltip?: boolean
7 | };
8 |
9 | export const LinearProgressIndicator: React.FC = ({ data, noTooltip=false }) => {
10 | const total = data.reduce((acc: any, cur: any) => acc + cur.value, 0);
11 | let progress = 0;
12 |
13 | const bars = data.map((item: any) => {
14 | const width = `${(item.value / total) * 100}%`;
15 | const style = {
16 | width,
17 | backgroundColor: item.color,
18 | };
19 | progress += item.value;
20 | if (noTooltip) return
21 | else return (
22 |
23 |
24 |
25 | );
26 | });
27 |
28 | return (
29 |
30 | {total === 0 ? (
31 |
{bars}
32 | ) : (
33 |
{bars}
34 | )}
35 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/apps/app/hooks/use-workspace-details.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { useRouter } from "next/router";
3 | import useSWR from "swr";
4 | // services
5 | import workspaceService from "services/workspace.service";
6 | // fetch-keys
7 | import { WORKSPACE_DETAILS } from "constants/fetch-keys";
8 |
9 | const useWorkspaceDetails = () => {
10 | const router = useRouter();
11 | const { workspaceSlug } = router.query;
12 | // Fetching Workspace Details
13 | const {
14 | data: workspaceDetails,
15 | error: workspaceDetailsError,
16 | mutate: mutateWorkspaceDetails,
17 | } = useSWR(
18 | workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null,
19 | workspaceSlug ? () => workspaceService.getWorkspace(workspaceSlug as string) : null
20 | );
21 |
22 | useEffect(() => {
23 | if (workspaceDetailsError?.status === 404) {
24 | router.push("/404");
25 | } else if (workspaceDetailsError) {
26 | router.push("/error");
27 | }
28 | }, [workspaceDetailsError, router]);
29 |
30 | return {
31 | workspaceDetails,
32 | workspaceDetailsError,
33 | mutateWorkspaceDetails,
34 | };
35 | };
36 |
37 | export default useWorkspaceDetails;
38 |
--------------------------------------------------------------------------------
/apiserver/plane/api/serializers/analytic.py:
--------------------------------------------------------------------------------
1 | from .base import BaseSerializer
2 | from plane.db.models import AnalyticView
3 | from plane.utils.issue_filters import issue_filters
4 |
5 |
6 | class AnalyticViewSerializer(BaseSerializer):
7 | class Meta:
8 | model = AnalyticView
9 | fields = "__all__"
10 | read_only_fields = [
11 | "workspace",
12 | "query",
13 | ]
14 |
15 | def create(self, validated_data):
16 | query_params = validated_data.get("query_dict", {})
17 | if bool(query_params):
18 | validated_data["query"] = issue_filters(query_params, "POST")
19 | else:
20 | validated_data["query"] = dict()
21 | return AnalyticView.objects.create(**validated_data)
22 |
23 | def update(self, instance, validated_data):
24 | query_params = validated_data.get("query_data", {})
25 | if bool(query_params):
26 | validated_data["query"] = issue_filters(query_params, "POST")
27 | else:
28 | validated_data["query"] = dict()
29 | validated_data["query"] = issue_filters(query_params, "PATCH")
30 | return super().update(instance, validated_data)
31 |
--------------------------------------------------------------------------------
/apps/app/services/ai.service.ts:
--------------------------------------------------------------------------------
1 | // services
2 | import APIService from "services/api.service";
3 | import trackEventServices from "services/track-event.service";
4 |
5 | // types
6 | import { ICurrentUserResponse, IGptResponse } from "types";
7 |
8 | const { NEXT_PUBLIC_API_BASE_URL } = process.env;
9 |
10 | const trackEvent =
11 | process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
12 |
13 | class AiServices extends APIService {
14 | constructor() {
15 | super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
16 | }
17 |
18 | async createGptTask(
19 | workspaceSlug: string,
20 | projectId: string,
21 | data: { prompt: string; task: string },
22 | user: ICurrentUserResponse | undefined
23 | ): Promise {
24 | return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/ai-assistant/`, data)
25 | .then((response) => {
26 | if (trackEvent) trackEventServices.trackAskGptEvent(response?.data, "ASK_GPT", user);
27 | return response?.data;
28 | })
29 | .catch((error) => {
30 | throw error?.response;
31 | });
32 | }
33 | }
34 |
35 | export default new AiServices();
36 |
--------------------------------------------------------------------------------
/apps/app/layouts/app-layout/app-header.tsx:
--------------------------------------------------------------------------------
1 | // icons
2 | import { Bars3Icon } from "@heroicons/react/24/outline";
3 |
4 | type Props = {
5 | breadcrumbs?: JSX.Element;
6 | left?: JSX.Element;
7 | right?: JSX.Element;
8 | setToggleSidebar: React.Dispatch>;
9 | noHeader: boolean;
10 | };
11 |
12 | const Header: React.FC = ({ breadcrumbs, left, right, setToggleSidebar, noHeader }) => (
13 |
18 |
19 |
20 | setToggleSidebar((prevData) => !prevData)}
24 | >
25 |
26 |
27 |
28 | {breadcrumbs}
29 | {left}
30 |
31 | {right}
32 |
33 | );
34 |
35 | export default Header;
36 |
--------------------------------------------------------------------------------
/apiserver/plane/bgtasks/user_welcome_task.py:
--------------------------------------------------------------------------------
1 | # Django imports
2 | from django.conf import settings
3 |
4 | # Third party imports
5 | from celery import shared_task
6 | from sentry_sdk import capture_exception
7 | from slack_sdk import WebClient
8 | from slack_sdk.errors import SlackApiError
9 |
10 | # Module imports
11 | from plane.db.models import User
12 |
13 |
14 | @shared_task
15 | def send_welcome_slack(user_id, created, message):
16 | try:
17 | instance = User.objects.get(pk=user_id)
18 |
19 | if created and not instance.is_bot:
20 | # Send message on slack as well
21 | if settings.SLACK_BOT_TOKEN:
22 | client = WebClient(token=settings.SLACK_BOT_TOKEN)
23 | try:
24 | _ = client.chat_postMessage(
25 | channel="#trackers",
26 | text=message,
27 | )
28 | except SlackApiError as e:
29 | print(f"Got an error: {e.response['error']}")
30 | return
31 | except Exception as e:
32 | # Print logs if in DEBUG mode
33 | if settings.DEBUG:
34 | print(e)
35 | capture_exception(e)
36 | return
37 |
--------------------------------------------------------------------------------
/apps/app/constants/themes.ts:
--------------------------------------------------------------------------------
1 | export const THEMES = ["light", "dark", "light-contrast", "dark-contrast", "custom"];
2 |
3 | export const THEMES_OBJ = [
4 | {
5 | value: "light",
6 | label: "Light",
7 | type: "light",
8 | icon: {
9 | border: "#DEE2E6",
10 | color1: "#FAFAFA",
11 | color2: "#3F76FF",
12 | },
13 | },
14 | {
15 | value: "dark",
16 | label: "Dark",
17 | type: "dark",
18 | icon: {
19 | border: "#2E3234",
20 | color1: "#191B1B",
21 | color2: "#3C85D9",
22 | },
23 | },
24 | {
25 | value: "light-contrast",
26 | label: "Light High Contrast",
27 | type: "light",
28 | icon: {
29 | border: "#000000",
30 | color1: "#FFFFFF",
31 | color2: "#3F76FF",
32 | },
33 | },
34 | {
35 | value: "dark-contrast",
36 | label: "Dark High Contrast",
37 | type: "dark",
38 | icon: {
39 | border: "#FFFFFF",
40 | color1: "#030303",
41 | color2: "#3A8BE9",
42 | },
43 | },
44 | {
45 | value: "custom",
46 | label: "Custom Theming",
47 | type: "light",
48 | icon: {
49 | border: "#FFC9C9",
50 | color1: "#FFF7F7",
51 | color2: "#FF5151",
52 | },
53 | },
54 | ];
55 |
--------------------------------------------------------------------------------
/apiserver/plane/db/models/social_connection.py:
--------------------------------------------------------------------------------
1 | # Django imports
2 | from django.db import models
3 | from django.conf import settings
4 | from django.utils import timezone
5 |
6 | # Module import
7 | from . import BaseModel
8 |
9 |
10 | class SocialLoginConnection(BaseModel):
11 | medium = models.CharField(
12 | max_length=20,
13 | choices=(("Google", "google"), ("Github", "github")),
14 | default=None,
15 | )
16 | last_login_at = models.DateTimeField(default=timezone.now, null=True)
17 | last_received_at = models.DateTimeField(default=timezone.now, null=True)
18 | user = models.ForeignKey(
19 | settings.AUTH_USER_MODEL,
20 | on_delete=models.CASCADE,
21 | related_name="user_login_connections",
22 | )
23 | token_data = models.JSONField(null=True)
24 | extra_data = models.JSONField(null=True)
25 |
26 | class Meta:
27 | verbose_name = "Social Login Connection"
28 | verbose_name_plural = "Social Login Connections"
29 | db_table = "social_login_connections"
30 | ordering = ("-created_at",)
31 |
32 | def __str__(self):
33 | """Return name of the user and medium"""
34 | return f"{self.medium} <{self.user.email}>"
35 |
--------------------------------------------------------------------------------
/apiserver/plane/db/migrations/0013_auto_20230107_0041.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.16 on 2023-01-06 19:11
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('db', '0012_auto_20230104_0117'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='issue',
15 | name='description_html',
16 | field=models.TextField(blank=True),
17 | ),
18 | migrations.AddField(
19 | model_name='issue',
20 | name='description_stripped',
21 | field=models.TextField(blank=True),
22 | ),
23 | migrations.AddField(
24 | model_name='user',
25 | name='role',
26 | field=models.CharField(blank=True, max_length=300, null=True),
27 | ),
28 | migrations.AddField(
29 | model_name='workspacemember',
30 | name='view_props',
31 | field=models.JSONField(blank=True, null=True),
32 | ),
33 | migrations.AlterField(
34 | model_name='issue',
35 | name='description',
36 | field=models.JSONField(blank=True),
37 | ),
38 | ]
39 |
--------------------------------------------------------------------------------
/apiserver/plane/db/models/api_token.py:
--------------------------------------------------------------------------------
1 | # Python imports
2 | from uuid import uuid4
3 |
4 | # Django imports
5 | from django.db import models
6 | from django.conf import settings
7 |
8 | from .base import BaseModel
9 |
10 |
11 | def generate_label_token():
12 | return uuid4().hex
13 |
14 |
15 | def generate_token():
16 | return uuid4().hex + uuid4().hex
17 |
18 |
19 | class APIToken(BaseModel):
20 | token = models.CharField(max_length=255, unique=True, default=generate_token)
21 | label = models.CharField(max_length=255, default=generate_label_token)
22 | user = models.ForeignKey(
23 | settings.AUTH_USER_MODEL,
24 | on_delete=models.CASCADE,
25 | related_name="bot_tokens",
26 | )
27 | user_type = models.PositiveSmallIntegerField(
28 | choices=((0, "Human"), (1, "Bot")), default=0
29 | )
30 | workspace = models.ForeignKey(
31 | "db.Workspace", related_name="api_tokens", on_delete=models.CASCADE, null=True
32 | )
33 |
34 | class Meta:
35 | verbose_name = "API Token"
36 | verbose_name_plural = "API Tokems"
37 | db_table = "api_tokens"
38 | ordering = ("-created_at",)
39 |
40 | def __str__(self):
41 | return str(self.user.name)
42 |
--------------------------------------------------------------------------------
/apps/app/components/icons/x-mark-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import type { Props } from "./types";
4 |
5 | export const XMarkIcon: React.FC = ({ width = "24", height = "24", className, color }) => (
6 |
14 |
18 |
19 | );
20 |
--------------------------------------------------------------------------------