Salta el contingut

PR507409 — Aplicació web amb gràfics de Metabase incrustats sobre SQL Server

Tipus: Pràctica tècnica individual (JavaScript + Metabase + SQL Server) Durada estimada: 6 hores Lliurament: Campus Virtual — repositori de codi (backend + frontend) + captures de pantalla + document de reflexió


Objectius

Al finalitzar aquesta pràctica, l'alumne serà capaç de:

  1. Desplegar una instància de SQL Server amb Docker i carregar-hi un conjunt de dades relacional propi.
  2. Connectar Metabase a SQL Server i crear preguntes i un dashboard que responguin a objectius de negoci concrets.
  3. Activar i configurar el mode d'incrustació signada (Signed Embedding) de Metabase amb una clau secreta.
  4. Construir una aplicació web amb Node.js/Express i JavaScript que generi tokens JWT signats en un backend i incrusti els gràfics de Metabase com a <iframe> en una pàgina pròpia, sense exposar mai la clau secreta al navegador.
  5. Explicar per què la incrustació signada és necessària quan un dashboard conté dades sensibles o ha de restringir-se per usuari (row-level permissions), a diferència de la incrustació pública.

Materials necessaris

Diferència amb la pràctica PR507407

A PR507407 vas construir un dashboard dins de Power BI. En aquesta pràctica el dashboard viu a Metabase, però l'usuari final el veu incrustat dins d'una aplicació web pròpia que tu construeixes, sense necessitat d'obrir Metabase directament. És el patró que fan servir productes SaaS reals per oferir analítica als seus clients ("embedded analytics").


Descripció de la pràctica

L'objectiu final és una petita aplicació web (una pàgina HTML servida per un backend Express) que mostra un o més gràfics de Metabase incrustats, on les dades provenen d'una base de dades SQL Server que tu mateix aixeques i alimentes.

flowchart LR
    A["SQL Server\nDocker"] --> B["Metabase\nconnexió + dashboard"]
    B -- "Signed Embedding\nJWT" --> C["Backend Express\nsigna el token"]
    C --> D["Frontend\niframe amb el token"]
    D --> E["Usuari final\nnavegador"]

El flux de seguretat és el nucli tècnic de la pràctica: la clau secreta d'incrustació mai viatja al navegador. El backend és qui la coneix, signa un JWT amb l'identificador del dashboard (i, opcionalment, paràmetres de filtratge), i entrega al frontend només la URL final de l'iframe amb el token ja signat.

Part 1 — SQL Server amb Docker (1 hora)

Aixeca un contenidor de SQL Server 2022 amb Docker Compose:

# docker-compose.yml
services:
  sqlserver-nom-cognom:
    image: mcr.microsoft.com/mssql/server:2022-latest
    container_name: sqlserver-nom-cognom
    environment:
      ACCEPT_EULA: "Y"
      MSSQL_SA_PASSWORD: "Bigdata2026!"
      MSSQL_PID: "Developer"
    ports:
      - "1433:1433"
    volumes:
      - sqlserver_data_nom_cognom:/var/opt/mssql
    healthcheck:
      test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$$MSSQL_SA_PASSWORD\" -C -Q 'SELECT 1' || exit 1"]
      interval: 10s
      retries: 10

volumes:
  sqlserver_data_nom_cognom:
docker compose up -d
# Comprova que el contenidor està healthy:
docker compose ps

Connecta't amb el teu client SQL preferit (host localhost, port 1433, usuari sa, contrasenya de l'entorn) i crea una base de dades bigdata_nom_cognom amb almenys dues taules relacionades (per exemple clients i comandes, o productes i vendes) amb un mínim de 500 files en total. Pots reutilitzar i adaptar l'esquema i el generador de dades de la pràctica PR507402, traduint la sintaxi de PostgreSQL a Transact-SQL on calgui (per exemple, IDENTITY(1,1) en lloc de SERIAL).

SA_PASSWORD ha de complir la política de complexitat

