import { TreeModule } from '@circlon/angular-tree-component';
import { NgbNavModule, NgbPopoverModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
+import { NgxPipeFunctionModule } from 'ngx-pipe-function';
import { ActionLabels, URLVerbs } from '~/app/shared/constants/app.constants';
import { FeatureTogglesGuardService } from '~/app/shared/services/feature-toggles-guard.service';
NgbNavModule,
NgbPopoverModule,
NgbTooltipModule,
+ NgxPipeFunctionModule,
SharedModule,
RouterModule,
TreeModule
</cd-table>
<ng-template #configurationSourceTpl
- let-row="row"
let-value="value">
<div [ngSwitch]="value">
import { ChartsModule } from 'ng2-charts';
import { ComponentsModule } from '~/app/shared/components/components.module';
-import { TableComponent } from '~/app/shared/datatable/table/table.component';
import { RbdConfigurationEntry } from '~/app/shared/models/configuration';
-import { PipesModule } from '~/app/shared/pipes/pipes.module';
import { FormatterService } from '~/app/shared/services/formatter.service';
import { RbdConfigurationService } from '~/app/shared/services/rbd-configuration.service';
+import { SharedModule } from '~/app/shared/shared.module';
import { configureTestBed } from '~/testing/unit-test-helper';
import { RbdConfigurationListComponent } from './rbd-configuration-list.component';
ComponentsModule,
NgbDropdownModule,
ChartsModule,
- PipesModule,
+ SharedModule,
NgbTooltipModule
],
- declarations: [RbdConfigurationListComponent, TableComponent],
+ declarations: [RbdConfigurationListComponent],
providers: [FormatterService, RbdConfigurationService]
});
</ng-template>
<ng-template #deleteTpl
- let-expiresAt>
+ let-expiresAt="expiresAt"
+ let-isExpired="isExpired">
<p class="text-danger"
- *ngIf="!isExpired(expiresAt)">
+ *ngIf="!isExpired">
<strong>
<ng-container i18n>This image is protected until {{ expiresAt | cdDate }}.</ng-container>
</strong>
import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
import moment from 'moment';
+import { NgxPipeFunctionModule } from 'ngx-pipe-function';
import { ToastrModule } from 'ngx-toastr';
import { of } from 'rxjs';
RouterTestingModule,
SharedModule,
NgbNavModule,
+ NgxPipeFunctionModule,
ToastrModule.forRoot()
],
providers: [TaskListService]
const namespace = this.selection.first().namespace;
const imageId = this.selection.first().id;
const expiresAt = this.selection.first().deferment_end_time;
+ const isExpired = moment().isAfter(expiresAt);
const imageIdSpec = new ImageSpec(poolName, namespace, imageId);
this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
itemDescription: 'RBD',
itemNames: [imageIdSpec],
bodyTemplate: this.deleteTpl,
- bodyContext: { $implicit: expiresAt },
+ bodyContext: { expiresAt, isExpired },
submitActionObservable: () =>
this.taskWrapper.wrapTaskAroundCall({
task: new FinishedTask('rbd/trash/remove', {
});
}
- isExpired(expiresAt: string): boolean {
- return moment().isAfter(expiresAt);
- }
-
purgeModal() {
this.modalService.show(RbdTrashPurgeModalComponent);
}
});
it('should test all quota table actions permission combinations', () => {
- const permissionHelper: PermissionHelper = new PermissionHelper(component.permission);
+ const permissionHelper: PermissionHelper = new PermissionHelper(component.permission, {
+ single: { dirValue: 0 },
+ multiple: [{ dirValue: 0 }, {}]
+ });
const tableActions = permissionHelper.setPermissionsAndGetActions(
component.quota.tableActions
);
NgbTooltipModule,
NgbTypeaheadModule
} from '@ng-bootstrap/ng-bootstrap';
+import { NgxPipeFunctionModule } from 'ngx-pipe-function';
import { SharedModule } from '~/app/shared/shared.module';
import { PerformanceCounterModule } from '../performance-counter/performance-counter.module';
CephSharedModule,
NgbDatepickerModule,
NgbPopoverModule,
- NgbDropdownModule
+ NgbDropdownModule,
+ NgxPipeFunctionModule
],
declarations: [
HostsComponent,
<div [ngbNavOutlet]="nav"></div>
<ng-template #markOsdConfirmationTpl
- let-markActionDescription="markActionDescription">
- <ng-container i18n><strong>OSD(s) {{ getSelectedOsdIds() | join }}</strong> will be marked
+ let-markActionDescription="markActionDescription"
+ let-osdIds="osdIds">
+ <ng-container i18n><strong>OSD(s) {{ osdIds | join }}</strong> will be marked
<strong>{{ markActionDescription }}</strong> if you proceed.</ng-container>
</ng-template>
const tableActionElement = fixture.debugElement.query(By.directive(TableActionsComponent));
const toClassName = TestBed.inject(TableActionsComponent).toClassName;
const getActionClasses = (action: CdTableAction) =>
- tableActionElement.query(By.css(`[ngbDropdownItem].${toClassName(action.name)}`)).classes;
+ tableActionElement.query(By.css(`[ngbDropdownItem].${toClassName(action)}`)).classes;
component.tableActions.forEach((action) => {
if (action.name === 'Create') {
*/
getSelectedOsdIds(): number[] {
const osdIds = this.osds.map((osd) => osd.id);
- return this.selection.selected.map((row) => row.id).filter((id) => osdIds.includes(id));
+ return this.selection.selected
+ .map((row) => row.id)
+ .filter((id) => osdIds.includes(id))
+ .sort();
}
getSelectedOsds(): any[] {
}
showConfirmationModal(markAction: string, onSubmit: (id: number) => Observable<any>) {
+ const osdIds = this.getSelectedOsdIds();
this.bsModalRef = this.modalService.show(ConfirmationModalComponent, {
titleText: $localize`Mark OSD ${markAction}`,
buttonText: $localize`Mark ${markAction}`,
bodyTpl: this.markOsdConfirmationTpl,
bodyContext: {
- markActionDescription: markAction
+ markActionDescription: markAction,
+ osdIds
},
onSubmit: () => {
observableForkJoin(
<ng-template #statusTpl
let-row="row">
<span class="badge"
- [ngClass]="getStatusClass(row.status)">
+ [ngClass]="row | pipeFunction:getStatusClass">
{{ row.status_desc }}
</span>
</ng-template>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import _ from 'lodash';
+import { NgxPipeFunctionModule } from 'ngx-pipe-function';
import { of } from 'rxjs';
import { CephModule } from '~/app/ceph/ceph.module';
};
configureTestBed({
- imports: [HttpClientTestingModule, CephModule, CoreModule, SharedModule]
+ imports: [HttpClientTestingModule, CephModule, CoreModule, NgxPipeFunctionModule, SharedModule]
});
beforeEach(() => {
}
}
- getStatusClass(status: number) {
+ getStatusClass(row: Daemon): string {
return _.get(
{
'-1': 'badge-danger',
'0': 'badge-warning',
'1': 'badge-success'
},
- status,
+ row.status,
'badge-dark'
);
}
import { RouterTestingModule } from '@angular/router/testing';
import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
+import { NgxPipeFunctionModule } from 'ngx-pipe-function';
import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
import { SummaryService } from '~/app/shared/services/summary.service';
let fixture: ComponentFixture<ServiceDetailsComponent>;
configureTestBed({
- imports: [HttpClientTestingModule, RouterTestingModule, SharedModule, NgbNavModule],
+ imports: [
+ HttpClientTestingModule,
+ RouterTestingModule,
+ SharedModule,
+ NgbNavModule,
+ NgxPipeFunctionModule
+ ],
declarations: [ServiceDetailsComponent, ServiceDaemonListComponent],
providers: [{ provide: SummaryService, useValue: { subscribeOnce: jest.fn() } }]
});
return (ecpControl.valid || ecpControl.disabled) && ecp ? pgs / (ecp.k + ecp.m) : 0;
}
- private alignPgs(pgs = this.form.getValue('pgNum')) {
+ alignPgs(pgs = this.form.getValue('pgNum')) {
this.setPgs(Math.round(this.calculatePgPower(pgs < 1 ? 1 : pgs)));
}
<div class="col-12">
<button type="button"
class="btn btn-light float-right tc_addCapButton"
- [disabled]="hasAllCapabilities()"
+ [disabled]="capabilities | pipeFunction:hasAllCapabilities"
i18n-ngbTooltip
ngbTooltip="All capabilities are already added."
- [disableTooltip]="!hasAllCapabilities()"
+ [disableTooltip]="!(capabilities | pipeFunction:hasAllCapabilities)"
triggers="pointerenter:pointerleave"
(click)="showCapabilityModal()">
<i [ngClass]="[icons.add]"></i>
import { RouterTestingModule } from '@angular/router/testing';
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
+import { NgxPipeFunctionModule } from 'ngx-pipe-function';
import { ToastrModule } from 'ngx-toastr';
import { of as observableOf } from 'rxjs';
RouterTestingModule,
SharedModule,
ToastrModule.forRoot(),
- NgbTooltipModule
+ NgbTooltipModule,
+ NgxPipeFunctionModule
]
});
fixture.detectChanges();
- expect(component.hasAllCapabilities()).toBeTruthy();
+ expect(component.hasAllCapabilities(component.capabilities)).toBeTruthy();
const capabilityButton = fixture.debugElement.nativeElement.querySelector('.tc_addCapButton');
expect(capabilityButton.disabled).toBeTruthy();
});
// Add
// Create an observable to add the capability when the form is submitted.
this.submitObservables.push(this.rgwUserService.addCapability(uid, cap.type, cap.perm));
- this.capabilities.push(cap);
+ this.capabilities = [...this.capabilities, cap]; // Notify Angular CD
}
// Mark the form as dirty to be able to submit it.
this.userForm.markAsDirty();
);
// Remove the capability to update the UI.
this.capabilities.splice(index, 1);
+ this.capabilities = [...this.capabilities]; // Notify Angular CD
// Mark the form as dirty to be able to submit it.
this.userForm.markAsDirty();
}
- hasAllCapabilities() {
- return !_.difference(RgwUserCapabilities.getAll(), _.map(this.capabilities, 'type')).length;
+ hasAllCapabilities(capabilities: RgwUserCapability[]) {
+ return !_.difference(RgwUserCapabilities.getAll(), _.map(capabilities, 'type')).length;
}
/**
import { RouterModule, Routes } from '@angular/router';
import { NgbNavModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
+import { NgxPipeFunctionModule } from 'ngx-pipe-function';
import { ActionLabels, URLVerbs } from '~/app/shared/constants/app.constants';
import { AuthGuardService } from '~/app/shared/services/auth-guard.service';
PerformanceCounterModule,
NgbNavModule,
RouterModule,
- NgbTooltipModule
+ NgbTooltipModule,
+ NgxPipeFunctionModule
],
exports: [
Rgw501Component,
import { RouterModule, Routes } from '@angular/router';
import { NgbNavModule, NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap';
+import { NgxPipeFunctionModule } from 'ngx-pipe-function';
import { ActionLabels, URLVerbs } from '~/app/shared/constants/app.constants';
import { SharedModule } from '~/app/shared/shared.module';
SharedModule,
NgbNavModule,
NgbPopoverModule,
+ NgxPipeFunctionModule,
RouterModule
],
declarations: [
import { NgbDropdownModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
import { NgxDatatableModule } from '@swimlane/ngx-datatable';
+import { NgxPipeFunctionModule } from 'ngx-pipe-function';
import { ComponentsModule } from '../components/components.module';
import { PipesModule } from '../pipes/pipes.module';
imports: [
CommonModule,
NgxDatatableModule,
+ NgxPipeFunctionModule,
FormsModule,
NgbDropdownModule,
NgbTooltipModule,
<div class="btn-group">
- <ng-container *ngIf="getCurrentButton() as action">
+ <ng-container *ngIf="currentAction">
<button type="button"
- title="{{ useDisableDesc(action) }}"
+ title="{{ useDisableDesc(currentAction) }}"
class="btn btn-{{btnColor}}"
- [ngClass]="{'disabled': disableSelectionAction(action)}"
- (click)="useClickAction(action)"
- [routerLink]="useRouterLink(action)"
- [preserveFragment]="action.preserveFragment ? '' : null">
- <i [ngClass]="[action.icon]"></i>
- <span>{{ action.name }}</span>
+ [ngClass]="{'disabled': disableSelectionAction(currentAction)}"
+ (click)="useClickAction(currentAction)"
+ [routerLink]="useRouterLink(currentAction)"
+ [preserveFragment]="currentAction.preserveFragment ? '' : null">
+ <i [ngClass]="[currentAction.icon]"></i>
+ <span>{{ currentAction.name }}</span>
</button>
</ng-container>
<div class="btn-group"
role="group"
aria-label="Button group with nested dropdown">
<button class="btn btn-{{btnColor}} dropdown-toggle-split"
- *ngIf="showDropDownActions()"
+ *ngIf="dropDownActions.length > 1"
ngbDropdownToggle>
<ng-container *ngIf="dropDownOnly">{{ dropDownOnly }} </ng-container>
<span *ngIf="!dropDownOnly"
ngbDropdownMenu>
<ng-container *ngFor="let action of dropDownActions">
<button ngbDropdownItem
- class="{{ toClassName(action['name']) }}"
+ class="{{ toClassName(action) }}"
title="{{ useDisableDesc(action) }}"
(click)="useClickAction(action)"
[routerLink]="useRouterLink(action)"
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
+import { NgxPipeFunctionModule } from 'ngx-pipe-function';
+
import { ComponentsModule } from '~/app/shared/components/components.module';
import { CdTableAction } from '~/app/shared/models/cd-table-action';
import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
configureTestBed({
declarations: [TableActionsComponent],
- imports: [ComponentsModule, RouterTestingModule]
+ imports: [ComponentsModule, NgxPipeFunctionModule, RouterTestingModule]
});
beforeEach(() => {
});
it('should convert any name to a proper CSS class', () => {
- expect(component.toClassName('Create')).toBe('create');
- expect(component.toClassName('Mark x down')).toBe('mark-x-down');
- expect(component.toClassName('?Su*per!')).toBe('super');
+ expect(component.toClassName({ name: 'Create' } as CdTableAction)).toBe('create');
+ expect(component.toClassName({ name: 'Mark x down' } as CdTableAction)).toBe('mark-x-down');
+ expect(component.toClassName({ name: '?Su*per!' } as CdTableAction)).toBe('super');
});
describe('useDisableDesc', () => {
-import { Component, Input, OnInit } from '@angular/core';
+import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import _ from 'lodash';
templateUrl: './table-actions.component.html',
styleUrls: ['./table-actions.component.scss']
})
-export class TableActionsComponent implements OnInit {
+export class TableActionsComponent implements OnChanges, OnInit {
@Input()
permission: Permission;
@Input()
@Input()
dropDownOnly?: string;
+ currentAction?: CdTableAction;
// Array with all visible actions
dropDownActions: CdTableAction[] = [];
ngOnInit() {
this.removeActionsWithNoPermissions();
+ this.onSelectionChange();
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes.selection) {
+ this.onSelectionChange();
+ }
+ }
+
+ onSelectionChange(): void {
this.updateDropDownActions();
+ this.updateCurrentAction();
}
- toClassName(name: string): string {
- return name
+ toClassName(action: CdTableAction): string {
+ return action.name
.replace(/ /g, '-')
.replace(/[^a-z-]/gi, '')
.toLowerCase();
);
}
- private updateDropDownActions() {
+ private updateDropDownActions(): void {
this.dropDownActions = this.tableActions.filter((action) =>
action.visible ? action.visible(this.selection) : action
);
* Default button conditions of actions:
* - 'create' actions can be used with no or multiple selections
* - 'update' and 'delete' actions can be used with one selection
- *
- * @returns {CdTableAction}
*/
- getCurrentButton(): CdTableAction {
+ private updateCurrentAction(): void {
if (this.dropDownOnly) {
- return undefined;
+ this.currentAction = undefined;
+ return;
}
let buttonAction = this.dropDownActions.find((tableAction) => this.showableAction(tableAction));
if (!buttonAction && this.dropDownActions.length > 0) {
buttonAction = this.dropDownActions[0];
}
- return buttonAction;
+ this.currentAction = buttonAction;
}
/**
);
}
- showDropDownActions() {
- this.updateDropDownActions();
- return this.dropDownActions.length > 1;
- }
-
useClickAction(action: CdTableAction) {
/**
* In order to show tooltips for deactivated menu items, the class
const result = action.disable(this.selection);
return _.isString(result) ? result : undefined;
}
-
return undefined;
}
}
import { NgbDropdownModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
import { NgxDatatableModule } from '@swimlane/ngx-datatable';
+import { NgxPipeFunctionModule } from 'ngx-pipe-function';
import { ComponentsModule } from '~/app/shared/components/components.module';
import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
RouterTestingModule,
NgbDropdownModule,
PipesModule,
- NgbTooltipModule
+ NgbTooltipModule,
+ NgxPipeFunctionModule
]
});
<ng-template #classAddingTpl
let-value="value">
- <span class="{{useCustomClass(value)}}">{{ value }}</span>
+ <span class="{{ value | pipeFunction:useCustomClass:this }}">{{ value }}</span>
</ng-template>
<ng-template #badgeTpl
import { NgbDropdownModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
import { NgxDatatableModule } from '@swimlane/ngx-datatable';
import _ from 'lodash';
+import { NgxPipeFunctionModule } from 'ngx-pipe-function';
import { ComponentsModule } from '~/app/shared/components/components.module';
import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
imports: [
BrowserAnimationsModule,
NgxDatatableModule,
+ NgxPipeFunctionModule,
FormsModule,
ComponentsModule,
RouterTestingModule,
export class PermissionHelper {
tac: TableActionsComponent;
permission: Permission;
+ selection: { single: object; multiple: object[] };
- constructor(permission: Permission) {
+ /**
+ * @param permission The permissions used by this test.
+ * @param selection The selection used by this test. Configure this if
+ * the table actions require a more complex selection object to perform
+ * a correct test run.
+ * Defaults to `{ single: {}, multiple: [{}, {}] }`.
+ */
+ constructor(permission: Permission, selection?: { single: object; multiple: object[] }) {
this.permission = permission;
+ this.selection = _.defaultTo(selection, { single: {}, multiple: [{}, {}] });
}
setPermissionsAndGetActions(tableActions: CdTableAction[]): any {
testScenarios() {
const result: any = {};
// 'multiple selections'
- result.multiple = this.testScenario([{}, {}]);
+ result.multiple = this.testScenario(this.selection.multiple);
// 'select executing item'
- result.executing = this.testScenario([{ cdExecuting: 'someAction' }]);
+ result.executing = this.testScenario([
+ _.merge({ cdExecuting: 'someAction' }, this.selection.single)
+ ]);
// 'select non-executing item'
- result.single = this.testScenario([{}]);
+ result.single = this.testScenario([this.selection.single]);
// 'no selection'
result.no = this.testScenario([]);
private testScenario(selection: object[]) {
this.setSelection(selection);
- const btn = this.tac.getCurrentButton();
- return btn ? btn.name : '';
+ const action: CdTableAction = this.tac.currentAction;
+ return action ? action.name : '';
}
setSelection(selection: object[]) {
this.tac.selection.selected = selection;
+ this.tac.onSelectionChange();
}
}
const tableActionElement = fixture.debugElement.query(By.directive(TableActionsComponent));
const toClassName = TestBed.inject(TableActionsComponent).toClassName;
const getActionElement = (action: CdTableAction) =>
- tableActionElement.query(By.css(`[ngbDropdownItem].${toClassName(action.name)}`));
+ tableActionElement.query(By.css(`[ngbDropdownItem].${toClassName(action)}`));
const actions = {};
tableActions.forEach((action) => {