Spaces:
Running
Running
Deploy.
Browse files- app.py +6 -1
- components/tabs/__init__.py +44 -0
- components/tabs/frontend/.env +6 -0
- components/tabs/frontend/.prettierrc +5 -0
- components/tabs/frontend/build/asset-manifest.json +10 -0
- components/tabs/frontend/build/index.html +1 -0
- components/tabs/frontend/build/static/js/main.716a0ab4.js +0 -0
- components/tabs/frontend/build/static/js/main.716a0ab4.js.LICENSE.txt +73 -0
- components/tabs/frontend/build/static/js/main.716a0ab4.js.map +0 -0
- components/tabs/frontend/package-lock.json +0 -0
- components/tabs/frontend/package.json +47 -0
- components/tabs/frontend/public/index.html +27 -0
- components/tabs/frontend/src/Tabs.tsx +87 -0
- components/tabs/frontend/src/index.tsx +10 -0
- components/tabs/frontend/src/react-app-env.d.ts +1 -0
- core/files.py +9 -1
- core/past_projects.py +5 -5
- core/query_params.py +3 -37
- core/state.py +20 -0
- cypress/e2e/createManually.cy.js +6 -4
- cypress/e2e/displayErrors.cy.js +8 -2
- cypress/e2e/loadCroissant.cy.js +7 -2
- cypress/e2e/renameDistribution.cy.js +5 -7
- cypress/e2e/uploadCsv.cy.js +9 -5
- events/fields.py +0 -3
- events/metadata.py +0 -3
- events/record_sets.py +0 -3
- events/resources.py +0 -3
- utils.py +12 -11
- views/files.py +9 -6
- views/overview.py +15 -2
- views/record_sets.py +1 -3
- views/wizard.py +25 -17
app.py
CHANGED
@@ -20,6 +20,7 @@ col1.header("Croissant Editor")
|
|
20 |
init_state()
|
21 |
|
22 |
user = get_cached_user()
|
|
|
23 |
|
24 |
if OAUTH_CLIENT_ID and not user:
|
25 |
query_params = st.experimental_get_query_params()
|
@@ -56,6 +57,8 @@ def _back_to_menu():
|
|
56 |
def _logout():
|
57 |
"""Logs the user out."""
|
58 |
st.cache_data.clear()
|
|
|
|
|
59 |
_back_to_menu()
|
60 |
|
61 |
|
@@ -70,7 +73,9 @@ if timestamp:
|
|
70 |
col3.button("Menu", on_click=_back_to_menu)
|
71 |
|
72 |
|
73 |
-
|
|
|
|
|
74 |
render_editor()
|
75 |
else:
|
76 |
render_splash()
|
|
|
20 |
init_state()
|
21 |
|
22 |
user = get_cached_user()
|
23 |
+
print("USER", user)
|
24 |
|
25 |
if OAUTH_CLIENT_ID and not user:
|
26 |
query_params = st.experimental_get_query_params()
|
|
|
57 |
def _logout():
|
58 |
"""Logs the user out."""
|
59 |
st.cache_data.clear()
|
60 |
+
get_cached_user.clear()
|
61 |
+
st.session_state[User] = None
|
62 |
_back_to_menu()
|
63 |
|
64 |
|
|
|
73 |
col3.button("Menu", on_click=_back_to_menu)
|
74 |
|
75 |
|
76 |
+
should_display_editor = bool(st.session_state.get(CurrentProject))
|
77 |
+
|
78 |
+
if should_display_editor:
|
79 |
render_editor()
|
80 |
else:
|
81 |
render_splash()
|
components/tabs/__init__.py
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
import streamlit.components.v1 as components
|
4 |
+
|
5 |
+
from core.constants import OVERVIEW
|
6 |
+
|
7 |
+
# Create a _RELEASE constant. We'll set this to False while we're developing
|
8 |
+
# the component, and True when we're ready to package and distribute it.
|
9 |
+
_RELEASE = True
|
10 |
+
|
11 |
+
if not _RELEASE:
|
12 |
+
_component_func = components.declare_component(
|
13 |
+
"tabs_component",
|
14 |
+
url="http://localhost:3001",
|
15 |
+
)
|
16 |
+
else:
|
17 |
+
parent_dir = os.path.dirname(os.path.abspath(__file__))
|
18 |
+
build_dir = os.path.join(parent_dir, "frontend/build")
|
19 |
+
_component_func = components.declare_component("tabs_component", path=build_dir)
|
20 |
+
|
21 |
+
|
22 |
+
def render_tabs(tabs: list[str], selected_tab: int, json: str | None, key=None):
|
23 |
+
"""Create a new instance of "tabs_component".
|
24 |
+
|
25 |
+
Args:
|
26 |
+
tabs: The tabs to render in the component.
|
27 |
+
selected_tab: The selected tab.
|
28 |
+
key: An optional key that uniquely identifies this component. If this is
|
29 |
+
None, and the component's arguments are changed, the component will
|
30 |
+
be re-mounted in the Streamlit frontend and lose its current state.
|
31 |
+
|
32 |
+
Returns:
|
33 |
+
The number of times the component's "Click Me" button has been clicked.
|
34 |
+
(This is the value passed to `Streamlit.setComponentValue` on the
|
35 |
+
frontend.)
|
36 |
+
"""
|
37 |
+
component_value = _component_func(
|
38 |
+
tabs=tabs,
|
39 |
+
selected_tab=selected_tab,
|
40 |
+
json=json,
|
41 |
+
key=key,
|
42 |
+
default=OVERVIEW,
|
43 |
+
)
|
44 |
+
return component_value
|
components/tabs/frontend/.env
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Run the component's dev server on :3001
|
2 |
+
# (The Streamlit dev server already runs on :3000)
|
3 |
+
PORT=3001
|
4 |
+
|
5 |
+
# Don't automatically open the web browser on `npm run start`.
|
6 |
+
BROWSER=none
|
components/tabs/frontend/.prettierrc
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"endOfLine": "lf",
|
3 |
+
"semi": false,
|
4 |
+
"trailingComma": "es5"
|
5 |
+
}
|
components/tabs/frontend/build/asset-manifest.json
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"files": {
|
3 |
+
"main.js": "./static/js/main.716a0ab4.js",
|
4 |
+
"index.html": "./index.html",
|
5 |
+
"main.716a0ab4.js.map": "./static/js/main.716a0ab4.js.map"
|
6 |
+
},
|
7 |
+
"entrypoints": [
|
8 |
+
"static/js/main.716a0ab4.js"
|
9 |
+
]
|
10 |
+
}
|
components/tabs/frontend/build/index.html
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
<!doctype html><html lang="en"><head><title>Streamlit Tabs Component</title><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Streamlit Tree Component"/><script defer="defer" src="./static/js/main.716a0ab4.js"></script></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
components/tabs/frontend/build/static/js/main.716a0ab4.js
ADDED
The diff for this file is too large to render.
See raw diff
|
|
components/tabs/frontend/build/static/js/main.716a0ab4.js.LICENSE.txt
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
object-assign
|
3 |
+
(c) Sindre Sorhus
|
4 |
+
@license MIT
|
5 |
+
*/
|
6 |
+
|
7 |
+
/**
|
8 |
+
* @license React
|
9 |
+
* react-dom.production.min.js
|
10 |
+
*
|
11 |
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
12 |
+
*
|
13 |
+
* This source code is licensed under the MIT license found in the
|
14 |
+
* LICENSE file in the root directory of this source tree.
|
15 |
+
*/
|
16 |
+
|
17 |
+
/**
|
18 |
+
* @license React
|
19 |
+
* react-is.production.min.js
|
20 |
+
*
|
21 |
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
22 |
+
*
|
23 |
+
* This source code is licensed under the MIT license found in the
|
24 |
+
* LICENSE file in the root directory of this source tree.
|
25 |
+
*/
|
26 |
+
|
27 |
+
/**
|
28 |
+
* @license React
|
29 |
+
* react-jsx-runtime.production.min.js
|
30 |
+
*
|
31 |
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
32 |
+
*
|
33 |
+
* This source code is licensed under the MIT license found in the
|
34 |
+
* LICENSE file in the root directory of this source tree.
|
35 |
+
*/
|
36 |
+
|
37 |
+
/**
|
38 |
+
* @license React
|
39 |
+
* react.production.min.js
|
40 |
+
*
|
41 |
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
42 |
+
*
|
43 |
+
* This source code is licensed under the MIT license found in the
|
44 |
+
* LICENSE file in the root directory of this source tree.
|
45 |
+
*/
|
46 |
+
|
47 |
+
/**
|
48 |
+
* @license React
|
49 |
+
* scheduler.production.min.js
|
50 |
+
*
|
51 |
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
52 |
+
*
|
53 |
+
* This source code is licensed under the MIT license found in the
|
54 |
+
* LICENSE file in the root directory of this source tree.
|
55 |
+
*/
|
56 |
+
|
57 |
+
/** @license React v16.13.1
|
58 |
+
* react-is.production.min.js
|
59 |
+
*
|
60 |
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
61 |
+
*
|
62 |
+
* This source code is licensed under the MIT license found in the
|
63 |
+
* LICENSE file in the root directory of this source tree.
|
64 |
+
*/
|
65 |
+
|
66 |
+
/** @license React v16.14.0
|
67 |
+
* react.production.min.js
|
68 |
+
*
|
69 |
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
70 |
+
*
|
71 |
+
* This source code is licensed under the MIT license found in the
|
72 |
+
* LICENSE file in the root directory of this source tree.
|
73 |
+
*/
|
components/tabs/frontend/build/static/js/main.716a0ab4.js.map
ADDED
The diff for this file is too large to render.
See raw diff
|
|
components/tabs/frontend/package-lock.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
components/tabs/frontend/package.json
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "tree_component",
|
3 |
+
"version": "0.1.0",
|
4 |
+
"private": true,
|
5 |
+
"dependencies": {
|
6 |
+
"@emotion/react": "^11.11.1",
|
7 |
+
"@emotion/styled": "^11.11.0",
|
8 |
+
"@mui/material": "^5.14.17",
|
9 |
+
"react": "^18.2.0",
|
10 |
+
"react-dom": "^18.2.0",
|
11 |
+
"streamlit-component-lib": "^2.0.0"
|
12 |
+
},
|
13 |
+
"scripts": {
|
14 |
+
"start": "react-scripts start",
|
15 |
+
"build": "react-scripts build",
|
16 |
+
"test": "react-scripts test",
|
17 |
+
"eject": "react-scripts eject"
|
18 |
+
},
|
19 |
+
"eslintConfig": {
|
20 |
+
"extends": "react-app"
|
21 |
+
},
|
22 |
+
"browserslist": {
|
23 |
+
"production": [
|
24 |
+
">0.2%",
|
25 |
+
"not dead",
|
26 |
+
"not op_mini all"
|
27 |
+
],
|
28 |
+
"development": [
|
29 |
+
"last 1 chrome version",
|
30 |
+
"last 1 firefox version",
|
31 |
+
"last 1 safari version"
|
32 |
+
]
|
33 |
+
},
|
34 |
+
"homepage": ".",
|
35 |
+
"devDependencies": {
|
36 |
+
"@types/node": "^20.9.0",
|
37 |
+
"@types/react": "^18.2.37",
|
38 |
+
"@types/react-dom": "^18.2.15",
|
39 |
+
"react-scripts": "^5.0.1",
|
40 |
+
"typescript": "^5.2.2"
|
41 |
+
},
|
42 |
+
"overrides": {
|
43 |
+
"react-scripts": {
|
44 |
+
"typescript": "^5"
|
45 |
+
}
|
46 |
+
}
|
47 |
+
}
|
components/tabs/frontend/public/index.html
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
|
4 |
+
<head>
|
5 |
+
<title>Streamlit Tabs Component</title>
|
6 |
+
<meta charset="UTF-8" />
|
7 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
8 |
+
<meta name="theme-color" content="#000000" />
|
9 |
+
<meta name="description" content="Streamlit Tree Component" />
|
10 |
+
</head>
|
11 |
+
|
12 |
+
<body>
|
13 |
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
14 |
+
<div id="root"></div>
|
15 |
+
<!--
|
16 |
+
This HTML file is a template.
|
17 |
+
If you open it directly in the browser, you will see an empty page.
|
18 |
+
|
19 |
+
You can add webfonts, meta tags, or analytics to this file.
|
20 |
+
The build step will place the bundled scripts into the <body> tag.
|
21 |
+
|
22 |
+
To begin the development, run `npm start` or `yarn start`.
|
23 |
+
To create a production bundle, use `npm run build` or `yarn build`.
|
24 |
+
-->
|
25 |
+
</body>
|
26 |
+
|
27 |
+
</html>
|
components/tabs/frontend/src/Tabs.tsx
ADDED
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Streamlit,
|
3 |
+
StreamlitComponentBase,
|
4 |
+
withStreamlitConnection,
|
5 |
+
} from "streamlit-component-lib"
|
6 |
+
import React, { ReactNode } from "react"
|
7 |
+
import Button from "@mui/material/Button"
|
8 |
+
import Tabs from "@mui/material/Tabs"
|
9 |
+
import Tab from "@mui/material/Tab"
|
10 |
+
import Box from "@mui/material/Box"
|
11 |
+
import { ThemeProvider, createTheme } from "@mui/material"
|
12 |
+
import { orange } from "@mui/material/colors"
|
13 |
+
|
14 |
+
const theme = createTheme({
|
15 |
+
palette: {
|
16 |
+
primary: orange,
|
17 |
+
},
|
18 |
+
})
|
19 |
+
|
20 |
+
function BasicTabs({
|
21 |
+
tabs,
|
22 |
+
selectedTab,
|
23 |
+
json,
|
24 |
+
}: {
|
25 |
+
tabs: string[]
|
26 |
+
selectedTab: number
|
27 |
+
json?: { name: string; content: string }
|
28 |
+
}) {
|
29 |
+
const [value, setValue] = React.useState(selectedTab)
|
30 |
+
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
31 |
+
Streamlit.setComponentValue(tabs[newValue])
|
32 |
+
setValue(newValue)
|
33 |
+
}
|
34 |
+
|
35 |
+
return (
|
36 |
+
<div
|
37 |
+
style={{
|
38 |
+
display: "flex",
|
39 |
+
flexDirection: "row",
|
40 |
+
justifyContent: "center",
|
41 |
+
alignItems: "center",
|
42 |
+
marginTop: -8,
|
43 |
+
}}
|
44 |
+
>
|
45 |
+
<Box sx={{ width: "100%", margin: -1, padding: 0 }}>
|
46 |
+
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
47 |
+
<Tabs
|
48 |
+
value={value}
|
49 |
+
onChange={handleChange}
|
50 |
+
aria-label="navigation-tabs"
|
51 |
+
>
|
52 |
+
{tabs.map((tab) => (
|
53 |
+
<Tab key={`custom-tab-${tab}`} label={tab} />
|
54 |
+
))}
|
55 |
+
</Tabs>
|
56 |
+
</Box>
|
57 |
+
</Box>
|
58 |
+
<Button
|
59 |
+
disabled={!json}
|
60 |
+
variant="outlined"
|
61 |
+
href={
|
62 |
+
json
|
63 |
+
? `data:text/json;charset=utf-8,${encodeURIComponent(json.content)}`
|
64 |
+
: ""
|
65 |
+
}
|
66 |
+
download={json ? json.name : ""}
|
67 |
+
>
|
68 |
+
Export
|
69 |
+
</Button>
|
70 |
+
</div>
|
71 |
+
)
|
72 |
+
}
|
73 |
+
|
74 |
+
class StreamlitTabs extends StreamlitComponentBase<{}> {
|
75 |
+
public render = (): ReactNode => {
|
76 |
+
const tabs = this.props.args["tabs"]
|
77 |
+
const selectedTab = this.props.args["selected_tab"]
|
78 |
+
const json = this.props.args["json"]
|
79 |
+
return (
|
80 |
+
<ThemeProvider theme={theme}>
|
81 |
+
<BasicTabs tabs={tabs} selectedTab={selectedTab} json={json} />
|
82 |
+
</ThemeProvider>
|
83 |
+
)
|
84 |
+
}
|
85 |
+
}
|
86 |
+
|
87 |
+
export default withStreamlitConnection(StreamlitTabs)
|
components/tabs/frontend/src/index.tsx
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react"
|
2 |
+
import ReactDOM from "react-dom"
|
3 |
+
import Tabs from "./Tabs.tsx"
|
4 |
+
|
5 |
+
ReactDOM.render(
|
6 |
+
<React.StrictMode>
|
7 |
+
<Tabs />
|
8 |
+
</React.StrictMode>,
|
9 |
+
document.getElementById("root")
|
10 |
+
)
|
components/tabs/frontend/src/react-app-env.d.ts
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
/// <reference types="react-scripts" />
|
core/files.py
CHANGED
@@ -132,7 +132,13 @@ def file_from_upload(
|
|
132 |
|
133 |
|
134 |
def file_from_form(
|
135 |
-
file_type: FileType,
|
|
|
|
|
|
|
|
|
|
|
|
|
136 |
) -> FileObject | FileSet:
|
137 |
"""Creates a file based on manually added fields."""
|
138 |
if type == FILE_OBJECT:
|
@@ -143,12 +149,14 @@ def file_from_form(
|
|
143 |
encoding_format=file_type.encoding_format,
|
144 |
sha256=sha256,
|
145 |
df=None,
|
|
|
146 |
)
|
147 |
elif type == FILE_SET:
|
148 |
return FileSet(
|
149 |
name=find_unique_name(names, name),
|
150 |
description=description,
|
151 |
encoding_format=file_type.encoding_format,
|
|
|
152 |
)
|
153 |
else:
|
154 |
raise ValueError("type has to be one of FILE_OBJECT, FILE_SET")
|
|
|
132 |
|
133 |
|
134 |
def file_from_form(
|
135 |
+
file_type: FileType,
|
136 |
+
type: str,
|
137 |
+
name,
|
138 |
+
description,
|
139 |
+
sha256: str,
|
140 |
+
contained_in: list[str],
|
141 |
+
names: set[str],
|
142 |
) -> FileObject | FileSet:
|
143 |
"""Creates a file based on manually added fields."""
|
144 |
if type == FILE_OBJECT:
|
|
|
149 |
encoding_format=file_type.encoding_format,
|
150 |
sha256=sha256,
|
151 |
df=None,
|
152 |
+
contained_in=contained_in,
|
153 |
)
|
154 |
elif type == FILE_SET:
|
155 |
return FileSet(
|
156 |
name=find_unique_name(names, name),
|
157 |
description=description,
|
158 |
encoding_format=file_type.encoding_format,
|
159 |
+
contained_in=contained_in,
|
160 |
)
|
161 |
else:
|
162 |
raise ValueError("type has to be one of FILE_OBJECT, FILE_SET")
|
core/past_projects.py
CHANGED
@@ -30,11 +30,11 @@ def save_current_project():
|
|
30 |
st.session_state[CurrentProject] = project
|
31 |
project.path.mkdir(parents=True, exist_ok=True)
|
32 |
set_project(project)
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
|
39 |
|
40 |
def open_project(path: epath.Path) -> Metadata:
|
|
|
30 |
st.session_state[CurrentProject] = project
|
31 |
project.path.mkdir(parents=True, exist_ok=True)
|
32 |
set_project(project)
|
33 |
+
try:
|
34 |
+
pickled = pickle.dumps(metadata)
|
35 |
+
_pickle_file(project.path).write_bytes(pickled)
|
36 |
+
except pickle.PicklingError as e:
|
37 |
+
logging.error("Could not pickle metadata.", exc_info=True)
|
38 |
|
39 |
|
40 |
def open_project(path: epath.Path) -> Metadata:
|
core/query_params.py
CHANGED
@@ -4,7 +4,6 @@ from typing import Any
|
|
4 |
|
5 |
import streamlit as st
|
6 |
|
7 |
-
from core.constants import TABS
|
8 |
from core.state import CurrentProject
|
9 |
from core.state import RecordSet
|
10 |
|
@@ -14,7 +13,6 @@ class QueryParams:
|
|
14 |
|
15 |
OPEN_PROJECT = "project"
|
16 |
OPEN_RECORD_SET = "recordSet"
|
17 |
-
OPEN_TAB = "tab"
|
18 |
|
19 |
|
20 |
def _get_query_param(params: dict[str, Any], name: str) -> str | None:
|
@@ -28,46 +26,14 @@ def _get_query_param(params: dict[str, Any], name: str) -> str | None:
|
|
28 |
|
29 |
def _set_query_param(param: str, new_value: str) -> str | None:
|
30 |
params = st.experimental_get_query_params()
|
|
|
|
|
|
|
31 |
new_params = {k: v for k, v in params.items() if k != param}
|
32 |
new_params[param] = new_value
|
33 |
st.experimental_set_query_params(**new_params)
|
34 |
|
35 |
|
36 |
-
def go_to_tab(tabs: list[str]):
|
37 |
-
params = st.experimental_get_query_params()
|
38 |
-
if QueryParams.OPEN_TAB in params:
|
39 |
-
try:
|
40 |
-
index = int(params[QueryParams.OPEN_TAB][0])
|
41 |
-
if 0 <= index and index < len(TABS):
|
42 |
-
tab = TABS[index]
|
43 |
-
# Click on the tab.
|
44 |
-
js = f"""
|
45 |
-
<script>
|
46 |
-
function contains(selector, text) {{
|
47 |
-
const document = window.parent.document;
|
48 |
-
const elements = document.querySelectorAll(selector);
|
49 |
-
return Array.from(elements).filter(function(element) {{
|
50 |
-
return RegExp(text).test(element.innerText);
|
51 |
-
}});
|
52 |
-
}}
|
53 |
-
const tab = contains('button', '{tab}');
|
54 |
-
if (tab.length) {{
|
55 |
-
tab[0].click();
|
56 |
-
}}
|
57 |
-
</script>
|
58 |
-
"""
|
59 |
-
st.components.v1.html(js)
|
60 |
-
except ValueError:
|
61 |
-
pass
|
62 |
-
|
63 |
-
|
64 |
-
def set_tab(tab: str):
|
65 |
-
if tab not in TABS:
|
66 |
-
return
|
67 |
-
index = TABS.index(tab)
|
68 |
-
_set_query_param(QueryParams.OPEN_TAB, index)
|
69 |
-
|
70 |
-
|
71 |
def is_record_set_expanded(record_set: RecordSet) -> bool:
|
72 |
params = st.experimental_get_query_params()
|
73 |
open_record_set_name = _get_query_param(params, QueryParams.OPEN_RECORD_SET)
|
|
|
4 |
|
5 |
import streamlit as st
|
6 |
|
|
|
7 |
from core.state import CurrentProject
|
8 |
from core.state import RecordSet
|
9 |
|
|
|
13 |
|
14 |
OPEN_PROJECT = "project"
|
15 |
OPEN_RECORD_SET = "recordSet"
|
|
|
16 |
|
17 |
|
18 |
def _get_query_param(params: dict[str, Any], name: str) -> str | None:
|
|
|
26 |
|
27 |
def _set_query_param(param: str, new_value: str) -> str | None:
|
28 |
params = st.experimental_get_query_params()
|
29 |
+
if params.get(param) == [new_value]:
|
30 |
+
# The value already exists in the query params.
|
31 |
+
return
|
32 |
new_params = {k: v for k, v in params.items() if k != param}
|
33 |
new_params[param] = new_value
|
34 |
st.experimental_set_query_params(**new_params)
|
35 |
|
36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
def is_record_set_expanded(record_set: RecordSet) -> bool:
|
38 |
params = st.experimental_get_query_params()
|
39 |
open_record_set_name = _get_query_param(params, QueryParams.OPEN_RECORD_SET)
|
core/state.py
CHANGED
@@ -20,6 +20,7 @@ from core.constants import OAUTH_CLIENT_SECRET
|
|
20 |
from core.constants import PAST_PROJECTS_PATH
|
21 |
from core.constants import PROJECT_FOLDER_PATTERN
|
22 |
from core.constants import REDIRECT_URI
|
|
|
23 |
from core.names import find_unique_name
|
24 |
import mlcroissant as mlc
|
25 |
|
@@ -327,3 +328,22 @@ class Metadata:
|
|
327 |
def names(self) -> set[str]:
|
328 |
nodes = self.distribution + self.record_sets
|
329 |
return set([node.name for node in nodes])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
from core.constants import PAST_PROJECTS_PATH
|
21 |
from core.constants import PROJECT_FOLDER_PATTERN
|
22 |
from core.constants import REDIRECT_URI
|
23 |
+
from core.constants import TABS
|
24 |
from core.names import find_unique_name
|
25 |
import mlcroissant as mlc
|
26 |
|
|
|
328 |
def names(self) -> set[str]:
|
329 |
nodes = self.distribution + self.record_sets
|
330 |
return set([node.name for node in nodes])
|
331 |
+
|
332 |
+
|
333 |
+
class OpenTab:
|
334 |
+
pass
|
335 |
+
|
336 |
+
|
337 |
+
def get_tab():
|
338 |
+
tab = st.session_state.get(OpenTab)
|
339 |
+
if tab is None:
|
340 |
+
return 0
|
341 |
+
else:
|
342 |
+
return tab
|
343 |
+
|
344 |
+
|
345 |
+
def set_tab(tab: str):
|
346 |
+
if tab not in TABS:
|
347 |
+
return
|
348 |
+
index = TABS.index(tab)
|
349 |
+
st.session_state[OpenTab] = index
|
cypress/e2e/createManually.cy.js
CHANGED
@@ -10,13 +10,15 @@ describe('Create a resource manually', () => {
|
|
10 |
cy.visit('http://localhost:8501')
|
11 |
cy.get('button').contains('Create').click()
|
12 |
cy.get('input[aria-label="Name:red[*]"]').type('MyDataset').blur()
|
13 |
-
cy.
|
14 |
-
|
15 |
-
|
16 |
cy.get('input[aria-label="URL:red[*]"]').type('https://mydataset.com', {force: true})
|
17 |
|
18 |
// Create a resource manually.
|
19 |
-
cy.
|
|
|
|
|
20 |
cy.get('[data-testid="stMarkdownContainer"]').contains('Add manually').click()
|
21 |
|
22 |
cy.get('input[aria-label="File name:red[*]"]').type('test.csv').blur()
|
|
|
10 |
cy.visit('http://localhost:8501')
|
11 |
cy.get('button').contains('Create').click()
|
12 |
cy.get('input[aria-label="Name:red[*]"]').type('MyDataset').blur()
|
13 |
+
cy.enter('[title="components.tabs.tabs_component"]').then(getBody => {
|
14 |
+
getBody().contains('Metadata').click()
|
15 |
+
})
|
16 |
cy.get('input[aria-label="URL:red[*]"]').type('https://mydataset.com', {force: true})
|
17 |
|
18 |
// Create a resource manually.
|
19 |
+
cy.enter('[title="components.tabs.tabs_component"]').then(getBody => {
|
20 |
+
getBody().contains('Resources').click()
|
21 |
+
})
|
22 |
cy.get('[data-testid="stMarkdownContainer"]').contains('Add manually').click()
|
23 |
|
24 |
cy.get('input[aria-label="File name:red[*]"]').type('test.csv').blur()
|
cypress/e2e/displayErrors.cy.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
/// <reference types="cypress" />
|
2 |
|
3 |
import 'cypress-file-upload';
|
|
|
4 |
|
5 |
describe('load existing errored croissant', () => {
|
6 |
it('should display errors', () => {
|
@@ -21,10 +22,15 @@ describe('load existing errored croissant', () => {
|
|
21 |
})
|
22 |
cy.get('[data-testid="stMarkdownContainer"]').contains("Errors").should('not.exist')
|
23 |
// Empty the `name` field to create an error:
|
24 |
-
cy.
|
|
|
|
|
25 |
cy.contains('split_enums (2 fields)').click()
|
26 |
cy.get('input[aria-label="Name:red[*]"][value="split_enums"]').should('be.visible').type('{selectall}{backspace}{enter}')
|
27 |
-
cy.
|
|
|
|
|
|
|
28 |
cy.get('[data-testid="stMarkdownContainer"]').contains("Errors").should('exist')
|
29 |
})
|
30 |
})
|
|
|
1 |
/// <reference types="cypress" />
|
2 |
|
3 |
import 'cypress-file-upload';
|
4 |
+
import 'cypress-iframe';
|
5 |
|
6 |
describe('load existing errored croissant', () => {
|
7 |
it('should display errors', () => {
|
|
|
22 |
})
|
23 |
cy.get('[data-testid="stMarkdownContainer"]').contains("Errors").should('not.exist')
|
24 |
// Empty the `name` field to create an error:
|
25 |
+
cy.enter('[title="components.tabs.tabs_component"]').then(getBody => {
|
26 |
+
getBody().contains('RecordSets').click()
|
27 |
+
})
|
28 |
cy.contains('split_enums (2 fields)').click()
|
29 |
cy.get('input[aria-label="Name:red[*]"][value="split_enums"]').should('be.visible').type('{selectall}{backspace}{enter}')
|
30 |
+
cy.timeout(2000)
|
31 |
+
cy.enter('[title="components.tabs.tabs_component"]').then(getBody => {
|
32 |
+
getBody().contains('Overview').click({force: true})
|
33 |
+
})
|
34 |
cy.get('[data-testid="stMarkdownContainer"]').contains("Errors").should('exist')
|
35 |
})
|
36 |
})
|
cypress/e2e/loadCroissant.cy.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
/// <reference types="cypress" />
|
2 |
|
3 |
import 'cypress-file-upload';
|
|
|
4 |
import * as path from 'path';
|
5 |
|
6 |
describe('Editor loads Croissant without Error', () => {
|
@@ -20,7 +21,9 @@ describe('Editor loads Croissant without Error', () => {
|
|
20 |
events: ["dragenter", "drop"],
|
21 |
})
|
22 |
})
|
23 |
-
cy.
|
|
|
|
|
24 |
|
25 |
cy
|
26 |
.get("[data-testid='element-container']")
|
@@ -47,7 +50,9 @@ describe('Editor loads Croissant without Error', () => {
|
|
47 |
|
48 |
cy.get('[data-testid="stException"]').should('not.exist')
|
49 |
|
50 |
-
cy.
|
|
|
|
|
51 |
cy.fixture('titanic.json').then((fileContent) => {
|
52 |
const downloadsFolder = Cypress.config("downloadsFolder");
|
53 |
cy.readFile(path.join(downloadsFolder, "croissant-titanic.json"))
|
|
|
1 |
/// <reference types="cypress" />
|
2 |
|
3 |
import 'cypress-file-upload';
|
4 |
+
import 'cypress-iframe';
|
5 |
import * as path from 'path';
|
6 |
|
7 |
describe('Editor loads Croissant without Error', () => {
|
|
|
21 |
events: ["dragenter", "drop"],
|
22 |
})
|
23 |
})
|
24 |
+
cy.enter('[title="components.tabs.tabs_component"]').then(getBody => {
|
25 |
+
getBody().contains('Metadata').click()
|
26 |
+
})
|
27 |
|
28 |
cy
|
29 |
.get("[data-testid='element-container']")
|
|
|
50 |
|
51 |
cy.get('[data-testid="stException"]').should('not.exist')
|
52 |
|
53 |
+
cy.enter('[title="components.tabs.tabs_component"]').then(getBody => {
|
54 |
+
getBody().contains('Export').click()
|
55 |
+
})
|
56 |
cy.fixture('titanic.json').then((fileContent) => {
|
57 |
const downloadsFolder = Cypress.config("downloadsFolder");
|
58 |
cy.readFile(path.join(downloadsFolder, "croissant-titanic.json"))
|
cypress/e2e/renameDistribution.cy.js
CHANGED
@@ -21,20 +21,18 @@ describe('Renaming of FileObjects/FileSets/RecordSets/Fields.', () => {
|
|
21 |
events: ["dragenter", "drop"],
|
22 |
})
|
23 |
})
|
24 |
-
cy.
|
25 |
-
|
26 |
-
// Click on genders.csv
|
27 |
-
getBody().contains('genders.csv').click()
|
28 |
})
|
29 |
-
// TODO(marcenacp): There is a bug where this action has to be performed twice.
|
30 |
-
cy.get('button').contains('Resources').click()
|
31 |
cy.enter('[title="components.tree.tree_component"]').then(getBody => {
|
32 |
// Click on genders.csv
|
33 |
getBody().contains('genders.csv').click()
|
34 |
})
|
35 |
cy.get('input[aria-label="Name:red[*]"][value="genders.csv"]').type('{selectall}{backspace}the-new-name{enter}')
|
36 |
|
37 |
-
cy.
|
|
|
|
|
38 |
cy.contains('genders').click()
|
39 |
cy.contains('Edit fields details').click()
|
40 |
cy.contains('the-new-name')
|
|
|
21 |
events: ["dragenter", "drop"],
|
22 |
})
|
23 |
})
|
24 |
+
cy.enter('[title="components.tabs.tabs_component"]').then(getBody => {
|
25 |
+
getBody().contains('Resources').click()
|
|
|
|
|
26 |
})
|
|
|
|
|
27 |
cy.enter('[title="components.tree.tree_component"]').then(getBody => {
|
28 |
// Click on genders.csv
|
29 |
getBody().contains('genders.csv').click()
|
30 |
})
|
31 |
cy.get('input[aria-label="Name:red[*]"][value="genders.csv"]').type('{selectall}{backspace}the-new-name{enter}')
|
32 |
|
33 |
+
cy.enter('[title="components.tabs.tabs_component"]').then(getBody => {
|
34 |
+
getBody().contains('RecordSets').click()
|
35 |
+
})
|
36 |
cy.contains('genders').click()
|
37 |
cy.contains('Edit fields details').click()
|
38 |
cy.contains('the-new-name')
|
cypress/e2e/uploadCsv.cy.js
CHANGED
@@ -11,12 +11,14 @@ describe('Editor loads a local CSV as a resource', () => {
|
|
11 |
cy.get('button').contains('Create').click()
|
12 |
|
13 |
cy.get('input[aria-label="Name:red[*]"]').type('MyDataset').blur()
|
14 |
-
cy.
|
15 |
-
|
16 |
-
|
17 |
cy.get('input[aria-label="URL:red[*]"]').type('https://mydataset.com', {force: true})
|
18 |
|
19 |
-
cy.
|
|
|
|
|
20 |
// Drag and drop mimicking: streamlit/e2e/specs/st_file_uploader.spec.js.
|
21 |
cy.fixture('base.csv').then((fileContent) => {
|
22 |
const file = {
|
@@ -43,7 +45,9 @@ describe('Editor loads a local CSV as a resource', () => {
|
|
43 |
cy.contains('First rows of data:')
|
44 |
|
45 |
// On the record set page, we see the record set.
|
46 |
-
cy.
|
|
|
|
|
47 |
cy.contains('base.csv_record_set (2 fields)').click()
|
48 |
// We also see the fields with the proper types.
|
49 |
cy.get('[data-testid="stDataFrameResizable"]').contains("column1")
|
|
|
11 |
cy.get('button').contains('Create').click()
|
12 |
|
13 |
cy.get('input[aria-label="Name:red[*]"]').type('MyDataset').blur()
|
14 |
+
cy.enter('[title="components.tabs.tabs_component"]').then(getBody => {
|
15 |
+
getBody().contains('Metadata').click()
|
16 |
+
})
|
17 |
cy.get('input[aria-label="URL:red[*]"]').type('https://mydataset.com', {force: true})
|
18 |
|
19 |
+
cy.enter('[title="components.tabs.tabs_component"]').then(getBody => {
|
20 |
+
getBody().contains('Resources').click()
|
21 |
+
})
|
22 |
// Drag and drop mimicking: streamlit/e2e/specs/st_file_uploader.spec.js.
|
23 |
cy.fixture('base.csv').then((fileContent) => {
|
24 |
const file = {
|
|
|
45 |
cy.contains('First rows of data:')
|
46 |
|
47 |
// On the record set page, we see the record set.
|
48 |
+
cy.enter('[title="components.tabs.tabs_component"]').then(getBody => {
|
49 |
+
getBody().contains('RecordSets').click()
|
50 |
+
})
|
51 |
cy.contains('base.csv_record_set (2 fields)').click()
|
52 |
// We also see the fields with the proper types.
|
53 |
cy.get('[data-testid="stDataFrameResizable"]').contains("column1")
|
events/fields.py
CHANGED
@@ -3,8 +3,6 @@ from typing import Any
|
|
3 |
|
4 |
import streamlit as st
|
5 |
|
6 |
-
from core.constants import RECORD_SETS
|
7 |
-
from core.query_params import set_tab
|
8 |
from core.state import Field
|
9 |
from core.state import Metadata
|
10 |
import mlcroissant as mlc
|
@@ -79,7 +77,6 @@ def handle_field_change(
|
|
79 |
key: str,
|
80 |
**kwargs,
|
81 |
):
|
82 |
-
set_tab(RECORD_SETS)
|
83 |
value = st.session_state[key]
|
84 |
if change == FieldEvent.NAME:
|
85 |
old_name = field.name
|
|
|
3 |
|
4 |
import streamlit as st
|
5 |
|
|
|
|
|
6 |
from core.state import Field
|
7 |
from core.state import Metadata
|
8 |
import mlcroissant as mlc
|
|
|
77 |
key: str,
|
78 |
**kwargs,
|
79 |
):
|
|
|
80 |
value = st.session_state[key]
|
81 |
if change == FieldEvent.NAME:
|
82 |
old_name = field.name
|
events/metadata.py
CHANGED
@@ -2,8 +2,6 @@ import enum
|
|
2 |
|
3 |
import streamlit as st
|
4 |
|
5 |
-
from core.constants import METADATA
|
6 |
-
from core.query_params import set_tab
|
7 |
from core.state import Metadata
|
8 |
|
9 |
|
@@ -18,7 +16,6 @@ class MetadataEvent(enum.Enum):
|
|
18 |
|
19 |
|
20 |
def handle_metadata_change(event: MetadataEvent, metadata: Metadata, key: str):
|
21 |
-
set_tab(METADATA)
|
22 |
if event == MetadataEvent.NAME:
|
23 |
metadata.name = st.session_state[key]
|
24 |
elif event == MetadataEvent.DESCRIPTION:
|
|
|
2 |
|
3 |
import streamlit as st
|
4 |
|
|
|
|
|
5 |
from core.state import Metadata
|
6 |
|
7 |
|
|
|
16 |
|
17 |
|
18 |
def handle_metadata_change(event: MetadataEvent, metadata: Metadata, key: str):
|
|
|
19 |
if event == MetadataEvent.NAME:
|
20 |
metadata.name = st.session_state[key]
|
21 |
elif event == MetadataEvent.DESCRIPTION:
|
events/record_sets.py
CHANGED
@@ -2,9 +2,7 @@ import enum
|
|
2 |
|
3 |
import streamlit as st
|
4 |
|
5 |
-
from core.constants import RECORD_SETS
|
6 |
from core.query_params import expand_record_set
|
7 |
-
from core.query_params import set_tab
|
8 |
from core.state import Metadata
|
9 |
from core.state import RecordSet
|
10 |
|
@@ -18,7 +16,6 @@ class RecordSetEvent(enum.Enum):
|
|
18 |
|
19 |
|
20 |
def handle_record_set_change(event: RecordSetEvent, record_set: RecordSet, key: str):
|
21 |
-
set_tab(RECORD_SETS)
|
22 |
value = st.session_state[key]
|
23 |
if event == RecordSetEvent.NAME:
|
24 |
old_name = record_set.name
|
|
|
2 |
|
3 |
import streamlit as st
|
4 |
|
|
|
5 |
from core.query_params import expand_record_set
|
|
|
6 |
from core.state import Metadata
|
7 |
from core.state import RecordSet
|
8 |
|
|
|
16 |
|
17 |
|
18 |
def handle_record_set_change(event: RecordSetEvent, record_set: RecordSet, key: str):
|
|
|
19 |
value = st.session_state[key]
|
20 |
if event == RecordSetEvent.NAME:
|
21 |
old_name = record_set.name
|
events/resources.py
CHANGED
@@ -2,8 +2,6 @@ import enum
|
|
2 |
|
3 |
import streamlit as st
|
4 |
|
5 |
-
from core.constants import RESOURCES
|
6 |
-
from core.query_params import set_tab
|
7 |
from core.state import FileObject
|
8 |
from core.state import FileSet
|
9 |
from core.state import Metadata
|
@@ -23,7 +21,6 @@ class ResourceEvent(enum.Enum):
|
|
23 |
|
24 |
|
25 |
def handle_resource_change(event: ResourceEvent, resource: Resource, key: str):
|
26 |
-
set_tab(RESOURCES)
|
27 |
value = st.session_state[key]
|
28 |
if event == ResourceEvent.NAME:
|
29 |
old_name = resource.name
|
|
|
2 |
|
3 |
import streamlit as st
|
4 |
|
|
|
|
|
5 |
from core.state import FileObject
|
6 |
from core.state import FileSet
|
7 |
from core.state import Metadata
|
|
|
21 |
|
22 |
|
23 |
def handle_resource_change(event: ResourceEvent, resource: Resource, key: str):
|
|
|
24 |
value = st.session_state[key]
|
25 |
if event == ResourceEvent.NAME:
|
26 |
old_name = resource.name
|
utils.py
CHANGED
@@ -4,6 +4,7 @@ from core.past_projects import open_project
|
|
4 |
from core.query_params import get_project_timestamp
|
5 |
from core.state import CurrentProject
|
6 |
from core.state import Metadata
|
|
|
7 |
from core.state import SelectedRecordSet
|
8 |
from core.state import SelectedResource
|
9 |
import mlcroissant as mlc
|
@@ -17,17 +18,14 @@ def init_state(force=False):
|
|
17 |
"""Initializes the session state. `force=True` to force re-initializing it."""
|
18 |
|
19 |
timestamp = get_project_timestamp()
|
20 |
-
if
|
21 |
-
|
22 |
-
|
23 |
-
project
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
st.session_state[CurrentProject] =
|
28 |
-
st.session_state[Metadata] = open_project(project.path)
|
29 |
-
else:
|
30 |
-
st.session_state[CurrentProject] = None
|
31 |
|
32 |
if Metadata not in st.session_state or force:
|
33 |
st.session_state[Metadata] = Metadata()
|
@@ -41,6 +39,9 @@ def init_state(force=False):
|
|
41 |
if SelectedResource not in st.session_state or force:
|
42 |
st.session_state[SelectedRecordSet] = None
|
43 |
|
|
|
|
|
|
|
44 |
# Uncomment those lines if you work locally in order to avoid clicks at each reload.
|
45 |
# And comment all previous lines in `init_state`.
|
46 |
# if mlc.Dataset not in st.session_state or force:
|
|
|
4 |
from core.query_params import get_project_timestamp
|
5 |
from core.state import CurrentProject
|
6 |
from core.state import Metadata
|
7 |
+
from core.state import OpenTab
|
8 |
from core.state import SelectedRecordSet
|
9 |
from core.state import SelectedResource
|
10 |
import mlcroissant as mlc
|
|
|
18 |
"""Initializes the session state. `force=True` to force re-initializing it."""
|
19 |
|
20 |
timestamp = get_project_timestamp()
|
21 |
+
if CurrentProject not in st.session_state or force:
|
22 |
+
if timestamp:
|
23 |
+
project = CurrentProject.from_timestamp(timestamp)
|
24 |
+
if project:
|
25 |
+
st.session_state[CurrentProject] = project
|
26 |
+
st.session_state[Metadata] = open_project(project.path)
|
27 |
+
else:
|
28 |
+
st.session_state[CurrentProject] = None
|
|
|
|
|
|
|
29 |
|
30 |
if Metadata not in st.session_state or force:
|
31 |
st.session_state[Metadata] = Metadata()
|
|
|
39 |
if SelectedResource not in st.session_state or force:
|
40 |
st.session_state[SelectedRecordSet] = None
|
41 |
|
42 |
+
if OpenTab not in st.session_state or force:
|
43 |
+
st.session_state[OpenTab] = None
|
44 |
+
|
45 |
# Uncomment those lines if you work locally in order to avoid clicks at each reload.
|
46 |
# And comment all previous lines in `init_state`.
|
47 |
# if mlc.Dataset not in st.session_state or force:
|
views/files.py
CHANGED
@@ -2,14 +2,12 @@ import streamlit as st
|
|
2 |
|
3 |
from components.tree import render_tree
|
4 |
from core.constants import DF_HEIGHT
|
5 |
-
from core.constants import RESOURCES
|
6 |
from core.files import file_from_form
|
7 |
from core.files import file_from_upload
|
8 |
from core.files import file_from_url
|
9 |
from core.files import FILE_OBJECT
|
10 |
from core.files import FILE_TYPES
|
11 |
from core.files import RESOURCE_TYPES
|
12 |
-
from core.query_params import set_tab
|
13 |
from core.record_sets import infer_record_sets
|
14 |
from core.state import FileObject
|
15 |
from core.state import FileSet
|
@@ -27,6 +25,7 @@ _MANUAL_RESOURCE_TYPE_KEY = "create_manually_type"
|
|
27 |
_MANUAL_NAME_KEY = "manual_object_name"
|
28 |
_MANUAL_DESCRIPTION_KEY = "manual_object_description"
|
29 |
_MANUAL_SHA256_KEY = "manual_object_sha256"
|
|
|
30 |
|
31 |
|
32 |
def render_files():
|
@@ -69,7 +68,6 @@ def _render_resources_panel(files: list[Resource]) -> Resource | None:
|
|
69 |
if not name:
|
70 |
return None
|
71 |
file = filename_to_file[name]
|
72 |
-
set_tab(RESOURCES)
|
73 |
return file
|
74 |
|
75 |
|
@@ -104,9 +102,13 @@ def _render_upload_panel():
|
|
104 |
"SHA256",
|
105 |
key=_MANUAL_SHA256_KEY,
|
106 |
)
|
107 |
-
|
|
|
|
|
|
|
108 |
"Parent",
|
109 |
-
|
|
|
110 |
)
|
111 |
|
112 |
def handle_on_click():
|
@@ -126,6 +128,7 @@ def _render_upload_panel():
|
|
126 |
name = st.session_state[_MANUAL_NAME_KEY]
|
127 |
description = st.session_state[_MANUAL_DESCRIPTION_KEY]
|
128 |
sha256 = st.session_state[_MANUAL_SHA256_KEY] if needs_sha256 else None
|
|
|
129 |
errorMessage = (
|
130 |
"Please import either a local file, provide a download URL or fill"
|
131 |
" in all required fields: name"
|
@@ -141,7 +144,7 @@ def _render_upload_panel():
|
|
141 |
)
|
142 |
return
|
143 |
file = file_from_form(
|
144 |
-
file_type, resource_type, name, description, sha256, names
|
145 |
)
|
146 |
|
147 |
st.session_state[Metadata].add_distribution(file)
|
|
|
2 |
|
3 |
from components.tree import render_tree
|
4 |
from core.constants import DF_HEIGHT
|
|
|
5 |
from core.files import file_from_form
|
6 |
from core.files import file_from_upload
|
7 |
from core.files import file_from_url
|
8 |
from core.files import FILE_OBJECT
|
9 |
from core.files import FILE_TYPES
|
10 |
from core.files import RESOURCE_TYPES
|
|
|
11 |
from core.record_sets import infer_record_sets
|
12 |
from core.state import FileObject
|
13 |
from core.state import FileSet
|
|
|
25 |
_MANUAL_NAME_KEY = "manual_object_name"
|
26 |
_MANUAL_DESCRIPTION_KEY = "manual_object_description"
|
27 |
_MANUAL_SHA256_KEY = "manual_object_sha256"
|
28 |
+
_MANUAL_PARENT_KEY = "manual_object_parents"
|
29 |
|
30 |
|
31 |
def render_files():
|
|
|
68 |
if not name:
|
69 |
return None
|
70 |
file = filename_to_file[name]
|
|
|
71 |
return file
|
72 |
|
73 |
|
|
|
102 |
"SHA256",
|
103 |
key=_MANUAL_SHA256_KEY,
|
104 |
)
|
105 |
+
parent_options = [
|
106 |
+
file.name for file in st.session_state[Metadata].distribution
|
107 |
+
]
|
108 |
+
st.multiselect(
|
109 |
"Parent",
|
110 |
+
options=parent_options,
|
111 |
+
key=_MANUAL_PARENT_KEY,
|
112 |
)
|
113 |
|
114 |
def handle_on_click():
|
|
|
128 |
name = st.session_state[_MANUAL_NAME_KEY]
|
129 |
description = st.session_state[_MANUAL_DESCRIPTION_KEY]
|
130 |
sha256 = st.session_state[_MANUAL_SHA256_KEY] if needs_sha256 else None
|
131 |
+
parents = st.session_state[_MANUAL_PARENT_KEY]
|
132 |
errorMessage = (
|
133 |
"Please import either a local file, provide a download URL or fill"
|
134 |
" in all required fields: name"
|
|
|
144 |
)
|
145 |
return
|
146 |
file = file_from_form(
|
147 |
+
file_type, resource_type, name, description, sha256, parents, names
|
148 |
)
|
149 |
|
150 |
st.session_state[Metadata].add_distribution(file)
|
views/overview.py
CHANGED
@@ -1,3 +1,5 @@
|
|
|
|
|
|
1 |
import streamlit as st
|
2 |
|
3 |
from core.state import Metadata
|
@@ -7,6 +9,13 @@ from views.metadata import handle_metadata_change
|
|
7 |
from views.metadata import MetadataEvent
|
8 |
|
9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
def render_overview():
|
11 |
metadata: Metadata = st.session_state[Metadata]
|
12 |
col1, col2 = st.columns([1, 1], gap="medium")
|
@@ -39,8 +48,12 @@ def render_overview():
|
|
39 |
args=(MetadataEvent.DESCRIPTION, metadata, key),
|
40 |
)
|
41 |
|
42 |
-
st.subheader(
|
43 |
-
|
|
|
|
|
|
|
|
|
44 |
with col2:
|
45 |
user_started_editing = metadata.record_sets or metadata.distribution
|
46 |
if user_started_editing:
|
|
|
1 |
+
from typing import Any
|
2 |
+
|
3 |
import streamlit as st
|
4 |
|
5 |
from core.state import Metadata
|
|
|
9 |
from views.metadata import MetadataEvent
|
10 |
|
11 |
|
12 |
+
def _plural(array: list[Any]):
|
13 |
+
if array:
|
14 |
+
return "s"
|
15 |
+
else:
|
16 |
+
return ""
|
17 |
+
|
18 |
+
|
19 |
def render_overview():
|
20 |
metadata: Metadata = st.session_state[Metadata]
|
21 |
col1, col2 = st.columns([1, 1], gap="medium")
|
|
|
48 |
args=(MetadataEvent.DESCRIPTION, metadata, key),
|
49 |
)
|
50 |
|
51 |
+
st.subheader(
|
52 |
+
f"{len(metadata.distribution)} File" + _plural(metadata.distribution)
|
53 |
+
)
|
54 |
+
st.subheader(
|
55 |
+
f"{len(metadata.record_sets)} Record Set" + _plural(metadata.distribution)
|
56 |
+
)
|
57 |
with col2:
|
58 |
user_started_editing = metadata.record_sets or metadata.distribution
|
59 |
if user_started_editing:
|
views/record_sets.py
CHANGED
@@ -92,9 +92,7 @@ def _handle_create_record_set():
|
|
92 |
metadata.add_record_set(RecordSet(name="new-record-set", description=""))
|
93 |
|
94 |
|
95 |
-
def _handle_fields_change(
|
96 |
-
record_set_key: int, record_set: RecordSet, params: dict[str, Any]
|
97 |
-
):
|
98 |
expand_record_set(record_set=record_set)
|
99 |
data_editor_key = _data_editor_key(record_set_key, record_set)
|
100 |
result = st.session_state[data_editor_key]
|
|
|
92 |
metadata.add_record_set(RecordSet(name="new-record-set", description=""))
|
93 |
|
94 |
|
95 |
+
def _handle_fields_change(record_set_key: int, record_set: RecordSet):
|
|
|
|
|
96 |
expand_record_set(record_set=record_set)
|
97 |
data_editor_key = _data_editor_key(record_set_key, record_set)
|
98 |
result = st.session_state[data_editor_key]
|
views/wizard.py
CHANGED
@@ -3,10 +3,16 @@ import json
|
|
3 |
import streamlit as st
|
4 |
import streamlit_nested_layout # Do not remove this allows nesting columns.
|
5 |
|
|
|
|
|
|
|
|
|
|
|
6 |
from core.constants import TABS
|
7 |
from core.past_projects import save_current_project
|
8 |
-
from core.
|
9 |
from core.state import Metadata
|
|
|
10 |
import mlcroissant as mlc
|
11 |
from views.files import render_files
|
12 |
from views.metadata import render_metadata
|
@@ -14,31 +20,33 @@ from views.overview import render_overview
|
|
14 |
from views.record_sets import render_record_sets
|
15 |
|
16 |
|
17 |
-
def
|
18 |
metadata: Metadata = st.session_state[Metadata]
|
19 |
try:
|
20 |
-
|
21 |
-
"
|
22 |
-
|
23 |
-
|
24 |
-
data=json.dumps(metadata.to_canonical().to_json()),
|
25 |
-
help="Export the Croissant JSON-LD",
|
26 |
-
)
|
27 |
except mlc.ValidationError as exception:
|
28 |
-
|
29 |
|
30 |
|
31 |
def render_editor():
|
32 |
-
|
33 |
-
render_export_button(col2)
|
34 |
-
tab1, tab2, tab3, tab4 = col1.tabs(TABS)
|
35 |
|
36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
render_overview()
|
38 |
-
|
39 |
render_metadata()
|
40 |
-
|
41 |
render_files()
|
42 |
-
|
43 |
render_record_sets()
|
44 |
save_current_project()
|
|
|
|
3 |
import streamlit as st
|
4 |
import streamlit_nested_layout # Do not remove this allows nesting columns.
|
5 |
|
6 |
+
from components.tabs import render_tabs
|
7 |
+
from core.constants import METADATA
|
8 |
+
from core.constants import OVERVIEW
|
9 |
+
from core.constants import RECORD_SETS
|
10 |
+
from core.constants import RESOURCES
|
11 |
from core.constants import TABS
|
12 |
from core.past_projects import save_current_project
|
13 |
+
from core.state import get_tab
|
14 |
from core.state import Metadata
|
15 |
+
from core.state import set_tab
|
16 |
import mlcroissant as mlc
|
17 |
from views.files import render_files
|
18 |
from views.metadata import render_metadata
|
|
|
20 |
from views.record_sets import render_record_sets
|
21 |
|
22 |
|
23 |
+
def _export_json() -> str | None:
|
24 |
metadata: Metadata = st.session_state[Metadata]
|
25 |
try:
|
26 |
+
return {
|
27 |
+
"name": f"croissant-{metadata.name.lower()}.json",
|
28 |
+
"content": json.dumps(metadata.to_canonical().to_json()),
|
29 |
+
}
|
|
|
|
|
|
|
30 |
except mlc.ValidationError as exception:
|
31 |
+
return None
|
32 |
|
33 |
|
34 |
def render_editor():
|
35 |
+
export_json = _export_json()
|
|
|
|
|
36 |
|
37 |
+
# Warning: the custom component cannot be nested in a st.columns or it is forced to
|
38 |
+
# re-render even if a `key` is set.
|
39 |
+
selected_tab = get_tab()
|
40 |
+
tab = render_tabs(
|
41 |
+
tabs=TABS, selected_tab=selected_tab, json=export_json, key="tabs"
|
42 |
+
)
|
43 |
+
if tab == OVERVIEW:
|
44 |
render_overview()
|
45 |
+
elif tab == METADATA:
|
46 |
render_metadata()
|
47 |
+
elif tab == RESOURCES:
|
48 |
render_files()
|
49 |
+
elif tab == RECORD_SETS:
|
50 |
render_record_sets()
|
51 |
save_current_project()
|
52 |
+
set_tab(tab)
|