From 6f78071cf1e70ab8356bb6f24e83cd3a954af02c Mon Sep 17 00:00:00 2001 From: Flavien David Date: Tue, 13 Aug 2024 13:46:46 +0200 Subject: [PATCH] Add system vault navigation (#6750) * Add system vault navigation * Move DataSourceOrView to type. --- front/components/vaults/VaultSideBarMenu.tsx | 115 ++++++++++++++++-- front/lib/swr.ts | 3 +- .../[category]/data_sources/index.tsx | 78 ++++++++++++ types/src/front/api_handlers/public/vaults.ts | 8 ++ types/src/front/data_source_view.ts | 2 + 5 files changed, 194 insertions(+), 12 deletions(-) create mode 100644 front/pages/w/[wId]/data-sources/vaults/[vaultId]/categories/[category]/data_sources/index.tsx diff --git a/front/components/vaults/VaultSideBarMenu.tsx b/front/components/vaults/VaultSideBarMenu.tsx index 766a94d3732ab..2836a5a0ae5f0 100644 --- a/front/components/vaults/VaultSideBarMenu.tsx +++ b/front/components/vaults/VaultSideBarMenu.tsx @@ -10,6 +10,7 @@ import { Tree, } from "@dust-tt/sparkle"; import type { + DataSourceOrView, DataSourceOrViewInfo, LightWorkspaceType, VaultType, @@ -85,7 +86,7 @@ const renderVaultItems = (vaults: VaultType[], owner: LightWorkspaceType) => vaults.map((vault) => ( {vault.kind === "system" ? ( - + ) : ( )} @@ -122,9 +123,102 @@ const SubItemIconItemWrapper = ( // System vault. -const SystemVaultMenu = () => { - // TODO(Groups UI) Implement system vault menu. - return <>; +const SYSTEM_VAULTS_ITEMS = [ + { + label: "Connection Management", + visual: RootItemIconWrapper(CloudArrowLeftRightIcon), + category: "managed", + }, + { + label: "Files", + visual: RootItemIconWrapper(FolderIcon), + category: "files", + }, + // TODO(GROUPS_UI) Add support for Dust apps. +]; + +const SystemVaultMenu = ({ + owner, + vault, +}: { + owner: LightWorkspaceType; + vault: VaultType; +}) => { + return ( + <> + {SYSTEM_VAULTS_ITEMS.map((item) => ( + + ))} + + ); +}; + +const SystemVaultItem = ({ + category, + label, + owner, + vault, + visual, +}: { + category: string; + label: string; + owner: LightWorkspaceType; + vault: VaultType; + visual: ReactElement; +}) => { + const router = useRouter(); + + const itemPath = `/w/${owner.sId}/data-sources/vaults/${vault.sId}/categories/${category}/data_sources`; + const isAncestorToCurrentPage = router.asPath.includes(itemPath); + + // Unfold the item if it's an ancestor of the current page. + const [isExpanded, setIsExpanded] = useState(isAncestorToCurrentPage); + + const { isVaultDataSourceOrViewsLoading, vaultDataSourceOrViews } = + useVaultDataSourceOrViews({ + workspaceId: owner.sId, + vaultId: vault.sId, + category, + // System vault only has data sources. + type: "data_sources", + disabled: !isExpanded, + }); + + return ( + router.push(itemPath)} + isSelected={router.asPath === itemPath} + onChevronClick={() => setIsExpanded(!isExpanded)} + visual={visual} + size="md" + areActionsFading={false} + > + {isExpanded && ( + + {vaultDataSourceOrViews && + vaultDataSourceOrViews.map((ds) => ( + + ))} + + )} + + ); }; // Global + regular vaults. @@ -191,7 +285,7 @@ const DATA_SOURCE_OR_VIEW_SUB_ITEMS: { icon: ReactElement<{ className?: string; }>; - dataSourceOrView: "data_sources" | "data_source_views"; + dataSourceOrView: DataSourceOrView; }; } = { managed: { @@ -217,13 +311,15 @@ const DATA_SOURCE_OR_VIEW_SUB_ITEMS: { }; const VaultDataSourceOrViewItem = ({ + item, owner, vault, - item, + viewType, }: { + item: DataSourceOrViewInfo; owner: LightWorkspaceType; vault: VaultType; - item: DataSourceOrViewInfo; + viewType: "data_sources" | "data_source_views"; }): ReactElement => { const router = useRouter(); const configuration = item.connectorProvider @@ -232,8 +328,6 @@ const VaultDataSourceOrViewItem = ({ const LogoComponent = configuration?.logoComponent ?? FolderIcon; const label = configuration?.name ?? item.name; - const viewType = - DATA_SOURCE_OR_VIEW_SUB_ITEMS[item.category].dataSourceOrView; const dataSourceOrViewPath = `/w/${owner.sId}/data-sources/vaults/${vault.sId}/categories/${item.category}/${viewType}/${item.sId}`; return ( @@ -293,10 +387,11 @@ const VaultCategoryItem = ({ {vaultDataSourceOrViews && vaultDataSourceOrViews.map((ds) => ( ))} diff --git a/front/lib/swr.ts b/front/lib/swr.ts index c5a89c084abf4..f82a3382d118e 100644 --- a/front/lib/swr.ts +++ b/front/lib/swr.ts @@ -7,6 +7,7 @@ import type { ContentNodesViewType, ConversationMessageReactions, ConversationType, + DataSourceOrView, DataSourceType, GetDataSourceOrViewContentResponseBody, RunRunType, @@ -1341,8 +1342,6 @@ export function useVaultDataSourceOrViews({ }; } -type DataSourceOrView = "data_sources" | "data_source_views"; - export function useVaultDataSourceOrViewContent({ workspaceId, vaultId, diff --git a/front/pages/w/[wId]/data-sources/vaults/[vaultId]/categories/[category]/data_sources/index.tsx b/front/pages/w/[wId]/data-sources/vaults/[vaultId]/categories/[category]/data_sources/index.tsx new file mode 100644 index 0000000000000..30d5910516680 --- /dev/null +++ b/front/pages/w/[wId]/data-sources/vaults/[vaultId]/categories/[category]/data_sources/index.tsx @@ -0,0 +1,78 @@ +import type { + DataSourceOrViewCategory, + UserType, + VaultType, +} from "@dust-tt/types"; +import { isDataSourceOrViewCategory } from "@dust-tt/types"; +import type { InferGetServerSidePropsType } from "next"; +import type { ReactElement } from "react"; + +import type { VaultLayoutProps } from "@app/components/vaults/VaultLayout"; +import { VaultLayout } from "@app/components/vaults/VaultLayout"; +import config from "@app/lib/api/config"; +import { withDefaultUserAuthRequirements } from "@app/lib/iam/session"; +import { VaultResource } from "@app/lib/resources/vault_resource"; + +export const getServerSideProps = withDefaultUserAuthRequirements< + VaultLayoutProps & { + category: DataSourceOrViewCategory; + user: UserType; + vault: VaultType; + } +>(async (context, auth) => { + const owner = auth.getNonNullableWorkspace(); + const user = auth.getNonNullableUser(); + const subscription = auth.subscription(); + + if (!subscription) { + return { + notFound: true, + }; + } + + const vault = await VaultResource.fetchById( + auth, + context.query.vaultId as string + ); + if (!vault) { + return { + notFound: true, + }; + } + + const dataSourceOrViewCategory = context.query.category; + if ( + typeof dataSourceOrViewCategory !== "string" || + !isDataSourceOrViewCategory(dataSourceOrViewCategory) + ) { + return { + notFound: true, + }; + } + + return { + props: { + category: dataSourceOrViewCategory, + gaTrackingId: config.getGaTrackingId(), + owner, + subscription, + user, + vault: vault.toJSON(), + }, + }; +}); + +export default function Vault({ + category, + vault, +}: InferGetServerSidePropsType) { + return ( + <> + Manage {category} connections in vault {vault.name} + + ); +} + +Vault.getLayout = (page: ReactElement, pageProps: any) => { + return {page}; +}; diff --git a/types/src/front/api_handlers/public/vaults.ts b/types/src/front/api_handlers/public/vaults.ts index 0278096c3384e..4353425d43958 100644 --- a/types/src/front/api_handlers/public/vaults.ts +++ b/types/src/front/api_handlers/public/vaults.ts @@ -67,6 +67,14 @@ export const DATA_SOURCE_OR_VIEW_CATEGORIES = [ export type DataSourceOrViewCategory = (typeof DATA_SOURCE_OR_VIEW_CATEGORIES)[number]; +export function isDataSourceOrViewCategory( + category: string +): category is DataSourceOrViewCategory { + return DATA_SOURCE_OR_VIEW_CATEGORIES.includes( + category as DataSourceOrViewCategory + ); +} + export type DataSourceOrViewInfo = { createdAt: number; sId: string; diff --git a/types/src/front/data_source_view.ts b/types/src/front/data_source_view.ts index 8848a1a97bd46..34035e9e188c0 100644 --- a/types/src/front/data_source_view.ts +++ b/types/src/front/data_source_view.ts @@ -4,3 +4,5 @@ export interface DataSourceViewType { sId: string; updatedAt: number; } + +export type DataSourceOrView = "data_sources" | "data_source_views";