From a1cbcfca3341124ff6385317b5869bad35adae6a Mon Sep 17 00:00:00 2001 From: Mykyta Maliarchuk Date: Tue, 11 Jun 2024 13:23:56 +0200 Subject: [PATCH] [ACS-8001] content enrichment menu component --- docs/README.md | 1 + .../card-view-content-update.service.md | 5 +- .../content-enrichment-menu.component.md | 32 +++ .../base-card-view-update.interface.md | 17 +- .../interfaces/card-view-item.interface.md | 26 +-- .../prediction-status-update.interface.md | 28 +++ .../core/services/card-view-update.service.md | 19 ++ .../images/content-enrichment-menu.png | Bin 0 -> 31894 bytes docs/versionIndex.md | 8 + .../card-view-content-update.service.spec.ts | 11 + .../card-view-content-update.service.ts | 6 +- .../content-metadata.component.scss | 4 + .../content-metadata.component.spec.ts | 200 ++++++++++++++++-- .../content-metadata.component.ts | 80 +++++-- lib/content-services/src/public-api.ts | 1 - .../src/lib/assets/images/ai-sparkles.svg | 3 + .../src/lib/card-view/card-view.module.ts | 4 +- .../card-view/components/base-card-view.ts | 12 ++ .../card-view-boolitem.component.html | 23 +- .../card-view-boolitem.component.ts | 13 +- .../card-view-dateitem.component.html | 183 ++++++++-------- .../card-view-dateitem.component.ts | 3 + .../card-view-item-dispatcher.component.ts | 20 +- .../card-view-selectitem.component.html | 7 +- .../card-view-selectitem.component.scss | 12 ++ .../card-view-selectitem.component.ts | 8 +- .../card-view-textitem.component.html | 2 + .../card-view-textitem.component.ts | 3 + .../card-view/card-view.component.html | 3 +- .../card-view/card-view.component.scss | 11 + .../base-card-view-update.interface.ts | 3 + .../card-view-item-properties.interface.ts | 2 + .../interfaces/card-view-item.interface.ts | 3 + .../models/card-view-baseitem.model.ts | 3 + .../models/card-view-selectitem.model.spec.ts | 9 + .../models/card-view-selectitem.model.ts | 19 +- .../services/card-view-update.service.spec.ts | 10 + .../services/card-view-update.service.ts | 5 + .../lib/common/services/thumbnail.service.ts | 3 +- lib/core/src/lib/i18n/en.json | 13 ++ .../content-enrichment-menu.component.html | 49 +++++ .../content-enrichment-menu.component.scss | 136 ++++++++++++ .../content-enrichment-menu.component.spec.ts | 172 +++++++++++++++ .../content-enrichment-menu.component.ts | 93 ++++++++ .../src/lib/prediction/index.ts | 0 .../prediction-status-update.interface.ts | 21 ++ .../src/lib/prediction/public-api.ts | 1 + .../src/lib/prediction/services/index.ts | 0 .../services/prediction.service.spec.ts | 4 +- .../prediction/services/prediction.service.ts | 10 +- .../src/lib/styles/_components-variables.scss | 3 +- .../src/lib/styles/_reference-variables.scss | 1 + lib/core/src/public-api.ts | 1 + .../hxi-connector-api/api/predictions.api.ts | 11 +- 54 files changed, 1134 insertions(+), 183 deletions(-) create mode 100644 docs/core/components/content-enrichment-menu.component.md create mode 100644 docs/core/interfaces/prediction-status-update.interface.md create mode 100644 docs/docassets/images/content-enrichment-menu.png create mode 100644 lib/core/src/lib/assets/images/ai-sparkles.svg create mode 100644 lib/core/src/lib/prediction/components/content-enrichment-menu/content-enrichment-menu.component.html create mode 100644 lib/core/src/lib/prediction/components/content-enrichment-menu/content-enrichment-menu.component.scss create mode 100644 lib/core/src/lib/prediction/components/content-enrichment-menu/content-enrichment-menu.component.spec.ts create mode 100644 lib/core/src/lib/prediction/components/content-enrichment-menu/content-enrichment-menu.component.ts rename lib/{content-services => core}/src/lib/prediction/index.ts (100%) create mode 100644 lib/core/src/lib/prediction/interfaces/prediction-status-update.interface.ts rename lib/{content-services => core}/src/lib/prediction/public-api.ts (91%) rename lib/{content-services => core}/src/lib/prediction/services/index.ts (100%) rename lib/{content-services => core}/src/lib/prediction/services/prediction.service.spec.ts (94%) rename lib/{content-services => core}/src/lib/prediction/services/prediction.service.ts (83%) diff --git a/docs/README.md b/docs/README.md index ad25423a9b..576d900a2e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -96,6 +96,7 @@ A collection of Angular components for generic use. | [Card View component](core/components/card-view.component.md) | Displays a configurable property list renderer. | [Source](../lib/core/src/lib/card-view/components/card-view/card-view.component.ts) | | [Comment list component](core/components/comment-list.component.md) | Shows a list of comments. | [Source](../lib/core/src/lib/comments/comment-list/comment-list.component.ts) | | [Comments Component](core/components/comments.component.md) | Displays comments from users involved in a specified environment and allows an involved user to add a comment to a environment. | [Source](../lib/core/src/lib/comments/comments.component.ts) | +| [Content Enrichment Menu Component](core/components/content-enrichment-menu.component.md) | Allows the user to handle AI predictions by confirming or rejecting changes. | [Source](../lib/core/src/lib/prediction/components/content-enrichment-menu/content-enrichment-menu.component.ts) | | [Data Column Component](core/components/data-column.component.md) | Defines column properties for DataTable, Tasklist, Document List and other components. | [Source](../lib/core/src/lib/datatable/data-column/data-column.component.ts) | | [DataTable component](core/components/datatable.component.md) | Displays data as a table with customizable columns and presentation. | [Source](../lib/core/src/lib/datatable/components/datatable/datatable.component.ts) | | [Dynamic Chip List component](core/components/dynamic-chip-list.component.md) | This component shows dynamic list of chips which render depending on free space. | [Source](../lib/core/src/lib/dynamic-chip-list/dynamic-chip-list.component.ts) | diff --git a/docs/content-services/services/card-view-content-update.service.md b/docs/content-services/services/card-view-content-update.service.md index c487e805ca..95167dff87 100644 --- a/docs/content-services/services/card-view-content-update.service.md +++ b/docs/content-services/services/card-view-content-update.service.md @@ -2,7 +2,7 @@ Title: Card View Content Update Service Added: v6.0.0 Status: Active -Last reviewed: 2022-11-25 +Last reviewed: 2024-06-11 --- # [Card View Content Update Service](../../../lib/content-services/src/lib/common/services/card-view-content-update.service.ts "Defined in card-view-content-update.service.ts") @@ -25,6 +25,9 @@ Implements [`BaseCardViewContentUpdate`](../../../lib/content-services/src/lib/i - **updateNodeAspect**(node: [`MinimalNode`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeMinimalEntry.md))
Update node aspect observable. - _node:_ [`MinimalNode`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeMinimalEntry.md) - +- **onPredictionStatusChanged**(notification: [`PredictionStatusUpdate[]`](../../core/interfaces/prediction-status-update.interface.md))
+ Clears predictions for properties and sets the previous value, if provided. + - _notification:_ [`PredictionStatusUpdate[]`](../../core/interfaces/prediction-status-update.interface.md) - ## Properties diff --git a/docs/core/components/content-enrichment-menu.component.md b/docs/core/components/content-enrichment-menu.component.md new file mode 100644 index 0000000000..0d0b768bf1 --- /dev/null +++ b/docs/core/components/content-enrichment-menu.component.md @@ -0,0 +1,32 @@ +--- +Title: Content Enrichment Menu Component +Added: v6.10.0 +Status: Active +Last reviewed: 2024-06-11 +--- + +# [Content Enrichment Menu Component](../../../lib/core/src/lib/prediction/components/content-enrichment-menu/content-enrichment-menu.component.ts "Defined in content-enrichment-menu.component.ts") + +Allows the user to handle AI predictions by confirming or rejecting changes. + +![Content Enrichment Menu Component](../../docassets/images/content-enrichment-menu.png) + +## Basic Usage + +```html + + Form field + + + +``` + +### Properties + +| Name | Type | Default value | Description | +|------------|------------------------------------------------------------------------------------------------------------------------------------------|---------------|-----------------------------------| +| prediction | [`Prediction`](https://github.com/Alfresco/alfresco-ng2-components/blob/develop/lib/js-api/src/api/hxi-connector-api/docs/Prediction.md) | | Prediction for the node property. | + +## See also + +- [Prediction Status Update Interface](../interfaces/prediction-status-update.interface.md) diff --git a/docs/core/interfaces/base-card-view-update.interface.md b/docs/core/interfaces/base-card-view-update.interface.md index 64d2ec8dc0..5931ddf46b 100644 --- a/docs/core/interfaces/base-card-view-update.interface.md +++ b/docs/core/interfaces/base-card-view-update.interface.md @@ -16,20 +16,23 @@ export interface BaseCardViewUpdate { itemUpdated$: Subject; itemClicked$: Subject; updateItem$: Subject; + predictionStatusChanged$: Subject; update(property: CardViewBaseItemModel, newValue: any); clicked(property: CardViewBaseItemModel); updateElement(notification: CardViewBaseItemModel); + onPredictionStatusChanged(notification: PredictionStatusUpdate[]); } ``` ## Properties -| Name | Type | Description | -| ---- | ---- | ----------- | -| itemUpdated$ | [`Subject`](http://reactivex.io/documentation/subject.html)`<`[`UpdateNotification`](../../../lib/core/src/lib/card-view/interfaces/update-notification.interface.ts)`>` | The current updated item. | -| itemClicked$ | [`Subject`](http://reactivex.io/documentation/subject.html)`<`[`ClickNotification`](../../../lib/core/src/lib/card-view/interfaces/click-notification.interface.ts)`>` | The current clicked item. | -| updateItem$ | [`Subject`](http://reactivex.io/documentation/subject.html)`<`[`CardViewBaseItemModel`](../../../lib/core/src/lib/card-view/models/card-view-baseitem.model.ts)`>` | The current model for the update item. | +| Name | Type | Description | +|--------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------| +| itemUpdated$ | [`Subject`](http://reactivex.io/documentation/subject.html)`<`[`UpdateNotification`](../../../lib/core/src/lib/card-view/interfaces/update-notification.interface.ts)`>` | The current updated item. | +| itemClicked$ | [`Subject`](http://reactivex.io/documentation/subject.html)`<`[`ClickNotification`](../../../lib/core/src/lib/card-view/interfaces/click-notification.interface.ts)`>` | The current clicked item. | +| updateItem$ | [`Subject`](http://reactivex.io/documentation/subject.html)`<`[`CardViewBaseItemModel`](../../../lib/core/src/lib/card-view/models/card-view-baseitem.model.ts)`>` | The current model for the update item. | +| predictionStatusChanged$ | [`Subject`](http://reactivex.io/documentation/subject.html)`<`[`PredictionStatusUpdate[]`](./prediction-status-update.interface.md)`>` | The current model for items with irrelevant predictions. | ### Methods @@ -48,6 +51,10 @@ export interface BaseCardViewUpdate { Update updateItem$ observable. - notification:\_ [`CardViewBaseItemModel`](../../../lib/core/src/lib/card-view/models/card-view-baseitem.model.ts) - The notification. +- **onPredictionStatusChanged**(notification: [`PredictionStatusUpdate[]`](./prediction-status-update.interface.md))
+ Update predictionStatusChanged$ observable. + - notification:\_ [`PredictionStatusUpdate[]`](./prediction-status-update.interface.md) - The notification. + ## See also - [CardViewUpdate service](../services/card-view-update.service.md) diff --git a/docs/core/interfaces/card-view-item.interface.md b/docs/core/interfaces/card-view-item.interface.md index ec7a6e30d5..c9c5dcd6d3 100644 --- a/docs/core/interfaces/card-view-item.interface.md +++ b/docs/core/interfaces/card-view-item.interface.md @@ -2,7 +2,7 @@ Title: Card View Item interface Added: v2.0.0 Status: Active -Last reviewed: 2018-05-08 +Last reviewed: 2024-06-11 --- # [Card View Item interface](../../../lib/core/src/lib/card-view/interfaces/card-view-item.interface.ts "Defined in card-view-item.interface.ts") @@ -22,22 +22,24 @@ export interface CardViewItem { editable?: boolean; icon?: string; data?: any; + prediction?: Prediction; } ``` ### Properties -| Name | Type | Default | Description | -| ---- | ---- | ------- | ----------- | -| label | string | "" | Item label | -| value | any | | The original data value for the item | -| key | string | "" | Identifying key (important when editing the item) | -| default | any | | The default value to display if the value is empty | -| displayValue | string | "" | The value to display | -| editable | boolean | false | Toggles whether the item is editable | -| clickable | boolean | false | Toggles whether the item is clickable | -| icon | string | | The material icon to show beside clickable items | -| data | any | null | Any custom data which is needed to be provided and stored in the model for any reason. During an update or a click event this can be a container of any custom data which can be useful for 3rd party codes. | +| Name | Type | Default | Description | +|--------------|------------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| label | string | "" | Item label | +| value | any | | The original data value for the item | +| key | string | "" | Identifying key (important when editing the item) | +| default | any | | The default value to display if the value is empty | +| displayValue | string | "" | The value to display | +| editable | boolean | false | Toggles whether the item is editable | +| clickable | boolean | false | Toggles whether the item is clickable | +| icon | string | | The material icon to show beside clickable items | +| data | any | null | Any custom data which is needed to be provided and stored in the model for any reason. During an update or a click event this can be a container of any custom data which can be useful for 3rd party codes. | +| prediction | Prediction | | Property prediction | ## Details diff --git a/docs/core/interfaces/prediction-status-update.interface.md b/docs/core/interfaces/prediction-status-update.interface.md new file mode 100644 index 0000000000..f2100041be --- /dev/null +++ b/docs/core/interfaces/prediction-status-update.interface.md @@ -0,0 +1,28 @@ +--- +Title: Prediction Status Update Interface +Added: v6.10.0 +Status: Active +Last reviewed: 2024-06-11 +--- + +# [Prediction Status Update Interface](../../../lib/core/src/lib/prediction/interfaces/prediction-status-update.interface.ts "Defined in prediction-status-update.interface.ts") + +## Basic usage + +```ts +export interface PredictionStatusUpdate { + key: string; + previousValue?: any; + } +``` + +## Properties + +| Name | Type | Description | +|---------------|----------|-------------------------------| +| key | `string` | Key of the property. | +| previousValue | `any` | Previous human entered value. | + +## See also + +- [BaseCardViewUpdate interface](../interfaces/base-card-view-update.interface.md) diff --git a/docs/core/services/card-view-update.service.md b/docs/core/services/card-view-update.service.md index ecfbe06641..74747de049 100644 --- a/docs/core/services/card-view-update.service.md +++ b/docs/core/services/card-view-update.service.md @@ -137,8 +137,27 @@ Example this.cardViewUpdateService.updateElement(cardViewBaseItemModel) ``` +## Clear predictions for properties and set previous value + +`onPredictionStatusChanged` function helps to clear predictions and set previous value (if provided) for the card view item. It takes the [`PredictionStatusUpdate[]`](../interfaces/prediction-status-update.interface.md) as a parameter. + +Example + +```javascript + this.cardViewUpdateService.onPredictionStatusChanged(notification); +``` + +You can subscribe to the `predictionStatusChanged$` to be informed about prediction status changes. + +```ts +ngOnInit() { + this.cardViewUpdateService.predictionStatusChanged$.subscribe(this.respondToPredictionStatusChange.bind(this)); +} +``` + ## See also - [Card view component](../components/card-view.component.md) - [UpdateNotification interface](../interfaces/update-notification.interface.md) - [ClickNotification interface](../interfaces/click-notification.interface.md) +- [PredictionStatusUpdate interface](../interfaces/prediction-status-update.interface.md) diff --git a/docs/docassets/images/content-enrichment-menu.png b/docs/docassets/images/content-enrichment-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..2dcf18cf8cd7a3937a73f0de39859f7a6229a223 GIT binary patch literal 31894 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

