Skip to content

Comments

feat: create issue links#35

Merged
angelo-hub merged 6 commits intomainfrom
feat/create_issue_links
Dec 21, 2025
Merged

feat: create issue links#35
angelo-hub merged 6 commits intomainfrom
feat/create_issue_links

Conversation

@angelo-hub
Copy link
Owner

No description provided.

@angelo-hub angelo-hub requested a review from Copilot December 21, 2025 18:46
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements the ability to create, view, and delete issue links between tickets in both Linear and Jira platforms. Users can now search for issues and establish relationships like "blocks," "relates to," or "duplicates" directly from the ticket panel.

Key changes:

  • Added UI components for managing issue relationships with search, create, and delete functionality
  • Implemented backend API methods for creating and deleting issue links in both Linear and Jira clients
  • Extended type definitions to support issue relations and link types across platforms

Reviewed changes

Copilot reviewed 16 out of 18 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
webview-ui/src/shared/types/messages.ts Added type definitions for issue relations, search results, and new message commands
webview-ui/src/linear/ticket-panel/components/IssueRelationsSection.tsx New component for Linear issue relations with search, create, and delete UI
webview-ui/src/linear/ticket-panel/components/IssueRelationsSection.module.css Styles for Linear issue relations component
webview-ui/src/linear/ticket-panel/App.tsx Integrated IssueRelationsSection into Linear ticket panel
webview-ui/src/jira/ticket-panel/components/IssueLinksSection.tsx Enhanced existing Jira links section with create/delete functionality
webview-ui/src/jira/ticket-panel/components/IssueLinksSection.module.css Added styles for Jira link creation and deletion UI
webview-ui/src/jira/ticket-panel/App.tsx Added link types, search results state, and new message handlers
src/shared/base/BaseTicketProvider.ts Added base interfaces for issue links and link types
src/providers/linear/types.ts Added Linear-specific relation types and interfaces
src/providers/linear/LinearTicketPanel.ts Added handlers for searching, creating, and deleting issue relations
src/providers/linear/LinearClient.ts Implemented GraphQL mutations and queries for issue relations
src/providers/jira/server/JiraServerClient.ts Added REST API methods for managing Jira issue links
src/providers/jira/common/BaseJiraClient.ts Added abstract methods for issue link operations
src/providers/jira/cloud/JiraTicketPanel.ts Added message handlers for link operations
src/providers/jira/cloud/JiraCloudClient.ts Implemented Jira Cloud API methods for issue links
docs/planning/ROADMAP_1.0.0.md Updated roadmap to mark issue links feature as complete

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


