Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NAS-133540 / 25.04 / Allow disk list to be filtered by size #11335

Merged
merged 1 commit into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/app/helpers/file-size.utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { buildNormalizedFileSize } from 'app/helpers/file-size.utils';
import { buildNormalizedFileSize, convertStringDiskSizeToBytes } from 'app/helpers/file-size.utils';

describe('buildNormalizedFileSize with base 2', () => {
it('converts 1000 bytes to 1000 B with base 2', () => {
Expand Down Expand Up @@ -83,3 +83,21 @@ describe('buildNormalizedFileSize with base 10', () => {
expect(buildNormalizedFileSize(1000 ** 9, 'b', 10)).toBe('1000 Yb');
});
});

describe('convertStringDiskSizeToBytes', () => {
it('converts 16 gib of disk size to bytes', () => {
expect(convertStringDiskSizeToBytes('16 gib')).toBe(17179869184);
});

it('converts 16 g of disk size to bytes', () => {
expect(convertStringDiskSizeToBytes('16 g')).toBe(17179869184);
});

it('converts 16 gb of disk size to bytes', () => {
expect(convertStringDiskSizeToBytes('16 gb')).toBe(17179869184);
});

it('handles invalid value', () => {
expect(convertStringDiskSizeToBytes('1 dummy')).toBeNull();
});
});
41 changes: 41 additions & 0 deletions src/app/helpers/file-size.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,47 @@ export function buildNormalizedFileSize(
return `${formatted} ${unit}`;
}

export function convertStringDiskSizeToBytes(input: string): number | null {
const sizeRegex = /^(\d+(\.\d+)?)([KMGTP](?:i)?(?:B)?)?$/i;
const match = input.replace(/\s+/g, '').match(sizeRegex);

if (!match) {
return null;
}

const value = parseFloat(match[1]);
let unit = match[3]?.toUpperCase() || '';

const units = [
'B', 'Gb', 'kb', 'Mb', 'Tb', 'Pb', 'Eb', 'Zb', 'Yb',
'GiB', 'KiB', 'MiB', 'PiB', 'TiB', 'EiB', 'ZiB', 'YiB',
];

unit = units.find((item) => item.toUpperCase().includes(unit.toUpperCase())) || 'B';

const unitMultipliers: Record<string, number> = {
B: 1,
KIB: KiB,
MIB: MiB,
GIB: GiB,
TIB: TiB,
PIB: PiB,
EIB: EiB,
ZIB: ZiB,
YIB: YiB,
KB: KiB,
MB: MiB,
GB: GiB,
TB: TiB,
PB: PiB,
EB: EiB,
ZB: ZiB,
YB: YiB,
};

return value * (unitMultipliers[unit.toUpperCase()] || 1);
}

function normalizeFileSizeBase2(value: number, baseUnit: 'b' | 'B'): [formatted: number, unit: string] {
let formatted = value;
let increment = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ interface TestTableData {
numberField: number;
stringField: string;
booleanField: boolean;
size?: number;
}

const testTableData: TestTableData[] = [
{ numberField: 1, stringField: 'a', booleanField: true },
{
numberField: 1, stringField: 'a', booleanField: true, size: 17179869999,
},
{ numberField: 2, stringField: 'c', booleanField: false },
{ numberField: 4, stringField: 'b', booleanField: false },
{ numberField: 3, stringField: 'd', booleanField: true },
Expand Down Expand Up @@ -67,4 +70,18 @@ describe('AsyncDataProvider', () => {
await firstValueFrom(dataProvider.currentPage$),
).toEqual([{ numberField: 2, stringField: 'c', booleanField: false }]);
});

it('filters rows based on "size" query param with a pre-defined margin', async () => {
const request$ = of(testTableData);
const dataProvider = new AsyncDataProvider<TestTableData>(request$);
dataProvider.load();

dataProvider.setFilter({ query: '16.3 gib', columnKeys: ['size'] });
expect(dataProvider.totalRows).toBe(1);
expect(
await firstValueFrom(dataProvider.currentPage$),
).toEqual([{
numberField: 1, stringField: 'a', booleanField: true, size: 17179869999,
}]);
});
});
10 changes: 10 additions & 0 deletions src/app/modules/ix-table/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { get } from 'lodash-es';
import { convertStringDiskSizeToBytes } from 'app/helpers/file-size.utils';
import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
import { TableFilter } from 'app/modules/ix-table/interfaces/table-filter.interface';

Expand Down Expand Up @@ -36,9 +37,18 @@ export function filterTableRows<T>(filter: TableFilter<T>): T[] {
return list.filter((item) => {
return columnKeys.some((columnKey) => {
let value = get(item, columnKey) as string | undefined;

if ((columnKey as string) === 'size' && typeof value === 'number') {
const margin = value * 0.05;
const parsedQuerySize = convertStringDiskSizeToBytes(filterString) as number;

return (value >= parsedQuerySize - margin && value <= parsedQuerySize + margin);
}

if (preprocessMap?.[columnKey]) {
value = preprocessMap[columnKey]?.(value as T[keyof T]);
}

return value?.toString()?.toLowerCase()?.includes(filterString);
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
<ix-page-header [loading]="!!(dataProvider.isLoading$ | async)">
<ix-search-input1 [value]="filterString" (search)="onListFiltered($event)"></ix-search-input1>
<ix-table-columns-selector [columns]="columns" (columnsChange)="columnsChange($event)"></ix-table-columns-selector>
<ix-search-input1
[value]="filterString"
(search)="onListFiltered($event)"
></ix-search-input1>

<ix-table-columns-selector
[columns]="columns"
(columnsChange)="columnsChange($event)"
></ix-table-columns-selector>
</ix-page-header>

@if (selectedDisks.length) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ export class DiskListComponent implements OnInit {

protected onListFiltered(query: string): void {
this.filterString = query;
this.dataProvider.setFilter({ list: this.disks, query, columnKeys: ['name', 'pool', 'serial'] });
this.dataProvider.setFilter({ list: this.disks, query, columnKeys: ['name', 'pool', 'serial', 'size'] });
}

protected columnsChange(columns: typeof this.columns): void {
Expand Down
Loading