diff --git a/.github/workflows/deploy.dev2.yml b/.github/workflows/deploy.dev2.yml index 3d58a1feb..cb2bff764 100644 --- a/.github/workflows/deploy.dev2.yml +++ b/.github/workflows/deploy.dev2.yml @@ -24,7 +24,7 @@ jobs: ADMIN_ORIGIN: https://portal-admin-q6.oera.no APP_ORIGIN: https://www-2.ansatt.dev.nav.no REVALIDATOR_PROXY_ORIGIN: http://nav-enonicxp-frontend-revalidator-proxy-dev2 - DECORATOR_URL: https://dekoratoren.ekstern.dev.nav.no + DECORATOR_URL: https://dekoratoren-beta.intern.dev.nav.no XP_ORIGIN: https://www-q6.nav.no TELEMETRY_URL: https://telemetry.ekstern.dev.nav.no/collect INNLOGGINGSSTATUS_URL: https://www.ekstern.dev.nav.no/person/nav-dekoratoren-api/auth diff --git a/.nais/vars/vars-dev2.yml b/.nais/vars/vars-dev2.yml index 4c1f83d4c..0315eb5db 100644 --- a/.nais/vars/vars-dev2.yml +++ b/.nais/vars/vars-dev2.yml @@ -4,6 +4,7 @@ dekoratorenApp: nav-dekoratoren-beta externalHosts: - www-q6.nav.no - www-2-failover.intern.dev.nav.no + - dekoratoren-beta.intern.dev.nav.no secret: nav-enonicxp-dev2 ingresses: - https://www-2.ansatt.dev.nav.no diff --git a/next.config.js b/next.config.js index 7e6e61acc..690f31483 100644 --- a/next.config.js +++ b/next.config.js @@ -279,10 +279,6 @@ const config = { { source: '/:path*', headers: [ - { - key: 'app-name', - value: 'nav-enonicxp-frontend', - }, { key: 'Content-Security-Policy', value: await csp(), diff --git a/package-lock.json b/package-lock.json index d9ef5e16e..b1dd526b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@navikt/ds-css": "6.13.0", "@navikt/ds-react": "6.13.0", "@navikt/ds-tokens": "6.13.0", - "@navikt/nav-dekoratoren-moduler": "2.1.6", + "@navikt/nav-dekoratoren-moduler": "3.0.0-beta.4", "@navikt/nav-office-reception-info": "1.0.6", "@reduxjs/toolkit": "2.2.6", "csp-header": "5.2.1", @@ -2581,13 +2581,13 @@ "license": "MIT" }, "node_modules/@navikt/nav-dekoratoren-moduler": { - "version": "2.1.6", - "resolved": "https://npm.pkg.github.com/download/@navikt/nav-dekoratoren-moduler/2.1.6/049e1daeecff43519e41e387f21fd7e3634c2bd3", - "integrity": "sha512-P9c+a8/HuIY6XScNr/DVS7lZ0UtndzYAMEaIVdAzCOrG4SudY4tXSp+44g1V58GKml1RjdzOfGVF4hxmFv3SiA==", + "version": "3.0.0-beta.4", + "resolved": "https://npm.pkg.github.com/download/@navikt/nav-dekoratoren-moduler/3.0.0-beta.4/c1bee4cb2092c6f46a20623464e75e7a95ca47ba", + "integrity": "sha512-EMd8gq2eARy4qnJhnWFo6UzCENZxmFINf4+uhdxt3bNBbLwvJ/48aDVASq7WexYtDZu2HzNaDyr8IiHwx2rTLw==", "dependencies": { - "csp-header": "^5.1.0", - "html-react-parser": "^3.0.16", - "node-cache": "^5.1.2" + "csp-header": "5.2.1", + "html-react-parser": "3.0.16", + "node-cache": "5.1.2" }, "engines": { "node": ">=18" diff --git a/package.json b/package.json index f7609ab4e..26ffeeb77 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@navikt/ds-css": "6.13.0", "@navikt/ds-react": "6.13.0", "@navikt/ds-tokens": "6.13.0", - "@navikt/nav-dekoratoren-moduler": "2.1.6", + "@navikt/nav-dekoratoren-moduler": "3.0.0-beta.4", "@navikt/nav-office-reception-info": "1.0.6", "@reduxjs/toolkit": "2.2.6", "csp-header": "5.2.1", diff --git a/server/jest.config.mjs b/server/jest.config.mjs index 6c438535e..8e4e368bc 100644 --- a/server/jest.config.mjs +++ b/server/jest.config.mjs @@ -8,6 +8,7 @@ export default { "^.+\\.tsx?$": "ts-jest" }, "moduleNameMapper": { - "srcCommon/(.*)": "/../srcCommon/$1" + "srcCommon/(.*)": "/../srcCommon/$1", + "cache/(.*)": "/src/cache/$1" }, }; diff --git a/server/src/cache/revalidator-proxy-heartbeat.test.ts b/server/src/cache/revalidator-proxy-heartbeat.test.ts index ffebd8812..99fd0ea8e 100644 --- a/server/src/cache/revalidator-proxy-heartbeat.test.ts +++ b/server/src/cache/revalidator-proxy-heartbeat.test.ts @@ -14,7 +14,7 @@ describe('Revalidator proxy heartbeat', () => { fetchMock.mockResponse('Hello!'); - initRevalidatorProxyHeartbeat('dummy-build-id'); + initRevalidatorProxyHeartbeat(); expect(fetchMock.mock.calls.length).toEqual(1); expect(fetchMock.mock.calls[0][0]).toMatch(new RegExp(`^${revalidatorProxyOrigin}`)); diff --git a/server/src/cache/revalidator-proxy-heartbeat.ts b/server/src/cache/revalidator-proxy-heartbeat.ts index 40dac36e7..9b4d83cb8 100644 --- a/server/src/cache/revalidator-proxy-heartbeat.ts +++ b/server/src/cache/revalidator-proxy-heartbeat.ts @@ -3,8 +3,8 @@ // See: https://github.com/navikt/nav-enonicxp-frontend-revalidator-proxy import { networkInterfaces } from 'os'; import { logger } from 'srcCommon/logger'; -import { getRenderCacheKeyPrefix, getResponseCacheKeyPrefix } from 'srcCommon/redis'; import { objectToQueryString } from 'srcCommon/fetch-utils'; +import { redisCache } from 'cache/page-cache-handler'; const { ENV, NODE_ENV, DOCKER_HOST_ADDRESS, REVALIDATOR_PROXY_ORIGIN, SERVICE_SECRET } = process.env; @@ -22,31 +22,26 @@ const getPodAddress = () => { const podAddress = nets?.eth0?.[0]?.address; if (!podAddress) { - logger.error( - 'Error: pod IP address could not be determined' + - ' - Event driven cache regeneration will not be active for this instance' - ); + logger.error('Error: pod IP address could not be determined!'); return null; } return podAddress; }; -const getProxyLivenessUrl = (buildId: string) => { +const getProxyLivenessUrl = () => { const podAddress = getPodAddress(); return podAddress ? `${REVALIDATOR_PROXY_ORIGIN}/liveness${objectToQueryString({ address: podAddress, - redisPrefixes: [getRenderCacheKeyPrefix(buildId), getResponseCacheKeyPrefix()].join( - ',' - ), + redisPrefixes: redisCache.getKeyPrefixes().join(','), })}` : null; }; let didStart = false; -export const initRevalidatorProxyHeartbeat = (buildId: string) => { +export const initRevalidatorProxyHeartbeat = () => { if (NODE_ENV === 'development') { return; } @@ -56,16 +51,17 @@ export const initRevalidatorProxyHeartbeat = (buildId: string) => { return; } - const url = getProxyLivenessUrl(buildId); - if (!url) { - return; - } - didStart = true; logger.info('Starting heartbeat loop'); const heartbeatFunc = () => { + const url = getProxyLivenessUrl(); + if (!url) { + logger.error('Failed to determine revalidator heartbeat url!'); + return; + } + fetch(url, { headers: { secret: SERVICE_SECRET }, }).catch((e) => logger.error(`Failed to send heartbeat signal - ${e}`)); diff --git a/server/src/server-setup/server-setup-dev.ts b/server/src/server-setup/server-setup-dev.ts index b99ffe5c0..e81307d48 100644 --- a/server/src/server-setup/server-setup-dev.ts +++ b/server/src/server-setup/server-setup-dev.ts @@ -1,6 +1,5 @@ import { Express } from 'express'; import { NextServer } from 'next/dist/server/next'; -import { logger } from 'srcCommon/logger'; const DEV_NAIS_DOMAIN = 'ansatt.dev.nav.no'; const APP_ORIGIN = process.env.APP_ORIGIN; @@ -26,7 +25,6 @@ export const serverSetupDev = (expressApp: Express, nextApp: NextServer) => { if (APP_ORIGIN.endsWith(DEV_NAIS_DOMAIN)) { expressApp.all('*', (req, res, next) => { if (!req.hostname.endsWith(DEV_NAIS_DOMAIN)) { - logger.info('Redirecting!'); return res.redirect(302, `${APP_ORIGIN}${req.path}`); } diff --git a/server/src/server-setup/server-setup.ts b/server/src/server-setup/server-setup.ts index 8ce341945..a6cc8b592 100644 --- a/server/src/server-setup/server-setup.ts +++ b/server/src/server-setup/server-setup.ts @@ -9,7 +9,12 @@ import { handleInvalidateAllReq } from 'req-handlers/invalidate-all'; import { handleGetPendingResponses } from 'req-handlers/pending-responses'; import { serverSetupDev } from 'server-setup/server-setup-dev'; import { logger } from 'srcCommon/logger'; -import { redisCache } from 'cache/page-cache-handler'; +import PageCacheHandler, { redisCache } from 'cache/page-cache-handler'; +import { + addDecoratorUpdateListener, + getDecoratorVersionId, +} from '@navikt/nav-dekoratoren-moduler/ssr'; +import { decoratorEnvProps } from 'srcCommon/decorator-utils-serverside'; // Set the no-cache header on json files from the incremental cache to ensure // data requested during client side navigation is always validated if cached @@ -29,7 +34,18 @@ export const serverSetup = async (expressApp: Express, nextApp: NextServer) => { const nextServer = await getNextServer(nextApp); const currentBuildId = getNextBuildId(nextServer); - await redisCache.init(currentBuildId); + const decoratorVersionId = await getDecoratorVersionId(decoratorEnvProps); + if (!decoratorVersionId) { + logger.error('Failed to fetch decorator version id!'); + } + + await redisCache.init(currentBuildId, decoratorVersionId); + + addDecoratorUpdateListener(decoratorEnvProps, (versionId) => { + logger.info(`New decorator version: ${versionId} - clearing render caches`); + redisCache.updateRenderCacheKeyPrefix(versionId); + new PageCacheHandler().clear(); + }); logger.info(`Current build id: ${currentBuildId}`); diff --git a/server/src/server.ts b/server/src/server.ts index 072b0f849..6f6c93751 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -5,7 +5,7 @@ import promBundle from 'express-prom-bundle'; import { initRevalidatorProxyHeartbeat } from 'cache/revalidator-proxy-heartbeat'; import { serverSetupFailover } from 'server-setup/server-setup-failover'; import { serverSetup } from 'server-setup/server-setup'; -import { getNextBuildId, getNextServer } from 'next-utils'; +import { getNextServer } from 'next-utils'; import { logger } from 'srcCommon/logger'; import path from 'path'; import { injectNextImageCacheDir } from 'cache/image-cache-handler'; @@ -47,6 +47,11 @@ nextApp.prepare().then(async () => { next(); }); + expressApp.all('*', (req, res, next) => { + res.setHeader('app-name', 'nav-enonicxp-frontend'); + next(); + }); + if (isFailover) { serverSetupFailover(expressApp, nextApp); } else { @@ -78,8 +83,7 @@ nextApp.prepare().then(async () => { } if (!isFailover) { - const buildId = getNextBuildId(nextServer); - initRevalidatorProxyHeartbeat(buildId); + initRevalidatorProxyHeartbeat(); } logger.info(`Server started on port ${port}`); diff --git a/src/components/PageWrapper.tsx b/src/components/PageWrapper.tsx index d83c26552..188ff426c 100644 --- a/src/components/PageWrapper.tsx +++ b/src/components/PageWrapper.tsx @@ -4,7 +4,7 @@ import { onBreadcrumbClick, onLanguageSelect, setParams } from '@navikt/nav-deko import { ContentProps } from 'types/content-props/_content-common'; import { hookAndInterceptInternalLink, prefetchOnMouseover } from 'utils/links'; import { hasWhiteHeader, hasWhitePage } from 'utils/appearance'; -import { getDecoratorParams } from 'utils/decorator/decorator-utils'; +import { getDecoratorParams } from 'utils/decorator-utils'; import { getInternalRelativePath } from 'utils/urls'; import { store } from 'store/store'; import { fetchAndSetInnloggingsstatus } from 'utils/fetch/fetch-innloggingsstatus'; diff --git a/src/components/_common/metatags/DocumentParameterMetatags.tsx b/src/components/_common/metatags/DocumentParameterMetatags.tsx index 0aede34e4..a8e3c776a 100644 --- a/src/components/_common/metatags/DocumentParameterMetatags.tsx +++ b/src/components/_common/metatags/DocumentParameterMetatags.tsx @@ -1,7 +1,7 @@ import React from 'react'; import Head from 'next/head'; import { ContentProps } from 'types/content-props/_content-common'; -import { getDecoratorParams } from 'utils/decorator/decorator-utils'; +import { getDecoratorParams } from 'utils/decorator-utils'; import { isLegacyContentType } from 'utils/content-types'; type Props = { diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index 251c54227..d5b59f67a 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -4,7 +4,7 @@ import { DocumentInitialProps } from 'next/dist/pages/_document'; import { DecoratorComponents } from '@navikt/nav-dekoratoren-moduler/ssr'; import { Language } from 'translations'; import { DocumentParameter } from 'components/_common/metatags/DocumentParameterMetatags'; -import { getDecoratorComponents } from 'utils/decorator/decorator-utils-serverside'; +import { getDecoratorComponents } from 'srcCommon/decorator-utils-serverside'; type DocumentProps = { language: Language; @@ -57,7 +57,14 @@ class MyDocument extends Document { return ( - {Decorator && } + + {Decorator && ( + <> + + + + )} + {Decorator && }
diff --git a/src/utils/decorator/decorator-utils.ts b/src/utils/decorator-utils.ts similarity index 95% rename from src/utils/decorator/decorator-utils.ts rename to src/utils/decorator-utils.ts index d606afb85..c96608866 100644 --- a/src/utils/decorator/decorator-utils.ts +++ b/src/utils/decorator-utils.ts @@ -1,11 +1,11 @@ import { DecoratorParams } from '@navikt/nav-dekoratoren-moduler'; import { Language } from 'translations'; -import { getContentLanguages } from 'utils/languages'; import { ContentProps, ContentType } from 'types/content-props/_content-common'; import { LanguageProps } from 'types/language'; -import { stripXpPathPrefix } from 'utils/urls'; import { Audience, getAudience } from 'types/component-props/_mixins'; -import { hasWhiteHeader } from 'utils/appearance'; +import { stripXpPathPrefix } from './urls'; +import { getContentLanguages } from './languages'; +import { hasWhiteHeader } from './appearance'; const defaultLanguage: DecoratorParams['language'] = 'nb'; diff --git a/src/utils/fetch/fetch-content.ts b/src/utils/fetch/fetch-content.ts index a1faaa981..87c426dc1 100644 --- a/src/utils/fetch/fetch-content.ts +++ b/src/utils/fetch/fetch-content.ts @@ -26,7 +26,7 @@ const getXpCacheKey = }) : () => ({}); -const redisCache = await new RedisCache().init(process.env.BUILD_ID); +const redisCache = await new RedisCache().init(process.env.BUILD_ID, ''); const fetchConfig = { headers: { diff --git a/src/utils/decorator/decorator-utils-serverside.ts b/srcCommon/decorator-utils-serverside.ts similarity index 86% rename from src/utils/decorator/decorator-utils-serverside.ts rename to srcCommon/decorator-utils-serverside.ts index 51079ea38..dd242c749 100644 --- a/src/utils/decorator/decorator-utils-serverside.ts +++ b/srcCommon/decorator-utils-serverside.ts @@ -1,9 +1,9 @@ import { + fetchDecoratorReact, DecoratorEnvProps, DecoratorFetchProps, - fetchDecoratorReact, + DecoratorParams, } from '@navikt/nav-dekoratoren-moduler/ssr'; -import { DecoratorParams } from '@navikt/nav-dekoratoren-moduler'; type AppEnv = typeof process.env.ENV; type DecoratorEnv = DecoratorEnvProps['env']; @@ -17,7 +17,7 @@ const envMap: Record = { const decoratorEnv = envMap[process.env.ENV] || 'prod'; -const envProps: DecoratorFetchProps = { +export const decoratorEnvProps: DecoratorFetchProps = { noCache: process.env.DECORATOR_NOCACHE === 'true', ...(decoratorEnv === 'localhost' ? { env: 'localhost', localUrl: process.env.DECORATOR_URL } @@ -26,7 +26,7 @@ const envProps: DecoratorFetchProps = { export const getDecoratorComponents = async (params?: DecoratorParams) => { const decoratorComponents = fetchDecoratorReact({ - ...envProps, + ...decoratorEnvProps, params, }); diff --git a/srcCommon/redis.ts b/srcCommon/redis.ts index 080d55ff6..901862703 100644 --- a/srcCommon/redis.ts +++ b/srcCommon/redis.ts @@ -31,7 +31,9 @@ class RedisCacheImpl { private readonly responseCacheTTL: number = TIME_72_HOURS_IN_MS; private readonly renderCacheTTL: number = TIME_24_HOURS_IN_MS; - private readonly responseCacheKeyPrefix = getResponseCacheKeyPrefix(); + private buildId: string = ''; + + private readonly responseCacheKeyPrefix = `${process.env.ENV}:xp-response`; private renderCacheKeyPrefix = ''; constructor() { @@ -53,8 +55,9 @@ class RedisCacheImpl { }); } - public async init(buildId: string) { - this.renderCacheKeyPrefix = getRenderCacheKeyPrefix(buildId); + public async init(buildId: string, decoratorVersionId: string) { + this.buildId = buildId; + this.updateRenderCacheKeyPrefix(decoratorVersionId); return this.client.connect().then(() => { logger.info( @@ -64,24 +67,34 @@ class RedisCacheImpl { }); } + public getKeyPrefixes() { + return [this.responseCacheKeyPrefix, this.renderCacheKeyPrefix]; + } + + public updateRenderCacheKeyPrefix(decoratorVersionId: string) { + this.renderCacheKeyPrefix = `${process.env.ENV}:render:${this.buildId}:${decoratorVersionId}`; + } + public async getRender(key: string) { + const fullKey = this.getFullKey(key, this.renderCacheKeyPrefix); return this.client - .getEx(this.getFullKey(key, this.renderCacheKeyPrefix), { + .getEx(fullKey, { PX: this.renderCacheTTL, }) .then((result) => (result ? JSON.parse(result) : result)) .catch((e) => { - logger.error(`Error getting render cache value for key ${key} - ${e}`); + logger.error(`Error getting render cache value for key ${fullKey} - ${e}`); return Promise.resolve(null); }); } public async getResponse(key: string) { + const fullKey = this.getFullKey(key, this.responseCacheKeyPrefix); return this.client - .get(this.getFullKey(key, this.responseCacheKeyPrefix)) + .get(fullKey) .then((result) => (result ? JSON.parse(result) : result)) .catch((e) => { - logger.error(`Error getting value for key ${key} - ${e}`); + logger.error(`Error getting value for key ${fullKey} - ${e}`); return Promise.resolve(null); }); } @@ -123,6 +136,10 @@ class RedisCacheDummy extends RedisCacheImpl { return this; } + public updateRenderCacheKeyPrefix(key: string) { + return; + } + public async getRender(key: string) { return null; } @@ -146,7 +163,3 @@ export const RedisCache = validateClientOptions() ? RedisCacheImpl : RedisCacheDummy; - -export const getRenderCacheKeyPrefix = (buildId: string) => `${process.env.ENV}:render:${buildId}`; - -export const getResponseCacheKeyPrefix = () => `${process.env.ENV}:xp-response`;