Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/value-set-expand/upload.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
FILENAME=$1
BASE="http://localhost:8080/fhir"

echo "Upload $FILENAME"

RESOURCE_TYPE="$(jq -r .resourceType "$FILENAME")"
if [[ "$RESOURCE_TYPE" =~ ValueSet|CodeSystem ]]; then
URL="$(jq -r .url "$FILENAME")"
if [[ "$URL" =~ http://unitsofmeasure.org|http://snomed.info/sct|http://loinc.org|urn:ietf:bcp:13 ]]; then
echo "Skip creating the code system or value set $URL which is internal in Blaze"
else
echo "Upload $FILENAME"
curl -sf -H "Content-Type: application/fhir+json" -H "Prefer: return=minimal" -d @"$FILENAME" "$BASE/$RESOURCE_TYPE"
fi
fi
7 changes: 2 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2034,8 +2034,8 @@ jobs:
- name: Run Keycloak
run: docker compose -f modules/frontend-e2e/docker-compose.yml up -d keycloak

- name: Sleep 20 Seconds
run: sleep 20
- name: Install Dependencies
run: make -C modules/frontend-e2e install

- name: Run Everything Else
run: docker compose -f modules/frontend-e2e/docker-compose.yml up -d
Expand All @@ -2061,9 +2061,6 @@ jobs:
- name: Download Patient Resources
run: modules/frontend-e2e/download-patient-resources.sh

- name: Install Playwright
run: make -C modules/frontend-e2e install

- name: Run Playwright Tests
run: make -C modules/frontend-e2e test

Expand Down
9 changes: 6 additions & 3 deletions modules/frontend-e2e/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
install:
npm ci
npm install
npx playwright install --with-deps

test:
Expand All @@ -8,12 +8,15 @@ test:
test-dev:
DEV="1" npx playwright test

test-ui-dev:
test-dev-chromium:
DEV="1" npx playwright test --project chromium

test-dev-ui:
DEV="1" npx playwright test --ui --project chromium

cloc-prod:

cloc-test:
cloc src

.PHONY: fmt lint install test test-ui-dev test-coverage cloc-prod cloc-test clean
.PHONY: fmt lint install test test-dev-chromium test-dev-ui test-coverage cloc-prod cloc-test clean
5 changes: 4 additions & 1 deletion modules/frontend-e2e/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@ services:
backend:
image: "blaze:latest"
environment:
JAVA_TOOL_OPTIONS: "-Xmx2g"
JAVA_TOOL_OPTIONS: "-Xmx4g"
LOG_LEVEL: "debug"
ENABLE_ADMIN_API: "true"
ENABLE_TERMINOLOGY_SERVICE: "true"
ENABLE_TERMINOLOGY_LOINC: "true"
DB_RESOURCE_CACHE_SIZE: "10000"
OPENID_PROVIDER_URL: "https://keycloak.localhost/realms/blaze"
OPENID_CLIENT_TRUST_STORE: "/app/keycloak-trust-store.p12"
OPENID_CLIENT_TRUST_STORE_PASS: "insecure"
Expand Down
28 changes: 21 additions & 7 deletions modules/frontend-e2e/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions modules/frontend-e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@
"devDependencies": {
"@playwright/test": "^1.42.1",
"@types/node": "^22.0.0"
},
"dependencies": {
"de.medizininformatikinitiative.kerndatensatz.fall": "^2025.0.0",
"de.medizininformatikinitiative.kerndatensatz.laborbefund": "^2025.0.2"
}
}
99 changes: 99 additions & 0 deletions modules/frontend-e2e/src/code-system.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { expect, type Locator, type Page, test } from '@playwright/test';

function breadcrumbItem(page: Page, text: string): Locator {
return page.getByLabel('Breadcrumb').getByRole('listitem').filter({ hasText: text });
}

test.beforeEach('Sign In', async ({ page }) => {
await page.goto('/fhir/CodeSystem');

// Blaze Sign-In Page
await expect(page).toHaveTitle('Sign-In - Blaze');
await page.getByRole('button', { name: 'Sign in with Keycloak' }).click();

// Keycloak Sign-In Page
await expect(page).toHaveTitle('Sign in to Keycloak');
await page.getByLabel('Username or email').fill('john');
await page.getByLabel('Password', { exact: true }).fill('insecure');
await page.getByRole('button', { name: 'Sign In' }).click();

await expect(page).toHaveTitle('CodeSystem - Blaze');
await expect(breadcrumbItem(page, 'CodeSystem')).toBeVisible();
});

test('Search Page', async ({ page }) => {
await expect(page.getByTitle('CodeSystem History')).toBeVisible();
await expect(page.getByTitle('CodeSystem Metadata')).toBeVisible();
await expect(page.getByText('Total:')).toBeVisible();
});

test('Search for LOINC', async ({ page }) => {
await page.goto('/fhir/CodeSystem?url=http://loinc.org');

await expect(breadcrumbItem(page, 'CodeSystem')).toBeVisible();

await expect(page.getByRole('link', { name: 'LOINC Code System v2.78' })).toBeVisible();
await expect(page.getByRole('listitem').filter({ hasText: 'Url http://loinc.org' })).toBeVisible();
await expect(page.getByRole('listitem').filter({ hasText: 'Version 2.78' })).toBeVisible();
await expect(page.getByRole('listitem').filter({ hasText: 'Name LOINC' })).toBeVisible();
await expect(page.getByRole('listitem').filter({ hasText: 'Title LOINC Code System' })).toBeVisible();
await expect(page.getByRole('listitem').filter({ hasText: 'Status active' })).toBeVisible();
await expect(page.getByRole('listitem').filter({ hasText: 'Experimental false' })).toBeVisible();
await expect(page.getByRole('listitem').filter({ hasText: 'Publisher Regenstrief Institute, Inc.' })).toBeVisible();
await expect(page.getByRole('listitem').filter({ hasText: 'Content not-present' })).toBeVisible();

await page.getByRole('link', { name: 'LOINC Code System v2.78' }).click();

await expect(breadcrumbItem(page, 'CodeSystem')).toBeVisible();
await expect(breadcrumbItem(page, 'LOINC Code System v2.78')).toBeVisible();
await expect(page.getByRole('link', { name: 'LOINC Code System v2.78' })).toBeVisible();
});

test.describe('$validate-code', () => {
test.describe('LOINC 718-7', () => {
test('type-level', async ({ page }) => {
await page.getByRole('button', { name: 'Operations' }).click();
await page.getByRole('menuitem', { name: '$validate-code' }).click();

await expect(breadcrumbItem(page, 'CodeSystem')).toBeVisible();
await expect(breadcrumbItem(page, '$validate-code')).toBeVisible();

await page.getByRole('heading', { name: 'Parameters' }).click();
await page.getByLabel('URL').fill('http://loinc.org');
await page.getByLabel('Code').fill('718-7');
await page.getByRole('button', { name: 'Submit' }).click();

await expect(page.getByRole('listitem').filter({ hasText: 'Result true' })).toBeVisible();
await expect(page.getByRole('listitem').filter({ hasText: 'Display Hemoglobin [Mass/volume] in Blood' })).toBeVisible();
await expect(page.getByRole('listitem').filter({ hasText: 'Code 718-7' })).toBeVisible();
await expect(page.getByRole('listitem').filter({ hasText: 'System http://loinc.org' })).toBeVisible();
await expect(page.getByRole('listitem').filter({ hasText: 'Version 2.78' })).toBeVisible();
});

test('instance-level', async ({ page }) => {
await page.goto('/fhir/CodeSystem?url=http://loinc.org');

await page.getByRole('link', { name: 'LOINC Code System v2.78' }).click();

await expect(breadcrumbItem(page, 'LOINC Code System v2.78')).toBeVisible();
await page.getByRole('button', { name: 'Operations' }).click();
await page.getByRole('menuitem', { name: '$validate-code' }).click();

await page.getByRole('heading', { name: 'LOINC Code System v2.78' }).click();

await expect(breadcrumbItem(page, 'CodeSystem')).toBeVisible();
await expect(breadcrumbItem(page, 'LOINC Code System v2.78')).toBeVisible();
await expect(breadcrumbItem(page, '$validate-code')).toBeVisible();

await page.getByRole('heading', { name: 'Parameters' }).click();
await page.getByLabel('Code').fill('718-7');
await page.getByRole('button', { name: 'Submit' }).click();

await expect(page.getByRole('listitem').filter({ hasText: 'Result true' })).toBeVisible();
await expect(page.getByRole('listitem').filter({ hasText: 'Display Hemoglobin [Mass/volume] in Blood' })).toBeVisible();
await expect(page.getByRole('listitem').filter({ hasText: 'Code 718-7' })).toBeVisible();
await expect(page.getByRole('listitem').filter({ hasText: 'System http://loinc.org' })).toBeVisible();
await expect(page.getByRole('listitem').filter({ hasText: 'Version 2.78' })).toBeVisible();
});
});
});
12 changes: 6 additions & 6 deletions modules/frontend-e2e/src/fhir.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ test('History Page', async ({page}) => {
await page.getByRole('link', {name: 'History', exact: true}).click();

await expect(page).toHaveTitle("History - Blaze");
await expect(page.getByText('Total: 92,300')).toBeVisible();
await expect(page.getByText('Total:')).toBeVisible();
});

test('Metadata Page', async ({page}) => {
Expand Down Expand Up @@ -71,7 +71,7 @@ test('Metadata Page', async ({page}) => {
await page.getByRole('link', {name: 'Resources'}).click();

await expect(page).toHaveTitle("Encounter - Blaze");
await expect(page.getByText('Total: 4,769')).toBeVisible();
await expect(page.getByText('Total:')).toBeVisible();
});

test.describe('Admin', () => {
Expand Down Expand Up @@ -223,8 +223,8 @@ test.describe('Admin', () => {
await expect(page.getByText('Search Param URL ' + searchParamUrl)).toBeVisible();

// may appear later
await expect(page.getByText('Total Resources 92.3 k')).toBeVisible({timeout: 30000});
await expect(page.getByText('Resources Processed 92.3 k')).toBeVisible({timeout: 50000});
await expect(page.getByText('Total Resources')).toBeVisible({timeout: 30000});
await expect(page.getByText('Resources Processed')).toBeVisible({timeout: 50000});
await expect(page.getByText('Processing Duration')).toBeVisible({timeout: 50000});
await expect(page.getByText('Status completed')).toBeVisible({timeout: 50000});
});
Expand All @@ -250,7 +250,7 @@ test('Patients Page', async ({page}) => {
await expect(page).toHaveTitle("Patient - Blaze");
await expect(page.getByTitle('Patient History')).toBeVisible();
await expect(page.getByTitle('Patient Metadata')).toBeVisible();
await expect(page.getByText('Total: 120')).toBeVisible();
await expect(page.getByText('Total:')).toBeVisible();

await page.getByTitle('Patient Metadata').click();
await expect(page).toHaveTitle("Patient - Metadata - Blaze");
Expand All @@ -270,7 +270,7 @@ test('Patients History Page', async ({page}) => {
await page.getByTitle('Patient History').click()

await expect(page).toHaveTitle("History - Patient - Blaze");
await expect(page.getByText('Total: 120')).toBeVisible();
await expect(page.getByText('Total:')).toBeVisible();
});

test('Signing in after sign out goes to the Keycloak Sign-In Page', async ({page}) => {
Expand Down
9 changes: 4 additions & 5 deletions modules/frontend-e2e/upload.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ blazectl --no-progress \
--token "$TOKEN" \
upload "$SCRIPT_DIR/../../.github/test-data/synthea"

echo "Download KDS Fall Package..."
wget -q --content-disposition "https://packages.simplifier.net/de.medizininformatikinitiative.kerndatensatz.fall/2025.0.0"
tar xzf de.medizininformatikinitiative.kerndatensatz.fall-2025.0.0.tgz

echo "Upload KDS Fall Profile..."
curl -sfH 'Content-Type: application/fhir+json' -H 'Prefer: return=minimal' --cacert "$CA_CERT" --oauth2-bearer "$TOKEN" -d @"package/StructureDefinition-mii-pr-fall-kontakt-gesundheitseinrichtung.json" "$BASE/StructureDefinition"
curl -sfH 'Content-Type: application/fhir+json' -H 'Prefer: return=minimal' --cacert "$CA_CERT" --oauth2-bearer "$TOKEN" -d @"$SCRIPT_DIR/node_modules/de.medizininformatikinitiative.kerndatensatz.fall/StructureDefinition-mii-pr-fall-kontakt-gesundheitseinrichtung.json" "$BASE/StructureDefinition"

echo "Upload one Value Set..."
curl -sfH 'Content-Type: application/fhir+json' -H 'Prefer: return=minimal' --cacert "$CA_CERT" --oauth2-bearer "$TOKEN" -d @"$SCRIPT_DIR/node_modules/de.medizininformatikinitiative.kerndatensatz.laborbefund/ValueSet-mii-vs-labor-laborbereich.json" "$BASE/ValueSet"
1 change: 0 additions & 1 deletion modules/frontend/src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
<html lang="en" class="h-full bg-white">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<link rel="icon" type="image/png" href="%sveltekit.assets%/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="%sveltekit.assets%/favicon.svg" />
<link rel="shortcut icon" href="%sveltekit.assets%/favicon.ico" />
Expand Down
15 changes: 15 additions & 0 deletions modules/frontend/src/lib/breadcrumb.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts">
import type { Snippet } from 'svelte';

interface Props {
children?: Snippet;
}

let { children }: Props = $props();
</script>

<nav class="flex pl-8 py-4 border-b border-gray-200" aria-label="Breadcrumb">
<ol class="flex items-center py-0.5 space-x-4">
{@render children?.()}
</ol>
</nav>
4 changes: 3 additions & 1 deletion modules/frontend/src/lib/breadcrumb/entry.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script lang="ts">
import type { Snippet } from 'svelte';

interface Props {
children?: import('svelte').Snippet;
children?: Snippet;
}

let { children }: Props = $props();
Expand Down
16 changes: 0 additions & 16 deletions modules/frontend/src/lib/breadcrumb/history.svelte

This file was deleted.

22 changes: 22 additions & 0 deletions modules/frontend/src/lib/breadcrumb/resource-history.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script lang="ts">
import { base } from '$app/paths';
import { page } from '$app/state';
import Entry from './entry.svelte';

interface Props {
last?: boolean;
}

let { last = false }: Props = $props();
</script>

<Entry>
{#if last}
<span class="ml-4 text-sm font-medium text-gray-500">History</span>
{:else}
<a
href="{base}/{page.params.type}/{page.params.id}/_history"
class="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700">History</a
>
{/if}
</Entry>
Loading