This is an automated email from the ASF dual-hosted git repository. sergehuber pushed a commit to branch UNOMI-932-unomi3-major-release-story in repository https://gitbox.apache.org/repos/asf/unomi-site.git
commit fb5eba30d32a101f48b05717f430ecf9ae50cdc1 Author: Serge Huber <[email protected]> AuthorDate: Fri Jul 17 14:11:43 2026 +0200 feat: add Unomi 3 in numbers blog, CDN/Matomo charts, and Netlify preview (UNOMI-932) Add metrics post with download/visit charts, blog publish dates and archive, plus preview.sh for personal Netlify staging with prerequisite checks. --- .gitignore | 1 + README.md | 107 +++++++ netlify.toml | 13 + preview.sh | 306 +++++++++++++++++++++ .../assets/images/unomi3/numbers-contributors.svg | 76 +++++ .../assets/images/unomi3/numbers-downloads-map.svg | 75 +++++ .../assets/images/unomi3/numbers-downloads.svg | 53 ++++ .../webapp/assets/images/unomi3/numbers-hero.svg | 74 +++++ .../webapp/assets/images/unomi3/numbers-loc.svg | 78 ++++++ .../webapp/assets/images/unomi3/numbers-tests.svg | 83 ++++++ .../images/unomi3/numbers-visits-countries.svg | 60 ++++ .../webapp/assets/images/unomi3/numbers-visits.svg | 31 +++ src/main/webapp/blog/index.html | 214 +++++++++++++- .../webapp/blog/introducing-apache-unomi-3.html | 7 + src/main/webapp/blog/migrating-to-unomi-3.html | 7 + src/main/webapp/blog/unomi-3-ai-agents.html | 7 + src/main/webapp/blog/unomi-3-build-sh.html | 7 + src/main/webapp/blog/unomi-3-in-numbers.html | 139 ++++++++++ src/main/webapp/blog/unomi-3-it-execution.html | 7 + src/main/webapp/blog/unomi-3-manual.html | 7 + src/main/webapp/blog/unomi-3-multitenancy.html | 7 + .../webapp/blog/unomi-3-openapi-enrichment.html | 7 + src/main/webapp/blog/unomi-3-opensearch.html | 7 + .../webapp/blog/unomi-3-platform-foundation.html | 7 + src/main/webapp/blog/unomi-3-request-tracing.html | 7 + src/main/webapp/blog/unomi-3-shell-commands.html | 7 + src/main/webapp/blog/unomi-3-task-scheduler.html | 7 + .../webapp/blog/unomi-3-unit-testing-harness.html | 7 + .../blog/welcome-to-the-apache-unomi-blog.html | 7 + src/main/webapp/sitemap.xml | 5 + 30 files changed, 1419 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ec2086a..60e1e40 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ target/ /.mvn/wrapper/maven-wrapper.jar Gemfile.lock .bundle/ +.netlify/ diff --git a/README.md b/README.md index 3b173b4..7d95ee7 100644 --- a/README.md +++ b/README.md @@ -68,11 +68,118 @@ Or serve locally with live-reload: bundle exec jekyll serve ``` +## Staging preview (Netlify) + +`./preview.sh` builds the site with Docker Jekyll and can publish a **personal/community** staging URL on Netlify (same approach as the UNOMI-932 preview at `https://unomi-v3-site.netlify.app/`). + +This is **not** how `https://unomi.apache.org/` is published. Production still uses Maven scm-publish (see [Publish](#publish) below). Do not commit Netlify tokens or `.netlify/` state into git. + +### Prerequisites + +| Requirement | Needed for | Notes | +|---|---|---| +| [Docker](https://docs.docker.com/get-docker/) + running daemon | All modes | Pulls `bretfisher/jekyll` / `bretfisher/jekyll-serve` | +| [Node.js](https://nodejs.org/) (includes `npx`) **or** [Netlify CLI](https://docs.netlify.com/cli/get-started/) | Netlify deploy (`./preview.sh`, `--prod`) | Script uses `netlify` if installed, otherwise `npx netlify-cli` | +| Netlify account | Netlify deploy | Free personal account is enough | +| Network access | Image pull + Netlify API | First build downloads the Jekyll image | + +Optional: + +- `curl` — used in prerequisite checks for Netlify reachability +- `open` (macOS) — used with `--open` to launch the preview URL +- `lsof` — warns if port 4000 is already taken for `--local` + +Verify tooling before building: + +```shell +./preview.sh --check-only +``` + +### Credentials and configuration + +**Auth (pick one):** + +1. **Interactive login** (simplest): + + ```shell + npx netlify-cli login + # or: netlify login + ``` + + Opens a browser and stores credentials in your user Netlify CLI config (`~/.netlify` / `~/.config/netlify`). + +2. **Personal access token** (CI / non-interactive): + + Create a token in the Netlify UI: **User settings → Applications → Personal access tokens**, then: + + ```shell + export NETLIFY_AUTH_TOKEN=your_token_here + ``` + +**Site linking (pick one):** + +| Variable / file | Purpose | +|---|---| +| (default) | Script creates/links a site named `unomi-v3-site` on first deploy | +| `NETLIFY_SITE_NAME` | Override the Netlify site name (default: `unomi-v3-site`) | +| `NETLIFY_SITE_ID` | Use an existing site id and skip interactive link | +| `.netlify/state.json` | Created after a successful link; reused on later runs (gitignored) | + +Example: + +```shell +export NETLIFY_AUTH_TOKEN=... # optional if already logged in +export NETLIFY_SITE_NAME=unomi-v3-site # optional +export NETLIFY_SITE_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # optional +``` + +Other optional env vars: + +| Variable | Default | Purpose | +|---|---|---| +| `JEKYLL_IMAGE` | `bretfisher/jekyll` | Docker image used to build | +| `JEKYLL_SERVE_IMAGE` | `bretfisher/jekyll-serve` | Docker image used for `--local` | + +`netlify.toml` in this repo sets `publish = "target/site"`. The script builds locally first; Netlify only hosts the static output. + +### Usage + +From the repository root: + +```shell +chmod +x ./preview.sh # once + +./preview.sh --check-only # validate Docker, layout, Netlify tooling/auth hints +./preview.sh # build + Netlify *draft* deploy (unique preview URL) +./preview.sh --prod # build + deploy to the linked site’s production URL +./preview.sh --local # build + serve at http://localhost:4000 (no Netlify) +./preview.sh --build-only # build only → target/site +./preview.sh --open # after a Netlify deploy, open the URL (macOS) +``` + +Flags can be combined where it makes sense, e.g. `./preview.sh --prod --open`. + +Typical first-time flow: + +1. Start Docker Desktop (or your Docker daemon). +2. `./preview.sh --check-only` and fix any `[FAIL]` lines. +3. `./preview.sh` — complete Netlify login / site link if prompted. +4. Share the printed `https://….netlify.app/` URL for review. +5. Later deploys to the same linked site: `./preview.sh --prod`. + +### What the script does + +1. Runs prerequisite checks (`[ok]` / `[warn]` / `[FAIL]`). +2. Builds with Docker Jekyll into `target/site`. +3. For Netlify modes: authenticates, links/creates the site if needed, deploys `target/site`, prints the preview URL. + ## Publish To publish the local website to the production location (https://unomi.apache.org/), you have to use: Do not use the `clean` maven goal to not remove the previous generated site. +Credentials: your **ASF LDAP username and password** (not Netlify). + ```shell mvn install scm-publish:publish-scm -Dusername=YOUR_APACHE_USERNAME -Dpassword=YOUR_APACHE_PASSWORD ``` diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..5e35b2e --- /dev/null +++ b/netlify.toml @@ -0,0 +1,13 @@ +# Personal/community staging only (see preview.sh). +# Production for unomi.apache.org is Maven scm-publish, not Netlify. + +[build] + publish = "target/site" + # Built by preview.sh (Docker Jekyll) before deploy — do not rely on Netlify CI. + command = "echo 'Build with ./preview.sh locally; Netlify only hosts the static output.'" + +[[headers]] + for = "/*" + [headers.values] + X-Frame-Options = "SAMEORIGIN" + X-Content-Type-Options = "nosniff" diff --git a/preview.sh b/preview.sh new file mode 100755 index 0000000..f74eda0 --- /dev/null +++ b/preview.sh @@ -0,0 +1,306 @@ +#!/usr/bin/env bash +# Build the Apache Unomi website and publish a Netlify staging preview. +# +# This mirrors the earlier UNOMI-932 staging flow (unomi-v3-site.netlify.app). +# It is for personal/community review only — production remains: +# mvn install scm-publish:publish-scm ... +# +# Usage: +# ./preview.sh # Docker build + Netlify draft deploy (unique URL) +# ./preview.sh --prod # Docker build + deploy to the linked site URL +# ./preview.sh --local # Docker build + serve at http://localhost:4000 +# ./preview.sh --build-only # Docker build only (output: target/site) +# ./preview.sh --check-only # Prerequisite checks only (no build/deploy) +# ./preview.sh --open # After deploy, open the preview URL (macOS) +# +# Optional environment: +# NETLIFY_AUTH_TOKEN Personal access token (or run: npx netlify-cli login) +# NETLIFY_SITE_ID Existing site id (skips interactive link) +# NETLIFY_SITE_NAME Site name when creating/linking (default: unomi-v3-site) +# JEKYLL_IMAGE Docker image (default: bretfisher/jekyll) +# JEKYLL_SERVE_IMAGE Serve image (default: bretfisher/jekyll-serve) + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$ROOT" + +MODE="draft" # draft | prod | local | build-only | check-only +OPEN_URL=0 +JEKYLL_IMAGE="${JEKYLL_IMAGE:-bretfisher/jekyll}" +JEKYLL_SERVE_IMAGE="${JEKYLL_SERVE_IMAGE:-bretfisher/jekyll-serve}" +NETLIFY_SITE_NAME="${NETLIFY_SITE_NAME:-unomi-v3-site}" +SITE_DIR="target/site" + +usage() { + sed -n '2,23p' "$0" | sed 's/^# \{0,1\}//' + exit "${1:-0}" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --prod) MODE="prod"; shift ;; + --local) MODE="local"; shift ;; + --build-only) MODE="build-only"; shift ;; + --check-only) MODE="check-only"; shift ;; + --open) OPEN_URL=1; shift ;; + -h|--help) usage 0 ;; + *) + echo "Unknown option: $1" >&2 + usage 1 + ;; + esac +done + +need_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Missing required command: $1" >&2 + return 1 + fi + return 0 +} + +have_cmd() { + command -v "$1" >/dev/null 2>&1 +} + +check_pass() { echo " [ok] $1"; } +check_fail() { echo " [FAIL] $1" >&2; CHECKS_FAILED=1; } +check_warn() { echo " [warn] $1" >&2; } + +CHECKS_FAILED=0 + +check_prerequisites() { + echo "==> Prerequisite checks (mode: ${MODE})" + CHECKS_FAILED=0 + + # --- Project layout --- + if [[ -f "${ROOT}/_config.yml" ]]; then + check_pass "_config.yml present" + else + check_fail "_config.yml missing — run from the unomi-site repo root" + fi + + if [[ -d "${ROOT}/src/main/webapp" ]]; then + check_pass "src/main/webapp present" + else + check_fail "src/main/webapp missing — unexpected project layout" + fi + + if [[ -f "${ROOT}/netlify.toml" ]]; then + check_pass "netlify.toml present" + else + check_warn "netlify.toml missing (deploy still works with --dir=${SITE_DIR})" + fi + + # --- Docker (required for all modes that build) --- + if have_cmd docker; then + check_pass "docker found ($(command -v docker))" + if docker info >/dev/null 2>&1; then + check_pass "Docker daemon is running" + else + check_fail "Docker daemon is not running — start Docker Desktop / dockerd" + fi + else + check_fail "docker not found — install Docker (required to build with ${JEKYLL_IMAGE})" + fi + + # Writable output dir + if mkdir -p "${ROOT}/target" 2>/dev/null && [[ -w "${ROOT}/target" ]]; then + check_pass "target/ is writable" + else + check_fail "cannot create/write ${ROOT}/target" + fi + + # --- Mode-specific --- + case "${MODE}" in + draft|prod|check-only) + if have_cmd netlify; then + check_pass "netlify CLI found ($(netlify --version 2>/dev/null | head -1 || echo ok))" + elif have_cmd npx; then + check_pass "npx found (will use: npx netlify-cli)" + if have_cmd node; then + check_pass "node found ($(node --version 2>/dev/null))" + else + check_warn "node not found — npx may still work depending on your Node install" + fi + else + check_fail "neither 'netlify' nor 'npx' found — install Netlify CLI or Node.js (npx)" + fi + + if [[ -n "${NETLIFY_AUTH_TOKEN:-}" ]]; then + check_pass "NETLIFY_AUTH_TOKEN is set" + elif [[ -f "${HOME}/.netlify/config.json" ]] || [[ -d "${HOME}/.config/netlify" ]]; then + check_pass "Netlify CLI config present (will verify auth at deploy time)" + else + check_warn "no NETLIFY_AUTH_TOKEN / Netlify config yet — script will run 'netlify login' if needed" + fi + + if [[ -n "${NETLIFY_SITE_ID:-}" ]]; then + check_pass "NETLIFY_SITE_ID is set" + elif [[ -f "${ROOT}/.netlify/state.json" ]]; then + check_pass "site already linked (.netlify/state.json)" + else + check_warn "site not linked yet — will create/link '${NETLIFY_SITE_NAME}'" + fi + + if have_cmd curl; then + if curl -fsS --connect-timeout 5 -o /dev/null https://api.netlify.com/api/v1/ 2>/dev/null \ + || curl -fsS --connect-timeout 5 -o /dev/null https://www.netlify.com/ 2>/dev/null; then + check_pass "network reachability to Netlify" + else + check_warn "could not reach Netlify APIs — deploy may fail offline" + fi + fi + ;; + local) + if have_cmd lsof && lsof -iTCP:4000 -sTCP:LISTEN >/dev/null 2>&1; then + check_warn "port 4000 is already in use — jekyll-serve may fail to bind" + else + check_pass "port 4000 appears free" + fi + ;; + build-only) + ;; + esac + + if [[ "${OPEN_URL}" -eq 1 ]]; then + if have_cmd open; then + check_pass "open found (will open preview URL)" + else + check_warn "'open' not found — --open will be ignored" + fi + fi + + if [[ "${CHECKS_FAILED}" -ne 0 ]]; then + echo "" >&2 + echo "Prerequisite checks failed. Fix the items marked [FAIL] and re-run." >&2 + exit 1 + fi + echo "==> Prerequisites OK" + echo "" +} + +build_site() { + echo "==> Building site with Docker (${JEKYLL_IMAGE})" + mkdir -p target + docker run --rm \ + --volume="${ROOT}:/site" \ + -w /site \ + "${JEKYLL_IMAGE}" \ + build + + if [[ ! -d "${SITE_DIR}" ]] || [[ -z "$(ls -A "${SITE_DIR}" 2>/dev/null || true)" ]]; then + echo "Build failed: ${SITE_DIR} is missing or empty" >&2 + exit 1 + fi + echo "==> Build OK → ${SITE_DIR}" +} + +netlify_cli() { + if have_cmd netlify; then + netlify "$@" + else + npx --yes netlify-cli "$@" + fi +} + +ensure_netlify_auth() { + if [[ -n "${NETLIFY_AUTH_TOKEN:-}" ]]; then + return 0 + fi + if netlify_cli status >/dev/null 2>&1; then + return 0 + fi + echo "==> Netlify auth required (browser login)" + netlify_cli login +} + +link_or_create_site() { + if [[ -n "${NETLIFY_SITE_ID:-}" ]]; then + echo "==> Using NETLIFY_SITE_ID=${NETLIFY_SITE_ID}" + return 0 + fi + if [[ -f .netlify/state.json ]]; then + echo "==> Using existing Netlify link (.netlify/state.json)" + return 0 + fi + + echo "==> Linking/creating Netlify site '${NETLIFY_SITE_NAME}'" + if netlify_cli sites:list --json 2>/dev/null | grep -q "\"name\":\"${NETLIFY_SITE_NAME}\""; then + netlify_cli link --name "${NETLIFY_SITE_NAME}" + else + netlify_cli sites:create --name "${NETLIFY_SITE_NAME}" --disable-linking || true + netlify_cli link --name "${NETLIFY_SITE_NAME}" || netlify_cli init + fi +} + +deploy_netlify() { + ensure_netlify_auth + link_or_create_site + + local args=(deploy --dir="${SITE_DIR}" --message "unomi-site preview $(date -u +%Y-%m-%dT%H:%MZ)") + if [[ "${MODE}" == "prod" ]]; then + args+=(--prod) + echo "==> Deploying to Netlify production URL for linked site" + else + echo "==> Deploying Netlify draft preview (unique URL)" + fi + + local log + log="$(mktemp)" + if ! netlify_cli "${args[@]}" 2>&1 | tee "${log}"; then + echo "Netlify deploy failed" >&2 + rm -f "${log}" + exit 1 + fi + + local url + url="$(grep -Eo 'https://[^ ]+\.netlify\.app/?[^ ]*' "${log}" | tail -1 || true)" + rm -f "${log}" + + if [[ -n "${url}" ]]; then + echo "" + echo "==> Preview URL: ${url}" + if [[ "${OPEN_URL}" -eq 1 ]] && command -v open >/dev/null 2>&1; then + open "${url}" + fi + else + echo "==> Deploy finished (check netlify output above for the URL)" + fi +} + +serve_local() { + echo "==> Serving with Docker (${JEKYLL_SERVE_IMAGE}) at http://localhost:4000/" + echo " Ctrl+C to stop" + docker run --rm \ + --volume="${ROOT}:/site" \ + -p 4000:4000 \ + -w /site \ + "${JEKYLL_SERVE_IMAGE}" +} + +check_prerequisites + +if [[ "${MODE}" == "check-only" ]]; then + echo "==> Done (check only)" + exit 0 +fi + +build_site + +case "${MODE}" in + build-only) + echo "==> Done (build only)" + ;; + local) + serve_local + ;; + draft|prod) + deploy_netlify + ;; + *) + echo "Internal error: unknown mode ${MODE}" >&2 + exit 1 + ;; +esac diff --git a/src/main/webapp/assets/images/unomi3/numbers-contributors.svg b/src/main/webapp/assets/images/unomi3/numbers-contributors.svg new file mode 100644 index 0000000..d65d94d --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/numbers-contributors.svg @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 840 400" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="Line chart of cumulative unique contributors from Unomi 1 through 3.1"> + <defs> + <style> + svg { + --unomi-primary: #6d5ce7; + --unomi-primary-dark: #5a48c9; + --unomi-primary-light: #ede9fe; + --unomi-primary-subtle: #f5f3ff; + --unomi-accent: #00cec9; + --unomi-accent-dark: #0d9488; + --unomi-hero-accent: #a78bfa; + --unomi-text: #1e293b; + --unomi-text-muted: #64748b; + --unomi-white: #ffffff; + --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; + --unomi-success: #059669; + --unomi-success-bg: #ecfdf5; + --unomi-amber: #d97706; + --unomi-amber-bg: #fef3c7; + --unomi-info: #2563eb; + --unomi-info-bg: #dbeafe; + font-family: Inter, system-ui, sans-serif; + } + @keyframes grow-bar { from { transform: scaleY(0); } to { transform: scaleY(1); } } + @keyframes fade-up { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } + @keyframes dash-flow { to { stroke-dashoffset: -20; } } + @keyframes pulse-dot { 0%,100%{opacity:.7;r:5} 50%{opacity:1;r:7} } + .bar { transform-box: fill-box; transform-origin: bottom; animation: grow-bar 0.9s ease-out both; } + .fade { animation: fade-up 0.7s ease-out both; } + .flow { stroke-dasharray: 5 4; animation: dash-flow 1.8s linear infinite; } + .dot { animation: pulse-dot 2.4s ease-in-out infinite; } + @media (prefers-reduced-motion: reduce) { + .bar,.fade,.flow,.dot { animation: none !important; } + } +</style> + <linearGradient id="areaFill" x1="0" y1="100" x2="0" y2="300" gradientUnits="userSpaceOnUse"> + <stop stop-color="#6d5ce7" stop-opacity="0.35"/> + <stop offset="1" stop-color="#6d5ce7" stop-opacity="0.02"/> + </linearGradient> + </defs> + <rect width="840" height="400" rx="20" fill="var(--unomi-bg-soft)"/> + <text class="fade" x="40" y="36" fill="var(--unomi-text)" font-size="18" font-weight="700">Cumulative contributors</text> + <text class="fade" style="animation-delay:.08s" x="40" y="58" fill="var(--unomi-text-muted)" font-size="12">Unique git authors through each release tag (name-based; aliases not merged)</text> + <line x1="80" y1="300" x2="760" y2="300" stroke="var(--unomi-border)" stroke-width="1.5"/> + <polygon class="fade" style="animation-delay:.15s" points="120,300 120,220.0 300,153.3 480,140.0 660,120.0 660,300" fill="url(#areaFill)"/> + <polyline class="fade flow" style="animation-delay:.2s" points="120,220.0 300,153.3 480,140.0 660,120.0" fill="none" stroke="var(--unomi-primary)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/> + + <g class="fade" style="animation-delay:0.20s"> + <circle class="dot" style="animation-delay:0.0s" cx="120" cy="220.0" r="6" fill="var(--unomi-primary)"/> + <text x="120" y="204.0" text-anchor="middle" fill="var(--unomi-primary-dark)" font-size="16" font-weight="800">62</text> + <text x="120" y="328" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="700">Unomi 1</text> + <text x="120" y="346" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">2,255 commits</text> + </g> + <g class="fade" style="animation-delay:0.32s"> + <circle class="dot" style="animation-delay:0.3s" cx="300" cy="153.3" r="6" fill="var(--unomi-primary)"/> + <text x="300" y="137.3" text-anchor="middle" fill="var(--unomi-primary-dark)" font-size="16" font-weight="800">72</text> + <text x="300" y="328" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="700">Unomi 2</text> + <text x="300" y="346" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">2,657 commits</text> + </g> + <g class="fade" style="animation-delay:0.44s"> + <circle class="dot" style="animation-delay:0.6s" cx="480" cy="140.0" r="6" fill="var(--unomi-primary)"/> + <text x="480" y="124.0" text-anchor="middle" fill="var(--unomi-primary-dark)" font-size="16" font-weight="800">74</text> + <text x="480" y="328" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="700">Unomi 3.0</text> + <text x="480" y="346" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">2,682 commits</text> + </g> + <g class="fade" style="animation-delay:0.56s"> + <circle class="dot" style="animation-delay:0.9s" cx="660" cy="120.0" r="6" fill="var(--unomi-primary)"/> + <text x="660" y="104.0" text-anchor="middle" fill="var(--unomi-primary-dark)" font-size="16" font-weight="800">77</text> + <text x="660" y="328" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="700">Unomi 3.1</text> + <text x="660" y="346" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">2,769 commits</text> + </g> + <text class="fade" style="animation-delay:.8s" x="40" y="375" fill="var(--unomi-text-muted)" font-size="12">Steady community growth across a decade - 77 authors and 2,769 commits on master ancestry.</text> +</svg> diff --git a/src/main/webapp/assets/images/unomi3/numbers-downloads-map.svg b/src/main/webapp/assets/images/unomi3/numbers-downloads-map.svg new file mode 100644 index 0000000..497224b --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/numbers-downloads-map.svg @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 960 560" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="World map of human Unomi software package downloads by country, bots excluded"> + <defs> + <style> + svg { + --unomi-primary: #6d5ce7; --unomi-text: #1e293b; --unomi-text-muted: #64748b; + --unomi-bg-soft: #f8fafc; font-family: Inter, system-ui, sans-serif; + } + path:hover { stroke: #6d5ce7; stroke-width: 1.2; } + @keyframes fade-up { from { opacity: 0; } to { opacity: 1; } } + .fade { animation: fade-up 0.7s ease-out both; } + @media (prefers-reduced-motion: reduce) { .fade { animation: none !important; } } +</style> + <linearGradient id="dlScale" x1="0" y1="0" x2="1" y2="0"><stop offset="0.0" stop-color="#e2e8f0"/><stop offset="0.25" stop-color="#8878ee"/><stop offset="0.5" stop-color="#6453da"/><stop offset="0.75" stop-color="#5443bf"/><stop offset="1.0" stop-color="#3b2d8f"/></linearGradient> + </defs> + <rect width="960" height="560" rx="20" fill="var(--unomi-bg-soft)"/> + <text class="fade" x="28" y="34" fill="var(--unomi-text)" font-size="18" font-weight="700">Downloads by country (bots excluded)</text> + <text class="fade" style="animation-delay:.08s" x="28" y="54" fill="var(--unomi-text-muted)" font-size="12">Human browser share of software packages - ASF CDN geo - Jul 2023 to Jul 2026</text> + <g class="fade" style="animation-delay:.15s"><path d="M740.0,305.7L740.0,306.8L738.7,307.3L737.5,307.8L737.2,307.0L738.2,306.5L738.8,306.4L740.0,305.7Z M736.3,308.9L736.7,308.5L737.4,309.2L737.1,310.3L735.9,310.6L734.8,310.4L734.6,309.4L735.3,308.6L736.3,308.9Z M20.4,305.6L20.2,306.7L20.0,306.8L20.0,305.7L20.4,305.6Z" fill="#e2e8f0" stroke="#ffffff" stroke-width="0.6" data-cc="FJ"><title>Fiji</title></path><path d="M447.8,272.1L448.1,272.4L455.4,276.9L455.5,278.2L458.4,280.4L457.5,283. [...] + <text x="750" y="62" fill="var(--unomi-text)" font-size="13" font-weight="700">Top countries</text> + + <text x="750" y="108" fill="var(--unomi-text-muted)" font-size="11">01</text> + <text x="772" y="108" fill="var(--unomi-text)" font-size="12" font-weight="600">Brazil</text> + <text x="944" y="108" text-anchor="end" fill="var(--unomi-text-muted)" font-size="11">3,627</text> + <rect x="772" y="113" width="120" height="4" rx="2" fill="#3b2d8f"/> + <text x="750" y="136" fill="var(--unomi-text-muted)" font-size="11">02</text> + <text x="772" y="136" fill="var(--unomi-text)" font-size="12" font-weight="600">China</text> + <text x="944" y="136" text-anchor="end" fill="var(--unomi-text-muted)" font-size="11">2,816</text> + <rect x="772" y="141" width="116" height="4" rx="2" fill="#3e2f94"/> + <text x="750" y="164" fill="var(--unomi-text-muted)" font-size="11">03</text> + <text x="772" y="164" fill="var(--unomi-text)" font-size="12" font-weight="600">Russia</text> + <text x="944" y="164" text-anchor="end" fill="var(--unomi-text-muted)" font-size="11">2,583</text> + <rect x="772" y="169" width="115" height="4" rx="2" fill="#3f3097"/> + <text x="750" y="192" fill="var(--unomi-text-muted)" font-size="11">04</text> + <text x="772" y="192" fill="var(--unomi-text)" font-size="12" font-weight="600">United States</text> + <text x="944" y="192" text-anchor="end" fill="var(--unomi-text-muted)" font-size="11">1,771</text> + <rect x="772" y="197" width="109" height="4" rx="2" fill="#44349f"/> + <text x="750" y="220" fill="var(--unomi-text-muted)" font-size="11">05</text> + <text x="772" y="220" fill="var(--unomi-text)" font-size="12" font-weight="600">Germany</text> + <text x="944" y="220" text-anchor="end" fill="var(--unomi-text-muted)" font-size="11">1,258</text> + <rect x="772" y="225" width="104" height="4" rx="2" fill="#4838a7"/> + <text x="750" y="248" fill="var(--unomi-text-muted)" font-size="11">06</text> + <text x="772" y="248" fill="var(--unomi-text)" font-size="12" font-weight="600">Netherlands</text> + <text x="944" y="248" text-anchor="end" fill="var(--unomi-text-muted)" font-size="11">912</text> + <rect x="772" y="253" width="99" height="4" rx="2" fill="#4c3caf"/> + <text x="750" y="276" fill="var(--unomi-text-muted)" font-size="11">07</text> + <text x="772" y="276" fill="var(--unomi-text)" font-size="12" font-weight="600">India</text> + <text x="944" y="276" text-anchor="end" fill="var(--unomi-text-muted)" font-size="11">280</text> + <rect x="772" y="281" width="82" height="4" rx="2" fill="#5a48ca"/> + <text x="750" y="304" fill="var(--unomi-text-muted)" font-size="11">08</text> + <text x="772" y="304" fill="var(--unomi-text)" font-size="12" font-weight="600">Austria</text> + <text x="944" y="304" text-anchor="end" fill="var(--unomi-text-muted)" font-size="11">246</text> + <rect x="772" y="309" width="80" height="4" rx="2" fill="#5b49cb"/> + <text x="750" y="332" fill="var(--unomi-text-muted)" font-size="11">09</text> + <text x="772" y="332" fill="var(--unomi-text)" font-size="12" font-weight="600">Singapore</text> + <text x="944" y="332" text-anchor="end" fill="var(--unomi-text-muted)" font-size="11">218</text> + <rect x="772" y="337" width="78" height="4" rx="2" fill="#5c4acc"/> + <text x="750" y="360" fill="var(--unomi-text-muted)" font-size="11">10</text> + <text x="772" y="360" fill="var(--unomi-text)" font-size="12" font-weight="600">France</text> + <text x="944" y="360" text-anchor="end" fill="var(--unomi-text-muted)" font-size="11">188</text> + <rect x="772" y="365" width="76" height="4" rx="2" fill="#5d4bce"/> + <text x="750" y="388" fill="var(--unomi-text-muted)" font-size="11">11</text> + <text x="772" y="388" fill="var(--unomi-text)" font-size="12" font-weight="600">Italy</text> + <text x="944" y="388" text-anchor="end" fill="var(--unomi-text-muted)" font-size="11">122</text> + <rect x="772" y="393" width="70" height="4" rx="2" fill="#604ed2"/> + <text x="750" y="416" fill="var(--unomi-text-muted)" font-size="11">12</text> + <text x="772" y="416" fill="var(--unomi-text)" font-size="12" font-weight="600">Spain</text> + <text x="944" y="416" text-anchor="end" fill="var(--unomi-text-muted)" font-size="11">91</text> + <rect x="772" y="421" width="66" height="4" rx="2" fill="#6250d5"/> + <rect x="30" y="505" width="220" height="12" rx="3" fill="url(#dlScale)"/> + <text x="30" y="499" fill="var(--unomi-text-muted)" font-size="10">Fewer</text> + <text x="250" y="499" text-anchor="end" fill="var(--unomi-text-muted)" font-size="10">More (log scale)</text> + <text x="30" y="533" fill="var(--unomi-text-muted)" font-size="10">Countries scaled by each file's human-browser UA share · ~15,345 attributed</text> +</svg> diff --git a/src/main/webapp/assets/images/unomi3/numbers-downloads.svg b/src/main/webapp/assets/images/unomi3/numbers-downloads.svg new file mode 100644 index 0000000..457b388 --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/numbers-downloads.svg @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 840 460" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="Cumulative Unomi software package downloads: human browsers, all packages including bots, and known bots"> + <defs> + <style> + svg { + --unomi-primary: #6d5ce7; + --unomi-primary-dark: #5a48c9; + --unomi-accent: #00cec9; + --unomi-accent-dark: #0d9488; + --unomi-bot: #8b83b8; + --unomi-text: #1e293b; + --unomi-text-muted: #64748b; + --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; + --unomi-amber: #d97706; + font-family: Inter, system-ui, sans-serif; + } + @keyframes fade-up { from { opacity: 0; } to { opacity: 1; } } + .fade { animation: fade-up 0.8s ease-out both; } + @media (prefers-reduced-motion: reduce) { .fade { animation: none !important; } } +</style> + <linearGradient id="softFill" x1="0" y1="80" x2="0" y2="360" gradientUnits="userSpaceOnUse"> + <stop stop-color="#6d5ce7" stop-opacity="0.28"/> + <stop offset="1" stop-color="#6d5ce7" stop-opacity="0.02"/> + </linearGradient> + </defs> + <rect width="840" height="460" rx="20" fill="var(--unomi-bg-soft)"/> + <text class="fade" x="40" y="36" fill="var(--unomi-text)" font-size="18" font-weight="700">Cumulative software-package downloads</text> + <text class="fade" style="animation-delay:.08s" x="40" y="58" fill="var(--unomi-text-muted)" font-size="12">Purple = human only · Teal = all package hits · Dotted = known bots - ASF CDN Jul 2023 to Jul 2026</text> + <line x1="70" y1="360.0" x2="800" y2="360.0" stroke="var(--unomi-border)" stroke-width="1"/><text x="62" y="364.0" text-anchor="end" fill="var(--unomi-text-muted)" font-size="10">0</text><line x1="70" y1="290.0" x2="800" y2="290.0" stroke="var(--unomi-border)" stroke-width="1"/><text x="62" y="294.0" text-anchor="end" fill="var(--unomi-text-muted)" font-size="10">12k</text><line x1="70" y1="220.0" x2="800" y2="220.0" stroke="var(--unomi-border)" stroke-width="1"/><text x="62" y="224.0" [...] + + <line x1="597.2" y1="80" x2="597.2" y2="360" stroke="var(--unomi-accent-dark)" stroke-width="1.5" stroke-dasharray="4 3"/> + <text x="591.2" y="94" text-anchor="end" fill="var(--unomi-accent-dark)" font-size="11" font-weight="700">2.7.0</text> + <line x1="637.8" y1="80" x2="637.8" y2="360" stroke="var(--unomi-amber)" stroke-width="1.5" stroke-dasharray="4 3"/> + <text x="643.8" y="94" fill="var(--unomi-amber)" font-size="11" font-weight="700">3.0</text> + <line x1="698.6" y1="80" x2="698.6" y2="360" stroke="var(--unomi-primary-dark)" stroke-width="1.5" stroke-dasharray="4 3"/> + <text x="704.6" y="94" fill="var(--unomi-primary-dark)" font-size="11" font-weight="700">Site refresh</text> + <polygon class="fade" style="animation-delay:.15s" points="70.0,360.0 70.0,360.0 90.3,359.2 110.6,358.4 130.8,357.5 151.1,355.9 171.4,354.6 191.7,353.8 211.9,352.6 232.2,351.8 252.5,350.7 272.8,349.4 293.1,348.3 313.3,347.2 333.6,346.0 353.9,344.7 374.2,343.7 394.4,342.5 414.7,340.7 435.0,338.5 455.3,337.0 475.6,335.6 495.8,333.2 516.1,332.0 536.4,316.7 556.7,306.3 576.9,303.3 597.2,287.0 617.5,282.5 637.8,278.9 658.1,276.4 678.3,273.7 698.6,269.7 718.9,267.2 739.2,264.2 759.4,260.2 77 [...] + <polyline class="fade" style="animation-delay:.2s" points="70.0,358.0 90.3,349.0 110.6,339.3 130.8,330.0 151.1,320.6 171.4,309.2 191.7,299.0 211.9,289.6 232.2,284.0 252.5,278.5 272.8,270.1 293.1,262.9 313.3,258.3 333.6,251.1 353.9,240.7 374.2,228.2 394.4,223.0 414.7,218.5 435.0,215.1 455.3,213.5 475.6,212.0 495.8,209.3 516.1,208.1 536.4,191.5 556.7,180.4 576.9,176.5 597.2,155.7 617.5,148.3 637.8,141.4 658.1,136.9 678.3,129.8 698.6,124.2 718.9,116.9 739.2,112.7 759.4,108.5 779.7,102.5 8 [...] + <polyline class="fade" style="animation-delay:.22s" points="70.0,358.0 90.3,349.9 110.6,341.0 130.8,332.3 151.1,324.0 171.4,314.0 191.7,304.7 211.9,296.6 232.2,291.6 252.5,287.2 272.8,280.2 293.1,274.3 313.3,270.9 333.6,265.2 353.9,256.0 374.2,244.8 394.4,241.4 414.7,238.6 435.0,236.5 455.3,236.3 475.6,236.2 495.8,236.0 516.1,235.9 536.4,235.0 556.7,234.9 576.9,233.7 597.2,229.8 617.5,227.0 637.8,223.6 658.1,222.1 678.3,219.1 698.6,217.5 718.9,212.6 739.2,210.5 759.4,210.1 779.7,209.0 [...] + <polyline class="fade" style="animation-delay:.25s" points="70.0,360.0 90.3,359.2 110.6,358.4 130.8,357.5 151.1,355.9 171.4,354.6 191.7,353.8 211.9,352.6 232.2,351.8 252.5,350.7 272.8,349.4 293.1,348.3 313.3,347.2 333.6,346.0 353.9,344.7 374.2,343.7 394.4,342.5 414.7,340.7 435.0,338.5 455.3,337.0 475.6,335.6 495.8,333.2 516.1,332.0 536.4,316.7 556.7,306.3 576.9,303.3 597.2,287.0 617.5,282.5 637.8,278.9 658.1,276.4 678.3,273.7 698.6,269.7 718.9,267.2 739.2,264.2 759.4,260.2 779.7,255.1 [...] + <text x="70.0" y="382" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">Jul 23</text><line x1="70.0" y1="360" x2="70.0" y2="366" stroke="var(--unomi-border)" stroke-width="1"/><text x="191.7" y="382" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">Jan 24</text><line x1="191.7" y1="360" x2="191.7" y2="366" stroke="var(--unomi-border)" stroke-width="1"/><text x="313.3" y="382" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">Jul 24</ [...] + <circle cx="800.0" cy="253.6" r="5" fill="var(--unomi-primary)"/> + <g class="fade" style="animation-delay:.5s"> + <rect x="40" y="420" width="14" height="14" rx="3" fill="var(--unomi-primary)"/> + <text x="62" y="431" fill="var(--unomi-text)" font-size="12">Human browsers - 17,785</text> + <rect x="280" y="420" width="14" height="14" rx="3" fill="var(--unomi-accent)"/> + <text x="302" y="431" fill="var(--unomi-text)" font-size="12">All packages (incl. bots) - 43,320</text> + <line x1="500" y1="427" x2="526" y2="427" stroke="var(--unomi-bot)" stroke-width="2" stroke-dasharray="2 5" stroke-linecap="round"/> + <text x="534" y="431" fill="var(--unomi-text)" font-size="12">Known bots / scripts - 25,295</text> + </g> +</svg> diff --git a/src/main/webapp/assets/images/unomi3/numbers-hero.svg b/src/main/webapp/assets/images/unomi3/numbers-hero.svg new file mode 100644 index 0000000..b7ef64b --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/numbers-hero.svg @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 840 400" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="Unomi 3 in numbers: lines of code, tests, and contributors at a glance"> + <defs><style> + svg { + --unomi-primary: #6d5ce7; + --unomi-primary-dark: #5a48c9; + --unomi-primary-light: #ede9fe; + --unomi-primary-subtle: #f5f3ff; + --unomi-accent: #00cec9; + --unomi-accent-dark: #0d9488; + --unomi-hero-accent: #a78bfa; + --unomi-text: #1e293b; + --unomi-text-muted: #64748b; + --unomi-white: #ffffff; + --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; + --unomi-success: #059669; + --unomi-success-bg: #ecfdf5; + --unomi-amber: #d97706; + --unomi-amber-bg: #fef3c7; + --unomi-info: #2563eb; + --unomi-info-bg: #dbeafe; + font-family: Inter, system-ui, sans-serif; + } + @keyframes grow-bar { from { transform: scaleY(0); } to { transform: scaleY(1); } } + @keyframes fade-up { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } + @keyframes dash-flow { to { stroke-dashoffset: -20; } } + @keyframes pulse-dot { 0%,100%{opacity:.7;r:5} 50%{opacity:1;r:7} } + .bar { transform-box: fill-box; transform-origin: bottom; animation: grow-bar 0.9s ease-out both; } + .fade { animation: fade-up 0.7s ease-out both; } + .flow { stroke-dasharray: 5 4; animation: dash-flow 1.8s linear infinite; } + .dot { animation: pulse-dot 2.4s ease-in-out infinite; } + @media (prefers-reduced-motion: reduce) { + .bar,.fade,.flow,.dot { animation: none !important; } + } +</style> + <linearGradient id="heroGrad" x1="0" y1="0" x2="840" y2="400" gradientUnits="userSpaceOnUse"> + <stop stop-color="#f5f3ff"/><stop offset="1" stop-color="#ecfeff"/> + </linearGradient> + </defs> + <rect width="840" height="400" rx="20" fill="url(#heroGrad)"/> + <text class="fade" x="40" y="42" fill="var(--unomi-text)" font-size="20" font-weight="700">Unomi 3 in numbers</text> + <text class="fade" style="animation-delay:.1s" x="40" y="66" fill="var(--unomi-text-muted)" font-size="13">From 1.x to 3.1 - codebase, tests, and community growth</text> + + <g class="fade" style="animation-delay:.15s"> + <rect x="40" y="100" width="175" height="150" rx="16" fill="var(--unomi-white)" stroke="var(--unomi-primary)" stroke-width="2"/> + <text x="127" y="145" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="12" font-weight="600">JAVA LOC</text> + <text x="127" y="190" text-anchor="middle" fill="var(--unomi-primary-dark)" font-size="36" font-weight="800">181k</text> + <text x="127" y="220" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">3.1 WIP (was 54k in 1.x)</text> + </g> + <g class="fade" style="animation-delay:.25s"> + <rect x="235" y="100" width="175" height="150" rx="16" fill="var(--unomi-white)" stroke="var(--unomi-accent)" stroke-width="2"/> + <text x="322" y="145" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="12" font-weight="600">UNIT @Test</text> + <text x="322" y="190" text-anchor="middle" fill="var(--unomi-accent-dark)" font-size="36" font-weight="800">1,148</text> + <text x="322" y="220" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">up from 34 in Unomi 1.x</text> + </g> + <g class="fade" style="animation-delay:.35s"> + <rect x="430" y="100" width="175" height="150" rx="16" fill="var(--unomi-white)" stroke="var(--unomi-info)" stroke-width="2"/> + <text x="517" y="145" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="12" font-weight="600">IT @Test</text> + <text x="517" y="190" text-anchor="middle" fill="var(--unomi-info)" font-size="36" font-weight="800">388</text> + <text x="517" y="220" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">up from 128 in Unomi 1.x</text> + </g> + <g class="fade" style="animation-delay:.45s"> + <rect x="625" y="100" width="175" height="150" rx="16" fill="var(--unomi-white)" stroke="var(--unomi-success)" stroke-width="2"/> + <text x="712" y="145" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="12" font-weight="600">CONTRIBUTORS</text> + <text x="712" y="190" text-anchor="middle" fill="var(--unomi-success)" font-size="36" font-weight="800">77</text> + <text x="712" y="220" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">cumulative unique authors</text> + </g> + + <rect x="40" y="280" width="760" height="90" rx="14" fill="var(--unomi-white)" stroke="var(--unomi-border)" stroke-width="2"/> + <text x="60" y="318" fill="var(--unomi-primary-dark)" font-size="14" font-weight="700">The story in one line</text> + <text x="60" y="344" fill="var(--unomi-text)" font-size="13">Production code nearly tripled since 1.x - test code grew ~7x, with the steepest climb after Unomi 3.0 into 3.1.</text> +</svg> diff --git a/src/main/webapp/assets/images/unomi3/numbers-loc.svg b/src/main/webapp/assets/images/unomi3/numbers-loc.svg new file mode 100644 index 0000000..7eebd24 --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/numbers-loc.svg @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 840 420" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="Stacked bar chart of Java lines of code from Unomi 1 through Unomi 3.1"> + <defs><style> + svg { + --unomi-primary: #6d5ce7; + --unomi-primary-dark: #5a48c9; + --unomi-primary-light: #ede9fe; + --unomi-primary-subtle: #f5f3ff; + --unomi-accent: #00cec9; + --unomi-accent-dark: #0d9488; + --unomi-hero-accent: #a78bfa; + --unomi-text: #1e293b; + --unomi-text-muted: #64748b; + --unomi-white: #ffffff; + --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; + --unomi-success: #059669; + --unomi-success-bg: #ecfdf5; + --unomi-amber: #d97706; + --unomi-amber-bg: #fef3c7; + --unomi-info: #2563eb; + --unomi-info-bg: #dbeafe; + font-family: Inter, system-ui, sans-serif; + } + @keyframes grow-bar { from { transform: scaleY(0); } to { transform: scaleY(1); } } + @keyframes fade-up { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } + @keyframes dash-flow { to { stroke-dashoffset: -20; } } + @keyframes pulse-dot { 0%,100%{opacity:.7;r:5} 50%{opacity:1;r:7} } + .bar { transform-box: fill-box; transform-origin: bottom; animation: grow-bar 0.9s ease-out both; } + .fade { animation: fade-up 0.7s ease-out both; } + .flow { stroke-dasharray: 5 4; animation: dash-flow 1.8s linear infinite; } + .dot { animation: pulse-dot 2.4s ease-in-out infinite; } + @media (prefers-reduced-motion: reduce) { + .bar,.fade,.flow,.dot { animation: none !important; } + } +</style></defs> + <rect width="840" height="420" rx="20" fill="var(--unomi-bg-soft)"/> + <text class="fade" x="40" y="36" fill="var(--unomi-text)" font-size="18" font-weight="700">Java lines of code</text> + <text class="fade" style="animation-delay:.08s" x="40" y="58" fill="var(--unomi-text-muted)" font-size="12">Physical LOC at release tags (production stacked on tests)</text> + <line x1="70" y1="330.0" x2="780" y2="330.0" stroke="var(--unomi-border)" stroke-width="1"/><text x="64" y="334.0" text-anchor="end" fill="var(--unomi-text-muted)" font-size="10">0</text><line x1="70" y1="270.0" x2="780" y2="270.0" stroke="var(--unomi-border)" stroke-width="1"/><text x="64" y="274.0" text-anchor="end" fill="var(--unomi-text-muted)" font-size="10">46k</text><line x1="70" y1="210.0" x2="780" y2="210.0" stroke="var(--unomi-border)" stroke-width="1"/><text x="64" y="214.0" [...] + + <g class="fade" style="animation-delay:0.10s"> + <rect class="bar" style="animation-delay:0.10s" x="110" y="318.7" width="70" height="11.3" rx="4" fill="var(--unomi-accent)"/> + <rect class="bar" style="animation-delay:0.15s" x="110" y="260.1" width="70" height="58.6" rx="4" fill="var(--unomi-primary)"/> + <text x="145.0" y="250.1" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="700">54k</text> + <text x="145.0" y="354" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="700">Unomi 1</text> + <text x="145.0" y="372" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">1.9.1</text> + </g> + <g class="fade" style="animation-delay:0.22s"> + <rect class="bar" style="animation-delay:0.22s" x="290" y="314.8" width="70" height="15.2" rx="4" fill="var(--unomi-accent)"/> + <rect class="bar" style="animation-delay:0.27s" x="290" y="223.9" width="70" height="90.9" rx="4" fill="var(--unomi-primary)"/> + <text x="325.0" y="213.9" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="700">82k</text> + <text x="325.0" y="354" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="700">Unomi 2</text> + <text x="325.0" y="372" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">2.7.0</text> + </g> + <g class="fade" style="animation-delay:0.34s"> + <rect class="bar" style="animation-delay:0.34s" x="470" y="314.4" width="70" height="15.6" rx="4" fill="var(--unomi-accent)"/> + <rect class="bar" style="animation-delay:0.39s" x="470" y="221.7" width="70" height="92.6" rx="4" fill="var(--unomi-primary)"/> + <text x="505.0" y="211.7" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="700">83k</text> + <text x="505.0" y="354" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="700">Unomi 3.0</text> + <text x="505.0" y="372" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">3.0.1</text> + </g> + <g class="fade" style="animation-delay:0.46s"> + <rect class="bar" style="animation-delay:0.46s" x="650" y="248.6" width="70" height="81.4" rx="4" fill="var(--unomi-accent)"/> + <rect class="bar" style="animation-delay:0.51s" x="650" y="95.6" width="70" height="153.0" rx="4" fill="var(--unomi-primary)"/> + <text x="685.0" y="85.6" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="700">181k</text> + <text x="685.0" y="354" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="700">Unomi 3.1</text> + <text x="685.0" y="372" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">HEAD</text> + </g> + <g class="fade" style="animation-delay:.6s"> + <rect x="40" y="385" width="14" height="14" rx="3" fill="var(--unomi-primary)"/> + <text x="62" y="396" fill="var(--unomi-text)" font-size="12">Production (main)</text> + <rect x="200" y="385" width="14" height="14" rx="3" fill="var(--unomi-accent)"/> + <text x="222" y="396" fill="var(--unomi-text)" font-size="12">Tests</text> + <text x="780" y="396" text-anchor="end" fill="var(--unomi-text-muted)" font-size="11">~3.4x total Java since Unomi 1</text> + </g> +</svg> diff --git a/src/main/webapp/assets/images/unomi3/numbers-tests.svg b/src/main/webapp/assets/images/unomi3/numbers-tests.svg new file mode 100644 index 0000000..2b39861 --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/numbers-tests.svg @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 840 420" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="Grouped bar chart of unit and integration @Test methods from Unomi 1 through 3.1"> + <defs><style> + svg { + --unomi-primary: #6d5ce7; + --unomi-primary-dark: #5a48c9; + --unomi-primary-light: #ede9fe; + --unomi-primary-subtle: #f5f3ff; + --unomi-accent: #00cec9; + --unomi-accent-dark: #0d9488; + --unomi-hero-accent: #a78bfa; + --unomi-text: #1e293b; + --unomi-text-muted: #64748b; + --unomi-white: #ffffff; + --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; + --unomi-success: #059669; + --unomi-success-bg: #ecfdf5; + --unomi-amber: #d97706; + --unomi-amber-bg: #fef3c7; + --unomi-info: #2563eb; + --unomi-info-bg: #dbeafe; + font-family: Inter, system-ui, sans-serif; + } + @keyframes grow-bar { from { transform: scaleY(0); } to { transform: scaleY(1); } } + @keyframes fade-up { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } + @keyframes dash-flow { to { stroke-dashoffset: -20; } } + @keyframes pulse-dot { 0%,100%{opacity:.7;r:5} 50%{opacity:1;r:7} } + .bar { transform-box: fill-box; transform-origin: bottom; animation: grow-bar 0.9s ease-out both; } + .fade { animation: fade-up 0.7s ease-out both; } + .flow { stroke-dasharray: 5 4; animation: dash-flow 1.8s linear infinite; } + .dot { animation: pulse-dot 2.4s ease-in-out infinite; } + @media (prefers-reduced-motion: reduce) { + .bar,.fade,.flow,.dot { animation: none !important; } + } +</style></defs> + <rect width="840" height="420" rx="20" fill="var(--unomi-bg-soft)"/> + <text class="fade" x="40" y="36" fill="var(--unomi-text)" font-size="18" font-weight="700">Unit and integration tests</text> + <text class="fade" style="animation-delay:.08s" x="40" y="58" fill="var(--unomi-text-muted)" font-size="12">Approximate @Test method counts (JUnit annotations)</text> + <text class="fade" style="animation-delay:.12s" x="40" y="78" fill="var(--unomi-text-muted)" font-size="11">Unomi 1: 11 unit / 25 IT classes | Unomi 2: 12 unit / 44 IT classes | Unomi 3.0: 15 unit / 43 IT classes | Unomi 3.1: 66 unit / 63 IT classes</text> + <line x1="70" y1="320.0" x2="780" y2="320.0" stroke="var(--unomi-border)" stroke-width="1"/><text x="64" y="324.0" text-anchor="end" fill="var(--unomi-text-muted)" font-size="10">0</text><line x1="70" y1="265.0" x2="780" y2="265.0" stroke="var(--unomi-border)" stroke-width="1"/><text x="64" y="269.0" text-anchor="end" fill="var(--unomi-text-muted)" font-size="10">300</text><line x1="70" y1="210.0" x2="780" y2="210.0" stroke="var(--unomi-border)" stroke-width="1"/><text x="64" y="214.0" [...] + + <g class="fade" style="animation-delay:0.10s"> + <rect class="bar" style="animation-delay:0.10s" x="100" y="313.8" width="36" height="6.2" rx="4" fill="var(--unomi-primary)"/> + <rect class="bar" style="animation-delay:0.15s" x="142" y="296.5" width="36" height="23.5" rx="4" fill="var(--unomi-info)"/> + <text x="118" y="305.8" text-anchor="middle" fill="var(--unomi-primary-dark)" font-size="11" font-weight="700">34</text> + <text x="160" y="288.5" text-anchor="middle" fill="var(--unomi-info)" font-size="11" font-weight="700">128</text> + <text x="139" y="344" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="700">Unomi 1</text> + <text x="139" y="362" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">1.9.1</text> + </g> + <g class="fade" style="animation-delay:0.22s"> + <rect class="bar" style="animation-delay:0.22s" x="280" y="312.3" width="36" height="7.7" rx="4" fill="var(--unomi-primary)"/> + <rect class="bar" style="animation-delay:0.27s" x="322" y="284.6" width="36" height="35.4" rx="4" fill="var(--unomi-info)"/> + <text x="298" y="304.3" text-anchor="middle" fill="var(--unomi-primary-dark)" font-size="11" font-weight="700">42</text> + <text x="340" y="276.6" text-anchor="middle" fill="var(--unomi-info)" font-size="11" font-weight="700">193</text> + <text x="319" y="344" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="700">Unomi 2</text> + <text x="319" y="362" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">2.7.0</text> + </g> + <g class="fade" style="animation-delay:0.34s"> + <rect class="bar" style="animation-delay:0.34s" x="460" y="303.3" width="36" height="16.7" rx="4" fill="var(--unomi-primary)"/> + <rect class="bar" style="animation-delay:0.39s" x="502" y="284.6" width="36" height="35.4" rx="4" fill="var(--unomi-info)"/> + <text x="478" y="295.3" text-anchor="middle" fill="var(--unomi-primary-dark)" font-size="11" font-weight="700">91</text> + <text x="520" y="276.6" text-anchor="middle" fill="var(--unomi-info)" font-size="11" font-weight="700">193</text> + <text x="499" y="344" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="700">Unomi 3.0</text> + <text x="499" y="362" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">3.0.1</text> + </g> + <g class="fade" style="animation-delay:0.46s"> + <rect class="bar" style="animation-delay:0.46s" x="640" y="109.5" width="36" height="210.5" rx="4" fill="var(--unomi-primary)"/> + <rect class="bar" style="animation-delay:0.51s" x="682" y="248.9" width="36" height="71.1" rx="4" fill="var(--unomi-info)"/> + <text x="658" y="101.5" text-anchor="middle" fill="var(--unomi-primary-dark)" font-size="11" font-weight="700">1,148</text> + <text x="700" y="240.9" text-anchor="middle" fill="var(--unomi-info)" font-size="11" font-weight="700">388</text> + <text x="679" y="344" text-anchor="middle" fill="var(--unomi-text)" font-size="13" font-weight="700">Unomi 3.1</text> + <text x="679" y="362" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">HEAD</text> + </g> + <g class="fade" style="animation-delay:.7s"> + <rect x="40" y="385" width="14" height="14" rx="3" fill="var(--unomi-primary)"/> + <text x="62" y="396" fill="var(--unomi-text)" font-size="12">Unit @Test methods</text> + <rect x="230" y="385" width="14" height="14" rx="3" fill="var(--unomi-info)"/> + <text x="252" y="396" fill="var(--unomi-text)" font-size="12">Integration @Test methods</text> + <text x="780" y="396" text-anchor="end" fill="var(--unomi-text-muted)" font-size="11">Unit methods ~34x since 1.x</text> + </g> +</svg> diff --git a/src/main/webapp/assets/images/unomi3/numbers-visits-countries.svg b/src/main/webapp/assets/images/unomi3/numbers-visits-countries.svg new file mode 100644 index 0000000..ae3c884 --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/numbers-visits-countries.svg @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 840 420" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="Top visitor countries for unomi.apache.org from ASF Matomo"> + <defs><style> + svg { + --unomi-primary: #6d5ce7; --unomi-primary-dark: #5a48c9; --unomi-accent: #00cec9; + --unomi-text: #1e293b; --unomi-text-muted: #64748b; --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; --unomi-amber: #d97706; + font-family: Inter, system-ui, sans-serif; + } + @keyframes fade-up { from { opacity: 0; } to { opacity: 1; } } + .fade { animation: fade-up 0.7s ease-out both; } + @media (prefers-reduced-motion: reduce) { .fade { animation: none !important; } } +</style></defs> + <rect width="840" height="420" rx="20" fill="var(--unomi-bg-soft)"/> + <text class="fade" x="40" y="36" fill="var(--unomi-text)" font-size="18" font-weight="700">Visitors by country</text> + <text class="fade" style="animation-delay:.08s" x="40" y="56" fill="var(--unomi-text-muted)" font-size="12">ASF Matomo geo for unomi.apache.org - tracking window through Jul 2026</text> + <g class="fade" style="animation-delay:.15s"> + <text x="40" y="90" fill="var(--unomi-text)" font-size="13" font-weight="600">United States</text> + <text x="800" y="90" text-anchor="end" fill="var(--unomi-text-muted)" font-size="12">12,941 (45%)</text> + <rect x="40" y="96" width="520.0" height="10" rx="5" fill="var(--unomi-primary)" fill-opacity="0.85"/> + <rect x="40" y="96" width="520" height="10" rx="5" fill="var(--unomi-primary)" fill-opacity="0.08"/> + + <text x="40" y="126" fill="var(--unomi-text)" font-size="13" font-weight="600">China</text> + <text x="800" y="126" text-anchor="end" fill="var(--unomi-text-muted)" font-size="12">2,127 (7%)</text> + <rect x="40" y="132" width="85.5" height="10" rx="5" fill="var(--unomi-primary)" fill-opacity="0.85"/> + <rect x="40" y="132" width="520" height="10" rx="5" fill="var(--unomi-primary)" fill-opacity="0.08"/> + + <text x="40" y="162" fill="var(--unomi-text)" font-size="13" font-weight="600">India</text> + <text x="800" y="162" text-anchor="end" fill="var(--unomi-text-muted)" font-size="12">1,658 (6%)</text> + <rect x="40" y="168" width="66.6" height="10" rx="5" fill="var(--unomi-primary)" fill-opacity="0.85"/> + <rect x="40" y="168" width="520" height="10" rx="5" fill="var(--unomi-primary)" fill-opacity="0.08"/> + + <text x="40" y="198" fill="var(--unomi-text)" font-size="13" font-weight="600">United Kingdom</text> + <text x="800" y="198" text-anchor="end" fill="var(--unomi-text-muted)" font-size="12">1,320 (5%)</text> + <rect x="40" y="204" width="53.0" height="10" rx="5" fill="var(--unomi-primary)" fill-opacity="0.85"/> + <rect x="40" y="204" width="520" height="10" rx="5" fill="var(--unomi-primary)" fill-opacity="0.08"/> + + <text x="40" y="234" fill="var(--unomi-text)" font-size="13" font-weight="600">Vietnam</text> + <text x="800" y="234" text-anchor="end" fill="var(--unomi-text-muted)" font-size="12">809 (3%)</text> + <rect x="40" y="240" width="32.5" height="10" rx="5" fill="var(--unomi-primary)" fill-opacity="0.85"/> + <rect x="40" y="240" width="520" height="10" rx="5" fill="var(--unomi-primary)" fill-opacity="0.08"/> + + <text x="40" y="270" fill="var(--unomi-text)" font-size="13" font-weight="600">Russia</text> + <text x="800" y="270" text-anchor="end" fill="var(--unomi-text-muted)" font-size="12">707 (2%)</text> + <rect x="40" y="276" width="28.4" height="10" rx="5" fill="var(--unomi-primary)" fill-opacity="0.85"/> + <rect x="40" y="276" width="520" height="10" rx="5" fill="var(--unomi-primary)" fill-opacity="0.08"/> + + <text x="40" y="306" fill="var(--unomi-text)" font-size="13" font-weight="600">France</text> + <text x="800" y="306" text-anchor="end" fill="var(--unomi-text-muted)" font-size="12">700 (2%)</text> + <rect x="40" y="312" width="28.1" height="10" rx="5" fill="var(--unomi-primary)" fill-opacity="0.85"/> + <rect x="40" y="312" width="520" height="10" rx="5" fill="var(--unomi-primary)" fill-opacity="0.08"/> + + <text x="40" y="342" fill="var(--unomi-text)" font-size="13" font-weight="600">Germany</text> + <text x="800" y="342" text-anchor="end" fill="var(--unomi-text-muted)" font-size="12">672 (2%)</text> + <rect x="40" y="348" width="27.0" height="10" rx="5" fill="var(--unomi-primary)" fill-opacity="0.85"/> + <rect x="40" y="348" width="520" height="10" rx="5" fill="var(--unomi-primary)" fill-opacity="0.08"/> +</g> + <text x="40" y="400" fill="var(--unomi-text-muted)" font-size="11">Desktop-heavy traffic (~88%) · mostly direct and search · Matomo bot filtering differs from CDN download logs</text> +</svg> diff --git a/src/main/webapp/assets/images/unomi3/numbers-visits.svg b/src/main/webapp/assets/images/unomi3/numbers-visits.svg new file mode 100644 index 0000000..bfc473c --- /dev/null +++ b/src/main/webapp/assets/images/unomi3/numbers-visits.svg @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg viewBox="0 0 840 420" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" + aria-label="Monthly visits to unomi.apache.org from Matomo, January 2025 through July 2026"> + <defs> + <style> + svg { + --unomi-primary: #6d5ce7; --unomi-primary-dark: #5a48c9; --unomi-accent: #00cec9; + --unomi-text: #1e293b; --unomi-text-muted: #64748b; --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; --unomi-amber: #d97706; + font-family: Inter, system-ui, sans-serif; + } + @keyframes fade-up { from { opacity: 0; } to { opacity: 1; } } + .fade { animation: fade-up 0.7s ease-out both; } + @media (prefers-reduced-motion: reduce) { .fade { animation: none !important; } } +</style> + <linearGradient id="visitFill" x1="0" y1="80" x2="0" y2="340" gradientUnits="userSpaceOnUse"> + <stop stop-color="#6d5ce7" stop-opacity="0.25"/> + <stop offset="1" stop-color="#6d5ce7" stop-opacity="0.02"/> + </linearGradient> + </defs> + <rect width="840" height="420" rx="20" fill="var(--unomi-bg-soft)"/> + <text class="fade" x="40" y="36" fill="var(--unomi-text)" font-size="18" font-weight="700">Website visits</text> + <text class="fade" style="animation-delay:.08s" x="40" y="56" fill="var(--unomi-text-muted)" font-size="12">unomi.apache.org via ASF Matomo - monthly series Jan 2025 to Jul 2026</text> + <line x1="70" y1="340.0" x2="800" y2="340.0" stroke="var(--unomi-border)" stroke-width="1"/><text x="62" y="344.0" text-anchor="end" fill="var(--unomi-text-muted)" font-size="10">0</text><line x1="70" y1="275.0" x2="800" y2="275.0" stroke="var(--unomi-border)" stroke-width="1"/><text x="62" y="279.0" text-anchor="end" fill="var(--unomi-text-muted)" font-size="10">542</text><line x1="70" y1="210.0" x2="800" y2="210.0" stroke="var(--unomi-border)" stroke-width="1"/><text x="62" y="214.0" [...] + <g class="fade" style="animation-delay:.12s"><rect x="57.5" y="269.3" width="25.0" height="70.7" rx="3" fill="var(--unomi-primary)" fill-opacity="0.35"/><rect x="98.1" y="141.2" width="25.0" height="198.8" rx="3" fill="var(--unomi-primary)" fill-opacity="0.35"/><rect x="138.6" y="113.9" width="25.0" height="226.1" rx="3" fill="var(--unomi-primary)" fill-opacity="0.35"/><rect x="179.2" y="133.3" width="25.0" height="206.7" rx="3" fill="var(--unomi-primary)" fill-opacity="0.35"/><rect x= [...] + <polygon class="fade" style="animation-delay:.18s" points="70.0,340.0 70.0,269.3 110.6,141.2 151.1,113.9 191.7,133.3 232.2,141.2 272.8,148.7 313.3,155.8 353.9,146.7 394.4,135.6 435.0,151.9 475.6,148.7 516.1,163.6 556.7,142.2 597.2,166.9 637.8,123.3 678.3,182.8 718.9,158.6 759.4,141.6 800.0,241.6 800.0,340.0" fill="url(#visitFill)"/> + <polyline class="fade" style="animation-delay:.22s" points="70.0,269.3 110.6,141.2 151.1,113.9 191.7,133.3 232.2,141.2 272.8,148.7 313.3,155.8 353.9,146.7 394.4,135.6 435.0,151.9 475.6,148.7 516.1,163.6 556.7,142.2 597.2,166.9 637.8,123.3 678.3,182.8 718.9,158.6 759.4,141.6 800.0,241.6" fill="none" stroke="var(--unomi-primary)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/> + <text x="70.0" y="362" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">Jan 25</text><text x="191.7" y="362" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">Apr 25</text><text x="313.3" y="362" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">Jul 25</text><text x="435.0" y="362" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">Oct 25</text><text x="556.7" y="362" text-anchor="middle" fill="var(--unomi-text-muted)" [...] + <circle cx="800.0" cy="241.6" r="5" fill="var(--unomi-primary)"/> + <text x="40" y="402" fill="var(--unomi-text-muted)" font-size="12">28,795 visits total · 62,762 actions · ~91s avg · bounce 67%</text> +</svg> diff --git a/src/main/webapp/blog/index.html b/src/main/webapp/blog/index.html index c83f6e5..1f861d8 100644 --- a/src/main/webapp/blog/index.html +++ b/src/main/webapp/blog/index.html @@ -213,6 +213,22 @@ layout: default </div> </div> <div class="row g-4"> + <div class="col-md-6 col-lg-3"> + <article class="feature-card h-100 overflow-hidden p-0 position-relative"> + <a href="/blog/unomi-3-in-numbers.html" class="d-block" aria-label="Read Unomi 3 in numbers"> + <img src="/assets/images/unomi3/numbers-hero.svg" class="img-fluid w-100" alt="Unomi 3 metrics charts" loading="lazy"> + </a> + <div class="p-3"> + <span class="badge badge-primary mb-2">Metrics</span> + <h3 class="h6 fw-semibold mb-2"> + <a href="/blog/unomi-3-in-numbers.html" class="text-decoration-none text-body">Unomi 3 in numbers</a> + </h3> + <p class="small text-muted mb-0">LOC, tests, and contributors — 1.x to 3.1.</p> + <a href="/blog/unomi-3-in-numbers.html" class="stretched-link" aria-label="Read Unomi 3 in numbers"></a> + </div> + </article> + </div> + <div class="col-md-6 col-lg-3"> <article class="feature-card h-100 overflow-hidden p-0 position-relative"> <a href="/blog/unomi-3-platform-foundation.html" class="d-block" aria-label="Read platform foundation"> @@ -339,8 +355,11 @@ layout: default <div class="col-lg-5"> <div class="feature-card h-100"> <p class="section-label mb-3">Browse by topic</p> + <p class="small mb-3"><a href="#all-posts">View all posts by date</a></p> <div class="d-flex flex-wrap gap-2 mb-4"> <a href="/blog/introducing-apache-unomi-3.html" class="badge badge-primary text-decoration-none px-3 py-2">Unomi 3</a> + <a href="/blog/unomi-3-in-numbers.html" class="badge badge-primary text-decoration-none px-3 py-2">In numbers</a> + <a href="/blog/unomi-3-multitenancy.html" class="badge badge-primary text-decoration-none px-3 py-2">Multi-tenancy</a> <a href="/blog/unomi-3-ai-agents.html" class="badge badge-primary text-decoration-none px-3 py-2">AI agents</a> <a href="/blog/migrating-to-unomi-3.html" class="badge badge-primary text-decoration-none px-3 py-2">Migration</a> @@ -364,6 +383,198 @@ layout: default </div> </section> + + + +<!-- Chronological archive (featured layout above stays as-is) --> +<section class="section section-alt" id="all-posts"> + <div class="container"> + <div class="row justify-content-center"> + <div class="col-lg-9"> + <div class="bg-white rounded-3 shadow-sm overflow-hidden"> + <button class="btn btn-link text-decoration-none text-body w-100 text-start px-4 py-3 d-flex align-items-center justify-content-between collapsed" + type="button" + data-bs-toggle="collapse" + data-bs-target="#blog-archive-list" + aria-expanded="false" + aria-controls="blog-archive-list"> + <span> + <span class="section-label d-block mb-1">Archive</span> + <span class="h5 mb-0 fw-semibold">All posts by date</span> + <span class="d-block small text-muted mt-1">Newest first · 16 posts</span> + </span> + <i class="bi bi-chevron-down fs-5 text-muted" aria-hidden="true"></i> + </button> + <div class="collapse" id="blog-archive-list"> + <div class="list-group list-group-flush border-top"> + <a href="/blog/unomi-3-in-numbers.html" class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0"> + <span class="text-primary fw-bold" style="min-width:2rem">16</span> + <div class="flex-grow-1"> + <div class="d-flex flex-wrap justify-content-between gap-2"> + <strong class="text-body">Unomi 3 in numbers</strong> + <time class="small text-muted text-nowrap" datetime="2026-07-17T10:00:00Z">Jul 17, 2026 · 10:00 UTC</time> + </div> + <div class="small text-muted">LOC, tests, downloads, visitors</div> + </div> + </a> + <a href="/blog/unomi-3-openapi-enrichment.html" class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0"> + <span class="text-primary fw-bold" style="min-width:2rem">15</span> + <div class="flex-grow-1"> + <div class="d-flex flex-wrap justify-content-between gap-2"> + <strong class="text-body">Richer OpenAPI docs in Unomi 3.1</strong> + <time class="small text-muted text-nowrap" datetime="2026-07-15T13:58:00Z">Jul 15, 2026 · 13:58 UTC</time> + </div> + <div class="small text-muted">Javadoc-enriched OpenAPI</div> + </div> + </a> + <a href="/blog/unomi-3-manual.html" class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0"> + <span class="text-primary fw-bold" style="min-width:2rem">14</span> + <div class="flex-grow-1"> + <div class="d-flex flex-wrap justify-content-between gap-2"> + <strong class="text-body">Unomi 3 manual improvements</strong> + <time class="small text-muted text-nowrap" datetime="2026-07-13T12:51:00Z">Jul 13, 2026 · 12:51 UTC</time> + </div> + <div class="small text-muted">Branded docs and PlantUML</div> + </div> + </a> + <a href="/blog/unomi-3-build-sh.html" class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0"> + <span class="text-primary fw-bold" style="min-width:2rem">13</span> + <div class="flex-grow-1"> + <div class="d-flex flex-wrap justify-content-between gap-2"> + <strong class="text-body">One entry point — Unomi 3 build.sh</strong> + <time class="small text-muted text-nowrap" datetime="2026-07-11T11:44:00Z">Jul 11, 2026 · 11:44 UTC</time> + </div> + <div class="small text-muted">One script for build and deploy</div> + </div> + </a> + <a href="/blog/unomi-3-it-execution.html" class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0"> + <span class="text-primary fw-bold" style="min-width:2rem">12</span> + <div class="flex-grow-1"> + <div class="d-flex flex-wrap justify-content-between gap-2"> + <strong class="text-body">Clearer integration test runs in Unomi 3</strong> + <time class="small text-muted text-nowrap" datetime="2026-07-09T10:37:00Z">Jul 09, 2026 · 10:37 UTC</time> + </div> + <div class="small text-muted">Clearer IT progress and CI</div> + </div> + </a> + <a href="/blog/unomi-3-unit-testing-harness.html" class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0"> + <span class="text-primary fw-bold" style="min-width:2rem">11</span> + <div class="flex-grow-1"> + <div class="d-flex flex-wrap justify-content-between gap-2"> + <strong class="text-body">Faster Unomi service unit tests</strong> + <time class="small text-muted text-nowrap" datetime="2026-07-07T09:30:00Z">Jul 07, 2026 · 09:30 UTC</time> + </div> + <div class="small text-muted">Fast in-memory unit tests</div> + </div> + </a> + <a href="/blog/unomi-3-request-tracing.html" class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0"> + <span class="text-primary fw-bold" style="min-width:2rem">10</span> + <div class="flex-grow-1"> + <div class="d-flex flex-wrap justify-content-between gap-2"> + <strong class="text-body">Request tracing with explain=true in Unomi 3</strong> + <time class="small text-muted text-nowrap" datetime="2026-07-05T13:23:00Z">Jul 05, 2026 · 13:23 UTC</time> + </div> + <div class="small text-muted">Debug with explain=true</div> + </div> + </a> + <a href="/blog/unomi-3-task-scheduler.html" class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0"> + <span class="text-primary fw-bold" style="min-width:2rem">09</span> + <div class="flex-grow-1"> + <div class="d-flex flex-wrap justify-content-between gap-2"> + <strong class="text-body">Cluster-aware task scheduler in Unomi 3.1</strong> + <time class="small text-muted text-nowrap" datetime="2026-07-03T12:16:00Z">Jul 03, 2026 · 12:16 UTC</time> + </div> + <div class="small text-muted">Cluster-aware scheduled work</div> + </div> + </a> + <a href="/blog/unomi-3-shell-commands.html" class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0"> + <span class="text-primary fw-bold" style="min-width:2rem">08</span> + <div class="flex-grow-1"> + <div class="d-flex flex-wrap justify-content-between gap-2"> + <strong class="text-body">Unomi 3 Karaf shell commands</strong> + <time class="small text-muted text-nowrap" datetime="2026-07-01T11:59:00Z">Jul 01, 2026 · 11:59 UTC</time> + </div> + <div class="small text-muted">Ops via Karaf shell</div> + </div> + </a> + <a href="/blog/unomi-3-opensearch.html" class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0"> + <span class="text-primary fw-bold" style="min-width:2rem">07</span> + <div class="flex-grow-1"> + <div class="d-flex flex-wrap justify-content-between gap-2"> + <strong class="text-body">Elasticsearch 9 and OpenSearch in Unomi 3</strong> + <time class="small text-muted text-nowrap" datetime="2026-06-29T10:52:00Z">Jun 29, 2026 · 10:52 UTC</time> + </div> + <div class="small text-muted">Dual search backends</div> + </div> + </a> + <a href="/blog/unomi-3-platform-foundation.html" class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0"> + <span class="text-primary fw-bold" style="min-width:2rem">06</span> + <div class="flex-grow-1"> + <div class="d-flex flex-wrap justify-content-between gap-2"> + <strong class="text-body">Unomi 3 platform foundation</strong> + <time class="small text-muted text-nowrap" datetime="2026-06-27T09:45:00Z">Jun 27, 2026 · 09:45 UTC</time> + </div> + <div class="small text-muted">Java 17, Karaf 4.4, ES9, clustering</div> + </div> + </a> + <a href="/blog/migrating-to-unomi-3.html" class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0"> + <span class="text-primary fw-bold" style="min-width:2rem">05</span> + <div class="flex-grow-1"> + <div class="d-flex flex-wrap justify-content-between gap-2"> + <strong class="text-body">Migrating from Unomi 2.x to Unomi 3</strong> + <time class="small text-muted text-nowrap" datetime="2026-06-25T13:38:00Z">Jun 25, 2026 · 13:38 UTC</time> + </div> + <div class="small text-muted">Upgrade path from 2.x</div> + </div> + </a> + <a href="/blog/unomi-3-ai-agents.html" class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0"> + <span class="text-primary fw-bold" style="min-width:2rem">04</span> + <div class="flex-grow-1"> + <div class="d-flex flex-wrap justify-content-between gap-2"> + <strong class="text-body">Apache Unomi 3 for AI agents</strong> + <time class="small text-muted text-nowrap" datetime="2026-06-23T12:31:00Z">Jun 23, 2026 · 12:31 UTC</time> + </div> + <div class="small text-muted">Privacy-first customer memory for agents</div> + </div> + </a> + <a href="/blog/unomi-3-multitenancy.html" class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0"> + <span class="text-primary fw-bold" style="min-width:2rem">03</span> + <div class="flex-grow-1"> + <div class="d-flex flex-wrap justify-content-between gap-2"> + <strong class="text-body">Multi-tenancy in Apache Unomi 3</strong> + <time class="small text-muted text-nowrap" datetime="2026-06-21T11:24:00Z">Jun 21, 2026 · 11:24 UTC</time> + </div> + <div class="small text-muted">Platform-wide tenant isolation</div> + </div> + </a> + <a href="/blog/introducing-apache-unomi-3.html" class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0"> + <span class="text-primary fw-bold" style="min-width:2rem">02</span> + <div class="flex-grow-1"> + <div class="d-flex flex-wrap justify-content-between gap-2"> + <strong class="text-body">Introducing Apache Unomi 3</strong> + <time class="small text-muted text-nowrap" datetime="2026-06-19T10:17:00Z">Jun 19, 2026 · 10:17 UTC</time> + </div> + <div class="small text-muted">The major Unomi 3 release story</div> + </div> + </a> + <a href="/blog/welcome-to-the-apache-unomi-blog.html" class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0"> + <span class="text-primary fw-bold" style="min-width:2rem">01</span> + <div class="flex-grow-1"> + <div class="d-flex flex-wrap justify-content-between gap-2"> + <strong class="text-body">Welcome to the Apache Unomi Blog</strong> + <time class="small text-muted text-nowrap" datetime="2026-06-17T09:10:00Z">Jun 17, 2026 · 09:10 UTC</time> + </div> + <div class="small text-muted">Why this Blog exists</div> + </div> + </a> + </div> + </div> + </div> + </div> + </div> + </div> +</section> + <!-- Reading order --> <section class="section"> <div class="container"> @@ -404,7 +615,8 @@ layout: default </div> </div> <p class="text-center small text-muted mt-4 mb-0"> - Then: <a href="/blog/migrating-to-unomi-3.html">Migrating from 2.x</a> · + Then: <a href="/blog/unomi-3-in-numbers.html">In numbers</a> · + <a href="/blog/migrating-to-unomi-3.html">Migrating from 2.x</a> · <a href="/blog/unomi-3-platform-foundation.html">Platform</a> · <a href="/blog/unomi-3-opensearch.html">OpenSearch</a> · <a href="/blog/unomi-3-shell-commands.html">Shell</a> · diff --git a/src/main/webapp/blog/introducing-apache-unomi-3.html b/src/main/webapp/blog/introducing-apache-unomi-3.html index 1bb690e..e0e0525 100644 --- a/src/main/webapp/blog/introducing-apache-unomi-3.html +++ b/src/main/webapp/blog/introducing-apache-unomi-3.html @@ -3,6 +3,7 @@ title: Introducing Apache Unomi 3 description: "Apache Unomi 3 is a major open-source CDP release — Elasticsearch 9 / OpenSearch, multi-tenancy, stable REST, quality investment, and AI-ready customer memory." keywords: "Apache Unomi 3, open source CDP, multi-tenancy, OpenSearch, V2 API compatibility, CDP tests" layout: default +date: 2026-06-19 10:17:00 +0000 --- <section class="page-header"> @@ -10,6 +11,12 @@ layout: default <p class="section-label text-hero-label">Unomi 3</p> <h1>Introducing Apache Unomi 3</h1> <p class="lead">A major Customer Data Platform release — platform foundation (3.0) and product leap (3.1) as one story.</p> + {% if page.date %} + <p class="small text-muted mt-2 mb-0"> + <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ page.date | date: "%B %d, %Y at %H:%M UTC" }}</time> + </p> + {% endif %} + </div> </section> <section class="section"> diff --git a/src/main/webapp/blog/migrating-to-unomi-3.html b/src/main/webapp/blog/migrating-to-unomi-3.html index 83520ad..c0d31de 100644 --- a/src/main/webapp/blog/migrating-to-unomi-3.html +++ b/src/main/webapp/blog/migrating-to-unomi-3.html @@ -3,6 +3,7 @@ title: Migrating from Unomi 2.x to Unomi 3 description: "Unomi 3 retains 100% of documented 2.x OpenAPI paths and offers V2 compatibility mode for phased client auth migration." keywords: "Unomi 3 migration, V2 compatibility mode, OpenAPI stability, upgrade from Unomi 2" layout: default +date: 2026-06-25 13:38:00 +0000 --- <section class="page-header"> @@ -10,6 +11,12 @@ layout: default <p class="section-label text-hero-label">Upgrades</p> <h1>Migrating from Unomi 2.x to Unomi 3</h1> <p class="lead">Familiar REST map, additive APIs, and an optional V2 compatibility bridge.</p> + {% if page.date %} + <p class="small text-muted mt-2 mb-0"> + <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ page.date | date: "%B %d, %Y at %H:%M UTC" }}</time> + </p> + {% endif %} + </div> </section> <section class="section"> diff --git a/src/main/webapp/blog/unomi-3-ai-agents.html b/src/main/webapp/blog/unomi-3-ai-agents.html index 2a9d3e1..b6f4a33 100644 --- a/src/main/webapp/blog/unomi-3-ai-agents.html +++ b/src/main/webapp/blog/unomi-3-ai-agents.html @@ -3,6 +3,7 @@ title: Apache Unomi 3 for AI agents — privacy-first customer memory description: "Apache Unomi 3 is an open-source CDP AI agents can query and update — retrieve customer context, store observations, summarize profiles, and build segments with built-in consent." keywords: "Apache Unomi 3, AI agents CDP, LLM customer context, open source CDP for AI, agentic personalization, consent-aware AI data layer" layout: default +date: 2026-06-23 12:31:00 +0000 --- <section class="page-header"> @@ -10,6 +11,12 @@ layout: default <p class="section-label text-hero-label">AI agents · Second message</p> <h1>Apache Unomi 3 for AI agents</h1> <p class="lead">Privacy-first customer memory — retrieve, store, summarize, and build segments. Unomi is not an LLM; it is the data layer agents plug into.</p> + {% if page.date %} + <p class="small text-muted mt-2 mb-0"> + <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ page.date | date: "%B %d, %Y at %H:%M UTC" }}</time> + </p> + {% endif %} + </div> </section> <section class="section"> diff --git a/src/main/webapp/blog/unomi-3-build-sh.html b/src/main/webapp/blog/unomi-3-build-sh.html index e06759e..c835d1e 100644 --- a/src/main/webapp/blog/unomi-3-build-sh.html +++ b/src/main/webapp/blog/unomi-3-build-sh.html @@ -3,6 +3,7 @@ title: One entry point — Unomi 3 build.sh description: "How Apache Unomi 3 replaced scattered build scripts with a single build.sh — preflight checks, CI mode, integration tests, deploy, and Windows build.ps1 parity." keywords: "Unomi build.sh, Apache Unomi developer experience, UNOMI-887, Maven CI, OpenSearch integration tests" layout: default +date: 2026-07-11 11:44:00 +0000 --- <section class="page-header"> @@ -10,6 +11,12 @@ layout: default <p class="section-label text-hero-label">Unomi 3 · Developer experience</p> <h1>One entry point: <code>build.sh</code></h1> <p class="lead">Preflight, build, test, and deploy — the same script locally and on GitHub Actions.</p> + {% if page.date %} + <p class="small text-muted mt-2 mb-0"> + <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ page.date | date: "%B %d, %Y at %H:%M UTC" }}</time> + </p> + {% endif %} + </div> </section> diff --git a/src/main/webapp/blog/unomi-3-in-numbers.html b/src/main/webapp/blog/unomi-3-in-numbers.html new file mode 100644 index 0000000..f6a6254 --- /dev/null +++ b/src/main/webapp/blog/unomi-3-in-numbers.html @@ -0,0 +1,139 @@ +--- +title: Unomi 3 in numbers +description: "Unomi 1 vs 2 vs 3 in numbers — lines of code, tests, contributors, ASF downloads, and unomi.apache.org Matomo visits." +keywords: "Unomi 3 metrics, lines of code, unit tests, integration tests, contributors, downloads, website visits, Matomo, Apache Unomi statistics" +layout: default +date: 2026-07-17 10:00:00 +0000 +--- + +<section class="page-header"> + <div class="container"> + <p class="section-label text-hero-label">Unomi 3 · Metrics</p> + <h1>Unomi 3 in numbers</h1> + <p class="lead">How the codebase, tests, community, downloads, and website traffic grew from Unomi 1 through Unomi 3.1.</p> + {% if page.date %} + <p class="small text-muted mt-2 mb-0"> + <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ page.date | date: "%B %d, %Y at %H:%M UTC" }}</time> + </p> + {% endif %} + + </div> +</section> + +<section class="section"> + <div class="container col-lg-8"> + <p>Unomi 3 is not only new features — multi-tenancy, OpenSearch, shell CRUD, tracing, and the task scheduler. + It is also a step-change in how much of the platform is covered by automated tests, and how the Java surface area evolved across a decade of releases.</p> + <p>The charts below use <strong>release tags</strong> as waypoints: + Unomi 1 = <code>unomi-root-1.9.1</code>, + Unomi 2 = <code>unomi-root-2.7.0</code>, + Unomi 3.0 = <code>unomi-root-3.0.1</code>, + Unomi 3.1 = current <code>master</code> (work in progress after 3.0.1). + Counts are physical lines of code and approximate JUnit <code>@Test</code> annotations from the git tree at each tag.</p> + + <img src="/assets/images/unomi3/numbers-hero.svg" class="img-fluid my-4" alt="Unomi 3 headline metrics: 181k Java LOC, 1148 unit tests, 388 IT tests, 77 contributors"/> + + <h2 class="h4 mt-5 mb-3">Lines of code</h2> + <p>Production Java grew from about <strong>45k</strong> lines in late Unomi 1 to about <strong>70k</strong> in Unomi 2 and <strong>71k</strong> at Unomi 3.0.1 — then jumped to about <strong>118k</strong> on the 3.1 line. + Test Java stayed modest through 3.0 (~12k) and then leapt to about <strong>63k</strong> with the unit harness, multi-tenancy coverage, and related work.</p> + <img src="/assets/images/unomi3/numbers-loc.svg" class="img-fluid my-4" alt="Stacked bar chart of Java production and test lines of code across Unomi 1, 2, 3.0, and 3.1"/> + <p class="small text-muted">Methodology: physical line counts over <code>.java</code> (and primary companion sources for totals elsewhere) from <code>git archive</code> at each tag; paths under <code>target/</code> excluded. + “Main” is everything outside <code>src/test/</code> and <code>itests/</code>; “Tests” is those trees.</p> + + <h2 class="h4 mt-5 mb-3">Unit and integration tests</h2> + <p>Integration tests have been Unomi’s historical strength — growing from roughly <strong>128</strong> IT <code>@Test</code> methods in 1.9.1 to about <strong>388</strong> on 3.1. + The dramatic change is on the unit side: from a few dozen methods through Unomi 2 to about <strong>1,148</strong> on 3.1, driven by the in-memory persistence harness and denser service-level coverage.</p> + <img src="/assets/images/unomi3/numbers-tests.svg" class="img-fluid my-4" alt="Grouped bars of unit versus integration @Test method counts across Unomi versions"/> + <ul> + <li><strong>Unit classes</strong> — 11 → 12 → 15 → <strong>66</strong></li> + <li><strong>IT classes</strong> — 25 → 44 → 43 → <strong>63</strong></li> + <li><strong>Test / main Java ratio</strong> — ~0.19 in 1.x, ~0.17 in 2.x, ~<strong>0.53</strong> on 3.1</li> + </ul> + <p>That ratio shift is intentional: Unomi 3 invests in fast feedback for rules, persistence, and tenancy while keeping the heavier IT suite for end-to-end confidence. + See also + <a href="/blog/unomi-3-unit-testing-harness.html">the unit testing harness</a> + and + <a href="/blog/unomi-3-it-execution.html">clearer IT execution</a>.</p> + + <h2 class="h4 mt-5 mb-3">Contributors</h2> + <p>The project has grown to <strong>77</strong> unique authors on the master ancestry (name-based; some people appear under multiple spellings), with about <strong>2,769</strong> commits through current <code>master</code>. + Late Unomi 1 already had a broad base (~62 authors); Unomi 2 and 3 continue that trajectory rather than restarting it.</p> + <img src="/assets/images/unomi3/numbers-contributors.svg" class="img-fluid my-4" alt="Line chart of cumulative unique contributors from Unomi 1 through 3.1"/> + <p class="small text-muted">Top committers historically include Serge Huber, Thomas Draier, Abdelkader Midani, Sergiy Shyrkov, and many others who shaped incubating 1.x through the 3.x platform leap.</p> + + <h2 class="h4 mt-5 mb-3">Downloads</h2> + <p>Apache Infra publishes monthly CDN hit logs for Unomi on + <a href="https://infra-reports.apache.org/download_stats/unomi/">infra-reports.apache.org</a> + (hosts <code>dlcdn.apache.org</code> and <code>downloads.apache.org</code>). + From July 2023 through mid-July 2026 that is about + <strong>43k</strong> software-package hits in total (binary + source archives) — + that figure <em>includes</em> bots. + Of those, about <strong>25k</strong> (~58%) are identifiable as bots or scripts + (Python Requests, scrapers, GPTBot, curl/wget, HeadlessChrome, and similar), + leaving about <strong>18k</strong> from recognizable human browsers + (the purple series below). + ASF’s coarse UA labels will miss some bots, so the human count is likely still a bit high.</p> + <img src="/assets/images/unomi3/numbers-downloads.svg" class="img-fluid my-4" alt="Cumulative Unomi software package downloads: human browsers only, all packages including bots, and known bots, July 2023 through July 2026"/> + <p class="small text-muted">Methodology: classify each file’s <code>useragents</code> labels into human browsers vs known bots/scripts + (plus a small unknown bucket), then cumulate over the available months. + Solid purple = human browsers only (bots excluded); + solid teal = all software-package hits (bots included); + dotted muted purple = identified bots/scripts only. + Manuals/PDFs are excluded from all three series. Stats start mid-2023; July 2026 is partial. + Vertical markers: teal <strong>2.7.0</strong> (Sep 2025 release), amber <strong>3.0</strong> (Nov 2025), + and purple <strong>Site refresh</strong> (Feb 2026 website modernization). + Bot detection is only as good as the coarse UA labels ASF publishes — some automated clients + (or bots spoofing a normal browser) will not be flagged, so the human series is a best-effort lower-noise estimate, + not a guarantee of bot-free traffic.</p> + + <h3 class="h5 mt-4 mb-3">Where downloads come from</h3> + <p>Country totals use the same bot filter: each file’s <code>cca2</code> hits are scaled by that file’s human-browser share + (ASF does not publish a full country×UA cross-tab). + After filtering, the densest human-attributed package volume is + <strong>Brazil</strong> (~24%), <strong>China</strong> (~18%), <strong>Russia</strong> (~17%), + the <strong>United States</strong> (~12%), and <strong>Germany</strong> (~8%), + with a long tail across Europe and Asia (~102 countries with attributed human traffic).</p> + <img src="/assets/images/unomi3/numbers-downloads-map.svg" class="img-fluid my-4" alt="World map of human Unomi software package downloads by country with bots excluded"/> + <p class="small text-muted">Map colors use a log scale. Geo still comes from CDN IP attribution, so mirrors, cloud egress, and VPNs can skew rankings + even after UA filtering. Hover a country in a supporting browser for its estimate.</p> + + <h2 class="h4 mt-5 mb-3">Website visitors</h2> + <p>Separately from CDN package logs, ASF runs + <a href="https://analytics.apache.org/index.php?module=CoreHome&action=index&idSite=61&period=day&date=yesterday">Matomo analytics</a> + for <a href="https://unomi.apache.org">unomi.apache.org</a> (site id 61). + Across the available reporting window that is about + <strong>29k</strong> visits and <strong>63k</strong> actions, + with roughly <strong>2.2</strong> actions per visit, about + <strong>91</strong> seconds average time on site, and a + <strong>67%</strong> bounce rate. + Monthly series in Matomo are populated from January 2025 onward + (the site record was created mid-2024; earlier months are empty in the API).</p> + <img src="/assets/images/unomi3/numbers-visits.svg" class="img-fluid my-4" alt="Monthly visits to unomi.apache.org from ASF Matomo, January 2025 through July 2026"/> + <p>Traffic is mostly <strong>desktop</strong> (~88%), arrives primarily via + <strong>direct entry</strong> and <strong>search engines</strong>, + and is led by visitors attributed to the <strong>United States</strong> (~45%), + then China, India, and the United Kingdom. + Top content is the home page, Getting Started, Documentation, Use Cases, and Download.</p> + <img src="/assets/images/unomi3/numbers-visits-countries.svg" class="img-fluid my-4" alt="Top visitor countries for unomi.apache.org from ASF Matomo"/> + <p class="small text-muted">Source: + <a href="https://analytics.apache.org/index.php?module=CoreHome&action=index&idSite=61">analytics.apache.org</a> + (public Matomo Reporting API). Matomo applies its own bot filtering, so country mix here will not match CDN download geography. + Figures are visits (not unique visitors for the full range — Matomo disables that metric on long ranges).</p> + + <h2 class="h4 mt-5 mb-3">What the numbers mean</h2> + <ul> + <li><strong>3.0 was the platform cut</strong> — LOC and IT counts look close to late 2.x; the runtime floor (Java 17, ES9, clustering) landed without ballooning the tree.</li> + <li><strong>3.1 is the product + quality leap</strong> — multi-tenancy and ops features arrive together with a much denser unit suite.</li> + <li><strong>Community continuity</strong> — contributor growth is cumulative; Unomi remains a long-running ASF codebase with steady authorship.</li> + <li><strong>Steady distribution</strong> — after excluding known bots/scripts (~58% of package hits), human browser downloads still show continuous uptake across three years of logged CDN data.</li> + <li><strong>Site interest holds</strong> — Matomo shows a steady ~1.5–1.9k visits/month through 2025–2026, concentrated on getting-started and docs paths.</li> + </ul> + + <p class="mt-4"> + <a class="btn btn-primary" href="/blog/introducing-apache-unomi-3.html">Introducing Unomi 3</a> + <a class="btn btn-outline-primary ms-2" href="/blog/unomi-3-unit-testing-harness.html">Unit harness</a> + <a class="btn btn-outline-primary ms-2" href="/blog/unomi-3-platform-foundation.html">Platform foundation</a> + </p> + <p><a href="/blog/">← All posts</a></p> + </div> +</section> diff --git a/src/main/webapp/blog/unomi-3-it-execution.html b/src/main/webapp/blog/unomi-3-it-execution.html index e620d92..8592d44 100644 --- a/src/main/webapp/blog/unomi-3-it-execution.html +++ b/src/main/webapp/blog/unomi-3-it-execution.html @@ -3,6 +3,7 @@ title: Clearer integration test runs in Unomi 3 description: "How Unomi 3 improved IT execution — live progress bars with ETA, quieter Maven and Docker output, and tooling for diagnosing long CI suites." keywords: "Unomi 3 integration tests, ProgressListener, CI logs, Apache Unomi IT, build.sh" layout: default +date: 2026-07-09 10:37:00 +0000 --- <section class="page-header"> @@ -10,6 +11,12 @@ layout: default <p class="section-label text-hero-label">Unomi 3 · Quality</p> <h1>Clearer integration test runs in Unomi 3</h1> <p class="lead">Progress bars, quieter CI output, and tools that turn a 90–minute suite into something you can actually follow.</p> + {% if page.date %} + <p class="small text-muted mt-2 mb-0"> + <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ page.date | date: "%B %d, %Y at %H:%M UTC" }}</time> + </p> + {% endif %} + </div> </section> diff --git a/src/main/webapp/blog/unomi-3-manual.html b/src/main/webapp/blog/unomi-3-manual.html index cebe055..258b8e2 100644 --- a/src/main/webapp/blog/unomi-3-manual.html +++ b/src/main/webapp/blog/unomi-3-manual.html @@ -3,6 +3,7 @@ title: Unomi 3 manual — PlantUML, branded theme, deeper docs description: "How the Apache Unomi 3 manual gained PlantUML/GraphViz diagrams, an Unomi-branded HTML/PDF theme, and major content expansions for migrations, multi-tenancy, and platform upgrades." keywords: "Apache Unomi manual, PlantUML AsciiDoc, Unomi 3 documentation, AsciiDoctor diagrams, Unomi CSS theme" layout: default +date: 2026-07-13 12:51:00 +0000 --- <section class="page-header"> @@ -10,6 +11,12 @@ layout: default <p class="section-label text-hero-label">Unomi 3 · Documentation</p> <h1>Unomi 3 manual: diagrams, theme, and depth</h1> <p class="lead">PlantUML in AsciiDoc, a brand-aligned HTML/PDF look, and chapters that finally match the Unomi 3 platform.</p> + {% if page.date %} + <p class="small text-muted mt-2 mb-0"> + <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ page.date | date: "%B %d, %Y at %H:%M UTC" }}</time> + </p> + {% endif %} + </div> </section> diff --git a/src/main/webapp/blog/unomi-3-multitenancy.html b/src/main/webapp/blog/unomi-3-multitenancy.html index 9b32c47..96f2563 100644 --- a/src/main/webapp/blog/unomi-3-multitenancy.html +++ b/src/main/webapp/blog/unomi-3-multitenancy.html @@ -3,6 +3,7 @@ title: Multi-tenancy in Apache Unomi 3 description: "Unomi 3 multi-tenancy was a major platform refactor — tenant isolation for profiles, events, segments, rules, and schemas with public and private API keys." keywords: "Unomi 3 multi-tenancy, tenant API keys, SaaS CDP, Apache Unomi tenants, multi-tenant CDP" layout: default +date: 2026-06-21 11:24:00 +0000 --- <section class="page-header"> @@ -10,6 +11,12 @@ layout: default <p class="section-label text-hero-label">Unomi 3 · Multi-tenancy</p> <h1>Multi-tenancy in Apache Unomi 3</h1> <p class="lead">A major platform refactoring — not a bolt-on flag. Isolate customer spaces with API keys on one cluster.</p> + {% if page.date %} + <p class="small text-muted mt-2 mb-0"> + <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ page.date | date: "%B %d, %Y at %H:%M UTC" }}</time> + </p> + {% endif %} + </div> </section> diff --git a/src/main/webapp/blog/unomi-3-openapi-enrichment.html b/src/main/webapp/blog/unomi-3-openapi-enrichment.html index e400418..4abe8bd 100644 --- a/src/main/webapp/blog/unomi-3-openapi-enrichment.html +++ b/src/main/webapp/blog/unomi-3-openapi-enrichment.html @@ -3,6 +3,7 @@ title: Richer OpenAPI docs in Unomi 3.1 description: "How Unomi 3.1 enriches /cxs/openapi.json from JAX-RS Javadoc — summaries, statuses, examples, and schemas without duplicating Swagger annotations (UNOMI-963)." keywords: "Unomi OpenAPI, openapi.json, UNOMI-963, CXF OpenAPI, Javadoc enrichment, Unomi 3.1 REST" layout: default +date: 2026-07-15 13:58:00 +0000 --- <section class="page-header"> @@ -10,6 +11,12 @@ layout: default <p class="section-label text-hero-label">Unomi 3.1 · Developer experience</p> <h1>Richer OpenAPI docs from Javadoc</h1> <p class="lead">Unomi 3.1 ships an enriched <code>/cxs/openapi.json</code> — summaries, status codes, examples, and schema notes merged from endpoint Javadoc.</p> + {% if page.date %} + <p class="small text-muted mt-2 mb-0"> + <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ page.date | date: "%B %d, %Y at %H:%M UTC" }}</time> + </p> + {% endif %} + </div> </section> diff --git a/src/main/webapp/blog/unomi-3-opensearch.html b/src/main/webapp/blog/unomi-3-opensearch.html index 02db882..537b1e0 100644 --- a/src/main/webapp/blog/unomi-3-opensearch.html +++ b/src/main/webapp/blog/unomi-3-opensearch.html @@ -3,6 +3,7 @@ title: Elasticsearch 9 and OpenSearch in Unomi 3 description: "Unomi 3 runs on Elasticsearch 9 by default and ships a first-class OpenSearch persistence distribution — same SPI, migrate guide, and build flags." keywords: "Unomi OpenSearch, Elasticsearch 9, Apache Unomi persistence, UNOMI-828, migrate Elasticsearch to OpenSearch" layout: default +date: 2026-06-29 10:52:00 +0000 --- <section class="page-header"> @@ -10,6 +11,12 @@ layout: default <p class="section-label text-hero-label">Unomi 3 · Operations</p> <h1>Elasticsearch 9 and OpenSearch in Unomi 3</h1> <p class="lead">One CDP runtime, two supported search backends — pick the distribution that matches your ops constraints.</p> + {% if page.date %} + <p class="small text-muted mt-2 mb-0"> + <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ page.date | date: "%B %d, %Y at %H:%M UTC" }}</time> + </p> + {% endif %} + </div> </section> diff --git a/src/main/webapp/blog/unomi-3-platform-foundation.html b/src/main/webapp/blog/unomi-3-platform-foundation.html index cc67d10..57243a3 100644 --- a/src/main/webapp/blog/unomi-3-platform-foundation.html +++ b/src/main/webapp/blog/unomi-3-platform-foundation.html @@ -3,6 +3,7 @@ title: Unomi 3 platform foundation description: "What Unomi 3.0 changed under the hood — Java 17, Karaf 4.4, Elasticsearch 9, and persistence-based clustering without Cellar or Hazelcast." keywords: "Unomi 3 platform, Java 17, Karaf 4.4, UNOMI-877, persistence clustering, Elasticsearch 9" layout: default +date: 2026-06-27 09:45:00 +0000 --- <section class="page-header"> @@ -10,6 +11,12 @@ layout: default <p class="section-label text-hero-label">Unomi 3 · Platform</p> <h1>Unomi 3 platform foundation</h1> <p class="lead">The 3.0 runtime floor — Java 17, Karaf 4.4, Elasticsearch 9, and clustering without Cellar or Hazelcast.</p> + {% if page.date %} + <p class="small text-muted mt-2 mb-0"> + <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ page.date | date: "%B %d, %Y at %H:%M UTC" }}</time> + </p> + {% endif %} + </div> </section> diff --git a/src/main/webapp/blog/unomi-3-request-tracing.html b/src/main/webapp/blog/unomi-3-request-tracing.html index faf1858..b1513e5 100644 --- a/src/main/webapp/blog/unomi-3-request-tracing.html +++ b/src/main/webapp/blog/unomi-3-request-tracing.html @@ -3,6 +3,7 @@ title: Request tracing with explain=true in Unomi 3 description: "Debug context and eventcollector requests with explain=true — admin-gated requestTracing trees, verified curl examples, and what 403 means for public API keys." keywords: "Unomi explain, requestTracing, Unomi tracing, UNOMI-873, eventcollector explain, context.json explain" layout: default +date: 2026-07-05 13:23:00 +0000 --- <section class="page-header"> @@ -10,6 +11,12 @@ layout: default <p class="section-label text-hero-label">Unomi 3 · Debugging</p> <h1>Request tracing with <code>explain=true</code></h1> <p class="lead">See condition evaluations, rule actions, and timings on <code>/cxs/context.json</code> and <code>/cxs/eventcollector</code>.</p> + {% if page.date %} + <p class="small text-muted mt-2 mb-0"> + <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ page.date | date: "%B %d, %Y at %H:%M UTC" }}</time> + </p> + {% endif %} + </div> </section> diff --git a/src/main/webapp/blog/unomi-3-shell-commands.html b/src/main/webapp/blog/unomi-3-shell-commands.html index 83497da..73a874d 100644 --- a/src/main/webapp/blog/unomi-3-shell-commands.html +++ b/src/main/webapp/blog/unomi-3-shell-commands.html @@ -3,6 +3,7 @@ title: Unomi 3 Karaf shell commands description: "Unomi 3 expands the unomi:* Karaf shell — unified unomi:crud, tenant session context, cache ops, task scheduler commands, and list-invalid-objects (UNOMI-879 / 880)." keywords: "Unomi shell, unomi:crud, Karaf shell, unomi:cache, unomi:task-list, tenant-set, UNOMI-879, UNOMI-880" layout: default +date: 2026-07-01 11:59:00 +0000 --- <section class="page-header"> @@ -10,6 +11,12 @@ layout: default <p class="section-label text-hero-label">Unomi 3 · Operator experience</p> <h1>Karaf shell commands in Unomi 3</h1> <p class="lead"><code>unomi:crud</code>, tenant session context, cache tools, and task commands — operate without leaving SSH.</p> + {% if page.date %} + <p class="small text-muted mt-2 mb-0"> + <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ page.date | date: "%B %d, %Y at %H:%M UTC" }}</time> + </p> + {% endif %} + </div> </section> diff --git a/src/main/webapp/blog/unomi-3-task-scheduler.html b/src/main/webapp/blog/unomi-3-task-scheduler.html index 47be3ac..648a754 100644 --- a/src/main/webapp/blog/unomi-3-task-scheduler.html +++ b/src/main/webapp/blog/unomi-3-task-scheduler.html @@ -3,6 +3,7 @@ title: Cluster-aware task scheduler in Unomi 3.1 description: "Unomi 3.1 durable task scheduler — full capability list: persistence, cluster locks, lifecycle, fluent builder, REST ops, shell, metrics, resume, and built-in jobs." keywords: "Unomi task scheduler, /cxs/tasks, unomi:task-list, Unomi 3.1, profile-purge, cluster tasks, TaskExecutor" layout: default +date: 2026-07-03 12:16:00 +0000 --- <section class="page-header"> @@ -10,6 +11,12 @@ layout: default <p class="section-label text-hero-label">Unomi 3.1 · Operations</p> <h1>Cluster-aware task scheduler</h1> <p class="lead">Durable background work with persistence, locks, retry/resume, REST, shell, and a plugin-friendly fluent API.</p> + {% if page.date %} + <p class="small text-muted mt-2 mb-0"> + <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ page.date | date: "%B %d, %Y at %H:%M UTC" }}</time> + </p> + {% endif %} + </div> </section> diff --git a/src/main/webapp/blog/unomi-3-unit-testing-harness.html b/src/main/webapp/blog/unomi-3-unit-testing-harness.html index 9e4135b..2b09332 100644 --- a/src/main/webapp/blog/unomi-3-unit-testing-harness.html +++ b/src/main/webapp/blog/unomi-3-unit-testing-harness.html @@ -3,6 +3,7 @@ title: Faster Unomi service unit tests — InMemoryPersistenceServiceImpl description: "How Unomi 3’s in-memory PersistenceService test harness lets Profile, Event, Rules, and Segment services run real unit tests in seconds without Karaf or Elasticsearch." keywords: "Unomi unit tests, InMemoryPersistenceServiceImpl, UNOMI-881, Unomi TestHelper, Surefire" layout: default +date: 2026-07-07 09:30:00 +0000 --- <section class="page-header"> @@ -10,6 +11,12 @@ layout: default <p class="section-label text-hero-label">Unomi 3 · Quality</p> <h1>Faster service unit tests</h1> <p class="lead"><code>InMemoryPersistenceServiceImpl</code> — a full PersistenceService on the test classpath so service logic no longer waits on Karaf.</p> + {% if page.date %} + <p class="small text-muted mt-2 mb-0"> + <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ page.date | date: "%B %d, %Y at %H:%M UTC" }}</time> + </p> + {% endif %} + </div> </section> diff --git a/src/main/webapp/blog/welcome-to-the-apache-unomi-blog.html b/src/main/webapp/blog/welcome-to-the-apache-unomi-blog.html index 380ce95..0e815b4 100644 --- a/src/main/webapp/blog/welcome-to-the-apache-unomi-blog.html +++ b/src/main/webapp/blog/welcome-to-the-apache-unomi-blog.html @@ -3,6 +3,7 @@ title: Welcome to the Apache Unomi Blog description: "The Apache Unomi project launches an official blog — Unomi 3 deep dives, AI/CDP guidance, and community updates." keywords: "Apache Unomi blog, Unomi 3, community" layout: default +date: 2026-06-17 09:10:00 +0000 --- <section class="page-header"> @@ -10,6 +11,12 @@ layout: default <p class="section-label text-hero-label">Blog</p> <h1>Welcome to the Apache Unomi Blog</h1> <p class="lead">Longer-form writing for Unomi 3, AI/CDP builders, and the community.</p> + {% if page.date %} + <p class="small text-muted mt-2 mb-0"> + <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ page.date | date: "%B %d, %Y at %H:%M UTC" }}</time> + </p> + {% endif %} + </div> </section> <section class="section"> diff --git a/src/main/webapp/sitemap.xml b/src/main/webapp/sitemap.xml index e2030eb..baf988f 100644 --- a/src/main/webapp/sitemap.xml +++ b/src/main/webapp/sitemap.xml @@ -245,6 +245,11 @@ <priority>0.75</priority> </url> <url> + <url> + <loc>https://unomi.apache.org/blog/unomi-3-in-numbers.html</loc> + <changefreq>monthly</changefreq> + <priority>0.85</priority> + </url> <loc>https://unomi.apache.org/blog/unomi-3-shell-commands.html</loc> <changefreq>monthly</changefreq> <priority>0.8</priority>
