Skip to content

Commit c059c84

Browse files
Merge pull request #65 from DataKitchen/release/5.32.2
Release/5.32.2
2 parents fdc284b + 11d5926 commit c059c84

515 files changed

Lines changed: 33136 additions & 25759 deletions

File tree

Some content is hidden

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

.github/actions/publish_charts/action.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ runs:
2222
helm repo add bitnami https://charts.bitnami.com/bitnami
2323
2424
- name: Run chart-releaser
25-
uses: helm/chart-releaser-action@v1.6.0
25+
uses: helm/chart-releaser-action@v1.7.0
2626
with:
2727
charts_dir: deploy/charts
2828
skip_existing: 'true'

deploy/build_api_docs.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Export the TestGen OpenAPI spec as a JSON file.
2+
3+
Usage:
4+
python deploy/build_api_docs.py [--output PATH] [--version VERSION]
5+
6+
The output JSON is served by a static Redoc HTML shell alongside it.
7+
"""
8+
9+
import argparse
10+
import json
11+
from pathlib import Path
12+
13+
from testgen.server import create_app
14+
15+
_REPO_ROOT = Path(__file__).resolve().parent.parent
16+
17+
18+
def _read_version_from_pyproject() -> str:
19+
try:
20+
import tomllib # Python 3.11+
21+
except ImportError:
22+
import tomli as tomllib # type: ignore[no-redef]
23+
24+
with open(_REPO_ROOT / "pyproject.toml", "rb") as f:
25+
return tomllib.load(f)["project"]["version"]
26+
27+
28+
def main() -> None:
29+
parser = argparse.ArgumentParser(description="Export the TestGen OpenAPI spec as JSON.")
30+
parser.add_argument(
31+
"--output",
32+
type=Path,
33+
default=Path("docs/api/openapi.json"),
34+
help="Output JSON file path (default: docs/api/openapi.json, relative to cwd)",
35+
)
36+
parser.add_argument("--version", help="API version string (default: read from pyproject.toml)")
37+
args = parser.parse_args()
38+
39+
version = args.version or _read_version_from_pyproject()
40+
app = create_app(version=version)
41+
spec = app.openapi()
42+
43+
output: Path = args.output
44+
output.parent.mkdir(parents=True, exist_ok=True)
45+
output.write_text(json.dumps(spec, indent=2) + "\n", encoding="utf-8")
46+
print(f"Exported OpenAPI spec -> {output} (v{version})")
47+
48+
49+
if __name__ == "__main__":
50+
main()

deploy/build_mcp_docs.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"""Export the TestGen MCP server as a Markdown reference page.
2+
3+
Usage:
4+
python deploy/build_mcp_docs.py [--output PATH]
5+
6+
Introspects the FastMCP instance built by ``build_mcp_server()`` and emits
7+
a single Markdown page listing prompts, tools, and resources. Tools are
8+
grouped by the ``_DOC_GROUP`` constant defined on each tool module — when
9+
adding a new tool module, declare ``_DOC_GROUP = "..."`` so the new tools
10+
land under the right heading automatically.
11+
"""
12+
13+
import argparse
14+
import re
15+
import sys
16+
import textwrap
17+
from pathlib import Path
18+
from typing import Any
19+
20+
from testgen.mcp.server import build_mcp_server
21+
from testgen.mcp.tools.common import DocGroup
22+
23+
_DEFAULT_OUTPUT = Path("docs/mcp/supported-tools.md")
24+
_ARGS_HEADER_RE = re.compile(r"^\s*Args:\s*$", re.MULTILINE)
25+
26+
# Order in which tool groups appear on the page. Each entry is a ``DocGroup``
27+
# member; tools whose module declares a ``_DOC_GROUP`` not in this list are
28+
# appended after these in the order they are first seen.
29+
_GROUP_ORDER: list[DocGroup] = [
30+
DocGroup.DISCOVER,
31+
DocGroup.INVESTIGATE,
32+
DocGroup.BROWSE_PROFILING,
33+
DocGroup.TRIGGER,
34+
]
35+
_FALLBACK_GROUP = "Other tools"
36+
37+
38+
def _short_description(docstring: str) -> str:
39+
"""Return the first prose paragraph of a docstring, stripped of Args/Returns sections."""
40+
if not docstring:
41+
return ""
42+
text = textwrap.dedent(docstring).strip()
43+
match = _ARGS_HEADER_RE.search(text)
44+
if match:
45+
text = text[: match.start()].rstrip()
46+
first_paragraph = text.split("\n\n", 1)[0]
47+
return " ".join(line.strip() for line in first_paragraph.splitlines())
48+
49+
50+
def _entry_name(item: Any) -> str:
51+
"""Display name for a tool, resource, or prompt."""
52+
return str(getattr(item, "uri", None) or item.name)
53+
54+
55+
def _render_entry(item: Any) -> str:
56+
description = _short_description(item.description or "")
57+
return f"- **`{_entry_name(item)}`** — {description}"
58+
59+
60+
def _group_for_tool(tool: Any) -> str:
61+
"""Resolve a tool's display group via its module's ``_DOC_GROUP`` constant."""
62+
module = sys.modules.get(tool.fn.__module__)
63+
group = getattr(module, "_DOC_GROUP", None)
64+
return str(group) if group is not None else _FALLBACK_GROUP
65+
66+
67+
def _group_tools(tools: list[Any]) -> list[tuple[str, list[Any]]]:
68+
"""Bucket tools by their module's ``_DOC_GROUP``, ordered by ``_GROUP_ORDER``."""
69+
buckets: dict[str, list[Any]] = {}
70+
for tool in tools:
71+
buckets.setdefault(_group_for_tool(tool), []).append(tool)
72+
73+
ordered: list[tuple[str, list[Any]]] = []
74+
for group in _GROUP_ORDER:
75+
title = str(group)
76+
if title in buckets:
77+
ordered.append((title, sorted(buckets.pop(title), key=lambda t: t.name)))
78+
for title, bucket in buckets.items():
79+
ordered.append((title, sorted(bucket, key=lambda t: t.name)))
80+
return ordered
81+
82+
83+
def _build_markdown(mcp: Any) -> str:
84+
tools = mcp._tool_manager.list_tools()
85+
resources = sorted(mcp._resource_manager.list_resources(), key=lambda r: str(r.uri))
86+
prompts = sorted(mcp._prompt_manager.list_prompts(), key=lambda p: p.name)
87+
grouped_tools = _group_tools(list(tools))
88+
89+
parts: list[str] = [
90+
"# Supported Tools",
91+
"",
92+
"The TestGen MCP server exposes the prompts, tools, and resources listed below.",
93+
"",
94+
"For setup instructions, see [Set up the MCP Server](setup.md).",
95+
"For example questions to ask an assistant, see [MCP Server](index.md#what-you-can-ask).",
96+
"",
97+
"## Prompts",
98+
"",
99+
(
100+
"Prompts are pre-built workflows you can invoke directly through your AI client — typically "
101+
"as a slash command (for example, `/testgen:table_health` in Claude Code) or "
102+
"from a quick-action menu. They orchestrate several tool calls behind the scenes for common "
103+
"investigations. Exact UX varies by client."
104+
),
105+
"",
106+
]
107+
parts.extend(_render_entry(prompt) for prompt in prompts)
108+
parts.append("")
109+
110+
parts.extend(["## Tools", "", "Tools are operations the assistant calls during a conversation, picked based on what you ask.", ""])
111+
for heading, bucket in grouped_tools:
112+
parts.append(f"### {heading}")
113+
parts.append("")
114+
parts.extend(_render_entry(tool) for tool in bucket)
115+
parts.append("")
116+
117+
parts.extend(
118+
[
119+
"## Resources",
120+
"",
121+
"Resources are static reference documents that AI clients can fetch by URI.",
122+
"",
123+
]
124+
)
125+
parts.extend(_render_entry(resource) for resource in resources)
126+
127+
return "\n".join(parts).rstrip() + "\n"
128+
129+
130+
def main() -> None:
131+
parser = argparse.ArgumentParser(description="Export the TestGen MCP server as a Markdown reference.")
132+
parser.add_argument(
133+
"--output",
134+
type=Path,
135+
default=_DEFAULT_OUTPUT,
136+
help=f"Output Markdown file path (default: {_DEFAULT_OUTPUT}, relative to cwd)",
137+
)
138+
args = parser.parse_args()
139+
140+
mcp = build_mcp_server(api_base_url="https://testgen.example.com")
141+
markdown = _build_markdown(mcp)
142+
143+
output: Path = args.output
144+
output.parent.mkdir(parents=True, exist_ok=True)
145+
frontmatter = "---\nsearch:\n boost: 0.5\n---\n"
146+
output.write_text(frontmatter + markdown, encoding="utf-8")
147+
print(f"Exported MCP supported tools -> {output}")
148+
149+
150+
if __name__ == "__main__":
151+
main()

deploy/charts/testgen-app/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type: application
1515
# This is the chart version. This version number should be incremented each time you make changes
1616
# to the chart and its templates, including the app version.
1717
# Versions are expected to follow Semantic Versioning (https://semver.org/)
18-
version: 1.1.0
18+
version: 1.2.0
1919

2020
# This is the version number of the application being deployed. This version number should be
2121
# incremented each time you make changes to the application. Versions are not expected to

deploy/charts/testgen-app/templates/_environment.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
- name: TG_EMAIL_FROM_ADDRESS
4444
value: {{ .fromAddress | quote }}
4545
{{- end -}}
46+
{{- if .Values.testgen.uiBaseUrl }}
47+
- name: TG_UI_BASE_URL
48+
value: {{ .Values.testgen.uiBaseUrl | quote }}
49+
{{- end }}
4650
{{- end -}}
4751

4852
{{- define "testgen.hookEnvironment" -}}

deploy/charts/testgen-app/values.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ testgen:
2222
port:
2323
username:
2424
password:
25+
uiBaseUrl:
2526
labels:
2627

2728
cliHooks:

deploy/testgen-base.dockerfile

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@ RUN apk update && apk upgrade && apk add --no-cache \
2929
unixodbc=2.3.14-r0 \
3030
unixodbc-dev=2.3.14-r0 \
3131
libarrow=21.0.0-r4 \
32-
apache-arrow-dev=21.0.0-r4 \
33-
# Pinned versions for security
34-
xz=5.8.2-r0
32+
apache-arrow-dev=21.0.0-r4
3533

