Skip to content

Commit 5ceaddc

Browse files
Merge pull request #38 from DataKitchen/release/4.0.9
Release/4.0.9
2 parents f4256cf + 0a521a6 commit 5ceaddc

File tree

94 files changed

+1920
-582
lines changed

Some content is hidden

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

94 files changed

+1920
-582
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ export TG_DECRYPT_SALT=<encryption_salt>
135135
export TESTGEN_USERNAME=<username>
136136
export TESTGEN_PASSWORD=<password>
137137

138+
# Set an arbitrary base64-encoded string to be used for signing authentication tokens
139+
export TG_JWT_HASHING_KEY=<base64_key>
140+
138141
# Set an accessible path for storing application logs
139142
export TESTGEN_LOG_FILE_PATH=<path_for_logs>
140143
```
@@ -149,12 +152,12 @@ Make sure the PostgreSQL database server is up and running. Initialize the appli
149152
testgen setup-system-db --yes
150153
```
151154

152-
### Run the TestGen UI
155+
### Run the application modules
153156

154-
Run the following command to start the TestGen UI. It will open the browser at [http://localhost:8501](http://localhost:8501).
157+
Run the following command to start TestGen. It will open the browser at [http://localhost:8501](http://localhost:8501).
155158

156159
```shell
157-
testgen ui run
160+
testgen run-app
158161
```
159162

160163
Verify that you can login to the UI with the `TESTGEN_USERNAME` and `TESTGEN_PASSWORD` values that you configured in the environment variables.

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
secretKeyRef:
1010
name: {{ .Values.testgen.authSecrets.name | quote }}
1111
key: "decrypt-password"
12+
- name: TG_JWT_HASHING_KEY
13+
valueFrom:
14+
secretKeyRef:
15+
name: {{ .Values.testgen.authSecrets.name | quote }}
16+
key: "jwt-hashing-key"
1217
- name: TG_METADATA_DB_HOST
1318
value: {{ .Values.testgen.databaseHost | quote }}
1419
- name: TG_METADATA_DB_NAME

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ type: Opaque
1212
data:
1313
decrypt-salt: {{ randAlphaNum 32 | b64enc | quote }}
1414
decrypt-password: {{ randAlphaNum 32 | b64enc | quote }}
15+
jwt-hashing-key: {{ randBytes 32 | b64enc | quote }}
1516
{{- end }}

deploy/testgen.dockerfile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,10 @@ RUN chown -R testgen:testgen /var/lib/testgen /dk/lib/python3.12/site-packages/s
2525
ENV TESTGEN_VERSION=${TESTGEN_VERSION}
2626
ENV TESTGEN_DOCKER_HUB_REPO=${TESTGEN_DOCKER_HUB_REPO}
2727
ENV TG_RELEASE_CHECK=docker
28-
ENV STREAMLIT_SERVER_MAX_UPLOAD_SIZE=200
2928

3029
USER testgen
3130

3231
WORKDIR /dk
3332

3433
ENTRYPOINT ["testgen"]
35-
CMD [ "ui", "run" ]
34+
CMD [ "run-app" ]

docs/local_development.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ Create a `local.env` file with the following environment variables, replacing th
6363
```shell
6464
export TESTGEN_DEBUG=yes
6565
export TESTGEN_LOG_TO_FILE=no
66+
export TG_ANALYTICS=no
67+
export TG_JWT_HASHING_KEY=<base64_key>
6668
export TESTGEN_USERNAME=<username>
6769
export TESTGEN_PASSWORD=<password>
6870
export TG_DECRYPT_SALT=<decrypt_salt>
@@ -98,8 +100,24 @@ testgen run-tests --project-key DEFAULT --test-suite-key default-suite-1
98100
testgen quick-start --simulate-fast-forward
99101
```
100102

101-
### Run Streamlit
102-
Run the local Streamlit-based TestGen application. It will open the browser at [http://localhost:8501](http://localhost:8501).
103+
### Run the Application
104+
105+
TestGen has two modules that have to be running: The web user interface (UI) and the Scheduler.
106+
The scheduler starts jobs (profiling, test execution, ...) at their scheduled times.
107+
108+
The following command starts both modules, each in their own process:
109+
110+
```shell
111+
testgen run-app
112+
```
113+
114+
Alternatively, you can run each individually:
115+
116+
117+
```shell
118+
testgen run-app ui
119+
```
120+
103121
```shell
104-
testgen ui run
122+
testgen run-app scheduler
105123
```

pyproject.toml

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"
88

99
[project]
1010
name = "dataops-testgen"
11-
version = "3.7.9"
11+
version = "4.0.9"
1212
description = "DataKitchen's Data Quality DataOps TestGen"
1313
authors = [
1414
{ "name" = "DataKitchen, Inc.", "email" = "[email protected]" },
@@ -38,17 +38,12 @@ dependencies = [
3838
"pycryptodome==3.21",
3939
"prettytable==3.7.0",
4040
"requests_extensions==1.1.3",
41-
"bz2file==0.98",
42-
"trogon==0.4.0",
4341
"numpy==1.26.4",
4442
"pandas==2.1.4",
45-
"streamlit==1.38.0",
43+
"streamlit==1.44.1",
4644
"streamlit-extras==0.3.0",
4745
"streamlit-aggrid==0.3.4.post3",
48-
"streamlit-antd-components==0.2.2",
49-
"streamlit-plotly-events==0.0.6",
5046
"plotly_express==0.4.1",
51-
"streamlit-option-menu==0.3.6",
5247
"streamlit-authenticator==0.2.3",
5348
"streamlit-javascript==0.1.5",
5449
"progress==1.6",
@@ -62,6 +57,7 @@ dependencies = [
6257
"reportlab==4.2.2",
6358
"pydantic==1.10.13",
6459
"streamlit-pydantic==0.6.0",
60+
"cron-converter==1.2.1",
6561

6662
# Pinned to match the manually compiled libs or for security
6763
"pyarrow==18.1.0",
@@ -245,7 +241,7 @@ ignore = ["TRY003", "S608", "S404", "F841", "B023"]
245241
"__init__.py" = ["F403"]
246242
"testgen/__main__.py" = ["ARG001", "S603"]
247243
"tasks.py" = ["F403"]
248-
"tests*" = ["S101", "T201"]
244+
"tests*" = ["S101", "T201", "ARG001"]
249245
"invocations/**" = ["ARG001", "T201"]
250246
"testgen/common/encrypt.py" = ["S413"]
251247
"testgen/ui/pdf/dk_logo.py" = ["T201"]

testgen/__main__.py

Lines changed: 59 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import logging
22
import os
3+
import signal
34
import subprocess
45
import sys
5-
import typing
66
from dataclasses import dataclass, field
77

88
import click
99
from click.core import Context
1010
from progress.spinner import MoonSpinner
11-
from trogon import tui
1211

1312
from testgen import settings
1413
from testgen.commands.run_execute_tests import run_execution_steps
@@ -40,14 +39,16 @@
4039
get_tg_db,
4140
get_tg_host,
4241
get_tg_schema,
43-
logs,
4442
version_service,
4543
)
44+
from testgen.scheduler import register_scheduler_job, run_scheduler
4645
from testgen.ui.queries import profiling_run_queries, test_run_queries
4746
from testgen.utils import plugins
4847

4948
LOG = logging.getLogger("testgen")
5049

50+
APP_MODULES = ["ui", "scheduler"]
51+
5152

5253
@dataclass
5354
class Configuration:
@@ -58,8 +59,16 @@ class Configuration:
5859
pass_configuration = click.make_pass_decorator(Configuration)
5960

6061

61-
@tui()
62+
class CliGroup(click.Group):
63+
def invoke(self, ctx: Context):
64+
try:
65+
super().invoke(ctx)
66+
except Exception:
67+
LOG.exception("There was an unexpected error")
68+
69+
6270
@click.group(
71+
cls=CliGroup,
6372
help=f"This version: {settings.VERSION} \n\nLatest version: {version_service.get_latest_version()} \n\nSchema revision: {get_schema_revision()}"
6473
)
6574
@click.option(
@@ -83,7 +92,7 @@ def cli(ctx: Context, verbose: bool):
8392
sys.exit(1)
8493

8594
if (
86-
ctx.invoked_subcommand not in ["ui", "tui", "setup-system-db", "upgrade-system-version", "quick-start"]
95+
ctx.invoked_subcommand not in ["run-app", "ui", "setup-system-db", "upgrade-system-version", "quick-start"]
8796
and not is_db_revision_up_to_date()
8897
):
8998
click.secho("The system database schema is outdated. Automatically running the following command:", fg="red")
@@ -93,6 +102,7 @@ def cli(ctx: Context, verbose: bool):
93102
LOG.debug("Current Step: Main Program")
94103

95104

105+
@register_scheduler_job
96106
@cli.command("run-profile", help="Generates a new profile of the table group.")
97107
@pass_configuration
98108
@click.option(
@@ -143,6 +153,7 @@ def run_test_generation(configuration: Configuration, table_group_id: str, test_
143153
click.echo("\n" + message)
144154

145155

156+
@register_scheduler_job
146157
@cli.command("run-tests", help="Performs tests defined for a test suite.")
147158
@click.option(
148159
"-pk",
@@ -590,19 +601,19 @@ def list_table_groups(configuration: Configuration, project_key: str, display: b
590601
def ui(): ...
591602

592603

593-
@ui.command("run", help="Run the browser application with default settings")
594-
@click.option("-d", "--debug", is_flag=True, default=False)
595-
def run(debug: bool):
604+
@ui.command("plugins", help="List installed application plugins")
605+
def list_ui_plugins():
606+
installed_plugins = list(plugins.discover())
607+
608+
click.echo(click.style(len(installed_plugins), fg="bright_magenta") + click.style(" plugins installed", bold=True))
609+
for plugin in installed_plugins:
610+
click.echo(click.style(" + ", fg="bright_green") + f"{plugin.package: <30}" + f"\tversion: {plugin.version}")
611+
612+
613+
def run_ui():
596614
from testgen.ui.scripts import patch_streamlit
597-
configure_logging(
598-
level=logging.INFO,
599-
log_format="%(message)s",
600-
)
601615

602616
status_code: int = -1
603-
logger = logging.getLogger("testgen")
604-
stderr: typing.TextIO = typing.cast(typing.TextIO, logs.LogPipe(logger, logging.INFO))
605-
stdout: typing.TextIO = typing.cast(typing.TextIO, logs.LogPipe(logger, logging.INFO))
606617

607618
use_ssl = os.path.isfile(settings.SSL_CERT_FILE) and os.path.isfile(settings.SSL_KEY_FILE)
608619

@@ -621,31 +632,49 @@ def run(debug: bool):
621632
"run",
622633
app_file,
623634
"--browser.gatherUsageStats=false",
635+
"--client.showErrorDetails=none",
636+
"--client.toolbarMode=minimal",
624637
f"--server.sslCertFile={settings.SSL_CERT_FILE}" if use_ssl else "",
625638
f"--server.sslKeyFile={settings.SSL_KEY_FILE}" if use_ssl else "",
626639
"--",
627-
f"{'--debug' if debug else ''}",
640+
f"{'--debug' if settings.IS_DEBUG else ''}",
628641
],
629-
stdout=stdout,
630-
stderr=stderr,
642+
env={**os.environ, "TG_JOB_SOURCE": "UI"}
631643
)
632644
except Exception:
633645
LOG.exception(f"Testgen UI exited with status code {status_code}")
634-
raise
635-
finally:
636-
if stderr:
637-
stderr.close()
638-
if stdout:
639-
stdout.close()
640646

641647

642-
@ui.command("plugins", help="List installed application plugins")
643-
def list_ui_plugins():
644-
installed_plugins = list(plugins.discover())
648+
@cli.command("run-app", help="Runs TestGen's application modules")
649+
@click.argument(
650+
"module",
651+
type=click.Choice(["all", *APP_MODULES]),
652+
default="all",
653+
)
654+
def run_app(module):
645655

646-
click.echo(click.style(len(installed_plugins), fg="bright_magenta") + click.style(" plugins installed", bold=True))
647-
for plugin in installed_plugins:
648-
click.echo(click.style(" + ", fg="bright_green") + f"{plugin.package: <30}" + f"\tversion: {plugin.version}")
656+
match module:
657+
case "ui":
658+
run_ui()
659+
660+
case "scheduler":
661+
run_scheduler()
662+
663+
case "all":
664+
children = [
665+
subprocess.Popen([sys.executable, sys.argv[0], "run-app", m], start_new_session=True)
666+
for m in APP_MODULES
667+
]
668+
669+
def term_children(signum, _):
670+
for child in children:
671+
child.send_signal(signum)
672+
673+
signal.signal(signal.SIGINT, term_children)
674+
signal.signal(signal.SIGTERM, term_children)
675+
676+
for child in children:
677+
child.wait()
649678

650679

651680
if __name__ == "__main__":

testgen/commands/run_execute_cat_tests.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import logging
2+
from datetime import UTC, datetime
23

4+
from testgen import settings
35
from testgen.commands.queries.execute_cat_tests_query import CCATExecutionSQL
46
from testgen.commands.run_refresh_score_cards_results import run_refresh_score_cards_results
57
from testgen.common import (
@@ -9,6 +11,7 @@
911
WriteListToDB,
1012
date_service,
1113
)
14+
from testgen.common.mixpanel_service import MixpanelService
1215

1316
LOG = logging.getLogger("testgen")
1417

@@ -61,17 +64,35 @@ def ParseCATResults(clsCATExecute):
6164

6265

6366
def FinalizeTestRun(clsCATExecute: CCATExecutionSQL):
64-
lstQueries = [clsCATExecute.FinalizeTestResultsSQL(),
65-
clsCATExecute.PushTestRunStatusUpdateSQL(),
66-
clsCATExecute.FinalizeTestSuiteUpdateSQL(),
67-
clsCATExecute.CalcPrevalenceTestResultsSQL(),
68-
clsCATExecute.TestScoringRollupRunSQL(),
69-
clsCATExecute.TestScoringRollupTableGroupSQL()]
70-
RunActionQueryList(("DKTG"), lstQueries)
71-
run_refresh_score_cards_results(
72-
project_code=clsCATExecute.project_code,
73-
add_history_entry=True,
74-
refresh_date=date_service.parse_now(clsCATExecute.run_date),
67+
_, row_counts = RunActionQueryList(("DKTG"), [
68+
clsCATExecute.FinalizeTestResultsSQL(),
69+
clsCATExecute.PushTestRunStatusUpdateSQL(),
70+
clsCATExecute.FinalizeTestSuiteUpdateSQL(),
71+
])
72+
end_time = datetime.now(UTC)
73+
74+
try:
75+
RunActionQueryList(("DKTG"), [
76+
clsCATExecute.CalcPrevalenceTestResultsSQL(),
77+
clsCATExecute.TestScoringRollupRunSQL(),
78+
clsCATExecute.TestScoringRollupTableGroupSQL(),
79+
])
80+
run_refresh_score_cards_results(
81+
project_code=clsCATExecute.project_code,
82+
add_history_entry=True,
83+
refresh_date=date_service.parse_now(clsCATExecute.run_date),
84+
)
85+
except Exception:
86+
LOG.exception("Error refreshing scores after test run")
87+
pass
88+
89+
MixpanelService().send_event(
90+
"run-tests",
91+
source=settings.ANALYTICS_JOB_SOURCE,
92+
sql_flavor=clsCATExecute.flavor,
93+
test_count=row_counts[0],
94+
run_duration=(end_time - date_service.parse_now(clsCATExecute.run_date)).total_seconds(),
95+
scoring_duration=(datetime.now(UTC) - end_time).total_seconds(),
7596
)
7697

7798

testgen/commands/run_execute_tests.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,7 @@ def run_execution_steps_in_background(project_code, test_suite):
103103
empty_cache()
104104
background_thread = threading.Thread(
105105
target=run_execution_steps,
106-
args=(
107-
project_code,
108-
test_suite
109-
),
106+
args=(project_code, test_suite),
110107
)
111108
background_thread.start()
112109
else:
@@ -115,7 +112,12 @@ def run_execution_steps_in_background(project_code, test_suite):
115112
subprocess.Popen(script) # NOQA S603
116113

117114

118-
def run_execution_steps(project_code: str, test_suite: str, minutes_offset: int=0, spinner: Spinner=None) -> str:
115+
def run_execution_steps(
116+
project_code: str,
117+
test_suite: str,
118+
minutes_offset: int=0,
119+
spinner: Spinner=None,
120+
) -> str:
119121
# Initialize required parms for all steps
120122
has_errors = False
121123
error_msg = ""

0 commit comments

Comments
 (0)