mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
fix(workspaces): gate the Jira palette match on the issue's tenant (#14671)
* fix(workspaces): gate the Jira palette match on the issue's tenant Pasting a Jira issue URL matched any worktree whose linked item carried the same `jiraIdentifier`, with no comparison of the site it came from. Jira issue keys are per-project, not per-tenant, so every tenant with a PROJ project has a PROJ-123 — pasting one tenant's URL could jump to a worktree tracking a different tenant's issue entirely. The stored linked URL is the only tenant evidence available here, so where it exists it now decides, comparing origin and site path the way the fallback already did. This also covers path-scoped Jira Server installs sharing one host. The bare identifier still matches when no URL was stored, since it is then the only evidence there is. This mirrors the check `isWorkspaceLinkedItemSourceContextMatch` already makes on the same fields. * test(workspaces): cover the reachable Jira identifier fallback The fallback fixture used a blank url, a shape `normalizeWorkspaceLinkedItem` rejects outright, so it pinned a state that cannot reach the palette. The reachable way to have no tenant evidence is a url that is present but is not a Jira browse link, which is now what the test uses. Also covers a pasted url carrying Jira's `atlOrigin` tracking query, since the matcher compares pathname only.
This commit is contained in:
@@ -622,6 +622,115 @@ describe('matchWorktreePaletteTaskUrl', () => {
|
||||
})
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('matches a Jira issue URL on its own tenant', () => {
|
||||
const intent = parseCmdJTaskSourceUrl('https://acme.atlassian.net/browse/PROJ-123')
|
||||
|
||||
expect(
|
||||
matchWorktreePaletteTaskUrl({
|
||||
worktree: makeWorktree({
|
||||
linkedWorkItem: {
|
||||
provider: 'jira',
|
||||
type: 'issue',
|
||||
number: 0,
|
||||
title: 'Tenant scoped',
|
||||
jiraIdentifier: 'PROJ-123',
|
||||
url: 'https://acme.atlassian.net/browse/PROJ-123'
|
||||
}
|
||||
}),
|
||||
intent: intent!
|
||||
})
|
||||
).toMatchObject({ supportingText: { text: 'PROJ-123' } })
|
||||
})
|
||||
|
||||
// Why: Jira issue keys are per-project, so the same PROJ-123 exists on every
|
||||
// tenant that has a PROJ project.
|
||||
it('rejects a Jira issue URL from a different tenant with the same issue key', () => {
|
||||
const intent = parseCmdJTaskSourceUrl('https://acme.atlassian.net/browse/PROJ-123')
|
||||
|
||||
expect(
|
||||
matchWorktreePaletteTaskUrl({
|
||||
worktree: makeWorktree({
|
||||
linkedWorkItem: {
|
||||
provider: 'jira',
|
||||
type: 'issue',
|
||||
number: 0,
|
||||
title: 'Other tenant',
|
||||
jiraIdentifier: 'PROJ-123',
|
||||
url: 'https://other.atlassian.net/browse/PROJ-123'
|
||||
}
|
||||
}),
|
||||
intent: intent!
|
||||
})
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
// Same host, different site path: Jira Server installs are commonly path-scoped.
|
||||
it('rejects a Jira issue URL from a different site path on the same host', () => {
|
||||
const intent = parseCmdJTaskSourceUrl('https://jira.acme.test/one/browse/PROJ-123')
|
||||
|
||||
expect(
|
||||
matchWorktreePaletteTaskUrl({
|
||||
worktree: makeWorktree({
|
||||
linkedWorkItem: {
|
||||
provider: 'jira',
|
||||
type: 'issue',
|
||||
number: 0,
|
||||
title: 'Other site',
|
||||
jiraIdentifier: 'PROJ-123',
|
||||
url: 'https://jira.acme.test/two/browse/PROJ-123'
|
||||
}
|
||||
}),
|
||||
intent: intent!
|
||||
})
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
// The reachable fallback: `normalizeWorkspaceLinkedItem` drops any item with a
|
||||
// blank url, so the only way to have no tenant evidence is a url that is present
|
||||
// but is not a Jira browse link. The identifier is then all there is.
|
||||
it('falls back to the Jira identifier when the stored url is not a Jira link', () => {
|
||||
const intent = parseCmdJTaskSourceUrl('https://acme.atlassian.net/browse/PROJ-123')
|
||||
|
||||
expect(
|
||||
matchWorktreePaletteTaskUrl({
|
||||
worktree: makeWorktree({
|
||||
linkedWorkItem: {
|
||||
provider: 'jira',
|
||||
type: 'issue',
|
||||
number: 0,
|
||||
title: 'Linked elsewhere',
|
||||
jiraIdentifier: 'PROJ-123',
|
||||
url: 'https://github.com/stablyai/orca/issues/14198'
|
||||
}
|
||||
}),
|
||||
intent: intent!
|
||||
})
|
||||
).toMatchObject({ supportingText: { text: 'PROJ-123' } })
|
||||
})
|
||||
|
||||
// Jira appends a tracking query on copy, and the matcher is pathname-only.
|
||||
it('matches a pasted Jira url carrying a tracking query string', () => {
|
||||
const intent = parseCmdJTaskSourceUrl(
|
||||
'https://acme.atlassian.net/browse/PROJ-123?atlOrigin=eyJpIjoiZm9vIn0'
|
||||
)
|
||||
|
||||
expect(
|
||||
matchWorktreePaletteTaskUrl({
|
||||
worktree: makeWorktree({
|
||||
linkedWorkItem: {
|
||||
provider: 'jira',
|
||||
type: 'issue',
|
||||
number: 0,
|
||||
title: 'Tenant scoped',
|
||||
jiraIdentifier: 'PROJ-123',
|
||||
url: 'https://acme.atlassian.net/browse/PROJ-123'
|
||||
}
|
||||
}),
|
||||
intent: intent!
|
||||
})
|
||||
).toMatchObject({ supportingText: { text: 'PROJ-123' } })
|
||||
})
|
||||
})
|
||||
|
||||
describe('getCmdJTaskUrlCreatePreview', () => {
|
||||
|
||||
@@ -275,18 +275,21 @@ function worktreeMatchesLinearUrl(worktree: Worktree, intent: LinearIssueUrlInte
|
||||
}
|
||||
|
||||
function worktreeMatchesJiraUrl(worktree: Worktree, parsed: ParsedJiraIssueUrl): boolean {
|
||||
if (worktree.linkedWorkItem?.jiraIdentifier?.toUpperCase() === parsed.issueKey) {
|
||||
return true
|
||||
}
|
||||
const linkedUrl = worktree.linkedWorkItem?.url
|
||||
? parseJiraIssueUrl(worktree.linkedWorkItem.url)
|
||||
: null
|
||||
return (
|
||||
linkedUrl !== null &&
|
||||
linkedUrl.issueKey === parsed.issueKey &&
|
||||
linkedUrl.origin === parsed.origin &&
|
||||
linkedUrl.sitePath === parsed.sitePath
|
||||
)
|
||||
// Why url first: issue keys are per-project, not per-tenant, so two Jira sites
|
||||
// routinely both have a PROJ-123. The stored URL is the only tenant evidence
|
||||
// here, so where it exists it decides — matching on the bare identifier would
|
||||
// jump to another tenant's worktree.
|
||||
if (linkedUrl) {
|
||||
return (
|
||||
linkedUrl.issueKey === parsed.issueKey &&
|
||||
linkedUrl.origin === parsed.origin &&
|
||||
linkedUrl.sitePath === parsed.sitePath
|
||||
)
|
||||
}
|
||||
return worktree.linkedWorkItem?.jiraIdentifier?.toUpperCase() === parsed.issueKey
|
||||
}
|
||||
|
||||
export function matchWorktreePaletteTaskUrl(args: {
|
||||
|
||||
Reference in New Issue
Block a user