<div className={styles.linksList}>
{links.map((link) => (
<div key={link.id} className={styles.linkItemWrapper}>
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key attribute was moved from the <a> element to the parent <div>. While this works, it would be more maintainable to keep the key on the direct child of the map function. Consider keeping the key on the <a> element and using a different approach for the wrapper, or ensure this change is intentional and documented.

Copilot uses AI. Check for mistakes.
input: {
issueId: "${issueId}",
relatedIssueId: "${relatedIssueId}",
type: ${type}
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GraphQL mutation is vulnerable to injection attacks because the type parameter is directly interpolated into the query string without proper escaping. Although the type is constrained to specific values in the function signature, this should use GraphQL variables for safer query construction.

Copilot uses AI. Check for mistakes.
Comment on lines 936 to 937
{ identifier: { containsIgnoreCase: "${searchTerm}" } },
{ title: { containsIgnoreCase: "${searchTerm}" } }
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The search term is directly interpolated into the GraphQL query without sanitization, which could allow GraphQL injection attacks. Use parameterized queries or properly escape the search term to prevent potential exploits.

Copilot uses AI. Check for mistakes.

try {
// Search using JQL: key or summary contains the search term
const jql = `(key ~ "${searchTerm}" OR summary ~ "${searchTerm}") ORDER BY updated DESC`;
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The search term is directly interpolated into the JQL query without escaping, creating a JQL injection vulnerability. Escape special characters in the search term or use Jira's query builder methods to safely construct the JQL.

Copilot uses AI. Check for mistakes.
@angelo-hub angelo-hub requested a review from Copilot December 21, 2025 20:37
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 21 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (1)

webview-ui/src/jira/ticket-panel/components/IssueLinksSection.tsx:1

  • Using an anchor tag with href='#' is not ideal for interactive elements that don't navigate. Consider using a button element instead for better semantics and accessibility.
import React, { useState, useEffect, useCallback } from "react";

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +773 to +774
issueId: "${issueId}",
relatedIssueId: "${relatedIssueId}",
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String interpolation in GraphQL mutations creates SQL-injection-like vulnerabilities. The IDs should be passed as GraphQL variables instead of being directly interpolated into the query string, even though validation exists.

Copilot uses AI. Check for mistakes.
Comment on lines +822 to +830
mutation {
issueRelationDelete(id: "${relationId}") {
success
}
}
`;

try {
const response = await this.executeQuery(mutation, { skipCache: true });
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Direct string interpolation of relationId in GraphQL mutation is a security risk. Use GraphQL variables to pass parameters safely.

Suggested change
mutation {
issueRelationDelete(id: "${relationId}") {
success
}
}
`;
try {
const response = await this.executeQuery(mutation, { skipCache: true });
mutation DeleteIssueRelation($relationId: String!) {
issueRelationDelete(id: $relationId) {
success
}
}
`;
try {
const response = await this.executeQuery(mutation, {
variables: { relationId },
skipCache: true,
});

Copilot uses AI. Check for mistakes.
Comment on lines 581 to 587
// JQL requires escaping of: \ " ' space
const escapeJQL = (term: string): string => {
return term
.replace(/\\/g, '\\\\') // Escape backslashes first
.replace(/"/g, '\\"') // Escape double quotes
.replace(/'/g, "\\'") // Escape single quotes
.replace(/\s/g, '\\ '); // Escape spaces
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JQL escaping function does not escape all special JQL characters. Characters like [, ], (, ), and : can still be used to inject JQL syntax. Consider using a more comprehensive escaping approach or parameterized queries if available.

Suggested change
// JQL requires escaping of: \ " ' space
const escapeJQL = (term: string): string => {
return term
.replace(/\\/g, '\\\\') // Escape backslashes first
.replace(/"/g, '\\"') // Escape double quotes
.replace(/'/g, "\\'") // Escape single quotes
.replace(/\s/g, '\\ '); // Escape spaces
// JQL requires escaping of: + - & | ! ( ) { } [ ] ^ ~ * ? : \ / " ' and space
const escapeJQL = (term: string): string => {
return term
// Escape all JQL special characters by prefixing them with a backslash
.replace(/([+\-&|!(){}\[\]^~*?:\\\/"])/g, "\\$1")
.replace(/'/g, "\\'") // Escape single quotes
.replace(/\s/g, "\\ "); // Escape spaces

Copilot uses AI. Check for mistakes.
Comment on lines +138 to +140
const selectedLinkType = userSelectedLinkTypeId
? linkTypes?.find(t => t.id === userSelectedLinkTypeId) ?? linkTypes?.[0] ?? null
: linkTypes?.[0] ?? null;
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback logic for selectedLinkType is complex and could lead to unexpected behavior if userSelectedLinkTypeId points to a non-existent type. Consider simplifying by validating that userSelectedLinkTypeId exists in linkTypes before using it.

Copilot uses AI. Check for mistakes.
Comment on lines 943 to 945
const escapedSearchTerm = searchTerm
.replace(/\\/g, '\\\\') // Escape backslashes
.replace(/"/g, '\\"'); // Escape quotes
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GraphQL string escaping is duplicated from the searchUsers function and only handles backslashes and quotes. Additional control characters should be escaped. Consider extracting this into a reusable utility function with comprehensive escaping.

Copilot uses AI. Check for mistakes.
@angelo-hub angelo-hub requested a review from Copilot December 21, 2025 21:02
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 22 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

issueRelationCreate(
input: {
issueId: "${issueId}",
relatedIssueId: "${relatedIssueId}",
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type parameter is directly interpolated into the GraphQL query without proper escaping. While there is validation that checks against allowed values, this should use GraphQL variables instead to prevent any potential injection. Replace the string interpolation with proper GraphQL variable syntax.

Copilot uses AI. Check for mistakes.
Comment on lines +772 to +775
issueRelationCreate(
input: {
issueId: "${issueId}",
relatedIssueId: "${relatedIssueId}",
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issueId and relatedIssueId parameters are interpolated directly into the GraphQL query string without using GraphQL variables. This creates a potential GraphQL injection vulnerability. Use GraphQL variables with proper type definitions instead of string interpolation.

Copilot uses AI. Check for mistakes.
Comment on lines 232 to 234
<a
href="#"
onClick={(e) => handleClick(e, link.linkedIssue.key)}
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The link wrapper has been changed to not be the key element. The anchor tag should have a more semantic approach or be replaced with a button element since it's handling click events rather than actual navigation. Buttons are more appropriate for actions and provide better accessibility semantics.

Copilot uses AI. Check for mistakes.
@angelo-hub angelo-hub requested a review from Copilot December 21, 2025 21:20
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 22 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +58 to +59
// eslint-disable-next-line no-useless-escape
.replace(/([+\-&|!(){}\[\]^~*?:\\/"])/g, '\\$1') // Escape JQL operators
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern escapes the backslash in \\/ which eslint flags as unnecessary since forward slashes don't need escaping in character classes. Consider removing the backslash before the forward slash: .replace(/([+\-&|!(){}\[\]^~*?:/"])/g, '\\$1') to eliminate the need for the eslint-disable comment.

Copilot uses AI. Check for mistakes.
Comment on lines +138 to +141
const selectedLinkType = userSelectedLinkTypeId
? linkTypes?.find(t => t.id === userSelectedLinkTypeId) ?? linkTypes?.[0] ?? null
: linkTypes?.[0] ?? null;

Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback chain linkTypes?.find(...) ?? linkTypes?.[0] ?? null could result in unexpected behavior if the user-selected ID doesn't exist but linkTypes has items. In this case, it correctly falls back to the first link type, but this logic could be clearer by explicitly checking if the found type exists before falling back.

Suggested change
const selectedLinkType = userSelectedLinkTypeId
? linkTypes?.find(t => t.id === userSelectedLinkTypeId) ?? linkTypes?.[0] ?? null
: linkTypes?.[0] ?? null;
const selectedLinkType = (() => {
// If no link types are available, there is nothing to select
if (!linkTypes || linkTypes.length === 0) {
return null;
}
// If the user has not explicitly selected a type, default to the first
if (!userSelectedLinkTypeId) {
return linkTypes[0];
}
// Try to find the user-selected type; if not found, explicitly fall back to the first
const foundType = linkTypes.find(t => t.id === userSelectedLinkTypeId);
return foundType ?? linkTypes[0];
})();

Copilot uses AI. Check for mistakes.
@angelo-hub angelo-hub merged commit 846e725 into main Dec 21, 2025
4 checks passed
@angelo-hub angelo-hub deleted the feat/create_issue_links branch December 21, 2025 22:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant