Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: cross-project issue linking #1612

Merged
merged 3 commits into from
Jul 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 53 additions & 15 deletions apps/app/components/core/modals/existing-issues-list-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import useToast from "hooks/use-toast";
import useIssuesView from "hooks/use-issues-view";
import useDebounce from "hooks/use-debounce";
// ui
import { Loader, PrimaryButton, SecondaryButton } from "components/ui";
import { Loader, PrimaryButton, SecondaryButton, ToggleSwitch, Tooltip } from "components/ui";
// icons
import { LaunchOutlined } from "@mui/icons-material";
import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { LayerDiagonalIcon } from "components/icons";
// types
Expand Down Expand Up @@ -45,6 +46,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
const [isSearching, setIsSearching] = useState(false);
const [selectedIssues, setSelectedIssues] = useState<ISearchIssueResponse[]>([]);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(false);

const debouncedSearchTerm: string = useDebounce(searchTerm, 500);

Expand All @@ -59,6 +61,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
onClose();
setSearchTerm("");
setSelectedIssues([]);
setIsWorkspaceLevel(false);
};

const onSubmit = async () => {
Expand Down Expand Up @@ -104,10 +107,11 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
.projectIssuesSearch(workspaceSlug as string, projectId as string, {
search: debouncedSearchTerm,
...searchParams,
workspace_search: isWorkspaceLevel,
})
.then((res) => setIssues(res))
.finally(() => setIsSearching(false));
}, [debouncedSearchTerm, isOpen, projectId, searchParams, workspaceSlug]);
}, [debouncedSearchTerm, isOpen, isWorkspaceLevel, projectId, searchParams, workspaceSlug]);