3634
COPY --chmod=775 ./deploy/install_linuxodbc.sh /tmp/dk/install_linuxodbc.sh
3735
RUN /tmp/dk/install_linuxodbc.sh

deploy/testgen.dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ARG TESTGEN_BASE_LABEL=v14
1+
ARG TESTGEN_BASE_LABEL=v15
22

33
FROM datakitchen/dataops-testgen-base:${TESTGEN_BASE_LABEL} AS release-image
44

docs/configuration.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,3 +282,15 @@ default: `dataset`
282282

283283
When exporting to your instance of Observabilty, the key sent to the events API to identify the components.
284284
default: `default`
285+
286+
### URL Configuration
287+
288+
#### `TG_UI_BASE_URL`
289+
290+
Externally-reachable base URL for the TestGen web UI. Used in email notification links and PDF report links so recipients can click through to the correct address.
291+
292+
Must be set in production when TestGen is behind a reverse proxy or load balancer. If not set, defaults to `http://localhost:<STREAMLIT_SERVER_PORT>`.
293+
294+
Example: `https://testgen.example.com`
295+
296+
default: `http://localhost:8501`

invocations/dev.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__all__ = ["build_public_image", "clean", "install", "lint"]
1+
__all__ = ["build_api_docs", "build_mcp_docs", "build_public_image", "clean", "install", "lint"]
22

33
import re
44
from os.path import exists, join
@@ -72,6 +72,26 @@ def clean(ctx: Context) -> None:
7272
print("Cleaning finished!")
7373

7474

75+
@task(name="build-api-docs", pre=(install,))
76+
def build_api_docs(ctx: Context, version: str = "", output: str = "") -> None:
77+
"""Exports the OpenAPI spec as JSON for the static API docs."""
78+
args = []
79+
if version:
80+
args.append(f"--version {version}")
81+
if output:
82+
args.append(f"--output {output}")
83+
ctx.run(f"python deploy/build_api_docs.py {' '.join(args)}")
84+
85+
86+
@task(name="build-mcp-docs", pre=(install,))
87+
def build_mcp_docs(ctx: Context, output: str = "") -> None:
88+
"""Exports the MCP supported-tools page from the FastMCP server."""
89+
args = []
90+
if output:
91+
args.append(f"--output {output}")
92+
ctx.run(f"python deploy/build_mcp_docs.py {' '.join(args)}")
93+
94+
7595
@task(
7696
pre=(required_tools, prep_dk_builer),
7797
iterable=["label"],

0 commit comments

Comments
 (0)