Browse Source

UserInfo

pull/282/head
Sebastian Stehle 8 years ago
parent
commit
763931fa74
  1. 2
      src/Squidex/app/features/administration/pages/users/user-page.component.html
  2. 14
      src/Squidex/app/features/administration/pages/users/user-page.component.ts
  3. 21
      src/Squidex/app/features/administration/pages/users/users-page.component.html
  4. 13
      src/Squidex/app/features/administration/pages/users/users-page.component.ts
  5. 34
      src/Squidex/app/features/administration/state/users.state.spec.ts
  6. 46
      src/Squidex/app/features/administration/state/users.state.ts

2
src/Squidex/app/features/administration/pages/users/user-page.component.html

@ -40,7 +40,7 @@
<input type="text" class="form-control" id="displayName" maxlength="100" formControlName="displayName" autocomplete="false" /> <input type="text" class="form-control" id="displayName" maxlength="100" formControlName="displayName" autocomplete="false" />
</div> </div>
<div class="form-group form-group-password" [class.hidden]="usersState.isCurrentUser | async"> <div class="form-group form-group-password" [class.hidden]="user.isCurrentUser">
<div class="form-group"> <div class="form-group">
<label for="password">Password</label> <label for="password">Password</label>

14
src/Squidex/app/features/administration/pages/users/user-page.component.ts

