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: 2 additions & 1 deletion .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ jobs:
run: |
just install
# Install workspace packages
uv pip install -e dashboard
uv pip install -e common
uv pip install -e dashboard
uv pip install -e explore

- name: 🧪 Run pre-commit hooks
run: |
Expand Down
13 changes: 10 additions & 3 deletions .github/workflows/docker-validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ jobs:
failure-threshold: error

- name: 🐳 Test Docker build for ${{ matrix.sub-project }}
if: matrix.sub-project != 'discovery'
run: |
echo "🔨 Testing build for ${{ matrix.sub-project }}..."
docker build --build-arg PYTHON_VERSION=${{ env.PYTHON_VERSION }} -f ${{ matrix.sub-project }}/Dockerfile . --target builder
Expand Down Expand Up @@ -90,7 +91,7 @@ jobs:
- name: 🔍 Check docker-compose services
run: |
services=$(docker-compose config --services | sort | tr "\n" " " | sed "s/ $//")
expected="dashboard discovery extractor graphinator neo4j postgres rabbitmq redis tableinator"
expected="dashboard discovery explore extractor graphinator neo4j postgres rabbitmq redis tableinator"

if [ "$services" != "$expected" ]; then
echo "❌ Service mismatch!"
Expand Down Expand Up @@ -127,6 +128,12 @@ jobs:
exit 1
fi

deps=$(docker-compose config | yq eval '.services.explore.depends_on | keys | .[]' -)
if [ "$deps" != "neo4j" ]; then
echo "❌ Explore should only depend on neo4j"
exit 1
fi

deps=$(docker-compose config | yq eval '.services.tableinator.depends_on | keys | sort | join(" ")' -)
if [ "$deps" != "postgres rabbitmq" ]; then
echo "❌ Tableinator should depend on postgres and rabbitmq"
Expand All @@ -138,7 +145,7 @@ jobs:
- name: 🛡️ Check for security best practices
run: |
# Check that services run as non-root user
for service in dashboard discovery extractor graphinator tableinator; do
for service in dashboard discovery explore extractor graphinator tableinator; do
user=$(docker-compose config | yq eval ".services.$service.user" -)
if [ "$user" != "1000:1000" ]; then
echo "❌ $service should run as user 1000:1000"
Expand All @@ -148,7 +155,7 @@ jobs:
echo "✅ All services run as non-root user"

# Check security options
for service in dashboard discovery extractor graphinator tableinator; do
for service in dashboard discovery explore extractor graphinator tableinator; do
security_opt=$(docker-compose config | yq eval ".services.$service.security_opt[]" - | grep "no-new-privileges:true" || true)
if [ -z "$security_opt" ]; then
echo "❌ $service should have no-new-privileges security option"
Expand Down
70 changes: 57 additions & 13 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
# This workflow runs all E2E tests including desktop browsers and mobile device emulation.
# It can be called from other workflows or triggered directly on dashboard changes.
# It can be called from other workflows or triggered directly on service changes.
name: E2E Test

on:
Expand All @@ -11,7 +11,9 @@ on:
- main
paths:
- "dashboard/**"
- "explore/**"
- "tests/dashboard/**"
- "tests/explore/**"
- ".github/workflows/e2e-test.yml"
- "common/**"
- "pyproject.toml"
Expand All @@ -21,7 +23,9 @@ on:
- main
paths:
- "dashboard/**"
- "explore/**"
- "tests/dashboard/**"
- "tests/explore/**"
- ".github/workflows/e2e-test.yml"
- "common/**"
- "pyproject.toml"
Expand All @@ -39,6 +43,7 @@ env:
permissions:
contents: read
pull-requests: write # Required for coverage report comments
statuses: write # Required for coverage status creation

jobs:
e2e-test:
Expand Down Expand Up @@ -119,6 +124,7 @@ jobs:
uv sync --all-extras --frozen
# Install workspace packages
uv pip install -e dashboard
uv pip install -e explore
uv pip install -e common

- name: 🎭 Install Playwright browsers
Expand All @@ -129,13 +135,26 @@ jobs:
# Install system dependencies
uv run playwright install-deps ${{ matrix.browser-install }}

