From 3bfbe98b3c2dc7a04570e6bb09e0092515a5d50f Mon Sep 17 00:00:00 2001 From: Timofei Kachalov Date: Tue, 8 Sep 2026 13:47:00 +0400 Subject: [PATCH 1/5] Restore back local-dist flow and e2e --- .env.docker.example | 25 +++++++++- .github/workflows/e2e-tests.yml | 26 ++++++----- .gitignore | 9 ++++ Dockerfile | 7 +++ README.md | 27 +++++++---- api/src/constants/env.ts | 3 -- docker-compose.local-dist.yml | 9 ++++ docker-compose.yml | 75 ++++++++++++++++++++++++++++-- e2e/package.json | 3 +- e2e/support/helpers/sign-in.js | 59 +++++++++++++++++++++++ e2e/test/compatibility.cy.spec.js | 46 ++++++++++++++++++ local-dist/.gitkeep | 0 metabase/config.yml | 36 ++++++++++++++ metabase/entrypoint.sh | 26 +++++++++++ metabase/export_metabase_data.sh | 27 +++++++++++ metabase/metabase_data.tar.gz | Bin 0 -> 15275 bytes package.json | 1 + 17 files changed, 347 insertions(+), 32 deletions(-) create mode 100644 docker-compose.local-dist.yml create mode 100644 e2e/support/helpers/sign-in.js create mode 100644 local-dist/.gitkeep create mode 100644 metabase/config.yml create mode 100644 metabase/entrypoint.sh create mode 100755 metabase/export_metabase_data.sh create mode 100644 metabase/metabase_data.tar.gz diff --git a/.env.docker.example b/.env.docker.example index 37dc8f99..4bf3efda 100644 --- a/.env.docker.example +++ b/.env.docker.example @@ -1,8 +1,18 @@ +MB_PORT=4300 CLIENT_PORT=4400 API_PORT=4500 -MB_INSTANCE_URL="https://shoppy.metabaseapp.com" -METABASE_JWT_SHARED_SECRET="your_secret_here" +PREMIUM_EMBEDDING_TOKEN="" +METABASE_JWT_SHARED_SECRET="ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + +METABASE_ADMIN_EMAIL: "admin@example.com" +METABASE_ADMIN_PASSWORD="foobarbaz" + +METABASE_APP_DB_HOST: "shoppy_app_db" +METABASE_APP_DB_PORT: "5432" +METABASE_APP_DB: "shoppy_app" +METABASE_APP_DB_USER: "shoppy_app" +METABASE_APP_DB_PASSWORD: "foobarbaz" SHOPPY_DB_HOST="shoppy_db" SHOPPY_DB_PORT="5432" @@ -11,3 +21,14 @@ SHOPPY_DB_USER="shoppy" SHOPPY_DB_PASSWORD="foobarbaz" WATCH="false" + +# for development +# MB_RUN_MODE="dev" +# METASTORE_DEV_SERVER_URL="" + +# Datadog RUM (optional client-side monitoring; leave empty to disable). +DATADOG_APPLICATION_ID="" +DATADOG_CLIENT_TOKEN="" +DATADOG_SITE="" +DATADOG_SERVICE="" +DATADOG_ENV="" diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 85def127..537bd00f 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -14,20 +14,29 @@ jobs: runs-on: ubuntu-22.04 timeout-minutes: 15 name: e2e-tests + env: + # The local Metabase runs EE in dev mode and validates its token against the metastore. + PREMIUM_EMBEDDING_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }} + MB_RUN_MODE: dev + METASTORE_DEV_SERVER_URL: ${{ secrets.METASTORE_DEV_SERVER_URL }} permissions: contents: read steps: - uses: actions/checkout@v4 - - name: Prepare env + - name: Run Shoppy in Docker (local Metabase seeded from serialized data) run: | cp .env.docker.example .env.docker - # The api signs SSO JWTs that production Metabase must trust, so use the production secret. - sed -i "s|^METABASE_JWT_SHARED_SECRET=.*|METABASE_JWT_SHARED_SECRET=\"${{ secrets.SHOPPY_PROD_JWT_SHARED_SECRET }}\"|" .env.docker + yarn docker:e2e:up --wait - - name: Run Shoppy (client + api + warehouse) against production Metabase - run: yarn docker:e2e:up --wait + - name: Dump container logs on failure + if: failure() + run: | + echo "=== shoppy-metabase-1 ===" && docker logs shoppy-metabase-1 2>&1 || true + echo "=== shoppy-api-1 ===" && docker logs shoppy-api-1 2>&1 || true + echo "=== shoppy-client-1 ===" && docker logs shoppy-client-1 2>&1 || true + echo "=== container statuses ===" && docker ps -a 2>&1 || true - name: Install Chrome v111 uses: browser-actions/setup-chrome@v1 @@ -43,13 +52,6 @@ jobs: id: run-e2e-tests run: cd e2e && yarn cypress:run - - name: Dump container logs on failure - if: failure() - run: | - echo "=== shoppy-client-1 ===" && docker logs shoppy-client-1 2>&1 || true - echo "=== shoppy-api-1 ===" && docker logs shoppy-api-1 2>&1 || true - echo "=== container statuses ===" && docker ps -a 2>&1 || true - - name: Upload Cypress Artifacts upon failure uses: actions/upload-artifact@v4 if: ${{ steps.run-e2e-tests.outcome != 'success' }} diff --git a/.gitignore b/.gitignore index ec1940d1..34ed61fc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,10 @@ node_modules .pnp .pnp.js +# local dist +local-dist/* +!local-dist/.gitkeep + # testing coverage cypress @@ -13,6 +17,11 @@ cypress dist build +# db dumps +*.sql +# but allow migration files +!api/drizzle/**/*.sql + # misc .DS_Store .env.development.local diff --git a/Dockerfile b/Dockerfile index 874fdab8..601f6b22 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,6 +39,13 @@ RUN yarn --frozen-lockfile # Copy source code last (changes most frequently) COPY --exclude=./api --exclude=./metabase . . +RUN if [ -d "./local-dist/embedding-sdk" ]; then \ + echo "Local embedding-sdk dist is found in ./local-dist/embedding-sdk, installing it..."; \ + yarn add file:./local-dist/embedding-sdk; \ + else \ + echo "Local embedding-sdk dist is not found in ./local-dist/embedding-sdk, skipping copy"; \ + fi + RUN if [ "$WATCH" != "true" ]; then \ echo "WATCH env is not set; running production yarn build..."; \ yarn build; \ diff --git a/README.md b/README.md index f079554e..2e905c76 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,22 @@ This demo uses the data from the hosted Metabase Cloud instance and provides a h ### Using Docker -- Clone `.env.docker.example` to `.env.docker`. Set `MB_INSTANCE_URL` to your Metabase instance and `METABASE_JWT_SHARED_SECRET` to that instance's JWT shared secret. -- Run `yarn docker:up` for a production build, or `WATCH=true yarn docker:up` for a dev build with watch. - - The command launches the Shoppy DWH, API and Client, pointing at the configured Metabase instance. +- Clone `.env.docker.example` to `.env.docker` and set the proper `PREMIUM_EMBEDDING_TOKEN` value. +- Run Docker via `yarn docker:up` for the `production` build or `WATCH=true yarn docker:up` for the development build with the `watch` support. + - The command launches a local MB instance, the Shoppy DWH, Shoppy API and Shoppy Client. The local Metabase is seeded from the committed serialized data (`./metabase/metabase_data.tar.gz`), so no database dump is needed. - Visit `http://localhost:4400`. - To stop containers run `yarn docker:down`. - To remove containers and images completely run `yarn docker:rm`. +#### Local development (For Metabase developers) + +To run the containers with a locally built `metabase.jar` and/or a locally built Embedding SDK: + +- Copy a locally built `metabase.jar` to `./local-dist/metabase.jar`. Without it, the `metabase.jar` from the Docker image is used. +- Copy a locally built Embedding SDK package to `./local-dist/embedding-sdk`. Without it, the `@metabase/embedding-sdk-react` version from `package.json` is used. +- Run `yarn docker:local-dist:up` to start the containers using the local dist from `./local-dist`. +- To remove containers and images completely run `yarn docker:rm`. + ### Using an existing running MB instance - Place the metabase repository in `../metabase` @@ -65,11 +74,11 @@ If you cannot use the hosted JWT server, you can run the JWT server locally. ### Running e2e tests (For Metabase developers) -The e2e tests run the client, api and warehouse locally (via Docker) against the production Metabase instance, and drive the local client with Cypress. +The e2e tests run the local Shoppy stack (a local Metabase seeded from the committed serialized data) and drive it with Cypress. -- In `.env.docker`, set `METABASE_JWT_SHARED_SECRET` to the production instance's JWT shared secret (from 1Password) so the api's SSO tokens are trusted, and `MB_INSTANCE_URL` to that instance. -- Start the stack: `yarn docker:e2e:up --wait`. -- Run the tests: `cd e2e && yarn cypress:run` (headless) or `yarn cypress:open` (Cypress UI). -- Stop the stack: `yarn docker:down`. +- Clone `.env.docker.example` to `.env.docker` and set the proper `PREMIUM_EMBEDDING_TOKEN` value. +- Run `yarn docker:e2e:up --wait` to start all required containers. +- Run the tests: `cd e2e && yarn cypress:run` (headless) or `cd e2e && yarn cypress:open` (Cypress UI). +- To stop containers run `yarn docker:rm`. -CI runs the same suite in `.github/workflows/e2e-tests.yml`, injecting the production secret from the `SHOPPY_PROD_JWT_SHARED_SECRET` GitHub secret. +CI runs the same suite in `.github/workflows/e2e-tests.yml`. diff --git a/api/src/constants/env.ts b/api/src/constants/env.ts index 9b2cce9d..b1e35973 100644 --- a/api/src/constants/env.ts +++ b/api/src/constants/env.ts @@ -14,6 +14,3 @@ export const FRONTEND_URL = process.env.FRONTEND_URL ?? `http://localhost:${process.env.CLIENT_PORT}` export const { VERCEL_ENV } = process.env - -// This variable is used in a local (Docker) environment only -export const METABASE_ADMIN_EMAIL = process.env.METABASE_ADMIN_EMAIL diff --git a/docker-compose.local-dist.yml b/docker-compose.local-dist.yml new file mode 100644 index 00000000..7b24a77f --- /dev/null +++ b/docker-compose.local-dist.yml @@ -0,0 +1,9 @@ +services: + metabase: + volumes: + - type: bind + source: ./local-dist/metabase.jar + target: /app/metabase.jar + read_only: true + bind: + create_host_path: false diff --git a/docker-compose.yml b/docker-compose.yml index 55143a10..4c05883c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -# Shoppy stack: client + api + warehouse. Metabase is the production instance (MB_INSTANCE_URL); -# the client talks to it same-origin via `/mb`, which the api proxies onward (see api/src/main.ts). services: shoppy_db: image: postgres:latest @@ -16,8 +14,25 @@ services: expose: - "${SHOPPY_DB_PORT}" + shoppy_app_db: + image: postgres:latest + environment: + POSTGRES_PORT: "${METABASE_APP_DB_PORT}" + POSTGRES_DB: "${METABASE_APP_DB}" + POSTGRES_USER: "${METABASE_APP_DB_USER}" + POSTGRES_PASSWORD: "${METABASE_APP_DB_PASSWORD}" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${METABASE_APP_DB_USER}"] + interval: 10s + timeout: 5s + retries: 5 + expose: + - "${METABASE_APP_DB_PORT}" + api: depends_on: + shoppy_app_db: + condition: service_healthy shoppy_db: condition: service_healthy build: @@ -30,8 +45,9 @@ services: CLIENT_PORT: "${CLIENT_PORT}" IS_LOCAL_DB: "true" DB_URL: "postgres://${SHOPPY_DB_USER}:${SHOPPY_DB_PASSWORD}@${SHOPPY_DB_HOST}:${SHOPPY_DB_PORT}/${SHOPPY_DB}" - METABASE_INSTANCE_URL: "${MB_INSTANCE_URL}" + METABASE_INSTANCE_URL: "http://metabase:${MB_PORT}" METABASE_JWT_SHARED_SECRET: "${METABASE_JWT_SHARED_SECRET}" + METABASE_ADMIN_EMAIL: "${METABASE_ADMIN_EMAIL}" healthcheck: test: curl --fail -X GET -I "http://localhost:${API_PORT}/" || exit 1 interval: 15s @@ -40,15 +56,64 @@ services: ports: - "${API_PORT}:${API_PORT}" + metabase: + depends_on: + shoppy_app_db: + condition: service_healthy + shoppy_db: + condition: service_healthy + api: + condition: service_healthy + image: metabase/metabase-enterprise:v1.63.x + entrypoint: ["sh", "/app/entrypoint.sh"] + environment: + MB_CONFIG_FILE_PATH: "./app/config.yml" + MB_JETTY_PORT: "${MB_PORT}" + MB_EDITION: "ee" + MB_SITE_URL: "http://localhost:${MB_PORT}/" + MB_PREMIUM_EMBEDDING_TOKEN: "${PREMIUM_EMBEDDING_TOKEN}" + MB_RUN_MODE: "${MB_RUN_MODE}" + METASTORE_DEV_SERVER_URL: "${METASTORE_DEV_SERVER_URL}" + MB_JWT_IDENTITY_PROVIDER_URI: "http://localhost:${API_PORT}/sso/metabase" + MB_JWT_SHARED_SECRET: "${METABASE_JWT_SHARED_SECRET}" + MB_SETUP_TOKEN: "${PREMIUM_EMBEDDING_TOKEN}" + MB_WAREHOUSE_ALLOWED_NETWORKS: "allow-all" + MB_DB_TYPE: "postgres" + MB_DB_HOST: "${METABASE_APP_DB_HOST}" + MB_DB_PORT: "${METABASE_APP_DB_PORT}" + MB_DB_USER: "${METABASE_APP_DB_USER}" + MB_DB_PASS: "${METABASE_APP_DB_PASSWORD}" + MB_DB_DBNAME: "${METABASE_APP_DB}" + METABASE_ADMIN_EMAIL: "${METABASE_ADMIN_EMAIL}" + METABASE_ADMIN_PASSWORD: "${METABASE_ADMIN_PASSWORD}" + SHOPPY_DB_HOST: "${SHOPPY_DB_HOST}" + SHOPPY_DB_PORT: "${SHOPPY_DB_PORT}" + SHOPPY_DB: "${SHOPPY_DB}" + SHOPPY_DB_USER: "${SHOPPY_DB_USER}" + SHOPPY_DB_PASSWORD: "${SHOPPY_DB_PASSWORD}" + healthcheck: + test: curl --fail -X GET -I "http://localhost:${MB_PORT}/api/health" || exit 1 + interval: 15s + timeout: 5s + retries: 10 + ports: + - "${MB_PORT}:${MB_PORT}" + volumes: + - ./metabase/entrypoint.sh:/app/entrypoint.sh + - ./metabase/config.yml:/app/config.yml + - ./metabase/metabase_data.tar.gz:/app/metabase_data.tar.gz + client: depends_on: + metabase: + condition: service_healthy api: condition: service_healthy build: context: . dockerfile: Dockerfile args: - VITE_APP_METABASE_INSTANCE_URL: "/mb" + VITE_APP_METABASE_INSTANCE_URL: "http://localhost:${MB_PORT}" VITE_APP_BACKEND_URL: "http://localhost:${API_PORT}" VITE_DATADOG_APPLICATION_ID: "${DATADOG_APPLICATION_ID}" VITE_DATADOG_CLIENT_TOKEN: "${DATADOG_CLIENT_TOKEN}" @@ -58,7 +123,7 @@ services: WATCH: "${WATCH}" environment: CLIENT_PORT: "${CLIENT_PORT}" - VITE_APP_METABASE_INSTANCE_URL: "/mb" + VITE_APP_METABASE_INSTANCE_URL: "http://localhost:${MB_PORT}" VITE_APP_BACKEND_URL: "http://localhost:${API_PORT}" VITE_APP_DOCKER_OVERRIDE_BACKEND_URL: "http://api:${API_PORT}" WATCH: "${WATCH}" diff --git a/e2e/package.json b/e2e/package.json index e66e138e..5a2f42ee 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -9,6 +9,7 @@ }, "scripts": { "cypress:open": "env-cmd -f ../.env.docker cypress open --config-file ./support/cypress.config.js --e2e", - "cypress:run": "env-cmd -f ../.env.docker cypress run --config-file ./support/cypress.config.js" + "cypress:run": "env-cmd -f ../.env.docker cypress run --config-file ./support/cypress.config.js", + "cypress:synthetic": "cypress run --config-file ./support/cypress.synthetic.config.js --browser chrome" } } diff --git a/e2e/support/helpers/sign-in.js b/e2e/support/helpers/sign-in.js new file mode 100644 index 00000000..e4cb8a67 --- /dev/null +++ b/e2e/support/helpers/sign-in.js @@ -0,0 +1,59 @@ +// TODO (Kelvin 2026-07-07) bandage, not a fix (EMB-2059) — see compatibility.cy.spec.js. + +export const METABASE_URL = "http://localhost:4300" + +// JWT_SHARED_SECRET also appears in .env.docker.example, so it's not a real secret. +const JWT_SHARED_SECRET = + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +const ADMIN_EMAIL = "shoppy@metabase.com" + +function base64url(bytes) { + let binary = "" + new Uint8Array(bytes).forEach((byte) => { + binary += String.fromCharCode(byte) + }) + return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "") +} + +async function getSignedJwtForAdmin() { + const encoder = new TextEncoder() + const header = { alg: "HS256", typ: "JWT" } + const payload = { + email: ADMIN_EMAIL, + first_name: "Shoppy", + last_name: "Admin", + exp: Math.floor(Date.now() / 1000) + 600, + } + const signingInput = `${base64url(encoder.encode(JSON.stringify(header)))}.${base64url( + encoder.encode(JSON.stringify(payload)), + )}` + + const key = await crypto.subtle.importKey( + "raw", + encoder.encode(JWT_SHARED_SECRET), + { name: "HMAC", hash: "SHA-256" }, + false, + ["sign"], + ) + const signature = await crypto.subtle.sign( + "HMAC", + key, + encoder.encode(signingInput), + ) + + return `${signingInput}.${base64url(signature)}` +} + +export function signInAsAdmin() { + return cy + .wrap(getSignedJwtForAdmin()) + .then((jwt) => + cy.request({ + method: "GET", + url: `${METABASE_URL}/auth/sso`, + qs: { jwt }, + headers: { "X-Metabase-Client": "embedding-sdk-react" }, + }), + ) + .then(({ body }) => body.id) +} diff --git a/e2e/test/compatibility.cy.spec.js b/e2e/test/compatibility.cy.spec.js index 7bf6214d..206a0762 100644 --- a/e2e/test/compatibility.cy.spec.js +++ b/e2e/test/compatibility.cy.spec.js @@ -1,3 +1,5 @@ +import { METABASE_URL, signInAsAdmin } from "../support/helpers/sign-in" + const TIMEOUT = 20000 describe("Embedding SDK: shoppy compatibility", () => { @@ -169,7 +171,51 @@ describe("Embedding SDK: shoppy compatibility", () => { }) }) + // TODO (Kelvin 2026-07-07) bandage, not a fix. Metabase's appdb search-index reindex has a + // race that can strand a fully-built index as "pending" instead of activating it. Couldn't + // land the real backend fix yet; this forces a reindex and waits for it first. The actual + // backend bug (QUE2-736) hasn't landed yet. + // Scoped to just this test, not the whole suite — it's the only one that needs it. describe("data picker", () => { + beforeAll(() => { + const REINDEX_POLL_INTERVAL_MS = 1000 + const REINDEX_POLL_MAX_ATTEMPTS = 60 + + function waitForDatasetIndex(sessionId, attempt = 0) { + cy.request({ + method: "GET", + url: `${METABASE_URL}/api/search?models=dataset`, + headers: { "X-Metabase-Session": sessionId }, + }).then(({ body }) => { + if (body.total > 0) { + return + } + if (attempt >= REINDEX_POLL_MAX_ATTEMPTS) { + // Fail loudly here instead of silently proceeding into a doomed test — a confusing + // "can't find Orders" UI assertion failure downstream is much harder to diagnose than + // this being the actual problem. + throw new Error( + `waitForDatasetIndex: no datasets found after ${attempt} attempts`, + ) + } + + cy.wait(REINDEX_POLL_INTERVAL_MS) + waitForDatasetIndex(sessionId, attempt + 1) + }) + } + + signInAsAdmin().then((sessionId) => { + cy.request({ + method: "POST", + url: `${METABASE_URL}/api/search/force-reindex`, + headers: { "X-Metabase-Session": sessionId }, + failOnStatusCode: false, + }) + + waitForDatasetIndex(sessionId) + }) + }) + it("should not display tables in the data picker", () => { cy.visit({ url: "/admin/analytics/new/from-scratch", diff --git a/local-dist/.gitkeep b/local-dist/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/metabase/config.yml b/metabase/config.yml new file mode 100644 index 00000000..1dd2c826 --- /dev/null +++ b/metabase/config.yml @@ -0,0 +1,36 @@ +version: 1 +config: + users: + - first_name: "Admin" + last_name: "Admin" + password: "{{ env METABASE_ADMIN_PASSWORD }}" + email: "{{ env METABASE_ADMIN_EMAIL }}" + is_superuser: true + - first_name: "Rene" + last_name: "Mueller" + password: "{{ env METABASE_ADMIN_PASSWORD }}" + email: "rene@example.com" + - first_name: "Cecilia" + last_name: "Stark" + password: "{{ env METABASE_ADMIN_PASSWORD }}" + email: "cecilia@example.com" + - first_name: "Emily" + last_name: "Johnson" + password: "{{ env METABASE_ADMIN_PASSWORD }}" + email: "emily@example.com" + - first_name: "Jennifer" + last_name: "Martinez" + password: "{{ env METABASE_ADMIN_PASSWORD }}" + email: "jennifer@example.com" + settings: + enable-embedding-sdk: true + jwt-enabled: true + databases: + - name: Customer Zero Database + engine: postgres + details: + host: "{{ env SHOPPY_DB_HOST }}" + port: "{{ env SHOPPY_DB_PORT }}" + dbname: "{{ env SHOPPY_DB }}" + user: "{{ env SHOPPY_DB_USER }}" + password: "{{ env SHOPPY_DB_PASSWORD }}" diff --git a/metabase/entrypoint.sh b/metabase/entrypoint.sh new file mode 100644 index 00000000..a5564b2b --- /dev/null +++ b/metabase/entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -e + +./app/run_metabase.sh "$@" & +SERVER_PID=$! + +until curl -s http://metabase:${MB_JETTY_PORT}/api/health | grep -q "\"status\":\"ok\""; do + echo "Waiting for the Metabase to be healthy..." + sleep 2 +done + +echo "Metabase is healthy. Running additional configuration..." + +echo "Running import..."; + +SESSION_ID=$(curl -s -X POST \ + -H "Content-Type: application/json" \ + -d "{\"username\": \"${METABASE_ADMIN_EMAIL}\", \"password\": \"${METABASE_ADMIN_PASSWORD}\"}" \ + http://metabase:${MB_JETTY_PORT}/api/session | sed -n 's/.*"id":"\([^"]*\)".*/\1/p') + +curl -X POST \ + -H "X-Metabase-Session: $SESSION_ID" \ + -F file=@./app/metabase_data.tar.gz \ + http://metabase:${MB_JETTY_PORT}/api/ee/serialization/import + +wait $SERVER_PID diff --git a/metabase/export_metabase_data.sh b/metabase/export_metabase_data.sh new file mode 100755 index 00000000..2cdf729b --- /dev/null +++ b/metabase/export_metabase_data.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +if [ -z "$SHOPPY_METABASE_INSTANCE_API_KEY" ]; then + echo "Define SHOPPY_METABASE_INSTANCE_API_KEY value in your environment" + exit 1 +fi + +curl -X POST \ + -H "x-api-key: $SHOPPY_METABASE_INSTANCE_API_KEY" \ + "https://shoppy.metabaseapp.com/api/ee/serialization/export?data_model=false&dirname=metabase_data" \ + -o metabase_data.tar.gz + +tar -xzf metabase_data.tar.gz + +find metabase_data -type f -name "export.log" -delete + +# Find all directories ending with '_user_generated' and process them +find metabase_data/collections -type d -name "*_user_generated" | while IFS= read -r dir; do + # Recursively delete all nested directories inside the '_user_generated' directory + find "$dir" -mindepth 1 -type d -exec rm -rf {} + + + # Make sure to preserve files ending with '_user_generated.yaml' + find "$dir" -type f ! -name "*_user_generated.yaml" -exec rm -f {} + +done + +tar -czf metabase_data.tar.gz metabase_data +rm -rf metabase_data diff --git a/metabase/metabase_data.tar.gz b/metabase/metabase_data.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..0ff40974495865c37a00d486fef5b5a9c0fb5fa6 GIT binary patch literal 15275 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 r_Poj=W)e4yjJm%s*)Dq-{~W3J*fXbU$h Date: Tue, 8 Sep 2026 13:57:40 +0400 Subject: [PATCH 2/5] Removed sandboxing e2e --- e2e/test/compatibility.cy.spec.js | 41 ------------------------------- 1 file changed, 41 deletions(-) diff --git a/e2e/test/compatibility.cy.spec.js b/e2e/test/compatibility.cy.spec.js index 206a0762..89907144 100644 --- a/e2e/test/compatibility.cy.spec.js +++ b/e2e/test/compatibility.cy.spec.js @@ -130,47 +130,6 @@ describe("Embedding SDK: shoppy compatibility", () => { }) }) - it("should display dashboards with sandboxing for different shops", () => { - const getOrdersCountForShop = (site) => { - cy.log(`Get total orders count for ${site}`) - - cy.findByTestId(`site-switcher-button-${site}`).click() - - return ( - cy - .findAllByTestId("dashcard-container", { timeout: TIMEOUT }) - // TODO: find a way to not rely on dashboard name - .filter(":contains('Total Orders')") - .findByTestId("scalar-container", { timeout: TIMEOUT }) - .invoke("text") - ) - } - - cy.visit("/admin/analytics") - - cy.get("main").within(() => { - cy.findByText("Orders", { timeout: TIMEOUT }).click() - }) - - const shops = ["proficiency", "stitch", "luminara", "pug"] - const counts = [] - - cy.wrap(shops).each((site) => { - getOrdersCountForShop(site).then((text) => { - counts.push(text) - }) - }) - - cy.then(() => { - const unique = new Set(counts) - - expect( - unique.size, - `All counts: [${counts.join(", ")}]`, - ).to.be.greaterThan(1) - }) - }) - // TODO (Kelvin 2026-07-07) bandage, not a fix. Metabase's appdb search-index reindex has a // race that can strand a fully-built index as "pending" instead of activating it. Couldn't // land the real backend fix yet; this forces a reindex and waits for it first. The actual From 34188f3624f415f10831034a7a3e7daa5e4e4677 Mon Sep 17 00:00:00 2001 From: Timofei Kachalov Date: Tue, 8 Sep 2026 13:58:32 +0400 Subject: [PATCH 3/5] Remove stale step --- .github/workflows/deploy-production.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index f8e097cd..bd8d6c15 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -27,10 +27,6 @@ jobs: role-session-name: GitHub_to_AWS_via_FederatedOIDC aws-region: us-east-1 - - name: Get runner public IP - id: runner-ip - run: echo "ip=$(curl -s https://checkip.amazonaws.com)" >> "$GITHUB_OUTPUT" - - name: Ensure that Esno executable is ready run: yarn install --cwd api --frozen-lockfile From 0b731a8199333b1d3cf2a555d03ffc3f0f952992 Mon Sep 17 00:00:00 2001 From: Timofei Kachalov Date: Tue, 8 Sep 2026 14:14:37 +0400 Subject: [PATCH 4/5] Restore `before` hook --- e2e/test/compatibility.cy.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/test/compatibility.cy.spec.js b/e2e/test/compatibility.cy.spec.js index 89907144..af15c008 100644 --- a/e2e/test/compatibility.cy.spec.js +++ b/e2e/test/compatibility.cy.spec.js @@ -136,7 +136,7 @@ describe("Embedding SDK: shoppy compatibility", () => { // backend bug (QUE2-736) hasn't landed yet. // Scoped to just this test, not the whole suite — it's the only one that needs it. describe("data picker", () => { - beforeAll(() => { + before(() => { const REINDEX_POLL_INTERVAL_MS = 1000 const REINDEX_POLL_MAX_ATTEMPTS = 60 From db353599de2a92234d9e14a1eecb3b90b6561af9 Mon Sep 17 00:00:00 2001 From: Timofei Kachalov Date: Tue, 8 Sep 2026 14:41:55 +0400 Subject: [PATCH 5/5] Resolve comments --- docker-compose.yml | 1 - metabase/export_metabase_data.sh | 13 +++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4c05883c..97ed26e0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -76,7 +76,6 @@ services: METASTORE_DEV_SERVER_URL: "${METASTORE_DEV_SERVER_URL}" MB_JWT_IDENTITY_PROVIDER_URI: "http://localhost:${API_PORT}/sso/metabase" MB_JWT_SHARED_SECRET: "${METABASE_JWT_SHARED_SECRET}" - MB_SETUP_TOKEN: "${PREMIUM_EMBEDDING_TOKEN}" MB_WAREHOUSE_ALLOWED_NETWORKS: "allow-all" MB_DB_TYPE: "postgres" MB_DB_HOST: "${METABASE_APP_DB_HOST}" diff --git a/metabase/export_metabase_data.sh b/metabase/export_metabase_data.sh index 2cdf729b..086af65c 100755 --- a/metabase/export_metabase_data.sh +++ b/metabase/export_metabase_data.sh @@ -1,13 +1,18 @@ #!/bin/bash -if [ -z "$SHOPPY_METABASE_INSTANCE_API_KEY" ]; then - echo "Define SHOPPY_METABASE_INSTANCE_API_KEY value in your environment" +if [ -z "$METABASE_INSTANCE_API_KEY" ]; then + echo "Define METABASE_INSTANCE_API_KEY value in your environment" + exit 1 +fi + +if [ -z "$METABASE_INSTANCE_URL" ]; then + echo "Define METABASE_INSTANCE_URL value in your environment" exit 1 fi curl -X POST \ - -H "x-api-key: $SHOPPY_METABASE_INSTANCE_API_KEY" \ - "https://shoppy.metabaseapp.com/api/ee/serialization/export?data_model=false&dirname=metabase_data" \ + -H "x-api-key: $METABASE_INSTANCE_API_KEY" \ + "$METABASE_INSTANCE_URL/api/ee/serialization/export?data_model=false&dirname=metabase_data" \ -o metabase_data.tar.gz tar -xzf metabase_data.tar.gz