diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3.tsx index c0192ddbe9f6..308b59bda119 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3.tsx @@ -72,16 +72,14 @@ import {CompareEvaluationsPage} from './Browse3/pages/CompareEvaluationsPage/Com import {LeaderboardListingPage} from './Browse3/pages/LeaderboardPage/LeaderboardListingPage'; import {LeaderboardPage} from './Browse3/pages/LeaderboardPage/LeaderboardPage'; import {ModsPage} from './Browse3/pages/ModsPage'; -import {ObjectPage} from './Browse3/pages/ObjectPage'; -import {ObjectVersionPage} from './Browse3/pages/ObjectVersionPage'; -import { - ObjectVersionsPage, - WFHighLevelObjectVersionFilter, -} from './Browse3/pages/ObjectVersionsPage'; -import {OpPage} from './Browse3/pages/OpPage'; -import {OpsPage} from './Browse3/pages/OpsPage'; -import {OpVersionPage} from './Browse3/pages/OpVersionPage'; -import {OpVersionsPage} from './Browse3/pages/OpVersionsPage'; +import {ObjectPage} from './Browse3/pages/ObjectsPage/ObjectPage'; +import {WFHighLevelObjectVersionFilter} from './Browse3/pages/ObjectsPage/objectsPageTypes'; +import {ObjectVersionPage} from './Browse3/pages/ObjectsPage/ObjectVersionPage'; +import {ObjectVersionsPage} from './Browse3/pages/ObjectsPage/ObjectVersionsPage'; +import {OpPage} from './Browse3/pages/OpsPage/OpPage'; +import {OpsPage} from './Browse3/pages/OpsPage/OpsPage'; +import {OpVersionPage} from './Browse3/pages/OpsPage/OpVersionPage'; +import {OpVersionsPage} from './Browse3/pages/OpsPage/OpVersionsPage'; import {PlaygroundPage} from './Browse3/pages/PlaygroundPage/PlaygroundPage'; import {ScorersPage} from './Browse3/pages/ScorersPage/ScorersPage'; import {TablePage} from './Browse3/pages/TablePage'; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/context.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/context.tsx index fc522c0a9611..9a1dbc18f50c 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/context.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/context.tsx @@ -16,8 +16,8 @@ import React, { import {useHistory, useLocation} from 'react-router-dom'; import {WFHighLevelCallFilter} from './pages/CallsPage/callsTableFilter'; -import {WFHighLevelObjectVersionFilter} from './pages/ObjectVersionsPage'; -import {WFHighLevelOpVersionFilter} from './pages/OpVersionsPage'; +import {WFHighLevelObjectVersionFilter} from './pages/ObjectsPage/objectsPageTypes'; +import {WFHighLevelOpVersionFilter} from './pages/OpsPage/opsPageTypes'; import {useURLSearchParamsDict} from './pages/util'; import { AWL_ROW_EDGE_NAME, diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/DatasetVersionPage.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/DatasetVersionPage.tsx index 1fd637d20d44..0952c7ccf2ae 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/DatasetVersionPage.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/DatasetVersionPage.tsx @@ -13,8 +13,8 @@ import { ScrollableTabContent, SimplePageLayoutWithHeader, } from '../pages/common/SimplePageLayout'; -import {DeleteObjectButtonWithModal} from '../pages/ObjectVersionPage'; -import {TabUseDataset} from '../pages/TabUseDataset'; +import {DeleteObjectButtonWithModal} from '../pages/ObjectsPage/ObjectDeleteButtons'; +import {TabUseDataset} from '../pages/ObjectsPage/Tabs/TabUseDataset'; import {useWFHooks} from '../pages/wfReactInterface/context'; import {objectVersionKeyToRefUri} from '../pages/wfReactInterface/utilities'; import {ObjectVersionSchema} from '../pages/wfReactInterface/wfDataModelHooksInterface'; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/CallPage.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/CallPage.tsx index 5219f390c810..1afad3e3eaad 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/CallPage.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/CallPage.tsx @@ -36,7 +36,6 @@ import { SimplePageLayoutWithHeader, } from '../common/SimplePageLayout'; import {CompareEvaluationsPageContent} from '../CompareEvaluationsPage/CompareEvaluationsPage'; -import {TabUseCall} from '../TabUseCall'; import {useURLSearchParamsDict} from '../util'; import {useWFHooks} from '../wfReactInterface/context'; import {CallSchema} from '../wfReactInterface/wfDataModelHooksInterface'; @@ -46,6 +45,7 @@ import {CallOverview} from './CallOverview'; import {CallSummary} from './CallSummary'; import {CallTraceView, useCallFlattenedTraceTree} from './CallTraceView'; import {PaginationControls} from './PaginationControls'; +import {TabUseCall} from './TabUseCall'; export const CallPage: FC<{ entity: string; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/TabUseCall.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/TabUseCall.tsx similarity index 93% rename from weave-js/src/components/PagePanelComponents/Home/Browse3/pages/TabUseCall.tsx rename to weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/TabUseCall.tsx index 3041533f98c8..74a9dc0709b9 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/TabUseCall.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/TabUseCall.tsx @@ -2,10 +2,10 @@ import {Box} from '@mui/material'; import * as Tabs from '@wandb/weave/components/Tabs'; import React, {useState} from 'react'; -import {CopyableText} from '../../../../CopyableText'; -import {DocLink} from './common/Links'; -import {TabUseBanner} from './TabUseBanner'; -import {CallSchema} from './wfReactInterface/wfDataModelHooksInterface'; +import {CopyableText} from '../../../../../CopyableText'; +import {DocLink} from '../common/Links'; +import {TabUseBanner} from '../common/TabUseBanner'; +import {CallSchema} from '../wfReactInterface/wfDataModelHooksInterface'; type TabUseCallProps = { call: CallSchema; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/LeaderboardPage/LeaderboardListingPage.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/LeaderboardPage/LeaderboardListingPage.tsx index 6d495ec96292..f78bc28db6ae 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/LeaderboardPage/LeaderboardListingPage.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/LeaderboardPage/LeaderboardListingPage.tsx @@ -9,7 +9,7 @@ import {useWeaveflowRouteContext} from '../../context'; import {Empty} from '../common/Empty'; import {EMPTY_PROPS_LEADERBOARDS} from '../common/EmptyContent'; import {SimplePageLayout} from '../common/SimplePageLayout'; -import {ObjectVersionsTable} from '../ObjectVersionsPage'; +import {ObjectVersionsTable} from '../ObjectsPage/ObjectVersionsTable'; import { useBaseObjectInstances, useCreateBuiltinObjectInstance, diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ObjectsPage/ObjectDeleteButtons.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ObjectsPage/ObjectDeleteButtons.tsx new file mode 100644 index 000000000000..7d9aaf4cee99 --- /dev/null +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ObjectsPage/ObjectDeleteButtons.tsx @@ -0,0 +1,85 @@ +import {Button} from '@wandb/weave/components/Button'; +import {maybePluralizeWord} from '@wandb/weave/core/util/string'; +import React, {useState} from 'react'; + +import {useClosePeek} from '../../context'; +import {DeleteModal} from '../common/DeleteModal'; +import {useWFHooks} from '../wfReactInterface/context'; +import {ObjectVersionSchema} from '../wfReactInterface/wfDataModelHooksInterface'; + +export const DeleteObjectButtonWithModal: React.FC<{ + objVersionSchema: ObjectVersionSchema; + overrideDisplayStr?: string; +}> = ({objVersionSchema, overrideDisplayStr}) => { + const {useObjectDeleteFunc} = useWFHooks(); + const closePeek = useClosePeek(); + const {objectVersionsDelete} = useObjectDeleteFunc(); + const [deleteModalOpen, setDeleteModalOpen] = useState(false); + + const deleteStr = + overrideDisplayStr ?? + `${objVersionSchema.objectId}:v${objVersionSchema.versionIndex}`; + + return ( + <> + + ) : undefined; + const deleteButton = showDeleteButton ? ( + setSelectedVersions([])} + /> + ) : undefined; + + return ( + +
+ {compareButton} + {deleteButton} +
+
+ ); +}; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ObjectVersionsPage.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ObjectsPage/ObjectVersionsTable.tsx similarity index 69% rename from weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ObjectVersionsPage.tsx rename to weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ObjectsPage/ObjectVersionsTable.tsx index e9fee11f3e27..abced38a357f 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ObjectVersionsPage.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ObjectsPage/ObjectVersionsTable.tsx @@ -1,42 +1,22 @@ -/** - * This page is the list-view for object versions. When a single object is selected, it - * becomes a rich table of versions. It is likely that we will want to outfit it - * with features similar to the calls table. For example: - * [ ] Add the ability to expand refs - * [ ] Paginate & stream responses similar to calls - * [ ] Add the ability to sort / filter on values - * [ ] Add the ability to sort / filter on expanded values (blocked by general support for expansion operations) - * [ ] Add sort / filter state to URL - */ - import { GridColDef, GridColumnGroupingModel, GridRowSelectionModel, GridRowsProp, } from '@mui/x-data-grid-pro'; -import {useViewerInfo} from '@wandb/weave/common/hooks/useViewerInfo'; import {Checkbox} from '@wandb/weave/components/Checkbox'; -import {Tailwind} from '@wandb/weave/components/Tailwind'; -import {maybePluralizeWord} from '@wandb/weave/core/util/string'; import _ from 'lodash'; import React, {useEffect, useMemo, useState} from 'react'; -import {useHistory} from 'react-router-dom'; -import {TEAL_600} from '../../../../../common/css/color.styles'; -import {Button} from '../../../../Button'; -import {ErrorPanel} from '../../../../ErrorPanel'; -import {Loading} from '../../../../Loading'; -import {LoadingDots} from '../../../../LoadingDots'; -import {Timestamp} from '../../../../Timestamp'; -import { - useWeaveflowCurrentRouteContext, - useWeaveflowRouteContext, -} from '../context'; -import {StyledDataGrid} from '../StyledDataGrid'; -import {basicField} from './common/DataTable'; -import {DeleteModal} from './common/DeleteModal'; -import {Empty} from './common/Empty'; +import {TEAL_600} from '../../../../../../common/css/color.styles'; +import {ErrorPanel} from '../../../../../ErrorPanel'; +import {Loading} from '../../../../../Loading'; +import {LoadingDots} from '../../../../../LoadingDots'; +import {Timestamp} from '../../../../../Timestamp'; +import {useWeaveflowRouteContext} from '../../context'; +import {StyledDataGrid} from '../../StyledDataGrid'; +import {basicField} from '../common/DataTable'; +import {Empty} from '../common/Empty'; import { EMPTY_PROPS_ACTION_SPECS, EMPTY_PROPS_ANNOTATIONS, @@ -46,258 +26,35 @@ import { EMPTY_PROPS_OBJECTS, EMPTY_PROPS_PROGRAMMATIC_SCORERS, EMPTY_PROPS_PROMPTS, -} from './common/EmptyContent'; +} from '../common/EmptyContent'; import { CustomLink, ObjectVersionLink, ObjectVersionsLink, objectVersionText, -} from './common/Links'; -import {FilterLayoutTemplate} from './common/SimpleFilterableDataTable'; -import {SimplePageLayout} from './common/SimplePageLayout'; +} from '../common/Links'; +import {FilterLayoutTemplate} from '../common/SimpleFilterableDataTable'; import { buildDynamicColumns, prepareFlattenedDataForTable, -} from './common/tabularListViews/columnBuilder'; -import {TypeVersionCategoryChip} from './common/TypeVersionCategoryChip'; -import {useControllableState, useURLSearchParamsDict} from './util'; +} from '../common/tabularListViews/columnBuilder'; +import {TypeVersionCategoryChip} from '../common/TypeVersionCategoryChip'; +import {useURLSearchParamsDict} from '../util'; import { KNOWN_BASE_OBJECT_CLASSES, OBJECT_ATTR_EDGE_NAME, -} from './wfReactInterface/constants'; -import {useWFHooks} from './wfReactInterface/context'; +} from '../wfReactInterface/constants'; +import {useWFHooks} from '../wfReactInterface/context'; import { isTableRef, makeRefExpandedPayload, -} from './wfReactInterface/tsDataModelHooksCallRefExpansion'; -import {objectVersionKeyToRefUri} from './wfReactInterface/utilities'; -import { - KnownBaseObjectClassType, - ObjectVersionSchema, -} from './wfReactInterface/wfDataModelHooksInterface'; +} from '../wfReactInterface/tsDataModelHooksCallRefExpansion'; +import {objectVersionKeyToRefUri} from '../wfReactInterface/utilities'; +import {ObjectVersionSchema} from '../wfReactInterface/wfDataModelHooksInterface'; +import {WFHighLevelObjectVersionFilter} from './objectsPageTypes'; const DATASET_BASE_OBJECT_CLASS = 'Dataset'; -export const ObjectVersionsPage: React.FC<{ - entity: string; - project: string; - initialFilter?: WFHighLevelObjectVersionFilter; - // Setting this will make the component a controlled component. The parent - // is responsible for updating the filter. - onFilterUpdate?: (filter: WFHighLevelObjectVersionFilter) => void; -}> = props => { - const history = useHistory(); - const {loading: loadingUserInfo, userInfo} = useViewerInfo(); - const router = useWeaveflowCurrentRouteContext(); - const [filter, setFilter] = useControllableState( - props.initialFilter ?? {}, - props.onFilterUpdate - ); - const {entity, project} = props; - const [selectedVersions, setSelectedVersions] = useState([]); - const onCompare = () => { - history.push(router.compareObjectsUri(entity, project, selectedVersions)); - }; - - const title = useMemo(() => { - if (filter.objectName) { - return 'Versions of ' + filter.objectName; - } else if (filter.baseObjectClass) { - return _.capitalize(filter.baseObjectClass) + 's'; - } - return 'All Objects'; - }, [filter.objectName, filter.baseObjectClass]); - - if (loadingUserInfo) { - return ; - } - - const filteredOnObject = filter.objectName != null; - const hasComparison = filteredOnObject; - const viewer = userInfo ? userInfo.id : null; - const isReadonly = !viewer || !userInfo?.teams.includes(props.entity); - const isAdmin = userInfo?.admin; - const showDeleteButton = filteredOnObject && !isReadonly && isAdmin; - - return ( - - } - tabs={[ - { - label: '', - content: ( - - ), - }, - ]} - /> - ); -}; - -const ObjectVersionsPageHeaderExtra: React.FC<{ - entity: string; - project: string; - objectName: string | null; - selectedVersions: string[]; - setSelectedVersions: (selected: string[]) => void; - showDeleteButton?: boolean; - showCompareButton?: boolean; - onCompare: () => void; -}> = ({ - entity, - project, - objectName, - selectedVersions, - setSelectedVersions, - showDeleteButton, - showCompareButton, - onCompare, -}) => { - const compareButton = showCompareButton ? ( - - ) : undefined; - const deleteButton = showDeleteButton ? ( - setSelectedVersions([])} - /> - ) : undefined; - - return ( - -
- {compareButton} - {deleteButton} -
-
- ); -}; - -export type WFHighLevelObjectVersionFilter = { - objectName?: string | null; - baseObjectClass?: KnownBaseObjectClassType | null; -}; - -export const FilterableObjectVersionsTable: React.FC<{ - entity: string; - project: string; - frozenFilter?: WFHighLevelObjectVersionFilter; - initialFilter?: WFHighLevelObjectVersionFilter; - objectTitle?: string; - hideCategoryColumn?: boolean; - hideCreatedAtColumn?: boolean; - // Setting this will make the component a controlled component. The parent - // is responsible for updating the filter. - onFilterUpdate?: (filter: WFHighLevelObjectVersionFilter) => void; - selectedVersions?: string[]; - setSelectedVersions?: (selected: string[]) => void; -}> = props => { - const {useRootObjectVersions} = useWFHooks(); - const {baseRouter} = useWeaveflowRouteContext(); - - const effectiveFilter = useMemo(() => { - return {...props.initialFilter, ...props.frozenFilter}; - }, [props.initialFilter, props.frozenFilter]); - - const effectivelyLatestOnly = !effectiveFilter.objectName; - - const filteredObjectVersions = useRootObjectVersions( - props.entity, - props.project, - { - baseObjectClasses: effectiveFilter.baseObjectClass - ? [effectiveFilter.baseObjectClass] - : undefined, - objectIds: effectiveFilter.objectName - ? [effectiveFilter.objectName] - : undefined, - latestOnly: effectivelyLatestOnly, - }, - undefined, - effectivelyLatestOnly // metadata only when getting latest - ); - - if (filteredObjectVersions.loading) { - return ; - } - if (filteredObjectVersions.error) { - return ; - } - - // TODO: Only show the empty state if no filters other than baseObjectClass - const objectVersions = filteredObjectVersions.result ?? []; - const isEmpty = objectVersions.length === 0; - if (isEmpty) { - let propsEmpty = EMPTY_PROPS_OBJECTS; - const base = props.initialFilter?.baseObjectClass; - if ('Prompt' === base) { - propsEmpty = EMPTY_PROPS_PROMPTS; - } else if ('Model' === base) { - propsEmpty = EMPTY_PROPS_MODEL; - } else if (DATASET_BASE_OBJECT_CLASS === base) { - propsEmpty = EMPTY_PROPS_DATASETS; - } else if (base === 'Leaderboard') { - propsEmpty = EMPTY_PROPS_LEADERBOARDS; - } else if (base === 'Scorer') { - propsEmpty = EMPTY_PROPS_PROGRAMMATIC_SCORERS; - } else if (base === 'ActionSpec') { - propsEmpty = EMPTY_PROPS_ACTION_SPECS; - } else if (base === 'AnnotationSpec') { - propsEmpty = EMPTY_PROPS_ANNOTATIONS; - } - return ; - } - - return ( - 0} - showPopoutButton={Object.keys(props.frozenFilter ?? {}).length > 0} - filterPopoutTargetUrl={baseRouter.objectVersionsUIUrl( - props.entity, - props.project, - effectiveFilter - )}> - - - ); -}; - export const ObjectVersionsTable: React.FC<{ objectVersions: ObjectVersionSchema[]; objectTitle?: string; @@ -594,6 +351,99 @@ export const ObjectVersionsTable: React.FC<{ ); }; +export const FilterableObjectVersionsTable: React.FC<{ + entity: string; + project: string; + frozenFilter?: WFHighLevelObjectVersionFilter; + initialFilter?: WFHighLevelObjectVersionFilter; + objectTitle?: string; + hideCategoryColumn?: boolean; + hideCreatedAtColumn?: boolean; + // Setting this will make the component a controlled component. The parent + // is responsible for updating the filter. + onFilterUpdate?: (filter: WFHighLevelObjectVersionFilter) => void; + selectedVersions?: string[]; + setSelectedVersions?: (selected: string[]) => void; +}> = props => { + const {useRootObjectVersions} = useWFHooks(); + const {baseRouter} = useWeaveflowRouteContext(); + + const effectiveFilter = useMemo(() => { + return {...props.initialFilter, ...props.frozenFilter}; + }, [props.initialFilter, props.frozenFilter]); + + const effectivelyLatestOnly = !effectiveFilter.objectName; + + const filteredObjectVersions = useRootObjectVersions( + props.entity, + props.project, + { + baseObjectClasses: effectiveFilter.baseObjectClass + ? [effectiveFilter.baseObjectClass] + : undefined, + objectIds: effectiveFilter.objectName + ? [effectiveFilter.objectName] + : undefined, + latestOnly: effectivelyLatestOnly, + }, + undefined, + effectivelyLatestOnly // metadata only when getting latest + ); + + if (filteredObjectVersions.loading) { + return ; + } + if (filteredObjectVersions.error) { + return ; + } + + // TODO: Only show the empty state if no filters other than baseObjectClass + const objectVersions = filteredObjectVersions.result ?? []; + const isEmpty = objectVersions.length === 0; + if (isEmpty) { + let propsEmpty = EMPTY_PROPS_OBJECTS; + const base = props.initialFilter?.baseObjectClass; + if ('Prompt' === base) { + propsEmpty = EMPTY_PROPS_PROMPTS; + } else if ('Model' === base) { + propsEmpty = EMPTY_PROPS_MODEL; + } else if (DATASET_BASE_OBJECT_CLASS === base) { + propsEmpty = EMPTY_PROPS_DATASETS; + } else if (base === 'Leaderboard') { + propsEmpty = EMPTY_PROPS_LEADERBOARDS; + } else if (base === 'Scorer') { + propsEmpty = EMPTY_PROPS_PROGRAMMATIC_SCORERS; + } else if (base === 'ActionSpec') { + propsEmpty = EMPTY_PROPS_ACTION_SPECS; + } else if (base === 'AnnotationSpec') { + propsEmpty = EMPTY_PROPS_ANNOTATIONS; + } + return ; + } + + return ( + 0} + showPopoutButton={Object.keys(props.frozenFilter ?? {}).length > 0} + filterPopoutTargetUrl={baseRouter.objectVersionsUIUrl( + props.entity, + props.project, + effectiveFilter + )}> + + + ); +}; + const PeerVersionsLink: React.FC<{obj: ObjectVersionSchema}> = props => { const {useRootObjectVersions} = useWFHooks(); @@ -630,42 +480,3 @@ const PeerVersionsLink: React.FC<{obj: ObjectVersionSchema}> = props => { /> ); }; - -const DeleteObjectVersionsButtonWithModal: React.FC<{ - entity: string; - project: string; - objectName: string; - objectVersions: string[]; - disabled?: boolean; - onSuccess: () => void; -}> = ({entity, project, objectName, objectVersions, disabled, onSuccess}) => { - const {useObjectDeleteFunc} = useWFHooks(); - const {objectVersionsDelete} = useObjectDeleteFunc(); - const [deleteModalOpen, setDeleteModalOpen] = useState(false); - - const numObjects = objectVersions.length; - const versionsStr = maybePluralizeWord(numObjects, 'version', 's'); - const objectDigests = objectVersions.map(v => v.split(':')[1]); - const deleteTitleStr = `${numObjects} ${objectName} ${versionsStr}`; - - return ( - <> -