Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
4b35912
feat(my_egeria): add experimental my_network script
pcoldico Oct 4, 2026
3b62625
wip(my_egeria): my_network loads profile and existing peers, links ne…
pcoldico Oct 5, 2026
357c960
tidying up the project files, moved a number of folders to deprecated…
pcoldico Oct 8, 2026
024c598
Further reformatting to clear out deprecated files and add in the use…
pcoldico Oct 8, 2026
f8b76d3
Updates to Support Custom Report Spec
pcoldico Oct 9, 2026
121c9b2
start of week clean up and synch
pcoldico Oct 13, 2026
9f2782f
feat(my_profile): add My Network option for managing peers
pcoldico Oct 15, 2026
5f945d8
fix(my_profile): stop Technology Types "go back" from crashing
pcoldico Oct 16, 2026
689ff46
docs(my_profile): document the My Network option
pcoldico Oct 16, 2026
04dd837
chore: revert machine-specific config.json and test edits
pcoldico Oct 16, 2026
a298d8f
test(my_profile): cover Technology Types go back and missing details
pcoldico Oct 16, 2026
f908442
fix(my_profile): restore Shop for Data go back and letter shortcuts
pcoldico Oct 16, 2026
039f2b7
Merge branch 'main' into my-profile-strict-tests
dwolfson Oct 16, 2026
76e6692
feat(my_profile): use one key binding convention on every screen
pcoldico Oct 16, 2026
7fe6997
Merge remote-tracking branch 'origin/my-profile-strict-tests' into my…
pcoldico Oct 16, 2026
0682b9c
feat(my_profile): make the Roles -> My Team view discoverable, change…
pcoldico Oct 18, 2026
0991406
Merge remote-tracking branch 'upstream/main' into my-profile-strict-t…
pcoldico Oct 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/developer_guide_pyegeria_textual.md
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,7 @@ That is the second of the two standard worker patterns, so you now have both:
### 7.5 Acting on the selected row

My Profile lets the user act on "the selected row" from several places: add a comment
(Ctrl+A), show comments (Ctrl+S), edit (Ctrl+T), bookmark it (Ctrl+K) and open bookmarks with
(Ctrl+N), show comments (Ctrl+O), edit (Ctrl+T), bookmark it (Ctrl+K) and open bookmarks with
its GUID ready to add (Ctrl+B). Two problems need solving.

**Which row is selected?** `MainScreen` records the table and row whenever the user
Expand Down Expand Up @@ -737,7 +737,7 @@ qualified name. Some tables, such as Shop for Data's glossary table (built from
Because `get_row_guid` takes the table itself, not a table name on one particular screen, any
screen can use it, and each key binding is a few lines:

- **Ctrl+K** on the main screen, or **k** on Shop for Data, calls
- **Ctrl+K**, on the main screen or Shop for Data, calls
`self.app.bookmark_table_row(table, row_key)`, which bookmarks the row straight away.
- **Ctrl+B** calls `self.app.show_my_bookmarks(self.app.get_row_guid(table, row_key))`. The
bookmark screen receives the GUID as `target_guid` and pre-fills its input with it, just as
Expand Down
126 changes: 66 additions & 60 deletions docs/my_profile_app_manual.md

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions my_egeria/my_egeria/DemoCode/My_Profile/AddCommentScreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ class AddCommentScreen(ModalScreen):
""" Add a comment to an Egeria Element the user has access to """

BINDINGS = [
("a", "add_comment", "Add Comment"),
("c", "cancel", "Cancel"),
("q", "quit", "Quit")
("escape", "cancel", "Back"),
("ctrl+s", "add_comment", "Add Comment"),
]

CSS_PATH = "my_profile.tcss"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ class BaseAddScreen(ModalScreen):
"""

BINDINGS = [
("q", "quit", "Quit"),
("ctrl+a", "add_element", "Add"),
("escape", "quit", "Back"),
("ctrl+s", "add_element", "Add"),
]

CSS_PATH = "my_profile.tcss"
Expand Down Expand Up @@ -202,7 +202,8 @@ class AddAssociationScreen(ModalScreen):
"""

