Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,6 @@ load: ## Run load tests

smoke: ## Run smoke tests
k6 run tools/k6/smoke.js

livebook: ## Start Livebook server
livebook server --home livebooks/
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,31 @@ make db-up
make fmt
```

## πŸ““ Getting Started with Livebook

Interactive learning notebooks are available in `livebooks/`.

```bash
# Install dependencies (includes Kino for Livebook)
mix deps.get

# Start Livebook server
make livebook

# Open your browser to http://localhost:8080
# Navigate to setup.livemd to begin
```

**What's in Livebook?**

- **Interactive exercises** - Run code directly in your browser
- **7 Phase 1 checkpoints** - Pattern matching, recursion, Enum/Stream, error handling, property testing, and more
- **Progress tracking** - Monitor your completion across all 15 phases
- **Visualizations** - See benchmarks and performance comparisons
- **Self-assessments** - Check your understanding at each step

See `livebooks/README.md` for more details.

## πŸ“š Documentation

- **[Roadmap](docs/roadmap.md)** - Learning phases and milestones
Expand Down
57 changes: 56 additions & 1 deletion docs/LESSON-PLANNING-SYSTEM.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ This repository contains a comprehensive lesson planning system designed to tran

### System Components

The lesson planning system consists of **four interconnected layers**:
The lesson planning system consists of **five interconnected layers**:

1. **Workbooks** - Interactive exercises and self-assessment
2. **Study Guides** - Reading schedules and daily objectives
3. **Lesson Plans** - Detailed teaching materials and facilitation guides
4. **Curriculum Map** - Visual dependencies and learning pathways
5. **Livebook Notebooks** - Executable interactive learning experiences

---

Expand Down Expand Up @@ -193,6 +194,60 @@ graph TD

---

### 5. Livebook Notebooks (`livebooks/`)

**Purpose:** Interactive, executable learning experiences with immediate feedback

**Structure:**
- Phase-specific directories (phase-01-core, phase-02-processes, etc.)
- Multiple livebooks per phase (one per checkpoint)
- Setup guide and progress dashboard
- Smart cells for testing and load testing
- Embedded visualizations and benchmarks

**Content:**
- Executable code cells with examples
- Interactive exercises that students can modify and run
- Self-assessment checklists with Kino forms
- Visualizations using VegaLite
- Property-based tests with StreamData
- CSV parsing and streaming examples
- Real-time progress tracking

**Usage:**
- Students execute code directly in the browser
- Experiment by modifying examples
- Complete interactive exercises inline
- Track progress across all phases
- Visualize performance comparisons

**Example:** `livebooks/phase-01-core/01-pattern-matching.livemd`

**Key Features:**
```elixir
# Interactive exercise
defmodule ResultHandler do
def unwrap({:ok, value}, _default), do: value
def unwrap({:error, _}, default), do: default
end

# Self-assessment
form = Kino.Control.form([
objective1: {:checkbox, "I can pattern match on tuples"},
objective2: {:checkbox, "I understand guards"}
], submit: "Check Progress")
```

**Benefits over Static Workbooks:**
- Immediate execution and feedback
- No copy-paste to IEx required
- Visual benchmarks and charts
- Progress tracking built-in
- Encourages experimentation
- Self-paced with interactive validation

---

## πŸ”„ How the System Works Together

### For Self-Learners
Expand Down
134 changes: 134 additions & 0 deletions lib/livebook_extensions/k6_runner.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
defmodule LivebookExtensions.K6Runner do
@moduledoc """
Interactive k6 load test runner using Kino.Control.form.

This module provides a simple UI to:
- Select which phase to test
- Choose test type (smoke, load, stress)
- Configure virtual users and duration
- Run k6 tests and display results inline

## Usage

In a Livebook cell:

LivebookExtensions.K6Runner.render()
"""

def render(opts \\ []) do
phases = Enum.map(1..15, &"Phase #{String.pad_leading(to_string(&1), 2, "0")}")
default_phase = opts[:phase] || List.first(phases)

form =
Kino.Control.form(
[
phase: {:select, "Select phase to test", phases |> Enum.map(&{&1, &1})},
test_type:
{:select, "Test type",
[
{"Smoke test (quick validation)", "smoke"},
{"Load test (sustained load)", "load"},
{"Stress test (breaking point)", "stress"}
], default: "load"},
vus: {:number, "Virtual users", default: 50},
duration: {:text, "Duration (e.g., 30s, 1m)", default: "30s"}
],
submit: "Run k6 Test"
)

Kino.render(form)

# Listen for form submission and run k6
Kino.listen(form, fn %{data: data} ->
run_k6(data)
end)

form
end

defp run_k6(data) do
# Extract phase number from "Phase 01" format
phase_num = data.phase |> String.split() |> List.last()
script_path = "tools/k6/phase-#{phase_num}-gate.js"

result =
if File.exists?(script_path) do
{output, exit_code} =
System.cmd(
"k6",
[
"run",
"--vus",
to_string(data.vus),
"--duration",
data.duration,
script_path
],
stderr_to_stdout: true
)

metrics = parse_k6_output(output)

"""
## k6 Load Test Results: #{data.phase}

**Configuration:**
- Test type: #{data.test_type}
- Virtual users: #{data.vus}
- Duration: #{data.duration}

**Metrics:**
- Requests/sec: #{metrics.rps}
- p95 Latency: #{metrics.p95}
- Error Rate: #{metrics.error_rate}

#{if metrics.error_rate_numeric > 1.0, do: "⚠️ **Error rate above 1%**", else: "βœ… **All systems nominal**"}

### Full Output

```
#{output}
```
"""
else
"""
❌ **k6 script not found:** `#{script_path}`

