Skip to content

Commit 9ac12ce

Browse files
authored
UI tabs replaced with top-down scroll UX. Data and zip generation removed and uses the new graph-data-generator package (#18)
1 parent 6f8eb9c commit 9ac12ce

File tree

142 files changed

+2542
-23686
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

142 files changed

+2542
-23686
lines changed

Pipfile

+2-14
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,11 @@ verify_ssl = true
44
name = "pypi"
55

66
[packages]
7-
pytest = "*"
8-
streamlit = "*"
9-
streamlit-extras = "*"
10-
pyperclip = "*"
11-
streamlit-agraph = "*"
12-
lorem-text = "*"
13-
faker = "*"
147
streamlit-player = "*"
15-
requests = "*"
16-
pandas = "*"
17-
random-address = "*"
18-
openai = "*"
19-
numpy = "*"
20-
certifi = "*"
8+
graph-data-generator = "*"
9+
streamlit-extras = "*"
2110

2211
[dev-packages]
23-
mock-generators = {editable = true, path = "."}
2412

2513
[requires]
2614
python_version = "3.11"

Pipfile.lock

+897-1,161
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+5-13
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
11
# MOCK GRAPH DATA GENERATOR
2-
This is a prototype app for generating mock graph data for [Neo4j](https://neo4j.com/) database instances.
3-
4-
The app uses [Streamlit](https://streamlit.io/) to create and manage the UI interface.
5-
6-
7-
## Recommendations
8-
Connect with a Chromium browser. Known issues when using with Safari, especially with interfacing with arrows and the data-importer.
2+
Applet with Streamlit UI interface to conveniently use and test the graph-data-generator package.
93

4+
## Install Poetry
5+
This applet uses [Poetry](https://python-poetry.org) for dependency management.
106

117
## Running
12-
Locally
13-
```
14-
pipenv shell
15-
pipenv sync
16-
pipenv run streamlit run mock_generators/app.py
17-
```
8+
`poetry update`
9+
`poetry run streamlit run graph_data_generator_streamlit/app.py`

graph_data_generator_streamlit/__init__.py

Whitespace-only changes.

graph_data_generator_streamlit/app.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import streamlit as st
2+
from tabs.instructions_tab import instructions_tab
3+
from tabs.design_tab import design_tab
4+
from tabs.generate_tab import generate_tab
5+
from tabs.data_importer_tab import data_importer_tab
6+
import logging
7+
import sys
8+
9+
# SETUP
10+
st.set_page_config(layout="wide")
11+
logging.getLogger().setLevel(logging.DEBUG)
12+
logging.info(f'App Started')
13+
14+
instructions_tab()
15+
16+
st.markdown("-------------")
17+
st.markdown("**① DESIGN**")
18+
design_tab()
19+
20+
st.markdown("-------------")
21+
st.markdown("**② GENERATE**")
22+
generate_tab()
23+
24+
st.markdown("-------------")
25+
st.markdown("**③ IMPORT**")
26+
data_importer_tab()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import streamlit as st
2+
import streamlit.components.v1 as components
3+
from streamlit_extras.colored_header import colored_header
4+
5+
def data_importer_tab():
6+
is_local = st.checkbox("Use HTTP", value=False, help="Select Use HTTP if connecting with a local Neo4j instance.")
7+
8+
if is_local == True:
9+
components.iframe("http://data-importer.graphapp.io/", height=1000, scrolling=False)
10+
else:
11+
components.iframe("https://data-importer.graphapp.io/", height=1000, scrolling=False)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import streamlit as st
2+
import streamlit.components.v1 as components
3+
from graph_data_generator import generators, GeneratorType, Generator
4+
5+
import os
6+
import logging
7+
import datetime
8+
import json
9+
import sys
10+
import io
11+
12+
def load_string(filepath: str, default=None):
13+
if os.path.isfile(filepath) == False and os.access(filepath, os.R_OK) == False:
14+
with io.open(os.path.join(filepath), 'r') as _:
15+
logging.info(f"file_utils.py: No file at {filepath}")
16+
return default
17+
with open(filepath, 'r') as f:
18+
return f.read()
19+
20+
def filtered_generators(
21+
search_term: str,
22+
type_filter: str,
23+
generators: dict[str, Generator]):
24+
25+
def passes_search(search_term: str,
26+
generator: Generator):
27+
if search_term is None:
28+
return True
29+
if search_term != "" and search_term.lower() not in generator.name.lower() and search_term.lower() not in generator.description.lower():
30+
return False
31+
return True
32+
33+
def passes_type_filter(type_filter: str,
34+
generator: Generator):
35+
if type_filter != "All" and type_filter != generator.type.to_string():
36+
return False
37+
return True
38+
39+
return [generator for key, generator in sorted(generators.items(), key=lambda gen:(gen[1].name)) if passes_search(search_term, generator) and passes_type_filter(type_filter, generator)]
40+
41+
42+
def design_tab():
43+
st.markdown(
44+
"""
45+
Use the arrows app to quickly design your mock data. When ready, click on the `Download/Export` button, select the `JSON` tab, then copy the .JSON data to the **② Generate** section
46+
"""
47+
)
48+
c1, c2 = st.columns([8,2])
49+
with c1:
50+
components.iframe("https://arrows.app", height=1000, scrolling=False)
51+
with c2:
52+
search_term = st.text_input("Search Generators by keyword", "", help="Generators are functions for creating mock data.")
53+
# st.write(generators)
54+
type_filter = st.radio("Filter Generator outputs by type", ["All", "String", "Bool", "Integer", "Float", "Function", "Datetime", "Assignment"])
55+
56+
total_count = len(generators)
57+
display_generators = filtered_generators(search_term, type_filter, generators)
58+
count = len(display_generators)
59+
st.write(f"Displaying {count} of {total_count} generators:")
60+
for generator in display_generators:
61+
# for _, generator in sorted(generators.items(), key=lambda gen:(gen[1].name)):
62+
63+
# # Filtering
64+
# if type_filter != "All" and type_filter != generator.type.to_string():
65+
# continue
66+
67+
# if search_term != "" and search_term.lower() not in generator.name.lower() and search_term.lower() not in generator.description.lower():
68+
# continue
69+
70+
# # Don't have a vertical / horizontal scroll container, so limit display
71+
# if index > 10:
72+
# continue
73+
74+
# try:
75+
# # Check that we can load code first
76+
# code_filepath = generator.code_url
77+
# code_file = load_string(code_filepath)
78+
# except:
79+
# logging.error(f"Could not load generator code from {code_filepath}: {sys.exc_info()[0]}")
80+
# continue
81+
82+
with st.expander(generator.name):
83+
# st.markdown("------------------")
84+
st.write(generator.name)
85+
st.write(f"\n {generator.description}")
86+
# st.write(f'Generator Code:\n')
87+
# st.markdown(f'```python\n{code_file}\n```')
88+
args = generator.args
89+
arg_inputs = []
90+
for arg in args:
91+
if arg.type == GeneratorType.STRING:
92+
arg_inputs.append(st.text_input(
93+
label=arg.label,
94+
value = arg.default,
95+
key = f'{generator.name}_{arg.label}',
96+
placeholder = f'{arg.hint}',
97+
help = f'{arg.description}'
98+
))
99+
if arg.type == GeneratorType.INT or arg.type == GeneratorType.FLOAT:
100+
arg_inputs.append(st.number_input(
101+
label= arg.label,
102+
value= arg.default,
103+
key = f'{generator.name}_{arg.label}'
104+
))
105+
if arg.type == GeneratorType.BOOL:
106+
arg_inputs.append(st.radio(
107+
label=arg.label,
108+
index=arg.default,
109+
key = f'{generator.name}_{arg.label}'
110+
))
111+
if arg.type == GeneratorType.DATETIME:
112+
arg_inputs.append(st.date_input(
113+
label=arg.label,
114+
value=datetime.datetime.fromisoformat(arg.default),
115+
key = f'{generator.name}_{arg.label}'
116+
))
117+
# if c.button("Generate Example Output", key=f"run_{generator.name}"):
118+
# try:
119+
# module = __import__(generator.import_url(), fromlist=['generate'])
120+
# # logging.info(f'arg_inputs: {arg_inputs}')
121+
122+
# # TODO: Need a fake list of Node Mappings to test out assignment generators
123+
# result = module.generate(arg_inputs)
124+
# c.write(f'Output: {result}')
125+
# except:
126+
# c.error(f"Problem running generator {generator.name}: {sys.exc_info()[0]}")
127+
128+
# Property Code
129+
# name = generator.name
130+
args = arg_inputs
131+
obj = {
132+
generator.gid: args
133+
}
134+
135+
json_string = json.dumps(obj, default=str)
136+
137+
st.write('Copy & paste below as a node/relationship property value in arrows.app')
138+
st.code(f'{json_string}')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Now the new Generate Tab
2+
3+
import streamlit as st
4+
from graph_data_generator import generators
5+
import graph_data_generator as gdg
6+
7+
def generate_tab():
8+
9+
# c1, c2 = st.tabs(["Copy & Paste", "Import File"])
10+
# with c1:
11+
12+
st.write("Copy & Paste Arrows.app .JSON file")
13+
filename = st.text_input("Name of file", value="mock_data")
14+
txt = st.text_area("Paste arrows.app JSON here", height=500, help="Click out of the text area to generate the .zip file.")
15+
if txt is not None and txt != "":
16+
17+
try:
18+
zip = gdg.generate(txt, enable_logging=True)
19+
if zip is None:
20+
st.warning('Unexpected problem generating file. Try an alternate JSON input')
21+
else:
22+
st.download_button(
23+
label = "Download .zip file",
24+
data = zip,
25+
file_name = f"{filename}.zip",
26+
mime = "text/plain"
27+
)
28+
except Exception as e:
29+
st.error(e)
30+
31+
# with c2:
32+
# uploaded_file = st.file_uploader("Upload an arrows JSON file", type="json")
33+
# if uploaded_file is not None:
34+
# # To convert to a string based IO:
35+
# stringio = StringIO(uploaded_file.getvalue().decode("utf-8"))
36+
# # To read file as string:
37+
# current_file = stringio.read()
38+
39+
# # Save to session state
40+
# st.session_state[MAPPINGS] = Mapping.empty()
41+
42+
# name = uploaded_file.name.split(".")[0]
43+
# if current_file is not None:
44+
# # TODO: Verfiy file is valid arrows JSON
45+
# generators = st.session_state[GENERATORS]
46+
# mapping = mapping_from_json(
47+
# current_file,
48+
# generators)
49+
# zip = generate_zip(mapping)
50+
# st.download_button(
51+
# label = "Download .zip file",
52+
# data = zip,
53+
# file_name = f"{name}.zip",
54+
# mime = "text/plain"
55+
# )
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from streamlit_player import st_player
2+
import streamlit as st
3+
4+
def instructions_tab():
5+
st.title("Graph Data Generator App")
6+
st.markdown(
7+
"""
8+
This app is a central collection tools built around the [graph-data-generator](https://pypi.org/project/graph-data-generator/) package for generating .csvs of interconnected mock data that can be imported into databases, including a [Neo4j](https://neo4j.com) graph database.
9+
10+
NOTES:
11+
- Chromium browser recommended for best experience.
12+
- Each tool may require independent logins with first use.
13+
""")
14+
# url = st.secrets["VIDEO_TUTORIAL_URL"]
15+
# Disabling until updated video is available
16+
# st_player(url, height=600)

mock_generators/__init__.py

-3
This file was deleted.

mock_generators/app.py

-52
This file was deleted.

mock_generators/base_generators/README.md

-1
This file was deleted.

mock_generators/base_generators/bool_generator.py

-10
This file was deleted.

mock_generators/base_generators/datetime_generator.py

-10
This file was deleted.

0 commit comments

Comments
 (0)