- name: 🧪 Run dashboard unit tests (coverage baseline)
run: |
# Run unit tests first to establish coverage baseline for dashboard + explore
uv run pytest tests/dashboard/ -v -m 'not e2e' \
--cov=dashboard --cov=explore --cov-report=term

- name: 🧪 Run explore unit tests (coverage baseline)
run: |
# Append explore unit test coverage
uv run pytest tests/explore/ -v -m 'not e2e' \
--cov=dashboard --cov=explore --cov-append --cov-report=term

- name: 🧪 Run dashboard E2E tests (Desktop)
if: matrix.device == null
run: |
# Run E2E tests with coverage - server is started automatically by pytest fixture
uv run pytest tests/dashboard/test_dashboard_ui.py -v -m e2e \
--browser ${{ matrix.browser }} \
--cov --cov-report=xml --cov-report=json --cov-report=term
--cov=dashboard --cov=explore --cov-append \
--cov-report=xml --cov-report=json --cov-report=term

- name: 📱 Run dashboard E2E tests (Mobile)
if: matrix.device != null
Expand All @@ -144,22 +163,47 @@ jobs:
uv run pytest tests/dashboard/test_dashboard_ui.py -v -m e2e \
--browser ${{ matrix.browser }} \
--device "${{ matrix.device }}" \
--cov --cov-report=xml --cov-report=json --cov-report=term
--cov=dashboard --cov=explore --cov-append \
--cov-report=xml --cov-report=json --cov-report=term

- name: 🔍 Run explore E2E tests (Desktop)
if: matrix.device == null
run: |
# Run explore E2E tests with coverage - server is started automatically by pytest fixture
uv run pytest tests/explore/test_explore_ui.py -v -m e2e \
--browser ${{ matrix.browser }} \
--cov=dashboard --cov=explore --cov-append \
--cov-report=xml --cov-report=json --cov-report=term

- name: 📱 Run explore E2E tests (Mobile)
if: matrix.device != null
run: |
# Run explore E2E tests with coverage and device emulation
uv run pytest tests/explore/test_explore_ui.py -v -m e2e \
--browser ${{ matrix.browser }} \
--device "${{ matrix.device }}" \
--cov=dashboard --cov=explore --cov-append \
--cov-report=xml --cov-report=json --cov-report=term

- name: 📋 Process E2E coverage reports
id: coverage-setup
if: always() # Process coverage even if tests fail
run: |
# Transform coverage.json (generated by pytest) to coverage-summary.json format
jq '{
total: {
statements: {total: .totals.num_statements, covered: .totals.covered_lines},
lines: {total: .totals.num_statements, covered: .totals.covered_lines},
functions: {total: 0, covered: 0},
branches: {total: 0, covered: 0}
}
}' coverage.json > coverage-summary.json
echo "coverage-exists=true" >> "$GITHUB_OUTPUT"
if [ -f coverage.json ]; then
# Transform coverage.json (generated by pytest) to coverage-summary.json format
jq '{
total: {
statements: {total: .totals.num_statements, covered: .totals.covered_lines},
lines: {total: .totals.num_statements, covered: .totals.covered_lines},
functions: {total: 0, covered: 0},
branches: {total: 0, covered: 0}
}
}' coverage.json > coverage-summary.json
echo "coverage-exists=true" >> "$GITHUB_OUTPUT"
else
echo "⚠️ No coverage.json found — tests may not have run"
echo "coverage-exists=false" >> "$GITHUB_OUTPUT"
fi

