Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ACS-8325] [Bulk Legal Hold] Create Bulk Actions Dropdown #3956

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions extension.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@
"description": "Element title",
"type": "string"
},
"tooltip": {
"description": "Element tooltip to display on hover",
"type": "string"
},
"description": {
"description": "Element description, used for the tooltips.",
"type": "string"
Expand Down Expand Up @@ -837,6 +841,12 @@
"items": { "$ref": "#/definitions/contentActionRef" },
"minItems": 1
},
"bulk-actions": {
"description": "Bulk actions entries",
"type": "array",
"items": { "$ref": "#/definitions/contentActionRef" },
"minItems": 1
},
"content-metadata-presets": {
"description": "Configuration for the presets for content metadata component",
"type": "array",
Expand Down
5 changes: 5 additions & 0 deletions projects/aca-content/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,11 @@
}
},
"SEARCH": {
"BULK_ACTIONS_DROPDOWN": {
"TITLE": "Bulk Actions ({{ count }} Items)",
"BULK_NOT_AVAILABLE": "Bulk Actions (Not Available)",
"BULK_NOT_AVAILABLE_TOOLTIP": "Bulk Actions cannot be used without search results"
},
"INPUT": {
"PLACEHOLDER": "Search",
"FILES": "Files",
Expand Down
2 changes: 2 additions & 0 deletions projects/aca-content/src/lib/aca-content.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import { UserMenuComponent } from './components/sidenav/user-menu/user-menu.comp
import { ContextMenuComponent } from './components/context-menu/context-menu.component';
import { MAT_DIALOG_DEFAULT_OPTIONS } from '@angular/material/dialog';
import { SearchResultsRowComponent } from './components/search/search-results-row/search-results-row.component';
import { BulkActionsDropdownComponent } from './components/bulk-actions-dropdown/bulk-actions-dropdown.component';

@NgModule({
imports: [
Expand Down Expand Up @@ -138,6 +139,7 @@ export class ContentServiceExtensionModule {
'app.toolbar.toggleFavoriteLibrary': ToggleFavoriteLibraryComponent,
'app.toolbar.toggleJoinLibrary': ToggleJoinLibraryButtonComponent,
'app.menu.toggleJoinLibrary': ToggleJoinLibraryMenuComponent,
'app.bulk-actions-dropdown': BulkActionsDropdownComponent,
'app.shared-link.toggleSharedLink': ToggleSharedComponent,
'app.columns.name': CustomNameColumnComponent,
'app.columns.libraryName': LibraryNameColumnComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<mat-form-field
*ngIf="items?.length"
[title]="tooltip"
appearance="outline"
class="aca-bulk-actions-form-field"
data-automation-id="aca-bulk-actions-form-field"
>
<mat-select
[formControl]="disableControl"
[placeholder]="placeholder"
panelClass="aca-bulk-actions-select"
disableOptionCentering
data-automation-id="aca-bulk-actions-dropdown"
>
<mat-option
*ngFor="let option of items"
[value]="option.id"
[title]="option.tooltip | translate"
[attr.data-automation-id]="option.id"
>
<adf-icon
*ngIf="option.icon"
[title]="option.title | translate"
[value]="option.icon"
[attr.data-automation-id]="'aca-bulk-action-icon-' + option.id"
></adf-icon>
{{ option.title | translate }}
</mat-option>
</mat-select>
</mat-form-field>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* stylelint-disable selector-class-pattern */

// TODO: ACS-8458 Review css selectors

.aca-bulk-actions-select {
margin-top: 31px;
}

.aca-bulk-actions-form-field {
margin-left: 24px;
margin-top: 1.4375em;
width: 295px;

.mat-form-field-flex,
Copy link
Contributor

@swapnil-verma-gl swapnil-verma-gl Jul 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As part of the angular migration, we also added a mat-selectors file, that contains all angular material selectors. The plan is to use the references from that file, instead of directly referring to .mat selectors in our css (I assume that is what @DaryaBalvanovich meant when they said 'will be removed after Angular migration).

However, I am wondering how we will keep track of all the material selectors that were added/are being added after the angular migration was started (such as this one). Maybe we can add TODO statements, and then do a project wide search after the ng migration merge. Or we can keep references in a sepearate single file.
@MichalKinas @DenysVuika what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, fortunately ADF should be merged today and ACA will be next so this PR will be merged after migration one

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added TODO and created story for it that will be done in the end of Legal Hold development ACS-8458

.mat-form-field-infix {
height: 48px;
line-height: 48px;
display: flex;
align-items: center;
border: 0;
}

.mat-form-field-appearance-outline .mat-form-field-outline {
top: 0;
}

.mat-select-arrow-wrapper {
margin-top: 10px;
}

.mat-select {
margin-top: 0.25rem;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*!
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BulkActionsDropdownComponent } from './bulk-actions-dropdown.component';
import { Store } from '@ngrx/store';
import { AppStore } from '@alfresco/aca-shared/store';
import { BehaviorSubject, of } from 'rxjs';
import { By } from '@angular/platform-browser';
import { ContentActionRef, ContentActionType } from '@alfresco/adf-extensions';
import { AppTestingModule } from '../../testing/app-testing.module';
import { TranslationService } from '@alfresco/adf-core';

describe('BulkActionsDropdownComponent', () => {
let component: BulkActionsDropdownComponent;
let fixture: ComponentFixture<BulkActionsDropdownComponent>;
let store: Store<AppStore>;
let translationService: TranslationService;
let bulkFormField: HTMLElement;
let dropdown: HTMLElement;

const mockItem: ContentActionRef = {
id: 'mockId',
title: 'some title',
tooltip: 'some tooltip',
icon: 'adf:mock-icon',
type: ContentActionType.custom,
rules: {
visible: 'isItemVisible'
}
};

const totalItemsMock$: BehaviorSubject<number> = new BehaviorSubject(0);

const getElement = (selector: string): HTMLElement | null => fixture.debugElement.query(By.css(`[data-automation-id="${selector}"]`)).nativeElement;

const getLabelText = (selector: string): string => getElement(selector).textContent.trim();

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [BulkActionsDropdownComponent, AppTestingModule]
}).compileComponents();

store = TestBed.inject(Store);
translationService = TestBed.inject(TranslationService);

spyOn(store, 'select').and.returnValue(totalItemsMock$);
spyOn(translationService, 'get').and.callFake((key) => of(key));

fixture = TestBed.createComponent(BulkActionsDropdownComponent);
swapnil-verma-gl marked this conversation as resolved.
Show resolved Hide resolved
component = fixture.componentInstance;

component.items = [mockItem];
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

describe('when there are no search items', () => {
beforeEach(() => {
totalItemsMock$.next(0);
fixture.detectChanges();
dropdown = getElement('aca-bulk-actions-dropdown');
bulkFormField = getElement('aca-bulk-actions-form-field');
fixture.detectChanges();
});

it('should disable dropdown', () => {
expect(dropdown.getAttribute('aria-disabled')).toBe('true');
});

it('should have correct tooltip', () => {
expect(bulkFormField.getAttribute('title')).toBe('SEARCH.BULK_ACTIONS_DROPDOWN.BULK_NOT_AVAILABLE_TOOLTIP');
});

it('should have correct placeholder', () => {
expect(getLabelText('aca-bulk-actions-dropdown')).toEqual('SEARCH.BULK_ACTIONS_DROPDOWN.BULK_NOT_AVAILABLE');
});

it('should call translationService.get with correct arguments', () => {
expect(translationService.get).toHaveBeenCalledWith('SEARCH.BULK_ACTIONS_DROPDOWN.BULK_NOT_AVAILABLE');
expect(translationService.get).toHaveBeenCalledWith('SEARCH.BULK_ACTIONS_DROPDOWN.BULK_NOT_AVAILABLE_TOOLTIP');
});
});

describe('when there are search items', () => {
beforeEach(() => {
totalItemsMock$.next(10);
fixture.detectChanges();
dropdown = getElement('aca-bulk-actions-dropdown');
bulkFormField = getElement('aca-bulk-actions-form-field');
dropdown.click();
fixture.detectChanges();
});

it('should enable dropdown', () => {
expect(dropdown.getAttribute('aria-disabled')).toBe('false');
});

it('should have correct tooltip', () => {
expect(bulkFormField.getAttribute('title')).toBe('SEARCH.BULK_ACTIONS_DROPDOWN.TITLE');
});

it('should have correct placeholder', () => {
expect(getLabelText('aca-bulk-actions-dropdown')).toEqual('SEARCH.BULK_ACTIONS_DROPDOWN.TITLE');
});

it('should have option with correct tooltip', () => {
const option = getElement('mockId');

expect(option.getAttribute('title')).toEqual('some tooltip');
});

it('should have option with correct label', () => {
const optionLabel = getLabelText('mockId');

expect(optionLabel).toEqual('some title');
});

it('should have correct icon in an option', () => {
const icon = getElement('aca-bulk-action-icon-mockId');

expect(icon.getAttribute('title')).toEqual('some title');
});

it('should call translationService.get with correct arguments', () => {
expect(translationService.get).toHaveBeenCalledWith('SEARCH.BULK_ACTIONS_DROPDOWN.TITLE', { count: 10 });
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*!
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/

import { ContentActionRef } from '@alfresco/adf-extensions';
import { AppStore, getSearchItemsTotalCount } from '@alfresco/aca-shared/store';
import { CommonModule } from '@angular/common';
import { Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { MatSelectModule } from '@angular/material/select';
import { Store } from '@ngrx/store';
import { TranslateModule } from '@ngx-translate/core';
import { combineLatest, Observable, Subject } from 'rxjs';
import { IconComponent, TranslationService } from '@alfresco/adf-core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { switchMap, takeUntil } from 'rxjs/operators';

@Component({
standalone: true,
selector: 'aca-bulk-actions-dropdown',
templateUrl: './bulk-actions-dropdown.component.html',
styleUrls: ['./bulk-actions-dropdown.component.scss'],
imports: [CommonModule, TranslateModule, MatSelectModule, IconComponent, ReactiveFormsModule],
encapsulation: ViewEncapsulation.None
})
export class BulkActionsDropdownComponent implements OnInit, OnDestroy {
@Input() items: ContentActionRef[];

placeholder: string;
tooltip: string;
disableControl = new FormControl();

private readonly totalItems$: Observable<number> = this.store.select(getSearchItemsTotalCount);
private readonly onDestroy$ = new Subject();

constructor(private store: Store<AppStore>, private translationService: TranslationService) {}

ngOnInit() {
this.totalItems$
.pipe(
switchMap((totalItems) => {
if (totalItems > 0) {
this.disableControl.enable();

return combineLatest([
this.translationService.get('SEARCH.BULK_ACTIONS_DROPDOWN.TITLE', { count: totalItems }),
this.translationService.get('SEARCH.BULK_ACTIONS_DROPDOWN.TITLE', { count: totalItems })
]);
} else {
this.disableControl.disable();

return combineLatest([
this.translationService.get('SEARCH.BULK_ACTIONS_DROPDOWN.BULK_NOT_AVAILABLE'),
this.translationService.get('SEARCH.BULK_ACTIONS_DROPDOWN.BULK_NOT_AVAILABLE_TOOLTIP')
]);
}
}),
takeUntil(this.onDestroy$)
)
.subscribe(([placeholder, title]) => {
this.tooltip = title;
this.placeholder = placeholder;
});
}

ngOnDestroy() {
this.onDestroy$.next();
this.onDestroy$.complete();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<aca-page-layout>
<div class="aca-page-layout-header">
<aca-search-input></aca-search-input>
<aca-bulk-actions-dropdown *ngIf="bulkActions" [items]="bulkActions"></aca-bulk-actions-dropdown>
<div class="aca-search-toolbar-spacer"></div>
<aca-toolbar [items]="actions"></aca-toolbar>
</div>
Expand Down
Loading