feat: use flexsearch for all local searches #997

Merged
konrad merged 15 commits from feature/flexsearch into main 2021-11-14 20:49:53 +00:00
Owner

This PR introduces flexsearch when searching for labels, lists and namespaces locally.

To discuss:

There's quite a bit of duplication I created here, not sure how to handle this in a better way.

Also I noticed that, while pretty similar, all three (labels, namespaces, lists) handle storing items in vuex slightly different. Labels are stored in a labels object in the state with the id as key, lists are stored directly in the state without any object in the middle of it and namespaces are stored in a namespaces array.

That has grown historically, but I think it should be refactored at some point.

This PR introduces [flexsearch](https://github.com/nextapps-de/flexsearch) when searching for labels, lists and namespaces locally. #### To discuss: There's quite a bit of duplication I created here, not sure how to handle this in a better way. Also I noticed that, while pretty similar, _all three_ (labels, namespaces, lists) handle storing items in vuex slightly different. Labels are stored in a `labels` object in the state with the id as key, lists are stored directly in the state without any object in the middle of it and namespaces are stored in a `namespaces` array. That has grown historically, but I think it should be refactored at some point.
konrad added 3 commits 2021-11-13 22:55:34 +00:00
Author
Owner

@dpschen Do you maybe have an idea how to handle this in a good way?

@dpschen Do you maybe have an idea how to handle this in a good way?
dpschen requested changes 2021-11-14 15:55:21 +00:00
dpschen left a comment
Member

In general:

  • Flexsearch seems like a nice lib!
  • ID as key is usually the way to go. With repetition of the id inside the object. I guess we should change that. The question is when :D

What I don't like:

  • flexsearch builds another index basically a repetition of some of the info in the store. But I guess there is nothing we can do about that.
  • we add new complexity that makes it harder to improve and simplify the general architecture.
In general: - Flexsearch seems like a nice lib! - ID as key is usually the way to go. With repetition of the id inside the object. I guess we should change that. The question is when :D What I don't like: - flexsearch builds another index basically a repetition of some of the info in the store. But I guess there is nothing we can do about that. - we add new complexity that makes it harder to improve and simplify the general architecture.
@ -29,0 +31,4 @@
})
return resultIds
.filter((value, index, self) => self.indexOf(value) === index && !labelIdsToHide.includes(value))

Use includes()

Use `includes()`
Author
Owner

How? I'm using this to filter out duplicate Ids.

How? I'm using this to filter out duplicate Ids.

Ignore. Sry, I misread

Ignore. Sry, I misread
dpschen marked this conversation as resolved
@ -0,0 +1,19 @@
import {createNewIndex, withId} from '@/indexes/index'

In these three files the duplication can be avoided by creating a factory function.
Something like:

// createNewIndexer.js
import {createNewIndex, withId} from '@/indexes/index'


export function createNewIndexer() {
    function add(item: withId) {
        return index.add(label.id, label)
    }

    function remove(item: withId) {
        return index.remove(label.id)
    }

    function update(item: withId) {
        return index.update(label.id, label)
    }

    function search(item: withId) {
        return index.search(query)
    }
    
    return {
    	add,
        remove,
        update,
        search
    }
}

and then:

// labels.js
import { createNewIndexer } from './createNewIndxer'

export createNewIndexer()

In these three files the duplication can be avoided by creating a factory function. Something like: ```js // createNewIndexer.js import {createNewIndex, withId} from '@/indexes/index' export function createNewIndexer() { function add(item: withId) { return index.add(label.id, label) } function remove(item: withId) { return index.remove(label.id) } function update(item: withId) { return index.update(label.id, label) } function search(item: withId) { return index.search(query) } return { add, remove, update, search } } ``` and then: ```js // labels.js import { createNewIndexer } from './createNewIndxer' export createNewIndexer() ```

On another thought:
The createNewIndexer should probably just be defined inside the index file already

On another thought: The `createNewIndexer` should probably just be defined inside the index file already
Author
Owner

That sounds like a really good idea. I've moved it all to a factory function.

