Compare commits

..

2 Commits

Author SHA1 Message Date
Anthony LC
d340c8f1f1 🧐(frontend) dispatch the app version to posthog
We add the app version in Posthog events to be
able to track which versions are being used and
identify potential issues related to specific
versions.
2026-05-07 14:20:05 +02:00
Anthony LC
67773ef2d9 🩺(project) reload app if front and back unsync
We observe some cases where the frontend and
backend versions can get out of sync, which can
cause issues.
To mitigate this, we want to implement a mechanism
that detects when the frontend and backend
versions are mismatched and triggers a
reload of the application to ensure they are in sync.
2026-05-07 14:20:05 +02:00
10 changed files with 78 additions and 7 deletions

View File

@@ -17,6 +17,7 @@ and this project adheres to
### Fixed
- 🩺(project) reload app if front and back unsync #2276
- 🐛(frontend) fix patch and comments #2273
- 🐛(frontend) interlinking are exported correctly in print mode #2269
- 💬(frontend) add missing link in onboarding description #2233

View File

@@ -6,8 +6,9 @@ FROM python:3.13.13-alpine AS base
# Upgrade system packages to install security updates
RUN apk update && apk upgrade --no-cache
# We must do that to avoid having an outdated pip version with security issues
RUN python -m pip install --upgrade pip
# Remove pip. We don't use it.
RUN python -m pip uninstall -y pip
# ---- Back-end builder image ----
FROM base AS back-builder
@@ -113,7 +114,7 @@ RUN mkdir /cert && \
# Generate compiled translation messages
RUN DJANGO_CONFIGURATION=Build \
python manage.py compilemessages --ignore=".venv/**/*"
python manage.py compilemessages
# We wrap commands run in this container by the following entrypoint that

View File

@@ -2841,6 +2841,7 @@ class ConfigView(drf.views.APIView):
dict_settings[setting] = getattr(settings, setting)
dict_settings["theme_customization"] = self._load_theme_customization()
dict_settings["RELEASE_VERSION"] = settings.RELEASE
return drf.response.Response(dict_settings)

View File

@@ -34,6 +34,7 @@ pytestmark = pytest.mark.django_db
FRONTEND_THEME="test-theme",
MEDIA_BASE_URL="http://testserver/",
POSTHOG_KEY={"id": "132456", "host": "https://eu.i.posthog-test.com"},
RELEASE="1.0.0",
SENTRY_DSN="https://sentry.test/123",
THEME_CUSTOMIZATION_FILE_PATH="",
)
@@ -77,6 +78,7 @@ def test_api_config(is_authenticated):
"LANGUAGE_CODE": "en-us",
"MEDIA_BASE_URL": "http://testserver/",
"POSTHOG_KEY": {"id": "132456", "host": "https://eu.i.posthog-test.com"},
"RELEASE_VERSION": "1.0.0",
"SENTRY_DSN": "https://sentry.test/123",
"TRASHBIN_CUTOFF_DAYS": 30,
"theme_customization": {},

View File

