Skip to content

Commit ec9badf

Browse files
committed
feat: top level versioning for platforms and guides
1 parent e12191e commit ec9badf

File tree

10 files changed

+135
-34
lines changed

10 files changed

+135
-34
lines changed

app/[[...path]]/page.tsx

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@ import {
1616
nodeForPath,
1717
} from 'sentry-docs/docTree';
1818
import {isDeveloperDocs} from 'sentry-docs/isDeveloperDocs';
19-
import {getDevDocsFrontMatter, getDocsFrontMatter, getFileBySlug} from 'sentry-docs/mdx';
19+
import {
20+
getDevDocsFrontMatter,
21+
getDocsFrontMatter,
22+
getFileBySlug,
23+
getVersionsFromDoc,
24+
} from 'sentry-docs/mdx';
2025
import {mdxComponents} from 'sentry-docs/mdxComponents';
2126
import {setServerContext} from 'sentry-docs/serverContext';
22-
import {VERSION_INDICATOR} from 'sentry-docs/versioning';
2327

2428
export async function generateStaticParams() {
2529
const docs = await (isDeveloperDocs ? getDevDocsFrontMatter() : getDocsFrontMatter());
@@ -90,6 +94,7 @@ export default async function Page({params}: {params: {path?: string[]}}) {
9094
}
9195

9296
const pageNode = nodeForPath(rootNode, params.path);
97+
9398
if (!pageNode) {
9499
// eslint-disable-next-line no-console
95100
console.warn('no page node', params.path);
@@ -111,19 +116,8 @@ export default async function Page({params}: {params: {path?: string[]}}) {
111116
const {mdxSource, frontMatter} = doc;
112117

113118
// collect versioned files
114-
const versions = (await getDocsFrontMatter())
115-
.filter(({slug}) => {
116-
return (
117-
slug.includes(VERSION_INDICATOR) &&
118-
pageNode.path
119-
.split(VERSION_INDICATOR)[0]
120-
.includes(slug.split(VERSION_INDICATOR)[0])
121-
);
122-
})
123-
.map(({slug}) => {
124-
const segments = slug.split(VERSION_INDICATOR);
125-
return segments[segments.length - 1];
126-
});
119+
const allFm = await getDocsFrontMatter();
120+
const versions = getVersionsFromDoc(allFm, pageNode.path);
127121

128122
// pass frontmatter tree into sidebar, rendered page + fm into middle, headers into toc.
129123
return (

src/components/docPage/index.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {getCurrentGuide, getCurrentPlatform, nodeForPath} from 'sentry-docs/docT
44
import {serverContext} from 'sentry-docs/serverContext';
55
import {FrontMatter} from 'sentry-docs/types';
66
import {isTruthy} from 'sentry-docs/utils';
7+
import {getUnversionedPath} from 'sentry-docs/versioning';
78

89
import './type.scss';
910

@@ -43,14 +44,18 @@ export function DocPage({
4344

4445
const searchPlatforms = [currentPlatform?.name, currentGuide?.name].filter(isTruthy);
4546

46-
const leafNode = nodeForPath(rootNode, path);
47+
const unversionedPath = getUnversionedPath(path, false);
48+
49+
const leafNode = nodeForPath(rootNode, unversionedPath);
4750

4851
return (
4952
<div className="tw-app">
5053
<Header pathname={pathname} searchPlatforms={searchPlatforms} />
5154

5255
<section className="px-0 flex relative">
53-
{sidebar ?? <Sidebar path={path} versions={frontMatter.versions} />}
56+
{sidebar ?? (
57+
<Sidebar path={unversionedPath.split('/')} versions={frontMatter.versions} />
58+
)}
5459
<main className="main-content flex w-full mt-[var(--header-height)] flex-1 mx-auto">
5560
<div
5661
className={[

src/components/dynamicNav.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {Fragment} from 'react';
22

33
import {serverContext} from 'sentry-docs/serverContext';
44
import {sortPages} from 'sentry-docs/utils';
5-
import {VERSION_INDICATOR} from 'sentry-docs/versioning';
5+
import {getUnversionedPath, VERSION_INDICATOR} from 'sentry-docs/versioning';
66

77
import {NavChevron} from './sidebar/navChevron';
88
import {SidebarLink} from './sidebarLink';
@@ -31,11 +31,11 @@ export interface EntityTree extends Entity<EntityTree> {}
3131
export const toTree = (nodeList: Node[]): EntityTree[] => {
3232
const result: EntityTree[] = [];
3333
const level = {result};
34-
3534
nodeList
3635
.sort((a, b) => a.path.localeCompare(b.path))
3736
.forEach(node => {
3837
let curPath = '';
38+
3939
// hide versioned pages in sidebar
4040
if (!node.path.includes(VERSION_INDICATOR)) {
4141
node.path.split('/').reduce((r, name: string) => {
@@ -54,8 +54,7 @@ export const toTree = (nodeList: Node[]): EntityTree[] => {
5454
}
5555
});
5656

57-
result.length; // result[0] is undefined without this. wat
58-
return result[0].children;
57+
return result.length > 0 ? result[0].children : [];
5958
};
6059

6160
export const renderChildren = (
@@ -168,7 +167,7 @@ export function DynamicNav({
168167
parentNode && !noHeadingLink ? (
169168
<SmartLink
170169
to={`/${root}/`}
171-
className={`${headerClassName} ${path.join('/') === root ? 'active' : ''} justify-between`}
170+
className={`${headerClassName} ${getUnversionedPath(path, false) === root ? 'active' : ''} justify-between`}
172171
activeClassName="active"
173172
data-sidebar-link
174173
>

src/components/platformCategorySection.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ const isSupported = (
1919
return false;
2020
}
2121

22-
// @ts-ignore
23-
const categories = Object.values(platformOrGuide.categories) as string[];
22+
const categories = (platformOrGuide.categories || []) as string[];
2423

2524
if (supported.length && !supported.some(v => categories.includes(v))) {
2625
return false;

src/components/versionSelector/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export function VersionSelector({versions, sdk}: {sdk: string; versions: string[
6262
pathVersion !== storedSelection &&
6363
versions.includes(storedSelection)
6464
) {
65-
router.replace(getVersionedPathname(storedSelection));
65+
// router.replace(getVersionedPathname(storedSelection));
6666
}
6767
}, [
6868
getCurrentVersion,

src/docTree.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
PlatformGuide,
99
PlatformIntegration,
1010
} from './types';
11+
import {isVersioned, VERSION_INDICATOR} from './versioning';
1112

1213
export interface DocNode {
1314
children: DocNode[];
@@ -133,6 +134,7 @@ function frontmatterToTree(frontmatter: FrontMatter[]): DocNode {
133134
export function nodeForPath(node: DocNode, path: string | string[]): DocNode | undefined {
134135
const stringPath = typeof path === 'string' ? path : path.join('/');
135136
const parts = slugWithoutIndex(stringPath);
137+
136138
for (let i = 0; i < parts.length; i++) {
137139
const maybeChild = node.children.find(child => child.slug === parts[i]);
138140
if (maybeChild) {
@@ -196,15 +198,15 @@ export function getCurrentPlatform(
196198
if (path.length < 2 || path[0] !== 'platforms') {
197199
return undefined;
198200
}
199-
return getPlatform(rootNode, path[1]);
201+
return getPlatform(rootNode, path[1].split(VERSION_INDICATOR)[0]);
200202
}
201203

202204
export function getCurrentGuide(
203205
rootNode: DocNode,
204206
path: string[]
205207
): PlatformGuide | undefined {
206208
if (path.length >= 4 && path[2] === 'guides') {
207-
return getGuide(rootNode, path[1], path[3]);
209+
return getGuide(rootNode, path[1], path[3].split(VERSION_INDICATOR)[0]);
208210
}
209211

210212
return undefined;
@@ -243,15 +245,19 @@ export function extractPlatforms(rootNode: DocNode): Platform[] {
243245
return [];
244246
}
245247

246-
return platformsNode.children.map(nodeToPlatform);
248+
return platformsNode.children
249+
.filter(({path}) => !isVersioned(path))
250+
.map(nodeToPlatform);
247251
}
248252

249253
function extractGuides(platformNode: DocNode): PlatformGuide[] {
250254
const guidesNode = nodeForPath(platformNode, 'guides');
251255
if (!guidesNode) {
252256
return [];
253257
}
254-
return guidesNode.children.map(n => nodeToGuide(platformNode.slug, n));
258+
return guidesNode.children
259+
.filter(({path}) => !isVersioned(path))
260+
.map(n => nodeToGuide(platformNode.slug, n));
255261
}
256262

257263
const extractIntegrations = (p: DocNode): PlatformIntegration[] => {

src/mdx.spec.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
import {describe, expect, test} from 'vitest';
22

3-
import {getVersionedIndexPath} from './mdx';
3+
import {getVersionedIndexPath, getVersionsFromDoc} from './mdx';
4+
import {FrontMatter} from './types';
5+
6+
const mockFm: FrontMatter[] = [
7+
{
8+
title: 'js',
9+
slug: 'platforms/javascript',
10+
},
11+
{
12+
title: 'go',
13+
slug: 'platforms/go',
14+
},
15+
];
416

517
describe('mdx', () => {
618
describe('getVersionedIndexPath', () => {
@@ -27,5 +39,56 @@ describe('mdx', () => {
2739
'/does/not/exist.mdx'
2840
);
2941
});
42+
test('return versioned path for root level common file', () => {
43+
expect(
44+
getVersionedIndexPath('/', 'docs/platforms/javascript__v7/common', '.mdx')
45+
).toBe('/docs/platforms/javascript/common/index__v7.mdx');
46+
});
47+
});
48+
49+
describe('getVersionsFromDoc', () => {
50+
test('should return no versions from unversioned docs', () => {
51+
const fm: FrontMatter[] = [...mockFm];
52+
const versions = getVersionsFromDoc(fm, '/platforms/javascript');
53+
expect(versions).toHaveLength(0);
54+
});
55+
56+
test('should return one version from versioned docs', () => {
57+
const fm: FrontMatter[] = [
58+
...mockFm,
59+
{title: 'js', slug: 'platforms/javascript__v2'},
60+
{title: 'go', slug: 'platforms/go__v2'},
61+
];
62+
const versions = getVersionsFromDoc(fm, '/platforms/javascript');
63+
expect(versions).toHaveLength(1);
64+
expect(versions).toContain('2');
65+
});
66+
67+
test('should return several versions from versioned docs', () => {
68+
const fm: FrontMatter[] = [
69+
...mockFm,
70+
{title: 'js', slug: 'platforms/javascript__v2'},
71+
{title: 'js', slug: 'platforms/javascript__v1.23'},
72+
{title: 'js', slug: 'platforms/javascript__v1.23.1'},
73+
{title: 'go', slug: 'platforms/go__v2'},
74+
];
75+
const versions = getVersionsFromDoc(fm, '/platforms/javascript');
76+
expect(versions).toHaveLength(3);
77+
expect(versions).toContain('1.23');
78+
expect(versions).toContain('1.23.1');
79+
expect(versions).toContain('2');
80+
});
81+
82+
test('should not contain duplicates', () => {
83+
const fm: FrontMatter[] = [
84+
...mockFm,
85+
{title: 'js', slug: 'platforms/javascript__v2'},
86+
{title: 'js', slug: 'platforms/javascript/guides/nextjs'},
87+
{title: 'js', slug: 'platforms/javascript/guides/nextjs__v2'},
88+
];
89+
const versions = getVersionsFromDoc(fm, 'platforms/javascript/guides/nextjs');
90+
expect(versions).toHaveLength(1);
91+
expect(versions).toContain('2');
92+
});
3093
});
3194
});

src/mdx.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,26 @@ export function getDocsFrontMatter(): Promise<FrontMatter[]> {
7070
return getDocsFrontMatterCache;
7171
}
7272

73+
/**
74+
* collect all available versions for a given document path
75+
*/
76+
export const getVersionsFromDoc = (frontMatter: FrontMatter[], docPath: string) => {
77+
const versions = frontMatter
78+
.filter(({slug}) => {
79+
return (
80+
slug.includes(VERSION_INDICATOR) &&
81+
docPath.split(VERSION_INDICATOR)[0].includes(slug.split(VERSION_INDICATOR)[0])
82+
);
83+
})
84+
.map(({slug}) => {
85+
const segments = slug.split(VERSION_INDICATOR);
86+
return segments[segments.length - 1];
87+
});
88+
89+
// remove duplicates
90+
return [...new Set(versions)];
91+
};
92+
7393
async function getDocsFrontMatterUncached(): Promise<FrontMatter[]> {
7494
const frontMatter = getAllFilesFrontMatter();
7595

@@ -256,8 +276,14 @@ export const getVersionedIndexPath = (
256276
let versionedSlug = 'does/not/exist.mdx';
257277
const segments = slug.split(VERSION_INDICATOR);
258278
if (segments.length === 2) {
259-
versionedSlug = `${segments[0]}/index${VERSION_INDICATOR}${segments[1]}${fileExtension}`;
279+
if (segments[1].includes('common')) {
280+
const segmentWithoutCommon = segments[1].split('/common')[0];
281+
versionedSlug = `${segments[0]}/common/index${VERSION_INDICATOR}${segmentWithoutCommon}${fileExtension}`;
282+
} else {
283+
versionedSlug = `${segments[0]}/index${VERSION_INDICATOR}${segments[1]}${fileExtension}`;
284+
}
260285
}
286+
261287
return path.join(pathRoot, versionedSlug);
262288
};
263289

@@ -293,6 +319,7 @@ export async function getFileBySlug(slug: string) {
293319
commonFilePath = path.join(commonPath, slugParts.slice(5).join('/'));
294320
} else if (slugParts.length >= 3 && slugParts[1] === 'platforms') {
295321
commonFilePath = path.join(commonPath, slugParts.slice(3).join('/'));
322+
versionedMdxIndexPath = getVersionedIndexPath(root, commonFilePath, '.mdx');
296323
}
297324
if (commonFilePath && fs.existsSync(commonPath)) {
298325
mdxPath = path.join(root, `${commonFilePath}.mdx`);

src/versioning.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@ describe('versioning', () => {
88
expect(getUnversionedPath('/some/path')).toBe('/some/path/');
99
expect(getUnversionedPath('/some/path__v2')).toBe('/some/path/');
1010
expect(getUnversionedPath('/some/path__v2/')).toBe('/some/path/');
11+
expect(getUnversionedPath(['some', 'path__v2'])).toBe('some/path/');
12+
expect(getUnversionedPath(['some', 'path__v2'], false)).toBe('some/path');
1113
});
1214
});

src/versioning.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ export const VERSION_INDICATOR = '__v';
22

33
export const getLocalStorageVersionKey = (platform: string) => `version:${platform}`;
44

5-
export const getUnversionedPath = (path: string) => {
6-
const unversioned = path.split(VERSION_INDICATOR)[0];
7-
return unversioned[unversioned.length - 1] === '/' ? unversioned : `${unversioned}/`;
5+
export const getUnversionedPath = (path: string | string[], trailingSlash = true) => {
6+
const joined = Array.isArray(path) ? path.join('/') : path;
7+
const unversioned = joined.split(VERSION_INDICATOR)[0];
8+
if (trailingSlash) {
9+
return unversioned[unversioned.length - 1] === '/' ? unversioned : `${unversioned}/`;
10+
}
11+
return unversioned;
812
};
13+
14+
export const isVersioned = (path: string) => path.includes(VERSION_INDICATOR);

0 commit comments

Comments
 (0)