Skip to content

Commit

Permalink
ADW Saved Search
Browse files Browse the repository at this point in the history
  • Loading branch information
dominikiwanekhyland committed Oct 10, 2024
1 parent ab0ef88 commit ba75b71
Show file tree
Hide file tree
Showing 18 changed files with 668 additions and 39 deletions.
9 changes: 9 additions & 0 deletions projects/aca-content/assets/app.extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,15 @@
]
}
]
},
{
"id": "app.navbar.secondary",
"items": [
{
"id": "app.search.navbar",
"component": "app.search.navbar"
}
]
}
],
"toolbar": [
Expand Down
15 changes: 15 additions & 0 deletions projects/aca-content/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,21 @@
"ADVANCED_FILTERS": "Advanced Filters:",
"RESET": "Reset",
"RESET_ACTION": "Reset search filters",
"SAVE_SEARCH": {
"ACTION_BUTTON": "Save Search",
"MODAL_HEADER": "Save this search",
"NAME_LABEL": "Name",
"NAME_REQUIRED_ERROR": "This field is required",
"DESCRIPTION_LABEL": "Description",
"CANCEL_BUTTON": "Cancel",
"SAVE_BUTTON": "Save",
"SAVE_SUCCESS": "Search Saved",
"SAVE_ERROR": "Error occured. Search could not be saved.",
"NAVBAR": {
"TITLE": "Saved Searches",
"MANAGE_BUTTON": "Manage Searches"
}
},
"FOUND_RESULTS": "{{ number }} results found",
"FOUND_ONE_RESULT": "{{ number }} result found",
"CUSTOM_ROW": {
Expand Down
8 changes: 5 additions & 3 deletions projects/aca-content/src/lib/aca-content.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import { HammerModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TRANSLATION_PROVIDER, AuthGuardEcm, LanguagePickerComponent, NotificationHistoryComponent } from '@alfresco/adf-core';
import { AuthGuardEcm, LanguagePickerComponent, NotificationHistoryComponent, TRANSLATION_PROVIDER } from '@alfresco/adf-core';
import {
ContentModule,
ContentVersionService,
Expand Down Expand Up @@ -71,13 +71,14 @@ import { TagsColumnComponent } from './components/dl-custom-components/tags-colu
import { UserInfoComponent } from './components/common/user-info/user-info.component';
import { SidenavComponent } from './components/sidenav/sidenav.component';
import { ContentManagementService } from './services/content-management.service';
import { ShellLayoutComponent, SHELL_NAVBAR_MIN_WIDTH } from '@alfresco/adf-core/shell';
import { SHELL_NAVBAR_MIN_WIDTH, ShellLayoutComponent } from '@alfresco/adf-core/shell';
import { UserMenuComponent } from './components/sidenav/user-menu/user-menu.component';
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';
import { AgentsButtonComponent } from './components/knowledge-retrieval/search-ai/agents-button/agents-button.component';
import { SaveSearchSidenavComponent } from './components/search/search-save/sidenav/save-search-sidenav.component';

@NgModule({
imports: [
Expand Down Expand Up @@ -157,7 +158,8 @@ export class ContentServiceExtensionModule {
'app.user': UserInfoComponent,
'app.notification-center': NotificationHistoryComponent,
'app.user.menu': UserMenuComponent,
'app.search.columns.name': SearchResultsRowComponent
'app.search.columns.name': SearchResultsRowComponent,
'app.search.navbar': SaveSearchSidenavComponent
});

extensions.setEvaluators({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,26 @@
<div class="aca-content__advanced-filters">
<div class="aca-content__advanced-filters--header">
<p>{{ 'APP.BROWSE.SEARCH.ADVANCED_FILTERS' | translate }}</p>
<button
mat-button
adf-reset-search
class="aca-content__reset-action"
title="{{ 'APP.BROWSE.SEARCH.RESET_ACTION' | translate }}"
[attr.aria-label]="'APP.BROWSE.SEARCH.RESET_ACTION' | translate ">
{{ 'APP.BROWSE.SEARCH.RESET' | translate }}
</button>
<div class="aca-content__advanced-filters--header--action-buttons">
<button
mat-button
acaSaveSearch
[acaSaveSearchQuery]="encodedQuery"
[disabled]="!encodedQuery"
class="aca-content__save-search-action"
title="{{ 'APP.BROWSE.SEARCH.SAVE_SEARCH.ACTION_BUTTON' | translate }}"
[attr.aria-label]="'APP.BROWSE.SEARCH.SAVE_SEARCH.ACTION_BUTTON' | translate ">
{{ 'APP.BROWSE.SEARCH.SAVE_SEARCH.ACTION_BUTTON' | translate }}
</button>
<button
mat-button
adf-reset-search
class="aca-content__reset-action"
title="{{ 'APP.BROWSE.SEARCH.RESET_ACTION' | translate }}"
[attr.aria-label]="'APP.BROWSE.SEARCH.RESET_ACTION' | translate ">
{{ 'APP.BROWSE.SEARCH.RESET' | translate }}
</button>
</div>
</div>
<adf-search-filter-chips></adf-search-filter-chips>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,15 @@ aca-search-results {
&--header {
display: flex;
justify-content: space-between;

&--action-buttons {
display: flex;
}
}
}

&__reset-action {
&__reset-action,
&__save-search-action {
margin-top: 5px;
margin-right: 2px;
line-height: 33px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/

import { Component, inject, OnInit, ViewEncapsulation } from '@angular/core';
import { ChangeDetectorRef, Component, inject, OnInit, ViewEncapsulation } from '@angular/core';
import { NodeEntry, Pagination, ResultSetPaging } from '@alfresco/js-api';
import { ActivatedRoute, Params } from '@angular/router';
import {
Expand All @@ -39,8 +39,8 @@ import {
NavigateToFolder,
SetInfoDrawerPreviewStateAction,
SetInfoDrawerStateAction,
ShowInfoDrawerPreviewAction,
SetSearchItemsTotalCountAction
SetSearchItemsTotalCountAction,
ShowInfoDrawerPreviewAction
} from '@alfresco/aca-shared/store';
import {
CustomEmptyContentTemplateDirective,
Expand Down Expand Up @@ -83,6 +83,7 @@ import {
extractUserQueryFromEncodedQuery,
formatSearchTerm
} from '../../../utils/aca-search-utils';
import { SaveSearchDirective } from '../search-save/directive/save-search.directive';

@Component({
standalone: true,
Expand Down Expand Up @@ -117,7 +118,8 @@ import {
CustomEmptyContentTemplateDirective,
ViewerToolbarComponent,
BulkActionsDropdownComponent,
SearchAiInputContainerComponent
SearchAiInputContainerComponent,
SaveSearchDirective
],
selector: 'aca-search-results',
templateUrl: './search-results.component.html',
Expand All @@ -137,10 +139,12 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
totalResults: number;
isTagsEnabled = false;
columns: DocumentListPresetRef[] = [];
encodedQuery: string;

constructor(
tagsService: TagService,
private queryBuilder: SearchQueryBuilderService,
private changeDetectorRef: ChangeDetectorRef,
private route: ActivatedRoute,
private translationService: TranslationService
) {
Expand Down Expand Up @@ -175,6 +179,7 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
if (query) {
this.sorting = this.getSorting();
this.isLoading = true;
this.changeDetectorRef.detectChanges();
}
}),

Expand All @@ -183,6 +188,7 @@ export class SearchResultsComponent extends PageComponent implements OnInit {

this.onSearchResultLoaded(data);
this.isLoading = false;
this.changeDetectorRef.detectChanges();
}),

this.queryBuilder.error.subscribe((err: any) => {
Expand All @@ -194,9 +200,9 @@ export class SearchResultsComponent extends PageComponent implements OnInit {

if (this.route) {
this.route.queryParams.pipe(takeUntil(this.onDestroy$)).subscribe((params: Params) => {
const encodedQuery = params[this.queryParamName] ? params[this.queryParamName] : null;
this.searchedWord = extractSearchedWordFromEncodedQuery(encodedQuery);
const filtersFromEncodedQuery = extractFiltersFromEncodedQuery(encodedQuery);
this.encodedQuery = params[this.queryParamName] ? params[this.queryParamName] : null;
this.searchedWord = extractSearchedWordFromEncodedQuery(this.encodedQuery);
const filtersFromEncodedQuery = extractFiltersFromEncodedQuery(this.encodedQuery);
if (filtersFromEncodedQuery !== null) {
const filtersToLoad = Object.keys(filtersFromEncodedQuery).length;
let loadedFilters = this.searchedWord === '' ? 0 : 1;
Expand All @@ -210,9 +216,10 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
}
});
this.queryBuilder.populateFilters.next(filtersFromEncodedQuery);
} else {
this.queryBuilder.populateFilters.next({});
}

this.queryBuilder.userQuery = extractUserQueryFromEncodedQuery(encodedQuery);
this.queryBuilder.userQuery = extractUserQueryFromEncodedQuery(this.encodedQuery);
});
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<h2 class="aca-save-search-dialog__header" mat-dialog-title>{{"APP.BROWSE.SEARCH.SAVE_SEARCH.MODAL_HEADER" | translate}}</h2>

<mat-dialog-content>
<form [formGroup]="form" (submit)="submit()">
<mat-form-field class="aca-save-search-dialog__form-field">
<mat-label>{{ 'APP.BROWSE.SEARCH.SAVE_SEARCH.NAME_LABEL' | translate }}</mat-label>
<input
id="aca-save-search-dialog-name-input"
[attr.aria-label]="'APP.BROWSE.SEARCH.SAVE_SEARCH.NAME_LABEL' | translate"
matInput
required
[formControlName]="'name'"
adf-auto-focus
/>

<mat-hint *ngIf="form.controls['name'].dirty">
<span *ngIf="form.controls['name'].errors?.required">
{{ 'APP.BROWSE.SEARCH.SAVE_SEARCH.NAME_REQUIRED_ERROR' | translate }}
</span>
</mat-hint>
</mat-form-field>

<mat-form-field class="aca-save-search-dialog__form-field">
<mat-label>{{ 'APP.BROWSE.SEARCH.SAVE_SEARCH.DESCRIPTION_LABEL' | translate }}</mat-label>
<textarea
id="aca-save-search-dialog-description-input"
matInput
[attr.aria-label]="'APP.BROWSE.SEARCH.SAVE_SEARCH.DESCRIPTION_LABEL' | translate"
rows="4"
[formControlName]="'description'"></textarea>
</mat-form-field>
</form>
</mat-dialog-content>

<mat-dialog-actions align="end">
<button
mat-button
id="aca-save-search-dialog-cancel-button"
mat-dialog-close>
{{ 'APP.BROWSE.SEARCH.SAVE_SEARCH.CANCEL_BUTTON' | translate }}
</button>

<button id="aca-save-search-dialog-save-button"
mat-flat-button
color="primary"
(click)="submit()"
[disabled]="!form.valid">
{{'APP.BROWSE.SEARCH.SAVE_SEARCH.SAVE_BUTTON' | translate}}
</button>
</mat-dialog-actions>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.aca-save-search-dialog {
.aca-save-search-dialog__form-field {
width: 100%;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*!
* 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, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { of } from 'rxjs';
import { SaveSearchDialogComponent } from './save-search-dialog.component';
import { ContentTestingModule, SavedSearchesService } from '@alfresco/adf-content-services';
import { provideMockStore } from '@ngrx/store/testing';
import { AppTestingModule } from '../../../../testing/app-testing.module';
import { Store } from '@ngrx/store';
import { SnackbarErrorAction, SnackbarInfoAction } from '@alfresco/aca-shared/store';

describe('SaveSearchDialogComponent', () => {
let fixture: ComponentFixture<SaveSearchDialogComponent>;
let component: SaveSearchDialogComponent;
let savedSearchesService: SavedSearchesService;
let store: Store;

let submitButton: HTMLButtonElement;
const dialogRef = {
close: jasmine.createSpy('close')
};

beforeEach(() => {
TestBed.configureTestingModule({
imports: [ContentTestingModule, AppTestingModule],
providers: [
{ provide: MatDialogRef, useValue: dialogRef },
provideMockStore(),
{ provide: SavedSearchesService, useValue: { saveSearch: () => of() } },
{ provide: MAT_DIALOG_DATA, useValue: { searchUrl: 'abcdef' } }
]
});
dialogRef.close.calls.reset();
fixture = TestBed.createComponent(SaveSearchDialogComponent);
component = fixture.componentInstance;
savedSearchesService = TestBed.inject(SavedSearchesService);
store = TestBed.inject(Store);

submitButton = fixture.nativeElement.querySelector('#aca-save-search-dialog-save-button');
component.data = { searchUrl: 'abcdef' };
});

afterEach(() => {
fixture.destroy();
});

it('should not save search if form is invalid', () => {
spyOn(savedSearchesService, 'saveSearch').and.callThrough();
submitButton.click();
expect(savedSearchesService.saveSearch).not.toHaveBeenCalled();
});

it('should save search, show snackbar message and close modal if form is valid', fakeAsync(() => () => {
spyOn(savedSearchesService, 'saveSearch').and.callThrough();
expect(store.dispatch).toHaveBeenCalledWith(new SnackbarInfoAction('APP.BROWSE.SEARCH.SAVE_SEARCH.SAVE_SUCCESS'));
expect(dialogRef.close).toHaveBeenCalled();
}));

it('should save search, show snackbar message and close modal if form is valid', fakeAsync(() => () => {
spyOn(savedSearchesService, 'saveSearch').and.throwError('');
setFormValuesAndSubmit();
expect(store.dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.BROWSE.SEARCH.SAVE_SEARCH.SAVE_ERROR'));
expect(dialogRef.close).not.toHaveBeenCalled();
}));

function setFormValuesAndSubmit() {
spyOn(store, 'dispatch');
component.form.controls['name'].setValue('ABCDEF');
component.form.controls['description'].setValue('TEST');
submitButton.click();
tick();
expect(savedSearchesService.saveSearch).toHaveBeenCalledWith({
name: 'ABCDEF',
description: 'TEST',
encodedUrl: 'abcdef'
});
}
});
Loading

0 comments on commit ba75b71

Please sign in to comment.