Skip to content

ZenML 0.80.0 Migration and Project View Implementation #130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 101 commits into from
Mar 23, 2025
Merged
Show file tree
Hide file tree
Changes from 96 commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
f93427c
update RELEASE.md
marwan37 Mar 12, 2025
fa908ce
bump MIN_ZENML_VERSION to 0.80.0 and remove max version
marwan37 Mar 20, 2025
fb09530
downgrade vscode to 1.93 to be compatible with cursor IDE
marwan37 Mar 20, 2025
56a068d
commit package-lock.json
marwan37 Mar 20, 2025
b0a0923
update lsp_jsonrpc.py to strip and clean any ANSI chars or color codes
marwan37 Mar 20, 2025
a0b0d29
add commands to lsp_zenml for listing workspaces and projects
marwan37 Mar 20, 2025
c08e183
add workspace and project to typehints, including examples
marwan37 Mar 20, 2025
0338f8f
update Workspace structure
marwan37 Mar 20, 2025
0f7b71e
add workspaces and projects wrapper, suppress colorful warnings durin…
marwan37 Mar 20, 2025
6171931
add methods for listing projects, workspaces, and return active works…
marwan37 Mar 20, 2025
9ace530
update pipeline utils to return pipeline url according to new url format
marwan37 Mar 20, 2025
0f242c6
update stack utils to return stack url according to new url format
marwan37 Mar 20, 2025
f167485
add helper functions in server utils for url construction
marwan37 Mar 20, 2025
f547ecc
update createServerStatusFromDetails to include workspace/project/org…
marwan37 Mar 20, 2025
1325228
update ServerInfoTypes with new zenml v0.80.0 properties
marwan37 Mar 20, 2025
d5f61f5
bump packages in requirements files
marwan37 Mar 20, 2025
cb37010
Add workspace and project info to server tree view
marwan37 Mar 20, 2025
4d51385
set no-explicit-any eslint warning to off
marwan37 Mar 20, 2025
5f54fb7
add zenml/projectChanged message type
marwan37 Mar 21, 2025
7b0cdc4
add getProjectByName and setActiveProject lsp commands
marwan37 Mar 21, 2025
466370a
add cmds, registry, and utils TS files for projects
marwan37 Mar 21, 2025
0bba9fd
pass project to list_pipeline_runs and add methods to get and set pro…
marwan37 Mar 21, 2025
4068d49
send ZENML_PROJECT_CHANGED message via zen_watcher
marwan37 Mar 21, 2025
6c6a99e
update url for pipeline runs
marwan37 Mar 21, 2025
6cdcb8f
update server status details with workspace, project and org details
marwan37 Mar 21, 2025
5a3b09a
update stacks url
marwan37 Mar 21, 2025
f5b3436
handle project changed in LSClient
marwan37 Mar 21, 2025
b50fa50
add zenmlProjectView and register commands in ZenExtension
marwan37 Mar 21, 2025
86f57f7
Add Project Response types
marwan37 Mar 21, 2025
83bfdbe
update server info types
marwan37 Mar 21, 2025
258edb0
add LSP_ZENML_PROJECT_CHANGED to constants
marwan37 Mar 21, 2025
1bb8b44
add project view to loading tree item
marwan37 Mar 21, 2025
602a001
update status bar to show active stack + project
marwan37 Mar 21, 2025
63a3f40
add workspace and project name/id in server tree view
marwan37 Mar 21, 2025
b14626e
update Pipeline Runs view to refresh when active project changes
marwan37 Mar 21, 2025
1a43709
add project related commands to package.json
marwan37 Mar 21, 2025
c7f8c2d
add project commands in package.nls.json for accessibility from vscod…
marwan37 Mar 21, 2025
fc50c5f
add ProjectTypes.ts for zenml library response types
marwan37 Mar 21, 2025
0df23ca
Add ProjectDataProvider and ProjectTreeItems
marwan37 Mar 21, 2025
558b198
update project cmds and utils to use project name instead of id
marwan37 Mar 21, 2025
3081a80
improve event listener management
marwan37 Mar 21, 2025
14465a5
remove server status refresh from ui components refresh
marwan37 Mar 21, 2025
d6fc429
update tests to integrate changes to stack commands
marwan37 Mar 21, 2025
7932a1a
add test file for project commands
marwan37 Mar 21, 2025
2d1d327
update ProjectTypes
marwan37 Mar 21, 2025
b0fd100
remove redundant vscode info messages, console logs
marwan37 Mar 21, 2025
d008fd2
update MockEventBus
marwan37 Mar 21, 2025
ff673bd
remove REFRESH_SERVER_STATUS event as its not needed
marwan37 Mar 21, 2025
e7b921b
update ServerTreeItems to render details neatly
marwan37 Mar 21, 2025
77fcc8a
update ErrorTreeItem
marwan37 Mar 21, 2025
fbe31b9
Update pipeline view to show message if no pipelines exist in current…
marwan37 Mar 21, 2025
d5a21c6
show relevant message in tree view when no project exists or is set
marwan37 Mar 21, 2025
1319da8
refactor status bar to use activeProjectName instead of id
marwan37 Mar 21, 2025
4638d2d
add /pipelines to end of expected URL in test with comment explaining…
marwan37 Mar 21, 2025
3f831d0
run format/lint scripts
marwan37 Mar 21, 2025
f4708a6
Update README.md with new changes
marwan37 Mar 21, 2025
de9e4cd
Update registration and removal of event listeners in Components
marwan37 Mar 22, 2025
1c201cc
Update registration and removal of event listeners in PipelineDataPro…
marwan37 Mar 22, 2025
dab923b
Update registration and removal of event listeners in EnvironmentData…
marwan37 Mar 22, 2025
3ce85f2
Update registration and removal of event listeners in ProjectDataProv…
marwan37 Mar 22, 2025
670cb23
Update registration and removal of event listeners in ServerDataProvider
marwan37 Mar 22, 2025
57cbd58
Update registration and removal of event listeners in StackDataProvider
marwan37 Mar 22, 2025
c9fc6a9
update list_workspaces method to match zenml function signature
marwan37 Mar 22, 2025
796652f
update type_hints
marwan37 Mar 22, 2025
cf60685
update fetch_pipeline_runs to validate args and add typing
marwan37 Mar 22, 2025
133fc6e
add PipelineRun and ListPipelineRunsResponse to type_hints
marwan37 Mar 22, 2025
8688322
cast id fields to strings
marwan37 Mar 22, 2025
c1d5234
use class name instead of this in static contexts
marwan37 Mar 22, 2025
581b343
fix docstring: id/projectId --> projectName
marwan37 Mar 22, 2025
f3a15c4
use optional chaining for result?.message
marwan37 Mar 22, 2025
cb4f22e
remove extra blank lines
marwan37 Mar 22, 2025
dd91cb7
refactor views to use arrow functions for handlers, and use class nam…
marwan37 Mar 22, 2025
a374a12
remove await from async arrow funcs in command registries
marwan37 Mar 22, 2025
e828904
convert handlers to arrow functions in LSClient for consistency with …
marwan37 Mar 22, 2025
929f88c
update refreshUIComponents to refresh Projects view and refresh stack…
marwan37 Mar 22, 2025
81201ca
add a check for empty/undefined stack ID, pipeline run ID and project…
marwan37 Mar 22, 2025
40749fd
improve error handling for ProjectsWrapper methods
marwan37 Mar 22, 2025
9a97a72
refactor fetchPipelineRuns
marwan37 Mar 22, 2025
da7e729
implement coderabbit suggestions
marwan37 Mar 22, 2025
573f7f7
implement rabbit suggestions for calls to this.refresh and docstrings
marwan37 Mar 22, 2025
438c262
bump packages from dependabot alerts
marwan37 Mar 22, 2025
749852a
add StatusBarServerStatus type
marwan37 Mar 22, 2025
bdccd14
update pipeline runs fetch to extract config and steps info
marwan37 Mar 23, 2025
942b21b
update PipelineRun in type_hints.py
marwan37 Mar 23, 2025
71e919e
Add types for PipelineRunStep PipelineRunConfig PipelineModel
marwan37 Mar 23, 2025
a231760
Add MetadataType to ProjectTypes
marwan37 Mar 23, 2025
6ffca8b
update icons/colors for pipeline run status icons
marwan37 Mar 23, 2025
f78f282
implement project switching from status bar, and improve tooltip form…
marwan37 Mar 23, 2025
bb324e5
update PipelineDataProvider to group runs by pipeline id, and display…
marwan37 Mar 23, 2025
e8915f7
integrate nested children in PipelineTreeItems
marwan37 Mar 23, 2025
05cb645
modify stacks view icon
marwan37 Mar 23, 2025
2df720b
Add QuickPickItemTypes for status bar context menu
marwan37 Mar 23, 2025
d1ef309
update theme icon for active stacks
marwan37 Mar 23, 2025
eb792a1
add logo svg for activity bar
marwan37 Mar 23, 2025
f266bd4
add header sections in zenml_wrapper for easier scanning
marwan37 Mar 23, 2025
2576ae4
update logo in package.json
marwan37 Mar 23, 2025
6f94c18
remove tooltip from pipeline run item
marwan37 Mar 23, 2025
bfa78eb
add run status to tree items
marwan37 Mar 23, 2025
b3fe54c
Update README.md: project can be switched from status bar
marwan37 Mar 23, 2025
d858ffb
chore: bump version to 0.0.13 and update changelog for release
marwan37 Mar 23, 2025
af45f5e
Fix the doc comment to reference PipelineDataProvider instead of Serv…
marwan37 Mar 23, 2025
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
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ The ZenML VSCode extension seamlessly integrates with [ZenML](https://github.com

## Features

- **Server, Stacks, and Pipeline Runs Views**: Interact directly with ML stacks, pipeline runs, and server configurations from the Activity Bar.
- **DAG Visualization for Pipeline Runs**: Explore Directed Acyclic Graphs for each pipeline view directly directly on the Activity Bar.
- **Server, Projects, Stacks, and Pipeline Runs Views**: Interact directly with ML stacks, pipeline runs, and server configurations from the Activity Bar.
- **Project Management**: Explore, switch between, and manage your ZenML projects directly from the Activity Bar.
- **DAG Visualization for Pipeline Runs**: Explore Directed Acyclic Graphs for each pipeline view directly on the Activity Bar.
- **Python Tool Integration**: Utilizes a Language Server Protocol (LSP) server for real-time synchronization with the ZenML environment.
- **Real-Time Configuration Monitoring**: Leverages `watchdog` to dynamically update configurations, keeping the extension in sync with your ZenML setup.
- **Status Bar**: Display the current stack name and connection status. You can
also change your active stack from the status bar.
- **Status Bar**: Display the current active stack, active project, and connection status. You can also change your active stack from the status bar.

## Getting Started

Expand All @@ -27,6 +27,7 @@ this extension and your Python version needs to be 3.8 or greater.
## Using ZenML in VSCode

- **Manage Server Connections**: Connect or disconnect from ZenML servers and refresh server status.
- **Project Operations**: Browse available projects, view project details, set active projects, and refresh project information.
- **Stack Operations**: View stack details, register, update, delete, copy, or set active stacks directly from VSCode.
- **Stack Component Operations**: View stack component details, register, update, or delete stack components directly from VSCode.
- **Pipeline Runs**: Monitor and manage pipeline runs, including deleting runs from the system and rendering DAGs.
Expand All @@ -48,7 +49,7 @@ this extension and your Python version needs to be 3.8 or greater.
## Requirements

- **ZenML Installation:** ZenML needs to be installed in the local Python environment associated with the Python interpreter selected in the current VS Code workspace. This extension interacts directly with your ZenML environment, so ensuring that ZenML is installed and properly configured is essential.
- **ZenML Version**: This extension is fully compatible with ZenML versions 0.63.0 through 0.75.0. While newer versions may work, they haven't been officially tested. If you encounter any issues with newer versions, please report them.
- **ZenML Version**: This extension is compatible with ZenML version 0.80.0 or newer. While it may work with different versions, we recommend keeping your ZenML up to date for the best experience.
- **Python Version**: Python 3.8 or greater is required for the operation of the LSP server, which is a part of this extension.

## Feedback and Contributions
Expand Down
4 changes: 2 additions & 2 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ Once you've tested the release and everything looks good, you can publish it:
1. **Tag the Release**

```bash
git tag -a v0.0.x -m "Version 0.0.x"
git push origin v0.0.x
git tag -a 0.0.x -m "Version 0.0.x"
git push origin 0.0.x
```

2. **Create a GitHub Release**
Expand Down
4 changes: 2 additions & 2 deletions bundled/tool/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@

TOOL_MODULE_NAME = "zenml-python"
TOOL_DISPLAY_NAME = "ZenML"
MIN_ZENML_VERSION = "0.63.0"
MAX_ZENML_VERSION = "0.75.0"
MIN_ZENML_VERSION = "0.80.0"

"""Constants for ZenML Notifications and Events"""

IS_ZENML_INSTALLED = "zenml/isInstalled"
ZENML_CLIENT_INITIALIZED = "zenml/clientInitialized"
ZENML_SERVER_CHANGED = "zenml/serverChanged"
ZENML_STACK_CHANGED = "zenml/stackChanged"
ZENML_PROJECT_CHANGED = "zenml/projectChanged"
ZENML_REQUIREMENTS_NOT_MET = "zenml/requirementsNotMet"
39 changes: 35 additions & 4 deletions bundled/tool/lsp_jsonrpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,28 @@
import io
import json
import pathlib
import re
import subprocess
import threading
import uuid
from concurrent.futures import ThreadPoolExecutor
from typing import BinaryIO, Dict, Optional, Sequence, Union, cast

ANSI_ESCAPE_PATTERN = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")

CONTENT_LENGTH = "Content-Length: "
RUNNER_SCRIPT = str(pathlib.Path(__file__).parent / "lsp_runner.py")


def to_str(text) -> str:
"""Convert bytes to string as needed."""
return text.decode("utf-8") if isinstance(text, bytes) else text
result = text.decode("utf-8") if isinstance(text, bytes) else text
return result


def strip_ansi_codes(text: str) -> str:
"""Remove ANSI escape sequences from text."""
return ANSI_ESCAPE_PATTERN.sub("", text)


class StreamClosedException(Exception):
Expand Down Expand Up @@ -57,13 +66,30 @@ def write(self, data):
raise StreamClosedException()

with self._lock:
content = json.dumps(data)
if isinstance(data, dict):
# recursively clean string values in dict
clean_data = self._clean_ansi_in_data(data)
else:
clean_data = data

content = json.dumps(clean_data)
length = len(content.encode("utf-8"))
# Make sure we're writing a string, not bytes, to the TextIOWrapper
message = f"{CONTENT_LENGTH}{length}\r\n\r\n{content}"
self._writer.write(message)
self._writer.flush()

def _clean_ansi_in_data(self, data):
"""Recursively clean ANSI codes from string values in data structure."""
if isinstance(data, dict):
return {k: self._clean_ansi_in_data(v) for k, v in data.items()}
elif isinstance(data, list):
return [self._clean_ansi_in_data(item) for item in data]
elif isinstance(data, str):
return strip_ansi_codes(data)
else:
return data


class JsonReader:
"""Manages reading JSON-RPC messages from stream."""
Expand Down Expand Up @@ -91,13 +117,18 @@ def read(self):
line = to_str(self._readline()).strip()

content = to_str(self._reader.read(length))
return json.loads(content)
# clean any ANSI escape sequences that might cause JSON parsing issues
clean_content = strip_ansi_codes(content)
return json.loads(clean_content)

def _readline(self):
line = self._reader.readline()
if not line:
raise EOFError
return line
# strip any ANSI color codes that might interfere with header parsing
if isinstance(line, bytes):
return line
return strip_ansi_codes(line)


class JsonRpc:
Expand Down
40 changes: 40 additions & 0 deletions bundled/tool/lsp_zenml.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,43 @@ def get_run_artifact(wrapper_instance, args):
def get_run_dag(wrapper_instance, args):
"""Gets graph data for a specified ZenML pipeline run"""
return wrapper_instance.get_pipeline_run_graph(args)

@self.command(f"{TOOL_MODULE_NAME}.listWorkspaces")
@self.zenml_command(wrapper_name="workspaces_wrapper")
def list_workspaces(wrapper_instance, args):
"""Lists workspaces from ZenML Pro"""
return wrapper_instance.list_workspaces(args)

@self.command(f"{TOOL_MODULE_NAME}.getActiveWorkspace")
@self.zenml_command(wrapper_name="workspaces_wrapper")
def get_active_workspace(wrapper_instance, *args, **kwargs):
"""Gets the active workspace for the current user"""
return wrapper_instance.get_active_workspace()

@self.command(f"{TOOL_MODULE_NAME}.listProjects")
@self.zenml_command(wrapper_name="projects_wrapper")
def list_projects(wrapper_instance, args):
"""Lists projects from ZenML"""
return wrapper_instance.list_projects(args)

@self.command(f"{TOOL_MODULE_NAME}.getActiveProject")
@self.zenml_command(wrapper_name="projects_wrapper")
def get_active_project(wrapper_instance, *args, **kwargs):
"""Gets the active project for the current user"""
return wrapper_instance.get_active_project()

@self.command(f"{TOOL_MODULE_NAME}.setActiveProject")
@self.zenml_command(wrapper_name="projects_wrapper")
def set_active_project(wrapper_instance, args):
"""Sets the active project for the current user"""
result = wrapper_instance.set_active_project(args)
if not isinstance(result, dict) or "error" not in result:
# Only send notification if successful
self.send_custom_notification("zenml/projectChanged", result["id"])
return result

@self.command(f"{TOOL_MODULE_NAME}.getProjectByName")
@self.zenml_command(wrapper_name="projects_wrapper")
def get_project_by_name(wrapper_instance, args):
"""Gets a project by name"""
return wrapper_instance.get_project_by_name(args[0])
72 changes: 71 additions & 1 deletion bundled/tool/type_hints.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,20 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
# or implied. See the License for the specific language governing
# permissions and limitations under the License.
from typing import Any, Dict, List, Optional, TypedDict
from typing import Any, Dict, List, Optional, Set, Tuple, TypedDict, Union
from uuid import UUID

MetadataType = Union[
str,
int,
float,
bool,
Dict[Any, Any],
List[Any],
Set[Any],
Tuple[Any, ...],
]


class StepArtifactBody(TypedDict):
type: str
Expand Down Expand Up @@ -143,3 +154,62 @@ class ListFlavorsResponse(TypedDict):
total_pages: int
total: int
items: List[Flavor]


class PipelineRun(TypedDict):
id: str
name: str
status: str
stackName: str
pipelineName: str
startTime: Optional[str]
endTime: Optional[str]
config: Optional[Dict[str, Any]]
steps: Optional[Dict[str, Any]]


class ListPipelineRunsResponse(TypedDict):
runs: List[PipelineRun]
total: int
total_pages: int
current_page: int
items_per_page: int
project_name: Optional[str]


class Workspace(TypedDict):
id: str
name: str
description: Optional[str]
display_name: Optional[str]
organization_id: str
organization_name: str
status: str
zenml_version: str
zenml_server_url: str
dashboard_url: str
dashboard_organization_url: str


class ListWorkspacesResponse(TypedDict):
workspaces: List[Workspace]
total: int
offset: int
limit: int


class Project(TypedDict):
id: str
name: str
display_name: Optional[str]
created: Optional[str]
updated: Optional[str]
metadata: Optional[MetadataType]


class ListProjectsResponse(TypedDict):
projects: List[Project]
total: int
total_pages: int
current_page: int
items_per_page: int
14 changes: 13 additions & 1 deletion bundled/tool/zen_watcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from typing import Any, Optional

import yaml
from constants import ZENML_SERVER_CHANGED, ZENML_STACK_CHANGED
from constants import ZENML_PROJECT_CHANGED, ZENML_SERVER_CHANGED, ZENML_STACK_CHANGED
from lazy_import import suppress_stdout_temporarily
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
Expand All @@ -44,6 +44,7 @@ def __init__(self, lsp_server):
self._timer: Optional[Timer] = None
self.last_known_url: str = ""
self.last_known_stack_id: str = ""
self.last_known_project_name: str = ""
self.show_notification: bool = os.getenv("LS_SHOW_NOTIFICATION", "off") in [
"onError",
"onWarning",
Expand All @@ -66,9 +67,12 @@ def process_config_change(self, config_file_path: str):

new_url = config.get("store", {}).get("url", "")
new_stack_id = config.get("active_stack_id", "")
new_project_name = config.get("active_project_name", "")

url_changed = new_url != self.last_known_url
stack_id_changed = new_stack_id != self.last_known_stack_id
project_name_changed = new_project_name != self.last_known_project_name

# Send ZENML_SERVER_CHANGED if url changed
if url_changed:
server_details = {
Expand All @@ -81,10 +85,18 @@ def process_config_change(self, config_file_path: str):
server_details,
)
self.last_known_url = new_url

# Send ZENML_STACK_CHANGED if stack_id changed
if stack_id_changed:
self.LSP_SERVER.send_custom_notification(ZENML_STACK_CHANGED, new_stack_id)
self.last_known_stack_id = new_stack_id

# Send ZENML_PROJECT_CHANGED if project_name changed
if project_name_changed and new_project_name:
self.LSP_SERVER.send_custom_notification(
ZENML_PROJECT_CHANGED, new_project_name
)
self.last_known_project_name = new_project_name
except (FileNotFoundError, PermissionError) as e:
self.log_error(f"Configuration file access error: {e} - {config_file_path}")
except yaml.YAMLError as e:
Expand Down
13 changes: 10 additions & 3 deletions bundled/tool/zenml_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,25 @@ def __init__(self):
server interactions, stacks, and pipeline runs.
"""
# pylint: disable=wrong-import-position,import-error
from lazy_import import lazy_import
from lazy_import import lazy_import, suppress_stdout_temporarily
from zenml_wrappers import (
GlobalConfigWrapper,
PipelineRunsWrapper,
ProjectsWrapper,
StacksWrapper,
WorkspacesWrapper,
ZenServerWrapper,
)

self.client = lazy_import("zenml.client", "Client")()
# Suppress colorful warnings during client initialization
with suppress_stdout_temporarily():
self.client = lazy_import("zenml.client", "Client")()

# initialize zenml library wrappers
self.config_wrapper = GlobalConfigWrapper()
self.zen_server_wrapper = ZenServerWrapper(self.config_wrapper)
self.stacks_wrapper = StacksWrapper(self.client)
self.pipeline_runs_wrapper = PipelineRunsWrapper(self.client)
self.workspaces_wrapper = WorkspacesWrapper(self.client, self.config_wrapper)
self.projects_wrapper = ProjectsWrapper(self.client)
self.zen_server_wrapper = ZenServerWrapper(self.config_wrapper, self.projects_wrapper)
self.initialized = True
Loading