@@ -1,6 +1,11 @@
import { expect, test } from '@playwright/test';
import { createDoc, getCurrentConfig, verifyDocName } from './utils-common';
import {
createDoc,
getCurrentConfig,
overrideConfig,
verifyDocName,
} from './utils-common';
import { writeInEditor } from './utils-editor';
import { SignIn, expectLoginPage } from './utils-signin';
import { createRootSubPage } from './utils-sub-pages';
@@ -145,6 +150,29 @@ test.describe('Doc Routing', () => {
);
await expect(page).toHaveTitle(/401 Unauthorized - Docs/);
});
test('checks redirect if unsync version', async ({ page }) => {
await overrideConfig(page, {
RELEASE_VERSION: '0.0.0',
});
let counterReload = 0;
await page.route(/.*\/users\/me\/$/, async (route) => {
counterReload += 1;
await route.continue();
});
await page.waitForTimeout(1000);
// The sessionStorage guard should be set to the mismatched backend version.
const reloadVersion = await page.evaluate(() =>
sessionStorage.getItem('reload-version'),
);
expect(reloadVersion).toBe('0.0.0');
// The page should have reloaded once
expect(counterReload).toBe(2);
});
});
test.describe('Doc Routing: Not logged', () => {

View File

@@ -4,6 +4,7 @@ import path from 'path';
import { Locator, Page, TestInfo, expect } from '@playwright/test';
import theme_customization from '../../../../../backend/impress/configuration/theme/default.json';
import { version as packageJsonVersion } from '../../package.json';
export type BrowserName = 'chromium' | 'firefox' | 'webkit';
export const BROWSERS: BrowserName[] = ['chromium', 'webkit', 'firefox'];
@@ -40,6 +41,7 @@ export const CONFIG = {
],
LANGUAGE_CODE: 'en-us',
POSTHOG_KEY: {},
RELEASE_VERSION: packageJsonVersion,
SENTRY_DSN: null,
TRASHBIN_CUTOFF_DAYS: 30,
theme_customization,

View File

@@ -2,6 +2,8 @@ const crypto = require('crypto');
const { InjectManifest } = require('workbox-webpack-plugin');
const { version } = require('./package.json');
const buildId = crypto.randomBytes(256).toString('hex').slice(0, 8);
/** @type {import('next').NextConfig} */
@@ -25,6 +27,7 @@ const nextConfig = {
generateBuildId: () => buildId,
env: {
NEXT_PUBLIC_BUILD_ID: buildId,
NEXT_PUBLIC_APP_VERSION: version,
},
/**
* In dev mode, Next.js doesn't use Webpack, but Turbopack.

View File

@@ -85,6 +85,32 @@ export const ConfigProvider = ({ children }: PropsWithChildren) => {
});
}, [conf?.CRISP_WEBSITE_ID]);
useEffect(() => {
const frontendVersion = process.env.NEXT_PUBLIC_APP_VERSION;
if (
!conf?.RELEASE_VERSION ||
!frontendVersion ||
conf.RELEASE_VERSION === frontendVersion
) {
return;
}
// Avoid infinite reload loops: only reload once per backend version
const RELOAD_VERSION_KEY = 'reload-version';
try {
const reloadedForVersion = sessionStorage.getItem(RELOAD_VERSION_KEY);
if (reloadedForVersion === conf.RELEASE_VERSION) {
return;
}
sessionStorage.setItem(RELOAD_VERSION_KEY, conf.RELEASE_VERSION);
window.location.reload();
} catch {
console.warn('Failed to access sessionStorage for version reload logic');
}
}, [conf?.RELEASE_VERSION]);
if (!conf) {
return (
<Box $height="100vh" $width="100vw" $align="center" $justify="center">

View File

@@ -57,6 +57,7 @@ export interface ConfigResponse {
LANGUAGE_CODE: string;
MEDIA_BASE_URL?: string;
POSTHOG_KEY?: PostHogConf;
RELEASE_VERSION: string;
SENTRY_DSN?: string;
TRASHBIN_CUTOFF_DAYS?: number;
theme_customization?: ThemeCustomization;
@@ -94,13 +95,13 @@ export const KEY_CONFIG = 'config';
export function useConfig() {
const cachedData = getCachedTranslation();
const oneHour = 1000 * 60 * 60;
const staleTime = 1000 * 60 * 5;
return useQuery<ConfigResponse, APIError, ConfigResponse>({
queryKey: [KEY_CONFIG],
queryFn: () => getConfig(),
initialData: cachedData,
staleTime: oneHour,
initialDataUpdatedAt: Date.now() - oneHour, // Force initial data to be considered stale
staleTime,
initialDataUpdatedAt: Date.now() - staleTime, // Force initial data to be considered stale
});
}

View File

@@ -60,6 +60,12 @@ export function PostHogProvider({
if (process.env.NODE_ENV === 'development') {
posthogInstance.debug();
}
if (process.env.NEXT_PUBLIC_APP_VERSION) {
posthogInstance.register({
app_version: process.env.NEXT_PUBLIC_APP_VERSION,
});
}
},
capture_pageview: false,
capture_pageleave: true,