Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: highlighted views and queries #1953

Merged
merged 10 commits into from
May 30, 2024
4 changes: 4 additions & 0 deletions .infra/crons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,8 @@ export const crons: Cron[] = [
memory: '1Gi',
},
},
{
name: 'update-highlighted-views',
schedule: '15 4 * * 0',
},
];
22 changes: 0 additions & 22 deletions __tests__/__snapshots__/tags.ts.snap
Original file line number Diff line number Diff line change
@@ -1,27 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`query popularTags should return most popular tags ordered by value 1`] = `
Object {
"popularTags": Array [
Object {
"name": "development",
},
Object {
"name": "fullstack",
},
Object {
"name": "golang",
},
Object {
"name": "rust",
},
Object {
"name": "webdev",
},
],
}
`;

exports[`query searchTags should search for tags and order by value 1`] = `
Object {
"searchTags": Object {
Expand Down
10 changes: 10 additions & 0 deletions __tests__/cron/updateHighlightedViews.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { crons } from '../../src/cron/index';
import cron from '../../src/cron/updateHighlightedViews';

describe('updateHighlightedViews cron', () => {
it('should be registered', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. Registering the worker gets missed sometimes and this would ensure they got listed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100% agreed on this :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, all the recent crons/worker have this 👌

const registeredWorker = crons.find((item) => item.name === cron.name);

expect(registeredWorker).toBeDefined();
});
});
178 changes: 178 additions & 0 deletions __tests__/sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
NotificationPreferenceSource,
Post,
PostKeyword,
PostType,
SharePost,
Source,
SourceFeed,
Expand Down Expand Up @@ -232,6 +233,183 @@ describe('query sources', () => {
});
});

describe('query mostRecentSources', () => {
const QUERY = `
query MostRecentSources {
mostRecentSources {
id
name
image
public
}
}
`;

it('should return most recent sources', async () => {
const res = await client.query(QUERY);
expect(res.errors).toBeFalsy();
expect(res.data).toMatchObject({
mostRecentSources: [
{ id: 'a', name: 'A', image: 'http://image.com/a', public: true },
{ id: 'b', name: 'B', image: 'http://image.com/b', public: true },
],
});
});
});

describe('query trendingSources', () => {
const QUERY = `
query TrendingSources {
trendingSources {
id
name
image
public
}
}
`;

it('should return most trending sources', async () => {
await con.getRepository(Post).save(
new Array(5).fill('a').map((item, index) => {
return {
id: `post_${index}`,
shortId: `post_${index}`,
title: `Post ${index}`,
tagsStr: 'tag1',
upvotes: 10 + index,
createdAt: new Date(),
sourceId: 'a',
};
}),
);
await con.getRepository(Post).save({
id: `post_6`,
shortId: `post_6`,
title: `Post 6`,
tagsStr: 'tag1',
upvotes: 10,
createdAt: new Date(),
sourceId: 'b',
});
await con.query(`REFRESH MATERIALIZED VIEW trending_post`);
await con.query(`REFRESH MATERIALIZED VIEW trending_source`);

const res = await client.query(QUERY);
expect(res.errors).toBeFalsy();
expect(res.data).toMatchObject({
trendingSources: [
{ id: 'a', name: 'A', image: 'http://image.com/a', public: null },
],
});
});
});

describe('query popularSources', () => {
const QUERY = `
query PopularSources {
popularSources {
id
name
image
public
}
}
`;

it('should return most popular sources', async () => {
await con.getRepository(Post).save(
new Array(6).fill('a').map((item, index) => {
return {
id: `post_${index}`,
shortId: `post_${index}`,
title: `Post ${index}`,
tagsStr: 'tag1',
upvotes: 10 + index,
createdAt: new Date(),
sourceId: 'a',
};
}),
);
await con.getRepository(Post).save(
new Array(5).fill('b').map((item, index) => {
return {
id: `post_${index}`,
shortId: `post_${index}`,
title: `Post ${index}`,
tagsStr: 'tag1',
upvotes: 10 + index,
createdAt: new Date(),
sourceId: 'a',
};
}),
);
await con.query(`REFRESH MATERIALIZED VIEW popular_post`);
await con.query(`REFRESH MATERIALIZED VIEW popular_source`);

const res = await client.query(QUERY);
expect(res.errors).toBeFalsy();
expect(res.data).toMatchObject({
popularSources: [
{ id: 'a', name: 'A', image: 'http://image.com/a', public: null },
],
});
});
});

describe('query topVideoSources', () => {
const QUERY = `
query TopVideoSources {
topVideoSources {
id
name
image
public
}
}
`;

it('should return top video sources', async () => {
await con.getRepository(Post).save(
new Array(6).fill('a').map((item, index) => {
return {
id: `post_a_${index}`,
shortId: `post_a_${index}`,
title: `Post ${index}`,
tagsStr: 'tag1',
upvotes: 10 + index,
createdAt: new Date(),
sourceId: 'a',
type: PostType.VideoYouTube,
};
}),
);
await con.getRepository(Post).save(
new Array(6).fill('b').map((item, index) => {
return {
id: `post_b_${index}`,
shortId: `post_b_${index}`,
title: `Post ${index}`,
tagsStr: 'tag1',
upvotes: 10 + index,
createdAt: new Date(),
sourceId: 'b',
};
}),
);
await con.query(`REFRESH MATERIALIZED VIEW popular_video_post`);
await con.query(`REFRESH MATERIALIZED VIEW popular_video_source`);

const res = await client.query(QUERY);
expect(res.errors).toBeFalsy();
expect(res.data).toMatchObject({
topVideoSources: [
{ id: 'a', name: 'A', image: 'http://image.com/a', public: null },
],
});
});
});

describe('query sourceByFeed', () => {
const QUERY = `
query SourceByFeed($data: String!) {
Expand Down
88 changes: 67 additions & 21 deletions __tests__/tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
saveFixtures,
testQueryError,
} from './helpers';
import { ArticlePost, Keyword, PostKeyword, Source } from '../src/entity';
import { ArticlePost, Keyword, Post, PostKeyword, Source } from '../src/entity';
import {
keywordsFixture,
postRecommendedKeywordsFixture,
Expand Down Expand Up @@ -68,27 +68,46 @@ describe('query trendingTags', () => {
}
}`;

beforeEach(async () => {
await saveFixtures(con, Source, sourcesFixture);
let tags = 'tag1';
await con.getRepository(Post).save(
new Array(20).fill('tag').map((item, index) => {
tags += `,tag${index + 1}`;
return {
id: `post_${index}`,
shortId: `post_${index}`,
title: `Post ${index}`,
tagsStr: tags,
upvotes: 10 + index,
createdAt: new Date(),
sourceId: 'a',
};
}),
);
await con.query(`REFRESH MATERIALIZED VIEW trending_post`);
await con.query(`REFRESH MATERIALIZED VIEW trending_tag`);
});

it('should return most trending tags ordered by value', async () => {
const res = await client.query(QUERY);
expect(res.data).toMatchObject({
trendingTags: [
{ name: 'development' },
{ name: 'fullstack' },
{ name: 'golang' },
{ name: 'rust' },
{ name: 'webdev' },
{ name: 'tag1' },
{ name: 'tag2' },
{ name: 'tag3' },
{ name: 'tag4' },
{ name: 'tag5' },
{ name: 'tag6' },
{ name: 'tag7' },
{ name: 'tag8' },
{ name: 'tag9' },
{ name: 'tag10' },
],
});
});

it('should return limit of 10 by default', async () => {
await con.getRepository(Keyword).save(
new Array(20).fill('tag').map((item, index) => ({
value: item + index,
occurances: 0,
status: 'allow',
})),
);
const res = await client.query(QUERY);
expect(res.data.trendingTags.length).toBe(10);
});
Expand All @@ -101,19 +120,46 @@ describe('query popularTags', () => {
}
}`;

beforeEach(async () => {
await saveFixtures(con, Source, sourcesFixture);
let tags = 'tag1';
await con.getRepository(Post).save(
new Array(20).fill('tag').map((item, index) => {
tags += `,tag${index + 1}`;
return {
id: `post_${index}`,
shortId: `post_${index}`,
title: `Post ${index}`,
tagsStr: tags,
upvotes: 10 + index,
createdAt: new Date(),
sourceId: 'a',
};
}),
);
await con.query(`REFRESH MATERIALIZED VIEW popular_post`);
await con.query(`REFRESH MATERIALIZED VIEW popular_tag`);
});

it('should return most popular tags ordered by value', async () => {
const res = await client.query(QUERY);
expect(res.data).toMatchSnapshot();
expect(res.data).toMatchObject({
popularTags: [
{ name: 'tag1' },
{ name: 'tag2' },
{ name: 'tag3' },
{ name: 'tag4' },
{ name: 'tag5' },
{ name: 'tag6' },
{ name: 'tag7' },
{ name: 'tag8' },
{ name: 'tag9' },
{ name: 'tag10' },
],
});
});

it('should return limit of 10 by default', async () => {
await con.getRepository(Keyword).save(
new Array(20).fill('tag').map((item, index) => ({
value: item + index,
occurances: 0,
status: 'allow',
})),
);
const res = await client.query(QUERY);
expect(res.data.popularTags.length).toBe(10);
});
Expand Down
2 changes: 2 additions & 0 deletions src/cron/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import personalizedDigest from './personalizedDigest';
import generateSearchInvites from './generateSearchInvites';
import checkReferralReminder from './checkReferralReminder';
import dailyDigest from './dailyDigest';
import updateHighlightedViews from './updateHighlightedViews';

export const crons: Cron[] = [
updateViews,
Expand All @@ -25,4 +26,5 @@ export const crons: Cron[] = [
generateSearchInvites,
checkReferralReminder,
dailyDigest,
updateHighlightedViews,
];
Loading
Loading