]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Add users search filter
authorChocobozzz <me@florianbigard.com>
Mon, 8 Oct 2018 13:51:38 +0000 (15:51 +0200)
committerChocobozzz <me@florianbigard.com>
Mon, 8 Oct 2018 13:55:32 +0000 (15:55 +0200)
17 files changed:
client/src/app/+admin/follows/followers-list/followers-list.component.ts
client/src/app/+admin/follows/following-list/following-list.component.ts
client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts
client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts
client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts
client/src/app/+admin/users/user-list/user-list.component.html
client/src/app/+admin/users/user-list/user-list.component.ts
client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts
client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts
client/src/app/shared/moderation/user-moderation-dropdown.component.html
client/src/app/shared/moderation/user-moderation-dropdown.component.ts
client/src/app/shared/rest/rest-table.ts
client/src/app/shared/users/user.service.ts
server/controllers/api/users/index.ts
server/models/account/user.ts
server/tests/api/users/users.ts
server/tests/utils/users/users.ts

index ca993dcd37e1b24a15f43adde1a821a7dcf66431..4a25b7ff32075c7c4d6d52517d1644ec26a17c6f 100644 (file)
@@ -28,7 +28,7 @@ export class FollowersListComponent extends RestTable implements OnInit {
   }
 
   ngOnInit () {
-    this.loadSort()
+    this.initialize()
   }
 
   protected loadData () {
index dd57884c6445102b8e796be00f73e64ed55cf320..70235a48d4a3f43d5b754a13a49cbcd5cadba1af 100644 (file)
@@ -29,7 +29,7 @@ export class FollowingListComponent extends RestTable implements OnInit {
   }
 
   ngOnInit () {
-    this.loadSort()
+    this.initialize()
   }
 
   async removeFollowing (follow: ActorFollow) {
index 866ba1b234dd2aafa52cfbaca3f72d103911c172..44778ab5668253ef8ade3897ffcffc521992523d 100644 (file)
@@ -34,7 +34,7 @@ export class JobsListComponent extends RestTable implements OnInit {
 
   ngOnInit () {
     this.loadJobState()
-    this.loadSort()
+    this.initialize()
   }
 
   onJobStateChanged () {
index 681db743441a65d58ae5c5ecd9a918a0fa96052e..9837af586732c57d8bd53a7de7f539ec489d6138 100644 (file)
@@ -57,7 +57,7 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
   }
 
   ngOnInit () {
-    this.loadSort()
+    this.initialize()
   }
 
   openModerationCommentModal (videoAbuse: VideoAbuse) {
index bb051d00fb009d5aa35d3426a2736f119aea0c08..e491edacac61e2c8dff18ea69642b0f869f64a2d 100644 (file)
@@ -39,7 +39,7 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
   }
 
   ngOnInit () {
-    this.loadSort()
+    this.initialize()
   }
 
   getVideoUrl (videoBlacklist: VideoBlacklist) {
index 9d1f2e34aa620614d490baba79182e3cc30bc141..ae8921802946ff642145388eeff6b82e70bd0c8d 100644 (file)
@@ -25,6 +25,7 @@
       <div>
         <input
           type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
+          (keyup)="onSearch($event.target.value)"
         >
       </div>
     </div>
index f3e7e0ead8aad4998d988ebce38810f8a484ede3..33384dc35083c8b7b9542e075d841fb53475df97 100644 (file)
@@ -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
index 13517b9f4b337a2d528a2bc8f9f3f6eb9fbc2ff1..5202786717541771605416cfa2fb21108c149500 100644 (file)
@@ -31,7 +31,7 @@ export class MyAccountOwnershipComponent extends RestTable implements OnInit {
   }
 
   ngOnInit () {
-    this.loadSort()
+    this.initialize()
   }
 
   protected loadData () {
index d9fb20446067e087866d216f4a6232aac9d6eb95..5b920c98d9ea2af78704a1fb56b77f3a7a96830c 100644 (file)
@@ -27,7 +27,7 @@ export class MyAccountVideoImportsComponent extends RestTable implements OnInit
   }
 
   ngOnInit () {
-    this.loadSort()
+    this.initialize()
   }
 
   isVideoImportSuccess (videoImport: VideoImport) {
index 2c477ab23d251151f3e5bc373d5743b6ca7bef5e..01db7cd4a40b92cb47836ab0e0b38de40c46f5bd 100644 (file)
@@ -1,5 +1,5 @@
 <ng-container *ngIf="user && userActions.length !== 0">
   <my-user-ban-modal #userBanModal (userBanned)="onUserBanned()"></my-user-ban-modal>
 
-  <my-action-dropdown [actions]="userActions" [entry]="user" [buttonSize]="buttonSize"></my-action-dropdown>
+  <my-action-dropdown [actions]="userActions" [entry]="user" [buttonSize]="buttonSize" [placement]="placement"></my-action-dropdown>
 </ng-container>
\ No newline at end of file
index 174e9f024c82119f8ef71a7bc7a7e63a4017b32b..105c99d8b8df6133e2595e9bcd9708691fcb288d 100644 (file)
@@ -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()
index fe1a91d2d6e8bcd6479841e50a2a69be33d18a5b..26748f2451ccf14701b1f12c4277b08084d583c7 100644 (file)
@@ -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<string>
   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)
+  }
 }
index 0eb3870b054c93f4b0d1b491c369e6d01d290a87..27a81f0a249e68bf0f1ed80672f2cade52b2e3c0 100644 (file)
@@ -158,10 +158,12 @@ export class UserService {
                .pipe(catchError(err => this.restExtractor.handleError(err)))
   }
 
-  getUsers (pagination: RestPagination, sort: SortMeta): Observable<ResultList<User>> {
+  getUsers (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<User>> {
     let params = new HttpParams()
     params = this.restService.addRestGetParams(params, pagination, sort)
 
+    if (search) params = params.append('search', search)
+
     return this.authHttp.get<ResultList<User>>(UserService.BASE_USERS_URL, { params })
                .pipe(
                  map(res => this.restExtractor.convertResultListDateToHuman(res)),
index 0b008152076560f238a65a234d567af1c1c251c3..4f8137c03943b2717bf0fad4b2c2e2aaecc67a08 100644 (file)
@@ -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))
 }
index e56b0bf40b06bb988e8cf9c04f82ace36cd8ec50..39654cfcf2f51326f71dc538913aa911942fdb44 100644 (file)
@@ -181,7 +181,25 @@ export class UserModel extends Model<UserModel> {
     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<UserModel> {
       },
       offset: start,
       limit: count,
-      order: getSort(sort)
+      order: getSort(sort),
+      where
     }
 
     return UserModel.findAndCountAll(query)
index 8b9c6b45503caa0258dd4a0b1983a6d0b1f4e971..513bca8a06e58f95c658a8eb84689979671e9568 100644 (file)
@@ -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,
index 41d8ce265e5c5495e402af7c155d71a20090d985..d77233d6289cee5e3e8e23fb891339b59a8e87be 100644 (file)
@@ -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)