BINDINGS = [
("q", "quit", "Quit"),
("escape", "quit", "Back"),
("q", "quit", "Main Menu"),
]

CSS_PATH = "my_profile.tcss"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class CreateProfileScreen(ModalScreen[int]):
4xx on failure
"""

BINDINGS = [("q", "dismiss(200)", "Quit")]
BINDINGS = [("escape", "quit", "Back"),
("ctrl+s", "create_profile", "Create Profile")]
CSS_PATH = "my_profile.tcss"

def __init__(self, user: str | None = None, password: str | None = None, view_server: str | None = None, platform_url: str | None = None, *args, **kwargs):
Expand Down Expand Up @@ -66,6 +67,10 @@ def compose(self) -> ComposeResult:
yield Static()
yield Footer()

def action_create_profile(self) -> None:
""" ctrl+s does the same as pressing the Create Profile button """
self.create_profile()

@on(Button.Pressed, "#create_profile_btn")
def create_profile(self) -> int:
"""Create a new profile in Egeria from data provided in Input fields."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
class CreateSubscriptionRequestScreen(ModalScreen):

BINDINGS = [
("q", "quit", "Quit"),
("c", "create_subscription", "Create Subscription")
("escape", "quit", "Back"),
("ctrl+s", "create_subscription", "Create Subscription"),
]

CSS_PATH = "my_profile.tcss"
Expand Down
26 changes: 17 additions & 9 deletions my_egeria/my_egeria/DemoCode/My_Profile/EditElementsScreens.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,10 @@ class BaseEditScreen(ModalScreen):
SOURCE_TABLE: str = ""

BINDINGS = [
("escape", "exit_screen", "Exit"),
("a", "add_row", "Add Row"),
("d", "delete_row", "Delete Row"),
("escape", "exit_screen", "Back"),
("q", "exit_screen", "Main Menu"),
("ctrl+n", "add_row", "Add Row"),
("ctrl+d", "delete_row", "Delete Row"),
]

CSS_PATH = "my_profile.tcss"
Expand All @@ -120,7 +121,7 @@ def compose(self) -> ComposeResult:
yield Header(show_clock=True)
yield Static(self.TITLE_TEXT, classes="span-3", id=self.TITLE_ID)
yield Static(
" Press 'a' to add a row, 'd' to delete the selected row, Esc to exit."
" Press Ctrl+N to add a row, Ctrl+D to delete the selected row, Esc to go back."
" Please note you may only delete one row at a time!",
classes="span-3",
)
Expand Down Expand Up @@ -351,11 +352,14 @@ class EditProfileScreen(ModalScreen[Any]):
4xx on failure
"""

BINDINGS = [("q", "dismiss(200)", "Quit"),
("ctrl+c", "Edit_communities", "Edit communities"),
("ctrl+i", "Edit_identities", "Edit identities"),
("ctrl+r", "Edit_roles", "Edit roles"),
("ctrl+t", "Edit_teams", "Edit teams"),
# ctrl+c and ctrl+i can't be used here: ctrl+c is Textual's copy/quit key and a
# terminal sends ctrl+i as Tab. Input swallows ctrl+a/d/e/k/u, so these avoid those too.
BINDINGS = [("escape", "quit", "Back"),
("ctrl+s", "create_profile", "Save Profile"),
("ctrl+g", "Edit_communities", "Edit Communities"),
("ctrl+l", "Edit_identities", "Edit Identities"),
("ctrl+r", "Edit_roles", "Edit Roles"),
("ctrl+t", "Edit_teams", "Edit Teams"),
]

CSS_PATH = "my_profile.tcss"
Expand Down Expand Up @@ -470,6 +474,10 @@ def compose(self) -> ComposeResult:
yield Static()
yield Footer()

def action_create_profile(self) -> None:
""" ctrl+s does the same as pressing the Edit Profile button """
self.create_profile()

@on(Button.Pressed, "#edit_profile_btn")
def create_profile(self) -> Any:
"""Update profile in Egeria from data provided in Input fields."""
Expand Down
4 changes: 2 additions & 2 deletions my_egeria/my_egeria/DemoCode/My_Profile/FeedbackScreens.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ class FeedbackScreen(ModalScreen):
""" Collect feedback about the screen the user was on when they pressed the feedback key """

BINDINGS = [
("escape", "cancel", "Back"),
("ctrl+s", "submit", "Submit Feedback"),
("escape", "cancel", "Cancel"),
]

CSS_PATH = "my_profile.tcss"
Expand Down Expand Up @@ -98,8 +98,8 @@ class FeedbackLogScreen(ModalScreen):
""" Show the accumulated entries in the private feedback note log """

BINDINGS = [
("q", "quit", "Quit"),
("escape", "quit", "Back"),
("q", "quit", "Main Menu"),
]

CSS_PATH = "my_profile.tcss"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@ class GenericDataViewScreen(ModalScreen):
"""