@ -20,7 +20,8 @@ import { UserForm, UsersState } from './../../state/users.state';
}) })
export class UserPageComponent implements OnDestroy, OnInit { export class UserPageComponent implements OnDestroy, OnInit {
private selectedUserSubscription: Subscription; private selectedUserSubscription: Subscription;
private user?: UserDto;
public user?: { user: UserDto, isCurrentUser: boolean };
public userForm = new UserForm(this.formBuilder); public userForm = new UserForm(this.formBuilder);
@ -39,9 +40,12 @@ export class UserPageComponent implements OnDestroy, OnInit {
public ngOnInit() { public ngOnInit() {
this.selectedUserSubscription = this.selectedUserSubscription =
this.usersState.selectedUser this.usersState.selectedUser
.subscribe(user => { .subscribe(selectedUser => {
this.user = user; this.user = selectedUser;
this.userForm.load(user);
if (selectedUser) {
this.userForm.load(selectedUser.user);
}
}); });
} }
@ -50,7 +54,7 @@ export class UserPageComponent implements OnDestroy, OnInit {
if (value) { if (value) {
if (this.user) { if (this.user) {
this.usersState.update(this.user, value) this.usersState.update(this.user.user, value)
.subscribe(user => { .subscribe(user => {
this.userForm.submitCompleted(); this.userForm.submitCompleted();
}, error => { }, error => {

21
src/Squidex/app/features/administration/pages/users/users-page.component.html

@ -6,7 +6,7 @@
</ng-container> </ng-container>
<ng-container menu> <ng-container menu>
<button class="btn btn-link btn-secondary" (click)="load(true)" title="Refresh Users (CTRL + SHIFT + R)"> <button class="btn btn-link btn-secondary" (click)="reload()" title="Refresh Users (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh <i class="icon-reset"></i> Refresh
</button> </button>
@ -49,27 +49,28 @@
<div sqxIgnoreScrollbar> <div sqxIgnoreScrollbar>
<table class="table table-items table-fixed"> <table class="table table-items table-fixed">
<tbody> <tbody>
<ng-template ngFor let-user [ngForOf]="usersState.users | async" [ngForTrackBy]="trackByUser"> <ng-template ngFor let-userInfo [ngForOf]="usersState.users | async" [ngForTrackBy]="trackByUser">
<tr [routerLink]="user.id" routerLinkActive="active"> <tr [routerLink]="userInfo.user.id" routerLinkActive="active">
<td class="cell-user"> <td class="cell-user">
<img class="user-picture" [attr.title]="user.name" [attr.src]="user | sqxUserDtoPicture" /> <img class="user-picture" [attr.title]="userInfo.user.name" [attr.src]="userInfo.user | sqxUserDtoPicture" />
</td> </td>
<td class="cell-auto"> <td class="cell-auto">
<span class="user-name table-cell">{{user.displayName}}</span> <span class="user-name table-cell">{{userInfo.user.displayName}}</span>
</td> </td>
<td class="cell-auto"> <td class="cell-auto">
<span class="user-email table-cell">{{user.email}}</span> <span class="user-email table-cell">{{userInfo.user.email}}</span>
</td> </td>
<td class="cell-actions"> <td class="cell-actions">
<ng-container *ngIf="user.id !== authState.user?.id"> <ng-container *ngIf="!userInfo.isCurrentUser">
<button class="btn btn-link" (click)="lock(user); $event.stopPropagation();" *ngIf="!user.isLocked" title="Lock User"> <button class="btn btn-link" (click)="lock(user); $event.stopPropagation();" *ngIf="!userInfo.user.isLocked" title="Lock User">
<i class="icon icon-unlocked"></i> <i class="icon icon-unlocked"></i>
</button> </button>
<button class="btn btn-link" (click)="unlock(user); $event.stopPropagation();" *ngIf="user.isLocked" title="Unlock User"> <button class="btn btn-link" (click)="unlock(user); $event.stopPropagation();" *ngIf="userInfo.user.isLocked" title="Unlock User">
<i class="icon icon-lock"></i> <i class="icon icon-lock"></i>
</button> </button>
</ng-container> </ng-container>
<button *ngIf="user.id === authState.user?.id" class="btn btn-link invisible">
<button *ngIf="userInfo.isCurrentUser" class="btn btn-link invisible">
&nbsp; &nbsp;
</button> </button>
</td> </td>

13
src/Squidex/app/features/administration/pages/users/users-page.component.ts

@ -8,8 +8,6 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { AuthService } from '@app/shared';
import { UserDto } from './../../services/users.service'; import { UserDto } from './../../services/users.service';
import { UsersState } from './../../state/users.state'; import { UsersState } from './../../state/users.state';
@ -23,21 +21,20 @@ export class UsersPageComponent implements OnInit {
public usersFilter = new FormControl(); public usersFilter = new FormControl();
constructor( constructor(
public readonly authState: AuthService,
public readonly usersState: UsersState public readonly usersState: UsersState
) { ) {
} }
public ngOnInit() { public ngOnInit() {
this.load(); this.usersState.load().onErrorResumeNext().subscribe();
} }
public search() { public reload() {
this.usersState.search(this.usersFilter.value).onErrorResumeNext().subscribe(); this.usersState.load(true).onErrorResumeNext().subscribe();
} }
public load(notify = false) { public search() {
this.usersState.load(notify).onErrorResumeNext().subscribe(); this.usersState.search(this.usersFilter.value).onErrorResumeNext().subscribe();
} }
public lock(user: UserDto) { public lock(user: UserDto) {

34
src/Squidex/app/features/administration/state/users.state.spec.ts

@ -51,7 +51,7 @@ describe('UsersState', () => {
}); });
it('should load users', () => { it('should load users', () => {
expect(usersState.snapshot.users.values).toEqual(oldUsers); expect(usersState.snapshot.users.values.map(x => x.user)).toEqual(oldUsers);
expect(usersState.snapshot.usersPager.numberOfItems).toEqual(200); expect(usersState.snapshot.usersPager.numberOfItems).toEqual(200);
usersService.verifyAll(); usersService.verifyAll();
@ -70,7 +70,7 @@ describe('UsersState', () => {
usersState.load().subscribe(); usersState.load().subscribe();
expect(usersState.snapshot.selectedUser).toBe(newUsers[0]); expect(usersState.snapshot.selectedUser!.user).toBe(newUsers[0]);
}); });
it('should raise notification on load when notify is true', () => { it('should raise notification on load when notify is true', () => {
@ -82,7 +82,7 @@ describe('UsersState', () => {
it('should mark as current user when selected user equals to profile', () => { it('should mark as current user when selected user equals to profile', () => {
usersState.select('id2').subscribe(); usersState.select('id2').subscribe();
expect(usersState.snapshot.isCurrentUser).toBeTruthy(); expect(usersState.snapshot.selectedUser!.isCurrentUser).toBeTruthy();
}); });
it('should not load user when already loaded', () => { it('should not load user when already loaded', () => {
@ -93,7 +93,7 @@ describe('UsersState', () => {
}); });
expect(selectedUser!).toEqual(oldUsers[0]); expect(selectedUser!).toEqual(oldUsers[0]);
expect(usersState.snapshot.selectedUser).toBe(oldUsers[0]); expect(usersState.snapshot.selectedUser!.user).toBe(oldUsers[0]);
usersService.verify(x => x.getUser(It.isAnyString()), Times.never()); usersService.verify(x => x.getUser(It.isAnyString()), Times.never());
}); });
@ -109,7 +109,7 @@ describe('UsersState', () => {
}); });
expect(selectedUser!).toEqual(newUser); expect(selectedUser!).toEqual(newUser);
expect(usersState.snapshot.selectedUser).toBe(newUser); expect(usersState.snapshot.selectedUser!.user).toBe(newUser);
}); });
it('should return null when unselecting user', () => { it('should return null when unselecting user', () => {
@ -146,8 +146,10 @@ describe('UsersState', () => {
usersState.select('id1').subscribe(); usersState.select('id1').subscribe();
usersState.lock(oldUsers[0]).subscribe(); usersState.lock(oldUsers[0]).subscribe();
expect(usersState.snapshot.users.at(0).isLocked).toBeTruthy(); const user_1 = usersState.snapshot.users.at(0);
expect(usersState.snapshot.selectedUser).toBe(usersState.snapshot.users.at(0));
expect(user_1.user.isLocked).toBeTruthy();
expect(user_1).toBe(usersState.snapshot.selectedUser);
}); });
it('should unmark user as locked', () => { it('should unmark user as locked', () => {
@ -157,8 +159,10 @@ describe('UsersState', () => {
usersState.select('id2').subscribe(); usersState.select('id2').subscribe();
usersState.unlock(oldUsers[1]).subscribe(); usersState.unlock(oldUsers[1]).subscribe();
expect(usersState.snapshot.users.at(1).isLocked).toBeFalsy(); const user_1 = usersState.snapshot.users.at(0);
expect(usersState.snapshot.selectedUser).toBe(usersState.snapshot.users.at(1));
expect(user_1.user.isLocked).toBeFalsy();
expect(user_1).toBe(usersState.snapshot.selectedUser);
}); });
it('should update user on update', () => { it('should update user on update', () => {
@ -170,12 +174,14 @@ describe('UsersState', () => {
usersState.select('id1').subscribe(); usersState.select('id1').subscribe();
usersState.update(oldUsers[0], request).subscribe(); usersState.update(oldUsers[0], request).subscribe();
expect(usersState.snapshot.users.at(0).email).toEqual('new@mail.com'); const user_1 = usersState.snapshot.users.at(0);
expect(usersState.snapshot.users.at(0).displayName).toEqual('New');
expect(usersState.snapshot.selectedUser).toBe(usersState.snapshot.users.at(0)); expect(user_1.user.email).toEqual('new@mail.com');
expect(user_1.user.displayName).toEqual('New');
expect(user_1).toBe(usersState.snapshot.selectedUser);
}); });
it('should add user to state when created', () => { it('should add user to snapshot when created', () => {
const request = new CreateUserDto(newUser.email, newUser.displayName, 'password'); const request = new CreateUserDto(newUser.email, newUser.displayName, 'password');
usersService.setup(x => x.postUser(request)) usersService.setup(x => x.postUser(request))
@ -183,7 +189,7 @@ describe('UsersState', () => {
usersState.create(request).subscribe(); usersState.create(request).subscribe();
expect(usersState.snapshot.users.at(0)).toBe(newUser); expect(usersState.snapshot.users.at(0).user).toBe(newUser);
expect(usersState.snapshot.usersPager.numberOfItems).toBe(201); expect(usersState.snapshot.usersPager.numberOfItems).toBe(201);
}); });

46
src/Squidex/app/features/administration/state/users.state.ts

@ -70,14 +70,18 @@ export class UserForm extends Form<FormGroup> {
} }
} }
interface Snapshot { interface SnapshotUser {
isCurrentUser?: boolean; user: UserDto;
isCurrentUser: boolean;
}
users: ImmutableArray<UserDto>; interface Snapshot {
users: ImmutableArray<SnapshotUser>;
usersPager: Pager; usersPager: Pager;
usersQuery?: string; usersQuery?: string;
selectedUser?: UserDto; selectedUser?: SnapshotUser;
} }
@Injectable() @Injectable()
@ -94,10 +98,6 @@ export class UsersState extends State<Snapshot> {
this.changes.map(x => x.selectedUser) this.changes.map(x => x.selectedUser)
.distinctUntilChanged(); .distinctUntilChanged();
public isCurrentUser =
this.changes.map(x => x.isCurrentUser)
.distinctUntilChanged();
constructor( constructor(
private readonly authState: AuthService, private readonly authState: AuthService,
private readonly dialogs: DialogService, private readonly dialogs: DialogService,
@ -109,21 +109,19 @@ export class UsersState extends State<Snapshot> {
public select(id: string | null): Observable<UserDto | null> { public select(id: string | null): Observable<UserDto | null> {
return this.loadUser(id) return this.loadUser(id)
.do(selectedUser => { .do(selectedUser => {
const isCurrentUser = id === this.authState.user!.id; this.next(s => ({ ...s, selectedUser }));
})
this.next(s => ({ ...s, selectedUser, isCurrentUser })); .map(x => x && x.user);
});
} }
private loadUser(id: string | null) { private loadUser(id: string | null) {
return !id ? return !id ?
Observable.of(null) : Observable.of(null) :
Observable.of(this.snapshot.users.find(x => x.id === id)) Observable.of(this.snapshot.users.find(x => x.user.id === id))
.switchMap(user => { .switchMap(user => {
if (!user) { if (!user) {
return this.usersService.getUser(id).catch(() => Observable.of(null)); return this.usersService.getUser(id).map(x => this.createUser(x)).catch(() => Observable.of(null));
} else { } else {
return Observable.of(user); return Observable.of(user);
} }
}); });
@ -137,13 +135,13 @@ export class UsersState extends State<Snapshot> {
} }
this.next(s => { this.next(s => {
const users = ImmutableArray.of(dtos.items); const users = ImmutableArray.of(dtos.items.map(x => this.createUser(x)));
const usersPager = s.usersPager.setCount(dtos.total); const usersPager = s.usersPager.setCount(dtos.total);
let selectedUser = s.selectedUser; let selectedUser = s.selectedUser;
if (selectedUser) { if (selectedUser) {
const selectedFromResult = dtos.items.find(x => x.id === selectedUser!.id); const selectedFromResult = users.find(x => x.user.id === selectedUser!.user.id);
if (selectedFromResult) { if (selectedFromResult) {
selectedUser = selectedFromResult; selectedUser = selectedFromResult;
@ -160,7 +158,7 @@ export class UsersState extends State<Snapshot> {
return this.usersService.postUser(request) return this.usersService.postUser(request)
.do(dto => { .do(dto => {
this.next(s => { this.next(s => {
const users = s.users.pushFront(dto); const users = s.users.pushFront(this.createUser(dto));
const usersPager = s.usersPager.incrementCount(); const usersPager = s.usersPager.incrementCount();
return { ...s, users, usersPager }; return { ...s, users, usersPager };
@ -171,8 +169,6 @@ export class UsersState extends State<Snapshot> {
public update(user: UserDto, request: UpdateUserDto): Observable<any> { public update(user: UserDto, request: UpdateUserDto): Observable<any> {
return this.usersService.putUser(user.id, request) return this.usersService.putUser(user.id, request)
.do(() => { .do(() => {
this.dialogs.notifyInfo('User saved successsfull');
this.replaceUser(update(user, request)); this.replaceUser(update(user, request));
}); });
} }
@ -211,14 +207,20 @@ export class UsersState extends State<Snapshot> {
return this.load(); return this.load();
} }
private replaceUser(user: UserDto) { private replaceUser(userDto: UserDto) {
return this.next(s => { return this.next(s => {
const user = this.createUser(userDto);
const users = s.users.replaceBy('id', user); const users = s.users.replaceBy('id', user);
const selectedUser = s.selectedUser && s.selectedUser.id === user.id ? user : s.selectedUser; const selectedUser = s.selectedUser && s.selectedUser.user.id === userDto.id ? user : s.selectedUser;
return { ...s, users, selectedUser }; return { ...s, users, selectedUser };
}); });
} }
private createUser(user: UserDto): SnapshotUser {
return user ? { user, isCurrentUser: user.id === this.authState.user!.id } : null!;
}
} }

Loading…
Cancel
Save