Skip to content

Commit

Permalink
Enhancement of Linked Issues Feature in IssueModal Component (#11)
Browse files Browse the repository at this point in the history
* Add linked issues feature to IssueModal

Implemented a new feature to display linked issues in the IssueModal component. This is achieved by fetching additional data from the GraphQL server side, and restructuring the IssueLinkedIssues component to group issues by link type. The backend has also been adjusted to include linked issues data in the `issue` GraphQL query.

* Update frontend/components/IssueModal/IssueLinkedIssues.tsx

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update frontend/components/IssueModal/IssueLinkedIssues.tsx

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
claygorman and github-actions[bot] authored Dec 20, 2023
1 parent 7b0882f commit ec94586
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 50 deletions.
27 changes: 26 additions & 1 deletion backend/src/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,32 @@ const resolvers = {
});
},
issue: (parent, { input: { id } }, { db }) => {
return db.sequelize.models.Issue.findByPk(id);
return db.sequelize.models.Issue.findByPk(id, {
include: [
{
model: db.sequelize.models.Issue,
as: 'linkedToIssues',
through: {
attributes: [
['issue_id', 'issueId'],
['linked_issue_id', 'linkedIssueId'],
['link_type', 'linkType'],
],
},
},
{
model: db.sequelize.models.Issue,
as: 'linkedByIssues',
through: {
attributes: [
['issue_id', 'issueId'],
['linked_issue_id', 'linkedIssueId'],
['link_type', 'linkType'],
],
},
},
],
});
},
},
};
Expand Down
4 changes: 2 additions & 2 deletions backend/src/resolvers/issue.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
export const issueResolvers = {
links: async (parent, args, { db }) => {
return [
...parent.linkedToIssues.map((issue) => ({
...parent.linkedToIssues?.map((issue) => ({
...issue.toJSON(),
linkType: issue.IssueLinks.linkType,
linkedIssueId: parent.id,
})),
...parent.linkedByIssues.map((issue) => ({
...parent.linkedByIssues?.map((issue) => ({
...issue.toJSON(),
linkType: issue.IssueLinks.linkTypeInverted,
linkedIssueId: parent.id,
Expand Down
113 changes: 67 additions & 46 deletions frontend/components/IssueModal/IssueLinkedIssues.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,80 @@
import React from 'react';
import { CheckIcon, XMarkIcon } from '@heroicons/react/20/solid';
import { priorityToIcon } from '@/components/Icons';
import { GET_ISSUE_QUERY } from '@/gql/gql-queries-mutations';
import { useQuery } from '@apollo/client';
import { groupBy, lowerCase, startCase } from 'lodash';
import { BsPlus } from 'react-icons/bs';
import { Issue } from '@/gql/__generated__/graphql';

const IssueLinkedIssues = ({ issueId }: { issueId?: string }) => {
// TODO: This is sample data we need to pull from backend
// TODO: Restructure data to group by link type and then map over each group
const blockedIssues = [
{
id: 1,
key: 'ticket-1',
subject: 'PLACEHOLDER: blocked ticket subject',
priority: 3,
assignee: 'test',
status: 'in progress',
},
{
id: 2,
key: 'ticket-2',
subject: 'PLACEHOLDER: next ticket name',
priority: 5,
assignee: 'test',
status: 'done',
},
];
type Links = {
[key: string]: Issue[];
};

const IssueLinkedIssues = ({
issueId,
projectKey,
}: {
issueId?: string;
projectKey?: string;
}) => {
const { data, error, loading } = useQuery(GET_ISSUE_QUERY, {
skip: !issueId,
variables: { input: { id: issueId } },
});

if (loading) {
return <div>Loading...</div>;
}

if (error) {
return <div>Error loading linked issues</div>;
}

const links: Links = data?.issue?.links
? groupBy(data.issue.links, 'linkType')
: {};

return (
<>
<div className='pb-5 text-2xl'>Linked Issues</div>
{blockedIssues.map(({ id, key, subject, priority, status }) => (
<div
key={id}
className='group flex overflow-hidden rounded border border-primary/10 bg-surface-overlay px-2 shadow-sm hover:cursor-pointer hover:bg-surface-overlay-hovered'
>
<div className='relative m-1 flex w-full items-center gap-x-1 p-1'>
<div>
<CheckIcon className='h-6 w-6 text-blue-500' />
</div>
<div className='text-link-active hover:underline'>
{key}
</div>
<div className='ml-4 shrink grow basis-0'>
<div className='flex'>
<div className='grow hover:underline'>
{subject}
<div className='flex items-center justify-between align-middle'>
<div className='text-2xl'>Linked Issues</div>
<div className='rounded-md hover:cursor-pointer hover:bg-surface-overlay-hovered'>
<BsPlus className='h-6 w-6' />
</div>
</div>
{Object.keys(links).map((linkType) => (
<div key={linkType}>
<div className='pb-1 pt-2 text-lg opacity-80'>
{startCase(lowerCase(linkType))}
</div>
{links[linkType].map(({ id, title, priority, status }) => (
<div
key={id}
className='group flex overflow-hidden rounded border border-primary/10 bg-surface-overlay px-2 shadow-sm hover:cursor-pointer hover:bg-surface-overlay-hovered'
>
<div className='relative m-1 flex w-full items-center gap-x-1 p-1'>
<div>
<CheckIcon className='h-6 w-6 text-blue-500' />
</div>
<div className='text-link-active hover:underline'>
{projectKey}-{id}
</div>
<div className='ml-4 shrink grow basis-0'>
<div className='flex'>
<div className='grow hover:underline'>{title}</div>
</div>
</div>
<div className='flex'>{priorityToIcon(Number(priority))}</div>
<div className='flex items-center justify-center rounded-md bg-blue-500 px-2 text-primary-inverted'>
{status?.name}
</div>
<div className='right-0 opacity-0 hover:cursor-pointer hover:rounded-md hover:bg-surface-overlay group-hover:opacity-100'>
<XMarkIcon className='h-6 w-6' />
</div>
</div>
</div>
<div className='flex'>{priorityToIcon(priority)}</div>
<div className='flex items-center justify-center rounded-md bg-blue-500 px-2 text-primary-inverted'>
{status}
</div>
<div className='right-0 opacity-0 hover:cursor-pointer hover:rounded-md hover:bg-surface-overlay group-hover:opacity-100'>
<XMarkIcon className='h-6 w-6' />
</div>
</div>
))}
</div>
))}
</>
Expand Down
5 changes: 4 additions & 1 deletion frontend/components/IssueModal/IssueOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ const IssueOverview = ({
<IssueDescription issueId={issueId} />
</div>
<div>
<IssueLinkedIssues issueId={issueId} />
<IssueLinkedIssues
projectKey={projectKey}
issueId={issueId}
/>
</div>
<div className='pt-5'>
<IssueComments issueId={issueId} />
Expand Down
5 changes: 5 additions & 0 deletions frontend/gql/gql-queries-mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ export const GET_ISSUE_QUERY = gql(/* GraphQL */ `
query GetIssue($input: QueryIssueInput) {
issue(input: $input) {
...IssueFields
links {
...IssueFields
linkType
linkedIssueId
}
}
}
`);
Expand Down

0 comments on commit ec94586

Please sign in to comment.