diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6baa0bb07..b25a791d0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,8 +8,8 @@ Before submitting a new issue, please search the [issues](https://github.com/mak While we want to fix all the [issues](https://github.com/makeplane/plane/issues), before fixing a bug we need to be able to reproduce and confirm it. Please provide us with a minimal reproduction scenario using a repository or [Gist](https://gist.github.com/). Having a live, reproducible scenario gives us the information without asking questions back & forth with additional questions like: -- 3rd-party libraries being used and their versions -- a use-case that fails +- 3rd-party libraries being used and their versions +- a use-case that fails Without said minimal reproduction, we won't be able to investigate all [issues](https://github.com/makeplane/plane/issues), and the issue might not be resolved. @@ -19,10 +19,10 @@ You can open a new issue with this [issue form](https://github.com/makeplane/pla ### Requirements -- Node.js version v16.18.0 -- Python version 3.8+ -- Postgres version v14 -- Redis version v6.2.7 +- Node.js version v16.18.0 +- Python version 3.8+ +- Postgres version v14 +- Redis version v6.2.7 ### Setup the project @@ -30,6 +30,48 @@ The project is a monorepo, with backend api and frontend in a single repo. The backend is a django project which is kept inside apiserver +1. Clone the repo + +```bash +git clone https://github.com/makeplane/plane +cd plane +chmod +x setup.sh +``` + +2. Run setup.sh + +```bash +./setup.sh +``` + +3. Define `NEXT_PUBLIC_API_BASE_URL=http://localhost` in **web/.env** and **space/.env** file + +```bash +echo "\nNEXT_PUBLIC_API_BASE_URL=http://localhost\n" >> ./web/.env +``` + +```bash +echo "\nNEXT_PUBLIC_API_BASE_URL=http://localhost\n" >> ./space/.env +``` + +4. Run Docker compose up + +```bash +docker compose up -d +``` + +5. Install dependencies + +```bash +yarn install +``` + +6. Run the web app in development mode + +```bash +yarn dev +``` + ## Missing a Feature? If a feature is missing, you can directly _request_ a new one [here](https://github.com/makeplane/plane/issues/new?assignees=&labels=feature&template=feature_request.yml&title=%F0%9F%9A%80+Feature%3A+). You also can do the same by choosing "🚀 Feature" when raising a [New Issue](https://github.com/makeplane/plane/issues/new/choose) on our GitHub Repository. @@ -39,8 +81,8 @@ If you would like to _implement_ it, an issue with your proposal must be submitt To ensure consistency throughout the source code, please keep these rules in mind as you are working: -- All features or bug fixes must be tested by one or more specs (unit-tests). -- We use [Eslint default rule guide](https://eslint.org/docs/rules/), with minor changes. An automated formatter is available using prettier. +- All features or bug fixes must be tested by one or more specs (unit-tests). +- We use [Eslint default rule guide](https://eslint.org/docs/rules/), with minor changes. An automated formatter is available using prettier. ## Need help? Questions and suggestions @@ -48,11 +90,11 @@ Questions, suggestions, and thoughts are most welcome. We can also be reached in ## Ways to contribute -- Try Plane Cloud and the self hosting platform and give feedback -- Add new integrations -- Help with open [issues](https://github.com/makeplane/plane/issues) or [create your own](https://github.com/makeplane/plane/issues/new/choose) -- Share your thoughts and suggestions with us -- Help create tutorials and blog posts -- Request a feature by submitting a proposal -- Report a bug -- **Improve documentation** - fix incomplete or missing [docs](https://docs.plane.so/), bad wording, examples or explanations. +- Try Plane Cloud and the self hosting platform and give feedback +- Add new integrations +- Help with open [issues](https://github.com/makeplane/plane/issues) or [create your own](https://github.com/makeplane/plane/issues/new/choose) +- Share your thoughts and suggestions with us +- Help create tutorials and blog posts +- Request a feature by submitting a proposal +- Report a bug +- **Improve documentation** - fix incomplete or missing [docs](https://docs.plane.so/), bad wording, examples or explanations. diff --git a/apiserver/plane/db/migrations/0044_auto_20230913_0709.py b/apiserver/plane/db/migrations/0044_auto_20230913_0709.py index f30062371..19a1449af 100644 --- a/apiserver/plane/db/migrations/0044_auto_20230913_0709.py +++ b/apiserver/plane/db/migrations/0044_auto_20230913_0709.py @@ -26,19 +26,19 @@ def workspace_member_props(old_props): "calendar_date_range": old_props.get("calendarDateRange", ""), }, "display_properties": { - "assignee": old_props.get("properties", {}).get("assignee",None), - "attachment_count": old_props.get("properties", {}).get("attachment_count", None), - "created_on": old_props.get("properties", {}).get("created_on", None), - "due_date": old_props.get("properties", {}).get("due_date", None), - "estimate": old_props.get("properties", {}).get("estimate", None), - "key": old_props.get("properties", {}).get("key", None), - "labels": old_props.get("properties", {}).get("labels", None), - "link": old_props.get("properties", {}).get("link", None), - "priority": old_props.get("properties", {}).get("priority", None), - "start_date": old_props.get("properties", {}).get("start_date", None), - "state": old_props.get("properties", {}).get("state", None), - "sub_issue_count": old_props.get("properties", {}).get("sub_issue_count", None), - "updated_on": old_props.get("properties", {}).get("updated_on", None), + "assignee": old_props.get("properties", {}).get("assignee", True), + "attachment_count": old_props.get("properties", {}).get("attachment_count", True), + "created_on": old_props.get("properties", {}).get("created_on", True), + "due_date": old_props.get("properties", {}).get("due_date", True), + "estimate": old_props.get("properties", {}).get("estimate", True), + "key": old_props.get("properties", {}).get("key", True), + "labels": old_props.get("properties", {}).get("labels", True), + "link": old_props.get("properties", {}).get("link", True), + "priority": old_props.get("properties", {}).get("priority", True), + "start_date": old_props.get("properties", {}).get("start_date", True), + "state": old_props.get("properties", {}).get("state", True), + "sub_issue_count": old_props.get("properties", {}).get("sub_issue_count", True), + "updated_on": old_props.get("properties", {}).get("updated_on", True), }, } return new_props diff --git a/apiserver/plane/db/migrations/0045_auto_20230915_0655.py b/apiserver/plane/db/migrations/0045_auto_20230915_0655.py index a8360c63d..cd9aa6902 100644 --- a/apiserver/plane/db/migrations/0045_auto_20230915_0655.py +++ b/apiserver/plane/db/migrations/0045_auto_20230915_0655.py @@ -1,24 +1,42 @@ # Generated by Django 4.2.3 on 2023-09-15 06:55 -from django.db import migrations - - -def update_issue_activity(apps, schema_editor): - IssueActivityModel = apps.get_model("db", "IssueActivity") - updated_issue_activity = [] - for obj in IssueActivityModel.objects.all(): - if obj.field == "blocks": - obj.field = "blocked_by" - updated_issue_activity.append(obj) - IssueActivityModel.objects.bulk_update(updated_issue_activity, ["field"], batch_size=100) +from django.db import migrations, models +from django.conf import settings +import django.db.models.deletion +import uuid class Migration(migrations.Migration): - dependencies = [ - ('db', '0044_auto_20230913_0709'), + ("db", "0044_auto_20230913_0709"), ] operations = [ - migrations.RunPython(update_issue_activity), + migrations.CreateModel( + name="GlobalView", + fields=[ + ("created_at", models.DateTimeField(auto_now_add=True, verbose_name="Created At"),), + ("updated_at", models.DateTimeField(auto_now=True, verbose_name="Last Modified At"),), + ("id", models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True,),), + ("name", models.CharField(max_length=255, verbose_name="View Name")), + ("description", models.TextField(blank=True, verbose_name="View Description"),), + ("query", models.JSONField(verbose_name="View Query")), + ("access", models.PositiveSmallIntegerField(choices=[(0, "Private"), (1, "Public")], default=1),), + ("query_data", models.JSONField(default=dict)), + ("created_by", models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="%(class)s_created_by", to=settings.AUTH_USER_MODEL, verbose_name="Created By",),), + ("updated_by", models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="%(class)s_updated_by", to=settings.AUTH_USER_MODEL, verbose_name="Last Modified By",),), + ("workspace", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="global_views", to="db.workspace",),), + ], + options={ + "verbose_name": "Global View", + "verbose_name_plural": "Global Views", + "db_table": "global_views", + "ordering": ("-created_at",), + }, + ), + migrations.AddField( + model_name="issueactivity", + name="epoch", + field=models.FloatField(null=True), + ), ] diff --git a/apiserver/plane/db/migrations/0046_auto_20230919_1421.py b/apiserver/plane/db/migrations/0046_auto_20230919_1421.py deleted file mode 100644 index 4005a94d4..000000000 --- a/apiserver/plane/db/migrations/0046_auto_20230919_1421.py +++ /dev/null @@ -1,53 +0,0 @@ -# Generated by Django 4.2.3 on 2023-09-19 14:21 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -def update_epoch(apps, schema_editor): - IssueActivity = apps.get_model('db', 'IssueActivity') - updated_issue_activity = [] - for obj in IssueActivity.objects.all(): - obj.epoch = int(obj.created_at.timestamp()) - updated_issue_activity.append(obj) - IssueActivity.objects.bulk_update(updated_issue_activity, ["epoch"], batch_size=100) - - -class Migration(migrations.Migration): - - dependencies = [ - ('db', '0045_auto_20230915_0655'), - ] - - operations = [ - migrations.CreateModel( - name='GlobalView', - fields=[ - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), - ('name', models.CharField(max_length=255, verbose_name='View Name')), - ('description', models.TextField(blank=True, verbose_name='View Description')), - ('query', models.JSONField(verbose_name='View Query')), - ('access', models.PositiveSmallIntegerField(choices=[(0, 'Private'), (1, 'Public')], default=1)), - ('query_data', models.JSONField(default=dict)), - ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')), - ('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')), - ('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='global_views', to='db.workspace')), - ], - options={ - 'verbose_name': 'Global View', - 'verbose_name_plural': 'Global Views', - 'db_table': 'global_views', - 'ordering': ('-created_at',), - }, - ), - migrations.AddField( - model_name='issueactivity', - name='epoch', - field=models.FloatField(null=True), - ), - migrations.RunPython(update_epoch), - ] diff --git a/apiserver/plane/db/migrations/0046_auto_20230926_1015.py b/apiserver/plane/db/migrations/0046_auto_20230926_1015.py new file mode 100644 index 000000000..8bce37d95 --- /dev/null +++ b/apiserver/plane/db/migrations/0046_auto_20230926_1015.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.5 on 2023-09-26 10:15 + +from django.db import migrations + + +def update_issue_activity(apps, schema_editor): + IssueActivity = apps.get_model("db", "IssueActivity") + updated_issue_activity = [] + for obj in IssueActivity.objects.all(): + obj.epoch = int(obj.created_at.timestamp()) + updated_issue_activity.append(obj) + IssueActivity.objects.bulk_update( + updated_issue_activity, + ["epoch"], + batch_size=5000, + ) + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0045_auto_20230915_0655'), + ] + + operations = [ + migrations.RunPython(update_issue_activity), + ] diff --git a/apiserver/plane/db/migrations/0047_auto_20230921_0758.py b/apiserver/plane/db/migrations/0047_auto_20230921_0758.py deleted file mode 100644 index 4344963cd..000000000 --- a/apiserver/plane/db/migrations/0047_auto_20230921_0758.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 4.2.3 on 2023-09-21 07:58 - - -from django.db import migrations - - -def update_priority_history(apps, schema_editor): - IssueActivity = apps.get_model("db", "IssueActivity") - updated_issue_activity = [] - for obj in IssueActivity.objects.all(): - if obj.field == "priority": - obj.new_value = obj.new_value or "none" - obj.old_value = obj.old_value or "none" - updated_issue_activity.append(obj) - IssueActivity.objects.bulk_update( - updated_issue_activity, ["new_value", "old_value"], batch_size=100 - ) - - -class Migration(migrations.Migration): - dependencies = [ - ("db", "0046_auto_20230919_1421"), - ] - - operations = [ - migrations.RunPython(update_priority_history), - ] diff --git a/apiserver/plane/db/migrations/0047_auto_20230926_1029.py b/apiserver/plane/db/migrations/0047_auto_20230926_1029.py new file mode 100644 index 000000000..da64e11c8 --- /dev/null +++ b/apiserver/plane/db/migrations/0047_auto_20230926_1029.py @@ -0,0 +1,44 @@ +# Generated by Django 4.2.5 on 2023-09-26 10:29 + +from django.db import migrations + + +def update_issue_activity_priority(apps, schema_editor): + IssueActivity = apps.get_model("db", "IssueActivity") + updated_issue_activity = [] + for obj in IssueActivity.objects.filter(field="priority"): + # Set the old and new value to none if it is empty for Priority + obj.new_value = obj.new_value or "none" + obj.old_value = obj.old_value or "none" + updated_issue_activity.append(obj) + IssueActivity.objects.bulk_update( + updated_issue_activity, + ["new_value", "old_value"], + batch_size=1000, + ) + +def update_issue_activity_blocked(apps, schema_editor): + IssueActivity = apps.get_model("db", "IssueActivity") + updated_issue_activity = [] + for obj in IssueActivity.objects.filter(field="blocks"): + # Set the field to blocked_by + obj.field = "blocked_by" + updated_issue_activity.append(obj) + IssueActivity.objects.bulk_update( + updated_issue_activity, + ["field"], + batch_size=1000, + ) + + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0046_auto_20230926_1015'), + ] + + operations = [ + migrations.RunPython(update_issue_activity_priority), + migrations.RunPython(update_issue_activity_blocked), + ] diff --git a/nginx/nginx.conf.template b/nginx/nginx.conf.template index af80b04fa..4775dcbfa 100644 --- a/nginx/nginx.conf.template +++ b/nginx/nginx.conf.template @@ -11,6 +11,11 @@ http { client_max_body_size ${FILE_SIZE_LIMIT}; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Permissions-Policy "interest-cohort=()" always; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + location / { proxy_pass http://web:3000/; } @@ -20,6 +25,7 @@ http { } location /spaces/ { + rewrite ^/spaces/?$ /spaces/login break; proxy_pass http://space:3000/spaces/; } @@ -27,4 +33,4 @@ http { proxy_pass http://plane-minio:9000/uploads/; } } -} \ No newline at end of file +} diff --git a/space/components/accounts/sign-in.tsx b/space/components/accounts/sign-in.tsx index d3c29103d..c6a151d44 100644 --- a/space/components/accounts/sign-in.tsx +++ b/space/components/accounts/sign-in.tsx @@ -33,7 +33,7 @@ export const SignInView = observer(() => { const onSignInSuccess = (response: any) => { const isOnboarded = response?.user?.onboarding_step?.profile_complete || false; - const nextPath = router.asPath.includes("next_path") ? router.asPath.split("/?next_path=")[1] : "/"; + const nextPath = router.asPath.includes("next_path") ? router.asPath.split("/?next_path=")[1] : "/login"; userStore.setCurrentUser(response?.user); @@ -41,7 +41,7 @@ export const SignInView = observer(() => { router.push(`/onboarding?next_path=${nextPath}`); return; } - router.push((nextPath ?? "/").toString()); + router.push((nextPath ?? "/login").toString()); }; const handleGoogleSignIn = async ({ clientId, credential }: any) => { diff --git a/space/components/views/index.ts b/space/components/views/index.ts index 84d36cd29..f54d11bdd 100644 --- a/space/components/views/index.ts +++ b/space/components/views/index.ts @@ -1 +1 @@ -export * from "./home"; +export * from "./login"; diff --git a/space/components/views/home.tsx b/space/components/views/login.tsx similarity index 88% rename from space/components/views/home.tsx rename to space/components/views/login.tsx index 999fce073..d01a22681 100644 --- a/space/components/views/home.tsx +++ b/space/components/views/login.tsx @@ -4,7 +4,7 @@ import { useMobxStore } from "lib/mobx/store-provider"; // components import { SignInView, UserLoggedIn } from "components/accounts"; -export const HomeView = observer(() => { +export const LoginView = observer(() => { const { user: userStore } = useMobxStore(); if (!userStore.currentUser) return ; diff --git a/space/pages/index.tsx b/space/pages/index.tsx deleted file mode 100644 index fe0b7d33a..000000000 --- a/space/pages/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from "react"; - -// components -import { HomeView } from "components/views"; - -const HomePage = () => ; - -export default HomePage; diff --git a/space/pages/login/index.tsx b/space/pages/login/index.tsx new file mode 100644 index 000000000..a80eff873 --- /dev/null +++ b/space/pages/login/index.tsx @@ -0,0 +1,8 @@ +import React from "react"; + +// components +import { LoginView } from "components/views"; + +const LoginPage = () => ; + +export default LoginPage; \ No newline at end of file diff --git a/web/components/core/filters/issues-view-filter.tsx b/web/components/core/filters/issues-view-filter.tsx index f5b0f477a..2ad165dd8 100644 --- a/web/components/core/filters/issues-view-filter.tsx +++ b/web/components/core/filters/issues-view-filter.tsx @@ -93,7 +93,9 @@ export const IssuesFilterView: React.FC = () => { {replaceUnderscoreIfSnakeCase(option.type)} Layout + + {replaceUnderscoreIfSnakeCase(option.type)} Layout + } position="bottom" > @@ -318,7 +320,7 @@ export const IssuesFilterView: React.FC = () => { displayFilters.layout !== "spreadsheet" && displayFilters.layout !== "gantt_chart" && (
-

Show empty states

+

Show empty groups

= ({ : null ); + const { data: workspaceLabels } = useSWR( + workspaceSlug && displayFilters?.group_by === "labels" + ? WORKSPACE_LABELS(workspaceSlug.toString()) + : null, + workspaceSlug && displayFilters?.group_by === "labels" + ? () => issuesService.getWorkspaceLabels(workspaceSlug.toString()) + : null + ); + const { data: members } = useSWR( workspaceSlug && projectId && @@ -82,7 +91,10 @@ export const BoardHeader: React.FC = ({ title = addSpaceIfCamelCase(currentState?.name ?? ""); break; case "labels": - title = issueLabels?.find((label) => label.id === groupTitle)?.name ?? "None"; + title = + [...(issueLabels ?? []), ...(workspaceLabels ?? [])]?.find( + (label) => label.id === groupTitle + )?.name ?? "None"; break; case "project": title = projects?.find((p) => p.id === groupTitle)?.name ?? "None"; @@ -137,7 +149,9 @@ export const BoardHeader: React.FC = ({ break; case "labels": const labelColor = - issueLabels?.find((label) => label.id === groupTitle)?.color ?? "#000000"; + [...(issueLabels ?? []), ...(workspaceLabels ?? [])]?.find( + (label) => label.id === groupTitle + )?.color ?? "#000000"; icon = ( = ({ {calendarIssues ? (
-
+
void; onSuccess?: (data: IIssue) => Promise | void; prePopulatedData?: Partial; + dependencies: any[]; }; -const useCheckIfThereIsSpaceOnRight = (ref: React.RefObject) => { +const useCheckIfThereIsSpaceOnRight = (ref: React.RefObject, deps: any[]) => { const [isThereSpaceOnRight, setIsThereSpaceOnRight] = useState(true); + const router = useRouter(); + const { moduleId, cycleId, viewId } = router.query; + + const container = document.getElementById(`calendar-view-${cycleId ?? moduleId ?? viewId}`); + useEffect(() => { if (!ref.current) return; const { right } = ref.current.getBoundingClientRect(); - const width = right + 250; + const width = right; - if (width > window.innerWidth) setIsThereSpaceOnRight(false); + const innerWidth = container?.getBoundingClientRect().width ?? window.innerWidth; + + if (width > innerWidth) setIsThereSpaceOnRight(false); else setIsThereSpaceOnRight(true); - }, [ref]); + }, [ref, deps, container]); return isThereSpaceOnRight; }; @@ -63,11 +74,11 @@ const InlineInput = () => { }; export const CalendarInlineCreateIssueForm: React.FC = (props) => { - const { isOpen } = props; + const { isOpen, dependencies } = props; const ref = useRef(null); - const isSpaceOnRight = useCheckIfThereIsSpaceOnRight(ref); + const isSpaceOnRight = useCheckIfThereIsSpaceOnRight(ref, dependencies); return ( <> diff --git a/web/components/core/views/calendar-view/single-date.tsx b/web/components/core/views/calendar-view/single-date.tsx index 178151204..02ea56678 100644 --- a/web/components/core/views/calendar-view/single-date.tsx +++ b/web/components/core/views/calendar-view/single-date.tsx @@ -83,6 +83,7 @@ export const SingleCalendarDate: React.FC = (props) => { setIsCreateIssueFormOpen(false)} prePopulatedData={{ target_date: date.date, diff --git a/web/components/core/views/inline-issue-create-wrapper.tsx b/web/components/core/views/inline-issue-create-wrapper.tsx index 3c01c50ce..ace407ae1 100644 --- a/web/components/core/views/inline-issue-create-wrapper.tsx +++ b/web/components/core/views/inline-issue-create-wrapper.tsx @@ -218,11 +218,11 @@ export const InlineCreateIssueFormWrapper: React.FC = (props) => { ); if (isDraftIssues) - mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(projectId.toString() ?? "", params)); - if (displayFilters.layout === "calendar") mutate(calendarFetchKey); - if (displayFilters.layout === "gantt_chart") mutate(ganttFetchKey); - if (displayFilters.layout === "spreadsheet") mutate(spreadsheetFetchKey); - if (groupedIssues) mutateMyIssues(); + await mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(projectId.toString() ?? "", params)); + if (displayFilters.layout === "calendar") await mutate(calendarFetchKey); + if (displayFilters.layout === "gantt_chart") await mutate(ganttFetchKey); + if (displayFilters.layout === "spreadsheet") await mutate(spreadsheetFetchKey); + if (groupedIssues) await mutateMyIssues(); setToastAlert({ type: "success", diff --git a/web/components/core/views/list-view/single-list.tsx b/web/components/core/views/list-view/single-list.tsx index 50ca596c0..0c77c5222 100644 --- a/web/components/core/views/list-view/single-list.tsx +++ b/web/components/core/views/list-view/single-list.tsx @@ -34,7 +34,7 @@ import { UserAuth, } from "types"; // fetch-keys -import { PROJECT_ISSUE_LABELS, PROJECT_MEMBERS } from "constants/fetch-keys"; +import { PROJECT_ISSUE_LABELS, PROJECT_MEMBERS, WORKSPACE_LABELS } from "constants/fetch-keys"; // constants import { STATE_GROUP_COLORS } from "constants/state"; @@ -86,16 +86,33 @@ export const SingleList: React.FC = (props) => { const { displayFilters, groupedIssues } = viewProps; - const { data: issueLabels } = useSWR( - workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId as string) : null, - workspaceSlug && projectId - ? () => issuesService.getIssueLabels(workspaceSlug as string, projectId as string) + const { data: issueLabels } = useSWR( + workspaceSlug && projectId && displayFilters?.group_by === "labels" + ? PROJECT_ISSUE_LABELS(projectId.toString()) + : null, + workspaceSlug && projectId && displayFilters?.group_by === "labels" + ? () => issuesService.getIssueLabels(workspaceSlug.toString(), projectId.toString()) + : null + ); + + const { data: workspaceLabels } = useSWR( + workspaceSlug && displayFilters?.group_by === "labels" + ? WORKSPACE_LABELS(workspaceSlug.toString()) + : null, + workspaceSlug && displayFilters?.group_by === "labels" + ? () => issuesService.getWorkspaceLabels(workspaceSlug.toString()) : null ); const { data: members } = useSWR( - workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null, - workspaceSlug && projectId + workspaceSlug && + projectId && + (displayFilters?.group_by === "created_by" || displayFilters?.group_by === "assignees") + ? PROJECT_MEMBERS(projectId as string) + : null, + workspaceSlug && + projectId && + (displayFilters?.group_by === "created_by" || displayFilters?.group_by === "assignees") ? () => projectService.projectMembers(workspaceSlug as string, projectId as string) : null ); @@ -110,7 +127,10 @@ export const SingleList: React.FC = (props) => { title = addSpaceIfCamelCase(currentState?.name ?? ""); break; case "labels": - title = issueLabels?.find((label) => label.id === groupTitle)?.name ?? "None"; + title = + [...(issueLabels ?? []), ...(workspaceLabels ?? [])]?.find( + (label) => label.id === groupTitle + )?.name ?? "None"; break; case "project": title = projects?.find((p) => p.id === groupTitle)?.name ?? "None"; @@ -164,7 +184,9 @@ export const SingleList: React.FC = (props) => { break; case "labels": const labelColor = - issueLabels?.find((label) => label.id === groupTitle)?.color ?? "#000000"; + [...(issueLabels ?? []), ...(workspaceLabels ?? [])]?.find( + (label) => label.id === groupTitle + )?.color ?? "#000000"; icon = ( = ({ workspaceSlug={workspaceSlug?.toString() ?? ""} readOnly={disableUserActions} /> -
+
@@ -89,62 +93,66 @@ export const SpreadsheetView: React.FC = ({ userAuth={userAuth} /> ))} - -
- setIsInlineCreateIssueFormOpen(false)} - prePopulatedData={{ - ...(cycleId && { cycle: cycleId.toString() }), - ...(moduleId && { module: moduleId.toString() }), - }} - /> - - {type === "issue" - ? !disableUserActions && - !isInlineCreateIssueFormOpen && ( - - ) - : !disableUserActions && - !isInlineCreateIssueFormOpen && ( - - - Add Issue - - } - position="left" - verticalPosition="top" - optionsClassName="left-5 !w-36" - noBorder - > - setIsInlineCreateIssueFormOpen(true)}> - Create new - - {openIssuesListModal && ( - - Add an existing issue - - )} - - )} -
) : ( )}
+ +
+ setIsInlineCreateIssueFormOpen(false)} + prePopulatedData={{ + ...(cycleId && { cycle: cycleId.toString() }), + ...(moduleId && { module: moduleId.toString() }), + }} + /> + + {type === "issue" + ? !disableUserActions && + !isInlineCreateIssueFormOpen && ( + + ) + : !disableUserActions && + !isInlineCreateIssueFormOpen && ( + + + Add Issue + + } + position="left" + verticalPosition="top" + optionsClassName="left-5 !w-36" + noBorder + > + setIsInlineCreateIssueFormOpen(true)}> + Create new + + {openIssuesListModal && ( + + Add an existing issue + + )} + + )} +
); }; diff --git a/web/components/gantt-chart/sidebar.tsx b/web/components/gantt-chart/sidebar.tsx index 35b253ef9..fc2cea1e0 100644 --- a/web/components/gantt-chart/sidebar.tsx +++ b/web/components/gantt-chart/sidebar.tsx @@ -92,6 +92,7 @@ export const GanttSidebar: React.FC = (props) => { {(droppableProvided) => (
= (props) => { setIsCreateIssueFormOpen(false)} + onSuccess={() => { + const ganttSidebar = document.getElementById(`gantt-sidebar-${cycleId}`); + + const timeoutId = setTimeout(() => { + if (ganttSidebar) + ganttSidebar.scrollBy({ + top: ganttSidebar.scrollHeight, + left: 0, + behavior: "smooth", + }); + clearTimeout(timeoutId); + }, 10); + }} prePopulatedData={{ start_date: new Date(Date.now()).toISOString().split("T")[0], target_date: new Date(Date.now() + 86400000).toISOString().split("T")[0], diff --git a/web/components/issues/my-issues/my-issues-view-options.tsx b/web/components/issues/my-issues/my-issues-view-options.tsx index 549a00df2..8c8bd9dc7 100644 --- a/web/components/issues/my-issues/my-issues-view-options.tsx +++ b/web/components/issues/my-issues/my-issues-view-options.tsx @@ -234,7 +234,7 @@ export const MyIssuesViewOptions: React.FC = () => { displayFilters?.layout !== "spreadsheet" && ( <>
-

Show empty states

+

Show empty groups

{ displayFilters?.layout !== "spreadsheet" && ( <>
-

Show empty states

+

Show empty groups

{ > -
+
)}
-
-

- Good {greeting}, {user?.first_name} {user?.last_name} -

-
-
{greeting === "morning" ? "🌤️" : greeting === "afternoon" ? "🌥️" : "🌙️"}
-
- {DAYS[today.getDay()]}, {renderShortDate(today)} {render12HourFormatTime(today)} -
-
-
+ {projects ? ( projects.length > 0 ? ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx index f29629384..65007db3c 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx @@ -189,7 +189,7 @@ const SingleCycle: React.FC = () => { {cycleStatus === "completed" && ( setTransferIssuesModal(true)} /> )} -
+