feat: improve user assignments via quick add magic #3348
No reviewers
Labels
No Label
area/internal-code
changes requested
confirmed
dependencies
duplicate
good first issue
help wanted
hosting
invalid
kind/bug
kind/feature
question
wontfix
No Milestone
No project
No Assignees
4 Participants
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: vikunja/frontend#3348
Loading…
Reference in New Issue
No description provided.
Delete Branch "feature/better-user-search-assignees"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Resolves #2196
This PR follows the api changes from
a7231e197e
and ensures users are actually assignable when assigning them via their email. It also clarifies the api changes in the user settings.Hi konrad!
Thank you for creating a PR!
I've deployed the changes of this PR on a preview environment under this URL: https://3348-feature-better-user-search-assig--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!
Something is borked here:
I create a task named
go @demo
which is assigned to demo, but the title ends up withgo demo
yes, this happens for assignment by username and displayname, but not per email
user discovery and email assignment seem fine
When we switch the editor this will be simpler to implement.
This should now be fixed.
@ -19,2 +19,4 @@
</span>
</template>
<template #searchResult="{option: user}">
<user :avatar-size="24" :show-username="true" :user="user" class="user-search-result"/>
Use a prop here and change user so that it doesn't add a margin.
Reason: If we want to remove the margin in general from the user component later (which we should and I think I have done that already in some branch) then it will be much easier if we don't have classes that have dependencies that we are not aware of.
Sum up:
My first gut-reaction was, "but the user component is used a lot! That's quite a bit of effort to change it everywhere!". Then I started looking into and as it turns out, there are only 3 places where the margin from the component is not overridden from the outer component. That made it easier and kind of proved your point :)
@ -48,12 +48,18 @@
<input type="checkbox" v-model="settings.discoverableByName"/>
{{ $t('user.settings.general.discoverableByName') }}
</label>
By
discoverable
you mean "able to invite"?Because I can't assign someone if that person isn't part of the current project, correct?
In general I was now quite confused what this setting now actually does. Judging from this most users will probably not have a clue.
That's correct! Before the recent changes about finding users in the api, this setting would apply to searching users with access to a project as well. Now it does not anymore and the little explanation I added below does not make this clearer. I've adjusted the message and removed the explanation below the setting.
No, it's literally adding a user to a project or team. Mentioning is a topic for another day.
Inviting implies there's a form of "receiving an invitation and be able to reject it" which is not the case right now. There is an item on the roadmap to change that, but that's very much out of scope here (and it requires quite a few changes in the api).
We absolutely should group these settings more. Right now there's too much stuff and it does not look great. I'd prefer to move that to another PR since it would touch more than these two options and I feel like we should plan this properly first.
I guess that depends on how common your name is :) I think it makes sense to have them as distinct options, because an email address is unique, but a name might not be. Searching for a name also "feels" more personal than allowing someone else to find me by my email address.
How about 'add member' then?
makes sense
keep splitting options
Makes sense as well
I've changed the string yet again
@ -48,12 +48,18 @@
<input type="checkbox" v-model="settings.discoverableByName"/>
{{ $t('user.settings.general.discoverableByName') }}
</label>
<p class="is-size-7" v-if="!settings.discoverableByName">
Wrap test inside
<p>
with<small>
.@ -54,3 +57,4 @@
<input type="checkbox" v-model="settings.discoverableByEmail"/>
{{ $t('user.settings.general.discoverableByEmail') }}
</label>
<p class="is-size-7" v-if="!settings.discoverableByEmail">
Wrap test inside
<p>
with<small>
.@ -32,2 +34,4 @@
match: string,
}
// IDEA: maybe use a small fuzzy search here to prevent errors
For a simple implementation copy over: https://github.com/hiddentao/fast-levenshtein/blob/3.0.0/levenshtein.js
Why copy it instead of pulling it in as a dependency?
We don't do any fancy matching in the api (other than lowercase contains), which means the frontend can't find things with fuzzy matching which the api did not return in the first place. I feel like we should fuzzy matching in the api first and then add it in the frontend as well.
The module is a slim wrapper around fastest-levenshtein with some sensible defaults and using Intl.Collator. The wrapper is a bit dated. So I would only copy the function as a helper and import fastest-levenshtein directly. Fuzzy matching in the api makes sense.
Doing it in the frontend as well for the locally available data still makes sense, because fuzzy finding increases the chance that the wanted result is further at the top in the list of found results.
This is a really cheap method with a big impact. For the backend I would probably try to use something better directly.
But if the frontend and api both do fuzzy matching, what's the advantage of doing it in the frontend as well? Wouldn't that give use the same results? (right now neither does, but it should be done in the api anyway)
We have a few points in the code where we used strict matching for searching in local (aka downloaded to frontend) data. I thought this was one of those places. Since adding levenshtein seems simple I thought we would have a quick-win here. If we are offline first this also enables usage when offline without server request. To get up-to-date data an api-request is obviously unavoidable. So I'm unsure what our general strategy here is, I guess?
Sure, we could add it but I'm not sure how useful that would be in the current state. The offline argument sticks though… but then we could do that when we have offline storage. Better not complicate things more than we must right now to solve this.
@ -110,3 +110,3 @@
}
p = p.replace(prefix, '')
if (p.substring(0, 1) === prefix) {
Picky: use
startsWith
Done
@ -278,13 +280,16 @@ const getRepeats = (text: string): repeatParsedResult => {
export const cleanupItemText = (text: string, items: string[], prefix: string): string => {
Would this improve this function:
I don't think it would improve it by much. IMHO the current version is easier to understand what it does.
@ -31,1 +30,4 @@
import {useBaseStore} from '@/stores/base'
import ProjectUserService from '@/services/projectUsers'
interface MatchedAssignees extends IUser {
This should be singular, like
IUser
Done
@ -45,1 +58,4 @@
findPropertyByValueFuzzy(users, 'email', query)
}
return findPropertyByValue(users, 'username', query) ||
Fuzzy should be an option of
findPropertyByValue
.Picky: use bracket to group return value.
Done
@ -72,2 +87,3 @@
const userService = new ProjectUserService()
const assignees = parsedTaskAssignees.map(async a => {
const users = await userService.getAll({}, {s: a})
const users = (await userService.getAll({projectId}, {s: a}))
The bracket around seems to be unnecessary.What I meant: Seems better to use
await
withthen
hereDon't you mean
then
withoutawait
?No. You still need to await the resulting promise comining out of the then
Then what's the advantage of the extra
then
over the current solution?@ -392,2 +411,2 @@
const assignees = await findAssignees(parsedTask.assignees)
const assignees = await findAssignees(parsedTask.assignees, foundProjectId)
assignees
is used to create a task vianew TaskModel
.findAssignees
now returns an array ofMatchedAssignee
(s
) instead ofIUser[]
which is whatnew TaskModel
expects as type for theassignees
propertyThat should not be a problem, because
MatchedAssignee
implementsIUser
and thus can be used as such. If it can quack like a duck, it can be used as a duck, no matter what else it is capable of doing.The
match
property ofMatchedAssignee
will be ignored by the model (from the type perspective, in reality it will be sent to the api which will then ignore it).b721dc2d34
to60e051433c
60e051433c
tod1025c3982
d1025c3982
to4aad488707
7e50b6e270
tocd2b7fe185