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 all 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
10 changes: 10 additions & 0 deletions backend/LexBoxApi/GraphQL/LexQueries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using LexBoxApi.Auth;
using LexBoxApi.Auth.Attributes;
using LexBoxApi.GraphQL.CustomTypes;
using LexBoxApi.Services;
using LexCore.Auth;
using LexCore.Entities;
using LexCore.ServiceInterfaces;
Expand Down Expand Up @@ -186,6 +187,15 @@ 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(UserService userService, LoggedInContext loggedInContext)
{
return userService.UserQueryForTypeahead(loggedInContext.User);
}

[UseProjection]
[GraphQLType<OrgByIdGqlConfiguration>]
public async Task<Organization?> OrgById(LexBoxDbContext dbContext,
Expand Down
1 change: 1 addition & 0 deletions backend/LexBoxApi/Services/DevGqlSchemaWriterService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public static async Task GenerateGqlSchema(string[] args)
.AddScoped<LexBoxDbContext>()
.AddScoped<IPermissionService, PermissionService>()
.AddScoped<ProjectService>()
.AddScoped<UserService>()
.AddScoped<LexAuthService>()
.AddLexGraphQL(builder.Environment, true);
var host = builder.Build();
Expand Down
18 changes: 16 additions & 2 deletions backend/LexBoxApi/Services/UserService.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System.Net.Mail;
using LexBoxApi.Auth;
using LexBoxApi.Services.Email;
using LexCore.Auth;
using LexCore.Entities;
using LexCore.Exceptions;
using LexData;
using Microsoft.EntityFrameworkCore;

namespace LexBoxApi.Services;

public class UserService(LexBoxDbContext dbContext, IEmailService emailService, LexAuthService lexAuthService)
public class UserService(LexBoxDbContext dbContext, IEmailService emailService)
{
public async Task ForgotPassword(string email)
{
Expand Down Expand Up @@ -83,4 +84,17 @@ public static (string name, string? email, string? username) ExtractNameAndAddre
}
return (name, email, username);
}

public IQueryable<User> UserQueryForTypeahead(LexAuthUser user)
{
var myOrgIds = user.Orgs.Select(o => o.OrgId).ToList();
var myProjectIds = user.Projects.Select(p => p.ProjectId).ToList();
var myManagedProjectIds = user.Projects.Where(p => p.Role == ProjectRole.Manager).Select(p => p.ProjectId).ToList();
return dbContext.Users.Where(u =>
u.Id == user.Id ||
u.Organizations.Any(orgMember => myOrgIds.Contains(orgMember.OrgId)) ||
u.Projects.Any(projMember =>
myManagedProjectIds.Contains(projMember.ProjectId) ||
(projMember.Project != null && projMember.Project.IsConfidential != true && myProjectIds.Contains(projMember.ProjectId))));
}
}
98 changes: 98 additions & 0 deletions backend/Testing/ApiTests/UsersICanSeeQueryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System.Text.Json.Nodes;
using Shouldly;
using Testing.Services;

namespace Testing.ApiTests;

[Trait("Category", "Integration")]
public class UsersICanSeeQueryTests : ApiTestBase
{
private async Task<JsonObject> QueryUsersICanSee(bool expectGqlError = false)
{
var json = await ExecuteGql(
$$"""
query {
usersICanSee(take: 10) {
totalCount
items {
id
name
}
}
}
""",
expectGqlError, expectSuccessCode: false);
myieye marked this conversation as resolved.
Show resolved Hide resolved
return json;
}

private async Task AddUserToProject(Guid projectId, string username)
myieye marked this conversation as resolved.
Show resolved Hide resolved
{
await ExecuteGql(
$$"""
mutation {
addProjectMember(input: {
projectId: "{{projectId}}",
usernameOrEmail: "{{username}}",
role: EDITOR,
canInvite: false
}) {
project {
id
}
errors {
__typename
... on Error {
message
}
}
}
}
""");
}

private JsonArray GetUsers(JsonObject json)
{
var users = json["data"]!["usersICanSee"]!["items"]!.AsArray();
users.ShouldNotBeNull();
return users;
}

private void MustHaveUser(JsonArray users, string userName)
{
users.ShouldNotBeNull().ShouldNotBeEmpty();
users.ShouldContain(node => node!["name"]!.GetValue<string>() == userName,
"user list " + users.ToJsonString());
}

private void MustNotHaveUser(JsonArray users, string userName)
{
users.ShouldNotBeNull().ShouldNotBeEmpty();
users.ShouldNotContain(node => node!["name"]!.GetValue<string>() == userName,
"user list " + users.ToJsonString());
}

[Fact]
public async Task ManagerCanSeeProjectMembersOfAllProjects()
{
await LoginAs("manager");
await using var project = await this.RegisterProjectInLexBox(Utils.GetNewProjectConfig(isConfidential: true));
//refresh jwt
await LoginAs("manager");
await AddUserToProject(project.Id, "qa@test.com");
var json = GetUsers(await QueryUsersICanSee());
MustHaveUser(json, "Qa Admin");
}

[Fact]
public async Task MemberCanSeeNotProjectMembersOfConfidentialProjects()
{
await LoginAs("manager");
await using var project = await this.RegisterProjectInLexBox(Utils.GetNewProjectConfig(isConfidential: true));
//refresh jwt
await LoginAs("manager");
myieye marked this conversation as resolved.
Show resolved Hide resolved
await AddUserToProject(project.Id, "qa@test.com");
await LoginAs("editor");
var json = GetUsers(await QueryUsersICanSee());
MustNotHaveUser(json, "Qa Admin");
}
}
39 changes: 39 additions & 0 deletions backend/Testing/Fixtures/TempProjectWithoutRepo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using LexCore.Entities;
using LexData;
using Testing.Services;

namespace Testing.Fixtures;

public class TempProjectWithoutRepo(LexBoxDbContext dbContext, Project project) : IAsyncDisposable
{
public Project Project => project;
public static async Task<TempProjectWithoutRepo> Create(LexBoxDbContext dbContext, bool isConfidential = false, Guid? managerId = null)
{
var config = Utils.GetNewProjectConfig(isConfidential: isConfidential);
var project = new Project
{
Name = config.Name,
Code = config.Code,
IsConfidential = config.IsConfidential,
LastCommit = null,
Organizations = [],
Users = [],
RetentionPolicy = RetentionPolicy.Test,
Type = ProjectType.FLEx,
Id = config.Id,
};
if (managerId is Guid id)
{
project.Users.Add(new ProjectUsers { ProjectId = project.Id, UserId = id, Role = ProjectRole.Manager });
}
dbContext.Add(project);
await dbContext.SaveChangesAsync();
return new TempProjectWithoutRepo(dbContext, project);
}

public async ValueTask DisposeAsync()
{
dbContext.Remove(project);
await dbContext.SaveChangesAsync();
}
}
Loading
Loading