Skip to content

Commit

Permalink
NAS-124732 / 24.04 / Request: Improve sorting for Replace Disk Search…
Browse files Browse the repository at this point in the history
… for DRAIDs (#9111)

* NAS-124732: Request:  Improve sorting for Replace Disk Search for DRAIDs

* NAS-124732: Request:  Improve sorting for Replace Disk Search for DRAIDs

* NAS-124732: PR update

* NAS-124732: PR update
  • Loading branch information
AlexKarpov98 authored Oct 31, 2023
1 parent d30f33c commit 92cc911
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ <h1 matDialogTitle>
{{ 'Extend Vdev' | translate }}
</h1>

<form class="ix-form-container" (submit)="onSubmit($event)">
<ix-select
[formControl]="newDiskControl"
<form class="ix-form-container" [formGroup]="form" (submit)="onSubmit($event)">
<ix-combobox
formControlName="newDisk"
[label]="'New Disk' | translate"
[required]="true"
[options]="unusedDiskOptions$"
[provider]="disksProvider"
[tooltip]="helptext.dialogFormFields.new_disk.tooltip | translate"
></ix-select>
></ix-combobox>

<ix-form-actions>
<button mat-button type="button" ixTest="cancel" [matDialogClose]="false">
Expand All @@ -20,7 +20,7 @@ <h1 matDialogTitle>
type="submit"
color="primary"
ixTest="extend"
[disabled]="!newDiskControl.valid"
[disabled]="form.invalid"
>
{{ 'Extend' | translate }}
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectat
import { fakeSuccessfulJob } from 'app/core/testing/utils/fake-job.utils';
import { mockCall, mockJob, mockWebsocket } from 'app/core/testing/utils/mock-websocket.utils';
import { UnusedDisk } from 'app/interfaces/storage.interface';
import { IxSelectHarness } from 'app/modules/ix-forms/components/ix-select/ix-select.harness';
import { IxFormsModule } from 'app/modules/ix-forms/ix-forms.module';
import { IxFormHarness } from 'app/modules/ix-forms/testing/ix-form.harness';
import { AppLoaderModule } from 'app/modules/loader/app-loader.module';
import { SnackbarService } from 'app/modules/snackbar/services/snackbar.service';
import {
Expand All @@ -19,7 +19,7 @@ import { WebSocketService } from 'app/services/ws.service';
describe('ExtendDialogComponent', () => {
let spectator: Spectator<ExtendDialogComponent>;
let loader: HarnessLoader;
let newDiskSelect: IxSelectHarness;

const createComponent = createComponentFactory({
component: ExtendDialogComponent,
imports: [
Expand Down Expand Up @@ -59,19 +59,17 @@ describe('ExtendDialogComponent', () => {
],
});

beforeEach(async () => {
beforeEach(() => {
spectator = createComponent();
loader = TestbedHarnessEnvironment.loader(spectator.fixture);
newDiskSelect = await loader.getHarness(IxSelectHarness);
});

it('loads unused disks and shows them in a New Disk select', async () => {
expect(spectator.inject(WebSocketService).call).toHaveBeenCalled();
expect(await newDiskSelect.getOptionLabels()).toEqual(['sde (10.91 TiB)', 'sdf (9.1 TiB)']);
});

it('extends a vdev when new unused disk is selected', async () => {
await newDiskSelect.setValue('sde (10.91 TiB)');
const form = await loader.getHarness(IxFormHarness);
await form.fillForm({
'New Disk': 'sde (10.91 TiB)',
});

const extendButton = await loader.getHarness(MatButtonHarness.with({ text: 'Extend' }));
await extendButton.click();

Expand All @@ -87,7 +85,11 @@ describe('ExtendDialogComponent', () => {
});

it('sends submit request with allow_duplicate_serials = true when selected disk is listed as having duplicate serial', async () => {
await newDiskSelect.setValue('sdf (9.1 TiB)');
const form = await loader.getHarness(IxFormHarness);
await form.fillForm({
'New Disk': 'sdf (9.1 TiB)',
});

const extendButton = await loader.getHarness(MatButtonHarness.with({ text: 'Extend' }));
await extendButton.click();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import {
ChangeDetectionStrategy, Component, Inject, OnInit,
} from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
import { Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FormBuilder } from '@ngneat/reactive-forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { TranslateService } from '@ngx-translate/core';
import filesize from 'filesize';
import { map } from 'rxjs';
import { Observable, map } from 'rxjs';
import { JobState } from 'app/enums/job-state.enum';
import helptext from 'app/helptext/storage/volumes/volume-status';
import { Option } from 'app/interfaces/option.interface';
import { PoolAttachParams } from 'app/interfaces/pool.interface';
import { UnusedDisk } from 'app/interfaces/storage.interface';
import { SimpleAsyncComboboxProvider } from 'app/modules/ix-forms/classes/simple-async-combobox-provider';
import { AppLoaderService } from 'app/modules/loader/app-loader.service';
import { SnackbarService } from 'app/modules/snackbar/services/snackbar.service';
import { DialogService } from 'app/services/dialog.service';
Expand All @@ -29,26 +32,18 @@ export interface ExtendDialogParams {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExtendDialogComponent implements OnInit {
newDiskControl = new FormControl(null as string, Validators.required);
unusedDisks: UnusedDisk[] = [];

readonly unusedDiskOptions$ = this.ws.call('disk.get_unused').pipe(
map((disks) => {
return disks.map((disk) => {
const exportedPool = disk.exported_zpool ? ` (${disk.exported_zpool})` : '';
return {
label: `${disk.devname} (${filesize(disk.size, { standard: 'iec' })})${exportedPool}`,
value: disk.name,
};
});
}),
);
form = this.formBuilder.group({
newDisk: ['', Validators.required],
});

readonly helptext = helptext;

disksProvider = new SimpleAsyncComboboxProvider(this.loadUnusedDisks());
unusedDisks: UnusedDisk[] = [];
private disksWithDuplicateSerials: UnusedDisk[] = [];

constructor(
private formBuilder: FormBuilder,
private ws: WebSocketService,
private errorHandler: ErrorHandlerService,
private loader: AppLoaderService,
Expand All @@ -57,20 +52,20 @@ export class ExtendDialogComponent implements OnInit {
private dialogService: DialogService,
private dialogRef: MatDialogRef<ExtendDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: ExtendDialogParams,
) { }
) {}

ngOnInit(): void {
this.loadUnusedDisks();
this.setupWarningForExportedPools();
this.setupExportedPoolWarning();
}

setupWarningForExportedPools(): void {
this.newDiskControl.valueChanges.pipe(untilDestroyed(this)).subscribe(
this.warnForExportedPools.bind(this),
setupExportedPoolWarning(): void {
this.form.controls.newDisk.valueChanges.pipe(untilDestroyed(this)).subscribe(
this.warnAboutExportedPool.bind(this),
);
}

warnForExportedPools(diskName: string): void {
warnAboutExportedPool(diskName: string): void {
const unusedDisk = this.unusedDisks.find((disk) => disk.name === diskName);
if (!unusedDisk?.exported_zpool) {
return;
Expand All @@ -85,7 +80,7 @@ export class ExtendDialogComponent implements OnInit {
event.preventDefault();

const payload = {
new_disk: this.newDiskControl.value,
new_disk: this.form.value.newDisk,
target_vdev: this.data.targetVdevGuid,
} as PoolAttachParams;

Expand All @@ -110,12 +105,22 @@ export class ExtendDialogComponent implements OnInit {
});
}

private loadUnusedDisks(): void {
this.ws.call('disk.get_unused')
.pipe(this.errorHandler.catchError(), untilDestroyed(this))
.subscribe((disks) => {
this.unusedDisks = disks;
this.disksWithDuplicateSerials = disks.filter((disk) => disk.duplicate_serial.length);
});
loadUnusedDisks(): Observable<Option[]> {
return this.ws.call('disk.get_unused').pipe(
map((unusedDisks) => {
this.unusedDisks = unusedDisks;
this.disksWithDuplicateSerials = unusedDisks.filter((disk) => disk.duplicate_serial.length);

return unusedDisks.map((disk) => {
const exportedPool = disk.exported_zpool ? ` (${disk.exported_zpool})` : '';

return {
label: `${disk.devname} (${filesize(disk.size, { standard: 'iec' })})${exportedPool}`,
value: disk.name,
};
}).sort((a, b) => a.label.localeCompare(b.label));
}),
untilDestroyed(this),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ <h1 matDialogTitle>
{{ 'Replacing disk {name}' | translate: { name: data.diskName } }}
</h1>
<form class="ix-form-container" [formGroup]="form" (submit)="onSubmit()">
<ix-select
<ix-combobox
formControlName="replacement"
[label]="'Member Disk' | translate"
[required]="true"
[options]="unusedDisksOptions$"
[provider]="disksProvider"
[tooltip]="helptext.dialogFormFields.disk.tooltip | translate"
></ix-select>
></ix-combobox>

<ix-checkbox
formControlName="force"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import {
ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnInit,
ChangeDetectionStrategy, Component, Inject, OnInit,
} from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { MatDialog, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { TranslateService } from '@ngx-translate/core';
import filesize from 'filesize';
import { Observable, of } from 'rxjs';
import { Observable, map } from 'rxjs';
import helptext from 'app/helptext/storage/volumes/volume-status';
import { Option } from 'app/interfaces/option.interface';
import { UnusedDisk } from 'app/interfaces/storage.interface';
import { EntityJobComponent } from 'app/modules/entity/entity-job/entity-job.component';
import { SimpleAsyncComboboxProvider } from 'app/modules/ix-forms/classes/simple-async-combobox-provider';
import { SnackbarService } from 'app/modules/snackbar/services/snackbar.service';
import { DialogService } from 'app/services/dialog.service';
import { WebSocketService } from 'app/services/ws.service';
Expand All @@ -33,10 +34,9 @@ export class ReplaceDiskDialogComponent implements OnInit {
force: [false],
});

disksProvider = new SimpleAsyncComboboxProvider(this.loadUnusedDisks());
unusedDisks: UnusedDisk[] = [];

unusedDisksOptions$: Observable<Option[]>;

readonly helptext = helptext;

constructor(
Expand All @@ -48,29 +48,29 @@ export class ReplaceDiskDialogComponent implements OnInit {
private snackbar: SnackbarService,
@Inject(MAT_DIALOG_DATA) public data: ReplaceDiskDialogData,
private dialogService: DialogService,
private cdr: ChangeDetectorRef,
) {}

ngOnInit(): void {
this.loadUnusedDisks();
this.setupExportedPoolWarning();
}

loadUnusedDisks(): void {
this.ws.call('disk.get_unused').pipe(untilDestroyed(this)).subscribe((unusedDisks) => {
this.unusedDisks = unusedDisks;
const unusedDiskOptions = unusedDisks.map((disk) => {
const exportedPool = disk.exported_zpool ? ` (${disk.exported_zpool})` : '';
const size = filesize(disk.size, { standard: 'iec' });
loadUnusedDisks(): Observable<Option[]> {
return this.ws.call('disk.get_unused').pipe(
map((unusedDisks) => {
this.unusedDisks = unusedDisks;
return unusedDisks.map((disk) => {
const exportedPool = disk.exported_zpool ? ` (${disk.exported_zpool})` : '';
const size = filesize(disk.size, { standard: 'iec' });

return {
label: `${disk.devname} - ${size} ${exportedPool}`,
value: disk.identifier,
};
});
this.unusedDisksOptions$ = of(unusedDiskOptions);
this.cdr.markForCheck();
});
return {
label: `${disk.devname} - ${size} ${exportedPool}`,
value: disk.identifier,
};
}).sort((a, b) => a.label.localeCompare(b.label));
}),
untilDestroyed(this),
);
}

setupExportedPoolWarning(): void {
Expand Down
2 changes: 1 addition & 1 deletion src/assets/styles/other/_tn-styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ $primary-dark: darken(map-get($md-primary, 500), 8%);
}

.mat-mdc-select-panel .mat-mdc-option:hover,
.mat-mdc-option-active,
mat-option.mat-mdc-option-active,
.mat-mdc-menu-content button.mat-mdc-menu-item:hover,
.mat-mdc-menu-content button.mat-mdc-menu-item:hover .mat-icon:not(.theme-picker-swatch-icon),
.mat-mdc-menu-content button.mat-mdc-menu-item:focus,
Expand Down

0 comments on commit 92cc911

Please sign in to comment.