BINDINGS = [
("q", "quit", "Quit"),
("b", "back", "Go back"),
("escape", "back", "Back"),
("return", "back", "Back"),
("s", "subscribe", "Subscribe to Data Source"),
("q", "quit", "Main Menu"),
("ctrl+u", "subscribe", "Subscribe"),
]

CSS_PATH = "my_profile.tcss"
Expand Down
24 changes: 17 additions & 7 deletions my_egeria/my_egeria/DemoCode/My_Profile/MainScreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,38 @@
from textual.widgets import DataTable, OptionList, Header, Static, Footer
from textual.widgets._option_list import Option

from pyegeria import load_app_config, settings


class MainScreen(Screen):
"""Main Screen for My Profile App."""

BINDINGS = [
("q", "app.quit", "Quit"),
("ctrl+a", "add_comment","add comment to selected row"),
("ctrl+b", "bookmarks", "Manage Bookmarks"),
("ctrl+k", "bookmark_row", "Bookmark Selected Row"),
("ctrl+s", "show_comments", "Show Comments for Selected Row"),
("ctrl+t", "edit_table", "Edit Selected Table"),
("ctrl+o", "show_comments", "Show Comments"),
("ctrl+n", "add_comment", "Add Comment"),
("ctrl+k", "bookmark_row", "Bookmark Row"),
("ctrl+b", "bookmarks", "Bookmarks"),
]

CSS_PATH = "my_profile.tcss"

def __init__(self, *args, **kwargs):
super().__init__(id="main_screen", *args, **kwargs)
self.title = "Egeria"
self.sub_title = "My Profile"
self.other_function_list: OptionList = OptionList(id="other_function_list")
self.selected_table = None
self.selected_row = None
load_app_config()
app_user = settings.User_Profile
self.user_name = app_user.user_name or "garygeeke"
self.sub_title = f"User: {self.user_name}"

def on_mount(self) -> None:
# self.log needs a running app, so it can't be called from __init__
self.log(f"Application Config: {settings.Environment}")
self.log(f"User Profile: {settings.User_Profile}")

def compose(self) -> ComposeResult:
# place widgets into grid on screen, note sequence determines position!
Expand All @@ -54,7 +64,7 @@ def compose(self) -> ComposeResult:
Static(f"Other Functions"),
Static(f"[b]Select a function[/b]"),
OptionList(
Option("[] User Identities"),
# Option("[] User Identities"),
Option("[] Catalogs/Shop for Data"),
Option("[] Edit Profile"),
Option("[] Subscriptions"),
Expand All @@ -68,7 +78,7 @@ def compose(self) -> ComposeResult:
id="other_function_container"
)
yield ScrollableContainer(
Static("Roles"),
Static("Roles [dim](Enter on a TeamLeader/TeamMember role: show team)[/dim]"),
DataTable(id="roles_table"),
id="main_roles_container"
)
Expand Down
9 changes: 5 additions & 4 deletions my_egeria/my_egeria/DemoCode/My_Profile/MyBookMarksScreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ class MyBookMarksScreen(ModalScreen):
"""

BINDINGS = [
("q", "quit", "Quit"),
("ctrl+n", "new_bookmark", "Add New Bookmark"),
("d", "remove_bookmark", "Remove Highlighted Bookmark"),
("escape", "quit", "Back"),
("q", "quit", "Main Menu"),
("ctrl+n", "new_bookmark", "Add Bookmark"),
("ctrl+d", "remove_bookmark", "Remove Bookmark"),
]

CSS_PATH = "my_profile.tcss"
Expand All @@ -39,7 +40,7 @@ def __init__(self, my_bookmarks, *args, target_guid: str | None = None, **kwargs
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
yield ScrollableContainer(
Static("Your bookmarks. Ctrl+N adds one by GUID; 'd' removes the highlighted one."),
Static("Your bookmarks. Ctrl+N adds one by GUID; Ctrl+D removes the highlighted one."),
DataTable(id="my_bookmark_table", zebra_stripes=True, cursor_type="row"),
id="bookmarks_table_container",
)
Expand Down
7 changes: 4 additions & 3 deletions my_egeria/my_egeria/DemoCode/My_Profile/MyNetworkScreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,10 @@ class MyNetworkScreen(ModalScreen):
"""