SQL Server rebutja contrasenyes febles per a l'usuari sa: com a mínim 8 caràcters, amb majúscules, minúscules, dígits i un símbol. Si el contenidor es reinicia constantment, revisa docker compose logs sqlserver-nom-cognom — sovint l'error és aquest.

Part 2 — Metabase connectat a SQL Server (1 hora)

Amplia el mateix docker-compose.yml amb el servei de Metabase (com a Metabase), assegurant-te que comparteix xarxa amb sqlserver-nom-cognom:

  metabase-nom-cognom:
    image: metabase/metabase:v0.50
    container_name: metabase-nom-cognom
    ports:
      - "3001:3000"
    volumes:
      - metabase_data_nom_cognom:/metabase-data
    environment:
      MB_DB_FILE: /metabase-data/metabase.db
    depends_on:
      sqlserver-nom-cognom:
        condition: service_healthy

A Configuració d'administrador → Bases de dades → Afegeix una base de dades, tria SQL Server i introdueix Host sqlserver-nom-cognom, port 1433, usuari sa i la contrasenya configurada.

Crea, com a mínim, dues preguntes (Question builder o SQL natiu) que responguin a objectius de negoci concrets (per exemple, evolució temporal de vendes i rànquing per categoria) i afegeix-les a un Dashboard nou.

Part 3 — Activar la incrustació signada (Signed Embedding) (1 hora)

A Configuració d'administrador → Configuració → Incrustació (Embedding):

  1. Activa la incrustació (si Metabase la té desactivada per defecte, activa primer "Allow embedding in other applications").
  2. Genera i copia la clau secreta d'incrustació (Embedding secret key): una cadena llarga que no ha d'aparèixer mai al codi del frontend ni en cap fitxer pujat a un repositori públic.
  3. Al teu dashboard, obre Compartir → Incrustar aquest dashboard (Embed this dashboard) i activa-hi la incrustació signada. Tria quins filtres del dashboard vols que siguin blocats (fixats pel backend, no manipulables des del navegador) i quins editables per l'usuari.
  4. Anota l'identificador numèric del dashboard (visible a la URL de Metabase, per exemple .../dashboard/3-...).

La clau secreta és per al backend, mai per al frontend

Si la clau secreta d'incrustació s'inclou en JavaScript que s'executa al navegador (per exemple, en un fitxer .js servit estàticament), qualsevol visitant podria llegir-la i generar tokens propis amb accés il·limitat a totes les dades del dashboard, saltant-se qualsevol restricció de fila o de filtre. Ha de viure únicament en una variable d'entorn del servidor.

Part 4 — Backend Express: signatura del token JWT (1,5 hores)

Crea un projecte Node.js senzill:

mkdir pr507409-nom_cognom && cd pr507409-nom_cognom
npm init -y
npm install express jsonwebtoken dotenv
// server.js
require('dotenv').config();
const express = require('express');
const jwt = require('jsonwebtoken');

const app = express();
const METABASE_SITE_URL = process.env.METABASE_SITE_URL;   // p. ex. http://localhost:3001
const METABASE_SECRET_KEY = process.env.METABASE_SECRET_KEY;
const DASHBOARD_ID = Number(process.env.METABASE_DASHBOARD_ID);

app.use(express.static('public'));

app.get('/api/embed-url', (req, res) => {
  const payload = {
    resource: { dashboard: DASHBOARD_ID },
    params: {},                       // paràmetres blocats, p. ex. { client_id: req.session.clientId }
    exp: Math.round(Date.now() / 1000) + 10 * 60   // el token caduca als 10 minuts
  };

  const token = jwt.sign(payload, METABASE_SECRET_KEY);
  const iframeUrl = `${METABASE_SITE_URL}/embed/dashboard/${token}#bordered=true&titled=true`;

  res.json({ iframeUrl });
});

app.listen(3000, () => console.log('App escoltant a http://localhost:3000'));
# .env (mai el pugis a un repositori públic)
METABASE_SITE_URL=http://localhost:3001
METABASE_SECRET_KEY=la-clau-copiada-de-metabase
METABASE_DASHBOARD_ID=1

