Merge pull request #3297 from Human-Connection/2143-search-for-hashtags

feat: Search for Hashtags
This commit is contained in:
mattwr18 2020-03-20 12:25:47 +01:00 committed by GitHub
commit 576b511d03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 143 additions and 9 deletions

View File

@ -12,6 +12,7 @@ class Store {
[
'CALL db.index.fulltext.createNodeIndex("post_fulltext_search",["Post"],["title", "content"])',
'CALL db.index.fulltext.createNodeIndex("user_fulltext_search",["User"],["name", "slug"])',
'CALL db.index.fulltext.createNodeIndex("tag_fulltext_search",["Tag"],["id"])',
].map(statement => txc.run(statement)),
)
})

View File

@ -40,6 +40,7 @@ export async function down(next) {
await transaction.rollback()
// eslint-disable-next-line no-console
console.log('rolled back')
throw new Error(error)
} finally {
session.close()
}

View File

@ -0,0 +1,51 @@
import { getDriver } from '../../db/neo4j'
export const description =
'This migration adds a fulltext index for the tags in order to search for Hasthags.'
export async function up(next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
try {
await transaction.run(`
CALL db.index.fulltext.createNodeIndex("tag_fulltext_search",["Tag"],["id"])
`)
await transaction.commit()
next()
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
await transaction.rollback()
// eslint-disable-next-line no-console
console.log('rolled back')
throw new Error(error)
} finally {
session.close()
}
}
export async function down(next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
try {
// Implement your migration here.
await transaction.run(`
CALL db.index.fulltext.drop("tag_fulltext_search")
`)
await transaction.commit()
next()
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
await transaction.rollback()
// eslint-disable-next-line no-console
console.log('rolled back')
throw new Error(error)
} finally {
session.close()
}
}

View File

@ -41,6 +41,16 @@ export default {
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()
@ -55,14 +65,25 @@ export default {
limit,
thisUserId,
})
return Promise.all([postTransactionResponse, userTransactionResponse])
const tagTransactionResponse = transaction.run(tagCypher, {
query: myQuery,
limit,
})
return Promise.all([
postTransactionResponse,
userTransactionResponse,
tagTransactionResponse,
])
})
try {
const [postResults, userResults] = await searchResultPromise
const [postResults, userResults, tagResults] = await searchResultPromise
log(postResults)
log(userResults)
return [...postResults.records, ...userResults.records].map(r => r.get('resource'))
log(tagResults)
return [...postResults.records, ...userResults.records, ...tagResults.records].map(r =>
r.get('resource'),
)
} finally {
session.close()
}

View File

@ -41,6 +41,9 @@ const searchQuery = gql`
slug
name
}
... on Tag {
id
}
}
}
`
@ -439,6 +442,28 @@ und hinter tausend Stäben keine Welt.`,
})
})
})
describe('adding a tag', () => {
beforeAll(async () => {
await Factory.build('tag', { id: 'myHashtag' })
})
describe('query the first four characters of the tag', () => {
it('finds the tag', async () => {
variables = { query: 'myha' }
await expect(query({ query: searchQuery, variables })).resolves.toMatchObject({
data: {
findResources: [
{
__typename: 'Tag',
id: 'myHashtag',
},
],
},
})
})
})
})
})
})
})

View File

@ -1,4 +1,4 @@
union SearchResult = Post | User
union SearchResult = Post | User | Tag
type Query {
findResources(query: String!, limit: Int = 5): [SearchResult]!

View File

@ -1,5 +1,5 @@
<template>
<ds-tag>
<ds-tag class="hc-hashtag">
<nuxt-link :to="hashtagUrl">#{{ id }}</nuxt-link>
</ds-tag>
</template>

View File

@ -106,6 +106,16 @@ describe('SearchableInput.vue', () => {
params: { id: 'u2', slug: 'bob-der-baumeister' },
})
})
it('pushes hashtag query params', async () => {
select.element.value = 'Hash'
select.trigger('input')
const tags = wrapper.findAll('.hc-hashtag')
const tag = tags.filter(item => item.text().match(/#Hashtag/))
tag.trigger('click')
await Vue.nextTick()
expect(mocks.$router.push).toHaveBeenCalledWith('?hashtag=Hashtag')
})
})
})
})

View File

@ -106,6 +106,10 @@ export const searchResults = [
name: 'Tonya Mohr',
slug: 'tonya-mohr',
},
{
id: 'Hashtag',
__typename: 'Tag',
},
]
storiesOf('Search Field', module)

View File

@ -35,6 +35,12 @@
>
<search-post :option="option" />
</p>
<p
v-if="option.__typename === 'Tag'"
:class="{ 'option-with-heading': isFirstOfType(option) }"
>
<hc-hashtag :id="option.id" />
</p>
</template>
</ds-select>
<base-button v-if="isActive" icon="close" circle ghost size="small" @click="clear" />
@ -45,6 +51,7 @@
import { isEmpty } from 'lodash'
import SearchHeading from '~/components/generic/SearchHeading/SearchHeading.vue'
import SearchPost from '~/components/generic/SearchPost/SearchPost.vue'
import HcHashtag from '~/components/Hashtag/Hashtag.vue'
import UserTeaser from '~/components/UserTeaser/UserTeaser.vue'
export default {
@ -52,6 +59,7 @@ export default {
components: {
SearchHeading,
SearchPost,
HcHashtag,
UserTeaser,
},
props: {
@ -138,12 +146,19 @@ export default {
isPost(item) {
return item.__typename === 'Post'
},
isTag(item) {
return item.__typename === 'Tag'
},
goToResource(item) {
this.$nextTick(() => {
this.$router.push({
name: this.isPost(item) ? 'post-id-slug' : 'profile-id-slug',
params: { id: item.id, slug: item.slug },
})
if (!this.isTag(item)) {
this.$router.push({
name: this.isPost(item) ? 'post-id-slug' : 'profile-id-slug',
params: { id: item.id, slug: item.slug },
})
} else {
this.$router.push('?hashtag=' + item.id)
}
})
},
},

View File

@ -19,6 +19,9 @@ export const findResourcesQuery = gql`
... on User {
...user
}
... on Tag {
id
}
}
}
`

View File

@ -599,6 +599,7 @@
"failed": "Nichts gefunden",
"heading": {
"Post": "Beiträge",
"Tag": "Hashtags",
"User": "Benutzer"
},
"hint": "Wonach suchst Du?",

View File

@ -599,6 +599,7 @@
"failed": "Nothing found",
"heading": {
"Post": "Posts",
"Tag": "Hashtags",
"User": "Users"
},
"hint": "What are you searching for?",

View File

@ -130,6 +130,7 @@ export default {
return this.$apollo.loading || (this.posts && this.posts.length > 0)
},
},
watchQuery: ['hashtag'],
methods: {
...mapMutations({
selectOrder: 'posts/SELECT_ORDER',