use dataloader to fetch all labels of a list of linkIds in a single q… (#133)
* use dataloader to fetch all labels of a list of linkIds in a single query and cached * add labels in GQL query in frontend
This commit is contained in:
@ -79,9 +79,8 @@ import {
|
||||
generateDownloadSignedUrl,
|
||||
generateUploadFilePathName,
|
||||
} from '../utils/uploads'
|
||||
import { getRepository } from 'typeorm'
|
||||
import { Link } from '../entity/link'
|
||||
import { Label } from '../entity/label'
|
||||
import { labelsLoader } from '../services/labels'
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
type ResultResolveType = {
|
||||
@ -428,11 +427,9 @@ export const functionResolvers = {
|
||||
ctx.models
|
||||
)
|
||||
},
|
||||
async labels(article: { linkId: string }): Promise<Label[] | undefined> {
|
||||
const link = await getRepository(Link).findOne(article.linkId, {
|
||||
relations: ['labels'],
|
||||
})
|
||||
return link?.labels
|
||||
async labels(article: { linkId: string }): Promise<Label[]> {
|
||||
// retrieve labels for the link
|
||||
return labelsLoader.load(article.linkId)
|
||||
},
|
||||
},
|
||||
ArticleSavingRequest: {
|
||||
|
||||
@ -24,6 +24,7 @@ import { getManager, getRepository, ILike } from 'typeorm'
|
||||
import { setClaims } from '../../entity/utils'
|
||||
import { Link } from '../../entity/link'
|
||||
import { LinkLabel } from '../../entity/link_label'
|
||||
import { labelsLoader } from '../../services/labels'
|
||||
|
||||
export const labelsResolver = authorized<LabelsSuccess, LabelsError>(
|
||||
async (_obj, _params, { claims: { uid }, log }) => {
|
||||
@ -173,7 +174,7 @@ export const deleteLabelResolver = authorized<
|
||||
label,
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error)
|
||||
log.error('error', error)
|
||||
return {
|
||||
errorCodes: [DeleteLabelErrorCode.BadRequest],
|
||||
}
|
||||
@ -226,6 +227,9 @@ export const setLabelsResolver = authorized<
|
||||
.save(labels.map((label) => ({ link, label })))
|
||||
})
|
||||
|
||||
// clear cache
|
||||
labelsLoader.clear(linkId)
|
||||
|
||||
analytics.track({
|
||||
userId: uid,
|
||||
event: 'setLabels',
|
||||
|
||||
19
packages/api/src/services/labels.ts
Normal file
19
packages/api/src/services/labels.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import DataLoader from 'dataloader'
|
||||
import { Label } from '../entity/label'
|
||||
import { getRepository, In } from 'typeorm'
|
||||
import { Link } from '../entity/link'
|
||||
|
||||
const batchGetLabelsFromLinkIds = async (
|
||||
linkIds: readonly string[]
|
||||
): Promise<Label[][]> => {
|
||||
const links = await getRepository(Link).find({
|
||||
where: { id: In(linkIds as string[]) },
|
||||
relations: ['labels'],
|
||||
})
|
||||
|
||||
return linkIds.map(
|
||||
(linkId) => links.find((link) => link.id === linkId)?.labels || []
|
||||
)
|
||||
}
|
||||
|
||||
export const labelsLoader = new DataLoader(batchGetLabelsFromLinkIds)
|
||||
55
packages/api/test/services/labels.test.ts
Normal file
55
packages/api/test/services/labels.test.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import 'mocha'
|
||||
import { expect } from 'chai'
|
||||
import 'chai/register-should'
|
||||
import { labelsLoader } from '../../src/services/labels'
|
||||
import {
|
||||
createTestLabel,
|
||||
createTestLink,
|
||||
createTestPage,
|
||||
createTestUser,
|
||||
deleteTestUser,
|
||||
} from '../db'
|
||||
import { getRepository } from 'typeorm'
|
||||
import { LinkLabel } from '../../src/entity/link_label'
|
||||
import { Label } from '../../src/entity/label'
|
||||
import { Link } from '../../src/entity/link'
|
||||
|
||||
describe('batch get labels from linkIds', () => {
|
||||
let username = 'testUser'
|
||||
let labels: Label[] = []
|
||||
let link: Link
|
||||
|
||||
before(async () => {
|
||||
// create test user
|
||||
const user = await createTestUser(username)
|
||||
|
||||
// Create some test links
|
||||
const page = await createTestPage()
|
||||
link = await createTestLink(user, page)
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
// create testing labels
|
||||
const label = await createTestLabel(user, `label_${i}`, '#d55757')
|
||||
// set label to a link
|
||||
await getRepository(LinkLabel).save({
|
||||
link: link,
|
||||
label: label,
|
||||
})
|
||||
labels.push(label)
|
||||
}
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
// clean up
|
||||
await deleteTestUser(username)
|
||||
})
|
||||
|
||||
it('should return a list of label from one link', async () => {
|
||||
const result = await labelsLoader.load(link.id)
|
||||
|
||||
expect(result).length(3)
|
||||
expect(result[0].id).to.eql(labels[0].id)
|
||||
expect(result[1].id).to.eql(labels[1].id)
|
||||
expect(result[2].id).to.eql(labels[2].id)
|
||||
})
|
||||
})
|
||||
@ -76,6 +76,9 @@ const query = gql`
|
||||
highlights(input: { includeFriends: $includeFriendsHighlights }) {
|
||||
...HighlightFields
|
||||
}
|
||||
labels {
|
||||
...LabelFields
|
||||
}
|
||||
}
|
||||
}
|
||||
... on ArticleError {
|
||||
@ -85,6 +88,7 @@ const query = gql`
|
||||
}
|
||||
${articleFragment}
|
||||
${highlightFragment}
|
||||
${labelFragment}
|
||||
`
|
||||
export const cacheArticle = (
|
||||
mutate: ScopedMutator,
|
||||
|
||||
@ -6,6 +6,7 @@ import { articleFragment } from '../fragments/articleFragment'
|
||||
import { setLinkArchivedMutation } from '../mutations/setLinkArchivedMutation'
|
||||
import { deleteLinkMutation } from '../mutations/deleteLinkMutation'
|
||||
import { articleReadingProgressMutation } from '../mutations/articleReadingProgressMutation'
|
||||
import { labelFragment } from '../fragments/labelFragment'
|
||||
|
||||
export type LibraryItemsQueryInput = {
|
||||
limit: number
|
||||
@ -89,6 +90,9 @@ export function useGetLibraryItemsQuery({
|
||||
cursor
|
||||
node {
|
||||
...ArticleFields
|
||||
labels {
|
||||
...LabelFields
|
||||
}
|
||||
originalArticleUrl
|
||||
}
|
||||
}
|
||||
@ -106,6 +110,7 @@ export function useGetLibraryItemsQuery({
|
||||
}
|
||||
}
|
||||
${articleFragment}
|
||||
${labelFragment}
|
||||
`
|
||||
|
||||
const variables = {
|
||||
|
||||
Reference in New Issue
Block a user