Skip to content

Commit e451f76

Browse files
authored
Dashboard: added the validation of a factory name's uniqueness (eclipse-che#6764)
* CHE-5462: add unique-factory-name validation directive. Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com> * CHE-5462: use validation directive for factory name's uniqueness. Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
1 parent eee2bb6 commit e451f76

11 files changed

Lines changed: 464 additions & 37 deletions

File tree

dashboard/src/app/factories/create-factory/create-factory.controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class CreateFactoryCtrl {
2323
private $log: ng.ILogService;
2424
private cheAPI: CheAPI;
2525
private cheNotification: CheNotification;
26-
private lodash: _.LoDashStatic;
26+
private lodash: any;
2727
private $filter: ng.IFilterService;
2828
private $document: ng.IDocumentService;
2929
private isLoading: boolean;
@@ -43,7 +43,7 @@ export class CreateFactoryCtrl {
4343
* Default constructor that is using resource injection
4444
* @ngInject for Dependency injection
4545
*/
46-
constructor($location: ng.ILocationService, cheAPI: CheAPI, $log: ng.ILogService, cheNotification: CheNotification, $scope: ng.IScope, $filter: ng.IFilterService, lodash: _.LoDashStatic, $document: ng.IDocumentService) {
46+
constructor($location: ng.ILocationService, cheAPI: CheAPI, $log: ng.ILogService, cheNotification: CheNotification, $scope: ng.IScope, $filter: ng.IFilterService, lodash: any, $document: ng.IDocumentService) {
4747
this.$location = $location;
4848
this.cheAPI = cheAPI;
4949
this.$log = $log;

dashboard/src/app/factories/create-factory/create-factory.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727
ng-trim
2828
ng-minlength="3"
2929
ng-maxlength="20"
30-
ng-pattern="/^[ A-Za-z0-9_\-\.]+$/">
30+
ng-pattern="/^[ A-Za-z0-9_\-\.]+$/"
31+
unique-factory-name="">
3132
<div ng-message="pattern">Factory name may contain digits, latin letters, spaces, _ , . , - and should start only with digits, latin letters or underscores</div>
3233
<div ng-message="minlength">The name has to be more than 3 characters long.</div>
3334
<div ng-message="maxlength">The name has to be less than 20 characters long.</div>
35+
<div ng-message="uniqueFactoryName">This factory name is already used.</div>
3436
</che-input-box>
3537
</ng-form>
3638
<!--Factory source-->

dashboard/src/app/factories/factory-details/information-tab/factory-information/factory-information.controller.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class FactoryInformationController {
2424
private $location: ng.ILocationService;
2525
private $log: ng.ILogService;
2626
private $timeout: ng.ITimeoutService;
27-
private lodash: _.LoDashStatic;
27+
private lodash: any;
2828
private $filter: ng.IFilterService;
2929

3030
private timeoutPromise: ng.IPromise<any>;
@@ -40,13 +40,14 @@ export class FactoryInformationController {
4040
private workspaceName: string;
4141
private stackId: string;
4242
private workspaceConfig: any;
43+
private origName: string;
4344

4445
/**
4546
* Default constructor that is using resource injection
4647
* @ngInject for Dependency injection
4748
*/
4849
constructor($scope: ng.IScope, cheAPI: CheAPI, cheNotification: CheNotification, $location: ng.ILocationService, $log: ng.ILogService,
49-
$timeout: ng.ITimeoutService, lodash: _.LoDashStatic, $filter: ng.IFilterService, $q: ng.IQService, confirmDialogService: any) {
50+
$timeout: ng.ITimeoutService, lodash: any, $filter: ng.IFilterService, $q: ng.IQService, confirmDialogService: any) {
5051
this.cheAPI = cheAPI;
5152
this.cheNotification = cheNotification;
5253
this.$location = $location;
@@ -93,14 +94,15 @@ export class FactoryInformationController {
9394
this.environmentName = this.factory.workspace.defaultEnv;
9495

9596
this.copyOriginFactory = angular.copy(this.factory);
97+
this.origName = this.factory.name;
9698
if (this.copyOriginFactory.links) {
9799
delete this.copyOriginFactory.links;
98100
}
99101

100102
let factoryContent = this.$filter('json')(this.copyOriginFactory);
101103
if (factoryContent !== this.factoryContent) {
102104
if (!this.factoryContent) {
103-
this.editorLoadedPromise.then((instance) => {
105+
this.editorLoadedPromise.then((instance: any) => {
104106
this.$timeout(() => {
105107
instance.refresh();
106108
}, 500);
@@ -138,13 +140,30 @@ export class FactoryInformationController {
138140
return angular.equals(this.copyOriginFactory, testFactory) !== true;
139141
}
140142

143+
/**
144+
* Update factory name.
145+
*
146+
* @param {string} name new factory name.
147+
* @raram {ng.IFormController} form
148+
*/
149+
updateName(name: string, form: ng.IFormController): void {
150+
if (form.$invalid) {
151+
return;
152+
}
153+
154+
this.copyOriginFactory.name = name;
155+
156+
this.updateFactory(form);
157+
}
158+
141159
/**
142160
* Update factory data.
161+
* @param {ng.IFormController} form
143162
*/
144-
updateFactory(): void {
163+
updateFactory(form: ng.IFormController): void {
145164
this.factoryContent = this.$filter('json')(this.copyOriginFactory);
146165

147-
if (this.factoryInformationForm.$invalid || !this.isFactoryChanged()) {
166+
if (form.$invalid || !this.isFactoryChanged()) {
148167
return;
149168
}
150169

dashboard/src/app/factories/factory-details/information-tab/factory-information/factory-information.html

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,24 @@
1616
<div layout="column" class="factory-information-input">
1717
<ng-form name="factoryInformationForm">
1818
<che-input che-form="factoryInformationForm"
19-
ng-init="factoryInformationController.factoryInformationForm = factoryInformationForm"
2019
che-name="name"
2120
che-place-holder="Name of the factory"
2221
aria-label="Name of the factory"
2322
ng-model="factoryInformationController.copyOriginFactory.name"
24-
ng-change="factoryInformationController.updateFactory()"
23+
ng-change="factoryInformationController.updateName($value, factoryInformationForm)"
2524
ng-trim
2625
ng-minlength="3"
2726
ng-maxlength="20"
28-
ng-pattern="/^[ A-Za-z0-9_\-\.]+$/">
27+
ng-pattern="/^[ A-Za-z0-9_\-\.]+$/"
28+
unique-factory-name="factoryInformationController.origName">
2929
<div ng-message="required">A name is required.</div>
3030
<div ng-message="pattern">Factory name may contain digits, latin letters, spaces, _ , . , - and should start
3131
only
3232
with digits, latin letters or underscores
3333
</div>
3434
<div ng-message="minlength">The name has to be more than 3 characters long.</div>
3535
<div ng-message="maxlength">The name has to be less than 20 characters long.</div>
36+
<div ng-message="uniqueFactoryName">This factory name is already used.</div>
3637
</che-input>
3738
</ng-form>
3839
</div>
@@ -94,7 +95,7 @@
9495
che-place-holder="Name of the workspace"
9596
aria-label="Name of the workspace"
9697
ng-model="factoryInformationController.copyOriginFactory.workspace.name"
97-
ng-change="factoryInformationController.updateFactory()"
98+
ng-change="factoryInformationController.updateFactory(factoryInformationForm)"
9899
required
99100
ng-minlength="3"
100101
ng-maxlength="20"
@@ -123,7 +124,7 @@
123124
<span ng-if="factoryInformationController.getObjectKeys(environmentValue.machines).length > 1">MACHINE: {{machineKey}}</span>
124125
<che-workspace-ram-allocation-slider
125126
ng-model="machineValue.attributes.memoryLimitBytes"
126-
che-on-change="factoryInformationController.updateFactory()"></che-workspace-ram-allocation-slider>
127+
che-on-change="factoryInformationController.updateFactory(factoryInformationForm)"></che-workspace-ram-allocation-slider>
127128
</div>
128129
</div>
129130
</div>
@@ -136,7 +137,7 @@
136137
<che-label-container che-label-name="Configure Commands"
137138
che-label-description="Commands are processes that are invoked by users from a dropdown in the IDE.">
138139
<cdvy-factory-command cdvy-factory-object="factoryInformationController.copyOriginFactory"
139-
cdvy-on-change="factoryInformationController.updateFactory()"></cdvy-factory-command>
140+
cdvy-on-change="factoryInformationController.updateFactory(factoryInformationForm)"></cdvy-factory-command>
140141
</che-label-container>
141142

142143
<!-- Configure actions -->
@@ -151,7 +152,7 @@
151152
<cdvy-factory-action-box cdvy-lifecycle="onProjectsLoaded"
152153
cdvy-callback-controller="factoryInformationController"
153154
cdvy-factory-object="factoryInformationController.copyOriginFactory"
154-
cdvy-on-change="factoryInformationController.updateFactory()"></cdvy-factory-action-box>
155+
cdvy-on-change="factoryInformationController.updateFactory(factoryInformationForm)"></cdvy-factory-action-box>
155156
</che-label-container>
156157

157158
<!-- Configuration -->

dashboard/src/components/api/che-factory.factory.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,17 @@ export class CheFactory {
532532
return this.factoriesById.get(factoryId);
533533
}
534534

535+
/**
536+
* Get the factory by factoryName and userId
537+
* @param factoryName {string} the factory name
538+
* @param userId {string} the user ID
539+
* @returns factory {che.IFactory}
540+
*/
541+
getFactoryByName(factoryName: string, userId: string): che.IFactory {
542+
const key = `${userId}:${factoryName}`;
543+
return this.factoriesByName.get(key);
544+
}
545+
535546
/**
536547
* Set the factory
537548
* @param factory {che.IFactory}

dashboard/src/components/api/test/che-http-backend.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,9 @@ export class CheHttpBackend {
443443
this.httpBackend.when('DELETE', '/api/factory/' + factory.id).respond(() => {
444444
return [200, {success: true, errors: []}];
445445
});
446+
if (this.defaultUser) {
447+
this.httpBackend.when('GET', `/api/factory/find?creator.userId=${this.defaultUser.id}&name=${factory.name}`).respond([factory]);
448+
}
446449
allFactories.push(factory);
447450
}
448451

dashboard/src/components/typings/che.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ declare namespace che {
455455
ide?: any;
456456
button?: any;
457457
policies?: any;
458+
links: string[];
458459
}
459460

460461
export interface IRegistry {
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright (c) 2015-2017 Red Hat, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
*/
11+
'use strict';
12+
13+
import {CheAPI} from '../api/che-api.factory';
14+
15+
interface IFactoryNameValidatorAsyncModelValidators extends ng.IAsyncModelValidators {
16+
uniqueFactoryName: (modelValue: any, viewValue?: any) => ng.IPromise<any>;
17+
}
18+
19+
interface IFactoryNameValidatorAttributes extends ng.IAttributes {
20+
uniqueFactoryName: string;
21+
}
22+
23+
/**
24+
* Defines a directive for checking if the factory name is not already taken
25+
* @author Oleksii Kurinnyi
26+
*/
27+
export class UniqueFactoryNameValidator implements ng.IDirective {
28+
$q: ng.IQService;
29+
cheAPI: CheAPI;
30+
31+
restrict: string = 'A';
32+
require: string = 'ngModel';
33+
34+
user: che.IUser;
35+
36+
/**
37+
* Default constructor that is using resource
38+
* @ngInject for Dependency injection
39+
*/
40+
constructor (cheAPI: CheAPI, $q: ng.IQService) {
41+
this.cheAPI = cheAPI;
42+
this.$q = $q;
43+
44+
this.user = this.cheAPI.getUser().getUser();
45+
}
46+
47+
/**
48+
* Check that the name of workspace is unique
49+
*/
50+
link($scope: ng.IScope, element: ng.IAugmentedJQuery, attributes: IFactoryNameValidatorAttributes, ngModel: ng.INgModelController) {
51+
52+
const asyncValidators = ngModel.$asyncValidators as IFactoryNameValidatorAsyncModelValidators;
53+
54+
// validate only input element
55+
if ('input' === element[0].localName) {
56+
57+
asyncValidators.uniqueFactoryName = (modelValue: any, viewValue: any) => {
58+
59+
// create promise
60+
const deferred = this.$q.defer();
61+
62+
if (!this.user) {
63+
deferred.reject(false);
64+
return deferred.promise;
65+
}
66+
67+
// parent scope ?
68+
let scopingTest = $scope.$parent;
69+
if (!scopingTest) {
70+
scopingTest = $scope;
71+
}
72+
73+
const currentFactoryName = scopingTest.$eval(attributes.uniqueFactoryName);
74+
75+
if (!modelValue || modelValue === currentFactoryName) {
76+
deferred.resolve(true);
77+
} else if (this.cheAPI.getFactory().getFactoryByName(modelValue, this.user.id)) {
78+
deferred.reject(false);
79+
} else {
80+
this.cheAPI.getFactory().fetchFactoryByName(modelValue, this.user.id).finally(() => {
81+
if (this.cheAPI.getFactory().getFactoryByName(modelValue, this.user.id)) {
82+
deferred.reject(false);
83+
} else {
84+
deferred.resolve(true);
85+
}
86+
});
87+
}
88+
89+
// return promise
90+
return deferred.promise;
91+
};
92+
}
93+
}
94+
95+
}

0 commit comments

Comments
 (0)