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

use prefetched user list in select user component #4647

Merged
merged 3 commits into from
Sep 26, 2024
Merged
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
8 changes: 7 additions & 1 deletion scripts/api/desks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {IDesk, IStage} from 'superdesk-api';
import {IDesk, IStage, IUser} from 'superdesk-api';
import ng from 'core/services/ng';
import {OrderedMap} from 'immutable';

Expand Down Expand Up @@ -49,6 +49,10 @@ function getDeskById(id: IDesk['_id']): IDesk {
return getAllDesks().get(id);
}

function getDeskMembers(deskId: IDesk['_id']): Array<IUser> {
return ng.get('desks').deskMembers[deskId] ?? [];
}

interface IDesksApi {
/** Desk is considered active if it is being viewed in monitoring at the moment */
getActiveDeskId(): IDesk['_id'] | null;
Expand All @@ -58,6 +62,7 @@ interface IDesksApi {
getDeskById(id: IDesk['_id']): IDesk ;
getDeskStages(deskId: IDesk['_id']): OrderedMap<IStage['_id'], IStage>;
getCurrentUserDesks(): Array<IDesk>; // desks that current user has access to
getDeskMembers(deskId: IDesk['_id']): Array<IUser>; // members of the desk
}

export const desks: IDesksApi = {
Expand All @@ -68,4 +73,5 @@ export const desks: IDesksApi = {
getDeskById,
getDeskStages,
getCurrentUserDesks,
getDeskMembers,
};
49 changes: 5 additions & 44 deletions scripts/core/ui/components/SelectUser.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/* eslint-disable react/no-multi-comp */
import React from 'react';
import {IPropsSelectUser, IUser, IRestApiResponse} from 'superdesk-api';
import {gettext, getUserSearchMongoQuery} from 'core/utils';
import {IPropsSelectUser, IUser} from 'superdesk-api';
import {gettext, searchUsers} from 'core/utils';
import {UserAvatar} from 'apps/users/components/UserAvatar';
import {SelectWithTemplate, Spacer} from 'superdesk-ui-framework/react';
import {httpRequestJsonLocal} from 'core/helpers/network';
import {SuperdeskReactComponent} from 'core/SuperdeskReactComponent';
import {sdApi} from 'api';

Expand Down Expand Up @@ -118,47 +117,9 @@ export class SelectUser extends SuperdeskReactComponent<IPropsSelectUser, IState
inlineLabel={true}
labelHidden={true}
getItems={(searchString) => {
this.abortController?.abort();
this.abortController = new AbortController();

let query = {$and: []};
const desk = sdApi.desks.getDeskById(this.props.deskId);
const deskMemberIds = (desk?.members ?? []).map((member) => member.user);

if (this.props.deskId != null && this.props.deskId != '') {
query.$and.push({_id: {$in: deskMemberIds}});
}

if (searchString != null && searchString.length > 0) {
query.$and.push(getUserSearchMongoQuery(searchString));
}

const urlParams = {max_results: 50};

if (query.$and.length > 0) {
urlParams['where'] = query;
}

// Wrapping into additional promise in order to avoid having to handle rejected promise
// in `SelectWithTemplate` component. The component takes a generic promise
// as an argument and not a fetch result so it wouldn't be good to handle
// fetch-specific rejections there.
return new Promise((resolve) => {
httpRequestJsonLocal<IRestApiResponse<IUser>>({
method: 'GET',
path: '/users',
urlParams,
abortSignal: this.abortController.signal,
}).then((res) => {
resolve(res._items);
}).catch((err) => {
// If user types something in the filter input all unfinished requests will be aborted.
// This is expected behaviour here and should not throw an error.
if (err?.name !== 'AbortError') {
throw err;
}
});
});
const deskMembers = sdApi.desks.getDeskMembers(this.props.deskId);

return Promise.resolve(searchUsers(deskMembers, searchString));
}}
value={this.state.selectedUser}
onChange={(user) => {
Expand Down
39 changes: 37 additions & 2 deletions scripts/core/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import React from 'react';
import gettextjs from 'gettext.js';
import {appConfig, getUserInterfaceLanguage} from 'appConfig';
import {IVocabularyItem, IArticle, IBaseRestApiResponse, ILockInfo, IListViewFieldWithOptions} from 'superdesk-api';
import {
IVocabularyItem,
IArticle,
IBaseRestApiResponse,
ILockInfo,
IListViewFieldWithOptions,
IUser,
} from 'superdesk-api';
import {assertNever} from './helpers/typescript-helpers';
import {isObject, omit} from 'lodash';
import formatISO from 'date-fns/formatISO';
Expand Down Expand Up @@ -283,7 +290,15 @@ export function translateArticleType(type: IArticle['type']) {
}
}

export function getUserSearchMongoQuery(searchString: string) {
type IUserFieldQuery = {
[field in keyof IUser]?: {$regex: string, $options: string};
};

interface IMongoQuery {
$or: Array<IUserFieldQuery>;
}

export function getUserSearchMongoQuery(searchString: string): IMongoQuery {
return {
$or: [
{username: {$regex: searchString, $options: 'i'}},
Expand All @@ -296,6 +311,26 @@ export function getUserSearchMongoQuery(searchString: string) {
};
}

/**
* Should match the logic of `getUserSearchMongoQuery`
*/
export function searchUsers(users: Array<IUser>, searchString: string): Array<IUser> {
if (!searchString) {
return users;
}

const query = getUserSearchMongoQuery(searchString);
const regex = new RegExp(escapeRegExp(searchString), 'i');

return users.filter((user) => {
return query['$or'].some((orQuery) => {
return Object.keys(orQuery).every((key) => {
return user[key] ? regex.test(user[key]) : false;
});
});
});
}

export function getItemTypes() {
const item_types = [
{type: 'all', label: gettext('all')},
Expand Down
Loading