From 9aeda90446e380b0de4f86398c38993756db6657 Mon Sep 17 00:00:00 2001 From: Antoine Jaussoin Date: Wed, 8 Mar 2023 20:11:10 +0000 Subject: [PATCH] Simplify environment variables on frontend (#491) --- .github/workflows/alpha.yml | 2 +- docs/docs/self-hosting/optionals.md | 16 ++-- frontend/.env | 6 ++ frontend/Dockerfile | 1 + frontend/docker/frontend-entrypoint.sh | 30 ++++--- frontend/index.html | 15 +--- frontend/src/api/index.ts | 2 +- frontend/src/stripe/get-stripe.ts | 2 +- frontend/src/track.ts | 25 +++--- frontend/src/translations/i18n.ts | 2 +- frontend/src/utils/getConfig.ts | 95 +++++++-------------- frontend/src/views/Panel.tsx | 4 +- frontend/src/views/game/board/post/Post.tsx | 4 +- integration/Makefile | 1 + integration/docker-compose.ci.alpha.yml | 3 + k8s/frontend-deployment.yaml | 12 +-- marketing/docker/entrypoint.sh | 2 - self-hosting/docker-compose.full.yml | 14 +-- 18 files changed, 107 insertions(+), 129 deletions(-) diff --git a/.github/workflows/alpha.yml b/.github/workflows/alpha.yml index 9bc4f65ba..26e838e3b 100644 --- a/.github/workflows/alpha.yml +++ b/.github/workflows/alpha.yml @@ -2,7 +2,7 @@ name: 'Alpha Build' on: push: - branches: [v501/add-aw] + branches: [v502/simplify-fe-env] jobs: frontend: diff --git a/docs/docs/self-hosting/optionals.md b/docs/docs/self-hosting/optionals.md index f6ff3c718..acfe12236 100644 --- a/docs/docs/self-hosting/optionals.md +++ b/docs/docs/self-hosting/optionals.md @@ -107,18 +107,18 @@ services: - '80:80' # Change the first 80 to whatever port you want to access Retrospected from environment: # -- Optional -- - GA_ID: '' # Optional, Google Analytics ID (UA-1234456-7) - GOOGLE_AD_WORDS_ID: '' # Optional, Google Adwords ID (AW-1234456) - GOOGLE_AD_WORDS_EVENT: '' # Optional, Google Adwords Event ID (AW-1234456/1234456) - SENTRY_URL: '' # Optional, Sentry URL (https://1234567890abcdef12345@sentry.io/1234567) - GIPHY_API_KEY: '' # Optional, can be obtained here: https://developers.giphy.com/ - DEFAULT_LANGUAGE: 'en-GB' # Set the default language for new users - MARKETING_ROOT: 'https://www.retrospected.com' # URL of the marketing website + FRONTEND_GOOGLE_ANALYTICS_ID: '' # Optional, Google Analytics ID (UA-1234456-7) + FRONTEND_GOOGLE_AD_WORDS_ID: '' # Optional, Google Adwords ID (AW-1234456) + FRONTEND_GOOGLE_AD_WORDS_CONVERSION_ID: '' # Optional, Google Adwords Event ID (AW-1234456/1234456) + FRONTEND_SENTRY_URL: '' # Optional, Sentry URL (https://1234567890abcdef12345@sentry.io/1234567) + FRONTEND_GIPHY_API_KEY: '' # Optional, can be obtained here: https://developers.giphy.com/ + FRONTEND_DEFAULT_LANGUAGE: 'en-GB' # Set the default language for new users + FRONTEND_MARKETING_ROOT: 'https://www.retrospected.com' # URL of the marketing website # -- Do Not Change -- BACKEND_HOST: backend # This should be the name of the backend service BACKEND_PORT: 3201 # This should be the same as BACKEND_PORT on backend - STRIPE_KEY: '' # Stripe publishable key (for frontend) + FRONTEND_STRIPE_KEY: '' # Stripe publishable key (for frontend) restart: unless-stopped logging: diff --git a/frontend/.env b/frontend/.env index a7517dd86..808fa5753 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1 +1,7 @@ VITE_VERSION=$npm_package_version +VITE_STRIPE_KEY= +VITE_GIPHY_API_KEY= +VITE_MARKETING_ROOT=http://localhost:3001 +VITE_GOOGLE_ANALYTICS_ID= +VITE_GOOGLE_AD_WORDS_ID= +VITE_GOOGLE_AD_WORDS_CONVERSION_ID= diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 85d820d83..68ebde4bb 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -32,6 +32,7 @@ FROM nginx:latest RUN apt update RUN apt -y remove libfreetype6 +RUN apt -y install jq COPY --from=Node /home/node/app/dist /usr/share/nginx/html COPY ./docker/nginx.conf.template /etc/nginx/conf.d/default.conf.template diff --git a/frontend/docker/frontend-entrypoint.sh b/frontend/docker/frontend-entrypoint.sh index 15425b360..2e4573538 100644 --- a/frontend/docker/frontend-entrypoint.sh +++ b/frontend/docker/frontend-entrypoint.sh @@ -1,18 +1,28 @@ #!/usr/bin/env sh set -eu +# Configure Nginx with backend host and port BACKEND_HOST="${BACKEND_HOST:-backend}" \ BACKEND_PORT="${BACKEND_PORT:-3201}" \ envsubst '${BACKEND_HOST} ${BACKEND_PORT}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf -# Replacing Google Analytics and Sentry IDs. Separator is # because the sentry URL contains a / -sed -i "s#NO_GA#${GA_ID:-}#g" /usr/share/nginx/html/index.html -sed -i "s#NO_AD_WORDS_ID#${GOOGLE_AD_WORDS_ID:-}#g" /usr/share/nginx/html/index.html -sed -i "s#NO_AD_WORDS_EVENT#${GOOGLE_AD_WORDS_EVENT:-}#g" /usr/share/nginx/html/index.html -sed -i "s#NO_SENTRY#${SENTRY_URL:-}#g" /usr/share/nginx/html/index.html -sed -i "s#NO_GIPHY#${GIPHY_API_KEY:-}#g" /usr/share/nginx/html/index.html -sed -i "s#NO_STRIPE#${STRIPE_KEY:-}#g" /usr/share/nginx/html/index.html -sed -i "s#NO_DEFAULT_LANGUAGE#${DEFAULT_LANGUAGE:-en-GB}#g" /usr/share/nginx/html/index.html -sed -i "s#NO_MARKETING_ROOT#${MARKETING_ROOT:-https://www.retrospected.com}#g" /usr/share/nginx/html/index.html +# Configure the frontend with environment variables +CONFIG_FILE='/usr/share/nginx/html/config.tmp' +HTML_FILE='/usr/share/nginx/html/index.html' +PREFIX='FRONTEND_' -exec "$@" \ No newline at end of file +# Creates a file with the environment variables that start with FRONTEND_ +echo " window.__env__ = {" > "${CONFIG_FILE}" +jq -n 'env' | { grep "\"$PREFIX" || true; }>> "${CONFIG_FILE}" +echo " };" >> "${CONFIG_FILE}" + +# Removes the prefix from the environment variables +sed -i "s#\"${PREFIX}# \"#g" "${CONFIG_FILE}" + +# Injects the configuration file into the HTML file +sed -i -e "/RUN-TIME CONFIGURATION/r ${CONFIG_FILE}" ${HTML_FILE} + +# Removes the temporary configuration file +rm $CONFIG_FILE + +exec "$@" diff --git a/frontend/index.html b/frontend/index.html index 99bc44952..18ff1fe5e 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -20,18 +20,6 @@ - @@ -76,6 +64,9 @@ } }); + diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index a213028a7..55e8f4748 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -254,7 +254,7 @@ export async function deleteUser( export async function getGiphyUrl(giphyId: string): Promise { try { const response = await fetch( - `//api.giphy.com/v1/gifs/${giphyId}?api_key=${config.GiphyApiKey}`, + `//api.giphy.com/v1/gifs/${giphyId}?api_key=${config.GIPHY_API_KEY}`, { credentials: 'omit' } ); if (response.ok) { diff --git a/frontend/src/stripe/get-stripe.ts b/frontend/src/stripe/get-stripe.ts index 75e3461a3..10bdd5332 100644 --- a/frontend/src/stripe/get-stripe.ts +++ b/frontend/src/stripe/get-stripe.ts @@ -4,7 +4,7 @@ import config from '../utils/getConfig'; let stripePromise: Promise; export const getStripe = () => { if (!stripePromise) { - stripePromise = loadStripe(config.StripeKey); + stripePromise = loadStripe(config.STRIPE_KEY); } return stripePromise; }; diff --git a/frontend/src/track.ts b/frontend/src/track.ts index 22da9a6dc..cb72a1450 100644 --- a/frontend/src/track.ts +++ b/frontend/src/track.ts @@ -8,16 +8,19 @@ import { InitOptions } from 'react-ga4/types/ga4'; let sentryErrorCount = 0; +const hasGA = !!config.GOOGLE_ANALYTICS_ID; +const hasSentry = !!config.SENTRY_URL; + export const initialiseAnalytics = () => { if (isGAEnabled()) { ReactGA.initialize( [ { - trackingId: config.GoogleAnalyticsId, + trackingId: config.GOOGLE_ANALYTICS_ID, }, - config.googleAdWordsId + config.GOOGLE_AD_WORDS_ID ? { - trackingId: config.googleAdWordsId, + trackingId: config.GOOGLE_AD_WORDS_ID, gaOptions: { name: 'aw', }, @@ -29,16 +32,16 @@ export const initialiseAnalytics = () => { }; export const initialiseSentry = () => { - if (config.hasSentry) { + if (hasSentry) { Sentry.init({ - dsn: config.SentryUrl, - release: `frontend@${config.version}`, + dsn: config.SENTRY_URL, + release: `frontend@${config.VERSION}`, }); } }; export const setScope = (fn: (scope: Sentry.Scope | null) => void) => { - if (config.hasSentry) { + if (hasSentry) { Sentry.configureScope(fn); } else { fn(null); @@ -46,7 +49,7 @@ export const setScope = (fn: (scope: Sentry.Scope | null) => void) => { }; export const recordManualError = (message: string) => { - if (config.hasSentry) { + if (hasSentry) { sentryErrorCount += 1; if (sentryErrorCount > 100) { console.error( @@ -98,14 +101,14 @@ export const trackPageView = (path: string) => { }; export const trackAdWordsConversion = () => { - if (isGAEnabled() && config.googleAdWordsEvent) { + if (isGAEnabled() && config.GOOGLE_AD_WORDS_CONVERSION_ID) { ReactGA._gtag('event', 'conversion', { - send_to: config.googleAdWordsEvent, + send_to: config.GOOGLE_AD_WORDS_CONVERSION_ID, event_callback: noop, }); } }; const isGAEnabled = () => { - return isProduction() && config.hasGA; + return isProduction() && hasGA; }; diff --git a/frontend/src/translations/i18n.ts b/frontend/src/translations/i18n.ts index 860357419..5cdd28c9c 100644 --- a/frontend/src/translations/i18n.ts +++ b/frontend/src/translations/i18n.ts @@ -20,7 +20,7 @@ i18n .use(LanguageDetector) .use(initReactI18next) .init({ - fallbackLng: config.defaultLanguage, + fallbackLng: config.DEFAULT_LANGUAGE, debug: !isProduction(), defaultNS: 'ns1', ns: 'ns1', diff --git a/frontend/src/utils/getConfig.ts b/frontend/src/utils/getConfig.ts index b351d744b..69c069190 100644 --- a/frontend/src/utils/getConfig.ts +++ b/frontend/src/utils/getConfig.ts @@ -1,7 +1,7 @@ -interface HtmlConfig { +interface Config { GOOGLE_ANALYTICS_ID: string; GOOGLE_AD_WORDS_ID: string; - GOOGLE_AD_WORDS_EVENT: string; + GOOGLE_AD_WORDS_CONVERSION_ID: string; SENTRY_URL: string; GIPHY_API_KEY: string; STRIPE_KEY: string; @@ -10,89 +10,52 @@ interface HtmlConfig { MARKETING_ROOT: string; } -interface Config { - hasGA: boolean; - hasSentry: boolean; - hasGiphy: boolean; - GoogleAnalyticsId: string; - googleAdWordsId: string; - googleAdWordsEvent: string; - SentryUrl: string; - GiphyApiKey: string; - StripeKey: string; - defaultLanguage: string; - version: string; - marketingRoot: string; -} +const ALL_KEYS: (keyof Config)[] = [ + 'GOOGLE_ANALYTICS_ID', + 'GOOGLE_AD_WORDS_ID', + 'GOOGLE_AD_WORDS_CONVERSION_ID', + 'SENTRY_URL', + 'GIPHY_API_KEY', + 'STRIPE_KEY', + 'DEFAULT_LANGUAGE', + 'VERSION', + 'MARKETING_ROOT', +]; declare global { interface Window { - __env__: HtmlConfig; + __env__: Partial; } } window.__env__ = window.__env__ || {}; -function getKey( - key: - | 'GOOGLE_ANALYTICS_ID' - | 'GOOGLE_AD_WORDS_ID' - | 'GOOGLE_AD_WORDS_EVENT' - | 'SENTRY_URL' - | 'GIPHY_API_KEY' - | 'STRIPE_KEY' - | 'MARKETING_ROOT' - | 'DEFAULT_LANGUAGE', - noValue: string, - defaultValue?: string -): string { +function getKey(key: keyof Config): string { if (import.meta.env[`VITE_${key}`]) { return import.meta.env[`VITE_${key}`] || ''; } - if (!!window.__env__[key] && window.__env__[key] !== noValue) { - return window.__env__[key]; + const winObj = window.__env__[key]; + if (winObj) { + return winObj; } - return defaultValue || ''; + return ''; } function getConfig(): Config { - const googleAnalyticsId = getKey('GOOGLE_ANALYTICS_ID', 'NO_GA'); - const googleAdWordsId = getKey('GOOGLE_AD_WORDS_ID', 'NO_AD_WORDS_ID'); - const googleAdWordsEvent = getKey( - 'GOOGLE_AD_WORDS_EVENT', - 'NO_AD_WORDS_EVENT' - ); - const sentryUrl = getKey('SENTRY_URL', 'NO_SENTRY'); - const giphyApiKey = getKey('GIPHY_API_KEY', 'NO_GIPHY'); - const stripeKey = getKey('STRIPE_KEY', 'NO_STRIPE'); - const marketingRoot = getKey('MARKETING_ROOT', 'NO_MARKETING_ROOT'); - let defaultLanguage = getKey( - 'DEFAULT_LANGUAGE', - 'NO_DEFAULT_LANGUAGE', - 'en-GB' - ); - - if (defaultLanguage.length !== 5) { - console.error( + const config: Config = {} as Config; + ALL_KEYS.forEach((key) => { + config[key] = getKey(key); + }); + + // Special cases + if (!config.DEFAULT_LANGUAGE || config.DEFAULT_LANGUAGE.length !== 5) { + console.warn( 'Your default language (DEFAULT_LANGUAGE) is not in the right format. The right format should be a string of 5 characters, for example: en-GB, fr-FR, etc.' ); - defaultLanguage = 'en-GB'; + config.DEFAULT_LANGUAGE = 'en-GB'; } - return { - hasGA: !!googleAnalyticsId, - hasSentry: !!sentryUrl, - hasGiphy: !!giphyApiKey, - GoogleAnalyticsId: googleAnalyticsId, - googleAdWordsId, - googleAdWordsEvent, - SentryUrl: sentryUrl, - GiphyApiKey: giphyApiKey, - StripeKey: stripeKey, - defaultLanguage: defaultLanguage, - version: APP_VERSION, - marketingRoot, - }; + return config; } export default getConfig(); diff --git a/frontend/src/views/Panel.tsx b/frontend/src/views/Panel.tsx index e9824bf83..72b2c5d79 100644 --- a/frontend/src/views/Panel.tsx +++ b/frontend/src/views/Panel.tsx @@ -61,7 +61,7 @@ function Panel() { Legal Stuff {policies.map((policy) => ( @@ -74,7 +74,7 @@ function Panel() { component="div" style={{ color: colors.grey[700], textAlign: 'center' }} > - Version {config.version} + Version {config.VERSION} diff --git a/frontend/src/views/game/board/post/Post.tsx b/frontend/src/views/game/board/post/Post.tsx index 675d6fb7d..c7bf19020 100644 --- a/frontend/src/views/game/board/post/Post.tsx +++ b/frontend/src/views/game/board/post/Post.tsx @@ -279,7 +279,7 @@ const PostItem = ({ onClick={toggleAction} /> )} - {canEdit && config.hasGiphy && canUseGiphy && ( + {canEdit && !!config.GIPHY_API_KEY && canUseGiphy && ( diff --git a/integration/Makefile b/integration/Makefile index 3d1422796..1f8fe86b5 100644 --- a/integration/Makefile +++ b/integration/Makefile @@ -13,4 +13,5 @@ local: docker-compose down alpha: + docker-compose -f ./docker-compose.ci.alpha.yml pull docker-compose -f ./docker-compose.ci.alpha.yml up --exit-code-from cypress \ No newline at end of file diff --git a/integration/docker-compose.ci.alpha.yml b/integration/docker-compose.ci.alpha.yml index 95c16e1ea..af52da565 100644 --- a/integration/docker-compose.ci.alpha.yml +++ b/integration/docker-compose.ci.alpha.yml @@ -46,6 +46,9 @@ services: image: retrospected/frontend:alpha depends_on: - backend + environment: + BACKEND_HOST: backend + BACKEND_PORT: 3201 restart: unless-stopped logging: driver: 'json-file' diff --git a/k8s/frontend-deployment.yaml b/k8s/frontend-deployment.yaml index 75e283881..daff75fb9 100644 --- a/k8s/frontend-deployment.yaml +++ b/k8s/frontend-deployment.yaml @@ -22,15 +22,15 @@ spec: value: rb-backend-cluster-ip-service - name: BACKEND_PORT value: '3201' - - name: GA_ID + - name: FRONTEND_GOOGLE_ANALYTICS_ID value: '' - - name: GOOGLE_AD_WORDS_ID + - name: FRONTEND_GOOGLE_AD_WORDS_ID value: '' - - name: GOOGLE_AD_WORDS_EVENT + - name: FRONTEND_GOOGLE_AD_WORDS_CONVERSION_ID value: '' - - name: SENTRY_URL + - name: FRONTEND_SENTRY_URL value: '' - - name: GIPHY_API_KEY + - name: FRONTEND_GIPHY_API_KEY value: '' - - name: DEFAULT_LANGUAGE + - name: FRONTEND_DEFAULT_LANGUAGE value: 'en-GB' diff --git a/marketing/docker/entrypoint.sh b/marketing/docker/entrypoint.sh index 3b3500c5d..b76c62400 100644 --- a/marketing/docker/entrypoint.sh +++ b/marketing/docker/entrypoint.sh @@ -12,7 +12,6 @@ makeSedCommands() { IFS=$'\n' # For each sed command for c in $(makeSedCommands); do - echo "Executing $c" # For each file in the .next directory for f in $(find .next -type f); do # Execute the command against the file @@ -22,6 +21,5 @@ for c in $(makeSedCommands); do eval "$c ./server.js" done -echo "Starting Nextjs" # Run any arguments passed to this script exec "$@" \ No newline at end of file diff --git a/self-hosting/docker-compose.full.yml b/self-hosting/docker-compose.full.yml index 746d7618f..76fff70e2 100644 --- a/self-hosting/docker-compose.full.yml +++ b/self-hosting/docker-compose.full.yml @@ -135,16 +135,18 @@ services: - '80:80' # Change the first 80 to whatever port you want to access Retrospected from environment: # -- Optional -- - GA_ID: '' # Optional, Google Analytics ID (UA-1234456-7) - SENTRY_URL: '' # Optional, Sentry URL (https://1234567890abcdef12345@sentry.io/1234567) - GIPHY_API_KEY: '' # Optional, can be obtained here: https://developers.giphy.com/ - DEFAULT_LANGUAGE: 'en-GB' # Set the default language for new users - MARKETING_ROOT: 'https://www.retrospected.com' # URL of the marketing website + FRONTEND_GOOGLE_ANALYTICS_ID: '' # Optional, Google Analytics ID (UA-1234456-7) + FRONTEND_GOOGLE_AD_WORDS_ID: '' # Optional, Google Adwords ID (AW-1234456) + FRONTEND_GOOGLE_AD_WORDS_CONVERSION_ID: '' # Optional, Google Adwords Event ID (AW-1234456/1234456) + FRONTEND_SENTRY_URL: '' # Optional, Sentry URL (https://1234567890abcdef12345@sentry.io/1234567) + FRONTEND_GIPHY_API_KEY: '' # Optional, can be obtained here: https://developers.giphy.com/ + FRONTEND_DEFAULT_LANGUAGE: 'en-GB' # Set the default language for new users + FRONTEND_MARKETING_ROOT: 'https://www.retrospected.com' # URL of the marketing website # -- Do Not Change -- BACKEND_HOST: backend # This should be the name of the backend service BACKEND_PORT: 3201 # This should be the same as BACKEND_PORT on backend - STRIPE_KEY: '' # Stripe publishable key (for frontend) + FRONTEND_STRIPE_KEY: '' # Stripe publishable key (for frontend) restart: unless-stopped logging: