Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Environments #850

Merged
merged 10 commits into from
Dec 18, 2024
2 changes: 0 additions & 2 deletions .github/workflows/create-docker-image-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ jobs:
contents: read
security-events: write
steps:
- name: Print example variable's
run: echo "${{ inputs.example-variable}}"
- uses: actions/checkout@v4
with:
submodules: true
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/end-to-end-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ on:
type: string
default: chromium

env:
E2E_TESTS_RUNNING: "true"

jobs:
e2e-tests:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -74,7 +71,8 @@ jobs:
run: |
echo "Running e2e tests 1 time"

npm run dev &
npm run build
npm run preview -- --port 5173 &
DEV_PID=$!
npm run test:e2e -- --project ${{ inputs.browser }} --repeat-each 1
pkill -P $DEV_PID
Expand Down
2 changes: 1 addition & 1 deletion .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ fileignoreconfig:
- filename: doc/adr/0009-renovate.md
checksum: 172f8d22a6c8114b91ba4e430349c40599d1afa59fb96f49a651c1eac1e551dc
- filename: frontend/src/main.ts
checksum: c7bf1cf4779f88563975f13217a367a4be2100cb52709f38ddb4c6f1c6e3a857
checksum: fd82b62a209f40f1735e9bde784f76e47337ed6ede40335a864c74a529223a97
- filename: LegalDocML.de/*/fixtures/*.xml
ignore_detectors: [filecontent]
- filename: LegalDocML.de/*/samples/*.xml
Expand Down
2 changes: 1 addition & 1 deletion DockerfileApp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FROM node:22.12.0 AS frontend
WORKDIR /frontend
COPY frontend .
RUN npm ci
RUN npm run-script build
RUN npm run build

FROM gradle:8.11-jdk21 AS backend
ARG SENTRY_AUTH_TOKEN
Expand Down
5 changes: 3 additions & 2 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ The frontend is the main entry point for users of _RIS norms_.
- `npm run test:watch` runs the tests and automatically re-runs if something changes
- `npm run test:e2e` runs the E2E tests (requires a running frontend and backend)
- `npm run coverage` compiles a coverage report via `v8`
- `npm run typecheck` runs type checking through TypeScript
- `npm run style:check` does linting and formatting
- `npm run style:fix` will try to fix linting and formatting issues
- `npm run build` builds the app
- `npm run preview` previews the app (requires a build first)

## E2E Tests

Expand Down Expand Up @@ -62,8 +64,7 @@ npm run test:e2e -- --project firefox --repeat-each 1
npm run test:e2e -- --project msedge --repeat-each 1
```

Alternatively the [DEVELOPING.md](../DEVELOPING.md#how-to-run-locally) also explains how to run the e2e-tests inside a
docker container.
Alternatively the [DEVELOPING.md](../DEVELOPING.md#how-to-run-locally) also explains how to run the e2e-tests inside a docker container.

## Icons

Expand Down
2 changes: 1 addition & 1 deletion frontend/sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ sonar.tests=src/
sonar.test.inclusions=**/*.spec.ts
sonar.host.url=https://sonarcloud.io
sonar.javascript.lcov.reportPaths=coverage/lcov.info
# see vite.config.ts
# see vitest.config.ts
sonar.coverage.exclusions=**/*.d.ts, **/*.spec.ts, src/views/**/*, src/App.vue, src/router.ts, src/main.ts, src/**/*.story.vue

70 changes: 70 additions & 0 deletions frontend/src/lib/env.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { afterEach, describe, expect, it, vi } from "vitest"
import { detectEnv, RisEnvironment } from "./env"

describe("env", () => {
afterEach(() => {
vi.unstubAllEnvs()
vi.unstubAllGlobals()
})

it("detects development", () => {
vi.stubEnv("DEV", true)

expect(detectEnv()).toBe(RisEnvironment.DEVELOPMENT)
})

it("detects local environment", () => {
vi.stubEnv("DEV", false)

vi.stubGlobal("window", {
...window,
location: { hostname: "localhost" },
})

expect(detectEnv()).toBe(RisEnvironment.LOCAL)
})

it("detects production environment", () => {
vi.stubEnv("DEV", false)

vi.stubGlobal("window", {
...window,
location: { hostname: "foo.bar.prod.example.dev" },
})

expect(detectEnv()).toBe(RisEnvironment.PRODUCTION)
})

it("detects staging environment", () => {
vi.stubEnv("DEV", false)

vi.stubGlobal("window", {
...window,
location: { hostname: "foo.bar.dev.example.dev" },
})

expect(detectEnv()).toBe(RisEnvironment.STAGING)
})

it("detects uat environment", () => {
vi.stubEnv("DEV", false)

vi.stubGlobal("window", {
...window,
location: { hostname: "foo.bar-uat.example.dev" },
})

expect(detectEnv()).toBe(RisEnvironment.UAT)
})

it("returns a fallback environment", () => {
vi.stubEnv("DEV", false)

vi.stubGlobal("window", {
...window,
location: { hostname: "foo.bar.baz.dev.example.dev" },
})

expect(detectEnv()).toBe(RisEnvironment.UNKNOWN)
})
})
36 changes: 36 additions & 0 deletions frontend/src/lib/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export enum RisEnvironment {
/** Development mode = unbundled app served through dev server or unit tests */
DEVELOPMENT = "development",
/** Preview mode = production bundle is served on the local machine */
LOCAL = "local",
/** Production mode = production bundle is served from production environment */
PRODUCTION = "prod",
/** Staging mode = production bundle is served from staging environment */
STAGING = "staging",
/** UAT mode = production bundle is served from UAT environment */
UAT = "uat",
/** Any other environment */
UNKNOWN = "unknown",
}

/**
* Infers the environment that the app is running in during runtime based on
* build metadata and the domain the app is served from.
*
* @returns The environment the app is running in
*/
export function detectEnv(): RisEnvironment {
if (import.meta.env.DEV) {
return RisEnvironment.DEVELOPMENT
} else if (window.location.hostname.includes("-uat.")) {
return RisEnvironment.UAT
} else if (window.location.hostname.includes(".prod.")) {
return RisEnvironment.PRODUCTION
} else if (window.location.hostname.includes(".dev.")) {
return window.location.hostname.split(".").length > 5
? RisEnvironment.UNKNOWN
: RisEnvironment.STAGING
} else if (window.location.hostname.includes("localhost")) {
return RisEnvironment.LOCAL
} else return RisEnvironment.UNKNOWN
}
36 changes: 23 additions & 13 deletions frontend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,38 @@ import ConfirmationService from "primevue/confirmationservice"
import ToastService from "primevue/toastservice"
import { createApp } from "vue"
import App from "./App.vue"
import { detectEnv, RisEnvironment } from "./lib/env"
import router from "./router"
import "./style.css"

const app = createApp(App)
.use(PrimeVue, {
pt: RisUiTheme,
unstyled: true,
locale: RisUiLocale.deDE,
})
.use(ToastService)
.use(ConfirmationService)
.use(router)

const env = detectEnv()

app.use(PrimeVue, {
pt: RisUiTheme,
unstyled: true,
locale: RisUiLocale.deDE,
})
const enableSentry = [
RisEnvironment.PRODUCTION,
RisEnvironment.UAT,
RisEnvironment.STAGING,
].includes(env)

app.use(ToastService)
app.use(ConfirmationService)
console.info(
`Sentry reporting is ${enableSentry ? "enabled" : "disabled"} in environment "${env}"`,
)

if (import.meta.env.PROD && import.meta.env.E2E_TESTS_RUNNING !== "true") {
if (enableSentry) {
Sentry.init({
app,
environment: "staging",
environment: env,
dsn: "https://[email protected]/4507543284613120",
initialScope: {
tags: { source: "frontend" },
},
initialScope: { tags: { source: "frontend" } },
integrations: [
Sentry.browserTracingIntegration({ router }),
Sentry.captureConsoleIntegration(),
Expand All @@ -39,4 +49,4 @@ if (import.meta.env.PROD && import.meta.env.E2E_TESTS_RUNNING !== "true") {
})
}

app.use(router).mount("#app")
app.mount("#app")
127 changes: 39 additions & 88 deletions frontend/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,99 +1,50 @@
/// <reference types="vitest" />
import { sentryVitePlugin } from "@sentry/vite-plugin"
import vue from "@vitejs/plugin-vue"
import { fileURLToPath, URL } from "node:url"
import icons from "unplugin-icons/vite"
import { defineConfig } from "vite"
import { configDefaults } from "vitest/dist/config"
import { sentryVitePlugin } from "@sentry/vite-plugin"

const isTest = process.env.VITEST === "true"

// https://vitejs.dev/config/
export default defineConfig({
build: {
sourcemap: true,
target: ["edge127", "firefox115", "chrome127"],
},
plugins: [
vue(),
icons({
scale: 1.3333, // ~24px at the current default font size of 18px
compiler: "vue3",
}),
!isTest &&
process.env.NODE_ENV === "production" &&
sentryVitePlugin({
authToken: process.env.SENTRY_AUTH_TOKEN,
org: "digitalservice",
project: "ris-norms",
telemetry: process.env.VITEST !== "true",
}),
].filter(Boolean),
server: {
proxy: {
"/api": {
target: "http://localhost:8080",
changeOrigin: true,
secure: false,
},
},
},
test: {
setupFiles: ["src/vitest-setup.ts"],
globals: true,
environment: "jsdom",
exclude: [...configDefaults.exclude, "e2e/**/*.spec.ts"],
css: {
// Needed so we can reliably test for class names for CSS modules.
// Otherwise scoped CSS classes would have an unreliable hash
// attached to the class name.
modules: { classNameStrategy: "non-scoped" },
},
coverage: {
provider: "v8",
reporter: ["lcov"],
// Changes to this also need to be reflected in the sonar-project.properties
exclude: [
// Configuration and generated outputs
"**/[.]**",
"coverage/**/*",
"dist/**/*",
"**/.*rc.{?(c|m)js,yml}",
"*.config.{js,ts}",

// Types
"**/*.d.ts",
export default defineConfig(({ mode }) => {
const enableSentry = mode === "production" && process.env.SENTRY_AUTH_TOKEN

// Tests
"test/**/*",
"e2e/**/*",
console.info(
`Sentry plugin is ${enableSentry ? "enabled" : "disabled"} in ${mode} mode`,
)

// App content we're not interested in covering with unit tests. If you
// add something here, please also add a comment explaining why the
// exclusion is necessary.

// Views are too complex too set up and mock in unit tests, we're covering
// those with E2E test instead. (App is also a view)
"src/views/**/*",
"src/App.vue",

// If necessary to use e.g. guards, we'll have a router-guards file that
// then should be tested
"src/router.ts",

// Just the init file, nothing much to test here.
"src/main.ts",

// Stories are just for internal development use and don't need to be
// tested
"src/**/*.story.vue",
],
return {
build: {
sourcemap: true,
target: ["edge127", "firefox115", "chrome127"],
},
},
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
"@e2e": fileURLToPath(new URL("./e2e", import.meta.url)),
plugins: [
vue(),
icons({
scale: 1.3333, // ~24px at the current default font size of 18px
compiler: "vue3",
}),
enableSentry &&
sentryVitePlugin({
authToken: process.env.SENTRY_AUTH_TOKEN,
org: "digitalservice",
project: "ris-norms",
telemetry: process.env.VITEST !== "true",
}),
],
server: {
proxy: {
"/api": {
target: "http://localhost:8080",
changeOrigin: true,
secure: false,
},
},
},
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
"@e2e": fileURLToPath(new URL("./e2e", import.meta.url)),
},
},
},
}
})
Loading
Loading