diff --git a/docs/README.md b/docs/README.md index ad25423a9b5..576d900a2e2 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 c487e805ca4..95167dff874 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 00000000000..e9b45a19eef --- /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`](../../../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 64d2ec8dc0c..2df73a0c0df 100644 --- a/docs/core/interfaces/base-card-view-update.interface.md +++ b/docs/core/interfaces/base-card-view-update.interface.md @@ -2,7 +2,7 @@ Title: Base Card View Update Interface Added: v6.0.0 Status: Active -Last reviewed: 2022-11-25 +Last reviewed: 2024-06-11 --- # [Base Card View Update interface](../../../lib/core/src/lib/card-view/interfaces/base-card-view-update.interface.ts "Defined in base-card-view-update.interface.ts") @@ -16,10 +16,12 @@ 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[]); } ``` @@ -30,6 +32,7 @@ export interface BaseCardViewUpdate { | 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)`>` | Notification of prediction status change. | ### 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 ec7a6e30d57..55d8a09ff50 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,6 +22,7 @@ export interface CardViewItem { editable?: boolean; icon?: string; data?: any; + prediction?: Prediction; } ``` @@ -38,6 +39,7 @@ export interface CardViewItem { | 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 | null | 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 00000000000..f2100041beb --- /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 ecfbe06641a..74747de0490 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 00000000000..2dcf18cf8cd Binary files /dev/null and b/docs/docassets/images/content-enrichment-menu.png differ diff --git a/docs/versionIndex.md b/docs/versionIndex.md index eeab83126d0..b8e9251164f 100644 --- a/docs/versionIndex.md +++ b/docs/versionIndex.md @@ -12,6 +12,7 @@ backend services have been tested with each released version of ADF. ## Versions +- [v6.10.0](#v6100) - [v6.8.0](#v680) - [v6.7.0](#v670) - [v6.4.0](#v640) @@ -46,6 +47,15 @@ backend services have been tested with each released version of ADF. - [v2.1.0](#v210) - [v2.0.0](#v200) +## v6.10.0 + + + +- [Content Enrichment Menu Component](core/components/content-enrichment-menu.component.md) +- [Prediction Status Update Interface](core/interfaces/prediction-status-update.interface.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 21f7b4edfef..4dcf36f3c26 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 42f35330496..c44e1d27c96 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-card/content-metadata-card.component.spec.ts b/lib/content-services/src/lib/content-metadata/components/content-metadata-card/content-metadata-card.component.spec.ts index 9e7e5b6234b..9aa653352b1 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata-card/content-metadata-card.component.spec.ts +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata-card/content-metadata-card.component.spec.ts @@ -26,7 +26,7 @@ import { NodeAspectService } from '../../../aspect-list/services/node-aspect.ser import { ContentMetadataService } from '../../services/content-metadata.service'; import { AllowableOperationsEnum } from '../../../common/models/allowable-operations.enum'; import { of } from 'rxjs'; -import { AlfrescoApiService, AlfrescoApiServiceMock, AuthModule, PipeModule, TranslationMock, TranslationService } from '@alfresco/adf-core'; +import { AlfrescoApiService, AlfrescoApiServiceMock, AuthModule, PipeModule, TranslationMock, TranslationService, CONTENT_ENRICHMENT } from '@alfresco/adf-core'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { HttpClientModule } from '@angular/common/http'; import { versionCompatibilityFactory } from '../../../version-compatibility/version-compatibility-factory'; @@ -37,6 +37,7 @@ import { MatTooltipModule } from '@angular/material/tooltip'; import { CategoryService } from '../../../category'; import { TagService } from '../../../tag'; import { PropertyDescriptorsService } from '../../public-api'; +import { provideMockFeatureFlags } from '@alfresco/adf-core/feature-flags'; describe('ContentMetadataCardComponent', () => { let component: ContentMetadataCardComponent; @@ -72,7 +73,8 @@ describe('ContentMetadataCardComponent', () => { useFactory: versionCompatibilityFactory, deps: [VersionCompatibilityService], multi: true - } + }, + provideMockFeatureFlags({[CONTENT_ENRICHMENT.EXPERIENCE_INSIGHT]: true}) ] }); fixture = TestBed.createComponent(ContentMetadataCardComponent); 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 f4a4776fff4..dfc5a27544e 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 886f34500d9..f9c097190f7 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,20 @@ 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 +43,13 @@ import { PipeModule, TranslationMock, TranslationService, - UpdateNotification + UpdateNotification, + PredictionService, + PredictionStatusUpdate, + CONTENT_ENRICHMENT } 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'; @@ -55,18 +71,21 @@ import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { MatChipHarness } from '@angular/material/chips/testing'; +import { provideMockFeatureFlags } from '@alfresco/adf-core/feature-flags'; describe('ContentMetadataComponent', () => { let component: 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,13 +234,22 @@ describe('ContentMetadataComponent', () => { linkNodeToCategory: () => EMPTY, unlinkNodeFromCategory: () => EMPTY } - } + }, + { + provide: PredictionService, + useValue: { + getPredictions: () => EMPTY, + predictionStatusUpdated$: new Subject() + } + }, + provideMockFeatureFlags({[CONTENT_ENRICHMENT.EXPERIENCE_INSIGHT]: true}) ] }); fixture = TestBed.createComponent(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 +278,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 +296,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 +333,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 +366,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 +728,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 +738,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 +755,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 +767,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 +788,6 @@ describe('ContentMetadataComponent', () => { } ]; component.preset = presetConfig; - getGroupedPropertiesSpy.and.stub(); - component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); expect(contentMetadataService.getGroupedProperties).toHaveBeenCalledWith(expectedNode, presetConfig); @@ -786,8 +822,6 @@ describe('ContentMetadataComponent', () => { }); it('should hide card views group when the grouped properties are empty', async () => { - getGroupedPropertiesSpy.and.stub(); - component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); fixture.detectChanges(); @@ -799,8 +833,6 @@ describe('ContentMetadataComponent', () => { it('should display card views group when there is at least one property that is not empty', async () => { component.expanded = true; - getGroupedPropertiesSpy.and.stub(); - component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); fixture.detectChanges(); @@ -813,7 +845,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(); @@ -990,6 +1021,7 @@ describe('ContentMetadataComponent', () => { component.expanded = true; component.preset = 'default'; getGroupedPropertiesSpy.and.callThrough(); + getBasicPropertiesSpy.and.callThrough(); }); it('should show Versionable with given content-metadata config', async () => { @@ -1002,6 +1034,7 @@ describe('ContentMetadataComponent', () => { component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); fixture.detectChanges(); + await fixture.whenStable(); await component.groupedProperties$.toPromise(); fixture.detectChanges(); @@ -1022,6 +1055,7 @@ describe('ContentMetadataComponent', () => { component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); fixture.detectChanges(); + await fixture.whenStable(); await component.groupedProperties$.toPromise(); fixture.detectChanges(); @@ -1042,6 +1076,7 @@ describe('ContentMetadataComponent', () => { component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); fixture.detectChanges(); + await fixture.whenStable(); await component.groupedProperties$.toPromise(); fixture.detectChanges(); @@ -1063,6 +1098,7 @@ describe('ContentMetadataComponent', () => { component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); fixture.detectChanges(); + await fixture.whenStable(); await component.groupedProperties$.toPromise(); fixture.detectChanges(); @@ -1083,6 +1119,7 @@ describe('ContentMetadataComponent', () => { component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); fixture.detectChanges(); + await fixture.whenStable(); await component.groupedProperties$.toPromise(); fixture.detectChanges(); @@ -1107,6 +1144,7 @@ describe('ContentMetadataComponent', () => { component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); fixture.detectChanges(); + await fixture.whenStable(); await component.groupedProperties$.toPromise(); fixture.detectChanges(); @@ -1141,6 +1179,7 @@ describe('ContentMetadataComponent', () => { component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); fixture.detectChanges(); + await fixture.whenStable(); await component.groupedProperties$.toPromise(); fixture.detectChanges(); @@ -1204,7 +1243,7 @@ describe('ContentMetadataComponent', () => { describe('events', () => { it('should not propagate the event on left arrows press', () => { fixture.detectChanges(); - const event = { keyCode: 37, stopPropagation: () => {} }; + const event = { key: 'ArrowLeft', stopPropagation: () => {} }; spyOn(event, 'stopPropagation').and.stub(); const element = fixture.debugElement.query(By.css('adf-card-view')); element.triggerEventHandler('keydown', event); @@ -1213,7 +1252,7 @@ describe('ContentMetadataComponent', () => { it('should not propagate the event on right arrows press', () => { fixture.detectChanges(); - const event = { keyCode: 39, stopPropagation: () => {} }; + const event = { key: 'ArrowRight', stopPropagation: () => {} }; spyOn(event, 'stopPropagation').and.stub(); const element = fixture.debugElement.query(By.css('adf-card-view')); element.triggerEventHandler('keydown', event); @@ -1640,4 +1679,144 @@ 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 than 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 updatedNode = { ...node, name: 'new test name' }; + const onPredictionStatusChangedSpy = spyOn(updateService, 'onPredictionStatusChanged').and.stub(); + spyOn(nodesApiService, 'getNode').and.returnValue(of(updatedNode)); + 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 ec4f84e51e8..bf944f09dac 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 @@ -15,8 +15,8 @@ * limitations under the License. */ -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 { Component, Inject, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core'; +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,9 +24,12 @@ import { CardViewItem, CardViewModule, NotificationService, + PredictionService, TranslationService, - UpdateNotification + UpdateNotification, + CONTENT_ENRICHMENT } from '@alfresco/adf-core'; +import { FeaturesServiceToken, IFeaturesService } from '@alfresco/adf-core/feature-flags'; import { ContentMetadataService } from '../../services/content-metadata.service'; import { CardViewGroup, PresetConfig, ContentMetadataCustomPanel, ContentMetadataPanel } from '../../interfaces/content-metadata.interfaces'; import { catchError, debounceTime, map, takeUntil } from 'rxjs/operators'; @@ -89,14 +92,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 +111,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() @@ -156,6 +163,7 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { changedProperties = {}; hasMetadataChanged = false; + isContentEnrichmentFlagOn = false; assignedCategories: Category[] = []; categories: Category[] = []; categoriesManagementMode = CategoriesManagementMode.ASSIGN; @@ -176,7 +184,9 @@ 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, + @Inject(FeaturesServiceToken) private readonly featuresService: IFeaturesService ) { this.copyToClipboardAction = this.appConfig.get('content-metadata.copy-to-clipboard-action'); this.multiValueSeparator = this.appConfig.get('content-metadata.multi-value-pipe-separator') || DEFAULT_SEPARATOR; @@ -184,6 +194,12 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { } ngOnInit() { + this.featuresService.isOn$(CONTENT_ENRICHMENT.EXPERIENCE_INSIGHT).pipe( + takeUntil(this.onDestroy$) + ).subscribe((isOn) => { + this.isContentEnrichmentFlagOn = isOn; + }); + this.cardViewContentUpdateService.itemUpdated$ .pipe(debounceTime(500), takeUntil(this.onDestroy$)) .subscribe((updatedNode: UpdateNotification) => { @@ -197,6 +213,18 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { this.loadProperties(node); }); + if (this.isContentEnrichmentFlagOn && this.displayPredictions) { + this.predictionService.predictionStatusUpdated$.pipe(takeUntil(this.onDestroy$)).subscribe(({ key, previousValue }) => { + this.cardViewContentUpdateService.onPredictionStatusChanged([{ key, previousValue }]); + this.nodesApiService + .getNode(this.node.id) + .pipe(takeUntil(this.onDestroy$)) + .subscribe((node) => { + Object.assign(this.node, node); + }); + }); + } + this.loadProperties(this.node); this.verifyAllowableOperations(); @@ -384,8 +412,7 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { } keyDown(event: KeyboardEvent) { - if (event.keyCode === 37 || event.keyCode === 39) { - // ArrowLeft && ArrowRight + if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') { event.stopPropagation(); } } @@ -407,6 +434,11 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { ) .subscribe((result: any) => { if (result) { + if (this.isContentEnrichmentFlagOn && 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 +471,49 @@ 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.isContentEnrichmentFlagOn && 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 +530,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 +608,23 @@ 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/lib/content.module.ts b/lib/content-services/src/lib/content.module.ts index e4ab61c6f90..bea51a0b6de 100644 --- a/lib/content-services/src/lib/content.module.ts +++ b/lib/content-services/src/lib/content.module.ts @@ -47,6 +47,7 @@ import { NewVersionUploaderDialogComponent } from './new-version-uploader'; import { VersionCompatibilityDirective } from './version-compatibility'; import { CONTENT_UPLOAD_DIRECTIVES } from './upload'; import { TreeViewComponent } from './tree-view'; +import { provideDebugFeatureFlags, provideDummyFeatureFlags } from '@alfresco/adf-core/feature-flags'; @NgModule({ imports: [ @@ -109,7 +110,9 @@ import { TreeViewComponent } from './tree-view'; ] }) export class ContentModule { - static forRoot(): ModuleWithProviders { + static forRoot( + devTools = false + ): ModuleWithProviders { return { ngModule: ContentModule, providers: [ @@ -126,7 +129,13 @@ export class ContentModule { useFactory: contentAuthLoaderFactory, deps: [ContentAuthLoaderService], multi: true - } + }, + ...provideDummyFeatureFlags(), + ...(devTools + ? provideDebugFeatureFlags({ + storageKey: 'content-feature-flags' + }) + : []) ] }; } diff --git a/lib/content-services/src/public-api.ts b/lib/content-services/src/public-api.ts index 759af620dc9..fdab75491a5 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/api/src/lib/types.ts b/lib/core/api/src/lib/types.ts index 3df78a41746..0fd85a5cae7 100644 --- a/lib/core/api/src/lib/types.ts +++ b/lib/core/api/src/lib/types.ts @@ -17,6 +17,6 @@ export interface Dictionary { [key: string]: T; -}; +} export type Constructor = new (...args: any[]) => T; diff --git a/lib/core/feature-flags/src/lib/providers/dummy-feature-flags.provider.ts b/lib/core/feature-flags/src/lib/providers/dummy-feature-flags.provider.ts index ed333531434..dae309b706b 100644 --- a/lib/core/feature-flags/src/lib/providers/dummy-feature-flags.provider.ts +++ b/lib/core/feature-flags/src/lib/providers/dummy-feature-flags.provider.ts @@ -18,7 +18,7 @@ import { IsFlagsOverrideOn } from '../guards/is-flags-override-on.guard'; import { IsFeatureOn } from '../guards/is-feature-on.guard'; import { IsFeatureOff } from '../guards/is-feature-off.guard'; -import { FeaturesServiceToken, FlagsOverrideToken } from '../interfaces/features.interface'; +import { FeaturesServiceToken, FlagsOverrideToken, OverridableFeaturesServiceToken } from '../interfaces/features.interface'; import { DummyFeaturesService } from '../services/dummy-features.service'; /** @@ -28,7 +28,8 @@ import { DummyFeaturesService } from '../services/dummy-features.service'; */ export function provideDummyFeatureFlags() { return [ - { provide: FeaturesServiceToken, useClass: DummyFeaturesService }, + { provide: OverridableFeaturesServiceToken, useClass: DummyFeaturesService }, + { provide: FeaturesServiceToken, useExisting: OverridableFeaturesServiceToken }, { provide: FlagsOverrideToken, useValue: false }, IsFeatureOn, IsFeatureOff, diff --git a/lib/core/src/lib/app-config/app-config.loader.ts b/lib/core/src/lib/app-config/app-config.loader.ts index 1744729a593..9df72f13e88 100644 --- a/lib/core/src/lib/app-config/app-config.loader.ts +++ b/lib/core/src/lib/app-config/app-config.loader.ts @@ -45,4 +45,4 @@ export function loadAppConfig( }); }; return () => appConfigService.load(init); -}; +} 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 00000000000..ae1050ed472 --- /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 2c8700b8019..c9a663d667b 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 6ac45fcf820..c48108be561 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 016caa651b5..115347459da 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 d79c4e38e7f..3b1aee3fad8 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 - - - - -
- - - - - -
- - {{ property.default | translate }} - + + clear + -
- - - {{ propertyValue }} - cancel - - + + +
-
- - +
+ + {{ property.default | translate }} + + +
+ + + {{ propertyValue }} + cancel + + + +
+ + + + + +
+
diff --git a/lib/core/src/lib/card-view/components/card-view-dateitem/card-view-dateitem.component.scss b/lib/core/src/lib/card-view/components/card-view-dateitem/card-view-dateitem.component.scss index dd6aa7bc54f..f85b9940fca 100644 --- a/lib/core/src/lib/card-view/components/card-view-dateitem/card-view-dateitem.component.scss +++ b/lib/core/src/lib/card-view/components/card-view-dateitem/card-view-dateitem.component.scss @@ -6,7 +6,6 @@ padding-bottom: 6px; line-height: 20px; color: var(--adf-metadata-property-panel-title-color); - border-bottom: 1px solid var(--adf-metadata-property-panel-border-color); margin-top: 10px; &.adf-property-value-editable { @@ -14,8 +13,8 @@ align-items: center; justify-content: flex-end; border-radius: 6px; - border-bottom: inherit; margin-bottom: 18px; + width: 100%; } &.adf-property-readonly-value { 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 6f83a51cbb4..d214556bc43 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 0217cbc9b82..122bf2801b4 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 8aeacf38062..0e4e88991b1 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 +