Skip to content

Commit

Permalink
Alternative implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
onurtemizkan committed Nov 28, 2024
1 parent 73fbd62 commit 1d42620
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 47 deletions.
138 changes: 92 additions & 46 deletions packages/react/src/reactrouterv6.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export function reactRouterV6BrowserTracingIntegration(
integration.afterAllSetup(client);

const initPathName = WINDOW && WINDOW.location && WINDOW.location.pathname;

if (instrumentPageLoad && initPathName) {
startBrowserTracingPageLoadSpan(client, {
name: initPathName,
Expand Down Expand Up @@ -158,16 +159,16 @@ function sendIndexPath(pathBuilder: string, pathname: string, basename: string):
return [formattedPath, 'route'];
}

function pathEndsWithWildcard(path: string): boolean {
return path.endsWith('*');
function matchHasWildcard(match: RouteMatch<string>): boolean {
return !!match.params['*'];
}

function pathIsWildcardAndHasChildren(path: string, branch: RouteMatch<string>): boolean {
return (pathEndsWithWildcard(path) && branch.route.children && branch.route.children.length > 0) || false;
function pathEndsWithWildcard(path: string): boolean {
return path.endsWith('*');
}

function pathIsWildcardWithNoChildren(path: string, branch: RouteMatch<string>): boolean {
return (pathEndsWithWildcard(path) && (!branch.route.children || branch.route.children.length === 0)) || false;
function matchIsWildcardAndHasChildren(path: string, match: RouteMatch<string>): boolean {
return (matchHasWildcard(match) && match.route.children && match.route.children.length > 0) || false;
}

function getNormalizedName(
Expand All @@ -178,24 +179,31 @@ function getNormalizedName(
allRoutes: RouteObject[] = routes,
): [string, TransactionSource] {
if (!routes || routes.length === 0) {
debugger;
return [_stripBasename ? stripBasenameFromPathname(location.pathname, basename) : location.pathname, 'url'];
}

const matchedRoutes = _matchRoutes(routes, location);
const matchedRoutes = _matchRoutes(allRoutes, location, basename);

if (matchedRoutes) {
const wildCardRoutes: RouteMatch[] = matchedRoutes.filter(
(match: RouteMatch) => match.route.path && pathIsWildcardWithNoChildren(match.route.path, match),
);
const wildCardRoutes: RouteMatch[] = matchedRoutes.filter((match: RouteMatch) => {
return matchHasWildcard(match);
});

for (const wildCardRoute of wildCardRoutes) {
const wildCardRouteMatch = _matchRoutes(allRoutes, location, wildCardRoute.pathnameBase);

if (wildCardRouteMatch) {
const [name, source] = getNormalizedName(wildCardRoutes, location, wildCardRouteMatch, basename, allRoutes);
const [name, source] = getNormalizedName(
wildCardRoutes,
location,
wildCardRouteMatch,
wildCardRoute.pathnameBase,
allRoutes,
);

if (wildCardRoute.pathnameBase && name) {
return [wildCardRoute.pathnameBase + name, source];
return [basename + wildCardRoute.pathnameBase + name, source];
}
}
}
Expand All @@ -208,12 +216,13 @@ function getNormalizedName(
if (route) {
// Early return if index route
if (route.index) {
debugger;
return sendIndexPath(pathBuilder, branch.pathname, basename);
}
const path = route.path;

// If path is not a wildcard and has no child routes, append the path
if (path && !pathIsWildcardAndHasChildren(path, branch)) {
if (path && !matchIsWildcardAndHasChildren(path, branch)) {
const newPath = path[0] === '/' || pathBuilder[pathBuilder.length - 1] === '/' ? path : `/${path}`;
pathBuilder += newPath;

Expand All @@ -236,7 +245,7 @@ function getNormalizedName(
}

// if the last character of the pathbuilder is a wildcard and there are children, remove the wildcard
if (pathIsWildcardAndHasChildren(pathBuilder, branch)) {
if (matchIsWildcardAndHasChildren(pathBuilder, branch)) {
pathBuilder = pathBuilder.slice(0, -1);
}

Expand All @@ -247,23 +256,27 @@ function getNormalizedName(
}
}

debugger;
return [_stripBasename ? stripBasenameFromPathname(location.pathname, basename) : location.pathname, 'url'];
}

function updatePageloadTransaction(
activeRootSpan: Span | undefined,
location: Location,
routes: RouteObject[],
matches?: AgnosticDataRouteMatch,
basename?: string,
allRoutes?: RouteObject[],
): void {
function updatePageloadTransaction(options: {
activeRootSpan: Span | undefined;
location: Location;
routes: RouteObject[];
matches?: AgnosticDataRouteMatch;
basename?: string;
allRoutes?: RouteObject[];
}): void {
debugger;
const { activeRootSpan, location, routes, matches, basename, allRoutes } = options;

const branches = Array.isArray(matches)
? matches
: (_matchRoutes(routes, location, basename) as unknown as RouteMatch[]);
: (_matchRoutes(allRoutes, location, basename) as unknown as RouteMatch[]);

if (branches) {
const [name, source] = getNormalizedName(routes, location, branches, basename, allRoutes);
const [name, source] = getNormalizedName(allRoutes || routes, location, branches, basename, allRoutes);

getCurrentScope().setTransactionName(name);

Expand All @@ -274,14 +287,16 @@ function updatePageloadTransaction(
}
}

function handleNavigation(
location: Location,
routes: RouteObject[],
navigationType: Action,
matches?: AgnosticDataRouteMatch,
basename?: string,
allRoutes?: RouteObject[],
): void {
function handleNavigation(options: {
location: Location;
routes: RouteObject[];
navigationType: Action;
matches?: AgnosticDataRouteMatch;
basename?: string;
allRoutes?: RouteObject[];
}): void {
const { location, routes, navigationType, matches, basename, allRoutes } = options;

const branches = Array.isArray(matches) ? matches : _matchRoutes(routes, location, basename);

const client = getClient();
Expand Down Expand Up @@ -339,15 +354,25 @@ export function withSentryReactRouterV6Routing<P extends Record<string, any>, R
() => {
const routes = _createRoutesFromChildren(props.children) as RouteObject[];

routes.forEach(route => {
allRoutes.push(...getChildRoutesRecursively(route));
});

if (isMountRenderPass) {
updatePageloadTransaction(getActiveRootSpan(), location, routes, undefined, undefined, allRoutes);
routes.forEach(route => {
allRoutes.push(...getChildRoutesRecursively(route));
});

updatePageloadTransaction({
activeRootSpan: getActiveRootSpan(),
location,
routes,
allRoutes,
});
isMountRenderPass = false;
} else {
handleNavigation(location, routes, navigationType, undefined, undefined, allRoutes);
handleNavigation({
location,
routes,
navigationType,
allRoutes,
});
}
},
// `props.children` is purposely not included in the dependency array, because we do not want to re-run this effect
Expand Down Expand Up @@ -402,15 +427,26 @@ export function wrapUseRoutes(origUseRoutes: UseRoutes): UseRoutes {
const normalizedLocation =
typeof stableLocationParam === 'string' ? { pathname: stableLocationParam } : stableLocationParam;

routes.forEach(route => {
allRoutes.push(...getChildRoutesRecursively(route));
});

if (isMountRenderPass) {
updatePageloadTransaction(getActiveRootSpan(), normalizedLocation, routes, undefined, undefined, allRoutes);
routes.forEach(route => {
allRoutes.push(...getChildRoutesRecursively(route));
});

updatePageloadTransaction({
activeRootSpan: getActiveRootSpan(),
location: normalizedLocation,
routes,
allRoutes,
});

isMountRenderPass = false;
} else {
handleNavigation(normalizedLocation, routes, navigationType, undefined, undefined, allRoutes);
handleNavigation({
location: normalizedLocation,
routes,
navigationType,
allRoutes,
});
}
}, [navigationType, stableLocationParam]);

Expand Down Expand Up @@ -449,13 +485,23 @@ export function wrapCreateBrowserRouter<
// This is the earliest convenient time to update the transaction name.
// Callbacks to `router.subscribe` are not called for the initial load.
if (router.state.historyAction === 'POP' && activeRootSpan) {
updatePageloadTransaction(activeRootSpan, router.state.location, routes, undefined, basename);
updatePageloadTransaction({
activeRootSpan,
location: router.state.location,
routes,
basename,
});
}

router.subscribe((state: RouterState) => {
const location = state.location;
if (state.historyAction === 'PUSH' || state.historyAction === 'POP') {
handleNavigation(location, routes, state.historyAction, undefined, basename);
handleNavigation({
location,
routes,
navigationType: state.historyAction,
basename,
});
}
});

Expand Down
50 changes: 49 additions & 1 deletion packages/react/test/reactrouterv6.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,55 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
});
});

it('works with descendant wildcard routes', () => {
it('works with descendant wildcard routes - pageload', () => {
const client = createMockBrowserClient();
setCurrentClient(client);

client.addIntegration(
reactRouterV6BrowserTracingIntegration({
useEffect: React.useEffect,
useLocation,
useNavigationType,
createRoutesFromChildren,
matchRoutes,
}),
);
const SentryRoutes = withSentryReactRouterV6Routing(Routes);

const ProjectsRoutes = () => (
<SentryRoutes>
<Route path=":projectId" element={<div>Project Page</div>}>
<Route index element={<div>Project Page Root</div>} />
<Route element={<div>Editor</div>}>
<Route path="*" element={<Outlet />}>
<Route path="views/:viewId" element={<div>View Canvas</div>} />
</Route>
</Route>
</Route>
<Route path="*" element={<div>No Match Page</div>} />
</SentryRoutes>
);

render(
<MemoryRouter initialEntries={['/projects/foo/views/bar']}>
<SentryRoutes>
<Route path="projects/*" element={<ProjectsRoutes />}></Route>
</SentryRoutes>
</MemoryRouter>,
);

expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenCalledTimes(1);
expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), {
name: '/projects/:projectId/views/:viewId',
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6',
},
});
});

it('works with descendant wildcard routes - navigation', () => {
const client = createMockBrowserClient();
setCurrentClient(client);

Expand Down

0 comments on commit 1d42620

Please sign in to comment.