Skip to content
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

feat: object and op deletion backend + basic sdk + basic fe #2319

Draft
wants to merge 78 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
fbb11a8
wip
gtarpenning Sep 5, 2024
83ac645
wip
gtarpenning Sep 5, 2024
021e07f
Merge branch 'master' into griffin/objs-delete
gtarpenning Sep 5, 2024
6c14fff
wip, working??
gtarpenning Sep 5, 2024
52abf53
obj.delete()
gtarpenning Sep 5, 2024
e78dc87
Merge branch 'master' into griffin/objs-delete
gtarpenning Sep 6, 2024
29e2376
add test
gtarpenning Sep 6, 2024
52839eb
wip, now with configurable include_deleted flag
gtarpenning Sep 6, 2024
ff13b45
wip
gtarpenning Sep 6, 2024
99fc486
print
gtarpenning Sep 6, 2024
50c5728
wip
gtarpenning Sep 9, 2024
6cb4446
insert with full data payload
gtarpenning Sep 9, 2024
c8b20aa
retain version #
gtarpenning Sep 9, 2024
4d3194e
query magic
gtarpenning Sep 9, 2024
d553d21
merge
gtarpenning Sep 9, 2024
1fb0080
working with ops
gtarpenning Sep 9, 2024
f5a9c90
wip simple object deletion fe
gtarpenning Sep 10, 2024
07da651
refetch on delete
gtarpenning Sep 10, 2024
7569b84
simpler
gtarpenning Sep 10, 2024
49c559b
add order by
gtarpenning Sep 10, 2024
4182a24
merge
gtarpenning Sep 30, 2024
54ec61e
lint
gtarpenning Sep 30, 2024
18105cc
lint
gtarpenning Sep 30, 2024
92106bc
NotFoundError
gtarpenning Sep 30, 2024
c96b5cd
add back sqlite handling
gtarpenning Sep 30, 2024
04cd07e
undo
gtarpenning Sep 30, 2024
b76724a
cleanup
gtarpenning Oct 1, 2024
c3fe08b
merge
gtarpenning Oct 1, 2024
6a1a037
Merge branch 'master' into griffin/objs-delete
gtarpenning Oct 1, 2024
d381dfd
better error handling
gtarpenning Oct 1, 2024
fd09f90
sort by asc default
gtarpenning Oct 1, 2024
b92153c
merge
gtarpenning Oct 2, 2024
07917cf
add support for purging object values
gtarpenning Oct 2, 2024
41191fd
merge
gtarpenning Oct 6, 2024
4715f41
wip
gtarpenning Oct 7, 2024
17c8cd7
better
gtarpenning Oct 7, 2024
939621f
Merge branch 'master' into griffin/objs-delete
gtarpenning Oct 7, 2024
8bfd8c7
lint
gtarpenning Oct 7, 2024
f0cb066
merge
gtarpenning Oct 11, 2024
d10a9b7
now working with identical objects
gtarpenning Oct 11, 2024
3772edd
limit-1
gtarpenning Oct 14, 2024
72112cf
merge
gtarpenning Oct 30, 2024
d12d667
Merge branch 'master' into griffin/objs-delete
gtarpenning Oct 30, 2024
667ccf5
Merge branch 'master' into griffin/objs-delete
gtarpenning Oct 30, 2024
b6c2a16
use groupby
gtarpenning Oct 30, 2024
1731d8f
refactor delete and add op delete
gtarpenning Oct 30, 2024
3d80f49
object viewer support for deleted refs
gtarpenning Oct 30, 2024
fe1c7d4
better hack
gtarpenning Oct 30, 2024
32b466e
wip recursive
gtarpenning Oct 30, 2024
38c4074
simpler
gtarpenning Nov 7, 2024
226e1e3
merge
gtarpenning Nov 15, 2024
d42d1ec
merge
gtarpenning Nov 26, 2024
c1629fa
fix
gtarpenning Nov 26, 2024
8ae0003
fixes, working
gtarpenning Nov 26, 2024
d3c63f9
costmetic code refactor
gtarpenning Dec 3, 2024
4f1fc99
whitespace
gtarpenning Dec 3, 2024
e433562
Merge branch 'master' into griffin/objs-delete
gtarpenning Dec 3, 2024
3bcc8af
cleanup
gtarpenning Dec 3, 2024
6d4aa7e
fix test
gtarpenning Dec 3, 2024
7d0d151
merge
gtarpenning Dec 4, 2024
964a4a9
fix-sqlite-dupes
gtarpenning Dec 4, 2024
827b1ee
with lock
gtarpenning Dec 4, 2024
05bf534
lint
gtarpenning Dec 4, 2024
f56870a
cleaner
gtarpenning Dec 5, 2024
6a22665
Merge branch 'master' into griffin/objs-delete
gtarpenning Dec 5, 2024
5b37bcf
w
gtarpenning Dec 5, 2024
72a25c9
WIP
gtarpenning Dec 9, 2024
b795953
obj_delete with optional digests list
gtarpenning Dec 9, 2024
feef47e
lint
gtarpenning Dec 9, 2024
a8c7e95
merge
gtarpenning Dec 9, 2024
c343ec0
fix
gtarpenning Dec 9, 2024
add641f
lint
gtarpenning Dec 9, 2024
014d108
update
gtarpenning Dec 9, 2024
f0015a3
fixtest
gtarpenning Dec 9, 2024
e7ced6c
fix button spacing and delete modal autosizing
gtarpenning Dec 11, 2024
3e634d5
cache handling fe
gtarpenning Dec 11, 2024
f3e513b
dont do table delete on object delete
gtarpenning Dec 11, 2024
c30ef52
add back importand val_dump step
gtarpenning Dec 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions tests/trace/test_weave_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1544,3 +1544,117 @@ def func():
calls = list(func.calls())
call = calls[0]
assert len(call.display_name) <= MAX_DISPLAY_NAME_LENGTH


