Merge branch 'develop' of gurusainath:makeplane/plane into feat/mobx-global-views

This commit is contained in:
gurusainath 2024-02-03 21:23:56 +05:30
commit fe505e6b31
15 changed files with 207 additions and 32 deletions

View File

@ -243,6 +243,29 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
):
serializer = CycleSerializer(data=request.data)
if serializer.is_valid():
if (
request.data.get("external_id")
and request.data.get("external_source")
and Cycle.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source"),
external_id=request.data.get("external_id"),
).exists()
):
cycle = Cycle.objects.filter(
workspace__slug=slug,
project_id=project_id,
external_source=request.data.get("external_source"),
external_id=request.data.get("external_id"),
).first()
return Response(
{
"error": "Cycle with the same external id and external source already exists",
"cycle": str(cycle.id),
},
status=status.HTTP_409_CONFLICT,
)
serializer.save(
project_id=project_id,
owned_by=request.user,
@ -289,6 +312,23 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
serializer = CycleSerializer(cycle, data=request.data, partial=True)
if serializer.is_valid():
if (
request.data.get("external_id")
and (cycle.external_id != request.data.get("external_id"))
and Cycle.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source", cycle.external_source),
external_id=request.data.get("external_id"),
).exists()
):
return Response(
{
"error": "Cycle with the same external id and external source already exists",
"cycle_id": str(cycle.id),
},
status=status.HTTP_409_CONFLICT,
)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

View File

@ -220,6 +220,30 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
)
if serializer.is_valid():
if (
request.data.get("external_id")
and request.data.get("external_source")
and Issue.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source"),
external_id=request.data.get("external_id"),
).exists()
):
issue = Issue.objects.filter(
workspace__slug=slug,
project_id=project_id,
external_id=request.data.get("external_id"),
external_source=request.data.get("external_source"),
).first()
return Response(
{
"error": "Issue with the same external id and external source already exists",
"issue_id": str(issue.id),
},
status=status.HTTP_409_CONFLICT,
)
serializer.save()
# Track the issue
@ -256,6 +280,24 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
partial=True,
)
if serializer.is_valid():
if (
str(request.data.get("external_id"))
and (issue.external_id != str(request.data.get("external_id")))
and Issue.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source", issue.external_source),
external_id=request.data.get("external_id"),
).exists()
):
return Response(
{
"error": "Issue with the same external id and external source already exists",
"issue_id": str(issue.id),
},
status=status.HTTP_409_CONFLICT,
)
serializer.save()
issue_activity.delay(
type="issue.activity.updated",
@ -263,6 +305,8 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
actor_id=str(request.user.id),
issue_id=str(pk),
project_id=str(project_id),
external_id__isnull=False,
external_source__isnull=False,
current_instance=current_instance,
epoch=int(timezone.now().timestamp()),
)

View File

@ -132,6 +132,29 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
},
)
if serializer.is_valid():
if (
request.data.get("external_id")
and request.data.get("external_source")
and Module.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source"),
external_id=request.data.get("external_id"),
).exists()
):
module = Module.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source"),
external_id=request.data.get("external_id"),
).first()
return Response(
{
"error": "Module with the same external id and external source already exists",
"module_id": str(module.id),
},
status=status.HTTP_409_CONFLICT,
)
serializer.save()
module = Module.objects.get(pk=serializer.data["id"])
serializer = ModuleSerializer(module)
@ -149,8 +172,25 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
partial=True,
)
if serializer.is_valid():
if (
request.data.get("external_id")
and (module.external_id != request.data.get("external_id"))
and Module.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source", module.external_source),
external_id=request.data.get("external_id"),
).exists()
):
return Response(
{
"error": "Module with the same external id and external source already exists",
"module_id": str(module.id),
},
status=status.HTTP_409_CONFLICT,
)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def get(self, request, slug, project_id, pk=None):

View File

@ -38,6 +38,30 @@ class StateAPIEndpoint(BaseAPIView):
data=request.data, context={"project_id": project_id}
)
if serializer.is_valid():
if (
request.data.get("external_id")
and request.data.get("external_source")
and State.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source"),
external_id=request.data.get("external_id"),
).exists()
):
state = State.objects.filter(
workspace__slug=slug,
project_id=project_id,
external_id=request.data.get("external_id"),
external_source=request.data.get("external_source"),
).first()
return Response(
{
"error": "State with the same external id and external source already exists",
"state_id": str(state.id),
},
status=status.HTTP_409_CONFLICT,
)
serializer.save(project_id=project_id)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@ -91,6 +115,23 @@ class StateAPIEndpoint(BaseAPIView):
)
serializer = StateSerializer(state, data=request.data, partial=True)
if serializer.is_valid():
if (
str(request.data.get("external_id"))
and (state.external_id != str(request.data.get("external_id")))
and State.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source", state.external_source),
external_id=request.data.get("external_id"),
).exists()
):
return Response(
{
"error": "State with the same external id and external source already exists",
"state_id": str(state.id),
},
status=status.HTTP_409_CONFLICT,
)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