BINDINGS = [
("q", "quit", "Quit"),
("v", "view_details", "View Highlighted Person"),
("d", "remove_peer", "Remove Highlighted Person"),
("escape", "quit", "Back"),
("q", "quit", "Main Menu"),
("ctrl+o", "view_details", "Open Person"),
("ctrl+d", "remove_peer", "Remove Person"),
]

CSS_PATH = "my_profile.tcss"
Expand Down
4 changes: 2 additions & 2 deletions my_egeria/my_egeria/DemoCode/My_Profile/MyTeamScreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ class MyTeam(ModalScreen):
""" Display a list of team Members for a Team Leader """

BINDINGS = [
("q", "quit", "Quit"),
("b", "back", "Go back")
("escape", "back", "Back"),
("q", "quit", "Main Menu"),
]
CSS_PATH = "my_profile.tcss"

Expand Down
4 changes: 2 additions & 2 deletions my_egeria/my_egeria/DemoCode/My_Profile/RETURN_CODES.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ In accordance with application conventions:
| **`423`** | `BUSINESS_DOMAIN_SPEC_ERROR` | Error executing BusinessCapabilities report specification in Shop for Data handler. | `shop_for_data_handler.py` |
| **`429`** | `SAMPLE_DATA_RETRIEVAL_ERROR` | Failed to retrieve or parse sample data from Egeria Asset Catalog / Data Engine in Shop for Data handler. | `shop_for_data_handler.py` |
| **`430`** | `INCOMPATIBLE_ENVIRONMENT` | App started under a Python whose pydantic is older than pyegeria requires, so request bodies would reach Egeria with their parameters silently missing. The app exits at startup with a message naming the interpreter and how to run from the project venv instead. | `my_profile_app.py`, `profile_utils.py` |
| **`440`** | `TEAM_ROLES_OR_GLOSSARY_ERROR` | Error communicating with Egeria or retrieving team members, user roles, or glossary term details. | `team_roles_handler.py` |
| **`440`** | `TEAM_ROLES_OR_GLOSSARY_ERROR` | Error communicating with Egeria or retrieving glossary term details. (A failed team members lookup is now shown as an error notification and does not exit the app.) | `team_roles_handler.py` |

---

Expand All @@ -59,4 +59,4 @@ In accordance with application conventions:
- **410–419**: Tree resolution errors in selection overview, and technology type data extraction / callback payload errors.
- **420–429**: Data retrieval exceptions, report specification execution failures, and template instantiation failures across Shop for Data and Technology Types.
- **430**: Incompatible Python environment detected at startup.
- **440**: Team roles and glossary term query communication errors.
- **440**: Glossary term query communication errors.
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@

class SearchForTermScreen(ModalScreen):

