Repository navigation
Deploy custom bblocks-viewer #421
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy custom bblocks-viewer | |
| on: | |
| workflow_run: | |
| workflows: ["Validate and process Building Blocks"] | |
| types: [completed] | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| pages: write | |
| id-token: write | |
| concurrency: | |
| group: pages | |
| cancel-in-progress: true | |
| jobs: | |
| deploy-viewer: | |
| if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} | |
| # No timeout was set until 2026-10-05; a hung job then reads as a | |
| # random failure rather than a slow build. | |
| timeout-minutes: 15 | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deployment.outputs.page_url }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| - name: Get GitHub Pages URL | |
| id: pages-url | |
| run: | | |
| FALLBACK="https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/" | |
| URL=$(gh api repos/${{ github.repository }}/pages --jq '.html_url // empty' 2>/dev/null) || true | |
| if [ -z "$URL" ]; then | |
| URL="$FALLBACK" | |
| fi | |
| echo "url=$URL" >> "$GITHUB_OUTPUT" | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| - name: Read repo title from bblocks-config.yaml | |
| id: config | |
| run: | | |
| TITLE=$(grep '^name:' bblocks-config.yaml | sed 's/^name: *//') | |
| echo "title=$TITLE" >> "$GITHUB_OUTPUT" | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Augment register.json with resolvedSchema URLs | |
| run: python tools/augment_register.py | |
| - name: Generate custom validation report | |
| run: python tools/generate_custom_report.py --input build/tests/report.json --output build/tests/report.html | |
| - name: Generate config.js, landing page (index.html), and bblocks-viewer.html | |
| run: | | |
| PAGES_URL="${{ steps.pages-url.outputs.url }}" | |
| TITLE="${{ steps.config.outputs.title }}" | |
| BASE_PATH=$(echo "$PAGES_URL" | sed 's|https\?://[^/]*||') | |
| cat > config.js << EOF | |
| window.bblocksRegister = '${PAGES_URL}build/register.json'; | |
| window.bblocksViewer = { | |
| title: "${TITLE}", | |
| showImported: 0, | |
| baseUrl: '${BASE_PATH}viewer/', | |
| }; | |
| EOF | |
| # ---- viewer/index.html (the JSON Schema BB browser) ---- | |
| # Served at a directory path so the SPA router base ('${BASE_PATH}viewer/') resolves | |
| # to "/". Keeps the nav banner for navigating to the landing page or the UML browser. | |
| mkdir -p viewer | |
| cat > viewer/index.html << EOF | |
| <!DOCTYPE html> | |
| <!-- CDIF building blocks viewer - cache-refresh 2026-05-22 --> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <script> | |
| // SPA deep-link restore (pairs with the redirect in the site 404.html): | |
| // rebuild /viewer/<route> from the encoded ?/<route> form before the app boots. | |
| (function (l) { | |
| if (l.search && l.search.charAt(1) === '/') { | |
| var decoded = l.search.slice(1).split('&').map(function (s) { | |
| return s.replace(/~and~/g, '&'); | |
| }).join('?'); | |
| window.history.replaceState(null, null, l.pathname.slice(0, -1) + decoded + l.hash); | |
| } | |
| })(window.location); | |
| </script> | |
| <link rel="icon" href="https://cross-domain-interoperability-framework.github.io/bblocks-viewer/favicon.ico" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>${TITLE} — JSON Schema BB Viewer</title> | |
| <script src="${PAGES_URL}config.js"></script> | |
| <script type="module" crossorigin src="https://cross-domain-interoperability-framework.github.io/bblocks-viewer/assets/index.js"></script> | |
| <link rel="stylesheet" href="https://cross-domain-interoperability-framework.github.io/bblocks-viewer/assets/index.css"> | |
| <style> | |
| .cdif-nav-banner { | |
| position: fixed; top: 0; left: 0; right: 0; z-index: 9999; | |
| background: #2c3e50; color: white; | |
| padding: 6px 14px; font: 13px -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.15); | |
| display: flex; justify-content: space-between; align-items: center; | |
| } | |
| .cdif-nav-banner a { color: #cfe2f3; text-decoration: none; margin-left: 14px; } | |
| .cdif-nav-banner a:hover { color: white; text-decoration: underline; } | |
| body { padding-top: 32px; } | |
| </style> | |
| </head> | |
| <body> | |
| <nav class="cdif-nav-banner" aria-label="CDIF site navigation"> | |
| <span> | |
| <a href="${PAGES_URL}" title="Repo landing page">← Landing</a> | |
| <strong style="margin-left:14px;">JSON Schema Building Blocks Viewer</strong> | |
| </span> | |
| <span> | |
| <a href="${PAGES_URL}cdif-uml-model/index.html" title="Per-profile UML model browser with class definitions, attributes, associations, and embedded diagrams">UML profile model browser →</a> | |
| </span> | |
| </nav> | |
| <div id="app"></div> | |
| </body> | |
| </html> | |
| EOF | |
| # ---- bblocks-viewer.html: redirect stub so the old URL still resolves ---- | |
| cat > bblocks-viewer.html << 'REDIR' | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta http-equiv="refresh" content="0; url=viewer/" /> | |
| <title>Redirecting</title> | |
| </head> | |
| <body>Redirecting to the <a href="viewer/">CDIF building blocks viewer</a>.</body> | |
| </html> | |
| REDIR | |
| # ---- index.html (landing page at repo root URL) ---- | |
| cat > index.html << EOF | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>${TITLE}</title> | |
| <script> | |
| // SPA deep-link support: a /viewer/<route> URL that 404s lands on this page | |
| // (the site 404.html is a copy of this landing); bounce it into the viewer | |
| // with the route encoded so viewer/index.html can restore it client-side. | |
| (function (l) { | |
| var vbase = '${BASE_PATH}viewer/'; | |
| if (l.pathname.indexOf(vbase) === 0 && l.pathname.length > vbase.length) { | |
| var rest = l.pathname.slice(vbase.length); | |
| l.replace( | |
| l.protocol + '//' + l.host + vbase + '?/' + | |
| rest.replace(/&/g, '~and~') + | |
| (l.search ? '&' + l.search.slice(1).replace(/&/g, '~and~') : '') + | |
| l.hash | |
| ); | |
| } | |
| })(window.location); | |
| </script> | |
| <link rel="icon" href="https://cross-domain-interoperability-framework.github.io/bblocks-viewer/favicon.ico" /> | |
| <style> | |
| * { box-sizing: border-box; } | |
| body { font-family: -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; | |
| margin: 0; padding: 0; color: #222; background: #fafafa; line-height: 1.5; } | |
| header { background: #2c3e50; color: white; padding: 1.5rem 2rem; } | |
| header h1 { margin: 0; font-size: 1.8rem; font-weight: 500; } | |
| header p.subtitle { margin: 0.4rem 0 0; opacity: 0.85; font-size: 1rem; } | |
| main { max-width: 1000px; margin: 2rem auto; padding: 0 2rem 3rem; } | |
| h2 { color: #2c3e50; border-bottom: 1px solid #ddd; padding-bottom: 0.3rem; margin-top: 2.5rem; } | |
| .views { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-top: 1rem; } | |
| .view-card { display: block; background: white; border: 1px solid #ddd; border-radius: 6px; | |
| padding: 1.5rem; text-decoration: none; color: inherit; | |
| transition: box-shadow 0.15s, border-color 0.15s; } | |
| .view-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.08); border-color: #2c3e50; } | |
| .view-card h3 { margin: 0 0 0.5rem 0; color: #2c3e50; font-size: 1.2rem; } | |
| .view-card p { margin: 0; color: #555; } | |
| .resources li { margin-bottom: 0.4rem; } | |
| code { background: #f0f0f0; padding: 0.1rem 0.4rem; border-radius: 3px; font-size: 0.9em; } | |
| a { color: #06c; } | |
| a:hover { text-decoration: underline; } | |
| footer { margin-top: 3rem; color: #666; font-size: 0.85rem; | |
| border-top: 1px solid #eee; padding-top: 1rem; } | |
| @media (max-width: 700px) { .views { grid-template-columns: 1fr; } } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>CDIF Metadata Building Blocks</h1> | |
| <p class="subtitle">Modular JSON Schema components and UML profile models for the | |
| <a style="color:#cfe2f3;" href="https://cdif.org">Cross-Domain Interoperability Framework</a></p> | |
| </header> | |
| <main> | |
| <p>This repository hosts the <strong>shared core building blocks</strong> for CDIF: modular | |
| JSON Schema components for schema.org properties, CDIF-specific extensions, PROV-O provenance, | |
| W3C SKOS controlled vocabularies, and the DDI-CDI structural family. Building blocks compose | |
| into profiles that define complete metadata schemas for specific use cases (Discovery, Data | |
| Description, Data Structure, Codelist, Complete, XAS). The pattern follows the | |
| <a href="https://opengeospatial.github.io/bblocks/">OGC Building Blocks</a> conventions.</p> | |
| <h2>Two views</h2> | |
| <div class="views"> | |
| <a class="view-card" href="${PAGES_URL}viewer/"> | |
| <h3>JSON Schema Building Blocks Viewer →</h3> | |
| <p>Browse the <strong>JSON Schema</strong> implementation of every building block and | |
| profile. Filter by category; drill into resolved schemas, JSON-LD contexts, SHACL rules, | |
| and example instances. Backed by the OGC bblocks postprocessor.</p> | |
| </a> | |
| <a class="view-card" href="${PAGES_URL}cdif-uml-model/index.html"> | |
| <h3>UML Profile Model Browser →</h3> | |
| <p>Browse the <strong>UML class model</strong> per CDIF profile: class definitions, | |
| attribute and association tables, inheritance, embedded PlantUML diagrams, and | |
| cross-profile links. Derived from the DDI-CDI canonical UML via the | |
| <em>cdif-ucmism2m</em> configurations (see Related repositories below).</p> | |
| </a> | |
| </div> | |
| <h2>What's in the repo</h2> | |
| <p><strong>Building block categories</strong> under <code>_sources/</code>:</p> | |
| <ul> | |
| <li><code>schemaorgProperties/</code> — schema.org vocabulary (person, organization, identifier, definedTerm, instrument, ...)</li> | |
| <li><code>cdifProperties/</code> — CDIF-specific properties (core, provenance, instance variables, data structures, OpenAPI WebAPI, cdif:Reference, ...)</li> | |
| <li><code>skosProperties/</code> — W3C SKOS controlled vocabulary (Concept, ConceptScheme, Collection)</li> | |
| <li><code>provProperties/</code> — PROV-O provenance (generatedBy, derivedFrom, provActivity)</li> | |
| <li><code>ddiProperties/</code> — DDI-CDI vocabulary building blocks</li> | |
| <li><code>ddiCDIFProperties/</code> — DDI-CDI CDIF-flavored extensions</li> | |
| <li><code>qualityProperties/</code> — DQV data quality measures</li> | |
| <li><code>xasProperties/</code> — X-ray Absorption Spectroscopy domain</li> | |
| <li><code>profiles/cdifProfiles/</code> — assembled CDIF profiles</li> | |
| </ul> | |
| <p><strong>Tooling</strong> under <code>tools/</code>: <code>resolve_schema.py</code> (modular YAML → resolvedSchema.json), | |
| <code>convert_for_jsonforms.py</code> (resolved → JSON Forms), | |
| <code>generate_custom_report.py</code> (SHACL severity report).</p> | |
| <h2>Machine-readable resources</h2> | |
| <ul class="resources"> | |
| <li><a href="${PAGES_URL}build/register.json">register.json</a> — master registry of all building blocks and profiles</li> | |
| <li><a href="${PAGES_URL}build/bblocks.jsonld">bblocks.jsonld</a> — register in JSON-LD</li> | |
| <li><a href="${PAGES_URL}build/bblocks.ttl">bblocks.ttl</a> — register in RDF/Turtle</li> | |
| <li><a href="${PAGES_URL}build/generateddocs/markdown/">build/generateddocs/markdown/</a> — per-BB markdown documentation</li> | |
| <li><a href="${PAGES_URL}build/tests/report.html">build/tests/report.html</a> — validation report (JSON Schema + SHACL)</li> | |
| <li><a href="${PAGES_URL}cdif-uml-model/_registry.json">cdif-uml-model/_registry.json</a> — cross-profile class name → profile registry (used by the UML browser)</li> | |
| </ul> | |
| <h2>Persistent URIs</h2> | |
| <p>Every building block has a stable identifier under | |
| <code>https://w3id.org/cdif/bbr/metadata/{category}/{name}</code> with content negotiation | |
| (HTML landing page, JSON Schema, SHACL Turtle, JSON-LD context). See the | |
| <a href="https://github.com/Cross-Domain-Interoperability-Framework/metadataBuildingBlocks#building-block-identifiers-and-web-resolution">README</a> | |
| for the full URI scheme and Accept-header table.</p> | |
| <h2>Related repositories</h2> | |
| <ul> | |
| <li><a href="https://github.com/Cross-Domain-Interoperability-Framework/metadataBuildingBlocks">metadataBuildingBlocks</a> — source repo (you are here)</li> | |
| <li><a href="https://github.com/Cross-Domain-Interoperability-Framework/cdif-ucmism2m">cdif-ucmism2m</a> — JSON-config-driven UML transformations that produce the per-profile UML used by the model browser</li> | |
| <li><a href="https://github.com/usgin/ddeBuildingBlocks">ddeBuildingBlocks</a> — DDE (Deep-time Digital Earth) geoscience properties (7 BBs + 11 profiles)</li> | |
| <li><a href="https://github.com/usgin/geochemBuildingBlocks">geochemBuildingBlocks</a> — IEDA ADA geochemistry (30 BBs + 36 profiles)</li> | |
| <li><a href="https://github.com/usgin/ecrrBuildingBlocks">ecrrBuildingBlocks</a> — EarthCube Resource Registry (10 BBs + 11 profiles)</li> | |
| </ul> | |
| <footer> | |
| Built with NSF support (awards 2012893, 2012748, 2012593). | |
| Implementation notes in | |
| <a href="https://github.com/Cross-Domain-Interoperability-Framework/metadataBuildingBlocks#readme">README</a> | |
| and <a href="https://github.com/Cross-Domain-Interoperability-Framework/cdif-ucmism2m/blob/main/workflowDescription.md">workflowDescription.md</a>. | |
| </footer> | |
| </main> | |
| </body> | |
| </html> | |
| EOF | |
| # Use the landing page as the 404 target so any unknown URL drops the visitor on the hub. | |
| cp index.html 404.html | |
| - name: Setup Pages | |
| uses: actions/configure-pages@v5 | |
| - name: Upload artifact | |
| uses: actions/upload-pages-artifact@v3 | |
| with: | |
| path: '.' | |
| # The OGC postprocessor ("Validate and process Building Blocks") deploys Pages first; | |
| # this workflow re-deploys to override with the fork's viewer. GitHub Pages allows only | |
| # one in-progress deployment, so if the postprocessor's deployment is still finalizing | |
| # the create call fails with "in progress deployment". Retry a few times with waits so | |
| # the cross-workflow race recovers automatically instead of failing the run. | |
| - name: Deploy to GitHub Pages | |
| id: deployment | |
| continue-on-error: true | |
| uses: actions/deploy-pages@v4 | |
| - name: Wait for in-progress Pages deployment to clear (before retry 1) | |
| if: steps.deployment.outcome == 'failure' | |
| run: sleep 60 | |
| - name: Deploy to GitHub Pages (retry 1) | |
| id: deployment_retry1 | |
| if: steps.deployment.outcome == 'failure' | |
| continue-on-error: true | |
| uses: actions/deploy-pages@v4 | |
| - name: Wait for in-progress Pages deployment to clear (before retry 2) | |
| if: steps.deployment.outcome == 'failure' && steps.deployment_retry1.outcome == 'failure' | |
| run: sleep 150 | |
| - name: Deploy to GitHub Pages (retry 2) | |
| id: deployment_retry2 | |
| if: steps.deployment.outcome == 'failure' && steps.deployment_retry1.outcome == 'failure' | |
| uses: actions/deploy-pages@v4 |