That sounds like a really good idea. I've moved it all to a factory function.
konrad marked this conversation as resolved
@ -3,6 +3,7 @@ import {setLoading} from '@/store/helper'
import {success} from '@/message'
import {i18n} from '@/i18n'
import {getLabelsByIds, filterLabelsByQuery} from '@/helpers/labels'
import {add, remove, update} from '../../indexes/labels'

use @

use `@`
Author
Owner

Done.

Done.
konrad marked this conversation as resolved
@ -35,2 +39,4 @@
return typeof list === 'undefined' ? null : list
},
searchList: state => (query, includeArchived = false) => {
if (query === '' || query === null) {

Don' repeat all the search validation. Instead put that in the search function.
(you repeated this validatin multiple times)

Don' repeat all the search validation. Instead put that in the search function. (you repeated this validatin multiple times)
Author
Owner

In the indexer module? The problem here is it does not just return an array, but another object with the results in them (coming from flexsearch). Should I recreate that?

In the indexer module? The problem here is it does not just return an array, but another object with the results in them (coming from flexsearch). Should I recreate that?

I meant putting the query check in the search function that you create =)

I meant putting the query check in the search function that you create =)
Author
Owner
Just to clarify, here? https://kolaente.dev/vikunja/frontend/src/branch/feature/flexsearch/src/indexes/index.ts#L39

Yes!

Yes!
Author
Owner

Okay, but what should I return if the query is empty? I can't really return an empty array because the caller expects the object with the results from flexsearch.

Or should I return null and check that in the caller?

Okay, but what should I return if the query is empty? I can't really return an empty array because the caller expects the object with the results from flexsearch. Or should I return `null` and check that in the caller?

Return null (I would have used undefined, but after thinking about it null seems to make more sense…?).

After the call of search() you usually iterate over the results. You can use optional chaining to check is you can do that (result?.map( [...] ) || FALLBACK)

Return `null` (I would have used `undefined`, but after thinking about it `null` seems to make more sense…?). After the call of `search()` you usually iterate over the results. You can use optional chaining to check is you can do that (`result?.map( [...] ) || FALLBACK`)

Is that what you mean?

Is that what you mean?
Author
Owner

yeah pretty much.

yeah pretty much.
Author
Owner

Moved the check and simplified accessing it.

Moved the check and simplified accessing it.
konrad marked this conversation as resolved
@ -37,0 +51,4 @@
})
return resultIds
.filter((value, index, self) => self.indexOf(value) === index && value > 0)

use includes()

use `includes()`
konrad marked this conversation as resolved
@ -100,0 +112,4 @@
const namespacesFromIndex = search(query)
const resultIds = []
namespacesFromIndex.forEach(r => {

Use .map()

Use `.map()`
Author
Owner

Done.

Done.
konrad marked this conversation as resolved
@ -100,0 +117,4 @@
})
return resultIds
.filter((value, index, self) => self.indexOf(value) === index && value > 0)

Use .includes()

