Skip to content

Commit 6914e4f

Browse files
Merge pull request #43 from DataKitchen/release/4.1.3
Release/4.1.3
2 parents e3a40cf + 79c3d0f commit 6914e4f

35 files changed

Lines changed: 2787 additions & 917 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,6 @@ cython_debug/
127127
**/jsconfig.json
128128
.pytest_cache/
129129
.ruff_cache/
130+
131+
testgen/ui/components/frontend/js/plugins.js
132+
testgen/ui/components/frontend/js/plugin_pages/

deploy/testgen.dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ RUN addgroup -S testgen && adduser -S testgen -G testgen
2020

2121
# Streamlit has to be able to write to these dirs
2222
RUN mkdir /var/lib/testgen
23-
RUN chown -R testgen:testgen /var/lib/testgen /dk/lib/python3.12/site-packages/streamlit/static
23+
RUN chown -R testgen:testgen /var/lib/testgen /dk/lib/python3.12/site-packages/streamlit/static /dk/lib/python3.12/site-packages/testgen/ui/components/frontend
2424

2525
ENV TESTGEN_VERSION=${TESTGEN_VERSION}
2626
ENV TESTGEN_DOCKER_HUB_REPO=${TESTGEN_DOCKER_HUB_REPO}

pyproject.toml