return (
<>
Expand Down Expand Up @@ -162,7 +166,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
/>
</div>

<div className="text-custom-text-200 text-[0.825rem] p-2">
<div className="flex flex-col-reverse sm:flex-row sm:items-center sm:justify-between gap-4 text-custom-text-200 text-[0.825rem] p-2">
{selectedIssues.length > 0 ? (
<div className="flex items-center gap-2 flex-wrap mt-1">
{selectedIssues.map((issue) => (
Expand Down Expand Up @@ -190,6 +194,25 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
No issues selected
</div>
)}
<Tooltip tooltipContent="Toggle workspace level search">
<div
className={`flex-shrink-0 flex items-center gap-1 text-xs cursor-pointer ${
isWorkspaceLevel ? "text-custom-text-100" : "text-custom-text-200"
}`}
>
<ToggleSwitch
value={isWorkspaceLevel}
onChange={() => setIsWorkspaceLevel((prevData) => !prevData)}
/>
<button
type="button"
onClick={() => setIsWorkspaceLevel((prevData) => !prevData)}
className="flex-shrink-0"
>
workspace level
</button>
</div>
</Tooltip>
</div>

<Combobox.Options static className="max-h-80 scroll-py-2 overflow-y-auto">
Expand Down Expand Up @@ -242,22 +265,37 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
htmlFor={`issue-${issue.id}`}
value={issue}
className={({ active }) =>
`flex w-full cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-custom-text-200 ${
`group flex items-center justify-between gap-2 w-full cursor-pointer select-none rounded-md px-3 py-2 text-custom-text-200 ${
active ? "bg-custom-background-80 text-custom-text-100" : ""
} ${selected ? "text-custom-text-100" : ""}`
}
>
<input type="checkbox" checked={selected} readOnly />
<span
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
style={{
backgroundColor: issue.state__color,
}}
/>
<span className="flex-shrink-0 text-xs">
{issue.project__identifier}-{issue.sequence_id}
</span>
{issue.name}
<div className="flex items-center gap-2">
<input type="checkbox" checked={selected} readOnly />
<span
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
style={{
backgroundColor: issue.state__color,
}}
/>
<span className="flex-shrink-0 text-xs">
{issue.project__identifier}-{issue.sequence_id}
</span>
{issue.name}
</div>
<a
href={`/${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`}
target="_blank"
className="group-hover:block hidden relative z-1 text-custom-text-200 hover:text-custom-text-100"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
>
<LaunchOutlined
sx={{
fontSize: 16,
}}
/>
</a>
</Combobox.Option>
);
})}
Expand Down
4 changes: 2 additions & 2 deletions apps/app/components/issues/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const defaultValues: Partial<IIssue> = {
assignees_list: [],
labels: [],
labels_list: [],
target_date: null,
};

export interface IssueFormProps {
Expand Down Expand Up @@ -271,7 +272,6 @@ export const IssueForm: FC<IssueFormProps> = ({
</h3>
</div>
{watch("parent") &&
watch("parent") !== "" &&
(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) &&
selectedParentIssue && (
<div className="flex w-min items-center gap-2 whitespace-nowrap rounded bg-custom-background-80 p-2 text-xs">
Expand Down Expand Up @@ -476,7 +476,7 @@ export const IssueForm: FC<IssueFormProps> = ({
)}
{(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) && (
<CustomMenu ellipsis>
{watch("parent") && watch("parent") !== "" ? (
{watch("parent") ? (
<>
<CustomMenu.MenuItem
renderAs="button"
Expand Down
57 changes: 30 additions & 27 deletions apps/app/components/issues/main-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,26 @@ export const IssueMainContent: React.FC<Props> = ({
)
: null
);
const siblingIssuesList = siblingIssues?.sub_issues.filter((i) => i.id !== issueDetails.id);

return (
<>
<div className="rounded-lg">
{issueDetails?.parent && issueDetails.parent !== "" ? (
{issueDetails?.parent ? (
<div className="mb-5 flex w-min items-center gap-2 whitespace-nowrap rounded bg-custom-background-90 p-2 text-xs">
<Link href={`/${workspaceSlug}/projects/${projectId}/issues/${issueDetails.parent}`}>
<Link
href={`/${workspaceSlug}/projects/${issueDetails.parent_detail?.project_detail.id}/issues/${issueDetails.parent}`}
>
<a className="flex items-center gap-2 text-custom-text-200">
<span
className="block h-1.5 w-1.5 rounded-full"
style={{
backgroundColor: issueDetails?.state_detail?.color,
backgroundColor: issueDetails.parent_detail?.state_detail.color,
}}
/>
<span className="flex-shrink-0">
{issueDetails.project_detail.identifier}-{issueDetails.parent_detail?.sequence_id}
{issueDetails.parent_detail?.project_detail.identifier}-
{issueDetails.parent_detail?.sequence_id}
</span>
<span className="truncate">
{issueDetails.parent_detail?.name.substring(0, 50)}
Expand All @@ -77,29 +81,28 @@ export const IssueMainContent: React.FC<Props> = ({
</Link>

<CustomMenu position="left" ellipsis>
{siblingIssues && siblingIssues.sub_issues.length > 0 ? (
<>
<h2 className="text-custom-text-200 px-1 mb-2">Sibling issues</h2>
{siblingIssues.sub_issues.map((issue) => {
if (issue.id !== issueDetails.id)
return (
<CustomMenu.MenuItem
key={issue.id}
renderAs="a"
href={`/${workspaceSlug}/projects/${projectId as string}/issues/${
issue.id
}`}
>
{issueDetails.project_detail.identifier}-{issue.sequence_id}
</CustomMenu.MenuItem>
);
})}
</>
) : (
<p className="flex items-center gap-2 whitespace-nowrap px-1 text-left text-xs text-custom-text-200 py-1">
No sibling issues
</p>
)}
{siblingIssuesList ? (
siblingIssuesList.length > 0 ? (
<>
<h2 className="text-custom-text-200 px-1 mb-2">Sibling issues</h2>
{siblingIssuesList.map((issue) => (
<CustomMenu.MenuItem
key={issue.id}
renderAs="a"
href={`/${workspaceSlug}/projects/${projectId as string}/issues/${
issue.id
}`}
>
{issueDetails.project_detail.identifier}-{issue.sequence_id}
</CustomMenu.MenuItem>
))}
</>
) : (
<p className="flex items-center gap-2 whitespace-nowrap px-1 text-left text-xs text-custom-text-200 py-1">
No sibling issues
</p>
)
) : null}
<CustomMenu.MenuItem
renderAs="button"
onClick={() => submitChanges({ parent: null })}
Expand Down
51 changes: 45 additions & 6 deletions apps/app/components/issues/parent-issues-list-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import useDebounce from "hooks/use-debounce";
// components
import { LayerDiagonalIcon } from "components/icons";
// ui
import { Loader } from "components/ui";
import { Loader, ToggleSwitch, Tooltip } from "components/ui";
// icons
import { LaunchOutlined } from "@mui/icons-material";
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
// types
import { ISearchIssueResponse } from "types";
Expand All @@ -37,6 +38,7 @@ export const ParentIssuesListModal: React.FC<Props> = ({
const [searchTerm, setSearchTerm] = useState("");
const [issues, setIssues] = useState<ISearchIssueResponse[]>([]);
const [isSearching, setIsSearching] = useState(false);
const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(false);

const debouncedSearchTerm: string = useDebounce(searchTerm, 500);

Expand All @@ -46,6 +48,7 @@ export const ParentIssuesListModal: React.FC<Props> = ({
const handleClose = () => {
onClose();
setSearchTerm("");
setIsWorkspaceLevel(false);
};

useEffect(() => {
Expand All @@ -58,10 +61,11 @@ export const ParentIssuesListModal: React.FC<Props> = ({
search: debouncedSearchTerm,
parent: true,
issue_id: issueId,
workspace_search: isWorkspaceLevel,
})
.then((res) => setIssues(res))
.finally(() => setIsSearching(false));
}, [debouncedSearchTerm, isOpen, issueId, projectId, workspaceSlug]);
}, [debouncedSearchTerm, isOpen, issueId, isWorkspaceLevel, projectId, workspaceSlug]);

return (
<>
Expand Down Expand Up @@ -115,7 +119,29 @@ export const ParentIssuesListModal: React.FC<Props> = ({
displayValue={() => ""}
/>
</div>
<Combobox.Options static className="max-h-80 scroll-py-2 overflow-y-auto mt-2">
<div className="flex sm:justify-end p-2">
<Tooltip tooltipContent="Toggle workspace level search">
<div
className={`flex-shrink-0 flex items-center gap-1 text-xs cursor-pointer ${
isWorkspaceLevel ? "text-custom-text-100" : "text-custom-text-200"
}`}
>
<ToggleSwitch
value={isWorkspaceLevel}
onChange={() => setIsWorkspaceLevel((prevData) => !prevData)}
label="Workspace level"
/>
<button
type="button"
onClick={() => setIsWorkspaceLevel((prevData) => !prevData)}
className="flex-shrink-0"
>
workspace level
</button>
</div>
</Tooltip>
</div>
<Combobox.Options static className="max-h-80 scroll-py-2 overflow-y-auto">
{searchTerm !== "" && (
<h5 className="text-[0.825rem] text-custom-text-200 mx-2">
Search results for{" "}
Expand Down Expand Up @@ -158,12 +184,12 @@ export const ParentIssuesListModal: React.FC<Props> = ({
key={issue.id}
value={issue}
className={({ active, selected }) =>
`flex cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-custom-text-200 ${
`group flex items-center justify-between gap-2 cursor-pointer select-none rounded-md px-3 py-2 text-custom-text-200 ${
active ? "bg-custom-background-80 text-custom-text-100" : ""
} ${selected ? "text-custom-text-100" : ""}`
}
>
<>
<div className="flex items-center gap-2">
<span
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
style={{
Expand All @@ -174,7 +200,20 @@ export const ParentIssuesListModal: React.FC<Props> = ({
{issue.project__identifier}-{issue.sequence_id}
</span>{" "}
{issue.name}
</>
</div>
<a
href={`/${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`}
target="_blank"
className="group-hover:block hidden relative z-1 text-custom-text-200 hover:text-custom-text-100"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
>
<LaunchOutlined
sx={{
fontSize: 16,
}}
/>
</a>
</Combobox.Option>
))}
</ul>
Expand Down
Loading