Skip to content
This repository was archived by the owner on Mar 25, 2023. It is now read-only.

Commit 16671e6

Browse files
author
Vladimir Shakhov
authored
feat(disk-offering): show all disk offerings when creating a data disk (#1405)
* feat(disk-offering): show all disk offerings when creating a data disk and show error message when chosen disk offering don't fit account resource limitations * feat(disk-offering): fix test * fixes after review * fix bug * feat(disk-offerings): add slider to selector component * fix test * disable creation when disk offering doesnt fit the account resources * update after merge * fix typings * update * fix conditionals for selector and slider * fix volume resize * fixes for style guide * fix after review * add selector isDiskOfferingAvailableByResources * remove unused import * fix bugs after testing * remove log * add availableStorage selector * remove unused * commented test * commented test * dispatch zone from page * change load offering request * load all disk offerings without restriction of size * update * update
1 parent 0d5e805 commit 16671e6

33 files changed

+362
-159
lines changed

src/app/home/home.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { State, UserTagsActions, layoutStore } from '../root-store';
77
import { AuthService } from '../shared/services/auth.service';
88
import { WithUnsubscribe } from '../utils/mixins/with-unsubscribe';
99
import { Route, Subroute } from '../core/nav-menu/models';
10-
import * as authActions from '../reducers/auth/redux/auth.actions';
1110
import { getCurrentRoute, getRoutes, getSubroutes } from '../core/nav-menu/redux/nav-menu.reducers';
11+
import * as authActions from '../reducers/auth/redux/auth.actions';
1212

1313
@Component({
1414
selector: 'cs-home',

src/app/reducers/accounts/redux/accounts.reducers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,13 @@ export const selectUserAccount = createSelector(
203203
(accountsMap, accountId) => accountsMap[accountId],
204204
);
205205

206+
export const selectStorageAvailable = createSelector(selectUserAccount, account => {
207+
if (account) {
208+
const available = Number(account.primarystorageavailable);
209+
return !isNaN(available) ? available : null;
210+
}
211+
});
212+
206213
export const selectFilteredAccounts = createSelector(
207214
selectAll,
208215
filterSelectedRoleTypes,

src/app/reducers/disk-offerings/redux/disk-offerings.effects.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ describe('Disk Offering Effects', () => {
6868

6969
spyOn(service, 'getList').and.returnValue(of(diskOfferings));
7070
});
71-
7271
it('should return a collection from LoadOfferingsResponse', () => {
7372
const action = new actions.LoadOfferingsRequest();
7473
const completion = new actions.LoadOfferingsResponse(diskOfferings);

src/app/reducers/disk-offerings/redux/disk-offerings.reducers.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { createEntityAdapter, EntityAdapter, EntityState } from '@ngrx/entity';
22
import { createFeatureSelector, createSelector } from '@ngrx/store';
33

4-
import { isOfferingLocal } from '../../../shared/models/offering.model';
4+
import { isCustomized, isOfferingLocal } from '../../../shared/models/offering.model';
55
import { DiskOffering, ServiceOfferingAvailability, Zone } from '../../../shared/models';
66
import { configSelectors } from '../../../root-store';
77
import * as fromVolumes from '../../volumes/redux/volumes.reducers';
88
import * as fromZones from '../../zones/redux/zones.reducers';
99
import * as event from './disk-offerings.actions';
10+
import * as fromAuth from '../../auth/redux/auth.reducers';
11+
import * as fromVMs from '../../vm/redux/vm.reducers';
12+
import { isTemplate } from '../../../template/shared';
1013

1114
export interface State extends EntityState<DiskOffering> {
1215
loading: boolean;
@@ -113,3 +116,17 @@ export const getAvailableOfferings = createSelector(
113116
return [];
114117
},
115118
);
119+
120+
export const isDiskOfferingAvailableByResources = (minSize: number) =>
121+
createSelector(
122+
fromAuth.getUserAccount,
123+
fromVMs.getVmFormState,
124+
(account, state): boolean => {
125+
if (!isTemplate(state.template) && state.diskOffering) {
126+
const storageAvailability = account.primarystorageavailable;
127+
const size = isCustomized(state.diskOffering) ? minSize : state.diskOffering.disksize;
128+
return size < Number(storageAvailability);
129+
}
130+
return true;
131+
},
132+
);

src/app/service-offering/custom-service-offering/custom-service-offering.component.scss

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@import '../../../style/_variables';
2+
13
:host .mat-dialog-content {
24
overflow: hidden !important;
35
}
@@ -11,6 +13,6 @@ h5 {
1113
}
1214

1315
.error-message {
14-
color: #f44336;
16+
color: $error-message-color;
1517
font-size: 13px;
1618
}

src/app/service-offering/service-offering-dialog/service-offering-dialog.component.scss

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@import '../../../style/_variables';
2+
13
.service-offering-selector {
24
padding-top: 10px;
35
display: block;
@@ -22,6 +24,6 @@
2224
}
2325

2426
.error-message {
25-
color: #f44336;
27+
color: $error-message-color;
2628
font-size: 13px;
2729
}

src/app/shared/actions/volume-actions/volume-resize.container.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
33
import { select, Store } from '@ngrx/store';
44
import { filter, take } from 'rxjs/operators';
55

6-
import * as fromAuth from '../../../reducers/auth/redux/auth.reducers';
76
import * as diskOfferingActions from '../../../reducers/disk-offerings/redux/disk-offerings.actions';
87
import * as fromDiskOfferings from '../../../reducers/disk-offerings/redux/disk-offerings.reducers';
98
import * as zoneActions from '../../../reducers/zones/redux/zones.actions';
@@ -14,21 +13,24 @@ import { Account } from '../../models/account.model';
1413
import { Volume } from '../../models/volume.model';
1514
import { VolumeResizeData } from '../../services/volume.service';
1615
import { VolumeType } from '../../models';
16+
import * as fromAccounts from '../../../reducers/accounts/redux/accounts.reducers';
1717

1818
@Component({
1919
selector: 'cs-volume-resize-container',
2020
template: `
2121
<cs-volume-resize
2222
[maxSize]="maxSize"
2323
[volume]="volume"
24+
[availableStorage]="availableStorage$ | async"
2425
[diskOfferings]="offerings$ | async"
2526
(diskResized)="resizeDisk($event)"
2627
>
2728
</cs-volume-resize>`,
2829
})
2930
export class VolumeResizeContainerComponent implements OnInit {
3031
readonly offerings$ = this.store.pipe(select(fromDiskOfferings.getAvailableOfferings));
31-
readonly account$ = this.store.pipe(select(fromAuth.getUserAccount));
32+
readonly account$ = this.store.pipe(select(fromAccounts.selectUserAccount));
33+
readonly availableStorage$ = this.store.pipe(select(fromAccounts.selectStorageAvailable));
3234

3335
public volume: Volume;
3436

src/app/shared/actions/volume-actions/volume-resize/volume-resize.component.html

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,17 @@ <h3 class="mat-dialog-title">
99
novalidate
1010
>
1111
<div class="mat-dialog-content">
12-
<div *ngIf="!volumeIsRoot && diskOfferings?.length">
13-
<cs-disk-offering-selector
14-
name="diskOffering"
15-
[diskOfferings]="diskOfferings"
16-
[ngModel]="diskOffering"
17-
(changed)="updateDiskOffering($event)"
18-
></cs-disk-offering-selector>
19-
</div>
20-
<cs-slider
21-
*ngIf="isCustomizedForVolume(diskOffering) || volumeIsRoot"
12+
<cs-disk-offering-selector
13+
name="diskOffering"
14+
[enableSelector]="!volumeIsRoot && diskOfferings?.length"
15+
[enableSlider]="isCustomizedForVolume(diskOffering) || volumeIsRoot"
2216
[min]="volume.size | division:2:30"
23-
[max]="rootDiskSizeLimit"
24-
[label]="'VM_PAGE.STORAGE_DETAILS.VOLUME_RESIZE.NEW_SIZE' | translate"
25-
[(ngModel)]="newSize"
26-
name="new-size"
27-
[units]="'UNITS.GB' | translate"
28-
></cs-slider>
17+
[newSize]="newSize"
18+
[availableStorage]="availableStorage"
19+
[diskOfferings]="diskOfferings"
20+
[diskOffering]="diskOffering"
21+
(changed)="updateDiskOffering($event)"
22+
></cs-disk-offering-selector>
2923
</div>
3024
<div class="mat-dialog-actions">
3125
<button mat-button color="primary" matDialogClose type="button">

src/app/shared/actions/volume-actions/volume-resize/volume-resize.component.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export class VolumeResizeComponent implements OnInit, OnChanges {
2626
public volume: Volume;
2727
@Input()
2828
public diskOfferings: DiskOffering[];
29+
@Input()
30+
public availableStorage: number | null;
2931
@Output()
3032
public diskResized = new EventEmitter<VolumeResizeData>();
3133

@@ -43,19 +45,24 @@ export class VolumeResizeComponent implements OnInit, OnChanges {
4345
return maxRootCapability;
4446
}
4547

48+
public get volumeIsRoot(): boolean {
49+
return isRoot(this.volume);
50+
}
51+
52+
public get canResize(): boolean {
53+
return (this.diskOfferings && this.diskOfferings.length > 0) || isRoot(this.volume);
54+
}
55+
4656
constructor(
4757
public dialogRef: MatDialogRef<VolumeResizeComponent>,
4858
public authService: AuthService,
4959
) {}
5060

51-
public isCustomizedForVolume(diskOffering: DiskOffering): boolean {
52-
if (diskOffering) {
53-
return isCustomized(diskOffering);
54-
}
55-
}
56-
5761
public ngOnInit(): void {
5862
this.newSize = this.volume.size / Math.pow(2, 30);
63+
if (!!this.availableStorage) {
64+
this.availableStorage = this.availableStorage + this.newSize;
65+
}
5966
}
6067

6168
public ngOnChanges(changes: SimpleChanges): void {
@@ -64,16 +71,15 @@ export class VolumeResizeComponent implements OnInit, OnChanges {
6471
}
6572
}
6673

67-
public get volumeIsRoot(): boolean {
68-
return isRoot(this.volume);
69-
}
70-
71-
public get canResize(): boolean {
72-
return (this.diskOfferings && this.diskOfferings.length > 0) || isRoot(this.volume);
74+
public isCustomizedForVolume(diskOffering: DiskOffering): boolean {
75+
if (diskOffering) {
76+
return isCustomized(diskOffering);
77+
}
7378
}
7479

7580
public updateDiskOffering(value: DiskOffering): void {
7681
this.diskOffering = value;
82+
this.newSize = value.disksize;
7783
}
7884

7985
public resizeVolume(): void {

src/app/shared/components/disk-offering/disk-offering-dialog/disk-offering-dialog.component.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ <h3 class="mat-dialog-title">
8989
<ng-template #noResults>
9090
<cs-no-results></cs-no-results>
9191
</ng-template>
92-
92+
<mat-error *ngIf="resourcesLimitExceeded">
93+
{{ "ERRORS.VOLUME.VOLUME_RESOURCES_LIMIT_EXCEEDED" | translate }}
94+
</mat-error>
9395
<div class="mat-dialog-actions">
9496
<button
9597
mat-button

src/app/shared/components/disk-offering/disk-offering-dialog/disk-offering-dialog.component.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,28 @@ import { DiskOfferingDialogComponent } from './disk-offering-dialog.component';
1414
import { MockTranslatePipe } from '../../../../../testutils/mocks/mock-translate.pipe.spec';
1515
import { StringifyDatePipe } from '../../../pipes';
1616
import { DateTimeFormatterService } from '../../../services/date-time-formatter.service';
17+
import { User } from '../../../models/user.model';
18+
import { AuthService } from '../../../services/auth.service';
1719

1820
class MockConfigServiceDateTimeFormatterService {
1921
public stringifyToTime() {
2022
return;
2123
}
2224
}
2325

26+
class MockAuthService {
27+
// tslint:disable-next-line
28+
_user: User;
29+
30+
get user() {
31+
return this._user;
32+
}
33+
34+
getCustomDiskOfferingMinSize() {
35+
return 1;
36+
}
37+
}
38+
2439
describe('Disk Offering dialog', () => {
2540
let fixture: ComponentFixture<DiskOfferingDialogComponent>;
2641
let component: DiskOfferingDialogComponent;
@@ -72,6 +87,10 @@ describe('Disk Offering dialog', () => {
7287
provide: DateTimeFormatterService,
7388
useClass: MockConfigServiceDateTimeFormatterService,
7489
},
90+
{
91+
provide: AuthService,
92+
useClass: MockAuthService,
93+
},
7594
],
7695
schemas: [NO_ERRORS_SCHEMA],
7796
}).compileComponents();

src/app/shared/components/disk-offering/disk-offering-dialog/disk-offering-dialog.component.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { Component, Inject } from '@angular/core';
22
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
33
import * as moment from 'moment';
4-
54
import { DiskOffering } from '../../../models';
5+
import { AuthService } from '../../../services/auth.service';
66
import { Utils } from '../../../services/utils/utils.service';
7+
import { isCustomized } from '../../../models/offering.model';
78

89
@Component({
910
selector: 'cs-disk-offering-dialog',
@@ -13,13 +14,20 @@ import { Utils } from '../../../services/utils/utils.service';
1314
export class DiskOfferingDialogComponent {
1415
public diskOfferings: DiskOffering[];
1516
public selectedDiskOffering: DiskOffering;
17+
public storageAvailable: number | null;
18+
public resourcesLimitExceeded = false;
19+
public minSize: number = null;
1620

1721
constructor(
1822
@Inject(MAT_DIALOG_DATA) data,
1923
public dialogRef: MatDialogRef<DiskOfferingDialogComponent>,
24+
public authService: AuthService,
2025
) {
2126
this.diskOfferings = data.diskOfferings;
2227
this.selectedDiskOffering = data.diskOffering;
28+
this.storageAvailable = data.storageAvailable;
29+
this.minSize = this.authService.getCustomDiskOfferingMinSize();
30+
this.checkResourcesLimit();
2331
}
2432

2533
public offeringCreated(date: string): Date {
@@ -33,6 +41,7 @@ export class DiskOfferingDialogComponent {
3341

3442
public selectOffering(offering: DiskOffering) {
3543
this.selectedDiskOffering = offering;
44+
this.checkResourcesLimit();
3645
}
3746

3847
public preventTriggerExpansionPanel(e: Event) {
@@ -46,6 +55,29 @@ export class DiskOfferingDialogComponent {
4655
public isSubmitButtonDisabled() {
4756
const isDiskOfferingNotSelected = !this.selectedDiskOffering;
4857
const isNoDiskOfferings = !this.diskOfferings.length;
49-
return isDiskOfferingNotSelected || isNoDiskOfferings;
58+
return this.resourcesLimitExceeded || isDiskOfferingNotSelected || isNoDiskOfferings;
59+
}
60+
61+
private getDiskSize() {
62+
if (this.selectedDiskOffering) {
63+
if (isCustomized(this.selectedDiskOffering)) {
64+
return this.minSize;
65+
}
66+
67+
return this.selectedDiskOffering.disksize;
68+
}
69+
}
70+
71+
private getResourceLimitExceeded(): boolean {
72+
const diskSize = this.getDiskSize();
73+
if (this.storageAvailable && diskSize) {
74+
return Number(this.storageAvailable) < Number(diskSize);
75+
}
76+
77+
return false;
78+
}
79+
80+
private checkResourcesLimit() {
81+
this.resourcesLimitExceeded = this.getResourceLimitExceeded();
5082
}
5183
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<div class="top-row">
2+
<h4 class="disk-offerings">{{ 'VM_PAGE.VM_CREATION.DISK_OFFERING' | translate }}</h4>
3+
4+
<button mat-button type="button" (click)="changeOffering()">
5+
{{ 'COMMON.SELECT' | translate }}
6+
</button>
7+
</div>
8+
9+
<div class="mat-form-field-wrapper">
10+
<span class="disk-offering-info truncate">
11+
<ng-container *ngIf="diskOffering; then offeringName else nameStub"></ng-container>
12+
<ng-template #offeringName>{{ diskOffering.name }}</ng-template>
13+
<ng-template #nameStub>{{ 'VOLUME_PAGE.DETAILS.DISK_OFFERING' | translate }}</ng-template>
14+
</span>
15+
</div>

0 commit comments

Comments
 (0)