Techdocs: support HumanDuration for cache config

Signed-off-by: Gabriel Dugny <gabriel.dugny@believe.com>
This commit is contained in:
Gabriel Dugny
2025-07-11 21:27:19 +02:00
parent 445cd3bb76
commit 063cdc5ed5
7 changed files with 66 additions and 28 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-techdocs-backend': minor
---
Techdocs: support HumanDuration for cache TTL and timeout configuration
+6 -4
View File
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
/**
* Configuration options for the techdocs-backend plugin
@@ -277,7 +279,7 @@ export interface Config {
*/
cache?: {
/**
* The cache time-to-live for TechDocs sites (in milliseconds). Set this
* The cache time-to-live for TechDocs sites, in milliseconds for a number or a human duration. Set this
* to a non-zero value to cache TechDocs sites and assets as they are
* read from storage.
*
@@ -285,16 +287,16 @@ export interface Config {
* and to pass a PluginCacheManager instance to TechDocs Backend's
* createRouter method in your backend.
*/
ttl: number;
ttl: number | HumanDuration | string;
/**
* The time (in milliseconds) that the TechDocs backend will wait for
* The time (in milliseconds for a number or a human duration) that the TechDocs backend will wait for
* a cache service to respond before continuing on as though the cached
* object was not found (e.g. when the cache sercice is unavailable).
*
* Defaults to 1000 milliseconds.
*/
readTimeout?: number;
readTimeout?: number | HumanDuration | string;
};
/**
+1
View File
@@ -71,6 +71,7 @@
"@backstage/plugin-search-backend-module-techdocs": "workspace:^",
"@backstage/plugin-techdocs-common": "workspace:^",
"@backstage/plugin-techdocs-node": "workspace:^",
"@backstage/types": "workspace:^",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"fs-extra": "^11.2.0",
+16 -3
View File
@@ -14,8 +14,9 @@
* limitations under the License.
*/
import { assertError, CustomErrorBase } from '@backstage/errors';
import { Config } from '@backstage/config';
import { Config, readDurationFromConfig } from '@backstage/config';
import { CacheService, LoggerService } from '@backstage/backend-plugin-api';
import { durationToMilliseconds } from '@backstage/types';
export class CacheInvalidationError extends CustomErrorBase {}
@@ -42,8 +43,20 @@ export class TechDocsCache {
config: Config,
{ cache, logger }: { cache: CacheService; logger: LoggerService },
) {
const timeout = config.getOptionalNumber('techdocs.cache.readTimeout');
const readTimeout = timeout === undefined ? 1000 : timeout;
let readTimeout: number;
if (config.has('techdocs.cache.readTimeout')) {
if (typeof config.get('techdocs.cache.readTimeout') === 'number') {
readTimeout = config.getNumber('techdocs.cache.readTimeout');
} else {
readTimeout = durationToMilliseconds(
readDurationFromConfig(config, {
key: 'techdocs.cache.readTimeout',
}),
);
}
} else {
readTimeout = 1000;
}
return new TechDocsCache({ cache, logger, readTimeout });
}
@@ -30,14 +30,10 @@ import { TechDocsCache } from '../cache';
import { mockErrorHandler, mockServices } from '@backstage/backend-test-utils';
jest.mock('@backstage/catalog-client');
jest.mock('@backstage/config');
jest.mock('./CachedEntityLoader');
jest.mock('./DocsSynchronizer');
jest.mock('../cache/TechDocsCache');
const MockedConfigReader = ConfigReader as jest.MockedClass<
typeof ConfigReader
>;
const MockDocsSynchronizer = DocsSynchronizer as jest.MockedClass<
typeof DocsSynchronizer
>;
@@ -115,7 +111,13 @@ describe('createRouter', () => {
preparers,
generators,
publisher,
config: new ConfigReader({}),
config: new ConfigReader({
techdocs: {
cache: {
ttl: 1,
},
},
}),
logger: mockServices.logger.mock(),
discovery,
cache: mockServices.cache.mock(),
@@ -143,9 +145,6 @@ describe('createRouter', () => {
discovery.getBaseUrl.mockImplementation(async type => {
return `http://backstage.local/api/${type}`;
});
MockedConfigReader.prototype.getOptionalNumber.mockImplementation(key =>
key === 'techdocs.cache.ttl' ? 1 : undefined,
);
MockTechDocsCache.get.mockResolvedValue(undefined);
MockTechDocsCache.set.mockResolvedValue();
});
@@ -326,13 +325,17 @@ data: {"updated":true}
});
it('should check entity access when permissions are enabled', async () => {
MockedConfigReader.prototype.getOptionalBoolean.mockImplementation(key =>
key === 'permission.enabled' ? true : undefined,
);
const docsRouter = jest.fn((_req, res) => res.sendStatus(200));
publisher.docsRouter.mockReturnValue(docsRouter);
const app = await createApp(outOfTheBoxOptions);
const app = await createApp({
...outOfTheBoxOptions,
config: new ConfigReader({
permission: {
enabled: true,
},
}),
});
MockCachedEntityLoader.prototype.load.mockResolvedValue(entity);
@@ -345,11 +348,14 @@ data: {"updated":true}
});
it('should not return assets without corresponding entity access', async () => {
MockedConfigReader.prototype.getOptionalBoolean.mockImplementation(key =>
key === 'permission.enabled' ? true : undefined,
);
const app = await createApp(outOfTheBoxOptions);
const app = await createApp({
...outOfTheBoxOptions,
config: new ConfigReader({
permission: {
enabled: true,
},
}),
});
MockCachedEntityLoader.prototype.load.mockResolvedValue(undefined);
+14 -4
View File
@@ -16,7 +16,7 @@
import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { Config, readDurationFromConfig } from '@backstage/config';
import { NotFoundError } from '@backstage/errors';
import {
DocsBuildStrategy,
@@ -41,6 +41,7 @@ import {
HttpAuthService,
LoggerService,
} from '@backstage/backend-plugin-api';
import { durationToMilliseconds } from '@backstage/types';
/**
* Required dependencies for running TechDocs in the "out-of-the-box"
@@ -130,9 +131,18 @@ export async function createRouter(
// Set up a cache client if configured.
let cache: TechDocsCache | undefined;
const defaultTtl = config.getOptionalNumber('techdocs.cache.ttl');
if (defaultTtl) {
const cacheClient = options.cache.withOptions({ defaultTtl });
if (config.has('techdocs.cache.ttl')) {
let ttlMs: number;
if (typeof config.get('techdocs.cache.ttl') === 'number') {
ttlMs = config.getNumber('techdocs.cache.ttl');
} else {
ttlMs = durationToMilliseconds(
readDurationFromConfig(config, {
key: 'techdocs.cache.ttl',
}),
);
}
const cacheClient = options.cache.withOptions({ defaultTtl: ttlMs });
cache = TechDocsCache.fromConfig(config, { cache: cacheClient, logger });
}
+1
View File
@@ -8720,6 +8720,7 @@ __metadata:
"@backstage/plugin-search-backend-module-techdocs": "workspace:^"
"@backstage/plugin-techdocs-common": "workspace:^"
"@backstage/plugin-techdocs-node": "workspace:^"
"@backstage/types": "workspace:^"
"@types/express": "npm:^4.17.6"
express: "npm:^4.17.1"
express-promise-router: "npm:^4.1.0"