groundskeeping 0.2.1__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/PKG-INFO +4 -1
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/README.md +3 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/pages.md +5 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/app.py +18 -15
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/contracts/__init__.py +1 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/contracts/views.py +15 -0
- groundskeeping-0.2.2/src/groundskeeping/py.typed +1 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/widgets/workbench.py +11 -7
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/tests/test_app_smoke.py +90 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/.github/ISSUE_TEMPLATE/not_working.yml +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/.github/release-drafter.yml +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/.github/workflows/ci.yml +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/.github/workflows/docs.yml +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/.github/workflows/merge.yml +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/.github/workflows/publish.yml +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/.gitignore +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/CONTRIBUTING.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/actions.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/api/actions.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/api/app.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/api/configurator.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/api/jobs.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/api/pages.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/api/telemetry.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/api/views.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/api/widgets.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/api/wizards.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/configuration.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/contributing.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/index.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/ownership.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/quickstart.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/static/images/demo-layout-example.png +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/telemetry.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/docs/wizards.md +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/examples/demo_app.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/mkdocs.yml +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/pyproject.toml +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/__init__.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/configurator/__init__.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/configurator/adapter.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/configurator/models.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/contracts/actions.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/contracts/jobs.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/contracts/pages.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/contracts/telemetry.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/contracts/wizards.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/demo.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/navigation.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/telemetry/__init__.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/telemetry/providers/__init__.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/telemetry/providers/fake.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/telemetry/runtime.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/theme.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/themes/groundskeeping.tcss +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/widgets/__init__.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/widgets/configurator.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/widgets/primitives.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/widgets/telemetry.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/widgets/wizard.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/tests/test_actions_and_jobs.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/tests/test_configurator_adapter.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/tests/test_dependency_boundaries.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/tests/test_navigation_contracts.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/tests/test_telemetry_core.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/tests/test_wizard_contracts.py +0 -0
- {groundskeeping-0.2.1 → groundskeeping-0.2.2}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: groundskeeping
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Reusable Textual operator shell, workbench, configuration browser, and telemetry primitives.
|
|
5
5
|
Author: UNSW CAVA platform tooling
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -75,6 +75,9 @@ OperatorApp(spec).run()
|
|
|
75
75
|
|
|
76
76
|
Groundskeeping owns the frame around those pages. `DatabaseSetupPage`,
|
|
77
77
|
`EmbeddingsSetupPage`, and the services behind them stay in the application.
|
|
78
|
+
Use `workbench_labels` on `OperatorAppSpec` when the shared pane chrome needs different
|
|
79
|
+
language, such as naming the upper-right pane **Checks** instead of **Rows**. Page navigation
|
|
80
|
+
and view contracts still supply their own content titles.
|
|
78
81
|
|
|
79
82
|
The default page surface is the workbench:
|
|
80
83
|
|
|
@@ -58,6 +58,9 @@ OperatorApp(spec).run()
|
|
|
58
58
|
|
|
59
59
|
Groundskeeping owns the frame around those pages. `DatabaseSetupPage`,
|
|
60
60
|
`EmbeddingsSetupPage`, and the services behind them stay in the application.
|
|
61
|
+
Use `workbench_labels` on `OperatorAppSpec` when the shared pane chrome needs different
|
|
62
|
+
language, such as naming the upper-right pane **Checks** instead of **Rows**. Page navigation
|
|
63
|
+
and view contracts still supply their own content titles.
|
|
61
64
|
|
|
62
65
|
The default page surface is the workbench:
|
|
63
66
|
|
|
@@ -23,6 +23,11 @@ Translate domain objects before they reach the workbench. The workbench understa
|
|
|
23
23
|
`SectionItem`, `CatalogueItem`, `TableView`, `TreeView`, and friends; it does not need to know
|
|
24
24
|
what a Groundworkers resource or `cava-nlp-shard` evaluation object is.
|
|
25
25
|
|
|
26
|
+
Use `OperatorAppSpec.workbench_labels` to rename shared pane chrome such as the result and
|
|
27
|
+
detail panel labels. Page-owned titles still live on navigation and view contracts: for
|
|
28
|
+
example, `SectionNavigation.title` names the left pane for a specific page, and
|
|
29
|
+
`TableView.title` names the current result content.
|
|
30
|
+
|
|
26
31
|
## Routing
|
|
27
32
|
|
|
28
33
|
`PageRoute` is the navigation identity for one page: a `key`, an operator-facing `label`, and
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from collections.abc import Mapping
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
|
-
from typing import ClassVar
|
|
7
|
+
from typing import ClassVar, cast
|
|
8
8
|
|
|
9
9
|
from rich.text import Text
|
|
10
10
|
from textual.app import App, ComposeResult
|
|
@@ -52,6 +52,7 @@ from groundskeeping.contracts.views import (
|
|
|
52
52
|
PageNavigation,
|
|
53
53
|
SectionItem,
|
|
54
54
|
SurfaceView,
|
|
55
|
+
WorkbenchLabels,
|
|
55
56
|
)
|
|
56
57
|
from groundskeeping.contracts.wizards import (
|
|
57
58
|
WizardController,
|
|
@@ -76,6 +77,7 @@ class OperatorAppSpec:
|
|
|
76
77
|
operation_policy: OperationPolicy = field(default_factory=AllowAllOperationPolicy)
|
|
77
78
|
job_policy: JobPolicy = field(default_factory=SingleForegroundJobPolicy)
|
|
78
79
|
result_presenter: ResultPresenter = field(default_factory=DefaultResultPresenter)
|
|
80
|
+
workbench_labels: WorkbenchLabels = field(default_factory=WorkbenchLabels)
|
|
79
81
|
default_page: str | None = None
|
|
80
82
|
metadata: Mapping[str, object] | None = None
|
|
81
83
|
|
|
@@ -157,7 +159,7 @@ class OperatorApp(App[None]):
|
|
|
157
159
|
self.sub_title = spec.subtitle or ""
|
|
158
160
|
self.jobs = JobManager(spec.job_policy)
|
|
159
161
|
self._active_page = spec.default_page or self.registry[0].key
|
|
160
|
-
self._workbench = Workbench()
|
|
162
|
+
self._workbench = Workbench(spec.workbench_labels)
|
|
161
163
|
self._surface = _WorkbenchSurface(self._workbench)
|
|
162
164
|
# Textual's App already owns an internal `_context()` method used during
|
|
163
165
|
# startup. Keep the page-facing context under a distinct name so the shell does
|
|
@@ -180,13 +182,11 @@ class OperatorApp(App[None]):
|
|
|
180
182
|
with Vertical(id="workspace"):
|
|
181
183
|
yield Label("", id="workspace-title")
|
|
182
184
|
for route in self.registry:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
page.styles.max_height = 0
|
|
189
|
-
yield page
|
|
185
|
+
page_widget = self._page_widget(route.key)
|
|
186
|
+
page_widget.add_class("operator-page")
|
|
187
|
+
page_widget.styles.height = 0
|
|
188
|
+
page_widget.styles.max_height = 0
|
|
189
|
+
yield page_widget
|
|
190
190
|
yield self._workbench
|
|
191
191
|
yield Footer()
|
|
192
192
|
|
|
@@ -254,16 +254,13 @@ class OperatorApp(App[None]):
|
|
|
254
254
|
next_page = self._pages[page_key]
|
|
255
255
|
if previous is not None and previous is not next_page:
|
|
256
256
|
previous.deactivate(self._page_context)
|
|
257
|
-
|
|
258
|
-
previous.remove_class("-active")
|
|
257
|
+
self._page_widget(self._active_page).remove_class("-active")
|
|
259
258
|
self._active_page = page_key
|
|
260
|
-
|
|
261
|
-
if isinstance(page, Widget):
|
|
262
|
-
page.add_class("-active")
|
|
259
|
+
self._page_widget(page_key).add_class("-active")
|
|
263
260
|
tabs = self.query_one("#page-tabs", Tabs)
|
|
264
261
|
if tabs.active != page_key:
|
|
265
262
|
tabs.active = page_key
|
|
266
|
-
|
|
263
|
+
next_page.activate(self._page_context)
|
|
267
264
|
self._render_page(route)
|
|
268
265
|
|
|
269
266
|
def _render_page(self, route: PageRoute) -> None:
|
|
@@ -286,6 +283,12 @@ class OperatorApp(App[None]):
|
|
|
286
283
|
def _active_widget(self) -> OperatorPage:
|
|
287
284
|
return self._pages[self._active_page]
|
|
288
285
|
|
|
286
|
+
def _page_widget(self, page_key: str) -> Widget:
|
|
287
|
+
page = self._pages[page_key]
|
|
288
|
+
if not isinstance(page, Widget):
|
|
289
|
+
raise TypeError(f"Page {page_key!r} must be a Textual Widget.")
|
|
290
|
+
return cast(Widget, page)
|
|
291
|
+
|
|
289
292
|
def _select_navigation_item(self, item: NavigationItem) -> None:
|
|
290
293
|
self._active_widget().navigation_selected(item, self._page_context)
|
|
291
294
|
|
|
@@ -12,6 +12,21 @@ type ViewActionVariant = Literal["default", "primary", "success", "warning", "er
|
|
|
12
12
|
MAX_VIEW_ACTIONS = 6
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class WorkbenchLabels:
|
|
17
|
+
"""Operator-facing labels for reusable workbench chrome.
|
|
18
|
+
|
|
19
|
+
Page-owned content still supplies its own titles through navigation and view contracts.
|
|
20
|
+
These labels only cover structural defaults in the shared shell.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
navigation_panel: str = "Sections"
|
|
24
|
+
result_panel: str = "Rows"
|
|
25
|
+
detail_panel: str = "Detail"
|
|
26
|
+
initial_result_summary: str = "Select a section to inspect it."
|
|
27
|
+
key_value_columns: tuple[str, str] = ("Field", "Value")
|
|
28
|
+
|
|
29
|
+
|
|
15
30
|
class SemanticStatus(StrEnum):
|
|
16
31
|
"""Small health/status vocabulary understood by shared widgets."""
|
|
17
32
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -29,6 +29,7 @@ from groundskeeping.contracts.views import (
|
|
|
29
29
|
TreeNode,
|
|
30
30
|
TreeView,
|
|
31
31
|
ViewAction,
|
|
32
|
+
WorkbenchLabels,
|
|
32
33
|
)
|
|
33
34
|
from groundskeeping.theme import node_label, status_style
|
|
34
35
|
from groundskeeping.widgets.primitives import EmptyState, LoadingState
|
|
@@ -37,8 +38,9 @@ from groundskeeping.widgets.primitives import EmptyState, LoadingState
|
|
|
37
38
|
class Workbench(Widget):
|
|
38
39
|
"""Shared page surface with navigation left and rows/detail right."""
|
|
39
40
|
|
|
40
|
-
def __init__(self) -> None:
|
|
41
|
+
def __init__(self, labels: WorkbenchLabels | None = None) -> None:
|
|
41
42
|
super().__init__(id="workbench")
|
|
43
|
+
self.labels = labels or WorkbenchLabels()
|
|
42
44
|
self._loading_timer: Timer | None = None
|
|
43
45
|
self._loading_frame = 0
|
|
44
46
|
self._loading_view: LoadingView | None = None
|
|
@@ -54,7 +56,7 @@ class Workbench(Widget):
|
|
|
54
56
|
with Horizontal(id="result-header"):
|
|
55
57
|
yield Static("", id="result-status")
|
|
56
58
|
yield Static(
|
|
57
|
-
|
|
59
|
+
self.labels.initial_result_summary, id="result-summary"
|
|
58
60
|
)
|
|
59
61
|
with Horizontal(id="result-actions"):
|
|
60
62
|
for index in range(MAX_VIEW_ACTIONS):
|
|
@@ -68,9 +70,9 @@ class Workbench(Widget):
|
|
|
68
70
|
yield DataTable(id="context-table")
|
|
69
71
|
|
|
70
72
|
def on_mount(self) -> None:
|
|
71
|
-
self.query_one("#catalogue-panel").border_title =
|
|
72
|
-
self.query_one("#result-panel").border_title =
|
|
73
|
-
self.query_one("#context-panel").border_title =
|
|
73
|
+
self.query_one("#catalogue-panel").border_title = self.labels.navigation_panel
|
|
74
|
+
self.query_one("#result-panel").border_title = self.labels.result_panel
|
|
75
|
+
self.query_one("#context-panel").border_title = self.labels.detail_panel
|
|
74
76
|
self.catalogue.show_root = False
|
|
75
77
|
self.catalogue.root.expand()
|
|
76
78
|
self.catalogue.styles.display = "none"
|
|
@@ -317,12 +319,14 @@ class Workbench(Widget):
|
|
|
317
319
|
self,
|
|
318
320
|
rows: Iterable[tuple[str, str]],
|
|
319
321
|
*,
|
|
320
|
-
columns: tuple[str,
|
|
322
|
+
columns: tuple[str, ...] | None = None,
|
|
321
323
|
) -> None:
|
|
322
324
|
self.query_one("#context", TextArea).styles.display = "none"
|
|
323
325
|
table = self.query_one("#context-table", DataTable)
|
|
324
326
|
table.styles.display = "block"
|
|
325
327
|
table.clear(columns=True)
|
|
326
|
-
table.add_columns(
|
|
328
|
+
table.add_columns(
|
|
329
|
+
*(self.labels.key_value_columns if columns is None else columns)
|
|
330
|
+
)
|
|
327
331
|
for label, value in rows:
|
|
328
332
|
table.add_row(label, value)
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
from dataclasses import replace
|
|
5
5
|
|
|
6
|
+
import pytest
|
|
6
7
|
from textual.widget import Widget
|
|
7
8
|
from textual.widgets import Button, DataTable
|
|
8
9
|
|
|
@@ -23,6 +24,7 @@ from groundskeeping.contracts import (
|
|
|
23
24
|
ViewAction,
|
|
24
25
|
WizardResult,
|
|
25
26
|
WizardResultStatus,
|
|
27
|
+
WorkbenchLabels,
|
|
26
28
|
)
|
|
27
29
|
from groundskeeping.demo import build_demo_spec
|
|
28
30
|
|
|
@@ -64,6 +66,35 @@ class _CountingPage(Widget):
|
|
|
64
66
|
return None
|
|
65
67
|
|
|
66
68
|
|
|
69
|
+
class _NonWidgetPage:
|
|
70
|
+
def __init__(self, route: PageRoute) -> None:
|
|
71
|
+
self.route = route
|
|
72
|
+
|
|
73
|
+
def activate(self, context: PageContext) -> None:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
def deactivate(self, context: PageContext) -> None:
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
def build_navigation(self, context: PageContext) -> SectionNavigation:
|
|
80
|
+
return SectionNavigation(items=())
|
|
81
|
+
|
|
82
|
+
def landing_view(self, context: PageContext) -> SurfaceView:
|
|
83
|
+
return EmptyView(title=self.route.label, message="render")
|
|
84
|
+
|
|
85
|
+
def navigation_selected(self, item: NavigationItem, context: PageContext) -> None:
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
def action_selected(self, action_key: str, context: PageContext) -> None:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def row_highlighted(self, row_key: str, context: PageContext) -> None:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
def row_selected(self, row_key: str, context: PageContext) -> None:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
|
|
67
98
|
def test_demo_app_enters_textual_startup_context() -> None:
|
|
68
99
|
async def run() -> None:
|
|
69
100
|
app = OperatorApp(build_demo_spec())
|
|
@@ -95,6 +126,25 @@ def test_demo_app_enters_textual_startup_context() -> None:
|
|
|
95
126
|
asyncio.run(run())
|
|
96
127
|
|
|
97
128
|
|
|
129
|
+
def test_operator_app_rejects_non_widget_pages() -> None:
|
|
130
|
+
async def run() -> None:
|
|
131
|
+
route = PageRoute("setup", "Setup", "Setup page")
|
|
132
|
+
app = OperatorApp(
|
|
133
|
+
OperatorAppSpec(
|
|
134
|
+
app_id="non-widget-test",
|
|
135
|
+
title="Non Widget Test",
|
|
136
|
+
subtitle=None,
|
|
137
|
+
pages=(PageRegistration(route, lambda context: _NonWidgetPage(route)),),
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
with pytest.raises(TypeError, match="must be a Textual Widget"):
|
|
142
|
+
async with app.run_test():
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
asyncio.run(run())
|
|
146
|
+
|
|
147
|
+
|
|
98
148
|
def test_single_page_hides_tabs_without_removing_page_registry() -> None:
|
|
99
149
|
async def run() -> None:
|
|
100
150
|
demo = build_demo_spec()
|
|
@@ -113,6 +163,46 @@ def test_single_page_hides_tabs_without_removing_page_registry() -> None:
|
|
|
113
163
|
asyncio.run(run())
|
|
114
164
|
|
|
115
165
|
|
|
166
|
+
def test_app_spec_can_override_workbench_chrome_labels() -> None:
|
|
167
|
+
async def run() -> None:
|
|
168
|
+
demo = build_demo_spec()
|
|
169
|
+
spec = replace(
|
|
170
|
+
demo,
|
|
171
|
+
workbench_labels=WorkbenchLabels(
|
|
172
|
+
navigation_panel="Setup areas",
|
|
173
|
+
result_panel="Checks",
|
|
174
|
+
detail_panel="Inspector",
|
|
175
|
+
initial_result_summary="Choose a setup area.",
|
|
176
|
+
key_value_columns=("Setting", "Value"),
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
app = OperatorApp(spec)
|
|
180
|
+
|
|
181
|
+
async with app.run_test() as pilot:
|
|
182
|
+
assert app.query_one("#result-panel").border_title == "Checks"
|
|
183
|
+
assert app.query_one("#context-panel").border_title == "Inspector"
|
|
184
|
+
|
|
185
|
+
app._workbench.show_navigation(SectionNavigation(items=()))
|
|
186
|
+
assert app.query_one("#catalogue-panel").border_title == "Sections"
|
|
187
|
+
|
|
188
|
+
app._workbench.show_context_table((("provider", "ollama"),))
|
|
189
|
+
await pilot.pause()
|
|
190
|
+
|
|
191
|
+
table = app.query_one("#context-table", DataTable)
|
|
192
|
+
assert tuple(str(column.label) for column in table.ordered_columns) == (
|
|
193
|
+
"Setting",
|
|
194
|
+
"Value",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
app._workbench.show_context_table((), columns=())
|
|
198
|
+
await pilot.pause()
|
|
199
|
+
|
|
200
|
+
assert len(table.ordered_columns) == 0
|
|
201
|
+
await pilot.press("q")
|
|
202
|
+
|
|
203
|
+
asyncio.run(run())
|
|
204
|
+
|
|
205
|
+
|
|
116
206
|
def test_wizard_result_refreshes_active_page_when_requested() -> None:
|
|
117
207
|
async def run() -> None:
|
|
118
208
|
active_route = PageRoute("setup", "Setup", "Setup page")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/telemetry/providers/__init__.py
RENAMED
|
File without changes
|
{groundskeeping-0.2.1 → groundskeeping-0.2.2}/src/groundskeeping/telemetry/providers/fake.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|