Skip to content

Commit 6a22bc3

Browse files
authored
Merge pull request #32 from lohit-bruno/fetch_in_bruno_button
feat: add `Fetch in Bruno` button
2 parents b3ddf97 + fa922fc commit 6a22bc3

9 files changed

Lines changed: 89 additions & 8 deletions

File tree

packages/oc-docs/e2e/collection-docs.spec.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,22 @@ test.describe('Collection-level documentation', () => {
1919

2020
test('renders markdown headings', async ({ page }) => {
2121
const docs = page.locator('.collection-docs');
22-
await expect(docs.getByRole('heading', { name: 'Bruno Testbench', level: 1 })).toBeVisible();
2322
await expect(docs.getByRole('heading', { name: 'Getting Started', level: 2 })).toBeVisible();
2423
await expect(docs.getByRole('heading', { name: 'Authentication', level: 2 })).toBeVisible();
2524
await expect(docs.getByRole('heading', { name: 'Rate Limits', level: 2 })).toBeVisible();
2625
});
2726

27+
test('renders collection name as page header above docs', async ({ page }) => {
28+
const heading = page
29+
.locator('.playground-content')
30+
.getByRole('heading', { name: 'Bruno Testbench', level: 1 });
31+
await expect(heading).toBeVisible();
32+
33+
const headingBox = await heading.boundingBox();
34+
const docsBox = await page.locator('.collection-docs').boundingBox();
35+
expect(headingBox!.y).toBeLessThan(docsBox!.y);
36+
});
37+
2838
test('renders markdown paragraphs with inline formatting', async ({ page }) => {
2939
const docs = page.locator('.collection-docs');
3040
await expect(docs.getByText('comprehensive API collection for testing')).toBeVisible();

packages/oc-docs/src/components/Docs/Docs.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import type { OpenCollection as OpenCollectionCollection } from '@opencollection
33
import type { StructuredText } from '@opencollection/types/common/description';
44
import Sidebar from './Sidebar/Sidebar';
55
import Item from './Item/Item';
6+
import FetchInBrunoButton from './Sidebar/FetchInBrunoButton';
67
import { getItemId, generateSafeId } from '../../utils/itemUtils';
78
import { isFolder } from '../../utils/schemaHelpers';
89
import { useAppSelector, useAppDispatch } from '../../store/hooks';
910
import { selectSelectedItemId, selectItem } from '../../store/slices/docs';
11+
import { selectGitCollectionUrl } from '../../store/slices/app';
1012
import { useMarkdownRenderer } from '../../hooks';
1113

1214
interface DocsProps {
@@ -22,6 +24,7 @@ const Docs: React.FC<DocsProps> = ({
2224
}) => {
2325
const dispatch = useAppDispatch();
2426
const selectedItemId = useAppSelector(selectSelectedItemId);
27+
const gitCollectionUrl = useAppSelector(selectGitCollectionUrl);
2528
const md = useMarkdownRenderer();
2629
const isInitialMount = useRef(true);
2730

@@ -112,6 +115,28 @@ const Docs: React.FC<DocsProps> = ({
112115
className="playground-content h-full overflow-y-auto flex-1"
113116
>
114117
<div className="all-endpoints-view h-full overflow-y-auto" style={{ padding: '2rem', maxWidth: '100%' }}>
118+
{docsCollection?.info?.name && (
119+
<div
120+
className="flex items-center gap-3 mb-6"
121+
style={{ maxWidth: '80rem' }}
122+
>
123+
<h1
124+
className="text-2xl font-semibold truncate"
125+
style={{ color: 'var(--text-primary)' }}
126+
>
127+
{docsCollection.info.name}
128+
</h1>
129+
{gitCollectionUrl && (
130+
<a
131+
href={`bruno://app/collection/import/git?url=${encodeURIComponent(gitCollectionUrl)}`}
132+
className="flex-shrink-0"
133+
>
134+
<FetchInBrunoButton />
135+
</a>
136+
)}
137+
</div>
138+
)}
139+
115140
{/* Collection-level documentation/introduction */}
116141
{docsCollection?.docs && (
117142
<div
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
3+
const FetchInBrunoButton: React.FC = () => (
4+
<svg xmlns="http://www.w3.org/2000/svg" width="142" height="36" viewBox="0 0 146 36" role="img" aria-label="Fetch in Bruno">
5+
<rect x="2" y="2" width="142" height="32" rx="16" fill="#fffbf7" stroke="#de8116" strokeWidth="1" strokeLinejoin="round" strokeLinecap="round" />
6+
<g transform="translate(10, 1)">
7+
<svg width="24" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
8+
<g>
9+
<path fill="#F4AA41" stroke="none" d="M23.5,14.5855l-4.5,1.75l-7.25,8.5l-4.5,10.75l2,5.25c1.2554,3.7911,3.5231,7.1832,7.25,10l2.5-3.3333 c0,0,3.8218,7.7098,10.7384,8.9598c0,0,10.2616,1.936,15.5949-0.8765c3.4203-1.8037,4.4167-4.4167,4.4167-4.4167l3.4167-3.4167 l1.5833,2.3333l2.0833-0.0833l5.4167-7.25L64,37.3355l-0.1667-4.5l-2.3333-5.5l-4.8333-7.4167c0,0-2.6667-4.9167-8.1667-3.9167 c0,0-6.5-4.8333-11.8333-4.0833S32.0833,10.6688,23.5,14.5855z" />
10+
<polygon fill="#EA5A47" stroke="none" points="36,47.2521 32.9167,49.6688 30.4167,49.6688 30.3333,53.5021 31.0833,57.0021 32.1667,58.9188 35,60.4188 39.5833,59.8355 41.1667,58.0855 42.1667,53.8355 41.9167,49.8355 39.9167,50.0855" />
11+
<polygon fill="#3F3F3F" stroke="none" points="32.5,36.9188 30.9167,40.6688 33.0833,41.9188 34.3333,42.4188 38.6667,42.5855 41.5833,40.3355 39.8333,37.0855" />
12+
</g>
13+
<g>
14+
<path fill="#000000" stroke="none" d="M29.5059,30.1088c0,0-1.8051,1.2424-2.7484,0.6679c-0.9434-0.5745-1.2424-1.8051-0.6679-2.7484 s1.805-1.2424,2.7484-0.6679S29.5059,30.1088,29.5059,30.1088z" />
15+
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M33.1089,37.006h6.1457c0.4011,0,0.7634,0.2397,0.9203,0.6089l1.1579,2.7245l-2.1792,1.1456 c-0.6156,0.3236-1.3654-0.0645-1.4567-0.754" />
16+
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M34.7606,40.763c-0.1132,0.6268-0.7757,0.9895-1.3647,0.7471l-2.3132-0.952l1.0899-2.9035 c0.1465-0.3901,0.5195-0.6486,0.9362-0.6486" />
17+
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M30.4364,50.0268c0,0-0.7187,8.7934,3.0072,9.9375c2.6459,0.8125,5.1497,0.5324,6.0625-0.25 c0.875-0.75,2.6323-4.4741,1.8267-9.6875" />
18+
<path fill="#000000" stroke="none" d="M44.2636,30.1088c0,0,1.805,1.2424,2.7484,0.6679c0.9434-0.5745,1.2424-1.8051,0.6679-2.7484 c-0.5745-0.9434-1.805-1.2424-2.7484-0.6679C43.9881,27.9349,44.2636,30.1088,44.2636,30.1088z" />
19+
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M25.6245,42.8393c-0.475,3.6024,2.2343,5.7505,4.2847,6.8414c1.1968,0.6367,2.6508,0.5182,3.7176-0.3181l2.581-2.0233l2.581,2.0233 c1.0669,0.8363,2.5209,0.9548,3.7176,0.3181c2.0504-1.0909,4.7597-3.239,4.2847-6.8414" />
20+
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M19.9509,28.3572c-2.3166,5.1597-0.5084,13.0249,0.119,15.3759c0.122,0.4571,0.0755,0.9355-0.1271,1.3631l-1.9874,4.1937 c-0.623,1.3146-2.3934,1.5533-3.331,0.4409c-3.1921-3.7871-8.5584-11.3899-6.5486-16.686 c7.0625-18.6104,15.8677-18.1429,15.8677-18.1429c2.8453-1.9336,13.1042-6.9375,24.8125,0.875c0,0,8.6323-1.7175,14.9375,16.9375 c1.8036,5.3362-3.4297,12.8668-6.5506,16.6442c-0.9312,1.127-2.7162,0.8939-3.3423-0.4272l-1.9741-4.1656 c-0.2026-0.4275-0.2491-0.906-0.1271-1.3631c0.6275-2.3509,2.4356-10.2161,0.119-15.3759" />
21+
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M52.6309,46.4628c0,0-3.0781,6.7216-7.8049,8.2712" />
22+
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M19.437,46.969c0,0,3.0781,6.0823,7.8049,7.632" />
23+
<line x1="36.2078" x2="36.2078" y1="47.3393" y2="44.3093" fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" />
24+
</g>
25+
</svg>
26+
</g>
27+
<text x="39" y="23" fontFamily="system-ui, -apple-system, sans-serif" fontSize="13" fontWeight="600" fill="#d97706">Fetch In Bruno</text>
28+
</svg>
29+
);
30+
31+
export default FetchInBrunoButton;

packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ const Sidebar: React.FC<SidebarProps> = () => {
124124
<SidebarContainer className="h-full flex flex-col" style={{ width: 'var(--sidebar-width)' }}>
125125
{/* Collection name at top */}
126126
<div className="p-4 pt-0">
127-
<div className="flex items-center">
127+
<div className="flex items-center gap-2">
128128
<h1 className="font-semibold truncate flex-1" style={{ color: 'var(--text-primary)' }}>
129129
{collection?.info?.name || 'API Collection'}
130130
</h1>

packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import {
2727
setCollectionLoading,
2828
setCollectionSucceeded,
2929
setCollectionFailed,
30-
resetCollectionState
30+
resetCollectionState,
31+
setGitCollectionUrl
3132
} from '@slices/app';
3233
import { createOpenCollectionStore, type AppStore } from '../../store/store';
3334

@@ -162,10 +163,12 @@ const DesktopLayout: React.FC<DesktopLayoutProps> = ({
162163
export interface OpenCollectionProps {
163164
collection: IOpenCollection | string | File;
164165
logo?: React.ReactNode;
166+
gitCollectionUrl?: string;
165167
}
166168

167169
const OpenCollectionContent: React.FC<OpenCollectionProps> = ({
168170
collection,
171+
gitCollectionUrl,
169172
}) => {
170173
const dispatch = useAppDispatch();
171174
const docsCollection = useAppSelector(selectDocsCollection);
@@ -174,6 +177,10 @@ const OpenCollectionContent: React.FC<OpenCollectionProps> = ({
174177
const collectionError = useAppSelector(selectCollectionError);
175178
const selectedItemId = useAppSelector((state) => state.docs.selectedItemId);
176179

180+
useEffect(() => {
181+
gitCollectionUrl && dispatch(setGitCollectionUrl(gitCollectionUrl));
182+
}, [gitCollectionUrl, dispatch]);
183+
177184
useEffect(() => {
178185
let isActive = true;
179186

packages/oc-docs/src/dev.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const DevApp: React.FC = () => {
3131
<OpenCollection
3232
collection={sampleCollectionYaml}
3333
logo="/src/assets/opencollection-logo.svg"
34+
gitCollectionUrl="https://github.com/usebruno/bruno-testbench.git"
3435
/>
3536
</div>
3637
</Provider>

packages/oc-docs/src/sampleCollection.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ request:
3232
token: "{{bearer_auth_token}}"
3333
docs:
3434
content: |
35-
# Bruno Testbench
36-
3735
This is a comprehensive API collection for testing **OpenCollection** features.
3836
3937
## Getting Started

packages/oc-docs/src/standalone.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface OpenCollectionOptions {
2323
target: HTMLElement;
2424
opencollection: any;
2525
logo?: string;
26+
gitCollectionUrl?: string;
2627
}
2728

2829
export class OpenCollectionRenderer {
@@ -96,7 +97,8 @@ export class OpenCollectionRenderer {
9697

9798
this.root.render(React.createElement(OpenCollection, {
9899
collection,
99-
logo: this.createLogoElement()
100+
logo: this.createLogoElement(),
101+
gitCollectionUrl: this.options.gitCollectionUrl
100102
}));
101103
}
102104

packages/oc-docs/src/store/slices/app.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ export type CollectionStatus = 'idle' | 'loading' | 'succeeded' | 'failed';
66
export interface AppState {
77
collectionStatus: CollectionStatus;
88
collectionError: string | null;
9+
gitCollectionUrl: string | null;
910
}
1011

1112
const initialState: AppState = {
1213
collectionStatus: 'idle',
13-
collectionError: null
14+
collectionError: null,
15+
gitCollectionUrl: null
1416
};
1517

1618
const appSlice = createSlice({
@@ -32,6 +34,9 @@ const appSlice = createSlice({
3234
resetCollectionState: (state: AppState) => {
3335
state.collectionStatus = 'idle';
3436
state.collectionError = null;
37+
},
38+
setGitCollectionUrl: (state: AppState, action: PayloadAction<string | null>) => {
39+
state.gitCollectionUrl = action.payload;
3540
}
3641
}
3742
});
@@ -40,11 +45,13 @@ export const {
4045
setCollectionLoading,
4146
setCollectionSucceeded,
4247
setCollectionFailed,
43-
resetCollectionState
48+
resetCollectionState,
49+
setGitCollectionUrl
4450
} = appSlice.actions;
4551
export default appSlice.reducer;
4652

4753
export const selectCollectionStatus = (state: RootState) => state.app.collectionStatus;
4854
export const selectCollectionError = (state: RootState) => state.app.collectionError;
55+
export const selectGitCollectionUrl = (state: RootState) => state.app.gitCollectionUrl;
4956

5057

0 commit comments

Comments
 (0)