diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..d444e68 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(rg:*)", + "Bash(grep:*)", + "Bash(npm run compile:*)" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/.github/workflows/build-extensions.yml b/.github/workflows/build-extensions.yml index 63a850b..7f3f9cc 100644 --- a/.github/workflows/build-extensions.yml +++ b/.github/workflows/build-extensions.yml @@ -41,9 +41,9 @@ jobs: - name: Build theme extension run: | echo "Building theme extension..." - cd themes && npx @vscode/vsce package --out ../zenml-color-theme.vsix + cd themes && npx @vscode/vsce package --out ./zenml-color-theme-0.0.1.vsix - - name: Verify extensions were built + - name: Verify extensions were builts run: | echo "Checking built extensions..." if [ -d ".devcontainer/extensions" ]; then @@ -54,11 +54,11 @@ jobs: exit 1 fi - if [ -f "zenml-color-theme.vsix" ]; then + if [ -f "themes/zenml-color-theme-0.0.1.vsix" ]; then echo "Theme extension built successfully:" - ls -la zenml-color-theme.vsix + ls -la themes/zenml-color-theme-0.0.1.vsix else - echo "ERROR: zenml-color-theme.vsix not found" + echo "ERROR: zenml-color-theme-0.0.1.vsix not found" exit 1 fi @@ -72,7 +72,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: theme-extension - path: zenml-color-theme.vsix + path: themes/zenml-color-theme-0.0.1.vsix - name: Commit and push built extensions # Only commit on push to main/develop branches, not on PRs @@ -83,7 +83,7 @@ jobs: # Add the built extensions to git git add .devcontainer/extensions/zenml-codespace-tutorial-*.vsix - git add zenml-color-theme.vsix + git add themes/zenml-color-theme-0.0.1.vsix # Check if there are any changes to commit if git diff --staged --quiet; then diff --git a/assets/main.css b/assets/main.css index ac44b1d..73f7bd6 100644 --- a/assets/main.css +++ b/assets/main.css @@ -319,7 +319,7 @@ header { } .tutorial-title h2 { - font-size: 1rem; + font-size: 1.25rem; font-weight: 600; color: var(--zenml-dark); margin: 0; @@ -448,6 +448,13 @@ footer .run-pipeline-button.running { footer .run-pipeline-button.completed { background: var(--zenml-green); color: white; + cursor: default; +} + +footer .run-pipeline-button.completed:hover { + background: var(--zenml-green); + transform: none; + box-shadow: none; } footer .run-pipeline-button.failed { @@ -455,6 +462,54 @@ footer .run-pipeline-button.failed { color: white; } + +/* Pipeline button group */ +.pipeline-button-group { + display: flex; + gap: 8px; + align-items: center; +} + +/* Dashboard button styling - smaller variant */ +.dashboard-button-small { + background: var(--vscode-button-secondaryBackground); + border: 1px solid var(--vscode-sideBar-border); + color: var(--vscode-button-secondaryForeground); + padding: 9px 16px; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 6px; + justify-content: center; + font-family: "Inter", sans-serif; + margin: 0; + height: 38px; + text-decoration: none; + flex-shrink: 0; + white-space: nowrap; + box-sizing: border-box; +} + +.dashboard-button-small:hover { + background: var(--vscode-button-secondaryHoverBackground); + transform: translateY(-1px); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); + color: var(--vscode-button-secondaryForeground); + text-decoration: none; +} + +.dashboard-button-small:active { + transform: translateY(0); +} + +.dashboard-button-small .codicon { + font-size: 14px; +} + #progress { height: 4px; background-color: var(--vscode-progressBar-background); diff --git a/assets/main.js b/assets/main.js index f609a1f..b2b0031 100644 --- a/assets/main.js +++ b/assets/main.js @@ -476,7 +476,8 @@ } else if (status === "completed" || status === "cached") { runButton.innerHTML = `āœ“ ${buttonTexts[status]}`; //@ts-ignore - runButton.disabled = false; + runButton.disabled = true; + runButton.style.cursor = "default"; } else if (status === "failed") { runButton.innerHTML = ` ${buttonTexts[status]}`; //@ts-ignore @@ -493,14 +494,18 @@ } function showDashboardUrl(url) { - const dashboardLink = document.getElementById("dashboard-link"); - const dashboardUrl = document.getElementById("dashboard-url"); + const dashboardButton = document.getElementById("dashboard-button"); - if (dashboardLink && dashboardUrl) { + if (dashboardButton) { //@ts-ignore - dashboardUrl.href = url; - dashboardUrl.textContent = "View Pipeline in Dashboard"; - dashboardLink.style.display = "flex"; + dashboardButton.href = url; + dashboardButton.style.display = "flex"; + + // Add click handler to open in external browser + dashboardButton.addEventListener('click', function(e) { + e.preventDefault(); + vscode.postMessage({ type: "openDashboard", url: url }); + }); } } diff --git a/pipelines/caching/cache_pipeline.py b/pipelines/caching/cache_pipeline.py index 106786d..19277ec 100644 --- a/pipelines/caching/cache_pipeline.py +++ b/pipelines/caching/cache_pipeline.py @@ -11,9 +11,8 @@ @step(enable_cache=True) def slow_step() -> Annotated[int, "answer"]: - logger.info("Starting slow computation (3 seconds)...") + logger.info("šŸ”„ Actually computing result... (sleeping 3 seconds)") time.sleep(3) - logger.info("Computation completed!") return 42 @@ -23,10 +22,17 @@ def cache_pipeline(): if __name__ == "__main__": - logger.info("First run - will take ~3 seconds") - cache_pipeline() # first run ~3 s - - logger.info("Second run - should be instant (cache hit)") - cache_pipeline() # second run instant (cache hit) + logger.info("\n" + "="*60) + logger.info(">>> RUN 1: First execution (no cache available)") + logger.info("="*60) + cache_pipeline() + + logger.info("\n" + "="*60) + logger.info(">>> RUN 2: Second execution (cache should be used)") + logger.info("="*60) + cache_pipeline() + + logger.info("\nšŸ’” Notice: The step's log message only appears in Run 1!") + logger.info(" In Run 2, the step was skipped entirely due to caching.") log_dashboard_urls("cache_pipeline") diff --git a/pipelines/caching/caching.md b/pipelines/caching/caching.md index 1d89a4f..06ad35b 100644 --- a/pipelines/caching/caching.md +++ b/pipelines/caching/caching.md @@ -1,7 +1,3 @@ -# Caching - Smart Re-runs - -Learn how ZenML's caching system can save you time by avoiding redundant computations. - ## What you'll learn - How to enable caching on steps @@ -28,4 +24,5 @@ def slow_step() -> Annotated[int, "answer"]: ## Try it yourself -Run this pipeline twice! The first run takes ~3 seconds, the second is instant thanks to caching. \ No newline at end of file +Run this pipeline twice! The first run takes ~3 seconds, the second is instant +thanks to caching. diff --git a/pipelines/completion/completion.md b/pipelines/completion/completion.md index 1f7d7a0..afebd6d 100644 --- a/pipelines/completion/completion.md +++ b/pipelines/completion/completion.md @@ -14,7 +14,7 @@ You Completed the Tutorial -You've successfully completed the **ZenML Interactive Tutorial** and mastered the fundamentals of MLOps with ZenML! +You've successfully completed the **ZenML Interactive Tutorial** and mastered the fundamentals of using ZenML for MLOps!
diff --git a/pipelines/fanOut/fanOut.md b/pipelines/fanOut/fanOut.md index 2a981dc..c15ffa5 100644 --- a/pipelines/fanOut/fanOut.md +++ b/pipelines/fanOut/fanOut.md @@ -1,7 +1,3 @@ -# Fan-out/Fan-in - Parallel Processing - -Learn how to create parallel workflows that split work across multiple steps and then combine the results. - ## What you'll learn - How to create parallel processing patterns @@ -29,4 +25,5 @@ def fan_pipeline(parallel: int = 4): ## Try it yourself -Run this pipeline to see how it processes data in parallel across multiple steps, then combines the results! \ No newline at end of file +Run this pipeline to see how it processes data in parallel across multiple +steps, then combines the results! diff --git a/pipelines/fanOut/fan_pipeline.py b/pipelines/fanOut/fan_pipeline.py index c2f92a9..35c1a20 100644 --- a/pipelines/fanOut/fan_pipeline.py +++ b/pipelines/fanOut/fan_pipeline.py @@ -1,11 +1,3 @@ -""" -Shows parallel fan-out (multiple identical steps) and a fan-in step that -gathers all their outputs via the Client API. - -Run it once and observe the printed summary. Re-run: cache is OFF so you -see the steps execute every time. -""" - from typing_extensions import Annotated from zenml import get_step_context, pipeline, step from zenml.client import Client diff --git a/pipelines/helloWorld/helloWorld.md b/pipelines/helloWorld/helloWorld.md index e510199..a3edffe 100644 --- a/pipelines/helloWorld/helloWorld.md +++ b/pipelines/helloWorld/helloWorld.md @@ -1,5 +1,3 @@ -# Hello World - Steps & Pipelines - Welcome to your first ZenML pipeline! This tutorial introduces the fundamental concepts of **steps** and **pipelines**. ## What you'll learn @@ -30,4 +28,5 @@ def hello_pipeline(): ## Try it yourself -Click the **Run Pipeline** button to execute your first ZenML pipeline and see the output! \ No newline at end of file +Click the **Run Pipeline** button to execute your first ZenML pipeline and see +the output! diff --git a/pipelines/metadata/metadata.md b/pipelines/metadata/metadata.md index 36d9674..a5238ce 100644 --- a/pipelines/metadata/metadata.md +++ b/pipelines/metadata/metadata.md @@ -1,7 +1,3 @@ -# Metadata - Recording Useful Facts - -Learn how to log metadata to track important information about your pipeline runs. - ## What you'll learn - How to use `log_metadata()` to record key information @@ -29,4 +25,4 @@ def compute_accuracy() -> Annotated[float, "accuracy_metric"]: ## Try it yourself -Run this pipeline and check the ZenML dashboard to see the metadata card! \ No newline at end of file +Run this pipeline and check the ZenML dashboard to see the metadata card! diff --git a/pipelines/parameters/parameters.md b/pipelines/parameters/parameters.md index 64fc8d0..ac05558 100644 --- a/pipelines/parameters/parameters.md +++ b/pipelines/parameters/parameters.md @@ -1,7 +1,3 @@ -# Parameters - Configurable Behavior - -Learn how to make your pipelines configurable with parameters. - ## What you'll learn - How to add parameters to steps and pipelines @@ -31,4 +27,4 @@ def param_pipeline(number: int = 3, factor: int = 2): ## Try it yourself -Run this pipeline to see how parameters control the multiplication operation! \ No newline at end of file +Run this pipeline to see how parameters control the multiplication operation! diff --git a/pipelines/retries/retries.md b/pipelines/retries/retries.md index 59aaef6..80affb7 100644 --- a/pipelines/retries/retries.md +++ b/pipelines/retries/retries.md @@ -1,7 +1,3 @@ -# Retries & Hooks - Robust Pipelines - -Learn how to build resilient pipelines that can handle failures gracefully with automatic retries and hooks. - ## What you'll learn - How to configure automatic retries for flaky steps @@ -32,4 +28,5 @@ def flaky() -> Annotated[str, "result"]: ## Try it yourself -Run this pipeline multiple times! Sometimes it succeeds immediately, sometimes it needs retries to handle the random failures. \ No newline at end of file +Run this pipeline multiple times! Sometimes it succeeds immediately, sometimes +it needs retries to handle the random failures. diff --git a/pipelines/retries/robust_pipeline.py b/pipelines/retries/robust_pipeline.py index 69efa63..24d4b5f 100644 --- a/pipelines/retries/robust_pipeline.py +++ b/pipelines/retries/robust_pipeline.py @@ -1,8 +1,3 @@ -""" -Demonstrates automatic retries *and* a failure hook. -Run it a few times – roughly half will need 1–2 retries. -""" - import random import time diff --git a/pipelines/stepIO/stepIO.md b/pipelines/stepIO/stepIO.md index 5a70396..1c3058f 100644 --- a/pipelines/stepIO/stepIO.md +++ b/pipelines/stepIO/stepIO.md @@ -1,7 +1,3 @@ -# Step I/O - Typed Inputs and Outputs - -Now let's learn how to pass data between steps with proper type annotations. - ## What you'll learn - How to define typed step outputs using `Annotated` types @@ -37,4 +33,5 @@ def count_rows( ## Try it yourself -Run this pipeline to see how data flows from the `load_data` step to the `count_rows` step! \ No newline at end of file +Run this pipeline to see how data flows from the `load_data` step to the +`count_rows` step! diff --git a/pipelines/tagging/tagged_pipeline.py b/pipelines/tagging/tagged_pipeline.py index d2b86c9..e353c49 100644 --- a/pipelines/tagging/tagged_pipeline.py +++ b/pipelines/tagging/tagged_pipeline.py @@ -1,12 +1,3 @@ -""" -Artifact Tagging Demonstration -This example shows different ways to tag artifacts in ZenML: -1. Regular tags on artifacts using ArtifactConfig -2. Cascade tags from pipeline to artifacts -3. Dynamic tagging within steps -4. Filtering artifacts by tags -""" - from typing import Annotated import pandas as pd diff --git a/pipelines/tagging/tagging.md b/pipelines/tagging/tagging.md index aea40f2..098dfbe 100644 --- a/pipelines/tagging/tagging.md +++ b/pipelines/tagging/tagging.md @@ -1,7 +1,3 @@ -# Tagging - Keep Runs Organized - -Learn how to use tags to organize and categorize your pipeline runs. - ## What you'll learn - How to add simple tags to pipelines @@ -27,4 +23,5 @@ def tagged_pipeline(): ## Try it yourself -Run this pipeline multiple times and watch how the exclusive tag automatically moves to the latest run! \ No newline at end of file +Run this pipeline multiple times and watch how the exclusive tag automatically +moves to the latest run! diff --git a/pipelines/visualizations/visualizations-dashboard.png b/pipelines/visualizations/visualizations-dashboard.png new file mode 100644 index 0000000..c67235a Binary files /dev/null and b/pipelines/visualizations/visualizations-dashboard.png differ diff --git a/pipelines/visualizations/visualizations.md b/pipelines/visualizations/visualizations.md index 8de6779..58b0adf 100644 --- a/pipelines/visualizations/visualizations.md +++ b/pipelines/visualizations/visualizations.md @@ -1,7 +1,3 @@ -# Visualizations - Custom Charts - -Learn how to create automatic and custom visualizations that appear in the ZenML dashboard. - ## What you'll learn - How ZenML automatically visualizes common data types @@ -30,6 +26,9 @@ def scatter(df: pd.DataFrame) -> Annotated[HTMLString, "scatter_plot"]: - **HTMLString type** lets you embed rich content in the dashboard - **Dashboard integration** makes results easily shareable +![Two visualizations generated by this pipeline](./pipelines/visualizations/visualizations-dashboard.png) + ## Try it yourself -Run this pipeline and check the dashboard to see both the automatic DataFrame visualization and the custom scatter plot! \ No newline at end of file +Run this pipeline and check the dashboard to see both the automatic DataFrame +visualization and the custom scatter plot! diff --git a/pipelines/welcome/welcome.md b/pipelines/welcome/welcome.md index ad5e630..f1b9e4c 100644 --- a/pipelines/welcome/welcome.md +++ b/pipelines/welcome/welcome.md @@ -1,7 +1,7 @@
-# Welcome to ZenML Interactive Tutorial! +# Welcome to the ZenML Interactive Tutorial! This hands-on tutorial will teach you **ZenML fundamentals** through 10 progressive lessons. Each lesson introduces exactly **one new concept** with copy-paste code examples. diff --git a/src/tutorialOrchestrator.ts b/src/tutorialOrchestrator.ts index 94a5ec1..bc7ef69 100644 --- a/src/tutorialOrchestrator.ts +++ b/src/tutorialOrchestrator.ts @@ -27,8 +27,14 @@ export default class TutorialOrchestrator { private _getDashboardUrl(runId?: string): string { const baseUrl = vscode.workspace .getConfiguration("zenml") - .get("dashboardUrl", "http://localhost:8080"); - return runId ? `${baseUrl}/workspaces/default/runs/${runId}` : baseUrl; + .get("dashboardUrl", "https://cloud.zenml.io"); + // If we have a run ID, show specific run, otherwise show pipelines page + if (runId) { + return `${baseUrl}/workspaces/default/runs/${runId}`; + } else { + // Fallback to generic pipelines page when no specific run ID + return `${baseUrl}/workspaces/default/pipelines`; + } } constructor(context: vscode.ExtensionContext, tutorial: Tutorial) { @@ -142,7 +148,7 @@ export default class TutorialOrchestrator { codeRunner( this.terminal, this._codePanel.document.uri, - () => { + (dashboardUrl?: string) => { vscode.window.showInformationMessage("Code Ran Successfully! šŸŽ‰"); if (callback) { callback(); @@ -196,7 +202,7 @@ export default class TutorialOrchestrator { codeRunner( this.terminal, this._codePanel.document.uri, - (runId?: string) => { + (dashboardUrl?: string) => { // Pipeline completed successfully this._pipelineRunning = false; this._completedTutorials.add(this._tutorial.currentSection.index); @@ -204,19 +210,17 @@ export default class TutorialOrchestrator { type: "pipelineStatusUpdate", status: "completed", }); - this._sendWebviewMessage({ type: "pipelineCompleted", runId: runId }); + this._sendWebviewMessage({ type: "pipelineCompleted" }); // Save progress this._saveProgress(); - // Show dashboard URL - if (runId) { - const dashboardUrl = this._getDashboardUrl(runId); - this._sendWebviewMessage({ - type: "showDashboardUrl", - url: dashboardUrl, - }); - } + // Show dashboard URL - use the captured URL or fallback to generic + const finalDashboardUrl = dashboardUrl || this._getDashboardUrl(); + this._sendWebviewMessage({ + type: "showDashboardUrl", + url: finalDashboardUrl, + }); }, () => { // Pipeline failed @@ -779,10 +783,16 @@ export default class TutorialOrchestrator { Prev - +
+ + +