feat(RecommendationsRegistry): uses deterministic method to choose which banner is displayed (#6947)

feat(RecommendationsRegistry): uses deterministic to provide list of extension banners

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>
This commit is contained in:
axel7083 2024-04-25 16:31:37 +02:00 committed by GitHub
parent 2b40abad53
commit 299fb52774
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 101 additions and 20 deletions

View file

@ -248,6 +248,85 @@ describe('getExtensionBanners', () => {
expect(featuredMock.getFeaturedExtensions).toHaveBeenCalled();
});
test('same time should return same arrays', async () => {
getRecommendationIgnored.mockReturnValue(false);
vi.mocked(featuredMock.getFeaturedExtensions).mockResolvedValue(
Array.from({ length: 10 }, (_, i) => ({
id: `dummy.id-${i}`,
builtin: false,
description: '',
categories: [],
displayName: '',
fetchable: false,
icon: '',
installed: false,
})),
);
vi.setSystemTime(new Date(2050, 1, 1, 1));
const base = await recommendationsRegistry.getExtensionBanners(5);
for (let i = 0; i < 10; i++) {
expect(base).toStrictEqual(await recommendationsRegistry.getExtensionBanners(5));
}
});
test('different hours should return different arrays', async () => {
getRecommendationIgnored.mockReturnValue(false);
vi.mocked(featuredMock.getFeaturedExtensions).mockResolvedValue(
Array.from({ length: 10 }, (_, i) => ({
id: `dummy.id-${i}`,
builtin: false,
description: '',
categories: [],
displayName: '',
fetchable: false,
icon: '',
installed: false,
})),
);
vi.setSystemTime(new Date(2050, 1, 1, 1));
const resultA = await recommendationsRegistry.getExtensionBanners(5);
expect(resultA.length).toBe(5);
vi.setSystemTime(new Date(2050, 1, 1, 2));
const resultB = await recommendationsRegistry.getExtensionBanners(5);
expect(resultB.length).toBe(5);
expect(resultA).not.toStrictEqual(resultB);
});
test('all elements should have been shown in one day', async () => {
getRecommendationIgnored.mockReturnValue(false);
const featured = Array.from({ length: 10 }, (_, i) => ({
id: `dummy.id-${i}`,
builtin: false,
description: '',
categories: [],
displayName: '',
fetchable: false,
icon: '',
installed: false,
}));
vi.mocked(featuredMock.getFeaturedExtensions).mockResolvedValue(featured);
const expectedIds: Set<string> = new Set(featured.map(item => item.id));
const actualsIds: Set<string> = new Set();
for (let h = 0; h < 24; h++) {
vi.setSystemTime(new Date(2050, 1, 1, h));
const banners = await recommendationsRegistry.getExtensionBanners(1);
expect(banners.length).toBe(1);
actualsIds.add(banners[0].extensionId);
}
expect(expectedIds).toStrictEqual(actualsIds);
});
});
describe('getRegistries', () => {

View file

@ -93,33 +93,35 @@ export class RecommendationsRegistry {
);
// Filter and shuffle the extensions
const extensionBanners: ExtensionBanner[] = recommendations.extensions
.reduce((prev, extension) => {
// ensure the extension is in the featured extensions and is not install
if (!(extension.extensionId in featuredExtensions) || featuredExtensions[extension.extensionId].installed) {
const extensionBanners: ExtensionBanner[] = recommendations.extensions.reduce((prev, extension) => {
// ensure the extension is in the featured extensions and is not install
if (!(extension.extensionId in featuredExtensions) || featuredExtensions[extension.extensionId].installed) {
return prev;
}
// Check for publishDate property
if ('publishDate' in extension && typeof extension.publishDate === 'string') {
const publishDate = new Date(extension.publishDate).getTime();
if (isNaN(publishDate) || publishDate > Date.now()) {
return prev;
}
}
// Check for publishDate property
if ('publishDate' in extension && typeof extension.publishDate === 'string') {
const publishDate = new Date(extension.publishDate).getTime();
if (isNaN(publishDate) || publishDate > Date.now()) {
return prev;
}
}
prev.push({
...extension,
featured: featuredExtensions[extension.extensionId],
});
prev.push({
...extension,
featured: featuredExtensions[extension.extensionId],
});
return prev;
}, [] as ExtensionBanner[])
.toSorted(() => Math.random() - 0.5);
return prev;
}, [] as ExtensionBanner[]);
// Limit the number of
if (limit >= 0 && extensionBanners.length > limit) {
return extensionBanners.toSpliced(limit);
// instead of using random generator we ensure deterministic results for a period of time (here by the hours)
const startingIndex = new Date().getHours() % extensionBanners.length;
// Let's return the subset of banners starting at the chosen index
return Array.from({ length: limit }, (_, i) => extensionBanners[(startingIndex + i) % extensionBanners.length]);
}
return extensionBanners;
}