fix: lock directory during assignment changes
This commit is contained in:
@@ -15,10 +15,11 @@
|
|||||||
type="search"
|
type="search"
|
||||||
name="directorySearch"
|
name="directorySearch"
|
||||||
[value]="searchDraft()"
|
[value]="searchDraft()"
|
||||||
|
[disabled]="assignmentBusyUserId() !== null"
|
||||||
(input)="searchDraft.set($any($event.target).value)"
|
(input)="searchDraft.set($any($event.target).value)"
|
||||||
/>
|
/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<button mat-stroked-button type="submit" [disabled]="loading()">Suchen</button>
|
<button mat-stroked-button type="submit" [disabled]="loading() || assignmentBusyUserId() !== null">Suchen</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@if (mutationError()) {
|
@if (mutationError()) {
|
||||||
@@ -138,11 +139,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="pagination" aria-label="Benutzerseiten">
|
<nav class="pagination" aria-label="Benutzerseiten">
|
||||||
<button mat-button type="button" [disabled]="directory()!.page <= 1 || loading()" (click)="previousPage()">
|
<button
|
||||||
|
mat-button
|
||||||
|
type="button"
|
||||||
|
[disabled]="directory()!.page <= 1 || loading() || assignmentBusyUserId() !== null"
|
||||||
|
(click)="previousPage()"
|
||||||
|
>
|
||||||
<mat-icon>chevron_left</mat-icon>Zurück
|
<mat-icon>chevron_left</mat-icon>Zurück
|
||||||
</button>
|
</button>
|
||||||
<span>Seite {{ directory()!.page }} · {{ directory()!.total }} Benutzer</span>
|
<span>Seite {{ directory()!.page }} · {{ directory()!.total }} Benutzer</span>
|
||||||
<button mat-button type="button" [disabled]="!directory()!.hasNextPage || loading()" (click)="nextPage()">
|
<button
|
||||||
|
mat-button
|
||||||
|
type="button"
|
||||||
|
[disabled]="!directory()!.hasNextPage || loading() || assignmentBusyUserId() !== null"
|
||||||
|
(click)="nextPage()"
|
||||||
|
>
|
||||||
Weiter<mat-icon>chevron_right</mat-icon>
|
Weiter<mat-icon>chevron_right</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -474,6 +474,80 @@ describe('Users directory', () => {
|
|||||||
http.expectOne(`${adminApi}/players?assignment=all&page=1&limit=20`).flush(playersPage());
|
http.expectOne(`${adminApi}/players?assignment=all&page=1&limit=20`).flush(playersPage());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('blocks directory search and paging while an assignment mutation is pending, then restores them', () => {
|
||||||
|
isAdmin.set(true);
|
||||||
|
create();
|
||||||
|
http.expectOne(`${api}?page=1&limit=20`).flush(directoryPage([ada], 1, 60, true));
|
||||||
|
fixture.detectChanges();
|
||||||
|
button('Weiter').click();
|
||||||
|
http.expectOne(`${api}?page=2&limit=20`).flush(directoryPage([ada], 2, 60, true));
|
||||||
|
fixture.detectChanges();
|
||||||
|
button('Zuordnungen verwalten').click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
http.expectOne(`${adminApi}/players?assignment=all&page=1&limit=20`).flush(
|
||||||
|
playersPage({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: 101,
|
||||||
|
firstName: 'Ada',
|
||||||
|
lastName: 'Lovelace',
|
||||||
|
active: true,
|
||||||
|
team: { id: 5, name: 'First Team', alias: 'first' },
|
||||||
|
currentUser: { id: 7, firstName: 'Ada', lastName: 'Lovelace', status: { id: 1, name: 'Active' } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
fixture.detectChanges();
|
||||||
|
childButton(
|
||||||
|
(fixture.nativeElement as HTMLElement).querySelector<HTMLElement>('app-player-assignments')!,
|
||||||
|
'Verknüpfung lösen',
|
||||||
|
).click();
|
||||||
|
closeDialog.next(true);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const unlink = http.expectOne(`${adminApi}/7/players/101`);
|
||||||
|
|
||||||
|
const directoryForm = (fixture.nativeElement as HTMLElement).querySelector<HTMLFormElement>('.directory-search')!;
|
||||||
|
const directorySearch = directoryForm.querySelector<HTMLInputElement>('input[type="search"]')!;
|
||||||
|
const directorySearchButton = directoryForm.querySelector<HTMLButtonElement>('button[type="submit"]')!;
|
||||||
|
const paging = (fixture.nativeElement as HTMLElement).querySelector<HTMLElement>('nav.pagination')!;
|
||||||
|
const [previous, next] = [...paging.querySelectorAll<HTMLButtonElement>('button')];
|
||||||
|
const controlsWereBlocked = directorySearch.disabled && directorySearchButton.disabled && previous.disabled && next.disabled;
|
||||||
|
|
||||||
|
directorySearch.value = 'Other';
|
||||||
|
directorySearch.dispatchEvent(new Event('input'));
|
||||||
|
directoryForm.dispatchEvent(new Event('submit'));
|
||||||
|
previous.click();
|
||||||
|
next.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
const unexpectedQueries = http.match((request) => request.url.startsWith(api));
|
||||||
|
const mutationStayedActive = !unlink.cancelled;
|
||||||
|
|
||||||
|
if (controlsWereBlocked && unexpectedQueries.length === 0 && mutationStayedActive) {
|
||||||
|
unlink.flush(ada);
|
||||||
|
http.expectOne(`${api}?page=2&limit=20`).flush(directoryPage([ada], 2, 60, true));
|
||||||
|
http.expectOne(`${adminApi}/players?assignment=all&page=1&limit=20`).flush(playersPage());
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(directorySearch.disabled).toBe(false);
|
||||||
|
expect(directorySearchButton.disabled).toBe(false);
|
||||||
|
expect(previous.disabled).toBe(false);
|
||||||
|
expect(next.disabled).toBe(false);
|
||||||
|
directoryForm.dispatchEvent(new Event('submit'));
|
||||||
|
http.expectOne(`${api}?page=1&limit=20&search=Other`).flush(directoryPage([]));
|
||||||
|
} else {
|
||||||
|
for (const query of unexpectedQueries) query.flush(directoryPage([ada], 1, 60, true));
|
||||||
|
fixture.detectChanges();
|
||||||
|
if (!unlink.cancelled) unlink.flush(ada);
|
||||||
|
for (const playerLoad of http.match((request) => request.url.startsWith(`${adminApi}/players`))) {
|
||||||
|
playerLoad.flush(playersPage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(controlsWereBlocked).toBe(true);
|
||||||
|
expect(unexpectedQueries).toHaveLength(0);
|
||||||
|
expect(mutationStayedActive).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('shows a persistent explanation for disabled self-deactivation', () => {
|
it('shows a persistent explanation for disabled self-deactivation', () => {
|
||||||
isAdmin.set(true);
|
isAdmin.set(true);
|
||||||
currentUser.set({ id: 1, firstName: 'Grace', lastName: 'Admin', role: { id: 1 } });
|
currentUser.set({ id: 1, firstName: 'Grace', lastName: 'Admin', role: { id: 1 } });
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ export class Users {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected submitSearch(): void {
|
protected submitSearch(): void {
|
||||||
|
if (this.assignmentBusyUserId() !== null) return;
|
||||||
const search = this.searchDraft().trim();
|
const search = this.searchDraft().trim();
|
||||||
if (this.loading() && search === this.search() && this.page() === 1) return;
|
if (this.loading() && search === this.search() && this.page() === 1) return;
|
||||||
this.search.set(search);
|
this.search.set(search);
|
||||||
@@ -98,13 +99,13 @@ export class Users {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected previousPage(): void {
|
protected previousPage(): void {
|
||||||
if (this.page() <= 1 || this.loading()) return;
|
if (this.page() <= 1 || this.loading() || this.assignmentBusyUserId() !== null) return;
|
||||||
this.page.update((value) => value - 1);
|
this.page.update((value) => value - 1);
|
||||||
this.loadDirectory();
|
this.loadDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected nextPage(): void {
|
protected nextPage(): void {
|
||||||
if (!this.directory()?.hasNextPage || this.loading()) return;
|
if (!this.directory()?.hasNextPage || this.loading() || this.assignmentBusyUserId() !== null) return;
|
||||||
this.page.update((value) => value + 1);
|
this.page.update((value) => value + 1);
|
||||||
this.loadDirectory();
|
this.loadDirectory();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user