diff --git a/demo-shell/src/app/components/card-view/card-view.component.ts b/demo-shell/src/app/components/card-view/card-view.component.ts
index 0c3f50e61b..8226601186 100644
--- a/demo-shell/src/app/components/card-view/card-view.component.ts
+++ b/demo-shell/src/app/components/card-view/card-view.component.ts
@@ -28,7 +28,8 @@ import {
CardViewUpdateService,
CardViewMapItemModel,
UpdateNotification,
- DecimalNumberPipe
+ DecimalNumberPipe,
+ CardViewArrayItemModel
} from '@alfresco/adf-core';
import { of, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@@ -148,6 +149,14 @@ export class CardViewComponent implements OnInit, OnDestroy {
clickCallBack: () => {
this.respondToCardClick();
}
+ }),
+ new CardViewArrayItemModel({
+ label: 'CardView Array of items',
+ value: of(['Zlatan', 'Lionel Messi', 'Mohamed', 'Ronaldo']),
+ key: 'array',
+ icon: 'directions_bike',
+ default: 'Empty',
+ noOfItemsToDisplay: 2
})
];
}
diff --git a/docs/core/components/card-view.component.md b/docs/core/components/card-view.component.md
index 1bd8085072..130c23e14f 100644
--- a/docs/core/components/card-view.component.md
+++ b/docs/core/components/card-view.component.md
@@ -87,6 +87,14 @@ Defining properties from Typescript:
options$: of([{ key: 'one', label: 'One' }, { key: 'two', label: 'Two' }]),
key: 'select'
}),
+ new CardViewArrayItemModel({
+ label: 'Array of items',
+ value: '',
+ items$: of(['One', 'Two', 'Three', 'Four']),
+ key: 'array',
+ default: 'Empty',
+ noOfItemsToDisplay: 2
+ })
...
]
```
@@ -116,6 +124,7 @@ You define the property list, the [`CardViewComponent`](../../core/components/ca
- [**CardViewFloatItemModel**](#card-float-item) - _for float items_
- [**CardViewKeyValuePairsItemModel**](#card-key-value-pairs-item) - _for key-value-pairs items_
- [**CardViewSelectItemModel**](#card-select-item) - _for select items_
+- [**CardViewArrayItemModel**](#card-array-item) - _for array items_
Each of these types implements the [Card View Item interface](../interfaces/card-view-item.interface.md):
@@ -336,6 +345,21 @@ const selectItemProperty = new CardViewSelectItemModel(options);
| value | string | | The original data value for the item |
| options$\* | [`Observable`](http://reactivex.io/documentation/observable.html)<[`CardViewSelectItemOption`](../../../lib/core/card-view/interfaces/card-view-selectitem-properties.interface.ts)\[]> | | The original data value for the item |
+#### Card Array Item
+
+[`CardViewArrayItemModel`](../../../lib/core/card-view/models/card-view-arrayitem.model.ts) is a property type for array properties.
+
+```ts
+const arrayItemProperty = new CardViewArrayItemModel(items);
+```
+
+| Name | Type | Default | Description |
+| ---- | ---- | ------- | ----------- |
+| label\* | string | | Item label |
+| key\* | string | | Identifying key (important when editing the item) |
+| editable | boolean | false | Toggles whether the item is editable |
+| value | [`Observable`](http://reactivex.io/documentation/observable.html)<`string`\[]> | | The original data value for the item |
+
## See also
- [Card View Update service](../services/card-view-update.service.md)
diff --git a/docs/process-services-cloud/components/task-header-cloud.component.md b/docs/process-services-cloud/components/task-header-cloud.component.md
index 22566c42b3..546ec53b37 100644
--- a/docs/process-services-cloud/components/task-header-cloud.component.md
+++ b/docs/process-services-cloud/components/task-header-cloud.component.md
@@ -43,7 +43,7 @@ The component populates an internal array of
By default all properties are displayed:
-**_assignee_**, **_status_**, **_priority_**, **_dueDate_**, **_category_**, **_parentName_**, **_created_**, **_id_**, **_description_**, **_formName_**.
+**_assignee_**, **_status_**, **_priority_**, **_dueDate_**, **_category_**, **_parentName_**, **_created_**, **_id_**, **_description_**, **_formName_**, **_candidateUsers_**, **_candidateGroups_**.
However, you can also choose which properties to show using a configuration in `app.config.json`:
diff --git a/lib/core/card-view/card-view.module.scss b/lib/core/card-view/card-view.module.scss
index a3a1c75074..1aa461ff72 100644
--- a/lib/core/card-view/card-view.module.scss
+++ b/lib/core/card-view/card-view.module.scss
@@ -1,3 +1,4 @@
+@import './components/card-view-arrayitem/card-view-arrayitem.component';
@import './components/card-view-dateitem/card-view-dateitem.component';
@import './components/card-view-textitem/card-view-textitem.component';
@import './components/card-view/card-view.component';
@@ -8,4 +9,5 @@
@include adf-card-view-textitem-theme($theme);
@include adf-card-view-theme($theme);
@include mat-datetimepicker-theme($theme);
+ @include adf-card-view-array-item-theme($theme);
}
diff --git a/lib/core/card-view/card-view.module.ts b/lib/core/card-view/card-view.module.ts
index b8d4b2f06f..24f616469f 100644
--- a/lib/core/card-view/card-view.module.ts
+++ b/lib/core/card-view/card-view.module.ts
@@ -26,7 +26,10 @@ import {
MatInputModule,
MatCheckboxModule,
MatNativeDateModule,
- MatSelectModule
+ MatSelectModule,
+ MatChipsModule,
+ MatMenuModule,
+ MatCardModule
} from '@angular/material';
import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core';
import { FlexLayoutModule } from '@angular/flex-layout';
@@ -41,6 +44,7 @@ import { CardViewMapItemComponent } from './components/card-view-mapitem/card-vi
import { CardViewTextItemComponent } from './components/card-view-textitem/card-view-textitem.component';
import { CardViewKeyValuePairsItemComponent } from './components/card-view-keyvaluepairsitem/card-view-keyvaluepairsitem.component';
import { CardViewSelectItemComponent } from './components/card-view-selectitem/card-view-selectitem.component';
+import { CardViewArrayItemComponent } from './components/card-view-arrayitem/card-view-arrayitem.component';
@NgModule({
imports: [
@@ -56,6 +60,9 @@ import { CardViewSelectItemComponent } from './components/card-view-selectitem/c
MatIconModule,
MatSelectModule,
MatButtonModule,
+ MatChipsModule,
+ MatMenuModule,
+ MatCardModule,
MatDatetimepickerModule,
MatNativeDatetimeModule
],
@@ -68,7 +75,8 @@ import { CardViewSelectItemComponent } from './components/card-view-selectitem/c
CardViewKeyValuePairsItemComponent,
CardViewSelectItemComponent,
CardViewItemDispatcherComponent,
- CardViewContentProxyDirective
+ CardViewContentProxyDirective,
+ CardViewArrayItemComponent
],
entryComponents: [
CardViewBoolItemComponent,
@@ -76,7 +84,8 @@ import { CardViewSelectItemComponent } from './components/card-view-selectitem/c
CardViewMapItemComponent,
CardViewTextItemComponent,
CardViewSelectItemComponent,
- CardViewKeyValuePairsItemComponent
+ CardViewKeyValuePairsItemComponent,
+ CardViewArrayItemComponent
],
exports: [
CardViewComponent,
@@ -85,7 +94,8 @@ import { CardViewSelectItemComponent } from './components/card-view-selectitem/c
CardViewMapItemComponent,
CardViewTextItemComponent,
CardViewSelectItemComponent,
- CardViewKeyValuePairsItemComponent
+ CardViewKeyValuePairsItemComponent,
+ CardViewArrayItemComponent
]
})
export class CardViewModule {}
diff --git a/lib/core/card-view/components/card-view-arrayitem/card-view-arrayitem.component.html b/lib/core/card-view/components/card-view-arrayitem/card-view-arrayitem.component.html
new file mode 100644
index 0000000000..0324e38fbc
--- /dev/null
+++ b/lib/core/card-view/components/card-view-arrayitem/card-view-arrayitem.component.html
@@ -0,0 +1,49 @@
+
+
{{ property.label | translate }}
+
+
+ 0; else elseEmptyValueBlock" data-automation-id="card-arrayitem-chip-list-container">
+ 0; else withOutDisplayCount" >
+
+ {{property.icon}}
+ {{item}}
+
+ displayCount()"
+ data-automation-id="card-arrayitem-more-chip"
+ [matMenuTriggerFor]="menu">
+ {{items.length - displayCount()}} {{'CORE.CARDVIEW.MORE' | translate}}
+
+
+
+
+ {{property.icon}}
+ {{item}}
+
+
+
+
+
+
+
+
+ {{property.icon}}
+ {{item}}
+
+
+
+
+
+
+
+ {{ property.default | translate }}
+
+
diff --git a/lib/core/card-view/components/card-view-arrayitem/card-view-arrayitem.component.scss b/lib/core/card-view/components/card-view-arrayitem/card-view-arrayitem.component.scss
new file mode 100644
index 0000000000..108915b411
--- /dev/null
+++ b/lib/core/card-view/components/card-view-arrayitem/card-view-arrayitem.component.scss
@@ -0,0 +1,33 @@
+@mixin adf-card-view-array-item-theme($theme) {
+
+ .adf {
+ &-array-item-icon {
+ font-size: 16px;
+ padding-top: 8px;
+ }
+
+ &-array-item-more-chip-container {
+ &.mat-card {
+ box-shadow: none;
+ }
+
+ &.mat-card {
+ max-height: 300px;
+ overflow-y: auto;
+ }
+
+ .mat-chip {
+ cursor: pointer;
+ }
+ }
+
+ &-property-value {
+ .mat-chip-list {
+ cursor: pointer;
+ }
+ .mat-chip {
+ cursor: pointer;
+ }
+ }
+ }
+}
diff --git a/lib/core/card-view/components/card-view-arrayitem/card-view-arrayitem.component.spec.ts b/lib/core/card-view/components/card-view-arrayitem/card-view-arrayitem.component.spec.ts
new file mode 100644
index 0000000000..00d81de837
--- /dev/null
+++ b/lib/core/card-view/components/card-view-arrayitem/card-view-arrayitem.component.spec.ts
@@ -0,0 +1,107 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { of } from 'rxjs';
+import { setupTestBed } from '../../../testing/setupTestBed';
+import { CoreTestingModule } from '../../../testing/core.testing.module';
+import { CardViewArrayItemComponent } from './card-view-arrayitem.component';
+import { CardViewArrayItemModel } from '../../models/card-view-arrayitem.model';
+import { By } from '@angular/platform-browser';
+
+describe('CardViewArrayItemComponent', () => {
+ let component: CardViewArrayItemComponent;
+ let fixture: ComponentFixture;
+
+ const mockData = ['Zlatan', 'Lionel Messi', 'Mohamed', 'Ronaldo'];
+ const mockDefaultProps = {
+ label: 'Array of items',
+ value: of(mockData),
+ key: 'array',
+ icon: 'person'
+ };
+ setupTestBed({
+ imports: [CoreTestingModule]
+ });
+
+ afterEach(() => {
+ fixture.destroy();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(CardViewArrayItemComponent);
+ component = fixture.componentInstance;
+ component.property = new CardViewArrayItemModel(mockDefaultProps);
+ });
+
+ it('should create CardViewArrayItemComponent', () => {
+ expect(component instanceof CardViewArrayItemComponent).toBeTruthy();
+ });
+
+ describe('Rendering', () => {
+ it('should render the label', () => {
+ fixture.detectChanges();
+
+ const labelValue = fixture.debugElement.query(By.css('.adf-property-label'));
+ expect(labelValue).not.toBeNull();
+ expect(labelValue.nativeElement.innerText).toBe('Array of items');
+ });
+
+ it('should render chip list', () => {
+ component.property = new CardViewArrayItemModel({
+ ...mockDefaultProps,
+ editable: true
+ });
+ fixture.detectChanges();
+
+ const chiplistContainer = fixture.debugElement.query(By.css('[data-automation-id="card-arrayitem-chip-list-container"]'));
+ const chip1 = fixture.nativeElement.querySelector('[data-automation-id="card-arrayitem-chip-Zlatan"] span');
+ const chip2 = fixture.nativeElement.querySelector('[data-automation-id="card-arrayitem-chip-Lionel Messi"] span');
+
+ expect(chiplistContainer).not.toBeNull();
+ expect(chip1.innerText).toEqual('Zlatan');
+ expect(chip2.innerText).toEqual('Lionel Messi');
+ });
+
+ it('should render all values if noOfItemsToDisplay is not defined', () => {
+ fixture.detectChanges();
+
+ const chiplistContainer = fixture.debugElement.query(By.css('[data-automation-id="card-arrayitem-chip-list-container"]'));
+ const moreElement = fixture.debugElement.query(By.css('[data-automation-id="card-arrayitem-more-chip"]'));
+ const chip = fixture.nativeElement.querySelectorAll('mat-chip');
+
+ expect(chiplistContainer).not.toBeNull();
+ expect(moreElement).toBeNull();
+ expect(chip.length).toBe(4);
+ });
+
+ it('should render only two values along with more item chip if noOfItemsToDisplay is set to 2', () => {
+ component.property = new CardViewArrayItemModel({
+ ...mockDefaultProps,
+ noOfItemsToDisplay: 2
+ });
+ fixture.detectChanges();
+
+ const chiplistContainer = fixture.debugElement.query(By.css('[data-automation-id="card-arrayitem-chip-list-container"]'));
+ const chip = fixture.debugElement.queryAll(By.css('mat-chip'));
+
+ expect(chiplistContainer).not.toBeNull();
+ expect(chip.length).toBe(3);
+ expect(chip[2].nativeElement.innerText).toBe('2 CORE.CARDVIEW.MORE');
+ });
+ });
+});
diff --git a/lib/core/card-view/components/card-view-arrayitem/card-view-arrayitem.component.ts b/lib/core/card-view/components/card-view-arrayitem/card-view-arrayitem.component.ts
new file mode 100644
index 0000000000..81b07c3cc4
--- /dev/null
+++ b/lib/core/card-view/components/card-view-arrayitem/card-view-arrayitem.component.ts
@@ -0,0 +1,46 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, Input } from '@angular/core';
+import { CardViewArrayItemModel } from '../../models/card-view-arrayitem.model';
+import { CardViewUpdateService } from '../../services/card-view-update.service';
+
+@Component({
+ selector: 'adf-card-view-arrayitem',
+ templateUrl: './card-view-arrayitem.component.html',
+ styleUrls: ['./card-view-arrayitem.component.scss']
+})
+export class CardViewArrayItemComponent {
+
+ /** The CardViewArrayItemModel of data used to populate the cardView array items. */
+ @Input()
+ property: CardViewArrayItemModel;
+
+ constructor(private cardViewUpdateService: CardViewUpdateService) {}
+
+ clicked(): void {
+ this.cardViewUpdateService.clicked(this.property);
+ }
+
+ hasIcon(): boolean {
+ return !!this.property.icon;
+ }
+
+ displayCount(): number {
+ return this.property.noOfItemsToDisplay ? this.property.noOfItemsToDisplay : 0;
+ }
+}
diff --git a/lib/core/card-view/components/card-view.components.ts b/lib/core/card-view/components/card-view.components.ts
index 72e2c8e23b..b173249d99 100644
--- a/lib/core/card-view/components/card-view.components.ts
+++ b/lib/core/card-view/components/card-view.components.ts
@@ -23,3 +23,4 @@ export * from './card-view-mapitem/card-view-mapitem.component';
export * from './card-view-textitem/card-view-textitem.component';
export * from './card-view-selectitem/card-view-selectitem.component';
export * from './card-view-keyvaluepairsitem/card-view-keyvaluepairsitem.component';
+export * from './card-view-arrayitem/card-view-arrayitem.component';
diff --git a/lib/core/card-view/interfaces/card-view-arrayitem-properties.interface.ts b/lib/core/card-view/interfaces/card-view-arrayitem-properties.interface.ts
new file mode 100644
index 0000000000..47f8eb9551
--- /dev/null
+++ b/lib/core/card-view/interfaces/card-view-arrayitem-properties.interface.ts
@@ -0,0 +1,22 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { CardViewItemProperties } from './card-view-item-properties.interface';
+
+export interface CardViewArrayItemProperties extends CardViewItemProperties {
+ noOfItemsToDisplay?: number;
+}
diff --git a/lib/core/card-view/models/card-view-arrayitem.model.ts b/lib/core/card-view/models/card-view-arrayitem.model.ts
new file mode 100644
index 0000000000..9a491ac227
--- /dev/null
+++ b/lib/core/card-view/models/card-view-arrayitem.model.ts
@@ -0,0 +1,38 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { CardViewItem } from '../interfaces/card-view-item.interface';
+import { DynamicComponentModel } from '../../services/dynamic-component-mapper.service';
+import { CardViewBaseItemModel } from './card-view-baseitem.model';
+import { Observable } from 'rxjs';
+import { CardViewArrayItemProperties } from '../interfaces/card-view-arrayitem-properties.interface';
+
+export class CardViewArrayItemModel extends CardViewBaseItemModel implements CardViewItem, DynamicComponentModel {
+
+ type: string = 'array';
+ value: Observable;
+ noOfItemsToDisplay: number;
+
+ constructor(cardViewArrayItemProperties: CardViewArrayItemProperties) {
+ super(cardViewArrayItemProperties);
+ this.noOfItemsToDisplay = cardViewArrayItemProperties.noOfItemsToDisplay;
+ }
+
+ get displayValue(): Observable {
+ return this.value;
+ }
+}
diff --git a/lib/core/card-view/models/card-view.models.ts b/lib/core/card-view/models/card-view.models.ts
index 4a819b6c3a..b3997e20d6 100644
--- a/lib/core/card-view/models/card-view.models.ts
+++ b/lib/core/card-view/models/card-view.models.ts
@@ -25,3 +25,4 @@ export * from './card-view-mapitem.model';
export * from './card-view-textitem.model';
export * from './card-view-keyvaluepairs.model';
export * from './card-view-selectitem.model';
+export * from './card-view-arrayitem.model';
diff --git a/lib/core/card-view/public-api.ts b/lib/core/card-view/public-api.ts
index e6050f1d4b..f15275f780 100644
--- a/lib/core/card-view/public-api.ts
+++ b/lib/core/card-view/public-api.ts
@@ -22,7 +22,8 @@ export {
CardViewMapItemComponent,
CardViewTextItemComponent,
CardViewSelectItemComponent,
- CardViewKeyValuePairsItemComponent
+ CardViewKeyValuePairsItemComponent,
+ CardViewArrayItemComponent
} from './components/card-view.components';
export * from './interfaces/card-view.interfaces';
diff --git a/lib/core/card-view/services/card-item-types.service.ts b/lib/core/card-view/services/card-item-types.service.ts
index f7951c84d3..78afdab6e6 100644
--- a/lib/core/card-view/services/card-item-types.service.ts
+++ b/lib/core/card-view/services/card-item-types.service.ts
@@ -23,6 +23,7 @@ import { CardViewSelectItemComponent } from '../components/card-view-selectitem/
import { CardViewBoolItemComponent } from '../components/card-view-boolitem/card-view-boolitem.component';
import { CardViewKeyValuePairsItemComponent } from '../components/card-view-keyvaluepairsitem/card-view-keyvaluepairsitem.component';
import { DynamicComponentMapper, DynamicComponentResolveFunction, DynamicComponentResolver } from '../../services/dynamic-component-mapper.service';
+import { CardViewArrayItemComponent } from '../components/card-view-arrayitem/card-view-arrayitem.component';
@Injectable({
providedIn: 'root'
@@ -40,6 +41,7 @@ export class CardItemTypeService extends DynamicComponentMapper {
'datetime': DynamicComponentResolver.fromType(CardViewDateItemComponent),
'bool': DynamicComponentResolver.fromType(CardViewBoolItemComponent),
'map': DynamicComponentResolver.fromType(CardViewMapItemComponent),
- 'keyvaluepairs': DynamicComponentResolver.fromType(CardViewKeyValuePairsItemComponent)
+ 'keyvaluepairs': DynamicComponentResolver.fromType(CardViewKeyValuePairsItemComponent),
+ 'array': DynamicComponentResolver.fromType(CardViewArrayItemComponent)
};
}
diff --git a/lib/core/i18n/en.json b/lib/core/i18n/en.json
index 97d900c683..b52e26efe3 100644
--- a/lib/core/i18n/en.json
+++ b/lib/core/i18n/en.json
@@ -175,7 +175,8 @@
"VALIDATORS": {
"FLOAT_VALIDATION_ERROR": "Use a number format",
"INT_VALIDATION_ERROR": "Use an integer format"
- }
+ },
+ "MORE": "More"
},
"METADATA": {
"BASIC": {
diff --git a/lib/core/pipes/full-name.pipe.spec.ts b/lib/core/pipes/full-name.pipe.spec.ts
index e02ae71a8a..9206fd33ed 100644
--- a/lib/core/pipes/full-name.pipe.spec.ts
+++ b/lib/core/pipes/full-name.pipe.spec.ts
@@ -45,4 +45,14 @@ describe('FullNamePipe', () => {
const user = {firstName : 'Abc', lastName : 'Xyz'};
expect(pipe.transform(user)).toBe('Abc Xyz');
});
+
+ it('should return username when firstName and lastName are not available', () => {
+ const user = {firstName : '', lastName : '', username: 'username'};
+ expect(pipe.transform(user)).toBe('username');
+ });
+
+ it('should return user eamil when firstName, lastName and username are not available', () => {
+ const user = {firstName : '', lastName : '', username: '', email: 'abcXyz@gmail.com'};
+ expect(pipe.transform(user)).toBe('abcXyz@gmail.com');
+ });
});
diff --git a/lib/core/pipes/full-name.pipe.ts b/lib/core/pipes/full-name.pipe.ts
index 5cb4c1d7b0..396319a2d2 100644
--- a/lib/core/pipes/full-name.pipe.ts
+++ b/lib/core/pipes/full-name.pipe.ts
@@ -29,6 +29,9 @@ export class FullNamePipe implements PipeTransform {
fullName += fullName.length > 0 ? ' ' : '';
fullName += user.lastName;
}
+ if (!fullName) {
+ fullName += user.username ? user.username : user.email ? user.email : '';
+ }
}
return fullName;
}
diff --git a/lib/process-services-cloud/src/lib/i18n/en.json b/lib/process-services-cloud/src/lib/i18n/en.json
index 201778db72..a72a1f24d0 100644
--- a/lib/process-services-cloud/src/lib/i18n/en.json
+++ b/lib/process-services-cloud/src/lib/i18n/en.json
@@ -208,7 +208,11 @@
"DESCRIPTION": "Description",
"DESCRIPTION_DEFAULT": "No description",
"FORM_NAME": "Form Name",
- "FORM_NAME_DEFAULT": "No form"
+ "FORM_NAME_DEFAULT": "No form",
+ "CANDIDATE_USERS": "Candidate Users",
+ "CANDIDATE_USERS_DEFAULT": "No Candidate Users",
+ "CANDIDATE_GROUPS": "Candidate Groups",
+ "CANDIDATE_GROUPS_DEFAULT": "No Candidate Groups"
},
"FORM_VALIDATION": {
"INVALID_FIELD": "Enter a different value"
diff --git a/lib/process-services-cloud/src/lib/task/services/task-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/task/services/task-cloud.service.spec.ts
index 71b6582633..d56b74c417 100644
--- a/lib/process-services-cloud/src/lib/task/services/task-cloud.service.spec.ts
+++ b/lib/process-services-cloud/src/lib/task/services/task-cloud.service.spec.ts
@@ -60,6 +60,26 @@ describe('Task Cloud Service', () => {
};
}
+ function returnFakeCandidateUsersResults() {
+ return {
+ oauth2Auth: {
+ callCustomApi : () => {
+ return Promise.resolve(['mockuser1', 'mockuser2', 'mockuser3']);
+ }
+ }
+ };
+ }
+
+ function returnFakeCandidateGroupResults() {
+ return {
+ oauth2Auth: {
+ callCustomApi : () => {
+ return Promise.resolve(['mockgroup1', 'mockgroup2', 'mockgroup3']);
+ }
+ }
+ };
+ }
+
setupTestBed({
imports: [
CoreModule.forRoot()
@@ -312,4 +332,76 @@ describe('Task Cloud Service', () => {
done();
});
});
+
+ it('should return the candidate users by appName and taskId', (done) => {
+ const appName = 'taskp-app';
+ const taskId = '68d54a8f';
+ spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeCandidateUsersResults);
+ service.getCandidateUsers(appName, taskId).subscribe((res: string[]) => {
+ expect(res).toBeDefined();
+ expect(res).not.toBeNull();
+ expect(res.length).toBe(3);
+ expect(res[0]).toBe('mockuser1');
+ expect(res[1]).toBe('mockuser2');
+ done();
+ });
+ });
+
+ it('should log message and return empty array if appName is not defined when fetching candidate users', (done) => {
+ const appName = null;
+ const taskId = '68d54a8f';
+ spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeCandidateUsersResults);
+ service.getCandidateUsers(appName, taskId).subscribe(
+ (res: any[]) => {
+ expect(res.length).toBe(0);
+ done();
+ });
+ });
+
+ it('should log message and return empty array if taskId is not defined when fetching candidate users', (done) => {
+ const appName = 'task-app';
+ const taskId = null;
+ spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeCandidateUsersResults);
+ service.getCandidateUsers(appName, taskId).subscribe(
+ (res: any[]) => {
+ expect(res.length).toBe(0);
+ done();
+ });
+ });
+
+ it('should return the candidate groups by appName and taskId', (done) => {
+ const appName = 'taskp-app';
+ const taskId = '68d54a8f';
+ spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeCandidateGroupResults);
+ service.getCandidateGroups(appName, taskId).subscribe((res: string[]) => {
+ expect(res).toBeDefined();
+ expect(res).not.toBeNull();
+ expect(res.length).toBe(3);
+ expect(res[0]).toBe('mockgroup1');
+ expect(res[1]).toBe('mockgroup2');
+ done();
+ });
+ });
+
+ it('should log message and return empty array if appName is not defined when fetching candidate groups', (done) => {
+ const appName = null;
+ const taskId = '68d54a8f';
+ spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeCandidateGroupResults);
+ service.getCandidateGroups(appName, taskId).subscribe(
+ (res: any[]) => {
+ expect(res.length).toBe(0);
+ done();
+ });
+ });
+
+ it('should log message and return empty array if taskId is not defined when fetching candidate groups', (done) => {
+ const appName = 'task-app';
+ const taskId = null;
+ spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeCandidateGroupResults);
+ service.getCandidateGroups(appName, taskId).subscribe(
+ (res: any[]) => {
+ expect(res.length).toBe(0);
+ done();
+ });
+ });
});
diff --git a/lib/process-services-cloud/src/lib/task/services/task-cloud.service.ts b/lib/process-services-cloud/src/lib/task/services/task-cloud.service.ts
index 5f86487874..da505877c3 100644
--- a/lib/process-services-cloud/src/lib/task/services/task-cloud.service.ts
+++ b/lib/process-services-cloud/src/lib/task/services/task-cloud.service.ts
@@ -17,7 +17,7 @@
import { Injectable } from '@angular/core';
import { AlfrescoApiService, LogService, AppConfigService, IdentityUserService } from '@alfresco/adf-core';
-import { from, throwError, Observable } from 'rxjs';
+import { from, throwError, Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { TaskDetailsCloudModel, StartTaskCloudResponseModel } from '../start-task/models/task-details-cloud.model';
import { BaseCloudService } from '../../services/base-cloud.service';
@@ -246,6 +246,60 @@ export class TaskCloudService extends BaseCloudService {
}
}
+ /**
+ * Gets candidate users of the task.
+ * @param appName Name of the app
+ * @param taskId ID of the task
+ * @returns Candidate users
+ */
+ getCandidateUsers(appName: string, taskId: string): Observable {
+ if ((appName || appName === '') && taskId) {
+ const queryUrl = `${this.getBasePath(appName)}/query/v1/tasks/${taskId}/candidate-users`;
+ return from(this.apiService.getInstance()
+ .oauth2Auth.callCustomApi(queryUrl, 'GET',
+ null, null, null,
+ null, null,
+ this.contentTypes, this.accepts,
+ this.returnType, null, null)
+ ).pipe(
+ map((response: string[]) => {
+ return response;
+ }),
+ catchError((err) => this.handleError(err))
+ );
+ } else {
+ this.logService.error('AppName and TaskId are mandatory to get candidate user');
+ return of([]);
+ }
+ }
+
+ /**
+ * Gets candidate groups of the task.
+ * @param appName Name of the app
+ * @param taskId ID of the task
+ * @returns Candidate groups
+ */
+ getCandidateGroups(appName: string, taskId: string): Observable {
+ if ((appName || appName === '') && taskId) {
+ const queryUrl = `${this.getBasePath(appName)}/query/v1/tasks/${taskId}/candidate-groups`;
+ return from(this.apiService.getInstance()
+ .oauth2Auth.callCustomApi(queryUrl, 'GET',
+ null, null, null,
+ null, null,
+ this.contentTypes, this.accepts,
+ this.returnType, null, null)
+ ).pipe(
+ map((response: string[]) => {
+ return response;
+ }),
+ catchError((err) => this.handleError(err))
+ );
+ } else {
+ this.logService.error('AppName and TaskId are mandatory to get candidate groups');
+ return of([]);
+ }
+ }
+
private isAssignedToMe(assignee: string): boolean {
const currentUser = this.identityUserService.getCurrentUserInfo().username;
return assignee === currentUser;
diff --git a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.spec.ts
index 020fbac38f..7f96217cac 100644
--- a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.spec.ts
+++ b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.spec.ts
@@ -32,8 +32,12 @@ describe('TaskHeaderCloudComponent', () => {
let service: TaskCloudService;
let appConfigService: AppConfigService;
let identityUserService: IdentityUserService;
+ let getCandidateGroupsSpy: jasmine.Spy;
+ let getCandidateUsersSpy: jasmine.Spy;
const identityUserMock = { username: 'testuser', firstName: 'fake-identity-first-name', lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com' };
+ const mockCandidateUsers = ['mockuser1', 'mockuser2', 'mockuser3'];
+ const mockCandidateGroups = ['mockgroup1', 'mockgroup2', 'mockgroup3'];
setupTestBed({
imports: [
@@ -53,6 +57,8 @@ describe('TaskHeaderCloudComponent', () => {
identityUserService = TestBed.get(IdentityUserService);
appConfigService = TestBed.get(AppConfigService);
spyOn(service, 'getTaskById').and.returnValue(of(assignedTaskDetailsCloudMock));
+ getCandidateUsersSpy = spyOn(service, 'getCandidateUsers').and.returnValue(of(mockCandidateUsers));
+ getCandidateGroupsSpy = spyOn(service, 'getCandidateGroups').and.returnValue(of(mockCandidateGroups));
spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue(identityUserMock);
});
@@ -151,6 +157,60 @@ describe('TaskHeaderCloudComponent', () => {
});
}));
+ it('should display candidate user', async(() => {
+ component.ngOnInit();
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ const candidateUser1 = fixture.nativeElement.querySelector('[data-automation-id="card-arrayitem-chip-mockuser1"] span');
+ const candidateUser2 = fixture.nativeElement.querySelector('[data-automation-id="card-arrayitem-chip-mockuser2"] span');
+ expect(getCandidateUsersSpy).toHaveBeenCalled();
+ expect(candidateUser1.innerText).toBe('mockuser1');
+ expect(candidateUser2.innerText).toBe('mockuser2');
+ });
+ }));
+
+ it('should display placeholder if no candidate users', async(() => {
+ component.ngOnInit();
+ getCandidateUsersSpy.and.returnValue(of([]));
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ const labelValue = fixture.debugElement.query(By.css('[data-automation-id="card-array-label-candidateUsers"]'));
+ const defaultElement = fixture.debugElement.query(By.css('[data-automation-id="card-arrayitem-default"]'));
+ expect(labelValue.nativeElement.innerText).toBe('ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_USERS');
+ expect(defaultElement.nativeElement.innerText).toBe('ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_USERS_DEFAULT');
+ });
+
+ }));
+
+ it('should display candidate groups', async(() => {
+ component.ngOnInit();
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ const candidateGroup1 = fixture.nativeElement.querySelector('[data-automation-id="card-arrayitem-chip-mockgroup1"] span');
+ const candidateGroup2 = fixture.nativeElement.querySelector('[data-automation-id="card-arrayitem-chip-mockgroup2"] span');
+ expect(getCandidateGroupsSpy).toHaveBeenCalled();
+ expect(candidateGroup1.innerText).toBe('mockgroup1');
+ expect(candidateGroup2.innerText).toBe('mockgroup2');
+ });
+ }));
+
+ it('should display placeholder if no candidate groups', async(() => {
+ component.ngOnInit();
+ getCandidateGroupsSpy.and.returnValue(of([]));
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ const labelValue = fixture.debugElement.query(By.css('[data-automation-id="card-array-label-candidateGroups"]'));
+ const defaultElement = fixture.debugElement.query(By.css('[data-automation-id="card-arrayitem-default"]'));
+ expect(labelValue.nativeElement.innerText).toBe('ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_GROUPS');
+ expect(defaultElement.nativeElement.innerText).toBe('ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_GROUPS_DEFAULT');
+ });
+
+ }));
+
describe('Config Filtering', () => {
it('should show only the properties from the configuration file', async(() => {
diff --git a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts
index 1f8f68b3f8..cab32859ca 100644
--- a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts
+++ b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts
@@ -21,6 +21,7 @@ import {
CardViewItem,
CardViewTextItemModel,
CardViewBaseItemModel,
+ CardViewArrayItemModel,
TranslationService,
AppConfigService,
UpdateNotification,
@@ -29,7 +30,7 @@ import {
import { TaskDetailsCloudModel, TaskStatusEnum } from '../../start-task/models/task-details-cloud.model';
import { Router } from '@angular/router';
import { TaskCloudService } from '../../services/task-cloud.service';
-import { Subject } from 'rxjs';
+import { Subject, Observable } from 'rxjs';
import { NumericFieldValidator } from '../../../validators/numeric-field.validator';
import { takeUntil } from 'rxjs/operators';
@@ -197,10 +198,38 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
multiline: true,
editable: true
}
+ ),
+ new CardViewArrayItemModel(
+ {
+ label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_USERS',
+ value: this.getCandidateUsers(),
+ key: 'candidateUsers',
+ icon: 'person',
+ default: this.translationService.instant('ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_USERS_DEFAULT'),
+ noOfItemsToDisplay: 2
+ }
+ ),
+ new CardViewArrayItemModel(
+ {
+ label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_GROUPS',
+ value: this.getCandidateGroups(),
+ key: 'candidateGroups',
+ icon: 'group',
+ default: this.translationService.instant('ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_GROUPS_DEFAULT'),
+ noOfItemsToDisplay: 2
+ }
)
];
}
+ private getCandidateUsers(): Observable {
+ return this.taskCloudService.getCandidateUsers(this.appName, this.taskId);
+ }
+
+ private getCandidateGroups(): Observable {
+ return this.taskCloudService.getCandidateGroups(this.appName, this.taskId);
+ }
+
/**
* Refresh the card data
*/