Skip to content

Commit 2a37c7a

Browse files
thomass-devrouk1
andauthored
feat: Export to front-end all versions of an item, but only use the latest version in the front-end (#643)
Part of #504 . --------- Co-authored-by: Matthieu Jouis <[email protected]>
1 parent 59c1a75 commit 2a37c7a

File tree

5 files changed

+68
-56
lines changed

5 files changed

+68
-56
lines changed

skore-ui/src/models.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ export type Layout = string[];
1919
* A project is a collection of items and views
2020
*/
2121
export interface Project {
22-
items: { [key: string]: ProjectItem };
22+
items: { [key: string]: ProjectItem[] };
2323
views: { [key: string]: Layout };
2424
}

skore-ui/src/stores/project.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,20 @@ export const useProjectStore = defineStore("project", () => {
112112
/**
113113
* Set the current views and items
114114
* Autoselect the first view if no view is selected
115+
*
116+
* For now only the latest item is kept in memory.
117+
*
115118
* @param r data received from the backend
116119
*/
117120
async function setProject(r: Project) {
118-
items.value = r.items;
121+
const latestItemByKey: { [key: string]: ProjectItem } = {};
122+
for (const [key, value] of Object.entries(r.items)) {
123+
latestItemByKey[key] = value[value.length - 1];
124+
}
125+
126+
items.value = latestItemByKey;
119127
views.value = r.views;
128+
120129
const viewNames = Object.keys(views.value);
121130
if (currentView.value === null) {
122131
if (viewNames.length > 0) {

skore-ui/tests/stores/project.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ describe("Project store", () => {
4141
}
4242
const project = {
4343
items: {
44-
a: makeFakeViewItem(),
45-
"a/b": makeFakeViewItem(),
46-
"a/b/d": makeFakeViewItem(),
47-
"a/b/e": makeFakeViewItem(),
48-
"a/b/f/g": makeFakeViewItem(),
44+
a: [makeFakeViewItem()],
45+
"a/b": [makeFakeViewItem()],
46+
"a/b/d": [makeFakeViewItem()],
47+
"a/b/e": [makeFakeViewItem()],
48+
"a/b/f/g": [makeFakeViewItem()],
4949
},
5050
views: {},
5151
};

skore/src/skore/ui/project_routes.py

Lines changed: 39 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""The definition of API routes to list project items and get them."""
22

33
import base64
4+
from collections import defaultdict
45
from dataclasses import dataclass
56
from typing import Any
67

@@ -33,55 +34,52 @@ class SerializedItem:
3334
class SerializedProject:
3435
"""Serialized project, to be sent to the skore-ui."""
3536

36-
items: dict[str, SerializedItem]
37+
items: dict[str, list[SerializedItem]]
3738
views: dict[str, Layout]
3839

3940

4041
def __serialize_project(project: Project) -> SerializedProject:
41-
views = {}
42-
for key in project.list_view_keys():
43-
views[key] = project.get_view(key).layout
42+
items = defaultdict(list)
4443

45-
items = {}
4644
for key in project.list_item_keys():
47-
item = project.get_item(key)
48-
49-
media_type = None
50-
if isinstance(item, PrimitiveItem):
51-
value = item.primitive
52-
media_type = "text/markdown"
53-
elif isinstance(item, NumpyArrayItem):
54-
value = item.array_list
55-
media_type = "text/markdown"
56-
elif isinstance(item, PandasDataFrameItem):
57-
value = item.dataframe.to_dict(orient="tight")
58-
media_type = "application/vnd.dataframe+json"
59-
elif isinstance(item, PandasSeriesItem):
60-
value = item.series_list
61-
media_type = "text/markdown"
62-
elif isinstance(item, SklearnBaseEstimatorItem):
63-
value = item.estimator_html_repr
64-
media_type = "application/vnd.sklearn.estimator+html"
65-
elif isinstance(item, MediaItem):
66-
value = base64.b64encode(item.media_bytes).decode()
67-
media_type = item.media_type
68-
elif isinstance(item, CrossValidationItem):
69-
# Convert plot to MediaItem
70-
item = MediaItem.factory(item.plot)
71-
value = base64.b64encode(item.media_bytes).decode()
72-
media_type = item.media_type
73-
else:
74-
raise ValueError(f"Item {item} is not a known item type.")
75-
76-
items[key] = SerializedItem(
77-
media_type=media_type,
78-
value=value,
79-
updated_at=item.updated_at,
80-
created_at=item.created_at,
81-
)
45+
for item in project.get_item_versions(key):
46+
if isinstance(item, PrimitiveItem):
47+
value = item.primitive
48+
media_type = "text/markdown"
49+
elif isinstance(item, NumpyArrayItem):
50+
value = item.array_list
51+
media_type = "text/markdown"
52+
elif isinstance(item, PandasDataFrameItem):
53+
value = item.dataframe.to_dict(orient="tight")
54+
media_type = "application/vnd.dataframe+json"
55+
elif isinstance(item, PandasSeriesItem):
56+
value = item.series_list
57+
media_type = "text/markdown"
58+
elif isinstance(item, SklearnBaseEstimatorItem):
59+
value = item.estimator_html_repr
60+
media_type = "application/vnd.sklearn.estimator+html"
61+
elif isinstance(item, MediaItem):
62+
value = base64.b64encode(item.media_bytes).decode()
63+
media_type = item.media_type
64+
elif isinstance(item, CrossValidationItem):
65+
value = base64.b64encode(item.plot_bytes).decode()
66+
media_type = "application/vnd.plotly.v1+json"
67+
else:
68+
raise ValueError(f"Item {item} is not a known item type.")
69+
70+
items[key].append(
71+
SerializedItem(
72+
media_type=media_type,
73+
value=value,
74+
updated_at=item.updated_at,
75+
created_at=item.created_at,
76+
)
77+
)
78+
79+
views = {key: project.get_view(key).layout for key in project.list_view_keys()}
8280

8381
return SerializedProject(
84-
items=items,
82+
items=dict(items),
8583
views=views,
8684
)
8785

skore/tests/integration/ui/test_ui.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,25 @@ def test_get_items(client, in_memory_project):
2626
assert response.status_code == 200
2727
assert response.json() == {"views": {}, "items": {}}
2828

29-
in_memory_project.put("test", "test")
30-
item = in_memory_project.get_item("test")
29+
in_memory_project.put("test", "version_1")
30+
in_memory_project.put("test", "version_2")
31+
32+
items = in_memory_project.get_item_versions("test")
3133

3234
response = client.get("/api/project/items")
3335
assert response.status_code == 200
3436
assert response.json() == {
3537
"views": {},
3638
"items": {
37-
"test": {
38-
"media_type": "text/markdown",
39-
"value": "test",
40-
"updated_at": item.updated_at,
41-
"created_at": item.created_at,
42-
}
39+
"test": [
40+
{
41+
"media_type": "text/markdown",
42+
"value": item.primitive,
43+
"created_at": item.created_at,
44+
"updated_at": item.updated_at,
45+
}
46+
for item in items
47+
],
4348
},
4449
}
4550

0 commit comments

Comments
 (0)