-
Notifications
You must be signed in to change notification settings - Fork 86
2/2 automatically generate and render breadcrumbs in dashboard with Next.js /[tenantId]/@breadcrumbs/[...slug]/page.tsx parallel route
#1376
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dimaMachina
wants to merge
15
commits into
dimaMachina-patch-5
Choose a base branch
from
prd-5641
base: dimaMachina-patch-5
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
f56a5f5
breadcrumbs
dimaMachina 53130c7
breadcrumbs
dimaMachina 5b7e7bc
breadcrumbs
dimaMachina 44e9f44
breadcrumbs
dimaMachina 1159702
breadcrumbs
dimaMachina 53cc028
breadcrumbs
dimaMachina de1e7d2
breadcrumbs
dimaMachina db932bd
breadcrumbs
dimaMachina 7bc5f11
breadcrumbs
dimaMachina cc5008a
breadcrumbs
dimaMachina 00c07ed
breadcrumbs
dimaMachina f10704f
breadcrumbs
dimaMachina f8ac401
upd
dimaMachina 44b1e02
Merge branch 'dimaMachina-patch-5' into prd-5641
dimaMachina cc43fe8
changeset
dimaMachina File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@inkeep/agents-manage-ui": minor | ||
| --- | ||
|
|
||
| automatically generate and render breadcrumbs in dashboard with Next.js `/[tenantId]/@breadcrumbs/[...slug]/page.tsx` parallel route |
141 changes: 141 additions & 0 deletions
141
agents-manage-ui/src/app/[tenantId]/@breadcrumbs/[...slug]/page.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| import Link from 'next/link'; | ||
| import type { FC } from 'react'; | ||
| import { STATIC_LABELS } from '@/constants/theme'; | ||
| import { getFullAgentAction } from '@/lib/actions/agent-full'; | ||
| import { fetchArtifactComponent } from '@/lib/api/artifact-components'; | ||
| import { fetchCredential } from '@/lib/api/credentials'; | ||
| import { fetchDataComponent } from '@/lib/api/data-components'; | ||
| import { fetchExternalAgent } from '@/lib/api/external-agents'; | ||
| import { fetchProject } from '@/lib/api/projects'; | ||
| import { fetchMCPTool } from '@/lib/api/tools'; | ||
| import { fetchNangoProviders } from '@/lib/mcp-tools/nango'; | ||
| import { getErrorCode, getStatusCodeFromErrorCode } from '@/lib/utils/error-serialization'; | ||
|
|
||
| type LabelKey = keyof typeof STATIC_LABELS; | ||
|
|
||
| type FetcherRecord = Record<LabelKey, (id: string) => Promise<string | undefined>>; | ||
|
|
||
| interface BreadcrumbItem { | ||
| href: string; | ||
| label: string; | ||
| } | ||
|
|
||
| function getStaticLabel(segment: string) { | ||
| return segment in STATIC_LABELS ? STATIC_LABELS[segment as LabelKey] : undefined; | ||
| } | ||
|
|
||
| const BreadcrumbSlot: FC<PageProps<'/[tenantId]/[...slug]'>> = async ({ params }) => { | ||
| const { tenantId, slug } = await params; | ||
| const crumbs: BreadcrumbItem[] = []; | ||
| let href = `/${tenantId}`; | ||
| let projectId = ''; | ||
|
|
||
| const fetchers: Partial<FetcherRecord> = { | ||
| async projects(id) { | ||
| projectId = id; | ||
| const project = await fetchProject(tenantId, id); | ||
| return project.data.name; | ||
| }, | ||
| async agents(id) { | ||
| const result = await getFullAgentAction(tenantId, projectId, id); | ||
| if (result.success) { | ||
| return result.data.name; | ||
| } | ||
| throw { | ||
| message: result.error, | ||
| code: result.code, | ||
| }; | ||
| }, | ||
| async artifacts(id) { | ||
| const artifact = await fetchArtifactComponent(tenantId, projectId, id); | ||
| return artifact.name; | ||
| }, | ||
| async components(id) { | ||
| const component = await fetchDataComponent(tenantId, projectId, id); | ||
| return component.name; | ||
| }, | ||
| async credentials(id) { | ||
| const credential = await fetchCredential(tenantId, projectId, id); | ||
| return credential.name; | ||
| }, | ||
| async 'external-agents'(id) { | ||
| const externalAgent = await fetchExternalAgent(tenantId, projectId, id); | ||
| return externalAgent.name; | ||
| }, | ||
| async 'mcp-servers'(id) { | ||
| const tool = await fetchMCPTool(tenantId, projectId, id); | ||
| return tool.name; | ||
| }, | ||
| async providers(id) { | ||
| const providers = await fetchNangoProviders(); | ||
| for (const provider of providers) { | ||
| if (encodeURIComponent(provider.name) === id) { | ||
| return provider.display_name; | ||
| } | ||
| } | ||
| }, | ||
| async conversations() { | ||
| return 'Conversation Details'; | ||
| }, | ||
| }; | ||
|
|
||
| function addCrumb({ segment, label }: { segment: string; label: string }) { | ||
| href += `/${segment}`; | ||
| // This route isn't exist so we don't add it to crumbs list | ||
| if (href !== `/${tenantId}/projects/${projectId}/traces/conversations`) { | ||
| crumbs.push({ label, href }); | ||
| } | ||
| } | ||
|
|
||
| for (const [index, segment] of slug.entries()) { | ||
| let label: string | undefined; | ||
| try { | ||
| const prev = slug[index - 1]; | ||
| // this condition is needed until we remove all `/[segment]/new` routes | ||
| if (segment === 'new') { | ||
| const parentLabel = getStaticLabel(prev); | ||
| label = parentLabel ? `New ${parentLabel.replace(/s$/, '')}` : 'New'; | ||
| } else { | ||
| const fetcher = Object.hasOwn(fetchers, prev) | ||
| ? fetchers[prev as keyof typeof fetchers] | ||
| : undefined; | ||
| label = fetcher ? await fetcher(segment) : getStaticLabel(segment); | ||
| if (!label) { | ||
| throw new Error(`Unknown breadcrumb segment "${segment}"`); | ||
| } | ||
| } | ||
| } catch (error) { | ||
| const errorCode = getErrorCode(error); | ||
| const resolvedStatusCode = getStatusCodeFromErrorCode(errorCode); | ||
| label = resolvedStatusCode ? `${resolvedStatusCode} Error` : 'Error'; | ||
| addCrumb({ segment, label }); | ||
| break; // stop traversing if error occurs in some segment | ||
| } | ||
| addCrumb({ segment, label }); | ||
| } | ||
|
|
||
| return crumbs.map(({ label, href }, idx, arr) => { | ||
| const isLast = idx === arr.length - 1; | ||
| return ( | ||
| <li | ||
| key={href} | ||
| aria-current={isLast ? 'page' : undefined} | ||
| className={ | ||
| isLast | ||
| ? 'font-medium text-foreground' | ||
| : 'after:ml-2 after:content-["/"] after:text-muted-foreground/60' | ||
| } | ||
| > | ||
| {isLast ? ( | ||
| label | ||
| ) : ( | ||
| <Link href={href} className="hover:text-foreground"> | ||
| {label} | ||
| </Link> | ||
| )} | ||
| </li> | ||
| ); | ||
| }); | ||
| }; | ||
|
|
||
| export default BreadcrumbSlot; | ||
10 changes: 10 additions & 0 deletions
10
agents-manage-ui/src/app/[tenantId]/@breadcrumbs/default.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import type { FC } from 'react'; | ||
|
|
||
| // Accessing /[tenantId] triggers: | ||
| // No default component was found for a parallel route rendered on this page. Falling back to nearest NotFound boundary. | ||
| // Missing slots: @breadcrumbs | ||
| const Default: FC = () => { | ||
| return null; | ||
| }; | ||
|
|
||
| export default Default; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,44 @@ | ||
| import type { FC } from 'react'; | ||
| import { HeaderMenus } from '@/components/layout/header-menus'; | ||
| import { AppSidebarProvider } from '@/components/sidebar-nav/app-sidebar-provider'; | ||
| import { SidebarInset } from '@/components/ui/sidebar'; | ||
| import { Separator } from '@/components/ui/separator'; | ||
| import { SidebarInset, SidebarTrigger } from '@/components/ui/sidebar'; | ||
| import { cn } from '@/lib/utils'; | ||
|
|
||
| export default function Layout({ children }: LayoutProps<'/[tenantId]'>) { | ||
| const Layout: FC<LayoutProps<'/[tenantId]'>> = ({ children, breadcrumbs }) => { | ||
| return ( | ||
| <AppSidebarProvider> | ||
| <SidebarInset>{children}</SidebarInset> | ||
| <SidebarInset> | ||
| <div className="h-[calc(100vh-16px)] flex flex-col overflow-hidden"> | ||
| <header className="h-(--header-height) shrink-0 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height) bg-muted/20 dark:bg-background rounded-t-[14px] flex items-center gap-1 px-4 lg:gap-2 lg:px-6"> | ||
| <SidebarTrigger className="-ml-1 text-muted-foreground hover:text-foreground hover:bg-accent dark:text-muted-foreground dark:hover:text-foreground dark:hover:bg-accent/50" /> | ||
| <Separator orientation="vertical" className="mx-2 data-[orientation=vertical]:h-4" /> | ||
| <nav aria-label="Breadcrumb"> | ||
| <ol className="text-sm text-muted-foreground flex items-center gap-2"> | ||
| {breadcrumbs} | ||
| </ol> | ||
| </nav> | ||
| <Separator | ||
| orientation="vertical" | ||
| className="mx-2 data-[orientation=vertical]:h-4 ml-auto" | ||
| /> | ||
| <HeaderMenus /> | ||
| </header> | ||
| <main | ||
| id="main-content" | ||
| className={cn( | ||
| 'flex flex-col flex-1 @container', | ||
| 'overflow-y-auto', | ||
| 'scrollbar-thin scrollbar-track-transparent', | ||
| 'scrollbar-thumb-muted-foreground/30 dark:scrollbar-thumb-muted-foreground/50' | ||
| )} | ||
| > | ||
| <div className="flex-1 p-6 [&:has(>.no-container)]:contents">{children}</div> | ||
| </main> | ||
| </div> | ||
| </SidebarInset> | ||
| </AppSidebarProvider> | ||
| ); | ||
| } | ||
| }; | ||
|
|
||
| export default Layout; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,10 +1,9 @@ | ||||||
| import type { FC } from 'react'; | ||||||
| import { BodyTemplate } from '@/components/layout/body-template'; | ||||||
| import { Skeleton } from '@/components/ui/skeleton'; | ||||||
|
|
||||||
| export const AgentSkeleton: FC = () => { | ||||||
| const AgentLoading: FC = () => { | ||||||
| return ( | ||||||
| <div className="flex p-4"> | ||||||
| <div className="flex p-4 no-container"> | ||||||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
maybe better class name will be |
||||||
| <div className="flex flex-col gap-2" style={{ width: 160 }}> | ||||||
| {Array.from({ length: 5 }).map((_, i) => ( | ||||||
| <Skeleton key={i} style={{ height: 38 }} /> | ||||||
|
|
@@ -21,17 +20,4 @@ export const AgentSkeleton: FC = () => { | |||||
| ); | ||||||
| }; | ||||||
|
|
||||||
| // To avoid have flash of skeleton from `[projectId]/loading.tsx` until `agent` is fetched from `getFullAgentAction` in `page.tsx` file | ||||||
| const AgentLoading: FC = () => { | ||||||
| return ( | ||||||
| <BodyTemplate | ||||||
| breadcrumbs={[]} | ||||||
| // Remove inner div from the layout so the p-6 padding doesn’t apply | ||||||
| className="contents" | ||||||
| > | ||||||
| <AgentSkeleton /> | ||||||
| </BodyTemplate> | ||||||
| ); | ||||||
| }; | ||||||
|
|
||||||
| export default AgentLoading; | ||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this comment is no longer relevant, since we'll keep
/newroutes, right @sarah-inkeep ?