diff --git a/backend/src/schema/resolvers/searches.js b/backend/src/schema/resolvers/searches.js index 250746835..c6896c10f 100644 --- a/backend/src/schema/resolvers/searches.js +++ b/backend/src/schema/resolvers/searches.js @@ -19,7 +19,8 @@ const cypherTemplate = (setup) => ` const simpleWhereClause = 'WHERE score >= 0.0 AND NOT (resource.deleted = true OR resource.disabled = true)' -const postWhereClause = `WHERE score >= 0.0 AND NOT ( +const postWhereClause = `WHERE score >= 0.0 + AND NOT ( author.deleted = true OR author.disabled = true OR resource.deleted = true OR resource.disabled = true OR (:User {id: $userId})-[:MUTED]->(author) @@ -66,26 +67,22 @@ const searchHashtagsSetup = { resultKeyName: 'hashtags', } +const countSetup = { + returnClause: 'toString(size(collect(resource)))', + limit: '', +} + const countUsersSetup = { ...searchUsersSetup, - ...{ - returnClause: 'toString(size(collect(resource)))', - limit: '', - }, + ...countSetup, } const countPostsSetup = { ...searchPostsSetup, - ...{ - returnClause: 'toString(size(collect(resource)))', - limit: '', - }, + ...countSetup, } const countHashtagsSetup = { ...searchHashtagsSetup, - ...{ - returnClause: 'toString(size(collect(resource)))', - limit: '', - }, + ...countSetup, } const searchResultPromise = async (session, setup, params) => { @@ -94,23 +91,20 @@ const searchResultPromise = async (session, setup, params) => { }) } -const getSearchResults = async (context, setup, params) => { - const session = context.driver.session() - try { - const results = await searchResultPromise(session, setup, params) - log(results) - return results.records.map((r) => r.get('result')) - } finally { - session.close() - } +const searchResultCallback = (result) => { + return result.records.map((r) => r.get('result')) } -const countSearchResults = async (context, setup, params) => { +const countResultCallback = (result) => { + return result.records[0].get('result') +} + +const getSearchResults = async (context, setup, params, resultCallback = searchResultCallback) => { const session = context.driver.session() try { const results = await searchResultPromise(session, setup, params) log(results) - return results.records[0].get('result') + return resultCallback(results) } finally { session.close() } @@ -127,15 +121,19 @@ export default { Query: { searchPosts: async (_parent, args, context, _resolveInfo) => { const { query, postsOffset, firstPosts } = args - // const { id: userId } = context.user - const userId = '73' + const { id: userId } = context.user return { - postCount: countSearchResults(context, countPostsSetup, { - query: queryString(query), - skip: 0, - userId, - }), + postCount: getSearchResults( + context, + countPostsSetup, + { + query: queryString(query), + skip: 0, + userId, + }, + countResultCallback, + ), posts: getSearchResults(context, searchPostsSetup, { query: queryString(query), skip: postsOffset, @@ -147,10 +145,15 @@ export default { searchUsers: async (_parent, args, context, _resolveInfo) => { const { query, usersOffset, firstUsers } = args return { - userCount: countSearchResults(context, countUsersSetup, { - query: queryString(query), - skip: 0, - }), + userCount: getSearchResults( + context, + countUsersSetup, + { + query: queryString(query), + skip: 0, + }, + countResultCallback, + ), users: getSearchResults(context, searchUsersSetup, { query: queryString(query), skip: usersOffset, @@ -161,10 +164,15 @@ export default { searchHashtags: async (_parent, args, context, _resolveInfo) => { const { query, hashtagsOffset, firstHashtags } = args return { - hashtagCount: countSearchResults(context, countHashtagsSetup, { - query: queryString(query), - skip: 0, - }), + hashtagCount: getSearchResults( + context, + countHashtagsSetup, + { + query: queryString(query), + skip: 0, + }, + countResultCallback, + ), hashtags: getSearchResults(context, searchHashtagsSetup, { query: queryString(query), skip: hashtagsOffset, @@ -187,83 +195,24 @@ export default { userId, } */ - const postCypher = ` - CALL db.index.fulltext.queryNodes('post_fulltext_search', $query) - YIELD node as resource, score - MATCH (resource)<-[:WROTE]-(author:User) - WHERE score >= 0.0 - AND NOT ( - author.deleted = true OR author.disabled = true - OR resource.deleted = true OR resource.disabled = true - OR (:User {id: $userId})-[:MUTED]->(author) - ) - WITH resource, author, - [(resource)<-[:COMMENTS]-(comment:Comment) | comment] as comments, - [(resource)<-[:SHOUTED]-(user:User) | user] as shouter - RETURN resource { - .*, - __typename: labels(resource)[0], - author: properties(author), - commentsCount: toString(size(comments)), - shoutedCount: toString(size(shouter)) - } - LIMIT $limit - ` - - const userCypher = ` - CALL db.index.fulltext.queryNodes('user_fulltext_search', $query) - YIELD node as resource, score - MATCH (resource) - WHERE score >= 0.0 - AND NOT (resource.deleted = true OR resource.disabled = true) - RETURN resource {.*, __typename: labels(resource)[0]} - LIMIT $limit - ` - const tagCypher = ` - CALL db.index.fulltext.queryNodes('tag_fulltext_search', $query) - YIELD node as resource, score - MATCH (resource) - WHERE score >= 0.0 - AND NOT (resource.deleted = true OR resource.disabled = true) - RETURN resource {.*, __typename: labels(resource)[0]} - LIMIT $limit - ` - - const myQuery = queryString(query) - - const session = context.driver.session() - const searchResultPromise = session.readTransaction(async (transaction) => { - const postTransactionResponse = transaction.run(postCypher, { - query: myQuery, + return [ + ...(await getSearchResults(context, searchPostsSetup, { + query: queryString(query), + skip: 0, limit, userId, - }) - const userTransactionResponse = transaction.run(userCypher, { - query: myQuery, + })), + ...(await getSearchResults(context, searchUsersSetup, { + query: queryString(query), + skip: 0, limit, - }) - const tagTransactionResponse = transaction.run(tagCypher, { - query: myQuery, + })), + ...(await getSearchResults(context, searchHashtagsSetup, { + query: queryString(query), + skip: 0, limit, - }) - return Promise.all([ - postTransactionResponse, - userTransactionResponse, - tagTransactionResponse, - ]) - }) - - try { - const [postResults, userResults, tagResults] = await searchResultPromise - log(postResults) - log(userResults) - log(tagResults) - return [...postResults.records, ...userResults.records, ...tagResults.records].map((r) => - r.get('resource'), - ) - } finally { - session.close() - } + })), + ] }, }, } diff --git a/backend/src/schema/resolvers/searches.spec.js b/backend/src/schema/resolvers/searches.spec.js index 3d7bd039d..5c08497cc 100644 --- a/backend/src/schema/resolvers/searches.spec.js +++ b/backend/src/schema/resolvers/searches.spec.js @@ -24,12 +24,12 @@ beforeAll(async () => { }) afterAll(async () => { - await cleanDatabase() + // await cleanDatabase() }) const searchQuery = gql` query($query: String!) { - findResources(query: $query, limit: 5) { + searchResults(query: $query, limit: 5) { __typename ... on Post { id @@ -65,7 +65,7 @@ describe('resolvers/searches', () => { variables = { query: 'John' } await expect(query({ query: searchQuery, variables })).resolves.toMatchObject({ data: { - findResources: [ + searchResults: [ { id: 'a-user', name: 'John Doe', @@ -95,7 +95,7 @@ describe('resolvers/searches', () => { variables = { query: 'beitrag' } await expect(query({ query: searchQuery, variables })).resolves.toMatchObject({ data: { - findResources: [ + searchResults: [ { __typename: 'Post', id: 'a-post', @@ -114,7 +114,7 @@ describe('resolvers/searches', () => { variables = { query: 'BEITRAG' } await expect(query({ query: searchQuery, variables })).resolves.toMatchObject({ data: { - findResources: [ + searchResults: [ { __typename: 'Post', id: 'a-post', @@ -132,7 +132,7 @@ describe('resolvers/searches', () => { it('returns empty search results', async () => { await expect( query({ query: searchQuery, variables: { query: 'Unfug' } }), - ).resolves.toMatchObject({ data: { findResources: [] } }) + ).resolves.toMatchObject({ data: { searchResults: [] } }) }) }) @@ -189,7 +189,7 @@ und hinter tausend Stäben keine Welt.`, variables = { query: 'beitrag' } await expect(query({ query: searchQuery, variables })).resolves.toMatchObject({ data: { - findResources: expect.arrayContaining([ + searchResults: expect.arrayContaining([ { __typename: 'Post', id: 'a-post', @@ -216,7 +216,7 @@ und hinter tausend Stäben keine Welt.`, variables = { query: 'tee-ei' } await expect(query({ query: searchQuery, variables })).resolves.toMatchObject({ data: { - findResources: [ + searchResults: [ { __typename: 'Post', id: 'g-post', @@ -235,7 +235,7 @@ und hinter tausend Stäben keine Welt.`, variables = { query: '„teeei“' } await expect(query({ query: searchQuery, variables })).resolves.toMatchObject({ data: { - findResources: [ + searchResults: [ { __typename: 'Post', id: 'g-post', @@ -256,7 +256,7 @@ und hinter tausend Stäben keine Welt.`, variables = { query: '(a - b)²' } await expect(query({ query: searchQuery, variables })).resolves.toMatchObject({ data: { - findResources: [ + searchResults: [ { __typename: 'Post', id: 'c-post', @@ -277,7 +277,7 @@ und hinter tausend Stäben keine Welt.`, variables = { query: '(a-b)²' } await expect(query({ query: searchQuery, variables })).resolves.toMatchObject({ data: { - findResources: [ + searchResults: [ { __typename: 'Post', id: 'c-post', @@ -298,7 +298,7 @@ und hinter tausend Stäben keine Welt.`, variables = { query: '+ b² 2.' } await expect(query({ query: searchQuery, variables })).resolves.toMatchObject({ data: { - findResources: [ + searchResults: [ { __typename: 'Post', id: 'c-post', @@ -321,7 +321,7 @@ und hinter tausend Stäben keine Welt.`, variables = { query: 'der panther' } await expect(query({ query: searchQuery, variables })).resolves.toMatchObject({ data: { - findResources: [ + searchResults: [ { __typename: 'Post', id: 'd-post', @@ -349,7 +349,7 @@ und hinter tausend Stäben keine Welt.`, variables = { query: 'Vorü Subs' } await expect(query({ query: searchQuery, variables })).resolves.toMatchObject({ data: { - findResources: expect.arrayContaining([ + searchResults: expect.arrayContaining([ { __typename: 'Post', id: 'd-post', @@ -395,7 +395,7 @@ und hinter tausend Stäben keine Welt.`, variables = { query: '-maria-' } await expect(query({ query: searchQuery, variables })).resolves.toMatchObject({ data: { - findResources: expect.arrayContaining([ + searchResults: expect.arrayContaining([ { __typename: 'User', id: 'c-user', @@ -440,7 +440,7 @@ und hinter tausend Stäben keine Welt.`, variables = { query: 'beitrag' } await expect(query({ query: searchQuery, variables })).resolves.toMatchObject({ data: { - findResources: expect.not.arrayContaining([ + searchResults: expect.not.arrayContaining([ { __typename: 'Post', id: 'muted-post', @@ -465,7 +465,7 @@ und hinter tausend Stäben keine Welt.`, variables = { query: 'myha' } await expect(query({ query: searchQuery, variables })).resolves.toMatchObject({ data: { - findResources: [ + searchResults: [ { __typename: 'Tag', id: 'myHashtag', diff --git a/webapp/components/_new/features/SearchResults/SearchResults.story.js b/webapp/components/_new/features/SearchResults/SearchResults.story.js index 1c8c9cb08..223152de2 100644 --- a/webapp/components/_new/features/SearchResults/SearchResults.story.js +++ b/webapp/components/_new/features/SearchResults/SearchResults.story.js @@ -11,7 +11,7 @@ import { user } from '~/components/UserTeaser/UserTeaser.story.js' helpers.init() -const postMock = fields => { +const postMock = (fields) => { return { ...post, id: faker.random.uuid(), @@ -24,7 +24,7 @@ const postMock = fields => { } } -const userMock = fields => { +const userMock = (fields) => { return { ...user, id: faker.random.uuid(), diff --git a/webapp/components/_new/features/SearchResults/SearchResults.vue b/webapp/components/_new/features/SearchResults/SearchResults.vue index 46d78dd43..78c9ed11c 100644 --- a/webapp/components/_new/features/SearchResults/SearchResults.vue +++ b/webapp/components/_new/features/SearchResults/SearchResults.vue @@ -15,7 +15,7 @@ - {{postPage}} + {{ postPage }} 0 }, + hasMorePosts() { + return (this.postPage + 1) * this.pageSize <= this.postCount + }, + hasMoreUsers() { + return (this.userPage + 1) * this.pageSize <= this.userCount + }, + hasMoreHashtags() { + return (this.hashtagPage + 1) * this.pageSize <= this.hashtagCount + }, }, methods: { switchTab(tab) { this.activeTab = tab }, previousPosts() { - this.postsOffset = this.postPage * this.pageSize this.postPage-- + this.postsOffset = this.postPage * this.pageSize }, nextPosts() { this.postsOffset += this.pageSize this.postPage++ }, previousUsers() { - this.usersOffset = Math.max(this.usersOffset - this.pageSize, 0) this.userPage-- + this.usersOffset = this.userPage * this.pageSize }, nextUsers() { this.usersOffset += this.pageSize this.userPage++ }, previousHashtags() { - this.usersOffset = Math.max(this.usersOffset - this.pageSize, 0) this.hashtagPage-- + this.hashtagsOffset = this.hashtagPage * this.pageSize }, nextHashtags() { - this.usersOffset += this.pageSize + this.hashtagsOffset += this.pageSize this.hashtagPage++ }, }, @@ -197,7 +201,6 @@ export default { update({ searchPosts }) { this.posts = searchPosts.posts this.postCount = searchPosts.postCount - this.hasMorePosts = this.postCount >= (this.pageSize * this.postPage) if (searchPosts.posts.length) this.activeTab = 'Post' }, fetchPolicy: 'cache-and-network', @@ -220,33 +223,36 @@ export default { update({ searchUsers }) { this.users = searchUsers.users this.userCount = searchUsers.userCount - this.hasMoreUsers = this.users.length >= this.pageSize if (!searchPosts.posts.length && searchUsers.users.length) this.activeTab = 'User' }, fetchPolicy: 'cache-and-network', }, searchHashtags: { - query() { - return searchHashtags - }, - variables() { - const { firstHashtags, hashtagsOffset, search } = this - return { - query: search, - firstHashtags, - hashtagsOffset, - } - }, - skip() { - return !this.search - }, - update({ searchHashtags }) { - this.hashtags = searchHashtags.hashtags - this.hashtagCount = searchHashtags.hashtagCount - this.hasMoreHashtags = this.hashtags.length >= this.pageSize - if (!searchPosts.posts.length && !searchUsers.users.length && searchHashtags.hashtags.length) this.activeTab = 'Hashtag' - }, - fetchPolicy: 'cache-and-network', + query() { + return searchHashtags + }, + variables() { + const { firstHashtags, hashtagsOffset, search } = this + return { + query: search, + firstHashtags, + hashtagsOffset, + } + }, + skip() { + return !this.search + }, + update({ searchHashtags }) { + this.hashtags = searchHashtags.hashtags + this.hashtagCount = searchHashtags.hashtagCount + if ( + !searchPosts.posts.length && + !searchUsers.users.length && + searchHashtags.hashtags.length + ) + this.activeTab = 'Hashtag' + }, + fetchPolicy: 'cache-and-network', }, }, } diff --git a/webapp/graphql/Search.js b/webapp/graphql/Search.js index 98bd007f4..b298b48c1 100644 --- a/webapp/graphql/Search.js +++ b/webapp/graphql/Search.js @@ -7,8 +7,7 @@ export const searchQuery = gql` ${tagsCategoriesAndPinnedFragment} query($query: String!) { - searchResults(query: $query, limit: 5) - { + searchResults(query: $query, limit: 5) { __typename ... on Post { ...post @@ -38,16 +37,16 @@ export const searchPosts = gql` searchPosts(query: $query, firstPosts: $firstPosts, postsOffset: $postsOffset) { postCount posts { - __typename - ...post - ...tagsCategoriesAndPinned - commentsCount - shoutedCount - author { - ...user + __typename + ...post + ...tagsCategoriesAndPinned + commentsCount + shoutedCount + author { + ...user + } } } - } } ` @@ -58,21 +57,20 @@ export const searchUsers = gql` searchUsers(query: $query, firstUsers: $firstUsers, usersOffset: $usersOffset) { userCount users { - __typename - ...user + __typename + ...user } } } ` export const searchHashtags = gql` - query($query: String!, $firstHashtags: Int, $hashtagsOffset: Int) { searchHashtags(query: $query, firstHashtags: $firstHashtags, hashtagsOffset: $hashtagsOffset) { hashtagCount hashtags { - __typename - id + __typename + id } } }