From f3d41056d665bfe3b20e5332deea250e23fd92bc Mon Sep 17 00:00:00 2001 From: Yasa-Nataliya Date: Fri, 1 Sep 2023 00:13:08 +0530 Subject: [PATCH] [ACS-5645]Implemented changes as per the review comments --- .../content-metadata/button-type.enum.ts | 24 ++ .../content-metadata.component.html | 34 ++- .../content-metadata.component.spec.ts | 258 ++++++------------ .../content-metadata.component.ts | 103 ++++--- .../interfaces/card-view-group.interface.ts | 2 +- lib/content-services/src/lib/i18n/en.json | 3 +- .../tags-creator/tags-creator.component.html | 1 - 7 files changed, 182 insertions(+), 243 deletions(-) create mode 100644 lib/content-services/src/lib/content-metadata/components/content-metadata/button-type.enum.ts diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata/button-type.enum.ts b/lib/content-services/src/lib/content-metadata/components/content-metadata/button-type.enum.ts new file mode 100644 index 0000000000..9e2275a42e --- /dev/null +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata/button-type.enum.ts @@ -0,0 +1,24 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// eslint-disable-next-line no-shadow +export enum ButtonType { + GeneralInfo = 'generalInfo', + Tags = 'tags', + Categories = 'categories', + Group = 'group' +} diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.html b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.html index 1c52ec488a..6e1341c964 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.html +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.html @@ -16,7 +16,7 @@ {{ 'CORE.METADATA.BASIC.HEADER' | translate }} -
+
+ {{ 'METADATA.BASIC.NO_ITEMS_MESSAGE' | translate: { groupTitle: group.title } }} +
{ let component: ContentMetadataComponent; @@ -49,7 +51,6 @@ describe('ContentMetadataComponent', () => { let folderNode: Node; let tagService: TagService; let categoryService: CategoryService; - let classesApi: ClassesApi; let getClassSpy: jasmine.Spy; const preset = 'custom-preset'; @@ -90,12 +91,12 @@ describe('ContentMetadataComponent', () => { const clickOnGeneralInfoSave = () => { findSaveGeneralInfoButton().click(); fixture.detectChanges(); - } + }; const clickOnTagsSave = () => { findSaveTagsButton().click(); fixture.detectChanges(); - } + }; const findTagsCreator = (): TagsCreatorComponent => fixture.debugElement.query(By.directive(TagsCreatorComponent))?.componentInstance; @@ -144,9 +145,8 @@ describe('ContentMetadataComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - const buttonType = 'group'; const event = new MouseEvent('click'); - component.saveGroupChanges(buttonType, event); + component.saveChanges(ButtonType.Group, event); await fixture.whenStable(); } @@ -186,7 +186,7 @@ describe('ContentMetadataComponent', () => { tagService = TestBed.inject(TagService); categoryService = TestBed.inject(CategoryService); const propertyDescriptorsService = TestBed.inject(PropertyDescriptorsService); - classesApi = propertyDescriptorsService['classesApi']; + const classesApi = propertyDescriptorsService['classesApi']; node = { id: 'node-id', @@ -285,7 +285,7 @@ describe('ContentMetadataComponent', () => { })); it('should call removeTag and assignTagsToNode on TagService on save click', fakeAsync(() => { - component.editable = true; + component.editableTags = true; component.displayTags = true; const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel; const expectedNode = { ...node, name: 'some-modified-value' }; @@ -297,21 +297,20 @@ describe('ContentMetadataComponent', () => { spyOn(tagService, 'assignTagsToNode').and.returnValue(EMPTY); const tagName1 = tagPaging.list.entries[0].entry.tag; const tagName2 = 'New tag 3'; - updateService.update(property, 'updated-value'); - fixture.detectChanges(); findTagsCreator().tagsChange.emit([tagName1, tagName2]); - const mockEvent = new Event('click'); - component.saveChanges(mockEvent); - + fixture.detectChanges(); + tick(600); + clickOnTagsSave(); + tick(100); const tag1 = new TagBody(); tag1.tag = tagName1; const tag2 = new TagBody(); tag2.tag = tagName2; expect(tagService.removeTag).toHaveBeenCalledWith(node.id, tagPaging.list.entries[1].entry.id); expect(tagService.assignTagsToNode).toHaveBeenCalledWith(node.id, [tag1, tag2]); - })); + })); it('should call getTagsByNodeId on TagService on save click', () => { component.editableTags = true; @@ -330,8 +329,8 @@ describe('ContentMetadataComponent', () => { fixture.detectChanges(); findTagsCreator().tagsChange.emit([tagPaging.list.entries[0].entry.tag, 'New tag 3']); getTagsByNodeIdSpy.calls.reset(); - const mockEvent = new Event('click'); - component.saveChanges(mockEvent); + const mockEvent = new MouseEvent('click'); + component.saveChanges(ButtonType.Tags, mockEvent); expect(tagService.getTagsByNodeId).toHaveBeenCalledWith(node.id); }); @@ -433,171 +432,86 @@ describe('ContentMetadataComponent', () => { })); }); - describe('saveEditChanges', () => { - it('should save general info changes and toggle editable flag', () => { - const event = new Event('click'); - const buttonType = 'generalInfo'; - component.editable = true; - - spyOn(component, 'saveChanges'); - component.saveEditChanges(buttonType, event); - - expect(component.saveChanges).toHaveBeenCalledWith(event); - expect(component.editable).toBe(false); - }); - - it('should save tags changes and toggle editableTags flag', () => { - const event = new Event('click'); - const buttonType = 'tags'; - component.editableTags = true; - - spyOn(component, 'saveChanges'); - component.saveEditChanges(buttonType, event); - - expect(component.saveChanges).toHaveBeenCalledWith(event); + describe('editable', () => { + it('should toggle general editable', () => { + const eventMock = new MouseEvent('click'); + component.editable = false; + component.toggleGeneralEdit(eventMock); + expect(component.editable).toBe(true); expect(component.editableTags).toBe(false); - }); - - it('should save categories changes and toggle editableCategories flag', () => { - const event = new Event('click'); - const buttonType = 'categories'; - component.editableCategories = true; - - spyOn(component, 'saveChanges'); - component.saveEditChanges(buttonType, event); - - expect(component.saveChanges).toHaveBeenCalledWith(event); expect(component.editableCategories).toBe(false); }); - - it('should save group changes and toggle editable flag', () => { - const group = { editable: true }; - const event = new Event('click'); - const buttonType = 'group'; - - spyOn(component, 'saveChanges'); - component.saveEditChanges(buttonType, event, group); - - expect(component.saveChanges).toHaveBeenCalledWith(event); - expect(group.editable).toBe(false); - }); - }); - - describe('cancelChanges', () => { - it('should cancel group changes and set group editable to false', () => { - const group = { editable: true }; - const event = new Event('click'); - const buttonType = 'group'; - - spyOn(component, 'cancelChanges'); - component.cancelEditChanges(buttonType, event, group); - - expect(component.cancelChanges).toHaveBeenCalledWith(event); - expect(group.editable).toBe(false); - }); - - it('should cancel general info changes and toggle editable flag', () => { - const event = new Event('click'); - const buttonType = 'generalInfo'; - component.editable = true; - - spyOn(component, 'cancelChanges'); - component.cancelEditChanges(buttonType, event); - - expect(component.cancelChanges).toHaveBeenCalledWith(event); + + it('should toggle tags editable', () => { + const eventMock = new MouseEvent('click'); + component.editableTags = false; + component.toggleTagsEdit(eventMock); + expect(component.editableTags).toBe(true); + expect(component.tagNameControlVisible).toBe(true); + expect(component.tagsPanelState).toBe(true); expect(component.editable).toBe(false); - }); - - it('should cancel tags changes and toggle editableTags flag', () => { - const event = new Event('click'); - const buttonType = 'tags'; - component.editableTags = true; - - spyOn(component, 'cancelChanges'); - component.cancelEditChanges(buttonType, event); - - expect(component.cancelChanges).toHaveBeenCalledWith(event); - expect(component.editableTags).toBe(false); - }); - - it('should cancel categories changes and toggle editableCategories flag', () => { - const event = new Event('click'); - const buttonType = 'categories'; - component.editableCategories = true; - - spyOn(component, 'cancelChanges'); - component.cancelEditChanges(buttonType, event); - - expect(component.cancelChanges).toHaveBeenCalledWith(event); expect(component.editableCategories).toBe(false); }); - }); - - describe('editing', () => { - it('should toggle categories edit and set categoriesPanelState accordingly', () => { - const event = new Event('click'); - spyOn(event, 'stopPropagation'); + it('should toggle categories editable', () => { + const eventMock = new MouseEvent('click'); component.editableCategories = false; - component.categoriesPanelState = false; - component.toggleCategoriesEdit(event); - expect(event.stopPropagation).toHaveBeenCalled(); + component.toggleCategoriesEdit(eventMock); expect(component.editableCategories).toBe(true); + expect(component.categoryControlVisible).toBe(true); expect(component.categoriesPanelState).toBe(true); - component.toggleCategoriesEdit(event); - expect(component.editableCategories).toBe(false); - expect(component.categoriesPanelState).toBe(false); - }); + expect(component.editable).toBe(false); + expect(component.editableTags).toBe(false); + }); - it('should toggle group edit and expand the panel if editable', () => { - const event = new Event('click'); - spyOn(event, 'stopPropagation'); - const group = { editable: false, expanded: false }; - component.toggleEdit(group, event); - expect(event.stopPropagation).toHaveBeenCalled(); + it('should toggle group editable', () => { + const eventMock = new MouseEvent('click'); + const group: CardViewGroup = { + editable: false, expanded: false, + title: '', + properties: [] + }; + component.editableGroup = null; + component.toggleEdit(eventMock, group); expect(group.editable).toBe(true); expect(group.expanded).toBe(true); - }); - - it('should toggle group edit but not expand the panel if not editable', () => { - const event = new Event('click'); - spyOn(event, 'stopPropagation'); - const group = { editable: true, expanded: true }; - component.toggleEdit(group, event); - expect(event.stopPropagation).toHaveBeenCalled(); - expect(group.editable).toBe(false); - expect(group.expanded).toBe(true); - }); - - - it('should toggle general info edit and set generalInfoPanelState accordingly', () => { - const event = new Event('click'); - spyOn(event, 'stopPropagation'); - component.generalInfoPanelState = true; - component.editable = false; - component.toggleGeneralEdit(event); - expect(event.stopPropagation).toHaveBeenCalled(); - expect(component.editable).toBe(true); - expect(component.generalInfoPanelState).toBe(true); - component.toggleGeneralEdit(event); + expect(component.editableGroup).toBe(group); expect(component.editable).toBe(false); - expect(component.generalInfoPanelState).toBe(true); + expect(component.editableTags).toBe(false); + expect(component.editableCategories).toBe(false); + }); + }); + + describe('toggleEditMode', () => { + it('should toggle general editable', () => { + component.editable = false; + component.toggleEditMode(ButtonType.GeneralInfo); + expect(component.editable).toBe(true); + }); + + it('should toggle tags editable', () => { + component.editableTags = false; + component.toggleEditMode(ButtonType.Tags); + expect(component.editableTags).toBe(true); }); - it('should toggle tags edit and set tagsPanelState accordingly', () => { - const event = new Event('click'); - spyOn(event, 'stopPropagation'); - component.editableTags = false; - component.tagsPanelState = false; - component.toggleTagsEdit(event); - expect(event.stopPropagation).toHaveBeenCalled(); - expect(component.editableTags).toBe(true); - expect(component.tagsPanelState).toBe(true); - component.toggleTagsEdit(event); - expect(component.editableTags).toBe(false); - expect(component.tagsPanelState).toBe(false); + it('should toggle categories editable', () => { + component.editableCategories = false; + component.toggleEditMode(ButtonType.Categories); + expect(component.editableCategories).toBe(true); }); - }) + + it('should toggle group editable', () => { + const group: CardViewGroup = { + editable: false, expanded: false, + title: '', + properties: [] + }; + component.editableGroup = null; + component.toggleEditMode(ButtonType.Group, group); + expect(group.editable).toBe(true); + }); + }); describe('Reseting', () => { it('should reset properties on reset click', async () => { @@ -791,6 +705,7 @@ describe('ContentMetadataComponent', () => { describe('Display properties with aspect oriented config', () => { let appConfig: AppConfigService; + let classesApi: ClassesApi; let expectedNode: Node; const verResponse: PropertyGroup = { @@ -1285,14 +1200,6 @@ describe('ContentMetadataComponent', () => { expect(tagsCreator.disabledTagsRemoving).toBeFalse(); }); - it('should have assigned true to disabledTagsRemoving after clicking on update button', () => { - tagsCreator.tagsChange.emit([]); - fixture.detectChanges(); - - clickOnTagsSave(); - expect(tagsCreator.disabledTagsRemoving).toBeTrue(); - }); - it('should have assigned false to disabledTagsRemoving if forkJoin fails', () => { const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel; const expectedNode = { ...node, name: 'some-modified-value' }; @@ -1314,15 +1221,6 @@ describe('ContentMetadataComponent', () => { expect(tagsCreator.disabledTagsRemoving).toBeFalse(); }); - it('should have assigned false to tagNameControlVisible after clicking on update button', () => { - tagsCreator.tagNameControlVisibleChange.emit(true); - tagsCreator.tagsChange.emit([]); - fixture.detectChanges(); - - clickOnTagsSave(); - expect(tagsCreator.tagNameControlVisible).toBeFalse(); - }); - describe('Setting tags', () => { let tagPaging: TagPaging; 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 7f2b9fffc7..2da5fab8d4 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,7 +15,7 @@ * limitations under the License. */ -import { ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core'; import { Category, CategoryEntry, @@ -46,6 +46,7 @@ import { CategoryService } from '../../../category/services/category.service'; import { CategoriesManagementMode } from '../../../category/categories-management/categories-management-mode'; import { MatExpansionPanel } from '@angular/material/expansion'; import { AllowableOperationsEnum, ContentService } from '../../../common'; +import { ButtonType } from './button-type.enum'; const DEFAULT_SEPARATOR = ', '; @@ -57,7 +58,8 @@ const DEFAULT_SEPARATOR = ', '; encapsulation: ViewEncapsulation.None }) export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { - @ViewChild(MatExpansionPanel) panel: MatExpansionPanel; + @ViewChild(MatExpansionPanel) + panel: MatExpansionPanel; protected onDestroy$ = new Subject(); /** (required) The node entity to fetch metadata about */ @@ -129,6 +131,10 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { @Input() readOnly = false; + /** Emitted when content's editable state is changed. **/ + @Output() + editableChange = new EventEmitter(); + private _assignedTags: string[] = []; private assignedTagsEntries: TagEntry[] = []; private _editable = false; @@ -152,9 +158,13 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { classifiableChanged = this.classifiableChangedSubject.asObservable(); generalInfoPanelState: boolean; tagsPanelState: boolean; - editableTags: boolean = false; + editableTags = false; categoriesPanelState: boolean; - editableCategories: boolean = false; + editableCategories = false; + hasAllowableOperations = false; + editableGroup: any; + buttonType = ButtonType; + group: CardViewGroup; constructor( private contentMetadataService: ContentMetadataService, @@ -192,6 +202,7 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { .subscribe((node) => this.loadProperties(node)); this.loadProperties(this.node); + this.hasAllowableOperations = this.contentService.hasAllowableOperations(this.node, AllowableOperationsEnum.UPDATE); } get assignedTags(): string[] { @@ -260,8 +271,9 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { * Called after clicking save button. It confirms all changes done for metadata and hides both category and tag name controls. * Before clicking on that button they are not saved. */ - saveChanges(event: Event) { + saveChanges(buttonType: ButtonType, event: MouseEvent, group?: CardViewGroup) { event.stopPropagation(); + this.toggleEditMode(buttonType, group); this._saving = true; this.tagNameControlVisible = false; this.categoryControlVisible = false; @@ -274,19 +286,18 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { } } - saveEditChanges(buttonType: string, event: Event, group?: any) { - this.saveChanges(event); + toggleEditMode(buttonType: ButtonType, group?: CardViewGroup) { switch (buttonType) { - case 'generalInfo': + case ButtonType.GeneralInfo: this.editable = !this.editable; break; - case 'tags': + case ButtonType.Tags: this.editableTags = !this.editableTags; break; - case 'categories': + case ButtonType.Categories: this.editableCategories = !this.editableCategories; break; - case 'group': + case ButtonType.Group: if (group) { group.editable = !group.editable; } @@ -325,69 +336,65 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { this.categoryControlVisible = false; } - cancelChanges(event: Event) { + cancelChanges(buttonType: ButtonType, event: MouseEvent) { event.stopPropagation(); + this.toggleEditMode(buttonType); this.revertChanges(); this.loadProperties(this.node); } - cancelEditChanges(buttonType: string, event: Event, group?: any) { - this.cancelChanges(event); - switch (buttonType) { - case 'generalInfo': - this.editable = !this.editable; - break; - case 'tags': - this.editableTags = !this.editableTags; - break; - case 'categories': - this.editableCategories = !this.editableCategories; - break; - case 'group': - if (group) { - group.editable = !group.editable; - } - break; - default: - break; - } + cancelEditChanges() { + this.revertChanges(); + this.loadProperties(this.node); } - toggleGeneralEdit(event: Event): void { + toggleGeneralEdit(event: MouseEvent): void { event.stopPropagation(); this.editable = !this.editable; + this.cancelEditChanges(); if (this.editable) { this.panel.open(); + this.editableTags = false; + this.editableCategories = false; } } - toggleTagsEdit(event: Event): void { + toggleTagsEdit(event: MouseEvent): void { event.stopPropagation(); this.editableTags = !this.editableTags; + this.cancelEditChanges(); this.tagNameControlVisible = true; if (this.editableTags) { this.tagsPanelState = true; - } else { - this.tagsPanelState = false; + this.editable = false; + this.editableCategories = false; } } - toggleCategoriesEdit(event: Event): void { + toggleCategoriesEdit(event: MouseEvent): void { event.stopPropagation(); + this.cancelEditChanges(); this.editableCategories = !this.editableCategories; this.categoryControlVisible = true; if (this.editableCategories) { this.categoriesPanelState = true; - } else { - this.categoriesPanelState = false; + this.editable = false; + this.editableTags = false; } } - toggleEdit(group: any, event: Event): void { + toggleEdit(event: MouseEvent, group: CardViewGroup): void { event.stopPropagation(); + if (this.editableGroup && this.editableGroup !== group) { + this.editableGroup.editable = false; + } group.editable = !group.editable; - if(group.editable) { + this.editableGroup = group.editable ? group : null; + if (group.editable) { group.expanded = true; + this.editable = false; + this.editableTags = false; + this.editableCategories = false; } } @@ -406,6 +413,12 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { this.cdr.detectChanges(); } + showGroup(group: CardViewGroup): boolean { + const properties = group.properties.filter((property) => !this.isEmpty(property.displayValue)); + + return properties.length > 0; + } + canExpandTheCard(group: CardViewGroup): boolean { return group.title === this.displayAspect; } @@ -414,10 +427,6 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { return !this.expanded || this.displayAspect === 'Properties'; } - hasAllowableOperations() { - return this.contentService.hasAllowableOperations(this.node, AllowableOperationsEnum.UPDATE); - } - keyDown(event: KeyboardEvent) { if (event.keyCode === 37 || event.keyCode === 39) { // ArrowLeft && ArrowRight event.stopPropagation(); @@ -495,6 +504,10 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { })); } + private isEmpty(value: any): boolean { + return value === undefined || value === null || value === ''; + } + private loadCategoriesForNode(nodeId: string) { this.assignedCategories = []; this.categoryService.getCategoryLinksForNode(nodeId).subscribe((categoryPaging) => { diff --git a/lib/content-services/src/lib/content-metadata/interfaces/card-view-group.interface.ts b/lib/content-services/src/lib/content-metadata/interfaces/card-view-group.interface.ts index c85baa54b1..66eb685d78 100644 --- a/lib/content-services/src/lib/content-metadata/interfaces/card-view-group.interface.ts +++ b/lib/content-services/src/lib/content-metadata/interfaces/card-view-group.interface.ts @@ -21,5 +21,5 @@ export interface CardViewGroup { title: string; properties: CardViewItem[]; editable: boolean; - expanded: boolean + expanded: boolean; } diff --git a/lib/content-services/src/lib/i18n/en.json b/lib/content-services/src/lib/i18n/en.json index a2fc9ed566..bb26077359 100644 --- a/lib/content-services/src/lib/i18n/en.json +++ b/lib/content-services/src/lib/i18n/en.json @@ -488,7 +488,8 @@ "TAGS": "Tags", "ADD_TAG_TOOLTIP": "Add tag", "HEADER_TITLE": "General info", - "NO_TAGS_ADDED": "There are currently no tags added" + "NO_TAGS_ADDED": "There are currently no tags added", + "NO_ITEMS_MESSAGE": "There are currently no {{ groupTitle }} added" }, "CONTENT_TYPE": { "DIALOG" :{ diff --git a/lib/content-services/src/lib/tag/tags-creator/tags-creator.component.html b/lib/content-services/src/lib/tag/tags-creator/tags-creator.component.html index 8e2e528a5b..ea18f7f1c5 100644 --- a/lib/content-services/src/lib/tag/tags-creator/tags-creator.component.html +++ b/lib/content-services/src/lib/tag/tags-creator/tags-creator.component.html @@ -6,7 +6,6 @@ autocomplete="off" [formControl]="tagNameControl" (keyup.enter)="addTag()" - aria-labelledby="adf-tag-name-input-label" adf-auto-focus placeholder="{{'TAG.TAGS_CREATOR.INPUT_PLACEHOLDER' | translate}}" />