2~{9Se>GCtc1BLY}=V{!Q`*dNY|lD z*S18%%EjDeUw&fh219SUCm9Eh%3%xKfB!)~vHm@ID3+de*bK9y`oBy1vbPJC1O?TR zU*hw|N@o&Pa z_xg?=Y97D+Q}X-XmP$!Tg8E@kTm1@O*{QO+L+qsYQCW<;YKvILLcdPn>JGJWY}#uP z3#vETlpQ~!a7|b~yLNF9?eY-P^(C91G=PJB7%u}>E=Vu3SKcJ8Pq^3?nv?Z%y6~6v)LZ zyK9Ab$^k0b3LR*--;`b>ui}mZ{_K(tN~C$Z?tydeObw0q2)M*Hs=#_ZqdJkAYEhz^ zZ+O4lP%y4Ph<$Lc9U|jEQTYHb-$r=3vttHs-&|{H@0Qm*30c}q)j7^F-gQEy?aa+M z=Qg1wl0Vpumnidg}ty&%g6%CK_5PDpqPO^IL zB=W~UYQr0SAE|zypV#gxx%*J$vY>dK9e-O$r7H`U?xb&J?Hwh!=V=<}*!lL!L65uB z5%u(n6Zm>ZltkLS9(PLCSkDT>SJ{&Cs|BU{7iR2pw%0NyJzx%wS_&0x?Frw0G*Tk( zd_z+ERti)l!P zIb%}1(oPF$W&6TZmzkQHI@y+<)6*Rrd;bMPlCvKRjXAzJpq#9a zlE);;7%Pjcct|V=_Ty%krCd32_S#O35<7))`*5nAY)cNT&4bsF}xvB zLxf6C35znN2g1?Kx2d6hsGj+={t7eK=c#kc;I z{*E@eW~_0|Jo4@1099IsxAL-mcJaiQPKaW{vpDcf1dv%#Bo65Af0#w9!Das0!hFw) zkJVY>>2{TvoRXQOZgYYq`9<#w@b+~AwRfl7AT07L7M(;TyF~Ash>eD}0Yw8Nmh0|U z`m-x6w9_P;GFCZ)=aeUNuIU}nL;re+J0Sbmv<;8Y+7Tl^foNa#N%V#UyS=BLg`9wl zW^1>qD95W!Dj{U>**nOsa)aabl9C*;EsYBV`@d?xVc>ls + +- [Content Enrichment Menu Component](core/components/content-enrichment-menu.component.md) + + + ## v6.8.0 diff --git a/lib/content-services/src/lib/common/services/card-view-content-update.service.spec.ts b/lib/content-services/src/lib/common/services/card-view-content-update.service.spec.ts index 21f7b4edfe..4dcf36f3c2 100644 --- a/lib/content-services/src/lib/common/services/card-view-content-update.service.spec.ts +++ b/lib/content-services/src/lib/common/services/card-view-content-update.service.spec.ts @@ -18,12 +18,15 @@ import { Node } from '@alfresco/js-api'; import { fakeAsync, TestBed } from '@angular/core/testing'; import { CardViewContentUpdateService } from './card-view-content-update.service'; +import { CardViewUpdateService, PredictionStatusUpdate } from '@alfresco/adf-core'; describe('CardViewContentUpdateService', () => { let cardViewContentUpdateService: CardViewContentUpdateService; + let cardViewUpdateService: CardViewUpdateService; beforeEach(() => { cardViewContentUpdateService = TestBed.inject(CardViewContentUpdateService); + cardViewUpdateService = TestBed.inject(CardViewUpdateService); }); it('should send updated node when aspect changed', fakeAsync(() => { @@ -34,4 +37,12 @@ describe('CardViewContentUpdateService', () => { cardViewContentUpdateService.updateNodeAspect(fakeNode); })); + + it('should call onPredictionStatusChanged on cardViewUpdateService', () => { + spyOn(cardViewUpdateService, 'onPredictionStatusChanged'); + const mockNotification: PredictionStatusUpdate[] = [{ key: 'test', previousValue: 'value' }]; + cardViewContentUpdateService.onPredictionStatusChanged(mockNotification); + + expect(cardViewUpdateService.onPredictionStatusChanged).toHaveBeenCalledWith(mockNotification); + }); }); diff --git a/lib/content-services/src/lib/common/services/card-view-content-update.service.ts b/lib/content-services/src/lib/common/services/card-view-content-update.service.ts index 42f3533049..c44e1d27c9 100644 --- a/lib/content-services/src/lib/common/services/card-view-content-update.service.ts +++ b/lib/content-services/src/lib/common/services/card-view-content-update.service.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { UpdateNotification, CardViewBaseItemModel, CardViewUpdateService } from '@alfresco/adf-core'; +import { UpdateNotification, CardViewBaseItemModel, CardViewUpdateService, PredictionStatusUpdate } from '@alfresco/adf-core'; import { Node } from '@alfresco/js-api'; import { Injectable } from '@angular/core'; import { Subject } from 'rxjs'; @@ -40,6 +40,10 @@ export class CardViewContentUpdateService implements BaseCardViewContentUpdate { this.cardViewUpdateService.updateElement(notification); } + onPredictionStatusChanged(notification: PredictionStatusUpdate[]) { + this.cardViewUpdateService.onPredictionStatusChanged(notification); + } + updateNodeAspect(node: Node) { this.updatedAspect$.next(node); } diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.scss b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.scss index f4a4776fff..dfc5a27544 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.scss +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.scss @@ -34,6 +34,10 @@ $panel-properties-height: 56px !default; font-size: 19px; line-height: 20px; } + + adf-content-enrichment-menu button.adf-ai-button { + margin-left: -10px; + } } } diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.spec.ts b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.spec.ts index 886f34500d..66d5a59a40 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.spec.ts +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.spec.ts @@ -18,7 +18,17 @@ import { ComponentFixture, discardPeriodicTasks, fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; import { SimpleChange } from '@angular/core'; import { By } from '@angular/platform-browser'; -import { Category, CategoryPaging, ClassesApi, Node, Tag, TagBody, TagEntry, TagPaging, TagPagingList } from '@alfresco/js-api'; +import { + Category, + CategoryPaging, + ClassesApi, + Node, Prediction, PredictionPaging, ReviewStatus, + Tag, + TagBody, + TagEntry, + TagPaging, + TagPagingList +} from '@alfresco/js-api'; import { ContentMetadataComponent } from './content-metadata.component'; import { ContentMetadataService } from '../../services/content-metadata.service'; import { @@ -30,10 +40,12 @@ import { PipeModule, TranslationMock, TranslationService, - UpdateNotification + UpdateNotification, + PredictionService, + PredictionStatusUpdate } from '@alfresco/adf-core'; import { NodesApiService } from '../../../common/services/nodes-api.service'; -import { EMPTY, of, throwError } from 'rxjs'; +import { EMPTY, of, throwError, Subject } from 'rxjs'; import { TranslateModule } from '@ngx-translate/core'; import { CardViewContentUpdateService } from '../../../common/services/card-view-content-update.service'; import { PropertyGroup } from '../../interfaces/property-group.interface'; @@ -61,12 +73,14 @@ describe('ContentMetadataComponent', () => { let fixture: ComponentFixture; let contentMetadataService: ContentMetadataService; let updateService: CardViewContentUpdateService; + let predictionService: PredictionService; let nodesApiService: NodesApiService; let node: Node; let folderNode: Node; let tagService: TagService; let categoryService: CategoryService; let getClassSpy: jasmine.Spy; + let getBasicPropertiesSpy: jasmine.Spy; let notificationService: NotificationService; let getGroupedPropertiesSpy: jasmine.Spy; @@ -215,6 +229,13 @@ describe('ContentMetadataComponent', () => { linkNodeToCategory: () => EMPTY, unlinkNodeFromCategory: () => EMPTY } + }, + { + provide: PredictionService, + useValue: { + getPredictions: () => EMPTY, + predictionStatusUpdated$: new Subject() + } } ] }); @@ -222,6 +243,7 @@ describe('ContentMetadataComponent', () => { component = fixture.componentInstance; contentMetadataService = TestBed.inject(ContentMetadataService); updateService = TestBed.inject(CardViewContentUpdateService); + predictionService = TestBed.inject(PredictionService); nodesApiService = TestBed.inject(NodesApiService); tagService = TestBed.inject(TagService); categoryService = TestBed.inject(CategoryService); @@ -250,7 +272,8 @@ describe('ContentMetadataComponent', () => { component.node = node; component.preset = preset; spyOn(contentMetadataService, 'getContentTypeProperty').and.returnValue(of([])); - getGroupedPropertiesSpy = spyOn(contentMetadataService, 'getGroupedProperties'); + getGroupedPropertiesSpy = spyOn(contentMetadataService, 'getGroupedProperties').and.returnValue(of([])); + getBasicPropertiesSpy = spyOn(contentMetadataService, 'getBasicProperties').and.returnValue(of([])); getClassSpy = spyOn(classesApi, 'getClass'); fixture.detectChanges(); }); @@ -267,6 +290,10 @@ describe('ContentMetadataComponent', () => { it('should have expanded input param as false by default', () => { expect(component.expanded).toBeFalse(); }); + + it('should have display predictions param as false by default', () => { + expect(component.displayPredictions).toBeFalse(); + }); }); describe('Folder', () => { @@ -300,8 +327,6 @@ describe('ContentMetadataComponent', () => { it('nodeAspectUpdate', fakeAsync(() => { const fakeNode = { id: 'fake-minimal-node', aspectNames: ['ft:a', 'ft:b', 'ft:c'], name: 'fake-node' } as Node; - getGroupedPropertiesSpy.and.stub(); - spyOn(contentMetadataService, 'getBasicProperties').and.stub(); updateService.updateNodeAspect(fakeNode); tick(600); @@ -335,6 +360,18 @@ describe('ContentMetadataComponent', () => { expect(component.node).toEqual(expectedNode); })); + it('should call onPredictionStatusChanged with updated property keys to clear predictions if displayPredictions=true', () => { + spyOn(updateService, 'onPredictionStatusChanged'); + const expectedNode: Node = { ...node, name: 'some-modified-value' }; + spyOn(nodesApiService, 'updateNode').and.returnValue(of(expectedNode)); + + component.displayPredictions = true; + component.changedProperties = { properties: { key1: 'value1', key2: 'value2' } }; + + component.saveChanges(); + expect(updateService.onPredictionStatusChanged).toHaveBeenCalledWith([{ key: 'key1' }, { key: 'key2' }]); + }); + it('should call removeTag and assignTagsToNode on TagService on save click', fakeAsync(() => { component.displayTags = true; const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel; @@ -685,8 +722,6 @@ describe('ContentMetadataComponent', () => { }); it('should load the basic properties on node change', () => { - spyOn(contentMetadataService, 'getBasicProperties'); - component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); expect(contentMetadataService.getContentTypeProperty).toHaveBeenCalledWith(expectedNode); @@ -697,7 +732,7 @@ describe('ContentMetadataComponent', () => { const expectedProperties = []; component.expanded = false; - spyOn(contentMetadataService, 'getBasicProperties').and.returnValue(of(expectedProperties)); + getBasicPropertiesSpy.and.returnValue(of(expectedProperties)); component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); @@ -714,7 +749,7 @@ describe('ContentMetadataComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - spyOn(contentMetadataService, 'getBasicProperties').and.returnValue(of([])); + getBasicPropertiesSpy.and.returnValue(of([])); component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); @@ -726,10 +761,7 @@ describe('ContentMetadataComponent', () => { }); it('should load the group properties on node change', () => { - getGroupedPropertiesSpy.and.stub(); - component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); - expect(contentMetadataService.getGroupedProperties).toHaveBeenCalledWith(expectedNode, 'custom-preset'); }); @@ -750,8 +782,6 @@ describe('ContentMetadataComponent', () => { } ]; component.preset = presetConfig; - getGroupedPropertiesSpy.and.stub(); - component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); expect(contentMetadataService.getGroupedProperties).toHaveBeenCalledWith(expectedNode, presetConfig); @@ -813,7 +843,6 @@ describe('ContentMetadataComponent', () => { it('should revert reload properties for general info panel on cancel', () => { component.readOnly = false; fixture.detectChanges(); - spyOn(contentMetadataService, 'getBasicProperties'); toggleEditModeForGeneralInfo(); findCancelButton().click(); @@ -1002,6 +1031,7 @@ describe('ContentMetadataComponent', () => { component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); fixture.detectChanges(); + await fixture.whenStable(); await component.groupedProperties$.toPromise(); fixture.detectChanges(); @@ -1022,6 +1052,7 @@ describe('ContentMetadataComponent', () => { component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); fixture.detectChanges(); + await fixture.whenStable(); await component.groupedProperties$.toPromise(); fixture.detectChanges(); @@ -1042,6 +1073,7 @@ describe('ContentMetadataComponent', () => { component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); fixture.detectChanges(); + await fixture.whenStable(); await component.groupedProperties$.toPromise(); fixture.detectChanges(); @@ -1063,6 +1095,7 @@ describe('ContentMetadataComponent', () => { component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); fixture.detectChanges(); + await fixture.whenStable(); await component.groupedProperties$.toPromise(); fixture.detectChanges(); @@ -1083,6 +1116,7 @@ describe('ContentMetadataComponent', () => { component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); fixture.detectChanges(); + await fixture.whenStable(); await component.groupedProperties$.toPromise(); fixture.detectChanges(); @@ -1107,6 +1141,7 @@ describe('ContentMetadataComponent', () => { component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); fixture.detectChanges(); + await fixture.whenStable(); await component.groupedProperties$.toPromise(); fixture.detectChanges(); @@ -1141,6 +1176,7 @@ describe('ContentMetadataComponent', () => { component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); fixture.detectChanges(); + await fixture.whenStable(); await component.groupedProperties$.toPromise(); fixture.detectChanges(); @@ -1640,4 +1676,136 @@ describe('ContentMetadataComponent', () => { expect(customComponent).toBeDefined(); }); }); + + describe('Predictions', () => { + const getMockPrediction = (reviewStatus: ReviewStatus): Prediction => ({ + confidenceLevel: 0.9, + predictionDateTime: new Date(2024, 1, 1), + modelId: 'test-model-id', + property: 'test:test', + id: 'test-prediction-id', + previousValue: 'previous value', + predictionValue: 'new value', + updateType: 'AUTOCORRECT', + reviewStatus: reviewStatus + }); + + const getMockPredictionPaging = (predictions: Prediction[]): PredictionPaging => ({ + list: { + entries: predictions.map(prediction => ({entry: prediction})) + } + }); + + let getPredictionsSpy: jasmine.Spy; + + beforeEach(() => { + component.node = node; + component.displayPredictions = true; + getPredictionsSpy = spyOn(predictionService, 'getPredictions').and.returnValue(of(getMockPredictionPaging([getMockPrediction(ReviewStatus.UNREVIEWED)]))); + fixture.detectChanges(); + }); + + it('should load predictions when displayPredictions is true', () => { + component.ngOnInit(); + expect(predictionService.getPredictions).toHaveBeenCalledWith(node.id); + }); + + it('should map predictions to basic properties', (done) => { + getBasicPropertiesSpy.and.returnValue( + of([ + { + key: 'properties.test:test', + editable: true, + value: 'new value', + title: 'test' + } + ]) + ); + component.ngOnInit(); + + component.basicProperties$.subscribe(properties => { + expect(properties[0].prediction).toEqual(getMockPrediction(ReviewStatus.UNREVIEWED)); + done(); + }); + }); + + it('should map predictions to grouped properties', (done) => { + getGroupedPropertiesSpy.and.returnValue( + of([ + { + editable: true, + title: 'test', + properties: [{ + key: 'properties.test:test', + editable: true, + value: 'new value', + title: 'test' + }] + } + ]) + ); + component.ngOnInit(); + + component.groupedProperties$.subscribe(properties => { + expect(properties[0].properties[0].prediction).toEqual(getMockPrediction(ReviewStatus.UNREVIEWED)); + done(); + }); + }); + + it('should not map predictions when reviewStatus other then UNREVIEWED', (done) => { + getPredictionsSpy.and.returnValue(of(getMockPredictionPaging([getMockPrediction(ReviewStatus.REJECTED), getMockPrediction(ReviewStatus.CONFIRMED)]))); + getBasicPropertiesSpy.and.returnValue( + of([ + { + key: 'properties.test:test', + editable: true, + value: 'new value', + title: 'test' + } + ]) + ); + component.ngOnInit(); + + component.basicProperties$.subscribe(properties => { + expect(properties[0].prediction).toBeNull(); + done(); + }); + }); + + it('should not map predictions to properties if the property value is different from the prediction value', (done) => { + getBasicPropertiesSpy.and.returnValue( + of([ + { + key: 'properties.test:test', + editable: true, + value: 'different value', + title: 'test' + } + ]) + ); + component.ngOnInit(); + + component.basicProperties$.subscribe(properties => { + expect(properties[0].prediction).toBeNull(); + done(); + }); + }); + + it('should set updated node when prediction status has changed', () => { + const updatedNode = {...node, name: 'new test name'}; + const getNodeSpy = spyOn(nodesApiService, 'getNode').and.returnValue(of(updatedNode)); + component.ngOnInit(); + predictionService.predictionStatusUpdated$.next({key: 'test:test', previousValue: 'previous value'}); + expect(getNodeSpy).toHaveBeenCalledWith(node.id); + expect(component.node).toEqual(updatedNode); + }); + + it('should call onPredictionStatusChanged when prediction status has changed', () => { + const onPredictionStatusChangedSpy = spyOn(updateService, 'onPredictionStatusChanged'); + const notification = {key: 'test:test', previousValue: 'previous value'}; + component.ngOnInit(); + predictionService.predictionStatusUpdated$.next(notification); + expect(onPredictionStatusChangedSpy).toHaveBeenCalledWith([notification]); + }); + }); }); diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts index ec4f84e51e..5645d015fe 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts @@ -16,7 +16,7 @@ */ import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core'; -import { Category, CategoryEntry, CategoryLinkBody, CategoryPaging, Node, TagBody, TagEntry, TagPaging } from '@alfresco/js-api'; +import { Category, CategoryEntry, CategoryLinkBody, CategoryPaging, Node, TagBody, TagEntry, TagPaging, Prediction, ReviewStatus } from '@alfresco/js-api'; import { forkJoin, Observable, of, Subject, zip } from 'rxjs'; import { AppConfigService, @@ -24,6 +24,7 @@ import { CardViewItem, CardViewModule, NotificationService, + PredictionService, TranslationService, UpdateNotification } from '@alfresco/adf-core'; @@ -89,14 +90,14 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { /** Toggles whether to display empty values in the card view */ @Input() - displayEmpty: boolean = false; + displayEmpty = false; /** * Toggles between expanded (ie, full information) and collapsed * (ie, reduced information) in the display */ @Input() - expanded: boolean = false; + expanded = false; /** The multi parameter of the underlying material expansion panel, set to true to allow multi accordion to be expanded at the same time */ @Input() @@ -108,19 +109,23 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { /** Toggles whether the metadata properties should be shown */ @Input() - displayDefaultProperties: boolean = true; + displayDefaultProperties = true; /** (optional) shows the given aspect in the expanded card */ @Input() displayAspect: string = null; - /** Toggles whether or not to enable copy to clipboard action. */ + /** Toggles whether to enable copy to clipboard action. */ @Input() - copyToClipboardAction: boolean = true; + copyToClipboardAction = true; - /** Toggles whether or not to enable chips for multivalued properties. */ + /** Toggles whether AI predictions should be shown. */ @Input() - useChipsForMultiValueProperty: boolean = true; + displayPredictions = false; + + /** Toggles whether to enable chips for multivalued properties. */ + @Input() + useChipsForMultiValueProperty = true; /** True if tags should be displayed, false otherwise */ @Input() @@ -176,7 +181,8 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { private tagService: TagService, private categoryService: CategoryService, private contentService: ContentService, - private notificationService: NotificationService + private notificationService: NotificationService, + private predictionService: PredictionService ) { this.copyToClipboardAction = this.appConfig.get('content-metadata.copy-to-clipboard-action'); this.multiValueSeparator = this.appConfig.get('content-metadata.multi-value-pipe-separator') || DEFAULT_SEPARATOR; @@ -197,6 +203,15 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { this.loadProperties(node); }); + if (this.displayPredictions) { + this.predictionService.predictionStatusUpdated$.pipe(takeUntil(this.onDestroy$)).subscribe(({key, previousValue}) => { + this.cardViewContentUpdateService.onPredictionStatusChanged([{key, previousValue}]); + this.nodesApiService.getNode(this.node.id).subscribe((node) => { + Object.assign(this.node, node); + }); + }); + } + this.loadProperties(this.node); this.verifyAllowableOperations(); @@ -407,6 +422,9 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { ) .subscribe((result: any) => { if (result) { + if (this.displayPredictions) { + this.cardViewContentUpdateService.onPredictionStatusChanged(Object.keys(this.changedProperties['properties']).map(key => ({key}))); + } this.updateUndefinedNodeProperties(result.updatedNode); if (this.hasContentTypeChanged(this.changedProperties)) { this.cardViewContentUpdateService.updateNodeAspect(this.node); @@ -439,13 +457,37 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { private loadProperties(node: Node, loadBasicProps = true, loadGroupedProps = true, loadTags = true, loadCategories = true) { if (node) { + const requests = {}; + if (loadBasicProps) { - this.basicProperties$ = this.getProperties(node); + requests['properties'] = this.getProperties(node); } + if (loadGroupedProps) { - this.groupedProperties$ = this.contentMetadataService.getGroupedProperties(node, this.preset); + requests['groupedProperties'] = this.contentMetadataService.getGroupedProperties(node, this.preset); } + if (this.displayPredictions) { + requests['predictions'] = this.loadPredictionsForNode(this.node.id); + } + + forkJoin(requests).subscribe(({ predictions, properties, groupedProperties }: ({ predictions: Prediction[]; properties: CardViewItem[]; groupedProperties: CardViewGroup[] })) => { + if (loadBasicProps && properties) { + this.basicProperties$ = predictions + ? of(properties.map(property => this.mapPredictionsToProperty(property, predictions))) + : of(properties); + } + + if (loadGroupedProps && groupedProperties) { + this.groupedProperties$ = predictions + ? of(groupedProperties.map(group => { + group.properties = group.properties.map(property => this.mapPredictionsToProperty(property, predictions)); + return group; + })) + : of(groupedProperties); + } + }); + if (this.displayTags && loadTags) { this.loadTagsForNode(node.id); } @@ -462,7 +504,7 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { } } - private getProperties(node: Node) { + private getProperties(node: Node): Observable { const properties$ = this.contentMetadataService.getBasicProperties(node); const contentTypeProperty$ = this.contentMetadataService.getContentTypeProperty(node); return zip(properties$, contentTypeProperty$).pipe( @@ -540,4 +582,18 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { } return observables; } + + private mapPredictionsToProperty(property: CardViewItem, predictions: Prediction[]): CardViewItem { + const propertyKey = property.key.split('.')[1]; + const filteredPrediction = predictions.find(prediction => prediction.property === propertyKey && prediction.reviewStatus === ReviewStatus.UNREVIEWED && prediction.predictionValue === property.value); + + property.prediction = filteredPrediction || null; + return property; + } + + private loadPredictionsForNode(nodeId: string): Observable { + return this.predictionService.getPredictions(nodeId).pipe( + map(predictionPaging => predictionPaging.list.entries.map(predictionEntry => predictionEntry.entry)) + ); + } } diff --git a/lib/content-services/src/public-api.ts b/lib/content-services/src/public-api.ts index 759af620dc..fdab75491a 100644 --- a/lib/content-services/src/public-api.ts +++ b/lib/content-services/src/public-api.ts @@ -42,7 +42,6 @@ export * from './lib/category/index'; export * from './lib/viewer/index'; export * from './lib/security/index'; export * from './lib/infinite-scroll-datasource'; -export * from './lib/prediction/index'; export * from './lib/content.module'; export * from './lib/testing/content.testing.module'; diff --git a/lib/core/src/lib/assets/images/ai-sparkles.svg b/lib/core/src/lib/assets/images/ai-sparkles.svg new file mode 100644 index 0000000000..ae1050ed47 --- /dev/null +++ b/lib/core/src/lib/assets/images/ai-sparkles.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/src/lib/card-view/card-view.module.ts b/lib/core/src/lib/card-view/card-view.module.ts index 2c8700b801..c9a663d667 100644 --- a/lib/core/src/lib/card-view/card-view.module.ts +++ b/lib/core/src/lib/card-view/card-view.module.ts @@ -43,6 +43,7 @@ import { CardViewKeyValuePairsItemComponent } from './components/card-view-keyva import { CardViewSelectItemComponent } from './components/card-view-selectitem/card-view-selectitem.component'; import { CardViewArrayItemComponent } from './components/card-view-arrayitem/card-view-arrayitem.component'; import { SelectFilterInputComponent } from './components/card-view-selectitem/select-filter-input/select-filter-input.component'; +import { ContentEnrichmentMenuComponent } from '../prediction/components/content-enrichment-menu/content-enrichment-menu.component'; @NgModule({ imports: [ @@ -63,7 +64,8 @@ import { SelectFilterInputComponent } from './components/card-view-selectitem/se MatCardModule, MatDatetimepickerModule, MatNativeDatetimeModule, - MatSlideToggleModule + MatSlideToggleModule, + ContentEnrichmentMenuComponent ], declarations: [ CardViewComponent, diff --git a/lib/core/src/lib/card-view/components/base-card-view.ts b/lib/core/src/lib/card-view/components/base-card-view.ts index 6ac45fcf82..c48108be56 100644 --- a/lib/core/src/lib/card-view/components/base-card-view.ts +++ b/lib/core/src/lib/card-view/components/base-card-view.ts @@ -40,6 +40,18 @@ export abstract class BaseCardView implements OnDestroy this.property.value = itemModel.value; } }); + + this.cardViewUpdateService.predictionStatusChanged$.pipe(takeUntil(this.destroy$)).subscribe((items) => { + items.map(item => { + if (this.property.key.split('.')[1] === item.key) { + this.property.prediction = null; + + if (item.previousValue !== null && item.previousValue !== undefined) { + this.property.value = item.previousValue; + } + } + }); + }); } get isEditable(): boolean { diff --git a/lib/core/src/lib/card-view/components/card-view-boolitem/card-view-boolitem.component.html b/lib/core/src/lib/card-view/components/card-view-boolitem/card-view-boolitem.component.html index 016caa651b..115347459d 100644 --- a/lib/core/src/lib/card-view/components/card-view-boolitem/card-view-boolitem.component.html +++ b/lib/core/src/lib/card-view/components/card-view-boolitem/card-view-boolitem.component.html @@ -1,13 +1,16 @@ -