Lines changed: 1 addition & 1 deletion
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 = "4.1.2"
11+
version = "4.1.3"
1212
description = "DataKitchen's Data Quality DataOps TestGen"
1313
authors = [
1414
{ "name" = "DataKitchen, Inc.", "email" = "info@datakitchen.io" },

testgen/common/mixpanel_service.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ def _hash_value(self, value: bytes | str, digest_size: int = 8) -> str:
4545
@safe_method
4646
def send_event(self, event_name, **properties):
4747
properties.setdefault("instance_id", self.instance_id)
48+
properties.setdefault("edition", settings.DOCKER_HUB_REPOSITORY)
4849
properties.setdefault("version", settings.VERSION)
4950
properties.setdefault("distinct_id", self.distinct_id)
51+
properties.setdefault("username", session.username)
5052

5153
track_payload = {
5254
"event": event_name,

testgen/template/dbsetup/030_initialize_new_schema_structure.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ CREATE TABLE connections (
6565
CONSTRAINT connections_connection_id_pk
6666
PRIMARY KEY,
6767
sql_flavor VARCHAR(30),
68+
sql_flavor_code VARCHAR(30),
6869
project_host VARCHAR(250),
6970
project_port VARCHAR(5),
7071
project_user VARCHAR(50),

testgen/template/dbsetup/040_populate_new_schema_project.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ SELECT '{PROJECT_CODE}' as project_code,
99
'{OBSERVABILITY_API_URL}' as observability_api_url;
1010

1111
INSERT INTO connections
12-
(project_code, sql_flavor,
12+
(project_code, sql_flavor, sql_flavor_code,
1313
project_host, project_port, project_user, project_db,
1414
connection_name, project_pw_encrypted, http_path, max_threads, max_query_chars)
1515
SELECT '{PROJECT_CODE}' as project_code,
1616
'{SQL_FLAVOR}' as sql_flavor,
17+
'{SQL_FLAVOR}' as sql_flavor_code,
1718
'{PROJECT_HOST}' as project_host,
1819
'{PROJECT_PORT}' as project_port,
1920
'{PROJECT_USER}' as project_user,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SET SEARCH_PATH TO {SCHEMA_NAME};
2+
3+
ALTER TABLE connections ADD COLUMN sql_flavor_code VARCHAR(30) DEFAULT NULL;

testgen/ui/bootstrap.py

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
import dataclasses
2-
import importlib
3-
import inspect
42
import logging
53

6-
import streamlit as st
7-
84
from testgen import settings
95
from testgen.commands.run_upgrade_db_config import get_schema_revision
106
from testgen.common import configure_logging, version_service
11-
from testgen.ui.assets import get_asset_path
127
from testgen.ui.navigation.menu import Menu, Version
138
from testgen.ui.navigation.page import Page
149
from testgen.ui.navigation.router import Router
@@ -53,19 +48,8 @@
5348
LOG = logging.getLogger("testgen")
5449

5550

56-
class Logo:
57-
image_path: str = get_asset_path("dk_logo.svg")
58-
icon_path: str = get_asset_path("dk_icon.svg")
59-
60-
def render(self):
61-
st.logo(
62-
image=self.image_path,
63-
icon_image=self.icon_path,
64-
)
65-
66-
6751
class Application(singleton.Singleton):
68-
def __init__(self, logo: Logo, router: Router, menu: Menu, logger: logging.Logger) -> None:
52+
def __init__(self, logo: plugins.Logo, router: Router, menu: Menu, logger: logging.Logger) -> None:
6953
self.logo = logo
7054
self.router = router
7155
self.menu = menu
@@ -87,20 +71,30 @@ def run(log_level: int = logging.INFO) -> Application:
8771
pages = [*BUILTIN_PAGES]
8872
installed_plugins = plugins.discover()
8973

74+
if not settings.IS_DEBUG:
75+
"""
76+
This cleanup is called so that TestGen can remove uninstalled
77+
plugins without having to be reinstalled.
78+
79+
The check for DEBUG mode is because multithreading for Streamlit
80+
fragments loads before the plugins can be re-loaded.
81+
"""
82+
plugins.cleanup()
83+
9084
configure_logging(level=log_level)
91-
logo_class = Logo
85+
logo_class = plugins.Logo
9286

9387
for plugin in installed_plugins:
94-
module = importlib.import_module(plugin.package)
95-
for property_name in dir(module):
96-
if (
97-
(maybe_class := getattr(module, property_name, None))
98-
and inspect.isclass(maybe_class)
99-
):
100-
if issubclass(maybe_class, Page):
101-
pages.append(maybe_class)
102-
elif issubclass(maybe_class, Logo):
103-
logo_class = maybe_class
88+
spec = plugin.load()
89+
90+
if spec.page:
91+
pages.append(spec.page)
92+
93+
if spec.logo:
94+
logo_class = spec.logo
95+
96+
if spec.component:
97+
spec.component.provide()
10498

10599
return Application(
106100
logo=logo_class(),

testgen/ui/components/frontend/css/shared.css

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,38 @@ body {
426426
margin-left: 40px;
427427
}
428428

429+
.p-0 {
430+
padding: 0;
431+
}
432+
433+
.p-1 {
434+
padding: 4px;
435+
}
436+
437+
.p-2 {
438+
padding: 8px;
439+
}
440+
441+
.p-3 {
442+
padding: 12px;
443+
}
444+
445+
.p-4 {
446+
padding: 16px;
447+
}
448+
449+
.p-5 {
450+
padding: 24px;
451+
}
452+
453+
.p-6 {
454+
padding: 32px;
455+
}
456+
457+
.p-7 {
458+
padding: 40px;
459+
}
460+
429461
.pt-0 {
430462
padding-top: 0;
431463
}

testgen/ui/components/frontend/js/components/alert.js

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,41 @@
11
/**
2-
* @typedef Alert
3-
* @type {object}
4-
* @property {string} value
5-
* @property {string} color
6-
* @property {string} label
7-
*
82
* @typedef Properties
93
* @type {object}
104
* @property {string?} icon
11-
* @property {'info'|'success'|'error'} type
12-
* @property {string?} message
5+
* @property {number?} timeout
6+
* @property {boolean?} closeable
7+
* @property {'info'|'success'|'warn'|'error'} type
138
*/
149
import van from '../van.min.js';
15-
import { getValue, loadStylesheet } from '../utils.js';
10+
import { getValue, loadStylesheet, getRandomId } from '../utils.js';
1611
import { Icon } from './icon.js';
12+
import { Button } from './button.js';
1713

1814
const { div } = van.tags;
1915
const alertTypeColors = {
2016
info: {backgroundColor: 'rgba(28, 131, 225, 0.1)', color: 'rgb(0, 66, 128)'},
2117
success: {backgroundColor: 'rgba(33, 195, 84, 0.1)', color: 'rgb(23, 114, 51)'},
18+
warn: {backgroundColor: 'rgba(255, 227, 18, 0.2)', color: 'rgb(255, 255, 194)'},
2219
error: {backgroundColor: 'rgba(255, 43, 43, 0.09)', color: 'rgb(125, 53, 59)'},
2320
};
2421

2522
const Alert = (/** @type Properties */ props, /** @type Array<HTMLElement> */ ...children) => {
2623
loadStylesheet('alert', stylesheet);
2724

25+
const elementId = getValue(props.id) ?? 'tg-alert-' + getRandomId();
26+
const close = () => {
27+
document.getElementById(elementId)?.remove();
28+
};
29+
const timeout = getValue(props.timeout);
30+
if (timeout && timeout > 0) {
31+
setTimeout(close, timeout);
32+
}
33+
2834
return div(
2935
{
3036
...props,
31-
class: () => (getValue(props.class) ?? '') + ` tg-alert flex-row`,
32-
style: () => {
33-
const colors = alertTypeColors[getValue(props.type)];
34-
return `color: ${colors.color}; background-color: ${colors.backgroundColor};`;
35-
},
37+
id: elementId,
38+
class: () => `tg-alert flex-row ${getValue(props.class) ?? ''} tg-alert-${getValue(props.type)}`,
3639
role: 'alert',
3740
},
3841
() => {
@@ -43,6 +46,19 @@ const Alert = (/** @type Properties */ props, /** @type Array<HTMLElement> */ ..
4346
{class: 'flex-column'},
4447
...children,
4548
),
49+
() => {
50+
const isCloseable = getValue(props.closeable) ?? false;
51+
if (!isCloseable) {
52+
return '';
53+
}
54+
55+
const colors = alertTypeColors[getValue(props.type)];
56+
return Button({
57+
type: 'icon',
58+
icon: 'close',
59+
style: `margin-left: auto; color: ${colors.color};`,
60+
});
61+
},
4662
);
4763
};
4864

@@ -54,6 +70,34 @@ stylesheet.replace(`
5470
font-size: 16px;
5571
line-height: 24px;
5672
}
73+
74+
.tg-alert-info {
75+
background-color: rgba(28, 131, 225, 0.1);
76+
color: rgb(0, 66, 128);
77+
}
78+
79+
.tg-alert-success {
80+
background-color: rgba(33, 195, 84, 0.1);
81+
color: rgb(23, 114, 51);
82+
}
83+
84+
.tg-alert-error {
85+
background-color: rgba(255, 43, 43, 0.09);
86+
color: rgb(125, 53, 59);
87+
}
88+
89+
.tg-alert-warn {
90+
background-color: rgba(255, 227, 18, 0.1);
91+
color: rgb(146, 108, 5);
92+
}
93+
94+
@media (prefers-color-scheme: dark) {
95+
.tg-alert-warn {
96+
background-color: rgba(255, 227, 18, 0.2);
97+
color: rgb(255, 255, 194);
98+
}
99+
}
100+
57101
.tg-alert > .tg-icon {
58102
color: inherit !important;
59103
}

0 commit comments

Comments
 (0)