""" Shared utilities for Streamlit UI components. """ import streamlit as st import yaml from src.clients.tidbcloud_client import get_nodespecs, get_regions from src.services.report.filter_options import ( get_dynamic_filter_options, get_matching_spus, ) from src.services.workload_templates import ( CH_TEMPLATE, canonicalize_workload_template, resolve_workflow_yaml_path, ) # Cache API calls to improve performance @st.cache_data(ttl=60, show_spinner="Searching matching SPUs...") def cached_get_matching_spus( cloud=None, generation=None, workload=None, region=None, version=None, tiproxy=None, tidb_cores=None, ): """Cached version of get_matching_spus for better performance.""" return get_matching_spus( cloud=cloud, generation=generation, workload=workload, region=region, version=version, tiproxy=tiproxy, tidb_cores=tidb_cores, ) @st.cache_data(ttl=30, show_spinner="Loading filter options...") def cached_get_dynamic_filter_options( cloud=None, region=None, generation=None, tidb_spu=None, tidb_count=None, tikv_spu=None, tikv_count=None, tiflash_spu=None, tiflash_count=None, workload=None, version=None, tiproxy=None, status=None, ): """Cached version of get_dynamic_filter_options for better performance.""" selected_filters = {} if cloud: selected_filters["cloud"] = cloud if region: selected_filters["region"] = region if generation: selected_filters["generation"] = generation if tidb_spu: selected_filters["tidb_spu"] = tidb_spu if tidb_count is not None: selected_filters["tidb_count"] = tidb_count if tikv_spu: selected_filters["tikv_spu"] = tikv_spu if tikv_count is not None: selected_filters["tikv_count"] = tikv_count if tiflash_spu: selected_filters["tiflash_spu"] = tiflash_spu if tiflash_count is not None: selected_filters["tiflash_count"] = tiflash_count if workload: selected_filters["workload"] = workload if version: selected_filters["version"] = version if tiproxy is not None: selected_filters["tiproxy"] = tiproxy if status: selected_filters["status"] = status return get_dynamic_filter_options(selected_filters) def get_filter_options_excluding(filter_state, exclude_filter): """ Get filter options while excluding a specific filter from the query. This prevents self-referential filtering where a filter's own value limits what options are shown for that filter. Args: filter_state: Dictionary of current filter selections exclude_filter: Name of filter to exclude (e.g., "workload", "generation") Returns: Dictionary of available filter options """ # Build filter parameters, excluding the specified filter params = {} for key in [ "cloud", "region", "generation", "tidb_spu", "tidb_count", "tikv_spu", "tikv_count", "tiflash_spu", "tiflash_count", "workload", "version", "tiproxy", "status", ]: if key != exclude_filter and key in filter_state: params[key] = filter_state[key] return cached_get_dynamic_filter_options(**params) # Cache API calls to improve performance (existing) @st.cache_data(ttl=300, show_spinner="Loading regions...") # Cache for 5 minutes def cached_get_regions(cloud_provider=None): return get_regions(cloud_provider) @st.cache_data(ttl=300, show_spinner="Loading node specifications...") def cached_get_nodespecs(region_id, component_type=None): if component_type: return get_nodespecs(region_id, component_type) return get_nodespecs(region_id) def load_ch_workload_template(workflow): """ Load bench-ch template from __START__ node inputs. Returns dict with ch-specific parameters. """ result = {"template_type": "bench-ch", "params": {}} # Find __START__ node nodes = workflow.get("nodes", []) for node in nodes: if node.get("id") == "__START__": data = node.get("data", {}) inputs = data.get("inputs", []) # Extract the specific parameters we need for input_param in inputs: name = input_param.get("name") value = input_param.get("value") if name in [ "threads", "duration", "warehouses", "workload_node_memory", "workload_node_cpu", ]: result["params"][name] = value break return result def load_workload_template(template_name): canonical = canonicalize_workload_template(template_name) yaml_path = resolve_workflow_yaml_path(canonical) with open(yaml_path, "r") as f: workflow = yaml.safe_load(f) # Handle bench-ch template differently if canonical in (CH_TEMPLATE, "bench-ch-x"): return load_ch_workload_template(workflow) result = {"nodes": {}} nodes = workflow.get("nodes", []) for node in nodes: data = node.get("data") or {} name = data.get("name") if not name: continue node_type = node.get("type") spec = data.get("spec") or {} if node_type == "ti_workload_call": entry = {} for field in ("workload", "prepare"): content = spec.get(field) if not content: continue try: parsed = yaml.safe_load(content) except yaml.YAMLError: continue if isinstance(parsed, dict): entry[field] = parsed if entry: result["nodes"][name] = entry elif node_type == "sleep": duration = spec.get("duration") if duration is not None: result["nodes"][name] = {"sleep": {"duration": duration}} return result