From 2888c1d63472b40944d4c58e67572b73b520532c Mon Sep 17 00:00:00 2001 From: Ben Sherman Date: Fri, 13 Dec 2024 10:35:53 -0800 Subject: [PATCH] chore(dev): dataset editor mvp --- .../Home/Browse3/grid/pagination.ts | 2 +- .../Browse3/pages/CallPage/DataTableView.tsx | 2 +- .../pages/CallPage/EditableDataTableView.tsx | 215 ++++++ .../pages/CallPage/ObjectViewerSection.tsx | 28 +- .../Home/Browse3/pages/EditContext.tsx | 21 + .../Home/Browse3/pages/ObjectVersionPage.tsx | 727 +++++++++++------- .../wfReactInterface/traceServerClient.ts | 10 + .../traceServerClientTypes.ts | 32 + .../traceServerDirectClient.ts | 9 + .../wfReactInterface/tsDataModelHooks.ts | 59 +- .../wfDataModelHooksInterface.ts | 13 +- 11 files changed, 836 insertions(+), 282 deletions(-) create mode 100644 weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/EditableDataTableView.tsx create mode 100644 weave-js/src/components/PagePanelComponents/Home/Browse3/pages/EditContext.tsx diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/grid/pagination.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/grid/pagination.ts index ed9adf9d22a..e8dda623341 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/grid/pagination.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/grid/pagination.ts @@ -1,7 +1,7 @@ import {GridPaginationModel} from '@mui/x-data-grid-pro'; const MAX_PAGE_SIZE = 100; -export const DEFAULT_PAGE_SIZE = 100; +export const DEFAULT_PAGE_SIZE = 25; export const getValidPaginationModel = ( queryPage: string | undefined, diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/DataTableView.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/DataTableView.tsx index 9a954858926..5ca1e3ec733 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/DataTableView.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/DataTableView.tsx @@ -48,7 +48,7 @@ import {TABLE_ID_EDGE_NAME} from '../wfReactInterface/constants'; import {useWFHooks} from '../wfReactInterface/context'; import {SortBy} from '../wfReactInterface/traceServerClientTypes'; -const RowId = styled.span` +export const RowId = styled.span` font-family: 'Inconsolata', monospace; `; RowId.displayName = 'S.RowId'; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/EditableDataTableView.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/EditableDataTableView.tsx new file mode 100644 index 00000000000..577ded1c6e1 --- /dev/null +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/EditableDataTableView.tsx @@ -0,0 +1,215 @@ +import {Box} from '@mui/material'; +import {GridPaginationModel, GridRenderCellParams} from '@mui/x-data-grid-pro'; +import React, {FC, useCallback, useMemo, useState} from 'react'; +import styled from 'styled-components'; + +import {isWeaveObjectRef, parseRefMaybe} from '../../../../../../react'; +import {Tooltip} from '../../../../../Tooltip'; +import {flattenObjectPreservingWeaveTypes} from '../../../Browse2/browse2Util'; +import {DEFAULT_PAGE_SIZE} from '../../grid/pagination'; +import {StyledDataGrid} from '../../StyledDataGrid'; +import {A} from '../common/Links'; +import {useEditContext} from '../EditContext'; // Import the context +import {useWFHooks} from '../wfReactInterface/context'; +import {SortBy} from '../wfReactInterface/traceServerClientTypes'; +import {RowId} from './DataTableView'; // Import shared components + +type DatasetObjectVal = { + _type: 'Dataset'; + name: string | null; + description: string | null; + rows: string; + _class_name: 'Dataset'; + _bases: ['Object', 'BaseModel']; +}; + +interface EditableDataTableViewProps { + datasetObjectId: string; + datasetObject: DatasetObjectVal; + fullHeight?: boolean; +} + +// Add styled component for edited cells +const EditedCellHighlightWrapper = styled(Box)` + .edited-cell { + background-color: rgba(25, 118, 210, 0.1); + border-radius: 8px; + transition: background-color 0.5s ease; + } +`; + +export const EditableDataTableView: FC = props => { + const {useTableRowsQuery, useTableQueryStats} = useWFHooks(); + const [sortBy] = useState([]); + const {editedCellsMap, processRowUpdate, setRowIndices} = useEditContext(); // Use the context + + const [paginationModel, setPaginationModel] = useState({ + page: 0, + pageSize: DEFAULT_PAGE_SIZE, + }); + + // Parse table ref + const parsedRef = useMemo( + () => parseRefMaybe(props.datasetObject.rows), + [props.datasetObject.rows] + ); + const lookupKey = useMemo(() => { + if ( + parsedRef == null || + !isWeaveObjectRef(parsedRef) || + parsedRef.weaveKind !== 'table' + ) { + return null; + } + return { + entity: parsedRef.entityName, + project: parsedRef.projectName, + digest: parsedRef.artifactVersion, + }; + }, [parsedRef]); + + // Fetch row count + const numRowsQuery = useTableQueryStats( + lookupKey?.entity ?? '', + lookupKey?.project ?? '', + lookupKey?.digest ?? '', + {skip: lookupKey == null} + ); + + // Fetch rows + const fetchQuery = useTableRowsQuery( + lookupKey?.entity ?? '', + lookupKey?.project ?? '', + lookupKey?.digest ?? '', + undefined, + paginationModel.pageSize, + paginationModel.page * paginationModel.pageSize, + sortBy, + {skip: lookupKey == null} + ); + + // Convert data to list of dictionaries and flatten nested objects + const dataAsListOfDict = useMemo(() => { + return (fetchQuery.result?.rows ?? []).map(row => { + let val = row; + if (val == null) { + return {}; + } else if (typeof val === 'object' && !Array.isArray(val)) { + if ('val' in val) { + val = val.val; // Extract val field + } + return flattenObjectPreservingWeaveTypes(val); + } + return {'': val}; + }); + }, [fetchQuery.result?.rows]); + + // Reapply edits when rows are fetched + const rows = useMemo(() => { + if (!fetchQuery.loading && fetchQuery.result?.rows) { + return dataAsListOfDict.map((row, i) => { + const digest = fetchQuery.result!.rows[i].digest; + const rowKey = `${digest}`; + const editedRow = editedCellsMap.get(rowKey); + const baseRow = editedRow ? {...row, ...editedRow} : row; + setRowIndices(prev => { + const updatedMap = new Map(prev); + updatedMap.set(rowKey, i); + return updatedMap; + }); + return { + id: digest, + ...baseRow, + }; + }); + } + return []; + }, [ + fetchQuery.loading, + fetchQuery.result, + dataAsListOfDict, + editedCellsMap, + setRowIndices, + ]); + + // Generate columns with cell class names for edited cells + const columns = useMemo(() => { + const firstRow = rows[0] ?? {}; + return Object.keys(firstRow).map(field => { + if (field === 'id') { + return { + field, + headerName: 'id', + width: 50, + editable: false, + filterable: false, + sortable: false, + renderCell: (params: GridRenderCellParams) => { + const id = params.value; + const rowLabel = id ? id.slice(-4) : params.id; + const rowSpan = ( + {rowLabel}} content={id} /> + ); + return {}}>{rowSpan}; + }, + }; + } + return { + field, + headerName: field, + flex: 1, + editable: true, + sortable: false, + filterable: false, + cellClassName: (params: any) => { + const rowKey = `${params.row.id}`; + const editedRow = editedCellsMap.get(rowKey); + return editedRow && editedRow[field] !== undefined + ? 'edited-cell' + : ''; + }, + }; + }); + }, [rows, editedCellsMap]); + + // Handle pagination model change + const handlePaginationModelChange = useCallback( + (newModel: GridPaginationModel) => { + setPaginationModel(newModel); + }, + [] + ); + + return ( +
+ + + +
+ ); +}; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/ObjectViewerSection.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/ObjectViewerSection.tsx index d02a2e881ca..89a8ccb7620 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/ObjectViewerSection.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/ObjectViewerSection.tsx @@ -19,6 +19,7 @@ import {isCustomWeaveTypePayload} from '../../typeViews/customWeaveType.types'; import {CustomWeaveTypeDispatcher} from '../../typeViews/CustomWeaveTypeDispatcher'; import {OBJECT_ATTR_EDGE_NAME} from '../wfReactInterface/constants'; import {WeaveCHTable, WeaveCHTableSourceRefContext} from './DataTableView'; +import {EditableDataTableView} from './EditableDataTableView'; import {ObjectViewer} from './ObjectViewer'; import {getValueType, traverse} from './traverse'; import {ValueView} from './ValueView'; @@ -28,10 +29,12 @@ const EXPANDED_IDS_LENGTH = 200; type Data = Record; type ObjectViewerSectionProps = { + objectId: string; title: string; data: Data; noHide?: boolean; isExpanded?: boolean; + isEditing?: boolean; }; const TitleRow = styled.div` @@ -90,6 +93,7 @@ const ObjectViewerSectionNonEmptyMemoed = React.memo( ); const ObjectViewerSectionNonEmpty = ({ + objectId, title, data, noHide, @@ -229,10 +233,12 @@ const ObjectViewerSectionNonEmpty = ({ }; export const ObjectViewerSection = ({ + objectId, title, data, noHide, isExpanded, + isEditing, }: ObjectViewerSectionProps) => { const currentRef = useContext(WeaveCHTableSourceRefContext); @@ -261,14 +267,13 @@ export const ObjectViewerSection = ({ if (numKeys === 1 && '_result' in data) { let value = data._result; if (isWeaveRef(value)) { - // Little hack to make sure that we render refs - // inside the expansion table view value = {' ': value}; } const valueType = getValueType(value); if (valueType === 'object' || (valueType === 'array' && value.length > 0)) { return ( - + + {title} + + {isEditing ? ( + + ) : ( + + )} ); + if (currentRef != null) { return ( ); }; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/EditContext.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/EditContext.tsx new file mode 100644 index 00000000000..ba4f8ce229c --- /dev/null +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/EditContext.tsx @@ -0,0 +1,21 @@ +import React, {createContext, useContext} from 'react'; + +interface EditContextType { + editedCellsMap: Map; + setEditedCellsMap: React.Dispatch>>; + rowIndices: Map; + setRowIndices: React.Dispatch>>; + processRowUpdate: (newRow: any, oldRow: any) => any; +} + +export const EditContext = createContext( + undefined +); + +export const useEditContext = () => { + const context = useContext(EditContext); + if (!context) { + throw new Error('useEditContext must be used within an EditProvider'); + } + return context; +}; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ObjectVersionPage.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ObjectVersionPage.tsx index 085587a64d8..c4bc3000926 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ObjectVersionPage.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ObjectVersionPage.tsx @@ -1,8 +1,10 @@ +import {Modal, Typography} from '@mui/material'; import Box from '@mui/material/Box'; import {useObjectViewEvent} from '@wandb/weave/integrations/analytics/useViewEvents'; -import React, {useMemo} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {maybePluralizeWord} from '../../../../../core/util/string'; +import {Button} from '../../../../Button'; import {Icon, IconName} from '../../../../Icon'; import {LoadingDots} from '../../../../LoadingDots'; import {Tailwind} from '../../../../Tailwind'; @@ -25,6 +27,7 @@ import { SimpleKeyValueTable, SimplePageLayoutWithHeader, } from './common/SimplePageLayout'; +import {EditContext} from './EditContext'; import {EvaluationLeaderboardTab} from './LeaderboardTab'; import {TabPrompt} from './TabPrompt'; import {TabUseDataset} from './TabUseDataset'; @@ -33,6 +36,11 @@ import {TabUseObject} from './TabUseObject'; import {TabUsePrompt} from './TabUsePrompt'; import {KNOWN_BASE_OBJECT_CLASSES} from './wfReactInterface/constants'; import {useWFHooks} from './wfReactInterface/context'; +import { + TableInsertSpec, + TablePopSpec, + TableUpdateSpec, +} from './wfReactInterface/traceServerClientTypes'; import { objectVersionKeyToRefUri, refUriToOpVersionKey, @@ -108,7 +116,13 @@ const ObjectVersionPageInner: React.FC<{ }> = ({objectVersion}) => { useObjectViewEvent(objectVersion); - const {useRootObjectVersions, useCalls, useRefsData} = useWFHooks(); + const { + useRootObjectVersions, + useCalls, + useRefsData, + useTableUpdate, + useObjCreate, + } = useWFHooks(); const entityName = objectVersion.entity; const projectName = objectVersion.project; const objectName = objectVersion.objectId; @@ -184,294 +198,465 @@ const ObjectVersionPageInner: React.FC<{ if (dataIsPrimitive) { // _result is a special key that is automatically removed by the // ObjectViewerSection component. - return {_result: viewerData}; + return { + _result: viewerData, + }; } return viewerData; }, [viewerData]); const isDataset = baseObjectClass === 'Dataset' && refExtra == null; + const [isEditing, setIsEditing] = useState(false); + const [isPublishModalOpen, setIsPublishModalOpen] = useState(false); + + // Ref to track the previous value of isEditing + const prevIsEditingRef = useRef(isEditing); + + // Effect to open modal when isEditing changes from true to false + useEffect(() => { + if (prevIsEditingRef.current && !isEditing) { + setIsPublishModalOpen(true); + } + prevIsEditingRef.current = isEditing; // Update the ref with the current value + }, [isEditing]); + + const [editedCellsMap, setEditedCellsMap] = useState>( + new Map() + ); + const [editedRows, setEditedRows] = useState>(new Map()); + const [rowIndices, setRowIndices] = useState>(new Map()); + + const handleEditClick = useCallback(() => { + if (isEditing) { + if (editedCellsMap.size > 0) { + setIsPublishModalOpen(true); + } else { + setIsEditing(false); + } + } else { + setIsEditing(true); + } + }, [isEditing, editedCellsMap]); + + const processRowUpdate = useCallback((newRow: any, oldRow: any) => { + const changedField = Object.keys(newRow).find( + key => newRow[key] !== oldRow[key] && key !== 'id' + ); + + if (changedField) { + const rowKey = `${newRow.id}`; + setEditedCellsMap(prev => { + const existingEdits = prev.get(rowKey) || {}; + const updatedMap = new Map(prev); + updatedMap.set(rowKey, { + ...existingEdits, + [changedField]: newRow[changedField], + }); + return updatedMap; + }); + setEditedRows(prev => { + const updatedMap = new Map(prev); + updatedMap.set(rowKey, newRow); + return updatedMap; + }); + } + return newRow; + }, []); + + // Function to convert edited cells to TableUpdateSpec + const convertEditsToTableUpdateSpec = useCallback(() => { + const updates: TableUpdateSpec[] = []; + editedRows.forEach((editedRow, rowKey) => { + const rowIndex = rowIndices.get(rowKey); + if (rowIndex !== undefined) { + const popSpec: TablePopSpec = { + pop: { + index: rowIndex, + }, + }; + const {id: _, ...row} = editedRow; + const insertSpec: TableInsertSpec = { + insert: { + index: rowIndex, + row: row, + }, + }; + updates.push(popSpec); + updates.push(insertSpec); + } + }); + return updates; + }, [editedRows, rowIndices]); + + const tableUpdate = useTableUpdate(); + const objCreate = useObjCreate(); + + const projectId = `${entityName}/${projectName}`; + const originalTableDigest = viewerDataAsObject?.rows?.split('/').pop() ?? ''; + + const handlePublish = useCallback(async () => { + setIsPublishModalOpen(false); + setIsEditing(false); + const tableUpdateSpecs = convertEditsToTableUpdateSpec(); + const tableUpdateResp = await tableUpdate( + projectId, + originalTableDigest, + tableUpdateSpecs + ); + const tableRef = `weave:///${projectId}/table/${tableUpdateResp.digest}`; + await objCreate(projectId, objectName, { + ...objectVersion.val, + rows: tableRef, + }); + }, [ + objectName, + objectVersion.val, + convertEditsToTableUpdateSpec, + projectId, + objCreate, + tableUpdate, + originalTableDigest, + ]); const isEvaluation = baseObjectClass === 'Evaluation' && refExtra == null; const evalHasCalls = (consumingCalls.result?.length ?? 0) > 0; const evalHasCallsLoading = consumingCalls.loading; + const editButtonTitle = isEditing ? 'Finished' : 'Edit'; + const editorToggleIcon = isEditing ? 'checkmark' : 'pencil-edit'; + if (isEvaluation && evalHasCallsLoading) { return ; } return ( - -
- {baseObjectClass && ( - - )} - {objectVersionText(objectName, objectVersionIndex)} -
- - } - headerContent={ - -
-
-

Name

-
- -
- {objectName} - {objectVersions.loading ? ( - - ) : ( - - ({objectVersionCount} version - {objectVersionCount !== 1 ? 's' : ''}) - - )} - + + +
+ {baseObjectClass && ( + + )} + {objectVersionText(objectName, objectVersionIndex)} +
+ + } + headerContent={ + +
+
+
+

Name

+
+ +
+ {objectName} + {objectVersions.loading ? ( + + ) : ( + + ({objectVersionCount} version + {objectVersionCount !== 1 ? 's' : ''}) + + )} + +
+
+
+
+
+

Version

+

{objectVersionIndex}

+
+ {refExtra && ( +
+

Subpath

+

{refExtra}

- + )}
+ {isDataset && ( + + )}
-
-

Version

-

{objectVersionIndex}

-
- {refExtra && ( -
-

Subpath

-

{refExtra}

-
- )} -
- - } - // menuItems={[ - // { - // label: 'Open in Board', - // onClick: () => { - // onMakeBoard(); - // }, - // }, - // { - // label: '(Under Construction) Compare', - // onClick: () => { - // console.log('(Under Construction) Compare'); - // }, - // }, - // { - // label: '(Under Construction) Process with Function', - // onClick: () => { - // console.log('(Under Construction) Process with Function'); - // }, - // }, - // { - // label: '(Coming Soon) Add to Hub', - // onClick: () => { - // console.log('(Under Construction) Add to Hub'); - // }, - // }, - // ]} - tabs={[ - ...(showPromptTab - ? [ - { - label: 'Prompt', - content: ( - - {data.loading ? ( - - ) : ( - - )} - - ), - }, - ] - : []), - ...(isEvaluation && evalHasCalls - ? [ - { - label: 'Leaderboard', - content: ( - - ), - }, - ] - : []), - { - label: isDataset ? 'Rows' : 'Values', - content: ( - - - {data.loading ? ( - - ) : ( - - - - - - )} - - - ), - }, - { - label: 'Use', - content: ( - - - {baseObjectClass === 'Prompt' ? ( - - ) : baseObjectClass === 'Dataset' ? ( - - ) : baseObjectClass === 'Model' ? ( - - ) : ( - - )} - - - ), - }, + + } + // menuItems={[ + // { + // label: 'Open in Board', + // onClick: () => { + // onMakeBoard(); + // }, + // }, + // { + // label: '(Under Construction) Compare', + // onClick: () => { + // console.log('(Under Construction) Compare'); + // }, + // }, + // { + // label: '(Under Construction) Process with Function', + // onClick: () => { + // console.log('(Under Construction) Process with Function'); + // }, + // }, + // { + // label: '(Coming Soon) Add to Hub', + // onClick: () => { + // console.log('(Under Construction) Add to Hub'); + // }, + // }, + // ]} + tabs={[ + ...(showPromptTab + ? [ + { + label: 'Prompt', + content: ( + + {data.loading ? ( + + ) : ( + + )} + + ), + }, + ] + : []), + ...(isEvaluation && evalHasCalls + ? [ + { + label: 'Leaderboard', + content: ( + + ), + }, + ] + : []), + { + label: isDataset ? 'Rows' : 'Values', + content: ( + + + {data.loading ? ( + + ) : ( + + + + + + )} + + + ), + }, + { + label: 'Use', + content: ( + + + {baseObjectClass === 'Prompt' ? ( + + ) : baseObjectClass === 'Dataset' ? ( + + ) : baseObjectClass === 'Model' ? ( + + ) : ( + + )} + + + ), + }, - // { - // label: 'Metadata', - // content: ( - // - // - // ), - // 'Type Version': ( - // <> - // + // { + // label: 'Metadata', + // content: ( + // + // + // ), + // 'Type Version': ( + // <> + // - // - // - // ), - // Ref: fullUri, - // 'Producing Calls': ( - // - // ), - // }} - // /> - // - // ), - // }, - // { - // label: 'Consuming Calls', - // content: ( - // - // ), - // }, - ...(showCallsTab - ? [ - { - label: 'Calls', - content: ( - - 0 - ? { - [maybePluralizeWord( - producingCalls.result!.length, - 'Producing Call' - )]: ( - - ), - } - : {}), - ...(consumingCalls.result!.length - ? { - [maybePluralizeWord( - consumingCalls.result!.length, - 'Consuming Call' - )]: ( - - ), - } - : {}), - }} - /> - - ), - }, - ] - : []), - ]} - /> + // + // + // ), + // Ref: fullUri, + // 'Producing Calls': ( + // + // ), + // }} + // /> + // + // ), + // }, + // { + // label: 'Consuming Calls', + // content: ( + // + // ), + // }, + ...(showCallsTab + ? [ + { + label: 'Calls', + content: ( + + 0 + ? { + [maybePluralizeWord( + producingCalls.result!.length, + 'Producing Call' + )]: ( + + ), + } + : {}), + ...(consumingCalls.result!.length + ? { + [maybePluralizeWord( + consumingCalls.result!.length, + 'Consuming Call' + )]: ( + + ), + } + : {}), + }} + /> + + ), + }, + ] + : []), + ]} + /> + setIsPublishModalOpen(false)} + aria-labelledby="publish-modal-title" + aria-describedby="publish-modal-description"> + + + Confirm Publish + + + Are you sure you want to publish the changes? + + + + + + + + ); }; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClient.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClient.ts index b3a15a8a595..8d32fbe4a71 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClient.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClient.ts @@ -8,6 +8,8 @@ import { FeedbackCreateRes, FeedbackPurgeReq, FeedbackPurgeRes, + TableUpdateReq, + TableUpdateRes, TraceCallsDeleteReq, TraceCallUpdateReq, TraceObjCreateReq, @@ -115,6 +117,14 @@ export class TraceServerClient extends CachingTraceServerClient { return res; } + public tableUpdate(req: TableUpdateReq): Promise { + const res = super.tableUpdate(req).then(updateRes => { + this.onObjectListeners.forEach(listener => listener()); + return updateRes; + }); + return res; + } + public feedbackCreate(req: FeedbackCreateReq): Promise { const res = super.feedbackCreate(req).then(createRes => { const listeners = this.onFeedbackListeners[req.weave_ref] ?? []; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClientTypes.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClientTypes.ts index c396962f0fb..cb129a048ca 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClientTypes.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerClientTypes.ts @@ -347,3 +347,35 @@ export type ActionsExecuteBatchReq = { }; export type ActionsExecuteBatchRes = {}; + +export type TableUpdateSpec = TableAppendSpec | TablePopSpec | TableInsertSpec; + +export interface TableAppendSpec { + append: { + row: Record; + }; +} + +export interface TablePopSpec { + pop: { + index: number; + }; +} + +export interface TableInsertSpec { + insert: { + index: number; + row: Record; + }; +} + +export type TableUpdateReq = { + project_id: string; + base_digest: string; + updates: TableUpdateSpec[]; +}; + +export type TableUpdateRes = { + digest: string; + updated_row_digests: string[]; +}; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerDirectClient.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerDirectClient.ts index e162476920a..22d2ec79781 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerDirectClient.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/traceServerDirectClient.ts @@ -27,6 +27,8 @@ import { FeedbackPurgeRes, FeedbackQueryReq, FeedbackQueryRes, + TableUpdateReq, + TableUpdateRes, TraceCallReadReq, TraceCallReadRes, TraceCallSchema, @@ -253,6 +255,13 @@ export class DirectTraceServerClient { ); } + public tableUpdate(req: TableUpdateReq): Promise { + return this.makeRequest( + '/table/update', + req + ); + } + public tableQuery(req: TraceTableQueryReq): Promise { return this.makeRequest( '/table/query', diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/tsDataModelHooks.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/tsDataModelHooks.ts index 8e2f42035a1..2226ff8c9cf 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/tsDataModelHooks.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/tsDataModelHooks.ts @@ -1713,14 +1713,67 @@ export const traceCallToUICallSchema = ( }; }; +export const useObjCreate = (): (( + project_id: string, + object_id: string, + val: any, + base_object_class?: string +) => Promise) => { + const getTsClient = useGetTraceServerClientContext(); + + return useCallback( + ( + project_id: string, + object_id: string, + val: any, + base_object_class?: string + ) => { + return getTsClient() + .objCreate({ + obj: { + project_id: project_id, + object_id: object_id, + val: val, + set_base_object_class: base_object_class, + }, + }) + .then(res => { + return res.digest; + }); + }, + [getTsClient] + ); +}; + +export const useTableUpdate = (): (( + projectId: string, + digest: string, + updates: traceServerTypes.TableUpdateSpec[] +) => Promise) => { + const getTsClient = useGetTraceServerClientContext(); + + return useCallback( + ( + projectId: string, + baseDigest: string, + updates: traceServerTypes.TableUpdateSpec[] + ) => { + return getTsClient().tableUpdate({ + project_id: projectId, + base_digest: baseDigest, + updates, + }); + }, + [getTsClient] + ); +}; + /// Utility Functions /// export const convertISOToDate = (iso: string): Date => { return new Date(iso); }; -// Export // - export const tsWFDataModelHooks: WFDataModelHooksInterface = { useCall, useCalls, @@ -1728,6 +1781,7 @@ export const tsWFDataModelHooks: WFDataModelHooksInterface = { useCallsDeleteFunc, useCallUpdateFunc, useCallsExport, + useObjCreate, useOpVersion, useOpVersions, useObjectVersion, @@ -1738,6 +1792,7 @@ export const tsWFDataModelHooks: WFDataModelHooksInterface = { useFileContent, useTableRowsQuery, useTableQueryStats, + useTableUpdate, derived: { useChildCallsForCompare, useGetRefsType, diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/wfDataModelHooksInterface.ts b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/wfDataModelHooksInterface.ts index f28c7be6714..42dd2c18c58 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/wfDataModelHooksInterface.ts +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/wfReactInterface/wfDataModelHooksInterface.ts @@ -14,7 +14,7 @@ import {WeaveKind} from '../../../../../../react'; import {KNOWN_BASE_OBJECT_CLASSES, OP_CATEGORIES} from './constants'; import {Query} from './traceServerClientInterface/query'; // TODO: This import is not ideal, should delete this whole interface import * as traceServerClientTypes from './traceServerClientTypes'; // TODO: This import is not ideal, should delete this whole interface -import {ContentType} from './traceServerClientTypes'; +import {ContentType, TableUpdateSpec} from './traceServerClientTypes'; export type OpCategory = (typeof OP_CATEGORIES)[number]; export type KnownBaseObjectClassType = @@ -214,6 +214,12 @@ export type WFDataModelHooksInterface = { expandedRefCols?: string[], includeFeedback?: boolean ) => Promise; + useObjCreate: () => ( + project_id: string, + object_id: string, + val: any, + base_object_class?: string + ) => Promise; useOpVersion: (key: OpVersionKey | null) => Loadable; useOpVersions: ( entity: string, @@ -270,6 +276,11 @@ export type WFDataModelHooksInterface = { key: FeedbackKey | null, sortBy?: traceServerClientTypes.SortBy[] ) => LoadableWithError & Refetchable; + useTableUpdate: () => ( + projectId: string, + baseDigest: string, + updates: traceServerClientTypes.TableUpdateSpec[] + ) => Promise; derived: { useChildCallsForCompare: ( entity: string,