- name: 📤 Upload test results
if: always()
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/list-sub-projects.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
[
{"name": "dashboard", "use_cache": true},
{"name": "discovery", "use_cache": false},
{"name": "explore", "use_cache": true},
{"name": "extractor/pyextractor", "use_cache": true},
{"name": "extractor/rustextractor", "use_cache": true},
{"name": "graphinator", "use_cache": true},
Expand Down
58 changes: 58 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,61 @@ jobs:
flags: discovery
name: discovery-tests

# ============================================================================
# EXPLORE SERVICE TESTS - Runs in parallel with all other test jobs
# ============================================================================
test-explore:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: 🔀 Checkout repository
uses: actions/checkout@v6

- name: 🔧 Setup Python and UV
uses: ./.github/actions/setup-python-uv
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: 🔧 Setup Just
uses: ./.github/actions/setup-just

- name: 📦 Install dependencies
run: just install

- name: 🧪 Run explore tests
run: |
uv run pytest tests/explore/ -v -m 'not e2e' \
--cov=explore --cov-report=xml --cov-report=json --cov-report=term

- name: 📋 Process coverage reports
id: coverage-setup
if: always()
run: |
if [ -f coverage.json ]; then
jq '{
total: {
statements: {total: .totals.num_statements, covered: .totals.covered_lines},
lines: {total: .totals.num_statements, covered: .totals.covered_lines},
functions: {total: 0, covered: 0},
branches: {total: 0, covered: 0}
}
}' coverage.json > coverage-summary.json
echo "coverage-exists=true" >> "$GITHUB_OUTPUT"
else
echo "coverage-exists=false" >> "$GITHUB_OUTPUT"
fi

- name: 📊 Upload coverage (explore)
if: always()
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
flags: explore
name: explore-tests
fail_ci_if_error: false

# ============================================================================
# PYTHON EXTRACTOR TESTS - Runs in parallel with all other test jobs
# ============================================================================
Expand Down Expand Up @@ -334,6 +389,7 @@ jobs:
- test-common
- test-dashboard
- test-discovery
- test-explore
- test-pyextractor
- test-graphinator
- test-tableinator
Expand All @@ -347,6 +403,7 @@ jobs:
echo " Common: ${{ needs.test-common.result }}"
echo " Dashboard: ${{ needs.test-dashboard.result }}"
echo " Discovery: ${{ needs.test-discovery.result }}"
echo " Explore: ${{ needs.test-explore.result }}"
echo " PyExtractor: ${{ needs.test-pyextractor.result }}"
echo " Graphinator: ${{ needs.test-graphinator.result }}"
echo " Tableinator: ${{ needs.test-tableinator.result }}"
Expand All @@ -356,6 +413,7 @@ jobs:
if [[ "${{ needs.test-common.result }}" == "failure" ]] || \
[[ "${{ needs.test-dashboard.result }}" == "failure" ]] || \
[[ "${{ needs.test-discovery.result }}" == "failure" ]] || \
[[ "${{ needs.test-explore.result }}" == "failure" ]] || \
[[ "${{ needs.test-pyextractor.result }}" == "failure" ]] || \
[[ "${{ needs.test-graphinator.result }}" == "failure" ]] || \
[[ "${{ needs.test-tableinator.result }}" == "failure" ]] || \
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ http://localhost:8000/api/health
### Service Ports

