Add multiget to usersettings to batch get()s in one call and cache the result for a short period

Signed-off-by: Gustaf Räntilä <g.rantila@gmail.com>
This commit is contained in:
Gustaf Räntilä
2025-09-05 14:11:36 +02:00
parent 3581e7d2c9
commit 104ca74ec2
13 changed files with 917 additions and 133 deletions
@@ -55,6 +55,7 @@
"@backstage/plugin-signals-node": "workspace:^",
"@backstage/plugin-user-settings-common": "workspace:^",
"@backstage/types": "workspace:^",
"already": "^2.2.1",
"express": "^4.22.0",
"express-promise-router": "^4.1.0",
"knex": "^3.0.0"
@@ -14,12 +14,17 @@
* limitations under the License.
*/
import { InputError } from '@backstage/errors';
import { InputError, serializeError } from '@backstage/errors';
import { map } from 'already';
import express, { Request } from 'express';
import Router from 'express-promise-router';
import { UserSettingsStore } from '../database/UserSettingsStore';
import { SignalsService } from '@backstage/plugin-signals-node';
import { UserSettingsSignal } from '@backstage/plugin-user-settings-common';
import {
MultiUserSetting,
UserSettingsSignal,
parseDataLoaderKey,
} from '@backstage/plugin-user-settings-common';
import { HttpAuthService } from '@backstage/backend-plugin-api';
export async function createRouter(options: {
@@ -40,6 +45,59 @@ export async function createRouter(options: {
return credentials.principal.userEntityRef;
};
// get multiple values
router.get('/multi', async (req, res) => {
const userEntityRef = await getUserEntityRef(req);
const bucketsAndKeys: ReturnType<typeof parseDataLoaderKey>[] = [];
const items = req.query.items;
if (typeof items === 'string') {
bucketsAndKeys.push(parseDataLoaderKey(items));
} else if (Array.isArray(items)) {
bucketsAndKeys.push(
...items.map(item => {
if (typeof item !== 'string') {
throw new InputError(
'Expected query param "items" to be an array of strings',
);
}
return parseDataLoaderKey(item);
}),
);
} else {
throw new InputError('Expected query param "items" to be an array');
}
const userSettings = await map(
bucketsAndKeys,
{ concurrency: 10 },
async ({ bucket, key }): Promise<MultiUserSetting> => {
try {
const setting = await options.userSettingsStore.get({
userEntityRef,
bucket,
key,
});
return setting;
} catch (e) {
if (e instanceof Error) {
const serialized = serializeError(e);
return { bucket, key, error: serialized };
}
return {
bucket,
key,
error: { name: 'Error', message: 'Unknown error' },
};
}
},
);
res.json(userSettings);
});
// get a single value
router.get('/buckets/:bucket/keys/:key(*)', async (req, res) => {
const userEntityRef = await getUserEntityRef(req);