Skip to content

Commit

Permalink
DOP-4613: Fix breadcrumb links (#1091)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmeigs authored May 13, 2024
1 parent daf26a7 commit 2dedee6
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 65 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions src/components/Breadcrumbs/BreadcrumbContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import styled from '@emotion/styled';
import { reportAnalytics } from '../../utils/report-analytics';
import { theme } from '../../theme/docsTheme';
import { getFullBreadcrumbPath } from '../../utils/get-complete-breadcrumb-data';
import IndividualBreadcrumb from './IndividualBreadcrumb';
import CollapsedBreadcrumbs from './CollapsedBreadcrumbs';

Expand Down Expand Up @@ -67,7 +68,7 @@ const BreadcrumbContainer = ({ breadcrumbs }) => {
setIsExcessivelyTruncated={collapseBreadcrumbs}
onClick={() =>
reportAnalytics('BreadcrumbClick', {
breadcrumbClicked: crumb.url,
breadcrumbClicked: getFullBreadcrumbPath(crumb.path, true),
})
}
></IndividualBreadcrumb>
Expand All @@ -81,7 +82,7 @@ const BreadcrumbContainer = ({ breadcrumbs }) => {

const crumbObjectShape = {
title: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
};

BreadcrumbContainer.propTypes = {
Expand Down
25 changes: 19 additions & 6 deletions src/components/Breadcrumbs/CollapsedBreadcrumbs.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,27 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Menu, MenuItem } from '@leafygreen-ui/menu';
import IconButton from '@leafygreen-ui/icon-button';
import { withPrefix } from 'gatsby';
import { useLocation } from '@gatsbyjs/reach-router';
import Icon from '@leafygreen-ui/icon';
import { formatText } from '../../utils/format-text';
import { isGatsbyPreview } from '../../utils/is-gatsby-preview';
import { getGatsbyPreviewLink } from '../../utils/get-gatsby-preview-link';

const CollapsedBreadcrumbs = ({ crumbs }) => {
const location = useLocation();

const menuItems = crumbs.map((crumb, index) => {
let to = withPrefix(crumb.path);
if (isGatsbyPreview()) to = getGatsbyPreviewLink(to, location);

return (
<MenuItem key={index} href={to}>
{formatText(crumb.title)}
</MenuItem>
);
});

return (
<React.Fragment>
<Menu
Expand All @@ -17,19 +34,15 @@ const CollapsedBreadcrumbs = ({ crumbs }) => {
</IconButton>
}
>
{crumbs.map((crumb, index) => (
<MenuItem key={index} href={crumb.url}>
{formatText(crumb.title)}
</MenuItem>
))}
{menuItems}
</Menu>
</React.Fragment>
);
};

const crumbObjectShape = {
title: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
};

CollapsedBreadcrumbs.propTypes = {
Expand Down
5 changes: 3 additions & 2 deletions src/components/Breadcrumbs/IndividualBreadcrumb.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { theme } from '../../theme/docsTheme';
const linkStyling = LeafyCss`
font-size: ${theme.fontSize.small};
vertical-align: middle;
line-height: unset;
:hover,
:focus {
Expand Down Expand Up @@ -82,7 +83,7 @@ const IndividualBreadcrumb = ({ crumb, setIsExcessivelyTruncated, onClick }) =>

let result = (
<div className={cx(linkWrapperLayoutStyling, crumb.title.length > 21 ? ellipsisStyling : '')} ref={measuredRef}>
<Link className={cx(linkStyling)} to={crumb.url} onClick={onClick}>
<Link className={cx(linkStyling)} to={crumb.path} onClick={onClick}>
{formatText(crumb.title)}
</Link>
</div>
Expand All @@ -109,7 +110,7 @@ const IndividualBreadcrumb = ({ crumb, setIsExcessivelyTruncated, onClick }) =>

const crumbObjectShape = {
title: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.arrayOf(PropTypes.object)]),
url: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
};

IndividualBreadcrumb.propTypes = {
Expand Down
15 changes: 2 additions & 13 deletions src/components/Link.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { isRelativeUrl } from '../utils/is-relative-url';
import { joinClassNames } from '../utils/join-class-names';
import { isGatsbyPreview } from '../utils/is-gatsby-preview';
import { validateHTMAttributes } from '../utils/validate-element-attributes';
import { getGatsbyPreviewLink } from '../utils/get-gatsby-preview-link';

/*
* Note: This component is not suitable for internal page navigation:
Expand Down Expand Up @@ -86,19 +87,7 @@ const Link = ({
// Ensure trailing slash
to = to.replace(/\/?(\?|#|$)/, '/$1');

if (isGatsbyPreview()) {
// If we're in preview mode, we build the pages of each project and branch of the site within
// its own namespace so each author can preview their own pages e.g.
// /project1/branch1/doc-path
// /project2/branch2/doc-path
//
// So to navigate with the namespaced site, we add to each link the current project and branch
// the user is browsing in.
const projectAndBranchPrefix = `/` + location.pathname.split(`/`).slice(1, 3).join(`/`);
if (!to.startsWith(projectAndBranchPrefix)) {
to = projectAndBranchPrefix + to;
}
}
if (isGatsbyPreview()) to = getGatsbyPreviewLink(to, location);

return (
<GatsbyLink
Expand Down
19 changes: 11 additions & 8 deletions src/components/StructuredData/BreadcrumbSchema.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getCompleteBreadcrumbData } from '../../utils/get-complete-breadcrumb-data.js';
import { assertTrailingSlash } from '../../utils/assert-trailing-slash';
import { getCompleteBreadcrumbData, getFullBreadcrumbPath } from '../../utils/get-complete-breadcrumb-data.js';
import { useBreadcrumbs } from '../../hooks/use-breadcrumbs';
import useSnootyMetadata from '../../utils/use-snooty-metadata';

const getBreadcrumbList = (breadcrumbs) =>
breadcrumbs.map(({ url, title }, index) => ({
'@type': 'ListItem',
position: index + 1,
name: title,
item: assertTrailingSlash(url),
}));
breadcrumbs.map(({ path, title }, index) => {
path = getFullBreadcrumbPath(path, true);

return {
'@type': 'ListItem',
position: index + 1,
name: title,
item: path,
};
});

const BreadcrumbSchema = ({ slug }) => {
const { parentPaths, title: siteTitle } = useSnootyMetadata();
Expand Down
20 changes: 16 additions & 4 deletions src/utils/get-complete-breadcrumb-data.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { withPrefix } from 'gatsby';
import { baseUrl } from './base-url';
import { assertTrailingSlash } from './assert-trailing-slash';
import { removeLeadingSlash } from './remove-leading-slash';
import { assertLeadingSlash } from './assert-leading-slash';
import { isRelativeUrl } from './is-relative-url';

const nodesToString = (titleNodes) => {
if (typeof titleNodes === 'string') {
Expand All @@ -23,23 +25,33 @@ const nodesToString = (titleNodes) => {
.join('');
};

export const getFullBreadcrumbPath = (path, needsPrefix) => {
if (needsPrefix) {
path = withPrefix(path);
}
if (isRelativeUrl(path)) {
path = baseUrl() + removeLeadingSlash(path);
}
return assertTrailingSlash(path);
};

export const getCompleteBreadcrumbData = ({ siteTitle, slug, queriedCrumbs, parentPaths }) => {
//get intermediate breadcrumbs
const intermediateCrumbs = (queriedCrumbs?.breadcrumbs ?? []).map((crumb) => {
return { ...crumb, url: assertTrailingSlash(baseUrl() + removeLeadingSlash(crumb.url)) };
return { ...crumb, path: getFullBreadcrumbPath(crumb.path, false) };
});

const homeCrumb = {
title: 'Docs Home',
url: baseUrl(),
path: baseUrl(),
};

// If site is the property homepage, leave the propertyCrumb blank
let propertyCrumb;
if (slug !== '/') {
propertyCrumb = {
title: nodesToString(siteTitle),
url: '/',
path: '/',
};
}

Expand All @@ -49,7 +61,7 @@ export const getCompleteBreadcrumbData = ({ siteTitle, slug, queriedCrumbs, pare
return {
...crumb,
title: nodesToString(crumb.title),
url: assertLeadingSlash(crumb.path),
path: assertLeadingSlash(crumb.path),
};
});

Expand Down
18 changes: 18 additions & 0 deletions src/utils/get-gatsby-preview-link.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* If we're in preview mode, we build the pages of each project and branch of the site within
* its own namespace so each author can preview their own pages e.g.
* /project1/branch1/doc-path
* /project2/branch2/doc-path
*
* So to navigate with the namespaced site, we add to each link the current project and branch
* the user is browsing in.
*/
const getGatsbyPreviewLink = (to, location) => {
const projectAndBranchPrefix = `/` + location.pathname.split(`/`).slice(1, 3).join(`/`);
if (!to.startsWith(projectAndBranchPrefix)) {
to = projectAndBranchPrefix + to;
}
return to;
};

module.exports = { getGatsbyPreviewLink };
16 changes: 8 additions & 8 deletions tests/unit/BreadcrumbContainer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,30 @@ jest.mock(`../../src/utils/use-snooty-metadata`, () => jest.fn());

const mockIntermediateCrumbs = {
title: 'MongoDB Atlas',
url: 'https://www.mongodb.com/docs/atlas/',
path: 'https://www.mongodb.com/docs/atlas/',
};

const mockPropertyCrumb = {
title: 'MongoDB Atlas Device SDKs',
url: 'https://www.mongodb.com/docs/atlas/device-sdks/',
path: 'https://www.mongodb.com/docs/atlas/device-sdks/',
};

describe('BreadcrumbContainer', () => {
//home breadcrumb
const mockHomeCrumb = {
title: 'Docs Home',
url: 'https://www.mongodb.com/docs/',
path: 'https://www.mongodb.com/docs/',
};

const mockParents = mockData;

it('renders a driver site correctly with intermediate breadcrumb and with project parents', () => {
const breadcrumbs = [
{ title: 'Docs Home', url: 'https://www.mongodb.com/docs/' },
{ title: 'Languages', url: 'https://www.mongodb.com/docs/languages' },
{ title: 'C#/.NET', url: 'https://www.mongodb.com/docs/languages/csharp/' },
{ title: 'C#/.NET Driver', url: 'https://www.mongodb.com/docs/languages/csharp/csharp-driver/' },
{ title: 'Usage Examples', url: 'https://www.mongodb.com/docs/languages/csharp/csharp-driver/usage-examples/' },
{ title: 'Docs Home', path: 'https://www.mongodb.com/docs/' },
{ title: 'Languages', path: 'https://www.mongodb.com/docs/languages' },
{ title: 'C#/.NET', path: 'https://www.mongodb.com/docs/languages/csharp/' },
{ title: 'C#/.NET Driver', path: 'https://www.mongodb.com/docs/languages/csharp/csharp-driver/' },
{ title: 'Usage Examples', path: 'https://www.mongodb.com/docs/languages/csharp/csharp-driver/usage-examples/' },
];

const tree = mountBreadcrumbContainer(breadcrumbs);
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/Breadcrumbs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ beforeAll(() => {
const mockIntermediateCrumbs = [
{
title: 'MongoDB Atlas',
url: '/atlas',
path: '/atlas',
},
];
const useStaticQuery = jest.spyOn(Gatsby, 'useStaticQuery');
Expand Down
Loading

0 comments on commit 2dedee6

Please sign in to comment.