View File

@ -47,6 +47,7 @@ export const PriorityIcon: React.FC<IPriorityIcon> = (props) => {
>
<Icon
size={size}
viewBox="0 0 23.5 24"
className={cn(
{
"text-white": priority === "urgent",

View File

@ -89,8 +89,8 @@ export const DeactivateAccountModal: React.FC<Props> = (props) => {
<div className="px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div className="">
<div className="flex items-start gap-x-4">
<div className="grid place-items-center rounded-full bg-red-500/20 p-4">
<Trash2 className="h-6 w-6 text-red-600" aria-hidden="true" />
<div className="grid place-items-center rounded-full bg-red-500/20 p-2 sm:p-2 md:p-4 lg:p-4 mt-3 sm:mt-3 md:mt-0 lg:mt-0 ">
<Trash2 className="h-4 w-4 sm:h-4 sm:w-4 md:h-6 md:w-6 lg:h-6 lg:w-6 text-red-600" aria-hidden="true" />
</div>
<div>
<Dialog.Title as="h3" className="my-4 text-2xl font-medium leading-6 text-custom-text-100">

View File

@ -26,11 +26,11 @@ export const DurationFilterDropdown: React.FC<Props> = (props) => {
placement="bottom-end"
closeOnSelect
>
{DURATION_FILTER_OPTIONS.map((option) => (
<CustomMenu.MenuItem key={option.key} onClick={() => onChange(option.key)}>
{option.label}
</CustomMenu.MenuItem>
))}
{DURATION_FILTER_OPTIONS.map((option) => (
<CustomMenu.MenuItem key={option.key} onClick={() => onChange(option.key)}>
{option.label}
</CustomMenu.MenuItem>
))}
</CustomMenu>
);
};

View File

@ -77,7 +77,7 @@ export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
})}
>
Issues
<span className="flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-medium rounded-xl h-4 min-w-6 flex items-center text-center justify-center">
<span className="flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-medium rounded-xl px-3 flex items-center text-center justify-center">
{totalIssues}
</span>
</h6>

View File

@ -9,6 +9,7 @@ import { WidgetLoader } from "components/dashboard/widgets";
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
// types
import { TOverviewStatsWidgetResponse } from "@plane/types";
import { cn } from "helpers/common.helper";
export type WidgetProps = {
dashboardId: string;
@ -71,10 +72,18 @@ export const OverviewStatsWidget: React.FC<WidgetProps> = observer((props) => {
[&>div:nth-child(2)>a>div]:lg:border-r
"
>
{STATS_LIST.map((stat) => (
<div className="w-full flex flex-col gap-2 hover:bg-custom-background-80 rounded-[10px]">
{STATS_LIST.map((stat, index) => (
<div
className={cn(
`w-full flex flex-col gap-2 hover:bg-custom-background-80`,
index === 0 ? "rounded-tl-xl lg:rounded-l-xl" : "",
index === STATS_LIST.length - 1 ? "rounded-br-xl lg:rounded-r-xl" : "",
index === 1 ? "rounded-tr-xl lg:rounded-[0px]" : "",
index == 2 ? "rounded-bl-xl lg:rounded-[0px]" : ""
)}
>
<Link href={stat.link} className="py-4 duration-300 rounded-[10px] w-full ">
<div className={`relative flex justify-center items-center`}>
<div className={`relative flex pl-10 sm:pl-20 md:pl-20 lg:pl-20 items-center`}>
<div>
<h5 className="font-semibold text-xl">{stat.count}</h5>
<p className="text-custom-text-300 text-sm xl:text-base">{stat.title}</p>

View File

@ -18,7 +18,7 @@ export const WorkspaceDashboardHeader = () => {
return (
<>
<ProductUpdatesModal isOpen={isProductUpdatesModalOpen} setIsOpen={setIsProductUpdatesModalOpen} />
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="relative z-20 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 overflow-ellipsis whitespace-nowrap">
<SidebarHamburgerToggle />
<div>

View File

@ -237,7 +237,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
)}
<div className="horizontal-scroll-enable relative h-full w-full overflow-auto bg-custom-background-90">
<div className="relative h-full w-max min-w-full bg-custom-background-90 px-2">
<div className="relative h-max w-max min-w-full bg-custom-background-90 px-2">
<DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
{/* drag and delete component */}
<div

View File

@ -148,9 +148,15 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
const handleDisplayFiltersUpdate = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug) return;
if (!workspaceSlug || !globalViewId) return;
updateFilters(workspaceSlug.toString(), undefined, EIssueFilterType.DISPLAY_FILTERS, { ...updatedDisplayFilter });
updateFilters(
workspaceSlug.toString(),
undefined,
EIssueFilterType.DISPLAY_FILTERS,
{ ...updatedDisplayFilter },
globalViewId.toString()
);
},
[updateFilters, workspaceSlug]
);

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import { observer } from "mobx-react-lite";
import { Dialog, Transition } from "@headlessui/react";
// hooks
import { useApplication, useCycle, useIssues, useModule, useProject, useUser, useWorkspace } from "hooks/store";
import { useApplication, useCycle, useIssues, useModule, useProject, useWorkspace } from "hooks/store";
import useToast from "hooks/use-toast";
import useLocalStorage from "hooks/use-local-storage";
// components
@ -32,7 +32,6 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
const {
eventTracker: { postHogEventTracker },
} = useApplication();
const { currentUser } = useUser();
const {
router: { workspaceSlug, projectId, cycleId, moduleId, viewId: projectViewId },
} = useApplication();
@ -49,27 +48,22 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
const issueStores = {
[EIssuesStoreType.PROJECT]: {
store: projectIssues,
dataIdToUpdate: activeProjectId,
viewId: undefined,
},
[EIssuesStoreType.PROJECT_VIEW]: {
store: viewIssues,
dataIdToUpdate: activeProjectId,
viewId: projectViewId,
},
[EIssuesStoreType.PROFILE]: {
store: profileIssues,
dataIdToUpdate: currentUser?.id || undefined,
viewId: undefined,
},
[EIssuesStoreType.CYCLE]: {
store: cycleIssues,
dataIdToUpdate: activeProjectId,
viewId: cycleId,
},
[EIssuesStoreType.MODULE]: {
store: moduleIssues,
dataIdToUpdate: activeProjectId,
viewId: moduleId,
},
};
@ -78,7 +72,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
// local storage
const { setValue: setLocalStorageDraftIssue } = useLocalStorage<any>("draftedIssue", {});
// current store details
const { store: currentIssueStore, viewId, dataIdToUpdate } = issueStores[storeType];
const { store: currentIssueStore, viewId } = issueStores[storeType];
useEffect(() => {
// if modal is closed, reset active project to null
@ -129,13 +123,13 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
};
const handleCreateIssue = async (payload: Partial<TIssue>): Promise<TIssue | undefined> => {
if (!workspaceSlug || !dataIdToUpdate) return;
if (!workspaceSlug || !payload.project_id) return;
try {
const response = await currentIssueStore.createIssue(workspaceSlug, dataIdToUpdate, payload, viewId);
const response = await currentIssueStore.createIssue(workspaceSlug, payload.project_id, payload, viewId);
if (!response) throw new Error();
currentIssueStore.fetchIssues(workspaceSlug, dataIdToUpdate, "mutation", viewId);
currentIssueStore.fetchIssues(workspaceSlug, payload.project_id, "mutation", viewId);
if (payload.cycle_id && payload.cycle_id !== "" && storeType !== EIssuesStoreType.CYCLE)
await addIssueToCycle(response, payload.cycle_id);
@ -182,10 +176,10 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
};
const handleUpdateIssue = async (payload: Partial<TIssue>): Promise<TIssue | undefined> => {
if (!workspaceSlug || !dataIdToUpdate || !data?.id) return;
if (!workspaceSlug || !payload.project_id || !data?.id) return;
try {
const response = await currentIssueStore.updateIssue(workspaceSlug, dataIdToUpdate, data.id, payload, viewId);
const response = await currentIssueStore.updateIssue(workspaceSlug, payload.project_id, data.id, payload, viewId);
setToastAlert({
type: "success",
title: "Success!",
@ -226,7 +220,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
};
const handleFormSubmit = async (formData: Partial<TIssue>) => {
if (!workspaceSlug || !dataIdToUpdate || !storeType) return;
if (!workspaceSlug || !formData.project_id || !storeType) return;
const payload: Partial<TIssue> = {
...formData,

View File

@ -229,7 +229,7 @@ export const PagesListItem: FC<IPagesListItem> = observer(({ pageId, projectId }
)}
<Tooltip
position="top-right"
tooltipContent={`Created by ${ownerDetails?.member.display_name} on ${renderFormattedDate(
tooltipContent={`Created by ${ownerDetails?.member?.display_name} on ${renderFormattedDate(
created_at
)}`}
>

View File

@ -42,7 +42,7 @@ export const AppSidebar: FC<IAppSidebar> = observer(() => {
return (
<div
className={`inset-y-0 z-20 flex h-full flex-shrink-0 flex-grow-0 flex-col border-r border-custom-sidebar-border-200 bg-custom-sidebar-background-100 duration-300
className={`inset-y-0 z-30 flex h-full flex-shrink-0 flex-grow-0 flex-col border-r border-custom-sidebar-border-200 bg-custom-sidebar-background-100 duration-300
fixed md:relative
${themStore.sidebarCollapsed ? "-ml-[280px]" : ""}
sm:${themStore.sidebarCollapsed ? "-ml-[280px]" : ""}