def test_object_deletion(client):
# Simple case, delete a single version of an object
obj = {"a": 5}
weave_obj = client.save(obj, "my-obj")
assert client.get(weave_obj.ref) == obj

client.delete_object(weave_obj.ref)
with pytest.raises(weave.trace_server.errors.ObjectDeletedError):
client.get(weave_obj.ref)

# create 3 versions of the object
obj["a"] = 6
weave_obj2 = client.save(obj, "my-obj")
obj["a"] = 7
weave_obj3 = client.save(obj, "my-obj")
obj["a"] = 8
weave_obj4 = client.save(obj, "my-obj")

# delete weave_obj3 with class method
weave_obj3.delete()

# make sure we can't get the deleted object
with pytest.raises(weave.trace_server.errors.ObjectDeletedError):
client.get(weave_obj3.ref)

# count the number of versions of the object
versions = client.server.objs_query(
req=tsi.ObjQueryReq(
project_id=client._project_id(),
names=["my-obj"],
)
)
assert len(versions.objs) == 2

# make sure we can still get the existing object versions
assert client.get(weave_obj4.ref)
assert client.get(weave_obj2.ref)

weave_obj4.delete()
weave_obj2.delete()

versions = client.server.objs_query(
req=tsi.ObjQueryReq(
project_id=client._project_id(),
names=["my-obj"],
)
)
assert len(versions.objs) == 0


def test_recursive_object_deletion(client):
# Create a bunch of objects that refer to each other
obj1 = {"a": 5}
obj1_ref = client.save(obj1, "obj1").ref

obj2 = {"b": obj1_ref}
obj2_ref = client.save(obj2, "obj2").ref

obj3 = {"c": obj2_ref}
obj3_ref = client.save(obj3, "obj3").ref

# Delete obj1
client.get(obj1_ref).delete()

# Make sure we can't get obj1
with pytest.raises(weave.trace_server.errors.ObjectDeletedError):
out = client.get(obj1_ref)
print(out)

# Make sure we can get obj2, but the ref to object 1 should return None
assert client.get(obj2_ref) == {"b": None}
# Object2 should store the ref to object2, as instantiated
assert client.get(obj3_ref) == {"c": obj2}


def test_table_object_deletion(client):
# create a dataset
data = [{"a": 1}, {"a": 2}, {"a": 3}]
dataset = weave.Table(data)
dataset_ref = client.save(dataset, "my-dataset").ref

# get the dataset
assert client.get(dataset_ref) == dataset

# delete the dataset
client.delete_object(dataset_ref)

# make sure we can't get the dataset
with pytest.raises(weave.trace_server.errors.ObjectDeletedError):
client.get(dataset_ref)

# recreate the EXACT SAME dataset
dataset = weave.Table(data)
dataset_ref = client.save(dataset, "my-dataset").ref

