Skip to content

Commit 056dda7

Browse files
Add bulk delete for test runs
1 parent cc04f4c commit 056dda7

File tree

14 files changed

+257
-80
lines changed

14 files changed

+257
-80
lines changed

e2e/elements/modal.element.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,36 @@ export class Modal extends BaseElement {
88

99
private _keys = {
1010
yes: 'yes',
11-
no: 'no'
11+
no: 'no',
12+
cancel: 'cancel'
1213
};
1314

1415
private async _clickActionBtn(buttonName: string) {
15-
const isVisible = await this.isVisible();
16-
if (!isVisible) {
16+
const isPresent = await this.isPresent();
17+
if (!isPresent) {
1718
throw new Error('You are trying to click button on the modal but the modal does not exists');
1819
}
1920

2021
return this.element.element(by.xpath(`.//button[text()="${buttonName}"]`)).click();
2122
}
2223

24+
private _clickSuccess() {
25+
return this.element.element(by.css(`button.btn-success`)).click();
26+
}
27+
2328
clickYes() {
2429
return this._clickActionBtn(this._keys.yes);
2530
}
2631

2732
clickNo() {
2833
return this._clickActionBtn(this._keys.no);
2934
}
35+
36+
clickCancel() {
37+
return this._clickActionBtn(this._keys.cancel);
38+
}
39+
40+
clickSuccess() {
41+
return this._clickSuccess();
42+
}
3043
}

e2e/elements/smartTable.element/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Row, CellElements } from './row.element';
99
import { ManageColumns } from './manageCollumns.element';
1010
import { compareCSVStrings } from '../../utils/csv.util';
1111
import { Dots } from '../dots.element';
12+
import { Checkbox } from '../checkbox.element';
1213

1314
const EC = protractor.ExpectedConditions;
1415