BINDINGS = [("q", "quit", "Quit"),
("g", "go_back", "Go back"),
BINDINGS = [("escape", "go_back", "Back"),
]

CSS_PATH = "my_profile.tcss"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@

class SelectionOverviewScreen(ModalScreen):
"""Screen to display the selection of the user's data sources."""
BINDINGS = [("q", "quit", "Quit"),
("s", "subscribe", "Subscribe to Data Source"),
("b", "back", "Go back")]
BINDINGS = [("escape", "back", "Back"),
("q", "quit", "Main Menu"),
("ctrl+u", "subscribe", "Subscribe")]

CSS_PATH = "my_profile.tcss"

Expand Down
18 changes: 8 additions & 10 deletions my_egeria/my_egeria/DemoCode/My_Profile/ShopForDataScreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from pyegeria import load_app_config, settings
from textual import on
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import ScrollableContainer
from textual.events import Action
from textual.screen import Screen
Expand All @@ -20,14 +19,13 @@

class ShopForDataScreen(Screen):
""" Screen to Present a choice of different data sources to the user."""
BINDINGS = [("q", "dismiss(200)", "Quit"),
("b", "back", "Go back"),
Binding("escape", "back", "Go back", show=False),
("s", "sample_data_source", "Sample data source"),
("u", "subscribe_to_data_source", "Subscribe"),
("ctrl+s", "subscribe_to_data_source", "Subscribe"),
("t", "search_for_term", "Search Glossary Terms"),
("k", "bookmark_row", "Bookmark"),
BINDINGS = [("escape", "back", "Back"),
# Opened straight from the main screen, so back and main menu are the same place
("q", "back", "Main Menu"),
("ctrl+l", "sample_data_source", "Sample Data"),
("ctrl+u", "subscribe_to_data_source", "Subscribe"),
("ctrl+g", "search_for_term", "Search Glossary Terms"),
("ctrl+k", "bookmark_row", "Bookmark Row"),
("ctrl+b", "bookmarks", "Bookmarks")]

CSS_PATH = "my_profile.tcss"
Expand Down Expand Up @@ -89,7 +87,7 @@ def compose(self) -> ComposeResult:
""" Compose the UI components for the ShopForDataScreen screen."""
yield Header(show_clock=True)
# A Static, not a TextArea: a focused TextArea swallows the screen's letter key bindings (b, q, s, ...)
yield Static("\nPlease click on a line to select a data source, or press b or Escape to go back:")
yield Static("\nPlease click on a line to select a data source, or press Escape to go back:")
yield ScrollableContainer(
Static("Glossary"),
self.glossary_table)
Expand Down
15 changes: 8 additions & 7 deletions my_egeria/my_egeria/DemoCode/My_Profile/ShowCommentsScreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ class ShowCommentsScreen(ModalScreen):
"""Show any comments for an element Screen for My Profile App."""

BINDINGS = [
("q", "quit", "Quit"),
("ctrl+a", "add_comment", "Add Comment"),
("ctrl+r", "add_response", "Add Response"),
("escape", "quit", "Back"),
("q", "quit", "Main Menu"),
("ctrl+n", "add_comment", "Add Comment"),
("ctrl+o", "add_response", "Open Responses"),
]

CSS_PATH = "my_profile.tcss"
Expand All @@ -40,7 +41,7 @@ def __init__(
load_app_config()
# The comment whose responses this screen shows (None = show an element's comments)
self.selected_comment_guid = selected_comment_guid or None
# The comment currently highlighted in the DataTable (target for Ctrl+R)
# The comment currently highlighted in the DataTable (target for Ctrl+O)
self.highlighted_comment_guid: str | None = None
app_config = settings.Environment
app_user = settings.User_Profile
Expand Down Expand Up @@ -116,15 +117,15 @@ def load_comments(self, element_guid: str) -> None:

if comment_table.row_count == 0:
if self.selected_comment_guid:
self.notify("This comment has no responses yet - use Ctrl+A to add one", timeout=10)
self.notify("This comment has no responses yet - use Ctrl+N to add one", timeout=10)
else:
self.notify("Selected element does not have any comments - use Ctrl+A to add one", timeout=10)
self.notify("Selected element does not have any comments - use Ctrl+N to add one", timeout=10)
return

# Mount the table once, after all rows have been added
container = self.query_one("#show_comments_container", ScrollableContainer)
container.mount(comment_table)
container.mount(Static("Ctrl+R on a highlighted comment to view and add responses to it"))
container.mount(Static("Ctrl+O on a highlighted comment to view and add responses to it"))

def extract_backend_identifier(self, table_name, row_key):
try:
Expand Down
Loading
Loading