From 24b9417cec5cc785a57b2fe169a1ae88b88801a4 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 8 Oct 2018 15:51:38 +0200 Subject: [PATCH] Add users search filter --- .../followers-list.component.ts | 2 +- .../following-list.component.ts | 2 +- .../jobs/jobs-list/jobs-list.component.ts | 2 +- .../video-abuse-list.component.ts | 2 +- .../video-blacklist-list.component.ts | 2 +- .../users/user-list/user-list.component.html | 1 + .../users/user-list/user-list.component.ts | 4 +-- .../my-account-ownership.component.ts | 2 +- .../my-account-video-imports.component.ts | 2 +- .../user-moderation-dropdown.component.html | 2 +- .../user-moderation-dropdown.component.ts | 1 + client/src/app/shared/rest/rest-table.ts | 27 +++++++++++++- client/src/app/shared/users/user.service.ts | 4 ++- server/controllers/api/users/index.ts | 2 +- server/models/account/user.ts | 23 ++++++++++-- server/tests/api/users/users.ts | 36 ++++++++++++++++++- server/tests/utils/users/users.ts | 3 +- 17 files changed, 100 insertions(+), 17 deletions(-) diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.ts b/client/src/app/+admin/follows/followers-list/followers-list.component.ts index ca993dcd3..4a25b7ff3 100644 --- a/client/src/app/+admin/follows/followers-list/followers-list.component.ts +++ b/client/src/app/+admin/follows/followers-list/followers-list.component.ts @@ -28,7 +28,7 @@ export class FollowersListComponent extends RestTable implements OnInit { } ngOnInit () { - this.loadSort() + this.initialize() } protected loadData () { diff --git a/client/src/app/+admin/follows/following-list/following-list.component.ts b/client/src/app/+admin/follows/following-list/following-list.component.ts index dd57884c6..70235a48d 100644 --- a/client/src/app/+admin/follows/following-list/following-list.component.ts +++ b/client/src/app/+admin/follows/following-list/following-list.component.ts @@ -29,7 +29,7 @@ export class FollowingListComponent extends RestTable implements OnInit { } ngOnInit () { - this.loadSort() + this.initialize() } async removeFollowing (follow: ActorFollow) { diff --git a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts index 866ba1b23..44778ab56 100644 --- a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts +++ b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts @@ -34,7 +34,7 @@ export class JobsListComponent extends RestTable implements OnInit { ngOnInit () { this.loadJobState() - this.loadSort() + this.initialize() } onJobStateChanged () { diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts index 681db7434..9837af586 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts @@ -57,7 +57,7 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { } ngOnInit () { - this.loadSort() + this.initialize() } openModerationCommentModal (videoAbuse: VideoAbuse) { diff --git a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts index bb051d00f..e491edaca 100644 --- a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts +++ b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts @@ -39,7 +39,7 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { } ngOnInit () { - this.loadSort() + this.initialize() } getVideoUrl (videoBlacklist: VideoBlacklist) { diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html index 9d1f2e34a..ae8921802 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.html +++ b/client/src/app/+admin/users/user-list/user-list.component.html @@ -25,6 +25,7 @@
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts index f3e7e0ead..33384dc35 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/users/user-list/user-list.component.ts @@ -35,7 +35,7 @@ export class UserListComponent extends RestTable implements OnInit { } ngOnInit () { - this.loadSort() + this.initialize() this.bulkUserActions = [ { @@ -58,7 +58,7 @@ export class UserListComponent extends RestTable implements OnInit { protected loadData () { this.selectedUsers = [] - this.userService.getUsers(this.pagination, this.sort) + this.userService.getUsers(this.pagination, this.sort, this.search) .subscribe( resultList => { this.users = resultList.data diff --git a/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts b/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts index 13517b9f4..520278671 100644 --- a/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts +++ b/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts @@ -31,7 +31,7 @@ export class MyAccountOwnershipComponent extends RestTable implements OnInit { } ngOnInit () { - this.loadSort() + this.initialize() } protected loadData () { diff --git a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts index d9fb20446..5b920c98d 100644 --- a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts +++ b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts @@ -27,7 +27,7 @@ export class MyAccountVideoImportsComponent extends RestTable implements OnInit } ngOnInit () { - this.loadSort() + this.initialize() } isVideoImportSuccess (videoImport: VideoImport) { diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.html b/client/src/app/shared/moderation/user-moderation-dropdown.component.html index 2c477ab23..01db7cd4a 100644 --- a/client/src/app/shared/moderation/user-moderation-dropdown.component.html +++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.html @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts index 174e9f024..105c99d8b 100644 --- a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts +++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts @@ -17,6 +17,7 @@ export class UserModerationDropdownComponent implements OnInit { @Input() user: User @Input() buttonSize: 'normal' | 'small' = 'normal' + @Input() placement = 'left' @Output() userChanged = new EventEmitter() @Output() userDeleted = new EventEmitter() diff --git a/client/src/app/shared/rest/rest-table.ts b/client/src/app/shared/rest/rest-table.ts index fe1a91d2d..26748f245 100644 --- a/client/src/app/shared/rest/rest-table.ts +++ b/client/src/app/shared/rest/rest-table.ts @@ -1,8 +1,9 @@ import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' import { LazyLoadEvent } from 'primeng/components/common/lazyloadevent' import { SortMeta } from 'primeng/components/common/sortmeta' - import { RestPagination } from './rest-pagination' +import { Subject } from 'rxjs' +import { debounceTime, distinctUntilChanged } from 'rxjs/operators' export abstract class RestTable { @@ -11,10 +12,17 @@ export abstract class RestTable { abstract sort: SortMeta abstract pagination: RestPagination + protected search: string + private searchStream: Subject private sortLocalStorageKey = 'rest-table-sort-' + this.constructor.name protected abstract loadData (): void + initialize () { + this.loadSort() + this.initSearch() + } + loadSort () { const result = peertubeLocalStorage.getItem(this.sortLocalStorageKey) @@ -46,4 +54,21 @@ export abstract class RestTable { peertubeLocalStorage.setItem(this.sortLocalStorageKey, JSON.stringify(this.sort)) } + initSearch () { + this.searchStream = new Subject() + + this.searchStream + .pipe( + debounceTime(400), + distinctUntilChanged() + ) + .subscribe(search => { + this.search = search + this.loadData() + }) + } + + onSearch (search: string) { + this.searchStream.next(search) + } } diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts index 0eb3870b0..27a81f0a2 100644 --- a/client/src/app/shared/users/user.service.ts +++ b/client/src/app/shared/users/user.service.ts @@ -158,10 +158,12 @@ export class UserService { .pipe(catchError(err => this.restExtractor.handleError(err))) } - getUsers (pagination: RestPagination, sort: SortMeta): Observable> { + getUsers (pagination: RestPagination, sort: SortMeta, search?: string): Observable> { let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination, sort) + if (search) params = params.append('search', search) + return this.authHttp.get>(UserService.BASE_USERS_URL, { params }) .pipe( map(res => this.restExtractor.convertResultListDateToHuman(res)), diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index 0b0081520..4f8137c03 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts @@ -238,7 +238,7 @@ async function autocompleteUsers (req: express.Request, res: express.Response, n } async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) { - const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort) + const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.search) return res.json(getFormattedObjects(resultList.data, resultList.total)) } diff --git a/server/models/account/user.ts b/server/models/account/user.ts index e56b0bf40..39654cfcf 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts @@ -181,7 +181,25 @@ export class UserModel extends Model { return this.count() } - static listForApi (start: number, count: number, sort: string) { + static listForApi (start: number, count: number, sort: string, search?: string) { + let where = undefined + if (search) { + where = { + [Sequelize.Op.or]: [ + { + email: { + [Sequelize.Op.iLike]: '%' + search + '%' + } + }, + { + username: { + [ Sequelize.Op.iLike ]: '%' + search + '%' + } + } + ] + } + } + const query = { attributes: { include: [ @@ -204,7 +222,8 @@ export class UserModel extends Model { }, offset: start, limit: count, - order: getSort(sort) + order: getSort(sort), + where } return UserModel.findAndCountAll(query) diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index 8b9c6b455..513bca8a0 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts @@ -180,7 +180,7 @@ describe('Test users', function () { it('Should be able to upload a video again') it('Should be able to create a new user', async function () { - await createUser(server.url, accessToken, user.username,user.password, 2 * 1024 * 1024) + await createUser(server.url, accessToken, user.username, user.password, 2 * 1024 * 1024) }) it('Should be able to login with this user', async function () { @@ -322,6 +322,40 @@ describe('Test users', function () { expect(users[ 1 ].nsfwPolicy).to.equal('display') }) + it('Should search user by username', async function () { + const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'oot') + const users = res.body.data as User[] + + expect(res.body.total).to.equal(1) + expect(users.length).to.equal(1) + + expect(users[ 0 ].username).to.equal('root') + }) + + it('Should search user by email', async function () { + { + const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'r_1@exam') + const users = res.body.data as User[] + + expect(res.body.total).to.equal(1) + expect(users.length).to.equal(1) + + expect(users[ 0 ].username).to.equal('user_1') + expect(users[ 0 ].email).to.equal('user_1@example.com') + } + + { + const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'example') + const users = res.body.data as User[] + + expect(res.body.total).to.equal(2) + expect(users.length).to.equal(2) + + expect(users[ 0 ].username).to.equal('root') + expect(users[ 1 ].username).to.equal('user_1') + } + }) + it('Should update my password', async function () { await updateMyUser({ url: server.url, diff --git a/server/tests/utils/users/users.ts b/server/tests/utils/users/users.ts index 41d8ce265..d77233d62 100644 --- a/server/tests/utils/users/users.ts +++ b/server/tests/utils/users/users.ts @@ -112,7 +112,7 @@ function getUsersList (url: string, accessToken: string) { .expect('Content-Type', /json/) } -function getUsersListPaginationAndSort (url: string, accessToken: string, start: number, count: number, sort: string) { +function getUsersListPaginationAndSort (url: string, accessToken: string, start: number, count: number, sort: string, search?: string) { const path = '/api/v1/users' return request(url) @@ -120,6 +120,7 @@ function getUsersListPaginationAndSort (url: string, accessToken: string, start: .query({ start }) .query({ count }) .query({ sort }) + .query({ search }) .set('Accept', 'application/json') .set('Authorization', 'Bearer ' + accessToken) .expect(200)