@@ -23,6 +24,8 @@ export class SmartTable extends BaseElement {
2324
private refreshButton = this.element.element(by.css('.actions-header .ft-refresh'));
2425
private totalLabel = this.element.element(by.css('.ft-total-label'));
2526
private getCSVButton = this.element.element(by.id('getSCV'));
27+
private deleteAllButton = this.element.element(by.css('.bulk-delete'));
28+
private selectAllCheckbox = new Checkbox(this.element.element(by.css('th input[name="select_all"]')));
2629

2730
constructor(locator: Locator) {
2831
super(locator);
@@ -169,6 +172,10 @@ export class SmartTable extends BaseElement {
169172
return this.bulkEditRow.clickAction();
170173
}
171174

175+
public deleteAll() {
176+
return this.deleteAllButton.click();
177+
}
178+
172179
public async getCreationTextFieldValue(columnName: string) {
173180
const columnIndex = await this.getColumnIndex(columnName);
174181
if (await this.isCreationOpened()) {
@@ -368,6 +375,10 @@ export class SmartTable extends BaseElement {
368375
const row = await this.getRow(searchValue, searchColumn);
369376
return row.removeMultiselectValueByColumnIndex(value, index);
370377
}
378+
379+
public isSelectorAvailable(): Promise<boolean> {
380+
return this.selectAllCheckbox.isPresent();
381+
}
371382

372383
private isCreationOpened() {
373384
return this.creationRow.isPresent();

e2e/pages/login.po/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class LogIn extends BasePage {
1010
}
1111

1212
async navigateTo() {
13+
await browser.manage().deleteCookie('iio78');
1314
await browser.get(baseUrl);
1415
return this.waitForIsOpened();
1516
}

e2e/pages/testrun/list.po/index.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { elements, names, columns } from './constants';
22
import { BasePage } from '../../base.po';
33
import { waiter } from '../../../utils/wait.util';
4+
import { promise } from 'protractor';
45

56
class TestRunList extends BasePage {
67
constructor() {
@@ -11,8 +12,16 @@ class TestRunList extends BasePage {
1112
return elements.testRunsTable.clickAction(buildName, columns.build);
1213
}
1314

14-
isTestRunRowDisplayed(buildName: string) {
15-
return elements.testRunsTable.isRowExists(buildName, columns.build);
15+
async areAllTestRunsDisplayed(...buildNames: string[]) {
16+
for (let i = 0; i < buildNames.length; i++) {
17+
const buildName = buildNames[i];
18+
const displayed = await elements.testRunsTable.isRowExists(buildName, columns.build);
19+
if(!displayed) {
20+
return false;
21+
}
22+
}
23+
24+
return true;
1625
}
1726

1827
openTestRun(buildName: string) {
@@ -37,13 +46,13 @@ class TestRunList extends BasePage {
3746

3847
async waitForTestRun(buildName: string) {
3948
return waiter.forTrue(async () => {
40-
const isDisplayed = await this.isTestRunRowDisplayed(buildName);
49+
const isDisplayed = await this.areAllTestRunsDisplayed(buildName);
4150
if (isDisplayed) {
4251
return true;
4352
}
4453
await this.menuBar.import();
4554
await this.menuBar.testRuns();
46-
return await this.isTestRunRowDisplayed(buildName);
55+
return await this.areAllTestRunsDisplayed(buildName);
4756
}, 5, 3000);
4857
}
4958

@@ -56,9 +65,24 @@ class TestRunList extends BasePage {
5665
return elements.testRunsTable.editRow(name, columns.milestone, build_name, columns.build);
5766
}
5867

59-
isTableEditable(): any {
68+
isTableEditable(): Promise<boolean> {
6069
return elements.testRunsTable.isRowEditableByIndex(0);
6170
}
71+
72+
clickDeleteAll(): promise.Promise<void> {
73+
return elements.testRunsTable.deleteAll();
74+
}
75+
76+
async selectTestRun(...build_names: string[]): Promise<void> {
77+
for (let i = 0; i < build_names.length; i++) {
78+
const build_name = build_names[i];
79+
await elements.testRunsTable.selectRow(build_name, columns.build);
80+
}
81+
}
82+
83+
isSelectorAvailable(): Promise<boolean> {
84+
return elements.testRunsTable.isSelectorAvailable();
85+
}
6286
}
6387

6488
export const testRunList = new TestRunList();
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { logIn } from '../../pages/login.po';
2+
import { projectView } from '../../pages/project/view.po';
3+
import { ProjectHelper } from '../../helpers/project.helper';
4+
import { TestSuite } from '../../../src/app/shared/models/testSuite';
5+
import { testRunList } from '../../pages/testrun/list.po';
6+
import { TestRun } from '../../../src/app/shared/models/testRun';
7+
import { Milestone } from '../../../src/app/shared/models/milestone';
8+
import users from '../../data/users.json';
9+
import using from 'jasmine-data-provider';
10+
import cucumberImport from '../../data/import/cucumber.json';
11+
12+
const editorExamples = {
13+
localAdmin: users.localAdmin,
14+
localManager: users.localManager,
15+
manager: users.manager,
16+
};
17+
18+
const notEditorExamples = {
19+
localEngineer: users.localEngineer
20+
};
21+
22+
describe('Test Run List: Bulk Delete:', () => {
23+
const projectHelper: ProjectHelper = new ProjectHelper();
24+
const suite: TestSuite = { name: 'Smoke' };
25+
let testRuns: TestRun[];
26+
27+
beforeAll(async () => {
28+
await projectHelper.init({
29+
localAdmin: users.localAdmin,
30+
localManager: users.localManager,
31+
manager: users.manager,
32+
admin: users.autoAdmin,
33+
localEngineer: users.localEngineer
34+
});
35+
});
36+
37+
afterAll(async () => {
38+
return projectHelper.dispose();
39+
});
40+
41+
using(editorExamples, (user, description) => {
42+
describe(`${description} role:`, () => {
43+
44+
beforeAll(async () => {
45+
testRuns = await projectHelper.importer
46+
.executeCucumberImport(suite.name,
47+
[cucumberImport,cucumberImport,cucumberImport,cucumberImport,cucumberImport],
48+
[`build_1.json`, `build_2.json`, `build_3.json`, `build_4.json`, `build_5.json`]);
49+
await logIn.logInAs(user.user_name, user.password);
50+
await projectHelper.openProject();
51+
return projectView.menuBar.testRuns();
52+
});
53+
54+
it('Can Cancel Bulk Delete', async () => {
55+
await testRunList.selectTestRun(testRuns[0].build_name, testRuns[1].build_name, testRuns[2].build_name);
56+
await testRunList.clickDeleteAll();
57+
await expect(testRunList.modal.isVisible()).toBe(true, 'Modal was not opened on Delete All click');
58+
await testRunList.modal.clickCancel();
59+
await expect(testRunList.modal.isPresent()).toBe(false, 'Modal was not closed on Cancel click');
60+
return expect(testRunList.areAllTestRunsDisplayed(testRuns[0].build_name, testRuns[1].build_name, testRuns[2].build_name))
61+
.toBe(true, 'Test runs were removed after cancelling bulk delete');
62+
});
63+
64+
it('Can Execute Bulk Delete', async () => {
65+
await testRunList.clickDeleteAll();
66+
await testRunList.modal.clickSuccess();
67+
await expect(testRunList.modal.isPresent()).toBe(false, 'Modal was not closed on Yes click');
68+
await testRunList.notification.assertIsSuccess('Test runs were deleted.');
69+
return expect(testRunList.areAllTestRunsDisplayed(testRuns[0].build_name, testRuns[1].build_name, testRuns[2].build_name))
70+
.toBe(false, 'Test runs were not removed after bulk delete');
71+
});
72+
});
73+
});
74+
75+
using(notEditorExamples, (user, description) => {
76+
describe(`${description} role:`, () => {
77+
78+
beforeAll(async () => {
79+
testRuns = await projectHelper.importer
80+
.executeCucumberImport(suite.name,
81+
[cucumberImport],
82+
[`build_1.json`]);
83+
await logIn.logInAs(user.user_name, user.password);
84+
await projectHelper.openProject();
85+
return projectView.menuBar.testRuns();
86+
});
87+
88+
it('Table Row selector is not available', async () => {
89+
return expect(testRunList.isSelectorAvailable()).toBe(false, 'Selector is available!');
90+
});
91+
});
92+
});
93+
});

e2e/specs/testrun/list.testrun.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,17 @@ describe('Test Run List:', () => {
7878

7979
it('Can filter by inactive Milestone', async () => {
8080
await testRunList.filterByMilestone(milestones.inactive.name);
81-
await expect(testRunList.isTestRunRowDisplayed(testRuns.build_2.build_name))
81+
await expect(testRunList.areAllTestRunsDisplayed(testRuns.build_2.build_name))
8282
.toBe(false, 'Test run with another milestone is still present');
83-
return expect(testRunList.isTestRunRowDisplayed(testRuns.build_1.build_name))
83+
return expect(testRunList.areAllTestRunsDisplayed(testRuns.build_1.build_name))
8484
.toBe(true, 'Test run with milestone is not present');
8585
});
8686

8787
it('Can filter by active Milestone', async () => {
8888
await testRunList.filterByMilestone(milestones.active.name);
89-
await expect(testRunList.isTestRunRowDisplayed(testRuns.build_1.build_name))
89+
await expect(testRunList.areAllTestRunsDisplayed(testRuns.build_1.build_name))
9090
.toBe(false, 'Test run with another milestone is still present');
91-
return expect(testRunList.isTestRunRowDisplayed(testRuns.build_2.build_name))
91+
return expect(testRunList.areAllTestRunsDisplayed(testRuns.build_2.build_name))
9292
.toBe(true, 'Test run with milestone is not present');
9393
});
9494

@@ -121,17 +121,17 @@ describe('Test Run List:', () => {
121121

122122
it('Can filter by inactive Milestone', async () => {
123123
await testRunList.filterByMilestone(milestones.inactive.name);
124-
await expect(testRunList.isTestRunRowDisplayed(testRuns.build_2.build_name))
124+
await expect(testRunList.areAllTestRunsDisplayed(testRuns.build_2.build_name))
125125
.toBe(false, 'Test run with another milestone is still present');
126-
return expect(testRunList.isTestRunRowDisplayed(testRuns.build_1.build_name))
126+
return expect(testRunList.areAllTestRunsDisplayed(testRuns.build_1.build_name))
127127
.toBe(true, 'Test run with milestone is not present');
128128
});
129129

130130
it('Can filter by active Milestone', async () => {
131131
await testRunList.filterByMilestone(milestones.active.name);
132-
await expect(testRunList.isTestRunRowDisplayed(testRuns.build_1.build_name))
132+
await expect(testRunList.areAllTestRunsDisplayed(testRuns.build_1.build_name))
133133
.toBe(false, 'Test run with another milestone is still present');
134-
return expect(testRunList.isTestRunRowDisplayed(testRuns.build_2.build_name))
134+
return expect(testRunList.areAllTestRunsDisplayed(testRuns.build_2.build_name))
135135
.toBe(true, 'Test run with milestone is not present');
136136
});
137137
});

src/app/elements/table/table.filter.component.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<manage-columns-modal *ngIf="columnManagement && columns" [(isHidden)]="hideManageColumnsModal" [shownColumns]="columns"
2-
[hiddenColumns]="hiddenColumns" [buttons]="[{name:'Apply', execute:true }, {name:'Cancel', execute:false}]"
2+
[hiddenColumns]="hiddenColumns" [buttons]="[{name:'Apply', execute:true }, {name:'cancel', execute:false}]"
33
(execute)="execute($event)" (closed)="wasClosed()"></manage-columns-modal>
4+
<simple-popup *ngIf="!hideBulkDeleteModal" title="Bulk Delete" message="You are about to permanently delete selected Test Runs. You will not be able to recover these Test Runs. This action cannot be undone!"
5+
[type]="'warning'" [buttons]="[{name:'I know what I`m doing', execute:true }, {name:'cancel', execute:false}]"
6+
(execute)="executeBulkDelete($event)" (closed)="wasClosed()"></simple-popup>
47

58
<div class="ft-wrapper-overflow">
69
<div *ngIf="columns" overflowParent class="no-padding ft-content-overflow">
@@ -12,6 +15,7 @@
1215
<mfBootstrapPaginator class="pull-left"></mfBootstrapPaginator>
1316
<p class="pull-left ft-total-label">Total Rows: {{filteredData?.length}} ({{data?.length}})</p>
1417
<div class="btn-group pull-right" role="group">
18+
<button *ngIf="allowBulkDelete && hasSelectedRows()" class="bulk-delete btn btn-sm btn-danger" (click)="confirmBulkDelete()">Delete All</button>
1519
<button *ngIf="filteredData && allowExport" id="getSCV" class="btn btn-sm btn-secondary"
1620
[matMenuTriggerFor]="appMenu">Get CSV</button>
1721
<mat-menu #appMenu="matMenu" xPosition="before">

src/app/elements/table/table.filter.component.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export class TableFilterComponent implements OnInit, AfterViewInit, OnDestroy, O
4444
@Input() actionsHeader = true;
4545
@Input() allowRefresh = false;
4646
@Input() allowBulkUpdate = false;
47+
@Input() allowBulkDelete = false;
4748
@Input() withSelector = false;
4849

4950
@Output() createEntity = new EventEmitter();
@@ -54,6 +55,7 @@ export class TableFilterComponent implements OnInit, AfterViewInit, OnDestroy, O
5455
@Output() shownData = new EventEmitter();
5556
@Output() refresh = new EventEmitter();
5657
@Output() bulkChanges = new EventEmitter();
58+
@Output() bulkDelete = new EventEmitter()
5759
@Output() lookupCreation = new EventEmitter<{ value: string, column: TFColumn, entity: any }>();
5860
@Output() lookupAction = new EventEmitter<{ value: string, column: TFColumn, entity: any }>();
5961

@@ -93,6 +95,7 @@ export class TableFilterComponent implements OnInit, AfterViewInit, OnDestroy, O
9395
lastSelectedRow = 0;
9496
errorMessage: string;
9597
hideManageColumnsModal = true;
98+
hideBulkDeleteModal = true;
9699
animate = false;
97100
timerToken: NodeJS.Timer;
98101
timer: NodeJS.Timer;
@@ -131,10 +134,10 @@ export class TableFilterComponent implements OnInit, AfterViewInit, OnDestroy, O
131134
});
132135
}
133136

134-
if (this.allowDelete || this.allowCreate || this.allowBulkUpdate) {
137+
if ((this.allowDelete || this.allowCreate || this.allowBulkUpdate) && (this.columns && !this.columns.find(x => x.name === 'Action'))) {
135138
this.columns.push({ name: 'Action', property: 'action', type: TFColumnType.button, editable: true });
136139
}
137-
if (this.allowBulkUpdate || this.withSelector) {
140+
if ((this.allowBulkUpdate || this.withSelector || this.allowBulkDelete) && (this.columns && !this.columns.find(x => x.name === 'Selector'))) {
138141
this.columns.unshift({ name: 'Selector', property: 'ft_select', type: TFColumnType.selector, editable: true, class: 'fit' });
139142
}
140143
}
@@ -238,6 +241,20 @@ export class TableFilterComponent implements OnInit, AfterViewInit, OnDestroy, O
238241
this.bulkChangeEntity = {};
239242
}
240243

244+
confirmBulkDelete() {
245+
this.hideBulkDeleteModal = false;
246+
}
247+
248+
async executeBulkDelete($event) {
249+
console.log(await $event)
250+
if (await $event) {
251+
const entitiesToDelete = this.getSelectedEntitites();
252+
this.bulkDelete.emit(entitiesToDelete);
253+
}
254+
this.hideBulkDeleteModal = true;
255+
}
256+
257+
241258
getSelectedEntitites() {
242259
return this.filteredData.filter(entity => entity.ft_select === true || entity.ft_select === 1);
243260
}
@@ -649,6 +666,7 @@ export class TableFilterComponent implements OnInit, AfterViewInit, OnDestroy, O
649666

650667
wasClosed() {
651668
this.hideManageColumnsModal = true;
669+
this.hideBulkDeleteModal = true;
652670
}
653671

654672
openlink(link) {

src/app/pages/project/testrun/testrun-list/testruns.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<br>
1414
<br>
1515
<div class="col-sm-12">
16-
<table-filter #testRunsTable *ngIf="tbCols && labels" [allowDelete]="allowDelete"
16+
<table-filter #testRunsTable *ngIf="tbCols && labels" [allowDelete]="allowDelete" [allowBulkDelete]="allowDelete" (bulkDelete)="bulkDelete($event)"
1717
[allowExport]="true" [data]="testRuns" (shownData)="tableDataUpdate($event)" [columns]="tbCols" [hiddenColumns]="hiddenCols" [defaultSortBy]="sortBy"
1818
[queryParams]="true" (rowClick)="rowClicked($event)" (dataChange)="testRunUpdate($event)" (action)="handleAction($event)"></table-filter>
1919
</div>

0 commit comments

Comments
 (0)