Skip to content

Commit

Permalink
Move admin user filters to GQL (#1219)
Browse files Browse the repository at this point in the history
* Make getSearchParam work better for union string types

* Move admin user filters to GQL

* sort draft projects by created date and name

---------

Co-authored-by: Kevin Hahn <[email protected]>
  • Loading branch information
myieye and hahn-kev authored Nov 8, 2024
1 parent c71b71d commit f69ef52
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 43 deletions.
16 changes: 0 additions & 16 deletions frontend/src/lib/components/Users/UserFilter.svelte
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
<script context="module" lang="ts">
import type { User } from '../../../routes/(authenticated)/admin/+page';
export type UserType = 'admin' | 'nonAdmin' | 'guest' | undefined;
export type UserFilters = {
userSearch: string;
usersICreated: boolean;
userType: UserType;
}
export function filterUsers(
users: User[],
userFilter: Partial<UserFilters>,
adminId: string | undefined,
): User[] {
return users.filter(
(u) =>
(!userFilter.usersICreated || u.createdById === adminId) &&
(!userFilter.userType ||
(userFilter.userType === (u.isAdmin ? 'admin' : 'nonAdmin')) ||
(userFilter.userType === 'guest' && u.createdById))
);
}
</script>
<script lang="ts">
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/lib/util/query-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,6 @@ export function getSearchParam<T extends PrimitiveRecord, R = string | undefined
return value as EnumOrString<R> | undefined;
}

type EnumOrString<R> = R extends StandardEnum<unknown> ? R : string;
type EnumOrString<R> = R extends StandardEnum<unknown> ? R
: R extends (string | undefined) ? R
: string;
7 changes: 2 additions & 5 deletions frontend/src/routes/(authenticated)/admin/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@
import type { Confidentiality } from '$lib/components/Projects';
import { browser } from '$app/environment';
import UserTable from '$lib/components/Users/UserTable.svelte';
import UserFilter, { filterUsers, type UserFilters, type UserType } from '$lib/components/Users/UserFilter.svelte';
import UserFilter, { type UserFilters, type UserType } from '$lib/components/Users/UserFilter.svelte';
export let data: PageData;
$: projects = data.projects;
$: draftProjects = data.draftProjects;
$: userData = data.users;
$: adminId = data.user?.id;
const { notifySuccess, notifyWarning } = useNotifications();
Expand Down Expand Up @@ -64,9 +63,7 @@
$: users = $userData?.items ?? [];
$: filteredUserCount = $userData?.totalCount ?? 0;
$: filters = queryParams.queryParamValues;
$: filteredUsers = filterUsers(users, $filters, adminId);
$: shownUsers = lastLoadUsedActiveFilter ? filteredUsers : filteredUsers.slice(0, 10);
$: shownUsers = lastLoadUsedActiveFilter ? users : users.slice(0, 10);
function filterProjectsByUser(user: User): void {
$queryParamValues.memberSearch = user.email ?? user.username ?? undefined;
Expand Down
75 changes: 54 additions & 21 deletions frontend/src/routes/(authenticated)/admin/+page.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getClient, graphql } from '$lib/gql';
import {getClient, graphql} from '$lib/gql';

