Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User typeahead enabled for non-admin project managers #1237

Open
wants to merge 19 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions backend/LexBoxApi/GraphQL/LexQueries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,22 @@ public IQueryable<User> UsersInMyOrg(LexBoxDbContext context, LoggedInContext lo
return context.Users.Where(u => u.Organizations.Any(orgMember => myOrgIds.Contains(orgMember.OrgId)));
}

[UseOffsetPaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<User> UsersICanSee(LexBoxDbContext context, LoggedInContext loggedInContext)
{
var myOrgIds = loggedInContext.User.Orgs.Select(o => o.OrgId).ToList();
var myProjectIds = loggedInContext.User.Projects.Select(p => p.ProjectId).ToList();
var myManagedProjectIds = loggedInContext.User.Projects.Where(p => p.Role == ProjectRole.Manager).Select(p => p.ProjectId).ToList();
return context.Users.Where(u =>
u.Organizations.Any(orgMember => myOrgIds.Contains(orgMember.OrgId)) ||
u.Projects.Any(projMember =>
myManagedProjectIds.Contains(projMember.ProjectId) ||
(projMember.Project != null && projMember.Project.IsConfidential == false && myProjectIds.Contains(projMember.ProjectId))));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want IsConfidential != true because we decided to show users for unknown projects.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit 44db0e3.

}

[UseProjection]
[GraphQLType<OrgByIdGqlConfiguration>]
public async Task<Organization?> OrgById(LexBoxDbContext dbContext,
Expand Down
16 changes: 15 additions & 1 deletion frontend/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ type InvalidEmailError implements Error {
address: String!
}

type InvalidOperationError implements Error {
message: String!
}

type IsAdminResponse {
value: Boolean!
}
Expand Down Expand Up @@ -439,6 +443,7 @@ type Query {
orgs(where: OrganizationFilterInput @cost(weight: "10") orderBy: [OrganizationSortInput!] @cost(weight: "10")): [Organization!]! @cost(weight: "10")
myOrgs(where: OrganizationFilterInput @cost(weight: "10") orderBy: [OrganizationSortInput!] @cost(weight: "10")): [Organization!]! @cost(weight: "10")
usersInMyOrg(skip: Int take: Int where: UserFilterInput @cost(weight: "10") orderBy: [UserSortInput!] @cost(weight: "10")): UsersInMyOrgCollectionSegment @listSize(assumedSize: 1000, slicingArguments: [ "take" ], sizedFields: [ "items" ]) @cost(weight: "10")
usersICanSee(skip: Int take: Int where: UserFilterInput @cost(weight: "10") orderBy: [UserSortInput!] @cost(weight: "10")): UsersICanSeeCollectionSegment @listSize(assumedSize: 1000, slicingArguments: [ "take" ], sizedFields: [ "items" ]) @cost(weight: "10")
orgById(orgId: UUID!): OrgById @cost(weight: "10")
users(skip: Int take: Int where: UserFilterInput @cost(weight: "10") orderBy: [UserSortInput!] @cost(weight: "10")): UsersCollectionSegment @authorize(policy: "AdminRequiredPolicy") @listSize(assumedSize: 1000, slicingArguments: [ "take" ], sizedFields: [ "items" ]) @cost(weight: "10")
me: MeDto @cost(weight: "10")
Expand Down Expand Up @@ -555,6 +560,15 @@ type UsersCollectionSegment {
totalCount: Int! @cost(weight: "10")
}

"A segment of a collection."
type UsersICanSeeCollectionSegment {
"Information to aid in pagination."
pageInfo: CollectionSegmentInfo!
"A flattened list of the items."
items: [User!]
totalCount: Int! @cost(weight: "10")
}

"A segment of a collection."
type UsersInMyOrgCollectionSegment {
"Information to aid in pagination."
Expand Down Expand Up @@ -608,7 +622,7 @@ union LeaveProjectError = NotFoundError | LastMemberCantLeaveError

union RemoveProjectFromOrgError = DbError | NotFoundError

union SendNewVerificationEmailByAdminError = NotFoundError | DbError | UniqueValueError
union SendNewVerificationEmailByAdminError = NotFoundError | DbError | InvalidOperationError

union SetOrgMemberRoleError = DbError | NotFoundError | OrgMemberInvitedByEmail

Expand Down
10 changes: 5 additions & 5 deletions frontend/src/lib/forms/UserTypeahead.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import { FormField, PlainInput, randomFormId } from '$lib/forms';
import { _userTypeaheadSearch, _orgMemberTypeaheadSearch, type SingleUserTypeaheadResult, type SingleUserInMyOrgTypeaheadResult } from '$lib/gql/typeahead-queries';
import { _userTypeaheadSearch, _orgMemberTypeaheadSearch, type SingleUserTypeaheadResult, type SingleUserICanSeeTypeaheadResult } from '$lib/gql/typeahead-queries';
import { overlay } from '$lib/overlay';
import { deriveAsync } from '$lib/util/time';
import { derived, writable } from 'svelte/store';
Expand All @@ -25,23 +25,23 @@

const dispatch = createEventDispatcher<{
selectedUserId: string | null;
selectedUser: SingleUserTypeaheadResult | SingleUserInMyOrgTypeaheadResult | null;
selectedUser: SingleUserTypeaheadResult | SingleUserICanSeeTypeaheadResult | null;
}>();

let selectedUser = writable<SingleUserTypeaheadResult | SingleUserInMyOrgTypeaheadResult | null>(null);
let selectedUser = writable<SingleUserTypeaheadResult | SingleUserICanSeeTypeaheadResult | null>(null);
let selectedUserId = derived(selectedUser, user => user?.id ?? null);
$: dispatch('selectedUserId', $selectedUserId);
$: dispatch('selectedUser', $selectedUser);

function formatResult(user: SingleUserTypeaheadResult | SingleUserInMyOrgTypeaheadResult): string {
function formatResult(user: SingleUserTypeaheadResult | SingleUserICanSeeTypeaheadResult): string {
const extra = 'username' in user && user.username && 'email' in user && user.email ? ` (${user.username}, ${user.email})`
: 'username' in user && user.username ? ` (${user.username})`
: 'email' in user && user.email ? ` (${user.email})`
: '';
return `${user.name}${extra}`;
}

function getInputValue(user: SingleUserTypeaheadResult | SingleUserInMyOrgTypeaheadResult): string {
function getInputValue(user: SingleUserTypeaheadResult | SingleUserICanSeeTypeaheadResult): string {
if ('email' in user && user.email) return user.email;
if ('username' in user && user.username) return user.username;
if ('name' in user && user.name) return user.name;
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/lib/gql/typeahead-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ export async function _userTypeaheadSearch(userSearch: string, limit = 10): Prom
return users;
}

export type UsersInMyOrgTypeaheadResult = NonNullable<NonNullable<LoadOrgMembersTypeaheadQuery['usersInMyOrg']>['items']>;
export type SingleUserInMyOrgTypeaheadResult = UsersInMyOrgTypeaheadResult[number];
export type UsersICanSeeTypeaheadResult = NonNullable<NonNullable<LoadOrgMembersTypeaheadQuery['usersICanSee']>['items']>;
export type SingleUserICanSeeTypeaheadResult = UsersICanSeeTypeaheadResult[number];

export async function _orgMemberTypeaheadSearch(orgMemberSearch: string, limit = 10): Promise<UsersInMyOrgTypeaheadResult> {
export async function _orgMemberTypeaheadSearch(orgMemberSearch: string, limit = 10): Promise<UsersICanSeeTypeaheadResult> {
if (!orgMemberSearch) return [];
const client = getClient();
const users = await client.query(graphql(`
query loadOrgMembersTypeahead($filter: UserFilterInput, $take: Int!) {
usersInMyOrg(where: $filter, orderBy: {name: ASC}, take: $take) {
usersICanSee(where: $filter, orderBy: {name: ASC}, take: $take) {
totalCount
items {
id
Expand All @@ -76,5 +76,5 @@ export async function _orgMemberTypeaheadSearch(orgMemberSearch: string, limit =
}
`), { filter: userFilter(orgMemberSearch), take: limit });

return users.data?.usersInMyOrg?.items ?? [];
return users.data?.usersICanSee?.items ?? [];
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import { SupHelp, helpLinks } from '$lib/components/help';
import type { UUID } from 'crypto';
import { _addOrgMember, type Org } from './+page';
import type { SingleUserInMyOrgTypeaheadResult, SingleUserTypeaheadResult } from '$lib/gql/typeahead-queries';
import type { SingleUserICanSeeTypeaheadResult, SingleUserTypeaheadResult } from '$lib/gql/typeahead-queries';
import UserProjects, { type Project } from '$lib/components/Users/UserProjects.svelte';

export let org: Org;
Expand All @@ -37,7 +37,7 @@
selectedProjects = [];
}

function populateUserProjects(user: SingleUserTypeaheadResult | SingleUserInMyOrgTypeaheadResult | null): void {
function populateUserProjects(user: SingleUserTypeaheadResult | SingleUserICanSeeTypeaheadResult | null): void {
resetProjects();
if (user && 'projects' in user) {
const userProjects = [...user.projects.map(p => ({memberRole: p.role, id: p.project.id, code: p.project.code, name: p.project.name}))];
Expand Down
Loading