From 46f35156987afcf7ae8342d6a50e327a00b71c5e Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 22 Nov 2023 23:46:55 +0800 Subject: [PATCH 1/3] feat: wildcard search for labels and nested labels --- packages/api/src/services/library_item.ts | 61 +++++++++++++++++++---- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/packages/api/src/services/library_item.ts b/packages/api/src/services/library_item.ts index 02d60e1d0..2be6a0311 100644 --- a/packages/api/src/services/library_item.ts +++ b/packages/api/src/services/library_item.ts @@ -156,22 +156,61 @@ const buildWhereClause = ( if (includeLabels && includeLabels.length > 0) { includeLabels.forEach((includeLabel, i) => { const param = `includeLabels_${i}` - queryBuilder.andWhere( - `lower(array_cat(library_item.label_names, library_item.highlight_labels)::text)::text[] && ARRAY[:...${param}]::text[]`, - { - [param]: includeLabel.labels, - } + const hasWildcard = includeLabel.labels.some((label) => + label.includes('*') ) + if (hasWildcard) { + queryBuilder.andWhere( + new Brackets((qb) => { + includeLabel.labels.forEach((label, j) => { + const param = `includeLabels_${i}_${j}` + qb.orWhere( + `array_to_string(array_cat(library_item.label_names, library_item.highlight_labels)::text[], ',') ILIKE :${param}`, + { + [param]: label.replace(/\*/g, '%'), + } + ) + }) + }) + ) + } else { + queryBuilder.andWhere( + `lower(array_cat(library_item.label_names, library_item.highlight_labels)::text)::text[] && ARRAY[:...${param}]::text[]`, + { + [param]: includeLabel.labels, + } + ) + } }) } if (excludeLabels && excludeLabels.length > 0) { - queryBuilder.andWhere( - 'NOT lower(array_cat(library_item.label_names, library_item.highlight_labels)::text)::text[] && ARRAY[:...excludeLabels]::text[]', - { - excludeLabels: excludeLabels.flatMap((filter) => filter.labels), - } - ) + const labels = excludeLabels.flatMap((filter) => filter.labels) + + const hasWildcard = labels.some((label) => label.includes('*')) + + if (hasWildcard) { + queryBuilder.andWhere( + new Brackets((qb) => { + labels.forEach((label, i) => { + const param = `excludeLabels_${i}` + qb.andWhere( + `array_to_string(array_cat(library_item.label_names, library_item.highlight_labels)::text[], ',') NOT ILIKE :${param}`, + { + [param]: label.replace(/\*/g, '%'), + } + ) + }) + }) + ) + } else { + queryBuilder.andWhere( + 'NOT lower(array_cat(library_item.label_names, library_item.highlight_labels)::text)::text[] && ARRAY[:...excludeLabels]::text[]', + { + excludeLabels: labels, + } + ) + } } } From 7c56dc2cbaa0918ed870db9d9b66091177f5a62a Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 23 Nov 2023 21:33:04 +0800 Subject: [PATCH 2/3] add test cases --- packages/api/test/resolvers/article.test.ts | 48 +++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/packages/api/test/resolvers/article.test.ts b/packages/api/test/resolvers/article.test.ts index 6fc8f5f88..d04d8933a 100644 --- a/packages/api/test/resolvers/article.test.ts +++ b/packages/api/test/resolvers/article.test.ts @@ -1195,6 +1195,54 @@ describe('Article API', () => { }) }) + context('when wildcard search for labels', () => { + let items: LibraryItem[] = [] + let labelIds: string[] + + before(async () => { + keyword = 'label:test/*' + // Create some test items + const label1 = await createLabel('test/one', '', user.id) + const label2 = await createLabel('test/two', '', user.id) + labelIds = [label1.id, label2.id] + + items = await createLibraryItems( + [ + { + user, + title: 'test title wildcard', + readableContent: '

test wildcard

', + slug: 'test slug wildcard', + originalUrl: `${url}/wildcard`, + }, + { + user, + title: 'test title wildcard 1', + readableContent: '

test wildcard

', + slug: 'test slug wildcard 1', + originalUrl: `${url}/wildcard_1`, + }, + ], + user.id + ) + await saveLabelsInLibraryItem([label1], items[0].id, user.id) + await saveLabelsInLibraryItem([label2], items[1].id, user.id) + }) + + after(async () => { + await deleteLabels(labelIds, user.id) + await deleteLibraryItems(items, user.id) + }) + + it('returns library items with label test/one and test/two', async () => { + const res = await graphqlRequest(query, authToken).expect(200) + + expect(res.body.data.search.pageInfo.totalCount).to.eq(2) + expect(res.body.data.search.edges[0].node.id).to.eq(items[0].id) + expect(res.body.data.search.edges[1].node.id).to.eq(items[1].id) + }) + }) + context('when type:file label:test is in the query', () => { let items: LibraryItem[] = [] let label: Label From da2b3c68951e07442f04b39176b319e1d52e9255 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 23 Nov 2023 21:34:35 +0800 Subject: [PATCH 3/3] add test cases --- packages/api/test/resolvers/article.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/api/test/resolvers/article.test.ts b/packages/api/test/resolvers/article.test.ts index d04d8933a..e9b390716 100644 --- a/packages/api/test/resolvers/article.test.ts +++ b/packages/api/test/resolvers/article.test.ts @@ -1222,6 +1222,13 @@ describe('Article API', () => { slug: 'test slug wildcard 1', originalUrl: `${url}/wildcard_1`, }, + { + user, + title: 'test title wildcard 2', + readableContent: '

test wildcard

', + slug: 'test slug wildcard 2', + originalUrl: `${url}/wildcard_2`, + }, ], user.id )