phlo-observatory-example 0.1.1__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.
- phlo_observatory_example-0.1.1/.gitignore +168 -0
- phlo_observatory_example-0.1.1/PKG-INFO +24 -0
- phlo_observatory_example-0.1.1/README.md +13 -0
- phlo_observatory_example-0.1.1/pyproject.toml +32 -0
- phlo_observatory_example-0.1.1/src/phlo_observatory_example/__init__.py +1 -0
- phlo_observatory_example-0.1.1/src/phlo_observatory_example/observatory_assets/README.txt +1 -0
- phlo_observatory_example-0.1.1/src/phlo_observatory_example/observatory_assets/example.js +141 -0
- phlo_observatory_example-0.1.1/src/phlo_observatory_example/observatory_plugin.py +81 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Environment variables
|
|
2
|
+
.env
|
|
3
|
+
.env.local
|
|
4
|
+
.env.*.local
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
*$py.class
|
|
10
|
+
*.so
|
|
11
|
+
.Python
|
|
12
|
+
build/
|
|
13
|
+
develop-eggs/
|
|
14
|
+
dist/
|
|
15
|
+
downloads/
|
|
16
|
+
eggs/
|
|
17
|
+
.eggs/
|
|
18
|
+
/lib/
|
|
19
|
+
/lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
.pytest_cache/
|
|
29
|
+
.coverage
|
|
30
|
+
htmlcov/
|
|
31
|
+
*.cover
|
|
32
|
+
.hypothesis/
|
|
33
|
+
.mypy_cache/
|
|
34
|
+
.ruff_cache/
|
|
35
|
+
.pytype/
|
|
36
|
+
*.pyc
|
|
37
|
+
.cache/
|
|
38
|
+
|
|
39
|
+
# Virtual environments
|
|
40
|
+
venv/
|
|
41
|
+
ENV/
|
|
42
|
+
env/
|
|
43
|
+
.venv
|
|
44
|
+
|
|
45
|
+
# Git worktrees
|
|
46
|
+
.worktrees/
|
|
47
|
+
|
|
48
|
+
# IDEs
|
|
49
|
+
.vscode/
|
|
50
|
+
.idea/
|
|
51
|
+
*.swp
|
|
52
|
+
*.swo
|
|
53
|
+
*~
|
|
54
|
+
.DS_Store
|
|
55
|
+
|
|
56
|
+
# Dagster
|
|
57
|
+
dagster/.nux/
|
|
58
|
+
dagster/.telemetry/
|
|
59
|
+
dagster/__pycache__/
|
|
60
|
+
dagster/history/
|
|
61
|
+
dagster/.logs_queue/
|
|
62
|
+
dagster/schedules/schedules.db
|
|
63
|
+
dagster/schedules/*.db
|
|
64
|
+
dagster/storage/
|
|
65
|
+
dagster/tmpfiles/
|
|
66
|
+
dagster_home/
|
|
67
|
+
|
|
68
|
+
# dbt
|
|
69
|
+
transforms/dbt/target/
|
|
70
|
+
transforms/dbt/dbt_packages/
|
|
71
|
+
transforms/dbt/logs/
|
|
72
|
+
transforms/dbt/profiles/profiles.yml.bak
|
|
73
|
+
transforms/dbt/package-lock.yml
|
|
74
|
+
|
|
75
|
+
# Data directories
|
|
76
|
+
volumes/
|
|
77
|
+
*.duckdb
|
|
78
|
+
*.duckdb.wal
|
|
79
|
+
|
|
80
|
+
# Great Expectations
|
|
81
|
+
great_expectations/uncommitted/
|
|
82
|
+
great_expectations/.ge_store_backend_id
|
|
83
|
+
great_expectations/**/.ge_store_backend_id
|
|
84
|
+
great_expectations/validations/
|
|
85
|
+
|
|
86
|
+
# Docker
|
|
87
|
+
docker-compose.override.yml
|
|
88
|
+
|
|
89
|
+
# Logs
|
|
90
|
+
*.log
|
|
91
|
+
logs/
|
|
92
|
+
|
|
93
|
+
# Temporary files
|
|
94
|
+
*.tmp
|
|
95
|
+
*.temp
|
|
96
|
+
.cache/
|
|
97
|
+
|
|
98
|
+
# Data files and formats
|
|
99
|
+
*.parquet
|
|
100
|
+
*.csv
|
|
101
|
+
*.pkl
|
|
102
|
+
*.pickle
|
|
103
|
+
*.h5
|
|
104
|
+
*.hdf5
|
|
105
|
+
*.feather
|
|
106
|
+
|
|
107
|
+
# Cloud and infrastructure
|
|
108
|
+
.aws/
|
|
109
|
+
.gcp/
|
|
110
|
+
.azure/
|
|
111
|
+
.terraform/
|
|
112
|
+
*.tfstate
|
|
113
|
+
*.tfvars
|
|
114
|
+
|
|
115
|
+
# Jupyter notebooks (if not wanted)
|
|
116
|
+
# *.ipynb
|
|
117
|
+
|
|
118
|
+
# OS generated files
|
|
119
|
+
.DS_Store
|
|
120
|
+
.DS_Store?
|
|
121
|
+
._*
|
|
122
|
+
.Spotlight-V100
|
|
123
|
+
.Trashes
|
|
124
|
+
ehthumbs.db
|
|
125
|
+
Thumbs.db
|
|
126
|
+
|
|
127
|
+
# IDE and editor files
|
|
128
|
+
.vscode/
|
|
129
|
+
.idea/
|
|
130
|
+
*.swp
|
|
131
|
+
*.swo
|
|
132
|
+
*~
|
|
133
|
+
.project
|
|
134
|
+
.classpath
|
|
135
|
+
.settings/
|
|
136
|
+
|
|
137
|
+
# Logs and temporary files
|
|
138
|
+
*.log
|
|
139
|
+
logs/
|
|
140
|
+
*.tmp
|
|
141
|
+
*.temp
|
|
142
|
+
.cache/
|
|
143
|
+
|
|
144
|
+
transforms/dbt/target
|
|
145
|
+
specs
|
|
146
|
+
PLATFORM_REVIEW.md
|
|
147
|
+
|
|
148
|
+
# Temporary/working files
|
|
149
|
+
# context/
|
|
150
|
+
context/
|
|
151
|
+
|
|
152
|
+
# Beads issue tracker (local-only files)
|
|
153
|
+
.beads/beads.db
|
|
154
|
+
.beads/beads.db-*
|
|
155
|
+
.beads/bd.sock
|
|
156
|
+
.beads/bd.pipe
|
|
157
|
+
.beads/.exclusive-lock
|
|
158
|
+
.beads/daemon.*
|
|
159
|
+
.git/beads-worktrees/
|
|
160
|
+
|
|
161
|
+
# bv (beads viewer) local config and caches
|
|
162
|
+
.bv/
|
|
163
|
+
|
|
164
|
+
# phlo local config and caches
|
|
165
|
+
.phlo/
|
|
166
|
+
|
|
167
|
+
.claude
|
|
168
|
+
.ralph/
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: phlo-observatory-example
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Example Observatory extension plugin
|
|
5
|
+
Author-email: Phlo Team <team@phlo.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: phlo-observatory>=0.1.0
|
|
9
|
+
Requires-Dist: phlo>=0.1.0
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# phlo-observatory-example
|
|
13
|
+
|
|
14
|
+
Example Observatory extension plugin for Phlo.
|
|
15
|
+
|
|
16
|
+
## What It Shows
|
|
17
|
+
|
|
18
|
+
- Extension manifest wiring for routes, nav items, dashboard slots, and settings.
|
|
19
|
+
- Static frontend asset packaging from `observatory_assets/`.
|
|
20
|
+
- Plugin registration through `phlo.plugins.observatory`.
|
|
21
|
+
|
|
22
|
+
## Entry Point
|
|
23
|
+
|
|
24
|
+
- `example = "phlo_observatory_example.observatory_plugin:ExampleObservatoryExtension"`
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# phlo-observatory-example
|
|
2
|
+
|
|
3
|
+
Example Observatory extension plugin for Phlo.
|
|
4
|
+
|
|
5
|
+
## What It Shows
|
|
6
|
+
|
|
7
|
+
- Extension manifest wiring for routes, nav items, dashboard slots, and settings.
|
|
8
|
+
- Static frontend asset packaging from `observatory_assets/`.
|
|
9
|
+
- Plugin registration through `phlo.plugins.observatory`.
|
|
10
|
+
|
|
11
|
+
## Entry Point
|
|
12
|
+
|
|
13
|
+
- `example = "phlo_observatory_example.observatory_plugin:ExampleObservatoryExtension"`
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "phlo-observatory-example"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Example Observatory extension plugin"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "Phlo Team", email = "team@phlo.dev"},
|
|
13
|
+
]
|
|
14
|
+
license = {text = "MIT"}
|
|
15
|
+
dependencies = [
|
|
16
|
+
"phlo>=0.1.0",
|
|
17
|
+
"phlo-observatory>=0.1.0",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.entry-points."phlo.plugins.observatory"]
|
|
21
|
+
example = "phlo_observatory_example.observatory_plugin:ExampleObservatoryExtension"
|
|
22
|
+
|
|
23
|
+
[tool.hatch.build.targets.wheel]
|
|
24
|
+
packages = ["src/phlo_observatory_example"]
|
|
25
|
+
include = ["src/phlo_observatory_example/observatory_assets/*"]
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.sdist]
|
|
28
|
+
include = ["src/phlo_observatory_example"]
|
|
29
|
+
|
|
30
|
+
[tool.ruff]
|
|
31
|
+
line-length = 100
|
|
32
|
+
target-version = "py311"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Example Observatory extension package."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Observatory assets for phlo-observatory-example.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export function registerRoutes({ createRoute, rootRoute }) {
|
|
2
|
+
return createRoute({
|
|
3
|
+
getParentRoute: () => rootRoute,
|
|
4
|
+
path: '/extensions/example',
|
|
5
|
+
component: ExamplePage,
|
|
6
|
+
})
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function registerDashboardSlot({ register }) {
|
|
10
|
+
register(DashboardNote)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function registerHubSlot({ register }) {
|
|
14
|
+
register(HubNote)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const settingsApi = { loadSettings: null, saveSettings: null }
|
|
18
|
+
|
|
19
|
+
export function registerSettings({ register, loadSettings, saveSettings }) {
|
|
20
|
+
settingsApi.loadSettings = loadSettings
|
|
21
|
+
settingsApi.saveSettings = saveSettings
|
|
22
|
+
register({
|
|
23
|
+
id: 'example-settings',
|
|
24
|
+
title: 'Example Extension',
|
|
25
|
+
description: 'Demonstrates extension settings panels.',
|
|
26
|
+
component: ExampleSettings,
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ExamplePage() {
|
|
31
|
+
return 'Example extension route loaded.'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function DashboardNote() {
|
|
35
|
+
return 'Extension slot: dashboard.after-cards.'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function HubNote() {
|
|
39
|
+
return 'Extension slot: hub.after-stats.'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function ExampleSettings() {
|
|
43
|
+
const React = typeof globalThis !== 'undefined' ? globalThis.__phloReact : null
|
|
44
|
+
if (!React) return 'React is not available for extension settings.'
|
|
45
|
+
|
|
46
|
+
const { useEffect, useState } = React
|
|
47
|
+
const el = React.createElement
|
|
48
|
+
const loadSettingsFn = settingsApi.loadSettings
|
|
49
|
+
const saveSettingsFn = settingsApi.saveSettings
|
|
50
|
+
if (!loadSettingsFn || !saveSettingsFn) {
|
|
51
|
+
return 'Settings API is not available for this extension.'
|
|
52
|
+
}
|
|
53
|
+
const [loading, setLoading] = useState(true)
|
|
54
|
+
const [message, setMessage] = useState('')
|
|
55
|
+
const [enabled, setEnabled] = useState(false)
|
|
56
|
+
const [status, setStatus] = useState(null)
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
let active = true
|
|
60
|
+
loadSettingsFn()
|
|
61
|
+
.then((data) => {
|
|
62
|
+
if (!active) return
|
|
63
|
+
setEnabled(Boolean(data.enabled))
|
|
64
|
+
setMessage(typeof data.message === 'string' ? data.message : '')
|
|
65
|
+
setLoading(false)
|
|
66
|
+
})
|
|
67
|
+
.catch(() => {
|
|
68
|
+
if (!active) return
|
|
69
|
+
setStatus('Failed to load settings.')
|
|
70
|
+
setLoading(false)
|
|
71
|
+
})
|
|
72
|
+
return () => {
|
|
73
|
+
active = false
|
|
74
|
+
}
|
|
75
|
+
}, [loadSettingsFn])
|
|
76
|
+
|
|
77
|
+
const handleSave = async () => {
|
|
78
|
+
setStatus(null)
|
|
79
|
+
try {
|
|
80
|
+
await saveSettingsFn({ enabled, message })
|
|
81
|
+
setStatus('Saved.')
|
|
82
|
+
} catch {
|
|
83
|
+
setStatus('Save failed.')
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (loading) return 'Loading settings...'
|
|
88
|
+
|
|
89
|
+
return el(
|
|
90
|
+
'div',
|
|
91
|
+
{ className: 'space-y-3' },
|
|
92
|
+
el(
|
|
93
|
+
'div',
|
|
94
|
+
{ className: 'space-y-2' },
|
|
95
|
+
el(
|
|
96
|
+
'label',
|
|
97
|
+
{
|
|
98
|
+
className: 'text-sm font-medium',
|
|
99
|
+
htmlFor: 'example-settings-message',
|
|
100
|
+
},
|
|
101
|
+
'Message',
|
|
102
|
+
),
|
|
103
|
+
el('input', {
|
|
104
|
+
id: 'example-settings-message',
|
|
105
|
+
className:
|
|
106
|
+
'w-full rounded-md border border-input bg-background px-3 py-2 text-sm',
|
|
107
|
+
placeholder: 'Message',
|
|
108
|
+
value: message,
|
|
109
|
+
onChange: (event) => setMessage(event.target.value),
|
|
110
|
+
}),
|
|
111
|
+
),
|
|
112
|
+
el(
|
|
113
|
+
'label',
|
|
114
|
+
{ className: 'flex items-center gap-2 text-sm' },
|
|
115
|
+
el('input', {
|
|
116
|
+
type: 'checkbox',
|
|
117
|
+
className: 'h-4 w-4',
|
|
118
|
+
checked: enabled,
|
|
119
|
+
onChange: (event) => setEnabled(event.target.checked),
|
|
120
|
+
}),
|
|
121
|
+
'Enabled',
|
|
122
|
+
),
|
|
123
|
+
el(
|
|
124
|
+
'div',
|
|
125
|
+
{ className: 'flex items-center gap-2' },
|
|
126
|
+
el(
|
|
127
|
+
'button',
|
|
128
|
+
{
|
|
129
|
+
type: 'button',
|
|
130
|
+
className:
|
|
131
|
+
'inline-flex items-center rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground shadow-sm',
|
|
132
|
+
onClick: handleSave,
|
|
133
|
+
},
|
|
134
|
+
'Save',
|
|
135
|
+
),
|
|
136
|
+
status
|
|
137
|
+
? el('span', { className: 'text-sm text-muted-foreground' }, status)
|
|
138
|
+
: null,
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Example Observatory extension plugin."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib import resources
|
|
6
|
+
from importlib.abc import Traversable
|
|
7
|
+
|
|
8
|
+
from phlo.plugins import PluginMetadata
|
|
9
|
+
from phlo_observatory import ObservatoryExtensionPlugin
|
|
10
|
+
from phlo_observatory.manifest import (
|
|
11
|
+
ObservatoryExtensionCompatibility,
|
|
12
|
+
ObservatoryExtensionManifest,
|
|
13
|
+
ObservatoryExtensionNavItem,
|
|
14
|
+
ObservatoryExtensionRoute,
|
|
15
|
+
ObservatoryExtensionSettings,
|
|
16
|
+
ObservatoryExtensionSettingsPanel,
|
|
17
|
+
ObservatoryExtensionSlot,
|
|
18
|
+
ObservatoryExtensionUI,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ExampleObservatoryExtension(ObservatoryExtensionPlugin):
|
|
23
|
+
"""Example Observatory extension showing routes, slots, and settings panels."""
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def metadata(self) -> PluginMetadata:
|
|
27
|
+
return PluginMetadata(
|
|
28
|
+
name="example",
|
|
29
|
+
version="0.1.0",
|
|
30
|
+
description="Example Observatory UI extension",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def manifest(self) -> ObservatoryExtensionManifest:
|
|
35
|
+
return ObservatoryExtensionManifest(
|
|
36
|
+
name="example",
|
|
37
|
+
version="0.1.0",
|
|
38
|
+
compat=ObservatoryExtensionCompatibility(observatory_min="0.1.0"),
|
|
39
|
+
settings=ObservatoryExtensionSettings(
|
|
40
|
+
settings_schema={
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": {
|
|
43
|
+
"enabled": {"type": "boolean"},
|
|
44
|
+
"message": {"type": "string"},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
defaults={"enabled": True, "message": "Hello from extension settings."},
|
|
48
|
+
scope="extension",
|
|
49
|
+
),
|
|
50
|
+
ui=ObservatoryExtensionUI(
|
|
51
|
+
routes=[
|
|
52
|
+
ObservatoryExtensionRoute(
|
|
53
|
+
path="/extensions/example",
|
|
54
|
+
module="/example.js",
|
|
55
|
+
export="registerRoutes",
|
|
56
|
+
)
|
|
57
|
+
],
|
|
58
|
+
nav=[ObservatoryExtensionNavItem(title="Example", to="/extensions/example")],
|
|
59
|
+
slots=[
|
|
60
|
+
ObservatoryExtensionSlot(
|
|
61
|
+
slot_id="dashboard.after-cards",
|
|
62
|
+
module="/example.js",
|
|
63
|
+
export="registerDashboardSlot",
|
|
64
|
+
),
|
|
65
|
+
ObservatoryExtensionSlot(
|
|
66
|
+
slot_id="hub.after-stats",
|
|
67
|
+
module="/example.js",
|
|
68
|
+
export="registerHubSlot",
|
|
69
|
+
),
|
|
70
|
+
],
|
|
71
|
+
settings=[
|
|
72
|
+
ObservatoryExtensionSettingsPanel(
|
|
73
|
+
module="/example.js", export="registerSettings"
|
|
74
|
+
)
|
|
75
|
+
],
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def asset_root(self) -> Traversable:
|
|
81
|
+
return resources.files("phlo_observatory_example").joinpath("observatory_assets")
|