Skip to content

Commit dd1aa5e

Browse files
authored
Fix/303 Project name dropdown suggestions (#424)
* #303: Project name suggestions
1 parent bb7d79a commit dd1aa5e

14 files changed

+293
-6
lines changed

ui/src/app/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import { JobTemplatesComponent } from './components/admin/job-templates/job-temp
7777
import { JobTemplatesEffects } from './stores/job-templates/job-templates.effects';
7878
import { JobTemplatesHomeComponent } from './components/admin/job-templates/job-templates-home/job-templates-home.component';
7979
import { JobTemplateShowComponent } from './components/admin/job-templates/job-template-show/job-template-show.component';
80+
import { StringWithSuggestionsPartComponent } from './components/workflows/workflow-form/dynamic-parts/string-with-suggestions-part/string-with-suggestions-part.component';
8081

8182
@NgModule({
8283
declarations: [
@@ -96,6 +97,7 @@ import { JobTemplateShowComponent } from './components/admin/job-templates/job-t
9697
JobsComponent,
9798
JobComponent,
9899
StringPartComponent,
100+
StringWithSuggestionsPartComponent,
99101
BooleanPartComponent,
100102
SelectPartComponent,
101103
StringSequencePartComponent,

ui/src/app/components/workflows/workflow-form/dynamic-parts/dynamic-parts.component.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@
2424
[property]="formPart.property"
2525
[partValidation]="formPart.partValidation">
2626
</app-string-part>
27+
<app-string-with-suggestions-part
28+
*ngSwitchCase="'string-with-suggestions-field'"
29+
[isShow]="isShow"
30+
[name]="formPart.name"
31+
[valueChanges]="valueChanges"
32+
[value]="getValue(formPart.property)"
33+
[property]="formPart.property"
34+
[options]="projects"
35+
[partValidation]="formPart.partValidation">
36+
</app-string-with-suggestions-part>
2737
<app-string-sequence-part
2838
*ngSwitchCase="'set-field'"
2939
[isShow]="isShow"

ui/src/app/components/workflows/workflow-form/dynamic-parts/dynamic-parts.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export class DynamicPartsComponent {
2828
@Input() formParts: FormPart[];
2929
@Input() values: WorkflowEntryModel[];
3030
@Input() valueChanges: Subject<WorkflowEntryModel>;
31+
@Input() projects: string[];
3132

3233
constructor() {
3334
// do nothing
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<!--
2+
~ Copyright 2018 ABSA Group Limited
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~ http://www.apache.org/licenses/LICENSE-2.0
8+
~
9+
~ Unless required by applicable law or agreed to in writing, software
10+
~ distributed under the License is distributed on an "AS IS" BASIS,
11+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
~ See the License for the specific language governing permissions and
13+
~ limitations under the License.
14+
-->
15+
16+
<clr-datalist-container *ngIf="!isShow">
17+
<label>{{name}}</label>
18+
<input clrDatalistInput
19+
type="text"
20+
[minLength]="1"
21+
[id]="name+uiid"
22+
[name]="name+uiid"
23+
[readOnly]="isShow"
24+
[ngModel]="value"
25+
(ngModelChange)="modelChanged($event)"
26+
[required]="partValidationSafe.isRequired"
27+
[minlength]="partValidationSafe.minLength"
28+
[maxlength]="partValidationSafe.maxLength"
29+
/>
30+
<datalist>
31+
<option *ngFor="let option of optionsSafe" [value]="option"></option>
32+
</datalist>
33+
<clr-control-error *clrIfError="'required'">{{texts.FORM_VALIDATION_MUST_BE_FILLED(name)}}</clr-control-error>
34+
<clr-control-error *clrIfError="'minlength'">{{texts.FORM_VALIDATION_MIN_LENGTH(name, partValidationSafe.minLength)}}</clr-control-error>
35+
<clr-control-error *clrIfError="'maxlength'">{{texts.FORM_VALIDATION_MAX_LENGTH(name, partValidationSafe.maxLength)}}</clr-control-error>
36+
</clr-datalist-container>
37+
38+
<div *ngIf="isShow" class="clr-form-control clr-row">
39+
<label class="clr-control-label clr-col-12 clr-col-md-2">{{name}}</label>
40+
<span class="clr-control-container clr-col-md-10 clr-col-12 breakableSpan">
41+
{{value}}
42+
</span>
43+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*!
2+
* Copyright 2018 ABSA Group Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
:host ::ng-deep input {
17+
width: 100%;
18+
}
19+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright 2018 ABSA Group Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
17+
18+
import { StringWithSuggestionsPartComponent } from './string-with-suggestions-part.component';
19+
import { Subject } from 'rxjs';
20+
import { WorkflowEntryModel, WorkflowEntryModelFactory } from '../../../../../models/workflowEntry.model';
21+
import { By } from '@angular/platform-browser';
22+
import { DebugElement, Predicate } from '@angular/core';
23+
import { FormsModule, NgForm } from '@angular/forms';
24+
import { PartValidationFactory } from '../../../../../models/workflowFormParts.model';
25+
26+
describe('StringWithSuggestionsPartComponent', () => {
27+
let fixture: ComponentFixture<StringWithSuggestionsPartComponent>;
28+
let underTest: StringWithSuggestionsPartComponent;
29+
30+
const inputSelector: Predicate<DebugElement> = By.css('input[type="text"]');
31+
32+
beforeEach(
33+
waitForAsync(() => {
34+
TestBed.configureTestingModule({
35+
declarations: [StringWithSuggestionsPartComponent],
36+
imports: [FormsModule],
37+
providers: [NgForm],
38+
}).compileComponents();
39+
}),
40+
);
41+
42+
beforeEach(() => {
43+
fixture = TestBed.createComponent(StringWithSuggestionsPartComponent);
44+
underTest = fixture.componentInstance;
45+
});
46+
47+
it('should create', () => {
48+
expect(underTest).toBeTruthy();
49+
});
50+
51+
describe('should set empty string on init when value is undefined or null', () => {
52+
const parameters = [null, undefined];
53+
54+
parameters.forEach((parameter) => {
55+
it(
56+
'should pass with ' + parameter + ' value',
57+
waitForAsync(() => {
58+
const oldValue = parameter;
59+
const newValue = '';
60+
const options = ['first', 'scond'];
61+
const propertyName = 'property';
62+
const partValidation = PartValidationFactory.create(true, 5, 50);
63+
const testedSubject = new Subject<WorkflowEntryModel>();
64+
const subjectSpy = spyOn(testedSubject, 'next');
65+
66+
underTest.isShow = false;
67+
underTest.name = 'name';
68+
underTest.value = oldValue;
69+
underTest.options = options;
70+
underTest.property = propertyName;
71+
underTest.valueChanges = testedSubject;
72+
underTest.partValidation = partValidation;
73+
fixture.detectChanges();
74+
75+
fixture.whenStable().then(() => {
76+
const result = fixture.debugElement.query(inputSelector).nativeElement.value;
77+
expect(result).toBe(newValue);
78+
expect(subjectSpy).toHaveBeenCalledTimes(1);
79+
expect(subjectSpy).toHaveBeenCalledWith(WorkflowEntryModelFactory.create(propertyName, newValue));
80+
});
81+
}),
82+
);
83+
});
84+
});
85+
86+
it(
87+
'should change value and publish change on user input',
88+
waitForAsync(() => {
89+
const oldValue = 'oldValue';
90+
const newValue = 'newValue';
91+
const options = ['first', 'second'];
92+
const propertyName = 'property';
93+
const testedSubject = new Subject<WorkflowEntryModel>();
94+
const subjectSpy = spyOn(testedSubject, 'next');
95+
const partValidation = PartValidationFactory.create(true, 5, 50);
96+
97+
underTest.isShow = false;
98+
underTest.name = 'name';
99+
underTest.value = oldValue;
100+
underTest.options = options;
101+
underTest.property = propertyName;
102+
underTest.valueChanges = testedSubject;
103+
underTest.partValidation = partValidation;
104+
105+
fixture.detectChanges();
106+
fixture.whenStable().then(() => {
107+
const inputElement = fixture.debugElement.query(inputSelector).nativeElement;
108+
109+
inputElement.value = newValue;
110+
inputElement.dispatchEvent(new Event('input'));
111+
112+
fixture.detectChanges();
113+
fixture.whenStable().then(() => {
114+
const testedValue = fixture.debugElement.query(inputSelector).nativeElement.value;
115+
expect(testedValue).toBe(newValue);
116+
expect(subjectSpy).toHaveBeenCalled();
117+
expect(subjectSpy).toHaveBeenCalledWith(WorkflowEntryModelFactory.create(propertyName, newValue));
118+
});
119+
});
120+
}),
121+
);
122+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2018 ABSA Group Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
import { Component, Input, OnInit } from '@angular/core';
17+
import { Subject } from 'rxjs';
18+
import { WorkflowEntryModel, WorkflowEntryModelFactory } from '../../../../../models/workflowEntry.model';
19+
import { ControlContainer, NgForm } from '@angular/forms';
20+
import { PartValidation, PartValidationFactory } from '../../../../../models/workflowFormParts.model';
21+
import { UuidUtil } from '../../../../../utils/uuid/uuid.util';
22+
import { texts } from 'src/app/constants/texts.constants';
23+
24+
@Component({
25+
selector: 'app-string-with-suggestions-part',
26+
templateUrl: './string-with-suggestions-part.component.html',
27+
styleUrls: ['./string-with-suggestions-part.component.scss'],
28+
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
29+
})
30+
export class StringWithSuggestionsPartComponent implements OnInit {
31+
uiid = UuidUtil.createUUID();
32+
texts = texts;
33+
@Input() isShow: boolean;
34+
@Input() name: string;
35+
@Input() value: string;
36+
@Input() options: string[] = [];
37+
@Input() property: string;
38+
@Input() valueChanges: Subject<WorkflowEntryModel>;
39+
@Input() partValidation: PartValidation;
40+
partValidationSafe: PartValidation;
41+
42+
optionsSafe: string[] = [];
43+
44+
constructor() {
45+
// do nothing
46+
}
47+
48+
ngOnInit(): void {
49+
if (!this.value) {
50+
this.modelChanged('');
51+
}
52+
this.optionsSafe = this.options;
53+
this.partValidationSafe = PartValidationFactory.create(
54+
!!this.partValidation.isRequired ? this.partValidation.isRequired : true,
55+
!!this.partValidation.maxLength ? this.partValidation.maxLength : Number.MAX_SAFE_INTEGER,
56+
!!this.partValidation.minLength ? this.partValidation.minLength : 1,
57+
);
58+
}
59+
60+
modelChanged(value: string) {
61+
this.value = value.trim();
62+
this.valueChanges.next(WorkflowEntryModelFactory.create(this.property, this.value));
63+
}
64+
}

ui/src/app/components/workflows/workflow-form/workflow-details/workflow-details.component.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@
1717
[isShow]="mode == workflowModes.SHOW || mode == workflowModes.COMPARISON"
1818
[formParts]="parts"
1919
[valueChanges]="detailsChanges"
20-
[values]="data">
20+
[values]="data"
21+
[projects]="projects">
2122
</app-dynamic-parts>

ui/src/app/components/workflows/workflow-form/workflow-details/workflow-details.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export class WorkflowDetailsComponent implements OnInit, OnDestroy {
3131
@Input() parts: FormPart[];
3232
@Input() data: WorkflowEntryModel[];
3333
@Input() changes: Subject<Action>;
34+
@Input() projects: string[];
3435

3536
workflowModes = workflowModes;
3637

ui/src/app/components/workflows/workflow-form/workflow-form.component.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@
9292
[mode]="mode"
9393
[parts]="workflowFormParts.detailsParts"
9494
[data]="workflowData.details"
95-
[changes]="changes">
95+
[changes]="changes"
96+
[projects]="projects">
9697
</app-workflow-details>
9798
</div>
9899
</div>

0 commit comments

Comments
 (0)