Skip to content

Commit 4403da9

Browse files
authored
[Vis Tools] Load examples on app start up (#6068)
## Related PR This is a follow-up to [6066](#6066). ## Issue [b/489513837](https://buganizer.corp.google.com/issues/489513837) ## Description This PR updates how the example content that populate the vis tools are loaded. Beforehand, both the very old examples and the newly added examples were loaded directly in the route, and so read from the relevant files on each request. This functionality is now performed on app started. ## Testing All the same testing considerations from the original PR apply here. This should not change anything visually. However, an important note about testing is that you will now have to restart the server to effect changes to the vis tools (due to the fact that the files are now loaded once on server start). ## Notes I've also updated the route level loading process to make a copy of the server cached JSON, rather than loading it directly. This is because now that this cache is shared, accidental updates of this anywhere downstream would affect the cache for all subsequent requests.
1 parent ce14087 commit 4403da9

5 files changed

Lines changed: 47 additions & 23 deletions

File tree

server/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from server.lib.nl.detection import llm_prompt
4242
from server.lib.nl.detection.agent.agent import create_detection_agent
4343
import server.lib.util as libutil
44+
from server.routes.tools import html as tools_html
4445
import server.services.bigtable as bt
4546
from server.services.discovery import configure_endpoints_from_ingress
4647
from server.services.discovery import get_health_check_urls
@@ -450,6 +451,9 @@ def create_app(nl_root=DEFAULT_NL_ROOT):
450451
custom_dc_template_folder = app.config.get(
451452
'CUSTOM_DC_TEMPLATE_FOLDER', None) or app.config.get('ENV', None)
452453

454+
app.config['VIS_TOOL_EXAMPLES'] = tools_html.get_all_tool_examples(
455+
app, custom_dc_template_folder)
456+
453457
# Get and save the blocklisted svgs.
454458
blocklist_svg = []
455459
if os.path.isfile(BLOCKLIST_SVG_FILE):

server/routes/tools/html.py

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import copy
16+
import glob
1517
import json
1618
import os
1719

@@ -27,28 +29,46 @@
2729
bp = flask.Blueprint("tools", __name__, url_prefix='/tools')
2830

2931

30-
def get_example_file(tool):
31-
example_file = os.path.join(current_app.root_path, 'templates/custom_dc',
32-
g.custom_dc_template_folder,
33-
'{}_examples.json'.format(tool))
34-
if os.path.exists(example_file):
35-
return example_file
32+
def get_all_tool_examples(app, custom_dc_template_folder):
33+
"""Finds and loads all example files into a dictionary."""
34+
example_paths = {}
35+
36+
# Find all example files (with custom DC versions taking precedence)
37+
search_dirs = [os.path.join(app.root_path, 'templates/tools')]
38+
if custom_dc_template_folder:
39+
search_dirs.append(
40+
os.path.join(app.root_path, 'templates/custom_dc',
41+
custom_dc_template_folder))
42+
for directory in search_dirs:
43+
for filepath in glob.glob(os.path.join(directory, '*_examples.json')):
44+
filename = os.path.basename(filepath)
45+
tool_name = filename.removesuffix('_examples.json')
46+
example_paths[tool_name] = filepath
47+
48+
# Load the examples and return them
49+
loaded_examples = {}
50+
for tool, filepath in example_paths.items():
51+
try:
52+
with open(filepath) as f:
53+
loaded_examples[tool] = json.load(f)
54+
except json.JSONDecodeError:
55+
app.logger.error('Malformed JSON for tool %s in %s', tool, filepath)
56+
except OSError as e:
57+
app.logger.error('Failed to read tool %s at %s: %s', tool, filepath, e)
3658

37-
return os.path.join(current_app.root_path,
38-
'templates/tools/{}_examples.json'.format(tool))
59+
return loaded_examples
3960

4061

41-
def load_example_file(tool_or_filename, default=None):
62+
def _load_example_file(tool_or_filename, default=None):
4263
"""Loads a JSON example file, returning a default if missing."""
4364
if default is None:
4465
default = {}
45-
filepath = get_example_file(tool_or_filename)
46-
if os.path.exists(filepath):
47-
try:
48-
with open(filepath) as f:
49-
return json.load(f)
50-
except json.JSONDecodeError:
51-
current_app.logger.error('Malformed JSON in %s', filepath)
66+
67+
examples = current_app.config.get('VIS_TOOL_EXAMPLES', {})
68+
data = examples.get(tool_or_filename)
69+
70+
if data is not None:
71+
return copy.deepcopy(data)
5272
return default
5373

5474

@@ -57,8 +77,8 @@ def _get_vis_tool_examples(tool_name):
5777
use_standardized_ui = is_feature_enabled(STANDARDIZED_VIS_TOOL_FEATURE_FLAG,
5878
request=request)
5979
if use_standardized_ui:
60-
return {}, load_example_file(f'{tool_name}_vis_tool', default=[]), True
61-
return load_example_file(tool_name, default={}), [], False
80+
return {}, _load_example_file(f'{tool_name}_vis_tool', default=[]), True
81+
return _load_example_file(tool_name, default={}), [], False
6282

6383

6484
@bp.route('/timeline')
@@ -124,7 +144,7 @@ def stat_var():
124144
def download():
125145
# List of DCIDs displayed in the info page for download tool
126146
# NOTE: EXACTLY 2 EXAMPLES REQUIRED.
127-
info_places = load_example_file('download', default=[])
147+
info_places = _load_example_file('download', default=[])
128148

129149
return flask.render_template('tools/download.html',
130150
info_places=json.dumps(info_places),
@@ -136,7 +156,7 @@ def download():
136156

137157
@bp.route('/visualization')
138158
def visualization():
139-
info_json = load_example_file('visualization', default={})
159+
info_json = _load_example_file('visualization', default={})
140160

141161
return flask.render_template('tools/visualization.html',
142162
manual_ga_pageview=True,

server/templates/tools/map.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
<div id="main-pane"></div>
3434
<script>
3535
globalThis.infoConfig = {{ info_json | tojson | safe }};
36-
globalThis.visToolExamples = {{ vis_tool_examples_json | default([]) | tojson | safe }};
36+
globalThis.visToolExamples = {{ vis_tool_examples_json | tojson | safe }};
3737
globalThis.minStatVarGeoCoverage = {{ config['MIN_STAT_VAR_GEO_COVERAGE'] | int(1) }};
3838
</script>
3939
{% endblock %}

server/templates/tools/scatter.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
<div id="main-pane"></div>
3333
<script>
3434
globalThis.infoConfig = {{ info_json | tojson | safe }};
35-
globalThis.visToolExamples = {{ vis_tool_examples_json | default([]) | tojson | safe }};
35+
globalThis.visToolExamples = {{ vis_tool_examples_json | tojson | safe }};
3636
globalThis.minStatVarGeoCoverage = {{ config['MIN_STAT_VAR_GEO_COVERAGE'] | int(1) }};
3737
</script>
3838
{% endblock %}

server/templates/tools/timeline.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
<div id="main-pane"></div>
3333
<script>
3434
globalThis.infoConfig = {{ info_json | tojson | safe }};
35-
globalThis.visToolExamples = {{ vis_tool_examples_json | default([]) | tojson | safe }};
35+
globalThis.visToolExamples = {{ vis_tool_examples_json | tojson | safe }};
3636
</script>
3737
{% endblock %}
3838

0 commit comments

Comments
 (0)