Per què el token caduca

El camp exp limita la validesa del token a una finestra curta. Si l'iframe es manté obert més estona que la finestra de caducitat, cal que el frontend torni a demanar un token nou a /api/embed-url — exactament com passa amb qualsevol sessió d'autenticació basada en JWT.

Part 5 — Frontend: incrustar el gràfic (1 hora)

Crea public/index.html, que demana la URL signada al backend i la injecta en un <iframe>:

<!DOCTYPE html>
<html lang="ca">
<head>
  <meta charset="UTF-8">
  <title>Dashboard — nom_cognom</title>
  <style>
    iframe { width: 100%; height: 800px; border: none; }
  </style>
</head>
<body>
  <h1>El meu dashboard de vendes</h1>
  <iframe id="metabase-frame"></iframe>

  <script>
    fetch('/api/embed-url')
      .then(res => res.json())
      .then(data => {
        document.getElementById('metabase-frame').src = data.iframeUrl;
      })
      .catch(err => console.error('Error carregant el dashboard:', err));
  </script>
</body>
</html>

Arrenca el backend (node server.js) i obre http://localhost:3000: el dashboard de Metabase hauria d'aparèixer incrustat dins la teva pròpia pàgina, sense cap barra de navegació ni menú de Metabase.

Part 6 — Filtratge dinàmic (opcional, +0,5 hores d'aprofundiment)

Amplia /api/embed-url perquè el payload.params inclogui un valor blocat derivat d'una petita simulació d'autenticació (per exemple, un ?client=... a la URL que el backend valida abans de generar el token). Comprova que, si canvies el valor blocat, el dashboard mostra només les dades corresponents — demostrant com un mateix dashboard es pot personalitzar per usuari sense que el navegador pugui alterar el filtre.


Lliurament

Puja al Campus Virtual:

Fitxer Contingut Format
pr507409_cognom_nom.zip Codi complet: docker-compose.yml, script/SQL de creació de dades, server.js, public/index.html, .env.example (sense la clau real) .zip
captures_cognom_nom.pdf Captures del dashboard a Metabase i de la mateixa aplicació incrustant-lo PDF
reflexio_cognom_nom.pdf Respostes a les preguntes de reflexió final PDF

Requisits mínims per ser avaluat

  • SQL Server ha de contenir com a mínim dues taules relacionades amb dades reals o realistes.
  • Metabase ha d'estar connectat a SQL Server (no a un altre motor) i el dashboard ha de tenir com a mínim dues visualitzacions.
  • La incrustació ha de ser signada (JWT), no pública ni estàtica sense token.
  • La clau secreta no pot aparèixer en cap fitxer del frontend ni en cap commit del repositori entregat (revisa .gitignore i .env.example).
  • L'aplicació ha de funcionar en local seguint només les instruccions del README.

Consulta la Rúbrica PR507409 per als criteris detallats d'avaluació.


Preguntes de reflexió final

Un cop completada la pràctica, respon per escrit:

  1. Quina diferència hi ha entre la incrustació pública (public embedding) i la incrustació signada (signed embedding) de Metabase? Quan triaries cadascuna?
  2. Per què la clau secreta d'incrustació ha d'estar només al backend? Què podria fer un atacant si l'obtingués?
  3. Quin paper juga el camp exp del JWT? Què passaria si no el poséssim mai?
  4. Com adaptaries server.js perquè cada usuari autenticat vegi només les seves pròpies dades (per exemple, només les vendes de la seva regió), sense canviar res al dashboard de Metabase?
  5. Compara aquesta arquitectura d'"embedded analytics" amb el dashboard de Power BI de la pràctica PR507407: quins avantatges i inconvenients té incrustar Metabase dins una aplicació pròpia enfront de compartir directament un fitxer o enllaç de Power BI?

Pràctica PR507409 | Mòdul M5074 Sistemes de Big Data | Institut Sa Palomera (Blanes) | Curs CEIABD 2026-2027