Make sure the k6 test scripts exist in `tools/k6/`.

Available scripts:
```
#{case File.ls("tools/k6") do
{:ok, files} -> Enum.join(files, "\n")
{:error, _} -> "tools/k6 directory not found"
end}
```
"""
end

Kino.Markdown.new(result) |> Kino.render()
end

defp parse_k6_output(output) do
%{
rps: extract_metric(output, ~r/http_reqs.*?([\d.]+)\/s/, "N/A"),
p95: extract_metric(output, ~r/http_req_duration.*?p\(95\)=([\d.]+)ms/, "N/A"),
error_rate: extract_metric(output, ~r/http_req_failed.*?([\d.]+)%/, "0.0%"),
error_rate_numeric: extract_numeric(output, ~r/http_req_failed.*?([\d.]+)%/, 0.0)
}
end

defp extract_metric(output, regex, default) do
case Regex.run(regex, output) do
[_, value] -> value
_ -> default
end
end

defp extract_numeric(output, regex, default) do
case Regex.run(regex, output) do
[_, value] -> String.to_float(value)
_ -> default
end
end
end
94 changes: 94 additions & 0 deletions lib/livebook_extensions/test_runner.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
defmodule LivebookExtensions.TestRunner do
@moduledoc """
Interactive test runner for labs_* applications using Kino.Control.form.

This module provides a simple UI to:
- Select which labs_* application to test
- Choose whether to show coverage
- Run tests and display results inline

## Usage

In a Livebook cell:

LivebookExtensions.TestRunner.render()
"""

def render(opts \\ []) do
apps = scan_lab_apps()
default_app = opts[:app] || List.first(apps) || "no_apps_found"

form =
Kino.Control.form(
[
app: {:select, "Select lab app to test", apps |> Enum.map(&{&1, &1})},
coverage: {:checkbox, "Show test coverage", default: true}
],
submit: "Run Tests"
)

Kino.render(form)

# Listen for form submission and run tests
Kino.listen(form, fn %{data: data} ->
run_tests(data.app, data.coverage)
end)

form
end

defp run_tests(app, show_coverage) do
app_path = Path.join("apps", app)

result =
if File.dir?(app_path) do
args = if show_coverage, do: ["test", "--cover"], else: ["test"]

{output, exit_code} =
System.cmd(
"mix",
args,
cd: app_path,
stderr_to_stdout: true,
env: [{"MIX_ENV", "test"}]
)

case exit_code do
0 ->
"""
## βœ… Tests Passed for #{app}

```
#{output}
```
"""

_ ->
"""
## ❌ Tests Failed for #{app}

```
#{output}
```
"""
end
else
"❌ App directory not found: #{app_path}"
end

Kino.Markdown.new(result) |> Kino.render()
end

defp scan_lab_apps do
apps_path = "apps"

if File.dir?(apps_path) do
apps_path
|> File.ls!()
|> Enum.filter(&String.starts_with?(&1, "labs_"))
|> Enum.sort()
else
[]
end
end
end
1 change: 1 addition & 0 deletions livebooks/.progress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Loading
Loading