Skip to content

Commit 337701b

Browse files
authored
Fix collection documentation tab crash caused due to missing docs_blob field (#3133)
1 parent 79c0474 commit 337701b

7 files changed

Lines changed: 141 additions & 4 deletions

File tree

frontend/hub/collections/CollectionPage/CollectionContents.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export function CollectionContents() {
3232
collection.collection_version?.name || ''
3333
}&namespace=${collection.collection_version?.namespace || ''}&version=${
3434
collection.collection_version?.version || ''
35-
}`
35+
}&exclude_fields=files,manifest,docs_blob`
3636
);
3737

3838
const tableColumns = useTableColumns(collection);

frontend/hub/collections/CollectionPage/CollectionDocumentation.test.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,39 @@ describe('CollectionDocumentation', () => {
123123
afterAll(() => server.close());
124124
afterEach(() => server.resetHandlers());
125125

126+
test('should request docs_blob by including exclude_fields in API URL', async () => {
127+
let capturedUrl = '';
128+
server.use(
129+
http.get(
130+
({ request }) => {
131+
return request.url.includes('/content/ansible/collection_versions/');
132+
},
133+
({ request }) => {
134+
capturedUrl = request.url;
135+
return HttpResponse.json(mockDocumentationResponse);
136+
}
137+
)
138+
);
139+
140+
render(
141+
<TestWrapper>
142+
<CollectionDocumentation />
143+
</TestWrapper>
144+
);
145+
146+
await waitFor(() => {
147+
expect(capturedUrl).toContain('exclude_fields=');
148+
});
149+
150+
const url = new URL(capturedUrl);
151+
const excludeFields = url.searchParams.get('exclude_fields');
152+
expect(excludeFields).toBeTruthy();
153+
expect(excludeFields).toContain('files');
154+
expect(excludeFields).toContain('manifest');
155+
expect(excludeFields).toContain('contents');
156+
expect(excludeFields).not.toContain('docs_blob');
157+
});
158+
126159
test('should render documentation tab with readme content', async () => {
127160
render(
128161
<TestWrapper>

frontend/hub/collections/CollectionPage/CollectionDocumentation.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export function CollectionDocumentation() {
4545
collection?.collection_version?.namespace || ''
4646
}&name=${collection?.collection_version?.name || ''}&version=${
4747
collection?.collection_version?.version || ''
48-
}&offset=0&limit=1`
48+
}&offset=0&limit=1&exclude_fields=files,manifest,contents`
4949
);
5050

5151
// create groups for left tab of contents menu

frontend/hub/collections/CollectionPage/CollectionInstall.test.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,70 @@ describe('CollectionInstall', () => {
126126
afterAll(() => server.close());
127127
afterEach(() => server.resetHandlers());
128128

129+
test('should request docs_blob by including exclude_fields in API URL', async () => {
130+
let capturedUrl = '';
131+
server.use(
132+
http.get(
133+
({ request }) => {
134+
return request.url.includes('/content/ansible/collection_versions/');
135+
},
136+
({ request }) => {
137+
capturedUrl = request.url;
138+
return HttpResponse.json(mockContentResponse);
139+
}
140+
)
141+
);
142+
143+
render(
144+
<TestWrapper>
145+
<CollectionInstall />
146+
</TestWrapper>
147+
);
148+
149+
await waitFor(() => {
150+
expect(capturedUrl).toContain('exclude_fields=');
151+
});
152+
153+
const url = new URL(capturedUrl);
154+
const excludeFields = url.searchParams.get('exclude_fields');
155+
expect(excludeFields).toBeTruthy();
156+
expect(excludeFields).toContain('files');
157+
expect(excludeFields).toContain('manifest');
158+
expect(excludeFields).toContain('contents');
159+
expect(excludeFields).not.toContain('docs_blob');
160+
});
161+
162+
test('should handle missing docs_blob gracefully', async () => {
163+
server.use(
164+
http.get(
165+
({ request }) => request.url.includes('/content/ansible/collection_versions/'),
166+
() =>
167+
HttpResponse.json({
168+
count: 1,
169+
next: '',
170+
previous: '',
171+
results: [
172+
{
173+
license: ['MIT'],
174+
},
175+
],
176+
})
177+
)
178+
);
179+
180+
render(
181+
<TestWrapper>
182+
<CollectionInstall />
183+
</TestWrapper>
184+
);
185+
186+
await waitFor(() => {
187+
expect(screen.getByRole('heading', { name: 'Install' })).toBeInTheDocument();
188+
});
189+
190+
expect(screen.queryByRole('link', { name: /go to documentation/i })).not.toBeInTheDocument();
191+
});
192+
129193
test('should render install information with license', async () => {
130194
render(
131195
<TestWrapper>

frontend/hub/collections/CollectionPage/CollectionInstall.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export function CollectionInstall() {
4141
collection?.collection_version?.namespace || ''
4242
}&name=${collection?.collection_version?.name || ''}&version=${
4343
collection?.collection_version?.version || ''
44-
}&offset=0&limit=1`
44+
}&offset=0&limit=1&exclude_fields=files,manifest,contents`
4545
);
4646

4747
const content = contentResults?.results[0];

frontend/hub/collections/CollectionPage/CollectionPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export function CollectionPage() {
6868
collection?.collection_version?.namespace || ''
6969
}&name=${collection?.collection_version?.name || ''}&version=${
7070
collection?.collection_version?.version || ''
71-
}&offset=0&limit=1`
71+
}&offset=0&limit=1&exclude_fields=files,manifest,docs_blob,contents`
7272
: ''
7373
);
7474
const content = contentResults?.results?.[0];

playwright/tests/integration/automation-content/collections/collection-details.spec.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,46 @@ test.describe('Hub Collections - Details Page', () => {
673673
);
674674
});
675675

676+
test.describe('Documentation Tab', () => {
677+
test(
678+
'should render documentation content without error',
679+
{ tag: ['@not_mock'] },
680+
async ({ page, collection }) => {
681+
const namespace = 'e2edocs';
682+
const name = 'docstab';
683+
684+
await collection.createNamespace({ name: namespace });
685+
const uploaded = await collection.uploadVersion({
686+
namespace,
687+
name,
688+
version: '1.0.0',
689+
repository: 'staging',
690+
});
691+
692+
await collection.approveCollection({
693+
namespace: uploaded.namespace,
694+
name: uploaded.name,
695+
version: uploaded.version,
696+
});
697+
698+
await navigateToCollectionDetails(page, uploaded);
699+
700+
// Click on the Documentation tab
701+
await page.getByRole('tab', { name: 'Documentation' }).click();
702+
703+
// Verify documentation content renders (not an error state)
704+
await expect(page.getByRole('heading', { name: 'Page not found' })).not.toBeVisible();
705+
706+
// Verify the page does not show a generic error boundary
707+
const mainContent = page.locator('main');
708+
await expect(mainContent).not.toContainText('Error', { timeout: 10000 });
709+
710+
// Verify documentation structure rendered (drawer with navigation panel)
711+
await expect(page.getByPlaceholder('Find content')).toBeVisible({ timeout: 10000 });
712+
}
713+
);
714+
});
715+
676716
test.describe('Deprecation', () => {
677717
test(
678718
'should deprecate and undeprecate a collection from detail page',

0 commit comments

Comments
 (0)