[ACS-10421] Add support for legacy config.json based approach for saved-searches (#4863)

* [ACS-9166]: introduces saved searches context service

* [ACS-9166]: migrates to context service

* [ACS-9166]: fixes tests

* [ACS-9166]: removes prev approach artifacts

* [ACS-9166]: fixes falsy emissions form pipe

* [ACS-9166]: adds reactive strategy option

* [ACS-9166]: tests fixes

* [ACS-9166]: minor fix
This commit is contained in:
Anton Ramanovich
2025-11-06 13:22:02 +01:00
committed by GitHub
parent d216c2f012
commit 71eee988aa
24 changed files with 365 additions and 82 deletions
+1 -1
View File
@@ -82,6 +82,6 @@ Rules/Evaluators created for specific features in ADW to be checked if supported
| Version | Key | Description | | Version | Key | Description |
|---------|---------------------------------|---------------------------------------------------------------------------| |---------|---------------------------------|---------------------------------------------------------------------------|
| 8.1.0 | isSavedSearchAvailable | Checks whether current ACS version supports PUT method in Preferences API | | 8.1.0 | isPreferencesApiAvailable | Checks whether current ACS version supports PUT method in Preferences API |
| 8.1.0 | isFolderInfoAvailable | Checks whether current ACS version supports folder size calculation API | | 8.1.0 | isFolderInfoAvailable | Checks whether current ACS version supports folder size calculation API |
| 8.1.0 | isBulkActionsAvailable | Checks whether current ACS version supports bulk update feature | | 8.1.0 | isBulkActionsAvailable | Checks whether current ACS version supports bulk update feature |
@@ -219,10 +219,7 @@
"items": [ "items": [
{ {
"id": "app.search.navbar", "id": "app.search.navbar",
"component": "app.search.navbar", "component": "app.search.navbar"
"rules": {
"visible": "isSavedSearchAvailable"
}
} }
] ]
} }
@@ -69,6 +69,7 @@ import { SearchResultsRowComponent } from './components/search/search-results-ro
import { BulkActionsDropdownComponent } from './components/bulk-actions-dropdown/bulk-actions-dropdown.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 { AgentsButtonComponent } from './components/knowledge-retrieval/search-ai/agents-button/agents-button.component';
import { SaveSearchSidenavComponent } from './components/search/search-save/sidenav/save-search-sidenav.component'; import { SaveSearchSidenavComponent } from './components/search/search-save/sidenav/save-search-sidenav.component';
import { IsFeatureSupportedInCurrentAcsPipe } from './pipes/is-feature-supported.pipe';
@NgModule({ @NgModule({
imports: [ContentModule.forRoot(), AppStoreModule, HammerModule], imports: [ContentModule.forRoot(), AppStoreModule, HammerModule],
@@ -134,7 +135,7 @@ import { SaveSearchSidenavComponent } from './components/search/search-save/side
isSmartFolder: rules.isSmartFolder, isSmartFolder: rules.isSmartFolder,
isMultiSelection: rules.isMultiselection, isMultiSelection: rules.isMultiselection,
canPrintFile: rules.canPrintFile, canPrintFile: rules.canPrintFile,
isSavedSearchAvailable: rules.isSavedSearchAvailable, isPreferencesApiAvailable: rules.isPreferencesApiAvailable,
isFolderInfoAvailable: rules.isFolderInfoAvailable, isFolderInfoAvailable: rules.isFolderInfoAvailable,
isBulkActionsAvailable: rules.isBulkActionsAvailable, isBulkActionsAvailable: rules.isBulkActionsAvailable,
@@ -169,7 +170,8 @@ import { SaveSearchSidenavComponent } from './components/search/search-save/side
'app.areCategoriesEnabled': rules.areCategoriesEnabled, 'app.areCategoriesEnabled': rules.areCategoriesEnabled,
'app.isSSOEnabled': rules.isSSOEnabled 'app.isSSOEnabled': rules.isSSOEnabled
} }
}) }),
IsFeatureSupportedInCurrentAcsPipe
] ]
}) })
export class ContentServiceExtensionModule {} export class ContentServiceExtensionModule {}
@@ -26,7 +26,6 @@
<div class="aca-content__advanced-filters--header"> <div class="aca-content__advanced-filters--header">
<p>{{ 'APP.BROWSE.SEARCH.ADVANCED_FILTERS' | translate }}</p> <p>{{ 'APP.BROWSE.SEARCH.ADVANCED_FILTERS' | translate }}</p>
<div class="aca-content__advanced-filters--header--action-buttons"> <div class="aca-content__advanced-filters--header--action-buttons">
@if('isSavedSearchAvailable' | isFeatureSupportedInCurrentAcs | async) {
<button <button
*ngIf="initialSavedSearch !== undefined else saveSearchButton" *ngIf="initialSavedSearch !== undefined else saveSearchButton"
mat-button mat-button
@@ -67,7 +66,6 @@
{{ 'APP.BROWSE.SEARCH.SAVE_SEARCH.ACTION_BUTTON' | translate }} {{ 'APP.BROWSE.SEARCH.SAVE_SEARCH.ACTION_BUTTON' | translate }}
</button> </button>
</ng-template> </ng-template>
}
<button <button
mat-button mat-button
adf-reset-search adf-reset-search
@@ -29,7 +29,7 @@ import { AppConfigService, NotificationService, TranslationService } from '@alfr
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { NavigateToFolder } from '@alfresco/aca-shared/store'; import { NavigateToFolder } from '@alfresco/aca-shared/store';
import { Pagination, SearchRequest } from '@alfresco/js-api'; import { Pagination, SearchRequest } from '@alfresco/js-api';
import { SavedSearchesService, SearchQueryBuilderService } from '@alfresco/adf-content-services'; import { SearchQueryBuilderService } from '@alfresco/adf-content-services';
import { ActivatedRoute, Event, NavigationStart, Params, Router } from '@angular/router'; import { ActivatedRoute, Event, NavigationStart, Params, Router } from '@angular/router';
import { BehaviorSubject, Observable, of, Subject, throwError } from 'rxjs'; import { BehaviorSubject, Observable, of, Subject, throwError } from 'rxjs';
import { AppTestingModule } from '../../../testing/app-testing.module'; import { AppTestingModule } from '../../../testing/app-testing.module';
@@ -42,6 +42,7 @@ import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatMenuHarness } from '@angular/material/menu/testing'; import { MatMenuHarness } from '@angular/material/menu/testing';
import { SavedSearchesContextService } from '../../../services/saved-searches-context.service';
@Pipe({ name: 'isFeatureSupportedInCurrentAcs' }) @Pipe({ name: 'isFeatureSupportedInCurrentAcs' })
class MockIsFeatureSupportedInCurrentAcsPipe implements PipeTransform { class MockIsFeatureSupportedInCurrentAcsPipe implements PipeTransform {
@@ -96,7 +97,7 @@ describe('SearchComponent', () => {
} }
}, },
{ {
provide: SavedSearchesService, provide: SavedSearchesContextService,
useValue: { useValue: {
getSavedSearches: jasmine getSavedSearches: jasmine
.createSpy('getSavedSearches') .createSpy('getSavedSearches')
@@ -30,7 +30,6 @@ import {
DocumentListComponent, DocumentListComponent,
ResetSearchDirective, ResetSearchDirective,
SavedSearch, SavedSearch,
SavedSearchesService,
SearchConfiguration, SearchConfiguration,
SearchFilterChipsComponent, SearchFilterChipsComponent,
SearchFormComponent, SearchFormComponent,
@@ -89,6 +88,7 @@ import { combineLatest, of } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { IsFeatureSupportedInCurrentAcsPipe } from '../../../pipes/is-feature-supported.pipe'; import { IsFeatureSupportedInCurrentAcsPipe } from '../../../pipes/is-feature-supported.pipe';
import { SavedSearchesContextService } from '../../../services/saved-searches-context.service';
@Component({ @Component({
imports: [ imports: [
@@ -155,7 +155,7 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
private readonly changeDetectorRef: ChangeDetectorRef, private readonly changeDetectorRef: ChangeDetectorRef,
private readonly route: ActivatedRoute, private readonly route: ActivatedRoute,
private readonly translationService: TranslationService, private readonly translationService: TranslationService,
private readonly savedSearchesService: SavedSearchesService private readonly savedSearchesService: SavedSearchesContextService
) { ) {
super(); super();
@@ -26,14 +26,15 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { SavedSearchDeleteDialogComponent } from './saved-search-delete-dialog.component'; import { SavedSearchDeleteDialogComponent } from './saved-search-delete-dialog.component';
import { SavedSearch, SavedSearchesService } from '@alfresco/adf-content-services'; import { SavedSearch } from '@alfresco/adf-content-services';
import { NotificationService } from '@alfresco/adf-core'; import { NotificationService } from '@alfresco/adf-core';
import { AppTestingModule } from '../../../../../testing/app-testing.module'; import { AppTestingModule } from '../../../../../testing/app-testing.module';
import { SavedSearchesContextService } from '../../../../../services/saved-searches-context.service';
describe('SaveSearchDeleteDialogComponent', () => { describe('SaveSearchDeleteDialogComponent', () => {
let fixture: ComponentFixture<SavedSearchDeleteDialogComponent>; let fixture: ComponentFixture<SavedSearchDeleteDialogComponent>;
let notificationService: NotificationService; let notificationService: NotificationService;
let savedSearchesService: SavedSearchesService; let savedSearchesService: SavedSearchesContextService;
let submitButton: HTMLButtonElement; let submitButton: HTMLButtonElement;
let cancelButton: HTMLButtonElement; let cancelButton: HTMLButtonElement;
@@ -52,13 +53,13 @@ describe('SaveSearchDeleteDialogComponent', () => {
imports: [AppTestingModule, SavedSearchDeleteDialogComponent], imports: [AppTestingModule, SavedSearchDeleteDialogComponent],
providers: [ providers: [
{ provide: MatDialogRef, useValue: dialogRef }, { provide: MatDialogRef, useValue: dialogRef },
{ provide: SavedSearchesService, useValue: { deleteSavedSearch: () => of({}) } }, { provide: SavedSearchesContextService, useValue: { deleteSavedSearch: () => of({}) } },
{ provide: MAT_DIALOG_DATA, useValue: savedSearchToDelete } { provide: MAT_DIALOG_DATA, useValue: savedSearchToDelete }
] ]
}); });
dialogRef.close.calls.reset(); dialogRef.close.calls.reset();
fixture = TestBed.createComponent(SavedSearchDeleteDialogComponent); fixture = TestBed.createComponent(SavedSearchDeleteDialogComponent);
savedSearchesService = TestBed.inject(SavedSearchesService); savedSearchesService = TestBed.inject(SavedSearchesContextService);
notificationService = TestBed.inject(NotificationService); notificationService = TestBed.inject(NotificationService);
submitButton = fixture.nativeElement.querySelector('#aca-save-search-delete-dialog-submit-button'); submitButton = fixture.nativeElement.querySelector('#aca-save-search-delete-dialog-submit-button');
@@ -23,7 +23,7 @@
*/ */
import { Component, Inject, ViewEncapsulation } from '@angular/core'; import { Component, Inject, ViewEncapsulation } from '@angular/core';
import { SavedSearch, SavedSearchesService } from '@alfresco/adf-content-services'; import { SavedSearch } from '@alfresco/adf-content-services';
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { NotificationService } from '@alfresco/adf-core'; import { NotificationService } from '@alfresco/adf-core';
@@ -31,6 +31,7 @@ import { TranslatePipe } from '@ngx-translate/core';
import { TitleCasePipe } from '@angular/common'; import { TitleCasePipe } from '@angular/common';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { SavedSearchesContextService } from '../../../../../services/saved-searches-context.service';
@Component({ @Component({
imports: [TranslatePipe, TitleCasePipe, MatIconModule, MatButtonModule, MatDialogModule], imports: [TranslatePipe, TitleCasePipe, MatIconModule, MatButtonModule, MatDialogModule],
@@ -46,7 +47,7 @@ export class SavedSearchDeleteDialogComponent {
constructor( constructor(
private readonly dialog: MatDialogRef<SavedSearchDeleteDialogComponent>, private readonly dialog: MatDialogRef<SavedSearchDeleteDialogComponent>,
private readonly notificationService: NotificationService, private readonly notificationService: NotificationService,
private readonly savedSearchesService: SavedSearchesService, private readonly savedSearchesService: SavedSearchesContextService,
@Inject(MAT_DIALOG_DATA) private readonly data: SavedSearch @Inject(MAT_DIALOG_DATA) private readonly data: SavedSearch
) {} ) {}
@@ -26,16 +26,17 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { SavedSearchEditDialogComponent } from './saved-search-edit-dialog.component'; import { SavedSearchEditDialogComponent } from './saved-search-edit-dialog.component';
import { SavedSearch, SavedSearchesService } from '@alfresco/adf-content-services'; import { SavedSearch } from '@alfresco/adf-content-services';
import { provideMockStore } from '@ngrx/store/testing'; import { provideMockStore } from '@ngrx/store/testing';
import { NotificationService } from '@alfresco/adf-core'; import { NotificationService } from '@alfresco/adf-core';
import { AppTestingModule } from '../../../../../testing/app-testing.module'; import { AppTestingModule } from '../../../../../testing/app-testing.module';
import { SavedSearchesContextService } from '../../../../../services/saved-searches-context.service';
describe('SaveSearchEditDialogComponent', () => { describe('SaveSearchEditDialogComponent', () => {
let fixture: ComponentFixture<SavedSearchEditDialogComponent>; let fixture: ComponentFixture<SavedSearchEditDialogComponent>;
let component: SavedSearchEditDialogComponent; let component: SavedSearchEditDialogComponent;
let notificationService: NotificationService; let notificationService: NotificationService;
let savedSearchesService: SavedSearchesService; let savedSearchesService: SavedSearchesContextService;
let submitButton: HTMLButtonElement; let submitButton: HTMLButtonElement;
const savedSearchToDelete: SavedSearch = { const savedSearchToDelete: SavedSearch = {
@@ -54,7 +55,7 @@ describe('SaveSearchEditDialogComponent', () => {
providers: [ providers: [
{ provide: MatDialogRef, useValue: dialogRef }, { provide: MatDialogRef, useValue: dialogRef },
provideMockStore(), provideMockStore(),
{ provide: SavedSearchesService, useValue: { editSavedSearch: () => of({}), getSavedSearches: () => of([]) } }, { provide: SavedSearchesContextService, useValue: { editSavedSearch: () => of({}), getSavedSearches: () => of([]) } },
{ provide: MAT_DIALOG_DATA, useValue: savedSearchToDelete } { provide: MAT_DIALOG_DATA, useValue: savedSearchToDelete }
] ]
}); });
@@ -62,7 +63,7 @@ describe('SaveSearchEditDialogComponent', () => {
fixture = TestBed.createComponent(SavedSearchEditDialogComponent); fixture = TestBed.createComponent(SavedSearchEditDialogComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
notificationService = TestBed.inject(NotificationService); notificationService = TestBed.inject(NotificationService);
savedSearchesService = TestBed.inject(SavedSearchesService); savedSearchesService = TestBed.inject(SavedSearchesContextService);
submitButton = fixture.nativeElement.querySelector('#aca-saved-search-edit-dialog-submit-button'); submitButton = fixture.nativeElement.querySelector('#aca-saved-search-edit-dialog-submit-button');
}); });
@@ -23,7 +23,7 @@
*/ */
import { Component, Inject, ViewEncapsulation } from '@angular/core'; import { Component, Inject, ViewEncapsulation } from '@angular/core';
import { AutoFocusDirective, forbidOnlySpaces, SavedSearch, SavedSearchesService } from '@alfresco/adf-content-services'; import { AutoFocusDirective, forbidOnlySpaces, SavedSearch } from '@alfresco/adf-content-services';
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { NotificationService } from '@alfresco/adf-core'; import { NotificationService } from '@alfresco/adf-core';
@@ -36,6 +36,7 @@ import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { TitleCasePipe } from '@angular/common'; import { TitleCasePipe } from '@angular/common';
import { SavedSearchesContextService } from '../../../../../services/saved-searches-context.service';
@Component({ @Component({
imports: [ imports: [
@@ -62,7 +63,7 @@ export class SavedSearchEditDialogComponent {
constructor( constructor(
private readonly dialog: MatDialogRef<SavedSearchEditDialogComponent>, private readonly dialog: MatDialogRef<SavedSearchEditDialogComponent>,
private readonly notificationService: NotificationService, private readonly notificationService: NotificationService,
private readonly savedSearchesService: SavedSearchesService, private readonly savedSearchesService: SavedSearchesContextService,
private readonly uniqueSearchNameValidator: UniqueSearchNameValidator, private readonly uniqueSearchNameValidator: UniqueSearchNameValidator,
@Inject(MAT_DIALOG_DATA) private readonly data: SavedSearch @Inject(MAT_DIALOG_DATA) private readonly data: SavedSearch
) { ) {
@@ -26,16 +26,16 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { SaveSearchDialogComponent } from './save-search-dialog.component'; import { SaveSearchDialogComponent } from './save-search-dialog.component';
import { SavedSearchesService } from '@alfresco/adf-content-services';
import { provideMockStore } from '@ngrx/store/testing'; import { provideMockStore } from '@ngrx/store/testing';
import { AppTestingModule } from '../../../../testing/app-testing.module'; import { AppTestingModule } from '../../../../testing/app-testing.module';
import { NoopTranslateModule, NotificationService } from '@alfresco/adf-core'; import { NoopTranslateModule, NotificationService } from '@alfresco/adf-core';
import { SavedSearchesContextService } from '../../../../services/saved-searches-context.service';
describe('SaveSearchDialogComponent', () => { describe('SaveSearchDialogComponent', () => {
let fixture: ComponentFixture<SaveSearchDialogComponent>; let fixture: ComponentFixture<SaveSearchDialogComponent>;
let component: SaveSearchDialogComponent; let component: SaveSearchDialogComponent;
let notificationService: NotificationService; let notificationService: NotificationService;
let savedSearchesService: SavedSearchesService; let savedSearchesService: SavedSearchesContextService;
let submitButton: HTMLButtonElement; let submitButton: HTMLButtonElement;
const dialogRef = { const dialogRef = {
@@ -48,7 +48,7 @@ describe('SaveSearchDialogComponent', () => {
providers: [ providers: [
{ provide: MatDialogRef, useValue: dialogRef }, { provide: MatDialogRef, useValue: dialogRef },
provideMockStore(), provideMockStore(),
{ provide: SavedSearchesService, useValue: { saveSearch: () => of({}), getSavedSearches: () => of([]) } }, { provide: SavedSearchesContextService, useValue: { saveSearch: () => of({}), getSavedSearches: () => of([]) } },
{ provide: MAT_DIALOG_DATA, useValue: { searchUrl: 'abcdef' } } { provide: MAT_DIALOG_DATA, useValue: { searchUrl: 'abcdef' } }
] ]
}); });
@@ -56,7 +56,7 @@ describe('SaveSearchDialogComponent', () => {
fixture = TestBed.createComponent(SaveSearchDialogComponent); fixture = TestBed.createComponent(SaveSearchDialogComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
notificationService = TestBed.inject(NotificationService); notificationService = TestBed.inject(NotificationService);
savedSearchesService = TestBed.inject(SavedSearchesService); savedSearchesService = TestBed.inject(SavedSearchesContextService);
submitButton = fixture.nativeElement.querySelector('#aca-save-search-dialog-save-button'); submitButton = fixture.nativeElement.querySelector('#aca-save-search-dialog-save-button');
}); });
@@ -34,11 +34,12 @@ import { A11yModule } from '@angular/cdk/a11y';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { NotificationService } from '@alfresco/adf-core'; import { NotificationService } from '@alfresco/adf-core';
import { AutoFocusDirective, forbidOnlySpaces, SavedSearchesService } from '@alfresco/adf-content-services'; import { AutoFocusDirective, forbidOnlySpaces } from '@alfresco/adf-content-services';
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { UniqueSearchNameValidator } from './unique-search-name-validator'; import { UniqueSearchNameValidator } from './unique-search-name-validator';
import { SavedSearchForm } from './saved-search-form.interface'; import { SavedSearchForm } from './saved-search-form.interface';
import { SavedSearchesContextService } from '../../../../services/saved-searches-context.service';
@Component({ @Component({
imports: [ imports: [
@@ -69,7 +70,7 @@ export class SaveSearchDialogComponent {
constructor( constructor(
private readonly dialog: MatDialogRef<SaveSearchDialogComponent>, private readonly dialog: MatDialogRef<SaveSearchDialogComponent>,
private readonly notificationService: NotificationService, private readonly notificationService: NotificationService,
private readonly savedSearchesService: SavedSearchesService, private readonly savedSearchesService: SavedSearchesContextService,
private readonly uniqueSearchNameValidator: UniqueSearchNameValidator, private readonly uniqueSearchNameValidator: UniqueSearchNameValidator,
@Inject(MAT_DIALOG_DATA) private readonly data: { searchUrl: string } @Inject(MAT_DIALOG_DATA) private readonly data: { searchUrl: string }
) { ) {
@@ -24,10 +24,10 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { UniqueSearchNameValidator } from './unique-search-name-validator'; import { UniqueSearchNameValidator } from './unique-search-name-validator';
import { SavedSearchesService } from '@alfresco/adf-content-services';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { NoopTranslateModule } from '@alfresco/adf-core'; import { NoopTranslateModule } from '@alfresco/adf-core';
import { SavedSearchesContextService } from '../../../../services/saved-searches-context.service';
describe('UniqueSearchNameValidator', () => { describe('UniqueSearchNameValidator', () => {
let validator: UniqueSearchNameValidator; let validator: UniqueSearchNameValidator;
@@ -40,7 +40,7 @@ describe('UniqueSearchNameValidator', () => {
describe('Save searches returns results', () => { describe('Save searches returns results', () => {
beforeEach(() => { beforeEach(() => {
TestBed.overrideProvider(SavedSearchesService, { useValue: { getSavedSearches: () => of([{ name: 'test' }]) } }); TestBed.overrideProvider(SavedSearchesContextService, { useValue: { getSavedSearches: () => of([{ name: 'test' }]) } });
validator = TestBed.inject(UniqueSearchNameValidator); validator = TestBed.inject(UniqueSearchNameValidator);
}); });
@@ -63,7 +63,7 @@ describe('UniqueSearchNameValidator', () => {
describe('Save searches returns error', () => { describe('Save searches returns error', () => {
beforeEach(() => { beforeEach(() => {
TestBed.overrideProvider(SavedSearchesService, { useValue: { getSavedSearches: () => of(null) } }); TestBed.overrideProvider(SavedSearchesContextService, { useValue: { getSavedSearches: () => of(null) } });
validator = TestBed.inject(UniqueSearchNameValidator); validator = TestBed.inject(UniqueSearchNameValidator);
}); });
@@ -22,14 +22,14 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>. * from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { SavedSearchesService } from '@alfresco/adf-content-services';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { AbstractControl, AsyncValidator, ValidationErrors } from '@angular/forms'; import { AbstractControl, AsyncValidator, ValidationErrors } from '@angular/forms';
import { catchError, map, Observable, of } from 'rxjs'; import { catchError, map, Observable, of } from 'rxjs';
import { SavedSearchesContextService } from '../../../../services/saved-searches-context.service';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class UniqueSearchNameValidator implements AsyncValidator { export class UniqueSearchNameValidator implements AsyncValidator {
constructor(private readonly savedSearchesService: SavedSearchesService) {} constructor(private readonly savedSearchesService: SavedSearchesContextService) {}
validate(control: AbstractControl): Observable<ValidationErrors | null> { validate(control: AbstractControl): Observable<ValidationErrors | null> {
return this.savedSearchesService.getSavedSearches().pipe( return this.savedSearchesService.getSavedSearches().pipe(
@@ -26,11 +26,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopTranslateModule, provideCoreAuthTesting } from '@alfresco/adf-core'; import { NoopTranslateModule, provideCoreAuthTesting } from '@alfresco/adf-core';
import { BehaviorSubject, ReplaySubject, Subject } from 'rxjs'; import { BehaviorSubject, ReplaySubject, Subject } from 'rxjs';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { SavedSearchesService, SavedSearch } from '@alfresco/adf-content-services'; import { SavedSearch } from '@alfresco/adf-content-services';
import { SavedSearchesSmartListComponent } from './saved-searches-smart-list.component'; import { SavedSearchesSmartListComponent } from './saved-searches-smart-list.component';
import { AppService, DocumentBasePageService, DocumentBasePageServiceMock } from '@alfresco/aca-shared'; import { AppService, DocumentBasePageService, DocumentBasePageServiceMock } from '@alfresco/aca-shared';
import { AppState } from '@alfresco/aca-shared/store'; import { AppState } from '@alfresco/aca-shared/store';
import { provideMockStore } from '@ngrx/store/testing'; import { provideMockStore } from '@ngrx/store/testing';
import { SavedSearchesContextService } from '../../../../../services/saved-searches-context.service';
const appServiceMock = { const appServiceMock = {
appNavNarMode$: new BehaviorSubject('collapsed'), appNavNarMode$: new BehaviorSubject('collapsed'),
@@ -63,7 +64,7 @@ describe('SavedSearchesSmartListComponent', () => {
initialState: { app: appState } initialState: { app: appState }
}), }),
{ provide: DocumentBasePageService, useClass: DocumentBasePageServiceMock }, { provide: DocumentBasePageService, useClass: DocumentBasePageServiceMock },
{ provide: SavedSearchesService, useValue: { savedSearches$: fakeSavedSearches$ } }, { provide: SavedSearchesContextService, useValue: { savedSearches$: fakeSavedSearches$ } },
{ provide: AppService, useValue: appServiceMock } { provide: AppService, useValue: appServiceMock }
] ]
}); });
@@ -27,9 +27,9 @@ import { CommonModule } from '@angular/common';
import { TranslatePipe } from '@ngx-translate/core'; import { TranslatePipe } from '@ngx-translate/core';
import { SavedSearchesListUiComponent } from '../ui-list/saved-searches-list.ui-component'; import { SavedSearchesListUiComponent } from '../ui-list/saved-searches-list.ui-component';
import { PageComponent, PageLayoutComponent } from '@alfresco/aca-shared'; import { PageComponent, PageLayoutComponent } from '@alfresco/aca-shared';
import { SavedSearchesService } from '@alfresco/adf-content-services';
import { EmptyContentComponent } from '@alfresco/adf-core'; import { EmptyContentComponent } from '@alfresco/adf-core';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { SavedSearchesContextService } from '../../../../../services/saved-searches-context.service';
@Component({ @Component({
selector: 'aca-saved-searches-smart-list', selector: 'aca-saved-searches-smart-list',
@@ -39,7 +39,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class SavedSearchesSmartListComponent extends PageComponent { export class SavedSearchesSmartListComponent extends PageComponent {
savedSearchesService = inject(SavedSearchesService); savedSearchesService = inject(SavedSearchesContextService);
savedSearches$ = this.savedSearchesService.savedSearches$; savedSearches$ = this.savedSearchesService.savedSearches$;
@@ -24,26 +24,29 @@
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { SaveSearchSidenavComponent } from './save-search-sidenav.component'; import { SaveSearchSidenavComponent } from './save-search-sidenav.component';
import { SavedSearchesService } from '@alfresco/adf-content-services';
import { AppTestingModule } from '../../../../testing/app-testing.module'; import { AppTestingModule } from '../../../../testing/app-testing.module';
import { of, ReplaySubject } from 'rxjs'; import { Observable, of } from 'rxjs';
import { SavedSearchesContextService } from '../../../../services/saved-searches-context.service';
import { SavedSearch } from '@alfresco/adf-content-services';
describe('SaveSearchSidenavComponent', () => { describe('SaveSearchSidenavComponent', () => {
let fixture: ComponentFixture<SaveSearchSidenavComponent>; let fixture: ComponentFixture<SaveSearchSidenavComponent>;
let component: SaveSearchSidenavComponent; let component: SaveSearchSidenavComponent;
let savedSearchesService: SavedSearchesService; let savedSearchesService: SavedSearchesContextService;
beforeEach(() => { beforeEach(() => {
const mockService = { const mockService: Partial<SavedSearchesContextService> = {
init: () => {}, init: (): void => {},
getSavedSearches: () => of(),
savedSearches$: new ReplaySubject(1) get savedSearches$(): Observable<SavedSearch[]> {
return of([]);
}
}; };
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [AppTestingModule, SaveSearchSidenavComponent], imports: [AppTestingModule, SaveSearchSidenavComponent],
providers: [ providers: [
{ {
provide: SavedSearchesService, provide: SavedSearchesContextService,
useValue: mockService useValue: mockService
} }
] ]
@@ -51,15 +54,11 @@ describe('SaveSearchSidenavComponent', () => {
fixture = TestBed.createComponent(SaveSearchSidenavComponent); fixture = TestBed.createComponent(SaveSearchSidenavComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
savedSearchesService = TestBed.inject(SavedSearchesService); savedSearchesService = TestBed.inject(SavedSearchesContextService);
}); });
it('should set navbar object if no search is saved', async () => { it('should set navbar object if no search is saved', async () => {
Object.defineProperty(savedSearchesService, 'savedSearches$', { spyOnProperty(savedSearchesService, 'savedSearches$', 'get').and.returnValue(of([]));
value: of([]),
writable: true,
configurable: true
});
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
@@ -82,11 +81,7 @@ describe('SaveSearchSidenavComponent', () => {
}); });
it('should set navbar object with children is searches are saved', fakeAsync(() => { it('should set navbar object with children is searches are saved', fakeAsync(() => {
Object.defineProperty(savedSearchesService, 'savedSearches$', { spyOnProperty(savedSearchesService, 'savedSearches$', 'get').and.returnValue(of([{ name: '1', order: 0, encodedUrl: 'abc' }]));
value: of([{ name: '1', order: 0, encodedUrl: 'abc' }]),
writable: true,
configurable: true
});
component.ngOnInit(); component.ngOnInit();
fixture.detectChanges(); fixture.detectChanges();
tick(100); tick(100);
@@ -23,13 +23,14 @@
*/ */
import { Component, DestroyRef, inject, OnInit, ViewEncapsulation } from '@angular/core'; import { Component, DestroyRef, inject, OnInit, ViewEncapsulation } from '@angular/core';
import { SavedSearch, SavedSearchesService } from '@alfresco/adf-content-services'; import { SavedSearch } from '@alfresco/adf-content-services';
import { TranslationService, UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core'; import { TranslationService, UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core';
import { NavBarLinkRef } from '@alfresco/adf-extensions'; import { NavBarLinkRef } from '@alfresco/adf-extensions';
import { ExpandMenuComponent } from '../../../sidenav/components/expand-menu.component'; import { ExpandMenuComponent } from '../../../sidenav/components/expand-menu.component';
import { AppService } from '@alfresco/aca-shared'; import { AppService } from '@alfresco/aca-shared';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { delay } from 'rxjs/operators'; import { delay } from 'rxjs/operators';
import { SavedSearchesContextService } from '../../../../services/saved-searches-context.service';
@Component({ @Component({
selector: 'aca-save-search-sidenav', selector: 'aca-save-search-sidenav',
@@ -38,7 +39,7 @@ import { delay } from 'rxjs/operators';
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class SaveSearchSidenavComponent implements OnInit { export class SaveSearchSidenavComponent implements OnInit {
savedSearchesService = inject(SavedSearchesService); savedSearchesService = inject(SavedSearchesContextService);
appService = inject(AppService); appService = inject(AppService);
translationService = inject(TranslationService); translationService = inject(TranslationService);
item: NavBarLinkRef; item: NavBarLinkRef;
@@ -22,34 +22,48 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>. * from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { of } from 'rxjs'; import { Subject } from 'rxjs';
import { IsFeatureSupportedInCurrentAcsPipe } from './is-feature-supported.pipe'; import { IsFeatureSupportedInCurrentAcsPipe } from './is-feature-supported.pipe';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { AppExtensionService } from '@alfresco/aca-shared'; import { AppExtensionService } from '@alfresco/aca-shared';
import { AppStore } from '@alfresco/aca-shared/store'; import { AppStore } from '@alfresco/aca-shared/store';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { RepositoryInfo, VersionInfo } from '@alfresco/js-api';
describe('IsFeatureSupportedInCurrentAcsPipe', () => { describe('IsFeatureSupportedInCurrentAcsPipe', () => {
let serviceSpy: jasmine.SpyObj<AppExtensionService>; let serviceSpy: jasmine.SpyObj<AppExtensionService>;
let storeSpy: jasmine.SpyObj<Store<AppStore>>; let storeSpy: jasmine.SpyObj<Store<AppStore>>;
let pipe: IsFeatureSupportedInCurrentAcsPipe; let pipe: IsFeatureSupportedInCurrentAcsPipe;
let repoStatusSubject: Subject<Partial<RepositoryInfo>>;
beforeEach(() => { beforeEach(() => {
serviceSpy = jasmine.createSpyObj('AppExtensionService', ['isFeatureSupported']); serviceSpy = jasmine.createSpyObj('AppExtensionService', ['isFeatureSupported']);
storeSpy = jasmine.createSpyObj('Store', ['dispatch', 'select']); storeSpy = jasmine.createSpyObj('Store', ['dispatch', 'select']);
repoStatusSubject = new Subject<Partial<RepositoryInfo>>();
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [IsFeatureSupportedInCurrentAcsPipe, { provide: AppExtensionService, useValue: serviceSpy }, { provide: Store, useValue: storeSpy }] providers: [IsFeatureSupportedInCurrentAcsPipe, { provide: AppExtensionService, useValue: serviceSpy }, { provide: Store, useValue: storeSpy }]
}); });
pipe = TestBed.inject(IsFeatureSupportedInCurrentAcsPipe); pipe = TestBed.inject(IsFeatureSupportedInCurrentAcsPipe);
}); });
it('should call isFeatureSupported in AppExtensionService', (done) => { it('should wait until the version property is available before calling isFeatureSupported', (done) => {
serviceSpy.isFeatureSupported.and.returnValue(false); const featureId = 'someFeature';
storeSpy.select.and.returnValue(of('7.4.0')); serviceSpy.isFeatureSupported.and.returnValue(true);
pipe.transform('someFeature').subscribe((result) => {
expect(result).toBe(false); storeSpy.select.and.returnValue(repoStatusSubject.asObservable());
expect(serviceSpy.isFeatureSupported).toHaveBeenCalledWith('someFeature');
pipe.transform(featureId).subscribe({
next: (result) => {
expect(result).toBe(true);
expect(serviceSpy.isFeatureSupported).toHaveBeenCalledWith(featureId);
done(); done();
},
error: done.fail
}); });
repoStatusSubject.next({});
expect(serviceSpy.isFeatureSupported).not.toHaveBeenCalled();
repoStatusSubject.next({ version: {} as VersionInfo });
}); });
}); });
@@ -26,7 +26,7 @@ import { AppExtensionService } from '@alfresco/aca-shared';
import { Pipe, PipeTransform } from '@angular/core'; import { Pipe, PipeTransform } from '@angular/core';
import { AppStore, getRepositoryStatus } from '@alfresco/aca-shared/store'; import { AppStore, getRepositoryStatus } from '@alfresco/aca-shared/store';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { map, Observable } from 'rxjs'; import { filter, map, Observable } from 'rxjs';
@Pipe({ @Pipe({
name: 'isFeatureSupportedInCurrentAcs' name: 'isFeatureSupportedInCurrentAcs'
@@ -38,6 +38,9 @@ export class IsFeatureSupportedInCurrentAcsPipe implements PipeTransform {
) {} ) {}
transform(evaluatorId: string): Observable<boolean> { transform(evaluatorId: string): Observable<boolean> {
return this.store.select(getRepositoryStatus).pipe(map(() => this.appExtensionsService.isFeatureSupported(evaluatorId))); return this.store.select(getRepositoryStatus).pipe(
filter((repoStatus) => 'version' in repoStatus),
map(() => this.appExtensionsService.isFeatureSupported(evaluatorId))
);
} }
} }
@@ -0,0 +1,186 @@
/*!
* Copyright © 2005-2025 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 { TestBed } from '@angular/core/testing';
import { Subject } from 'rxjs';
import { SavedSearchesContextService } from './saved-searches-context.service';
import { SavedSearch, SavedSearchesLegacyService, SavedSearchesService } from '@alfresco/adf-content-services';
import { IsFeatureSupportedInCurrentAcsPipe } from '../pipes/is-feature-supported.pipe';
describe('SavedSearchesContextService', () => {
let legacySpy: jasmine.SpyObj<SavedSearchesLegacyService>;
let modernSpy: jasmine.SpyObj<SavedSearchesService>;
let isSupportedPipeMock: Partial<IsFeatureSupportedInCurrentAcsPipe>;
let service: SavedSearchesContextService;
let isSupported: Subject<boolean>;
beforeEach(() => {
legacySpy = jasmine.createSpyObj('SavedSearchesLegacyService', [
'savedSearches$',
'init',
'getSavedSearches',
'saveSearch',
'editSavedSearch',
'deleteSavedSearch',
'changeOrder'
]);
modernSpy = jasmine.createSpyObj('SavedSearchesService', [
'savedSearches$',
'init',
'getSavedSearches',
'saveSearch',
'editSavedSearch',
'deleteSavedSearch',
'changeOrder'
]);
isSupported = new Subject<boolean>();
isSupportedPipeMock = {
transform: () => isSupported
};
TestBed.configureTestingModule({
providers: [
{ provide: IsFeatureSupportedInCurrentAcsPipe, useValue: isSupportedPipeMock },
{ provide: SavedSearchesLegacyService, useValue: legacySpy },
{ provide: SavedSearchesService, useValue: modernSpy }
]
});
service = TestBed.inject(SavedSearchesContextService);
});
describe('modern strategy', () => {
beforeEach(() => {
isSupported.next(true);
});
it('should use modern service when feature is supported', () => {
service.init();
expect(legacySpy.init).not.toHaveBeenCalled();
expect(modernSpy.init).toHaveBeenCalled();
});
it('should delegate init() call to a current strategy', () => {
service.init();
expect(modernSpy.init).toHaveBeenCalled();
});
it('should delegate getSavedSearches() call to a current strategy', () => {
service.getSavedSearches();
expect(modernSpy.getSavedSearches).toHaveBeenCalled();
});
it('should delegate saveSearch() call to a current strategy', () => {
const newSavedSearch = { name: 'Test Search', description: 'Test Description', encodedUrl: 'http://example.com' };
service.saveSearch(newSavedSearch);
expect(modernSpy.saveSearch).toHaveBeenCalledWith(newSavedSearch);
});
it('should delegate editSavedSearch() call to a current strategy', () => {
const updatedSavedSearch = {
name: 'Updated Search',
description: 'Updated Description',
encodedUrl: 'http://example.com',
order: 1
};
service.editSavedSearch(updatedSavedSearch);
expect(modernSpy.editSavedSearch).toHaveBeenCalledWith(updatedSavedSearch);
});
it('should delegate deleteSavedSearch() call to a current strategy', () => {
const deletedSavedSearch = {
name: 'Deleted Search',
description: 'Deleted Description',
encodedUrl: 'http://example.com',
order: 2
};
service.deleteSavedSearch(deletedSavedSearch);
expect(modernSpy.deleteSavedSearch).toHaveBeenCalledWith(deletedSavedSearch);
});
it('should delegate changeOrder() call to a current strategy', () => {
service.changeOrder(0, 1);
expect(modernSpy.changeOrder).toHaveBeenCalledWith(0, 1);
});
});
describe('legacy strategy', () => {
beforeEach(() => {
isSupported.next(false);
});
it('should use legacy service when feature is NOT supported', () => {
service.init();
expect(legacySpy.init).toHaveBeenCalled();
expect(modernSpy.init).not.toHaveBeenCalled();
});
it('should delegate init() call to a current strategy', () => {
service.init();
expect(legacySpy.init).toHaveBeenCalled();
});
it('should delegate getSavedSearches() call to a current strategy', () => {
service.getSavedSearches();
expect(legacySpy.getSavedSearches).toHaveBeenCalled();
});
it('should delegate saveSearch() call to a current strategy', () => {
const newSavedSearch = { name: 'Test Search', description: 'Test Description', encodedUrl: 'http://example.com' } as SavedSearch;
service.saveSearch(newSavedSearch);
expect(legacySpy.saveSearch).toHaveBeenCalledWith(newSavedSearch);
});
it('should delegate editSavedSearch() call to a current strategy', () => {
const updatedSavedSearch: SavedSearch = {
name: 'Updated Search',
description: 'Updated Description',
encodedUrl: 'http://example.com',
order: 1
};
service.editSavedSearch(updatedSavedSearch);
expect(legacySpy.editSavedSearch).toHaveBeenCalledWith(updatedSavedSearch);
});
it('should delegate deleteSavedSearch() call to a current strategy', () => {
const deletedSavedSearch: SavedSearch = {
name: 'Deleted Search',
description: 'Deleted Description',
encodedUrl: 'http://example.com',
order: 2
};
service.deleteSavedSearch(deletedSavedSearch);
expect(legacySpy.deleteSavedSearch).toHaveBeenCalledWith(deletedSavedSearch);
});
it('should delegate changeOrder() call to a current strategy', () => {
service.changeOrder(0, 1);
expect(legacySpy.changeOrder).toHaveBeenCalledWith(0, 1);
});
});
});
@@ -0,0 +1,80 @@
/*!
* Copyright © 2005-2025 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 { Injectable } from '@angular/core';
import { Observable, ReplaySubject, switchMap, take } from 'rxjs';
import { NodeEntry } from '@alfresco/js-api';
import { SavedSearch, SavedSearchesLegacyService, SavedSearchesService, SavedSearchStrategy } from '@alfresco/adf-content-services';
import { IsFeatureSupportedInCurrentAcsPipe } from '../pipes/is-feature-supported.pipe';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Injectable({
providedIn: 'root'
})
export class SavedSearchesContextService implements SavedSearchStrategy {
private readonly strategy$ = new ReplaySubject<SavedSearchStrategy>(1);
private strategy: SavedSearchStrategy;
constructor(
private readonly legacyService: SavedSearchesLegacyService,
private readonly modernService: SavedSearchesService,
isFeatureSupported: IsFeatureSupportedInCurrentAcsPipe
) {
isFeatureSupported
.transform('isPreferencesApiAvailable')
.pipe(takeUntilDestroyed())
.subscribe((isSupported) => {
this.strategy = isSupported ? this.modernService : this.legacyService;
this.strategy$.next(this.strategy);
});
}
get savedSearches$(): Observable<SavedSearch[]> {
return this.strategy$.pipe(switchMap((strategy) => strategy.savedSearches$));
}
init(): void {
this.strategy$.pipe(take(1)).subscribe((strategy) => strategy.init());
}
getSavedSearches(): Observable<SavedSearch[]> {
return this.strategy.getSavedSearches();
}
saveSearch(newSaveSearch: Pick<SavedSearch, 'name' | 'description' | 'encodedUrl'>): Observable<NodeEntry> {
return this.strategy.saveSearch(newSaveSearch);
}
editSavedSearch(updatedSavedSearch: SavedSearch): Observable<NodeEntry> {
return this.strategy.editSavedSearch(updatedSavedSearch);
}
deleteSavedSearch(deletedSavedSearch: SavedSearch): Observable<NodeEntry> {
return this.strategy.deleteSavedSearch(deletedSavedSearch);
}
changeOrder(previousIndex: number, currentIndex: number): void {
this.strategy.changeOrder(previousIndex, currentIndex);
}
}
@@ -23,7 +23,7 @@
*/ */
import * as app from './app.rules'; import * as app from './app.rules';
import { createVersionRule, getFileExtension, isSavedSearchAvailable, isFolderInfoAvailable, isBulkActionsAvailable } from './app.rules'; import { createVersionRule, getFileExtension, isPreferencesApiAvailable, isFolderInfoAvailable, isBulkActionsAvailable } from './app.rules';
import { TestRuleContext } from './test-rule-context'; import { TestRuleContext } from './test-rule-context';
import { NodeEntry, RepositoryInfo, StatusInfo } from '@alfresco/js-api'; import { NodeEntry, RepositoryInfo, StatusInfo } from '@alfresco/js-api';
import { ProfileState, RuleContext } from '@alfresco/adf-extensions'; import { ProfileState, RuleContext } from '@alfresco/adf-extensions';
@@ -1219,24 +1219,24 @@ describe('Versions compatibility', () => {
} as RuleContext; } as RuleContext;
} }
describe('isSavedSearchAvailable', () => { describe('isPreferencesApiAvailable', () => {
it('should return true if ACS version is equal to minimal version', () => { it('should return true if ACS version is equal to minimal version', () => {
expect(isSavedSearchAvailable(makeContext('25.1.0'))).toBe(true); expect(isPreferencesApiAvailable(makeContext('25.1.0'))).toBe(true);
}); });
it('should return true if ACS version is greater than minimal version', () => { it('should return true if ACS version is greater than minimal version', () => {
expect(isSavedSearchAvailable(makeContext('25.2.0'))).toBe(true); expect(isPreferencesApiAvailable(makeContext('25.2.0'))).toBe(true);
expect(isSavedSearchAvailable(makeContext('26.0.0'))).toBe(true); expect(isPreferencesApiAvailable(makeContext('26.0.0'))).toBe(true);
}); });
it('should return false if ACS version is less than minimal version', () => { it('should return false if ACS version is less than minimal version', () => {
expect(isSavedSearchAvailable(makeContext('24.4.0'))).toBe(false); expect(isPreferencesApiAvailable(makeContext('24.4.0'))).toBe(false);
expect(isSavedSearchAvailable(makeContext('25.0.9'))).toBe(false); expect(isPreferencesApiAvailable(makeContext('25.0.9'))).toBe(false);
}); });
it('should return false if ACS version is missing', () => { it('should return false if ACS version is missing', () => {
expect(isSavedSearchAvailable(makeContext())).toBe(false); expect(isPreferencesApiAvailable(makeContext())).toBe(false);
expect(isSavedSearchAvailable({ repository: {} } as any)).toBe(false); expect(isPreferencesApiAvailable({ repository: {} } as any)).toBe(false);
}); });
}); });
+2 -2
View File
@@ -485,9 +485,9 @@ export function canOpenWithOffice(context: AcaRuleContext): boolean {
/** /**
* Checks if user savedSearches are supported by current ACS version. * Checks if user savedSearches are supported by current ACS version.
* JSON ref: `isSavedSearchAvailable` * JSON ref: `isPreferencesApiAvailable`
*/ */
export const isSavedSearchAvailable = createVersionRule('25.1.0'); export const isPreferencesApiAvailable = createVersionRule('25.1.0');
/** /**
* Checks if folder info modal is supported by current ACS version. * Checks if folder info modal is supported by current ACS version.