diff --git a/services/src/main/java/org/jboss/windup/web/services/rest/MigrationProjectEndpoint.java b/services/src/main/java/org/jboss/windup/web/services/rest/MigrationProjectEndpoint.java index 7ea5ee92f..ec3e53158 100644 --- a/services/src/main/java/org/jboss/windup/web/services/rest/MigrationProjectEndpoint.java +++ b/services/src/main/java/org/jboss/windup/web/services/rest/MigrationProjectEndpoint.java @@ -59,6 +59,15 @@ public interface MigrationProjectEndpoint @Path("delete") void deleteProject(MigrationProject migration); + /** + * Look up a project ID by name. + */ + @GET + @Path("id-by-name/{title}") + Long getProjectIdByName(@PathParam("title") String title); + + + /** * Adds app count to MigrationProject solely for the purpose of this REST API. */ diff --git a/services/src/main/java/org/jboss/windup/web/services/rest/MigrationProjectEndpointImpl.java b/services/src/main/java/org/jboss/windup/web/services/rest/MigrationProjectEndpointImpl.java index f5005b9c3..ae25740f3 100644 --- a/services/src/main/java/org/jboss/windup/web/services/rest/MigrationProjectEndpointImpl.java +++ b/services/src/main/java/org/jboss/windup/web/services/rest/MigrationProjectEndpointImpl.java @@ -12,12 +12,12 @@ import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; import javax.ws.rs.NotFoundException; +import javax.ws.rs.PathParam; import org.jboss.windup.util.exception.WindupException; import org.jboss.windup.web.addons.websupport.WebPathUtil; import org.jboss.windup.web.furnaceserviceprovider.FromFurnace; -import org.jboss.windup.web.services.model.AnalysisContext; import org.jboss.windup.web.services.model.MigrationProject; import org.jboss.windup.web.services.service.AnalysisContextService; import org.jboss.windup.web.services.service.MigrationProjectService; @@ -95,4 +95,12 @@ public void deleteProject(MigrationProject migrationProject) this.migrationProjectService.deleteProject(project); } + @Override + public Long getProjectIdByName(String title){ + String jql = "SELECT id FROM MigrationProject p WHERE LOWER(p.title) = LOWER(:title)"; + List ids = this.entityManager.createQuery(jql, Long.class) + .setParameter("title", title) + .getResultList(); + return ids.isEmpty() ? null : ids.get(0); + } } diff --git a/ui/src/main/webapp/src/app/app.module.ts b/ui/src/main/webapp/src/app/app.module.ts index e4cfd19fb..03b35d95e 100644 --- a/ui/src/main/webapp/src/app/app.module.ts +++ b/ui/src/main/webapp/src/app/app.module.ts @@ -125,6 +125,7 @@ import {ExecutionApplicationListComponent} from "./reports/execution-application import {SourceResolve} from "./reports/source/source.resolve"; import {ExecutionResolve} from "./executions/execution.resolve"; import {CacheService, CacheServiceInstance} from "./shared/cache.service"; +import {ProjectNameNotExistsValidator} from "./shared/validators/project-name-not-exists.validator"; /** * Load all mapping data from the generated files. @@ -151,6 +152,7 @@ initializeModelMappingData(); declarations: [ // Directives InViewport, + ProjectNameNotExistsValidator, // pages AppComponent, diff --git a/ui/src/main/webapp/src/app/project/migration-project-form.component.html b/ui/src/main/webapp/src/app/project/migration-project-form.component.html index 67d0fda27..7a1071837 100644 --- a/ui/src/main/webapp/src/app/project/migration-project-form.component.html +++ b/ui/src/main/webapp/src/app/project/migration-project-form.component.html @@ -11,17 +11,14 @@

{{title}}

-
- + + The title must be unique + + The title must be greater than 3 characters long and fewer than 128 characters.
diff --git a/ui/src/main/webapp/src/app/project/migration-project-form.component.ts b/ui/src/main/webapp/src/app/project/migration-project-form.component.ts index 5af03d6c2..3eb981b7f 100644 --- a/ui/src/main/webapp/src/app/project/migration-project-form.component.ts +++ b/ui/src/main/webapp/src/app/project/migration-project-form.component.ts @@ -6,6 +6,7 @@ import {MigrationProjectService} from "./migration-project.service"; import {FormComponent} from "../shared/form.component"; import {Subscription} from "rxjs"; import {RouteFlattenerService} from "../core/routing/route-flattener.service"; +import {FormControl} from "@angular/forms"; @Component({ templateUrl: './migration-project-form.component.html', @@ -56,6 +57,11 @@ export class MigrationProjectFormComponent extends FormComponent implements OnIn return Math.min(4 + (this.model.description ? this.model.description.length : 0) / 80, 25) } + titleIsDuplicated(control:FormControl):boolean { + let touched = control.touched == null ? false : control.touched; + return control.hasError('nameIsTaken') && touched; + } + save() { if (this.editMode) { this._migrationProjectService.update(this.model).subscribe( diff --git a/ui/src/main/webapp/src/app/project/migration-project.service.ts b/ui/src/main/webapp/src/app/project/migration-project.service.ts index 231cafe95..4bcfbec7d 100644 --- a/ui/src/main/webapp/src/app/project/migration-project.service.ts +++ b/ui/src/main/webapp/src/app/project/migration-project.service.ts @@ -21,6 +21,7 @@ export class MigrationProjectService extends AbstractService { private CREATE_MIGRATION_PROJECT_URL = "/migrationProjects/create"; private UPDATE_MIGRATION_PROJECT_URL = "/migrationProjects/update"; private DELETE_MIGRATION_PROJECT_URL = '/migrationProjects/delete'; + private GET_ID_BY_NAME_URL = '/migrationProjects/id-by-name'; private monitoredProjects = new Map(); @@ -138,6 +139,12 @@ export class MigrationProjectService extends AbstractService { stopMonitoringProject(project: MigrationProject) { this.monitoredProjects.delete(project.id); } + + getIdByName(name: string): Observable | null { + return this._http.get(Constants.REST_BASE + this.GET_ID_BY_NAME_URL + "/" + encodeURIComponent(name)) + .map(res => res.json()) + .catch(this.handleError); + } } export interface HasAppCount{ applicationCount: number; } diff --git a/ui/src/main/webapp/src/app/shared/validators/project-name-not-exists.validator.ts b/ui/src/main/webapp/src/app/shared/validators/project-name-not-exists.validator.ts new file mode 100644 index 000000000..6762657b2 --- /dev/null +++ b/ui/src/main/webapp/src/app/shared/validators/project-name-not-exists.validator.ts @@ -0,0 +1,34 @@ +import {Directive, forwardRef} from '@angular/core'; +import {FormControl, NG_ASYNC_VALIDATORS, Validator} from "@angular/forms"; +import {MigrationProjectService} from "../../project/migration-project.service"; + +/** + * Fails validation if the name in the given control already exists as a project name. + */ +@Directive({ + selector: '[wuProjectNameNotUsed]', + providers: [ + { provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => ProjectNameNotExistsValidator), multi: true } + ] +}) +export class ProjectNameNotExistsValidator implements Validator { + + constructor(private projectService: MigrationProjectService) + { + } + + validate(control: FormControl):Promise<{[key: string] : boolean}> { + let projectService = this.projectService; + + return new Promise(resolve => { + projectService.getIdByName(control.value).subscribe(result => { + if (result == null) { + resolve(null); + } else { + resolve({['nameIsTaken']: true}); + } + }); + }); + } + +}