- -
{{ property.label | translate }}
-
+
+ +
+ +
{{ property.label | translate }}
+
+
diff --git a/lib/core/src/lib/card-view/components/card-view-boolitem/card-view-boolitem.component.ts b/lib/core/src/lib/card-view/components/card-view-boolitem/card-view-boolitem.component.ts index d79c4e38e7..3b1aee3fad 100644 --- a/lib/core/src/lib/card-view/components/card-view-boolitem/card-view-boolitem.component.ts +++ b/lib/core/src/lib/card-view/components/card-view-boolitem/card-view-boolitem.component.ts @@ -25,10 +25,12 @@ import { BaseCardView } from '../base-card-view'; templateUrl: './card-view-boolitem.component.html', styles: [ ` - .adf-property-value { - padding: 15px 0; - } - ` + .adf-property-value { + padding: 15px 0; + display: flex; + align-content: center; + } + ` ] }) @@ -36,6 +38,9 @@ export class CardViewBoolItemComponent extends BaseCardView {{ property.label | translate }} -
- - {{ property.displayValue }} - -
-
+
+ +
+ {{ property.displayValue }} - - {{ property.displayValue }} +
+
+ - + + {{ property.displayValue }} + - - clear - + + clear + - - -
+ + +
- - - - -
- - {{ property.default | translate }} - - -
- - - {{ propertyValue }} - cancel - - - -
- - +
+ + {{ property.default | translate }} + + +
+ + + {{ propertyValue }} + cancel + + + +
+ + + + + +
+
diff --git a/lib/core/src/lib/card-view/components/card-view-dateitem/card-view-dateitem.component.ts b/lib/core/src/lib/card-view/components/card-view-dateitem/card-view-dateitem.component.ts index 6f83a51cbb..d214556bc4 100644 --- a/lib/core/src/lib/card-view/components/card-view-dateitem/card-view-dateitem.component.ts +++ b/lib/core/src/lib/card-view/components/card-view-dateitem/card-view-dateitem.component.ts @@ -49,6 +49,9 @@ export class CardViewDateItemComponent extends BaseCardView; diff --git a/lib/core/src/lib/card-view/components/card-view-item-dispatcher/card-view-item-dispatcher.component.ts b/lib/core/src/lib/card-view/components/card-view-item-dispatcher/card-view-item-dispatcher.component.ts index 0217cbc9b8..122bf2801b 100644 --- a/lib/core/src/lib/card-view/components/card-view-item-dispatcher/card-view-item-dispatcher.component.ts +++ b/lib/core/src/lib/card-view/components/card-view-item-dispatcher/card-view-item-dispatcher.component.ts @@ -32,27 +32,30 @@ export class CardViewItemDispatcherComponent implements OnChanges { editable: boolean; @Input() - displayEmpty: boolean = true; + displayEmpty = true; @Input() - displayNoneOption: boolean = true; + displayNoneOption = true; @Input() - displayClearAction: boolean = true; + displayClearAction = true; @Input() - copyToClipboardAction: boolean = true; + copyToClipboardAction = true; @Input() - useChipsForMultiValueProperty: boolean = true; + useChipsForMultiValueProperty = true; @Input() - multiValueSeparator: string = DEFAULT_SEPARATOR; + multiValueSeparator = DEFAULT_SEPARATOR; @Input() - displayLabelForChips: boolean = false; + displayLabelForChips = false; - private loaded: boolean = false; + @Input() + hasContentEnrichment = false; + + private loaded = false; private componentReference: any = null; public ngOnInit; @@ -104,6 +107,7 @@ export class CardViewItemDispatcherComponent implements OnChanges { this.componentReference.instance.useChipsForMultiValueProperty = this.useChipsForMultiValueProperty; this.componentReference.instance.multiValueSeparator = this.multiValueSeparator; this.componentReference.instance.displayLabelForChips = this.displayLabelForChips; + this.componentReference.instance.hasContentEnrichment = this.hasContentEnrichment; } private proxy(methodName, ...args) { diff --git a/lib/core/src/lib/card-view/components/card-view-selectitem/card-view-selectitem.component.html b/lib/core/src/lib/card-view/components/card-view-selectitem/card-view-selectitem.component.html index 8aeacf3806..0e4e88991b 100644 --- a/lib/core/src/lib/card-view/components/card-view-selectitem/card-view-selectitem.component.html +++ b/lib/core/src/lib/card-view/components/card-view-selectitem/card-view-selectitem.component.html @@ -12,9 +12,12 @@ *ngIf="!isEditable" class="adf-property-value adf-property-read-only" [attr.data-automation-id]="'select-readonly-value-' + property.key" - data-automation-class="read-only-value">{{ (property.displayValue | async) | translate }} + data-automation-class="read-only-value"> + + {{ (property.displayValue | async) | translate }}
-
+
+ []>; @Input() - displayNoneOption: boolean = true; + displayNoneOption = true; @Input() - displayEmpty: boolean = true; + displayEmpty = true; + + @Input() + hasContentEnrichment = false; value: string | number; filter$ = new BehaviorSubject(''); @@ -50,6 +53,7 @@ export class CardViewSelectItemComponent extends BaseCardView +