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

[TOS-992 / 989] fix: Do not Allow deletion of uploads when used in Test Plans and add Test Plan Name to popup. #289

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,7 @@ public void delete(@PathVariable("id") Long id) throws ResourceNotFoundException

@DeleteMapping(value = "/bulk")
@ResponseStatus(HttpStatus.ACCEPTED)
public void bulkDelete(@RequestParam(value = "ids[]") Long[] ids) throws ResourceNotFoundException {
for (Long id : ids) {
uploadService.delete(uploadService.find(id));
}
public void bulkDelete(@RequestParam(value = "ids[]") Long[] ids) throws Exception {
uploadService.bulkDelete(ids);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public interface UploadVersionRepository extends BaseRepository<UploadVersion, L
List<UploadVersion> findAllByLastUploadedTimeBeforeAndUploadTypeIn(Timestamp lastUploadedTime, Collection<UploadType> uploadType);

List<UploadVersion> findAllByUploadTypeIn(Collection<UploadType> uploadType);
List<UploadVersion> findAllByUploadId(Long uploadId);


Optional<UploadVersion> findByNameAndUploadId(String name, Long importedId);
Expand Down
36 changes: 34 additions & 2 deletions server/src/main/java/com/testsigma/service/UploadService.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
import com.testsigma.repository.UploadRepository;
import com.testsigma.specification.SearchCriteria;
import com.testsigma.specification.SearchOperation;
import com.testsigma.specification.TestCaseSpecificationsBuilder;
import com.testsigma.specification.UploadSpecificationsBuilder;
import com.testsigma.web.request.UploadRequest;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
Expand All @@ -50,6 +52,7 @@ public class UploadService extends XMLExportImportService<Upload> {
private final WorkspaceService workspaceService;
private final WorkspaceVersionService workspaceVersionService;
private final UploadVersionService uploadVersionService;
private final TestCaseService testCaseService;
private final UploadMapper mapper;

public Upload find(Long id) throws ResourceNotFoundException {
Expand Down Expand Up @@ -112,9 +115,38 @@ public Upload save(Upload upload) {
}

public void delete(Upload upload) {
this.uploadRepository.delete(upload);
publishEvent(upload, EventType.DELETE);
TestCaseSpecificationsBuilder builder = new TestCaseSpecificationsBuilder();
Long id = upload.getId();
String workspace_type = String.valueOf(upload.getWorkspace().getWorkspaceType());
List<SearchCriteria> params = new ArrayList<>();
List<UploadVersion> uploadVersions = uploadVersionService.findByUploadId(id);
for(UploadVersion uploadVersion: uploadVersions ) {
params.add(new SearchCriteria("testData", SearchOperation.EQUALITY, "testsigma-storage:/"+uploadVersion.getPath()));
builder.setParams(params);
Specification<TestCase> spec = builder.build();
Page<TestCase> linkedTestCases = testCaseService.findAll(spec, PageRequest.of(0, 1));
if(linkedTestCases.getTotalElements() > 0){
throw new DataIntegrityViolationException("dataIntegrityViolationException: Failed to delete some of the Uploads " +
"since they are already associated to some Test Cases.");
}
try{
this.uploadRepository.delete(upload);
}
catch(DataIntegrityViolationException e){
throw new DataIntegrityViolationException ("dataIntegrityViolationException: Failed to delete some of the Uploads " +
"since they are already associated to some Test Cases, please remove mapping and try again.");
}
publishEvent(upload, EventType.DELETE);
}
}
public void bulkDelete(Long[] ids) throws Exception {
TestCaseSpecificationsBuilder builder = new TestCaseSpecificationsBuilder();
for (Long id : ids) {
Upload upload = find(id);
this.delete(upload);
}
}


public void publishEvent(Upload upload, EventType eventType) {
UploadEvent<Upload> event = createEvent(upload, eventType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,4 +356,7 @@ public boolean isEntityAlreadyImported(Optional<UploadVersion> previous, UploadV
}


public List<UploadVersion> findByUploadId(Long id) {
return uploadVersionRepository.findAllByUploadId(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ protected Expression<String> getPath(SearchCriteria criteria, Root<TestCase> roo
} else if(criteria.getKey().equals("forLoopTestDataId")){
Join s = root.join("testSteps",JoinType.INNER);
return s.get("forLoopTestDataId");
} else if(criteria.getKey().equals("testData")){
Join s = root.join("testSteps", JoinType.INNER);
return s.get("testData");
}
return root.get(criteria.getKey());
}
Expand Down
4 changes: 2 additions & 2 deletions ui/src/app/components/uploads/list.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<div class="d-flex align-items-center ml-auto"
*ngIf="selectedUploads.length">
<button (click)="checkForLinkedEnvironments(null)"
<button (click)="checkForLinkedEntities()"
[matTooltip]="'hint.message.common.delete_selected' | translate"
class="btn icon-btn border-rds-2 ml-14"
*ngIf="selectedUploads.length">
Expand Down Expand Up @@ -97,7 +97,7 @@
(click)="openSaveUploadForm(upload)"
class="fa-pencil-on-paper action-icon"></a>
<a
data-placement="bottom" (click)="checkForLinkedEnvironments(upload.id, upload.name)"
data-placement="bottom" (click)="checkForLinkedEntities(upload.id, upload.name)"
[matTooltip]="'pagination.delete' | translate"
href="javascript:void(0)"
class="fa-trash-thin action-icon"></a>
Expand Down
64 changes: 57 additions & 7 deletions ui/src/app/components/uploads/list.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {UploadEntitiesModalComponent} from "../../shared/components/webcomponent
import {TestDeviceService} from "../../services/test-device.service";
import {ToastrService} from "ngx-toastr";
import {UploadVersionService} from "../../shared/services/upload-version.service";
import {TestCaseService} from "../../services/test-case.service";
import {LinkedEntitiesModalComponent} from "../../shared/components/webcomponents/linked-entities-modal.component";

@Component({
selector: 'app-uploads',
Expand Down Expand Up @@ -57,6 +59,9 @@ export class ListComponent extends BaseComponent implements OnInit {
private workspaceVersionService: WorkspaceVersionService,
private _snackBar: MatSnackBar,
private _appOverlayContainer: OverlayContainer,

private testCaseService :TestCaseService,

public environmentService: TestDeviceService) {
super(authGuard, notificationsService, translate, toastrService);
}
Expand Down Expand Up @@ -141,6 +146,45 @@ export class ListComponent extends BaseComponent implements OnInit {
}
}

checkForLinkedTestCases(testData?,id?) {
let testCases: InfiniteScrollableDataSource;
let query = "workspaceVersionId:" + this.versionId + ",testData:" + encodeURI(testData?.join("#"))
query = this.byPassSpecialCharacters(query);
testCases = new InfiniteScrollableDataSource(this.testCaseService,query);

waitTillRequestResponds();
let _this = this;

function waitTillRequestResponds() {
if (testCases.isFetching)
setTimeout(() => waitTillRequestResponds(), 100);
else {
if (testCases.isEmpty)
_this.openDeleteDialog(id);
else
_this.openLinkedTestCasesDialog(testCases);
}
}
}
private openLinkedTestCasesDialog(list) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Raksha-testsigma Needs a little formatting. In some cases, there is no line gap between two functions. In some other cases, there are quite a few. Use uniform line gaps

this.translate.get("uploads.linked.with.test_cases").subscribe((res) => {
this.matDialog.open(LinkedEntitiesModalComponent, {
width: '568px',
height: 'auto',
data: {
description: res,
linkedEntityList: list,
},
panelClass: ['mat-dialog', 'rds-none']
});
});
}



checkForLinkedEntities(id?: number, name?: string) {
this.checkForLinkedEnvironments(id, name);
}
checkForLinkedEnvironments(id, name?: string) {
let environmentResults: InfiniteScrollableDataSource;
environmentResults = new InfiniteScrollableDataSource(this.environmentService, "appUploadId@"+(id ? id : this.selectedUploads.join("#"))+",entityType:TEST_PLAN");
Expand All @@ -152,14 +196,18 @@ export class ListComponent extends BaseComponent implements OnInit {
setTimeout(() => waitTillRequestResponds(environmentResults, id, name), 100);
else {
if (environmentResults.isEmpty)
_this.openDeleteDialog(id, name);
else
_this.uploadVersionService.findAll("uploadId@" + (id ? id : _this.selectedUploads.join("#"))).subscribe(res => {
let uploadPaths = res.content.map(version =>"testsigma-storage:/" + version.path)
_this.checkForLinkedTestCases(uploadPaths,id);
})
else {
_this.openLinkedUploadsDialog(environmentResults);
}
}
}
}

private openLinkedUploadsDialog(list) {
private openLinkedUploadsDialog(list) {
this.translate.get("message.delete.uploads").subscribe((res) => {
this.matDialog.open(UploadEntitiesModalComponent, {
width: '568px',
Expand Down Expand Up @@ -240,8 +288,7 @@ export class ListComponent extends BaseComponent implements OnInit {
private destroyUpload(id: any) {
this.uploadService.destroy(id).subscribe(
() => {
this.translate.get('message.common.deleted.success', {FieldName: "Upload"})
.subscribe(res => this.showNotification(NotificationType.Success, res));
this.translate.get('message.common.deleted.success', {FieldName: "Upload"}).subscribe(res => this.showNotification(NotificationType.Success, res));
this.fetchUploads();
this.selectedUploads = [];
},
Expand All @@ -251,13 +298,16 @@ export class ListComponent extends BaseComponent implements OnInit {
}

private multipleDelete() {
this.uploadService.bulkDestroy(this.selectedUploads).subscribe(
this.uploadService.bulkDestroy(this.selectedUploads, this.versionId).subscribe(
() => {
this.translate.get("message.common.deleted.success", {FieldName: "Uploads"}).subscribe(res => this.showNotification(NotificationType.Success, res));
this.fetchUploads();
this.selectedUploads = []
},
(err) => this.translate.get("message.common.deleted.failure", {FieldName: "Uploads"}).subscribe(res => this.showAPIError(NotificationType.Error, res,"Uploads","Test Case")))
(err) => {
this.translate.get("message.common.uploads.deleted.failure", {FieldName: "Uploads"}).subscribe(res => this.showAPIError(NotificationType.Error, res,"Uploads","Test Case"));
this.fetchUploads();
})
}

private goToPreviousPageIfEmpty(res) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {Component, Inject} from '@angular/core';
import {MAT_DIALOG_DATA} from '@angular/material/dialog';
import {LinkedEntitiesModalComponent} from "./linked-entities-modal.component";
import {TestDevice} from "../../../models/test-device.model";
import {TestPlanService} from "../../../services/test-plan.service";
import {TestPlan} from "../../../models/test-plan.model";

@Component({
selector: 'app-uploads-entities-modal',
Expand All @@ -10,17 +12,43 @@ import {TestDevice} from "../../../models/test-device.model";
export class UploadEntitiesModalComponent extends LinkedEntitiesModalComponent {

constructor(
public testPlanService : TestPlanService,
@Inject(MAT_DIALOG_DATA) public modalData: any) {
super(modalData);
}


ngOnInit(): void {
this.setTestPlans(this.modalData?.linkedEntityList.cachedItems)
}

setTestPlans(testdevices){
let testPlanIds = [];
testdevices.forEach((data: TestDevice) => {
testPlanIds.push(data.testPlanId);
})
this.testPlanService.findAll("id@"+ testPlanIds.join("#")).subscribe(
res=> {
this.setTestPlanNames(res.content)
},
error=>{
console.log(error)
}
)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Raksha-testsigma Just correct the indentation of the brackets

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


openLinkedEntity(id) {
let entityUrl;
if(this.modalData.linkedEntityList['cachedItems'][0] instanceof TestDevice)
entityUrl = "/ui/td/plans/"+ id +"/details";
window.open(window.location.origin + entityUrl, "_blank");
}

private setTestPlanNames(content: TestPlan[]) {
function findAssociatedTestPlan(d) {
return content.find((t)=> d.testPlanId==t.id)
}
this.modalData.linkedEntityList.cachedItems.forEach(
(d,i)=> this.modalData.linkedEntityList.cachedItems[i].name = findAssociatedTestPlan(d).name)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,30 @@
class="list-container virtual-scroll-viewport"
itemSize="27"
style="max-height:261px;height:261px">
<div class="ts-col-100 pointer d-flex">
<div class="text-truncate w-235">
<span
[textContent]="'Test Plans'">
</span>
</div>
<div>
<span
[textContent]="'Test Machines'">
</span>
</div></div>
<div
*cdkVirtualFor="let linkedEntity of modalData.linkedEntityList ; let index=index; let first=first"
[class.border-separator-t-1]="first"
class="list-view md-pm green-highlight">
<div (click)="openLinkedEntity(linkedEntity.testPlanId)" class="ts-col-100 pointer d-flex" target="_blank">
<div class="text-truncate">

<div class="text-truncate w-235">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Raksha-testsigma Same as in previous comment. Why the extra line gap here?

<span
[textContent]="linkedEntity.name"
[matTooltip]="linkedEntity.title">
</span>
</div>
<div>
<span
[textContent]="linkedEntity.title"
[matTooltip]="linkedEntity.title">
Expand Down
4 changes: 2 additions & 2 deletions ui/src/app/shared/services/upload.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export class UploadService {
);
}

public bulkDestroy(ids: number[]): Observable<void> {
let params = new HttpParams().set("ids[]", ids.toString());
public bulkDestroy(ids: number[], workspaceVersionId: number ): Observable<void> {
let params = new HttpParams().set("ids[]", ids.toString()).set("workspaceVersionId", workspaceVersionId);
return this.http.delete<void>(this.URLConstants.uploadsUrl + "/bulk", {
headers: this.httpHeaders.contentTypeApplication,
params: params
Expand Down
5 changes: 4 additions & 1 deletion ui/src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"message.common.confirmation.upload_des":"You will lose all Uploaded version, Run reports and Run configuration associated with this {{Item}}.",
"message.common.confirmation.test_suite_des":"You will lose all Run reports associated with this {{Item}}.",
"message.common.confirmation.test_plan_des":"You will lose all Run reports associated with this {{Item}}.",
"message.common.confirmation.uploads_des": "This file and all of its versions will be permanently deleted and not be retrievable. Please type DELETE to confirm",
"message.common.confirmation.action_undone":"This action cannot be undone.",
"message.common.confirmation.btn_delete": "I understand, delete this {{Item}}",
"message.common.confirmation.placeholder": "Enter 'DELETE' to confirm.",
Expand Down Expand Up @@ -1386,6 +1387,7 @@
"uploads.list.message.copied": "Copied!",
"uploads.list.copy_path": "Copy Path",
"uploads.bulk_delete.confirmation.message": "Are you sure you want to delete the selected ( {{FieldName}} ) Uploads?",
"uploads.linked.with.test_cases": "This Upload is used in the below Test Cases,Please remove the mapping and try again",
"uploads.form.create_title": "Upload",
"uploads.form.edit_title": "Update File",
"uploads.form.label.select_file_type": "Select Type",
Expand Down Expand Up @@ -2139,5 +2141,6 @@
"hint.xray.assets": "Enable Jira Integration to publish the logs, screenshots & videos in execution result.",
"xray.filter.title": "Added Xray Id",
"err.empty_elements": "Error While fetching empty elements",
"err.url_validation": "Error while validating testdata url"
"err.url_validation": "Error while validating testdata url",
"message.common.uploads.deleted.failure": "Failed to delete some of the Uploads since they are already associated to some Test Cases."
}