Skip to content

Commit fe9540b

Browse files
authored
[AI Search] Fix bug with #extractProductFromDocsUrl (#55447)
1 parent 4baf8ce commit fe9540b

File tree

2 files changed

+155
-2
lines changed

2 files changed

+155
-2
lines changed

src/search/components/helpers/ai-search-links-json.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,11 @@ function extractMarkdownLinks(markdownResponse: string) {
6767

6868
// Given a Docs URL, extract the product name
6969
function extractProductFromDocsUrl(url: string): string {
70-
const pathname = new URL(url).pathname
70+
const urlObject = new URL(url)
71+
if (urlObject.hostname !== 'docs.github.com') {
72+
return ''
73+
}
74+
const pathname = urlObject.pathname
7175

7276
const segments = pathname.split('/').filter((segment) => segment)
7377

@@ -78,7 +82,14 @@ function extractProductFromDocsUrl(url: string): string {
7882
}
7983

8084
if (segments[0].length === 2) {
81-
return segments[1] || ''
85+
if (segments.length < 2) {
86+
return ''
87+
}
88+
// if second segment is a version, then product is the third segment
89+
if (segments[1].includes('@')) {
90+
return segments[2] || ''
91+
}
92+
return segments[1]
8293
}
8394

8495
return segments[0]
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { expect, test, describe } from 'vitest'
2+
import { generateAISearchLinksJson } from '@/search/components/helpers/ai-search-links-json'
3+
4+
describe('generateAISearchLinksJson', () => {
5+
test('should return empty array JSON for no links', () => {
6+
const sources: { url: string }[] = []
7+
const aiResponse = 'This is a response with no links.'
8+
const result = generateAISearchLinksJson(sources, aiResponse)
9+
expect(JSON.parse(result)).toEqual([])
10+
})
11+
12+
test('should handle only reference links', () => {
13+
const sources = [
14+
{ url: 'https://docs.github.com/en/codespaces/overview' },
15+
{ url: 'https://docs.github.com/en/actions/learn-github-actions' },
16+
]
17+
const aiResponse = 'Response text.'
18+
const result = generateAISearchLinksJson(sources, aiResponse)
19+
expect(JSON.parse(result)).toEqual([
20+
{
21+
type: 'reference',
22+
url: 'https://docs.github.com/en/codespaces/overview',
23+
product: 'codespaces',
24+
},
25+
{
26+
type: 'reference',
27+
url: 'https://docs.github.com/en/actions/learn-github-actions',
28+
product: 'actions',
29+
},
30+
])
31+
})
32+
33+
test('should handle only inline links', () => {
34+
const sources: { url: string }[] = []
35+
const aiResponse =
36+
'Check out [Codespaces](https://docs.github.com/en/codespaces/overview) and [Actions](https://docs.github.com/en/actions/learn-github-actions).'
37+
const result = generateAISearchLinksJson(sources, aiResponse)
38+
expect(JSON.parse(result)).toEqual([
39+
{
40+
type: 'inline',
41+
url: 'https://docs.github.com/en/codespaces/overview',
42+
product: 'codespaces',
43+
},
44+
{
45+
type: 'inline',
46+
url: 'https://docs.github.com/en/actions/learn-github-actions',
47+
product: 'actions',
48+
},
49+
])
50+
})
51+
52+
test('should handle both reference and inline links', () => {
53+
const sources = [{ url: 'https://docs.github.com/en/billing/managing-billing' }]
54+
const aiResponse = 'Learn about [Billing](https://docs.github.com/en/billing/managing-billing).'
55+
const result = generateAISearchLinksJson(sources, aiResponse)
56+
// Note: The inline link appears first because it's processed first
57+
expect(JSON.parse(result)).toEqual([
58+
{
59+
type: 'inline',
60+
url: 'https://docs.github.com/en/billing/managing-billing',
61+
product: 'billing',
62+
},
63+
{
64+
type: 'reference',
65+
url: 'https://docs.github.com/en/billing/managing-billing',
66+
product: 'billing',
67+
},
68+
])
69+
})
70+
71+
test('should handle versioned URLs correctly', () => {
72+
const sources = [
73+
{ url: 'https://docs.github.com/en/enterprise-cloud@latest/codespaces/overview' },
74+
]
75+
const aiResponse =
76+
'See [Codespaces for Enterprise](https://docs.github.com/en/[email protected]/codespaces/overview).'
77+
const result = generateAISearchLinksJson(sources, aiResponse)
78+
expect(JSON.parse(result)).toEqual([
79+
{
80+
type: 'inline',
81+
url: 'https://docs.github.com/en/[email protected]/codespaces/overview',
82+
product: 'codespaces',
83+
},
84+
{
85+
type: 'reference',
86+
url: 'https://docs.github.com/en/enterprise-cloud@latest/codespaces/overview',
87+
product: 'codespaces',
88+
},
89+
])
90+
})
91+
92+
test('should handle non-docs URLs with empty product', () => {
93+
const sources = [{ url: 'https://github.com/features/actions' }]
94+
const aiResponse = 'Visit [GitHub](https://github.com/).'
95+
const result = generateAISearchLinksJson(sources, aiResponse)
96+
expect(JSON.parse(result)).toEqual([
97+
{ type: 'inline', url: 'https://github.com/', product: '' }, // Non-docs inline link
98+
{ type: 'reference', url: 'https://github.com/features/actions', product: '' }, // Non-docs reference link
99+
])
100+
})
101+
102+
test('should ignore invalid URLs in markdown', () => {
103+
const sources = [{ url: 'https://docs.github.com/en/copilot/overview' }]
104+
const aiResponse =
105+
'See [Copilot](https://docs.github.com/en/copilot/overview) and [invalid](not-a-valid-url).'
106+
const result = generateAISearchLinksJson(sources, aiResponse)
107+
expect(JSON.parse(result)).toEqual([
108+
{ type: 'inline', url: 'https://docs.github.com/en/copilot/overview', product: 'copilot' },
109+
{ type: 'reference', url: 'https://docs.github.com/en/copilot/overview', product: 'copilot' },
110+
])
111+
})
112+
113+
test('should handle URLs without language code', () => {
114+
const sources: { url: string }[] = []
115+
const aiResponse = 'Link: [Copilot](https://docs.github.com/copilot/overview)'
116+
const result = generateAISearchLinksJson(sources, aiResponse)
117+
expect(JSON.parse(result)).toEqual([
118+
{ type: 'inline', url: 'https://docs.github.com/copilot/overview', product: 'copilot' },
119+
])
120+
})
121+
122+
test('should handle URLs with only language code', () => {
123+
const sources = [{ url: 'https://docs.github.com/en' }]
124+
const aiResponse = 'Visit [English Docs](https://docs.github.com/en).'
125+
const result = generateAISearchLinksJson(sources, aiResponse)
126+
expect(JSON.parse(result)).toEqual([
127+
{ type: 'inline', url: 'https://docs.github.com/en', product: '' },
128+
{ type: 'reference', url: 'https://docs.github.com/en', product: '' },
129+
])
130+
})
131+
132+
test('should handle URLs with language code and version but no product', () => {
133+
const sources = [{ url: 'https://docs.github.com/en/[email protected]' }]
134+
const aiResponse =
135+
'Visit [Enterprise Server Docs](https://docs.github.com/en/[email protected]).'
136+
const result = generateAISearchLinksJson(sources, aiResponse)
137+
expect(JSON.parse(result)).toEqual([
138+
{ type: 'inline', url: 'https://docs.github.com/en/[email protected]', product: '' },
139+
{ type: 'reference', url: 'https://docs.github.com/en/[email protected]', product: '' },
140+
])
141+
})
142+
})

0 commit comments

Comments
 (0)