assert client.get(dataset_ref) == dataset
read_res = client.server.obj_read(
tsi.ObjReadReq(
project_id=client._project_id(),
object_id=dataset_ref.name,
digest=dataset_ref.digest,
)
)
# Version should be bumped
assert read_res.obj.version_index == 1

# delete the dataset again
client.delete_object(dataset_ref)

# make sure we can't get the dataset
with pytest.raises(weave.trace_server.errors.ObjectDeletedError):
client.get(dataset_ref)
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ export const SmallRef: FC<{
}
const objectVersion = useObjectVersion(objVersionKey);
const opVersion = useOpVersion(opVersionKey);

const isDeleted =
isObjDeleteError(objectVersion?.error) ||
isObjDeleteError(opVersion?.error);

const versionIndex =
objectVersion.result?.versionIndex ?? opVersion.result?.versionIndex;

Expand All @@ -140,7 +145,6 @@ export const SmallRef: FC<{
rootType = {type: 'OpDef'};
}
const {label} = objectRefDisplayName(objRef, versionIndex);

const rootTypeName = getTypeName(rootType);
let icon: IconName = IconNames.CubeContainer;
if (rootTypeName === 'Dataset') {
Expand Down Expand Up @@ -177,13 +181,14 @@ export const SmallRef: FC<{
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
textDecoration: isDeleted ? 'line-through' : 'none',
}}>
{label}
</Box>
)}
</Box>
);
if (refTypeQuery.loading) {
if (refTypeQuery.loading || isDeleted) {
return Item;
}
if (!isArtifactRef && !isWeaveObjRef) {
Expand All @@ -200,3 +205,12 @@ export const SmallRef: FC<{
</Link>
);
};

export const isObjDeleteError = (e: any): boolean => {
if (e == null) {
return false;
}
const errorStr = String(e);
const regex = /Obj .* was deleted at .*/;
return regex.test(errorStr);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Box from '@mui/material/Box';

Check warning on line 1 in weave-js/src/components/PagePanelComponents/Home/Browse3/pages/CallPage/ObjectViewer.tsx

View workflow job for this annotation

GitHub Actions / WeaveJS Lint and Compile

Run autofix to sort these imports!
import {
DataGridProProps,
GridApiPro,
Expand All @@ -7,6 +7,7 @@
GridRowId,
} from '@mui/x-data-grid-pro';
import {Button} from '@wandb/weave/components/Button';
import {parseRef} from '@wandb/weave/react';
import _ from 'lodash';
import React, {
Dispatch,
Expand Down Expand Up @@ -47,6 +48,7 @@
TraverseContext,
} from './traverse';
import {ValueView} from './ValueView';
import {objectRefDisplayName} from '../../../Browse2/SmallRef';

type Data = Record<string, any> | any[];

Expand Down Expand Up @@ -153,10 +155,17 @@

const refValues: RefValues = {};
for (const [r, v] of _.zip(refs, resolvedRefData)) {
if (!r || !v) {
if (!r) {
// Shouldn't be possible
continue;
}
if (!v) {
// Value for ref not found, probably deleted
refValues[r] = {
_weave_is_deleted_ref: objectRefDisplayName(parseRef(r)).label,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is terrible, we need a better way of storing/rendering deleted refs in the object viewer.

};
continue;
}
let val = r;
if (v == null) {
console.error('Error resolving ref', r);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export const ObjectViewerGroupingCell: FC<
event.stopPropagation();
};

const deletedRef = row.value?._weave_is_deleted_ref;

const tooltipContent = row.path.toString();
const box = (
<CursorBox
Expand Down Expand Up @@ -109,8 +111,9 @@ export const ObjectViewerGroupingCell: FC<
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
flex: '1 1 auto',
textDecoration: deletedRef ? 'line-through' : 'none',
}}>
{props.value}
{deletedRef ?? props.value}
</Box>
}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import {Button} from '@wandb/weave/components/Button';
import {useObjectViewEvent} from '@wandb/weave/integrations/analytics/useViewEvents';
import React, {useMemo} from 'react';
import React, {useMemo, useState} from 'react';

import {maybePluralizeWord} from '../../../../../core/util/string';
import {Icon, IconName} from '../../../../Icon';
import {LoadingDots} from '../../../../LoadingDots';
import {Tailwind} from '../../../../Tailwind';
import {Tooltip} from '../../../../Tooltip';
import {useClosePeek} from '../context';
import {NotFoundPanel} from '../NotFoundPanel';
import {CustomWeaveTypeProjectContext} from '../typeViews/CustomWeaveTypeDispatcher';
import {WeaveCHTableSourceRefContext} from './CallPage/DataTableView';
import {ObjectViewerSection} from './CallPage/ObjectViewerSection';
import {WFHighLevelCallFilter} from './CallsPage/callsTableFilter';
import {DeleteModal} from './common/DeleteModal';
import {
CallLink,
CallsLink,
Expand Down Expand Up @@ -94,7 +98,9 @@ export const ObjectVersionPage: React.FC<{
path: props.filePath,
refExtra: props.refExtra,
});
if (objectVersion.loading) {
if (objectVersion.error) {
return <NotFoundPanel title={objectVersion.error.message} />;
} else if (objectVersion.loading) {
return <CenteredAnimatedLoader />;
} else if (objectVersion.result == null) {
return <NotFoundPanel title="Object not found" />;
Expand All @@ -108,7 +114,10 @@ const ObjectVersionPageInner: React.FC<{
}> = ({objectVersion}) => {
useObjectViewEvent(objectVersion);

const {useRootObjectVersions, useCalls, useRefsData} = useWFHooks();
const closePeek = useClosePeek();
const {useRootObjectVersions, useCalls, useRefsData, useObjectDeleteFunc} =
useWFHooks();
const objectDelete = useObjectDeleteFunc();
const entityName = objectVersion.entity;
const projectName = objectVersion.project;
const objectName = objectVersion.objectId;
Expand Down Expand Up @@ -189,6 +198,7 @@ const ObjectVersionPageInner: React.FC<{
return viewerData;
}, [viewerData]);

const [deleteModalOpen, setDeleteModalOpen] = useState(false);
const isDataset = baseObjectClass === 'Dataset' && refExtra == null;
const isEvaluation = baseObjectClass === 'Evaluation' && refExtra == null;
const evalHasCalls = (consumingCalls.result?.length ?? 0) > 0;
Expand All @@ -211,47 +221,70 @@ const ObjectVersionPageInner: React.FC<{
</Tailwind>
}
headerContent={
<SimpleKeyValueTable
data={{
[refExtra ? 'Parent Object' : 'Name']: (
<>
{objectName}{' '}
{objectVersions.loading ? (
<LoadingDots />
) : (
<Stack
direction="row"
justifyContent="space-between"
alignItems="flex-start"
width="100%">
<Box flexGrow={1}>
<SimpleKeyValueTable
data={{
[refExtra ? 'Parent Object' : 'Name']: (
<>
[
<ObjectVersionsLink
entity={entityName}
project={projectName}
filter={{
objectName,
}}
versionCount={objectVersionCount}
neverPeek
variant="secondary"
/>
]
{objectName}{' '}
{objectVersions.loading ? (
<LoadingDots />
) : (
<>
[
<ObjectVersionsLink
entity={entityName}
project={projectName}
filter={{
objectName,
}}
versionCount={objectVersionCount}
neverPeek
variant="secondary"
/>
]
</>
)}
</>
)}
</>
),
Version: <>{objectVersionIndex}</>,
...(refExtra
? {
Subpath: refExtra,
}
: {}),
// 'Type Version': (
// <TypeVersionLink
// entityName={entityName}
// projectName={projectName}
// typeName={typeName}
// version={typeVersionHash}
// />
// ),
}}
/>
),
Version: <>{objectVersionIndex}</>,
...(refExtra
? {
Subpath: refExtra,
}
: {}),
// 'Type Version': (
// <TypeVersionLink
// entityName={entityName}
// projectName={projectName}
// typeName={typeName}
// version={typeVersionHash}
// />
// ),
}}
/>
</Box>

<Box mr={1}>
<Button
icon="delete"
variant="ghost"
onClick={() => setDeleteModalOpen(true)}
/>
</Box>
<DeleteModal
open={deleteModalOpen}
onClose={() => setDeleteModalOpen(false)}
deleteTargetStr={`${objectVersion.objectId}:v${objectVersion.versionIndex}`}
onDelete={() => objectDelete(objectVersion)}
onSuccess={closePeek}
/>
</Stack>
}
// menuItems={[
// {
Expand Down
Loading
Loading