Use `.includes()`
konrad marked this conversation as resolved
@ -100,0 +119,4 @@
return resultIds
.filter((value, index, self) => self.indexOf(value) === index && value > 0)
.map(id => {
const namespaceIndex = state.namespaces.findIndex(n => n.id === id)

Use .find()

Use `.find()`
Author
Owner

Done.

Done.
konrad marked this conversation as resolved
konrad added 1 commit 2021-11-14 16:19:06 +00:00
continuous-integration/drone/pr Build is failing Details
b0388407c2
chore: use @
konrad added 1 commit 2021-11-14 16:22:05 +00:00
continuous-integration/drone/pr Build is failing Details
b9ccbb16e4
chore: simplify searching for a namespace
konrad added 1 commit 2021-11-14 16:34:12 +00:00
continuous-integration/drone/pr Build is failing Details
6d3eb97d3c
chore: use factory function to avoid duplication
konrad added 1 commit 2021-11-14 16:38:16 +00:00
continuous-integration/drone/pr Build is failing Details
77b91e5181
chore: simplify getting result ids
konrad added 1 commit 2021-11-14 16:54:18 +00:00
continuous-integration/drone/pr Build is failing Details
2f9701f405
chore: move creating a new index
konrad added 1 commit 2021-11-14 18:10:45 +00:00
continuous-integration/drone/pr Build is failing Details
b6113cfba2
chore: move empty query to search method
konrad added 1 commit 2021-11-14 18:13:43 +00:00
continuous-integration/drone/pr Build is failing Details
9776b58efc
chore: simplify checking for empty search results
konrad added 1 commit 2021-11-14 18:15:23 +00:00
continuous-integration/drone/pr Build is failing Details
6dcefcecd1
Merge branch 'main' into feature/flexsearch
# Conflicts:
#	package.json
konrad added 1 commit 2021-11-14 18:36:25 +00:00
continuous-integration/drone/pr Build is passing Details
ad6b3222d0
fix: label tests
Member

Hi konrad!

Thank you for creating a PR!

I've deployed the changes of this PR on a preview environment under this URL: https://997-featureflexsearch--vikunja-frontend-preview.netlify.app

You can use this url to view the changes live and test them out.
You will need to manually connect this to an api running somehwere. The easiest to use is https://try.vikunja.io/.

Have a nice day!

Beep boop, I'm a bot.

Hi konrad! Thank you for creating a PR! I've deployed the changes of this PR on a preview environment under this URL: https://997-featureflexsearch--vikunja-frontend-preview.netlify.app You can use this url to view the changes live and test them out. You will need to manually connect this to an api running somehwere. The easiest to use is https://try.vikunja.io/. Have a nice day! > Beep boop, I'm a bot.
dpschen requested changes 2021-11-14 19:06:01 +00:00
@ -27,2 +26,2 @@
return !labelIds.includes(id) && title.toLowerCase().includes(labelQuery)
})
return search(query)
?.map(r => r.result)

Use flatMap.

Use flatMap.
Author
Owner

Oh, I didn't know that exists. Done.

Oh, I didn't know that exists. Done.
konrad marked this conversation as resolved
@ -5,1 +5,4 @@
import {getLabelsByIds, filterLabelsByQuery} from '@/helpers/labels'
import {createNewIndexer} from '@/indexes'
const {add, remove, update} = createNewIndexer('labels', ['title', 'description'])

I like that =)

I like that =)
konrad marked this conversation as resolved
@ -47,3 +55,4 @@
removeNamespaceById(state, namespaceId) {
for (const n in state.namespaces) {
if (state.namespaces[n].id === namespaceId) {
remove(state.namespaces[n])

Picky: remove after splice (if why ever splice fails remove will correctly not be reached

Picky: `remove` after splice (if why ever splice fails `remove` will correctly not be reached
Author
Owner

Done.

Done.
konrad marked this conversation as resolved
@ -100,0 +112,4 @@
.flat()
.filter((value, index, self) => self.indexOf(value) === index && value > 0)
.map(id => {
const namespace = state.namespaces.find(n => n.id === id)

Acutally: use getNamespaceById getter

Acutally: use getNamespaceById getter
Author
Owner

Done.

Done.
konrad marked this conversation as resolved
konrad added 1 commit 2021-11-14 19:26:10 +00:00
continuous-integration/drone/pr Build was killed Details
9d68566f90
chore: simplify searching
konrad added 1 commit 2021-11-14 19:26:58 +00:00
continuous-integration/drone/pr Build was killed Details
4e90319165
chore: call remove after splice
konrad added 1 commit 2021-11-14 19:29:24 +00:00
continuous-integration/drone/pr Build is passing Details
8afe4a9d15
chore: use getter
dpschen approved these changes 2021-11-14 20:38:44 +00:00
konrad merged commit 507a73e74c into main 2021-11-14 20:49:53 +00:00
konrad deleted branch feature/flexsearch 2021-11-14 20:49:53 +00:00
This repo is archived. You cannot comment on pull requests.
No description provided.