- Dashboard: 8003
- Explore: 8006 (service), 8007 (health)
- Discovery: 8005 (service), 8004 (health)
- Neo4j: 7474 (browser), 7687 (bolt)
- PostgreSQL: 5433 (mapped from 5432)
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Perfect for music researchers, data scientists, developers, and music enthusiast
| **[🔗](docs/emoji-guide.md#service-identifiers) Graphinator** | Builds Neo4j knowledge graphs | `neo4j-driver`, graph algorithms |
| **[🐘](docs/emoji-guide.md#service-identifiers) Tableinator** | Creates PostgreSQL analytics tables | `psycopg3`, JSONB, full-text search |
| **[🎵](docs/emoji-guide.md#service-identifiers) Discovery** | AI-powered music intelligence | `sentence-transformers`, `plotly`, `networkx` |
| **[🔍](docs/emoji-guide.md#service-identifiers) Explore** | Interactive graph exploration & trends | `FastAPI`, `D3.js`, `Plotly.js`, Neo4j |
| **[📊](docs/emoji-guide.md#service-identifiers) Dashboard** | Real-time system monitoring | `FastAPI`, WebSocket, reactive UI |

### 📐 System Architecture
Expand All @@ -64,6 +65,7 @@ graph TD
TABLE[["🐘 Tableinator<br/>Table Builder"]]
DASH[["📊 Dashboard<br/>Real-time Monitor<br/>WebSocket"]]
DISCO[["🎵 Discovery<br/>AI Engine<br/>ML Models"]]
EXPLORE[["🔍 Explore<br/>Graph Explorer<br/>Trends"]]
S3 -->|1a. Download & Parse| PYEXT
S3 -->|1b. Download & Parse| RSEXT
Expand All @@ -79,6 +81,8 @@ graph TD
DISCO -.->|Cache| REDIS
DISCO -.->|Analyze| DISCO
EXPLORE -.->|Query| NEO4J
DASH -.->|Monitor| PYEXT
DASH -.->|Monitor| RSEXT
DASH -.->|Monitor| GRAPH
Expand All @@ -98,6 +102,7 @@ graph TD
style REDIS fill:#ffebee,stroke:#b71c1c,stroke-width:2px
style DASH fill:#fce4ec,stroke:#880e4f,stroke-width:2px
style DISCO fill:#e3f2fd,stroke:#0d47a1,stroke-width:2px
style EXPLORE fill:#e8eaf6,stroke:#283593,stroke-width:2px
```

## 🌟 Key Features
Expand Down Expand Up @@ -212,6 +217,7 @@ open http://localhost:8003
| Service | URL | Default Credentials | Purpose |
| ----------------- | ---------------------- | ----------------------------------- | ------------------ |
| 📊 **Dashboard** | http://localhost:8003 | None | System monitoring |
| 🔍 **Explore** | http://localhost:8006 | None | Graph exploration |
| 🎵 **Discovery** | http://localhost:8005 | None | AI music discovery |
| 🐰 **RabbitMQ** | http://localhost:15672 | `discogsography` / `discogsography` | Queue management |
| 🔗 **Neo4j** | http://localhost:7474 | `neo4j` / `discogsography` | Graph exploration |
Expand All @@ -238,6 +244,7 @@ just init

# 5. Run any service
just dashboard # Monitoring UI
just explore # Graph exploration & trends
just discovery # AI discovery
just pyextractor # Python data ingestion
just rustextractor-run # Rust data ingestion (requires cargo)
Expand Down Expand Up @@ -523,6 +530,7 @@ uv run pytest tests/extractor/ # Extractor tests (Python)
uv run pytest tests/graphinator/ # Graphinator tests
uv run pytest tests/tableinator/ # Tableinator tests
uv run pytest tests/dashboard/ # Dashboard tests
uv run pytest tests/explore/ # Explore tests
```

#### 🎭 E2E Testing with Playwright
Expand Down Expand Up @@ -566,6 +574,9 @@ discogsography/
├── 📊 dashboard/ # Real-time monitoring dashboard
│ ├── dashboard.py # FastAPI backend with WebSocket
│ └── static/ # Frontend HTML/CSS/JS
├── 🔍 explore/ # Interactive graph exploration & trends
│ ├── explore.py # FastAPI backend with Neo4j queries
│ └── static/ # Frontend HTML/CSS/JS (D3.js, Plotly.js)
├── 📥 extractor/ # Data extraction services
│ ├── pyextractor/ # Python-based Discogs data ingestion
│ │ ├── extractor.py # Main processing logic
Expand Down Expand Up @@ -850,6 +861,7 @@ PGPASSWORD=discogsography psql -h localhost -U discogsography -d discogsography
curl http://localhost:8002/health # Tableinator
curl http://localhost:8003/health # Dashboard
curl http://localhost:8004/health # Discovery
curl http://localhost:8007/health # Explore
```

1. **🔍 Enable Debug Logging**
Expand Down
2 changes: 2 additions & 0 deletions common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
AMQP_QUEUE_PREFIX_TABLEINATOR,
DATA_TYPES,
DashboardConfig,
ExploreConfig,
ExtractorConfig,
GraphinatorConfig,
TableinatorConfig,
Expand Down Expand Up @@ -80,6 +81,7 @@
"CircuitState",
"DashboardConfig",
"DownloadPhase",
"ExploreConfig",
"ExponentialBackoff",
"ExtractionSummary",
"ExtractorConfig",
Expand Down
Loading
Loading