import type { PageLoadEvent } from './$types';
import { type LexAuthUser } from '$lib/user';
import { redirect } from '@sveltejs/kit';
import type {PageLoadEvent} from './$types';
import {type LexAuthUser} from '$lib/user';
import {redirect} from '@sveltejs/kit';
import {getBoolSearchParam, getSearchParam} from '$lib/util/query-params';
import { isGuid } from '$lib/util/guid';
import {isGuid} from '$lib/util/guid';
import type {
$OpResult,
ChangeUserAccountByAdminInput,
Expand All @@ -18,11 +18,12 @@ import type {
UserFilterInput,
} from '$lib/gql/types';
import type {LoadAdminDashboardProjectsQuery, LoadAdminDashboardUsersQuery} from '$lib/gql/types';
import type { ProjectFilters } from '$lib/components/Projects';
import { DEFAULT_PAGE_SIZE } from '$lib/components/Paging';
import type { AdminTabId } from './AdminTabs.svelte';
import { derived, readable } from 'svelte/store';
import type { UserType } from '$lib/components/Users/UserFilter.svelte';
import type {ProjectFilters} from '$lib/components/Projects';
import {DEFAULT_PAGE_SIZE} from '$lib/components/Paging';
import type {AdminTabId} from './AdminTabs.svelte';
import {derived, readable} from 'svelte/store';
import type {UserType} from '$lib/components/Users/UserFilter.svelte';
import type {UUID} from 'crypto';

// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- false positive?
export type AdminSearchParams = ProjectFilters & {
Expand All @@ -41,7 +42,6 @@ export async function load(event: PageLoadEvent) {
requireAdmin(parentData.user);

const withDeletedProjects = getBoolSearchParam<AdminSearchParams>('showDeletedProjects', event.url.searchParams);
const userSearch = getSearchParam<AdminSearchParams>('userSearch', event.url.searchParams) ?? '';
const memberSearch = getSearchParam<AdminSearchParams>('memberSearch', event.url.searchParams);

const client = getClient();
Expand All @@ -53,8 +53,6 @@ export async function load(event: PageLoadEvent) {
...(memberSearch ? { projectManager: { or: [ { email: { eq: memberSearch } }, { username: { eq: memberSearch } } ] } } : {})
};



//language=GraphQL
const projectResultsPromise = client.awaitedQueryStore(event.fetch, graphql(`
query loadAdminDashboardProjects($withDeletedProjects: Boolean!, $projectFilter: ProjectFilterInput, $draftFilter: DraftProjectFilterInput) {
Expand All @@ -75,7 +73,11 @@ export async function load(event: PageLoadEvent) {
userCount
}
draftProjects(
where: $draftFilter
where: $draftFilter,
orderBy: [
{ createdDate: DESC },
{ name: ASC }
]
) {
code
id
Expand All @@ -91,13 +93,7 @@ export async function load(event: PageLoadEvent) {
}
`), { withDeletedProjects, projectFilter, draftFilter });

const userFilter: UserFilterInput = isGuid(userSearch) ? {id: {eq: userSearch}} : {
or: [
{name: {icontains: userSearch}},
{email: {icontains: userSearch}},
{username: {icontains: userSearch}}
]
};
const userFilter = buildUserSearchFilter(event.url.searchParams, parentData.user);
const userResultsPromise = client.awaitedQueryStore(event.fetch, graphql(`
query loadAdminDashboardUsers($filter: UserFilterInput, $take: Int!) {
users(
Expand Down Expand Up @@ -139,6 +135,43 @@ export async function load(event: PageLoadEvent) {
}
}

function buildUserSearchFilter(searchParams: URLSearchParams, user: LexAuthUser): UserFilterInput {
const userSearch = getSearchParam<AdminSearchParams>('userSearch', searchParams) ?? '';
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- false positive?
const userType = getSearchParam<AdminSearchParams, UserType>('userType', searchParams);
const onlyUsersICreated = getBoolSearchParam<AdminSearchParams>('usersICreated', searchParams);

const userFilter: UserFilterInput = {};

if (isGuid(userSearch)) {
userFilter.id = { eq: userSearch };
} else {
userFilter.or = [
{ name: { icontains: userSearch } },
{ email: { icontains: userSearch } },
{ username: { icontains: userSearch } }
];
}

switch (userType) {
case 'admin':
userFilter.isAdmin = { eq: true };
break;
case 'nonAdmin':
userFilter.isAdmin = { eq: false };
break;
case 'guest':
userFilter.createdById = { neq: null };
break;
}

if (onlyUsersICreated) {
userFilter.createdById = { eq: user.id as UUID };
}

return userFilter;
}

function requireAdmin(user: LexAuthUser | null): void {
if (!user?.isAdmin) {
redirect(307, '/');
Expand Down

0 comments on commit f69ef52

Please sign in to comment.