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

[ACA-19] show search libraries hint #802

Merged
merged 9 commits into from
Nov 16, 2018
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
1 change: 1 addition & 0 deletions docs/extending/application-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ The content actions are applied to the toolbars for the following Views:
- Favorites
- Trash
- Search Results
- Libraries Search Results

## Context Menu

Expand Down
1 change: 1 addition & 0 deletions docs/extending/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The components are used to create custom:
| app.toolbar.toggleInfoDrawer | ToggleInfoDrawerComponent | The toolbar button component that toggles Info Drawer for the selection. |
| app.toolbar.toggleFavorite | ToggleFavoriteComponent | The toolbar button component that toggles Favorite state for the selection. |
| app.toolbar.toggleFavoriteLibrary | ToggleFavoriteLibraryComponent | The toolbar button component that toggles Favorite library state for the selection. |
| app.toolbar.toggleJoinLibrary | ToggleJoinLibraryComponent | The toolbar button component that toggles Join/Cancel Join request for the selected library. |

See [Registration](/extending/registration) section for more details
on how to register your own entries to be re-used at runtime.
Expand Down
21 changes: 12 additions & 9 deletions docs/extending/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ The button will be visible only when the linked rule evaluates to `true`.
| app.selection.file | A single File node is selected. |
| app.selection.file.canShare | User is able to share the selected file. |
| app.selection.library | A single Library node is selected. |
| app.selection.isPrivateLibrary | A private Library node is selected. |
| app.selection.hasLibraryRole | The selected Library node has a role property. |
| app.selection.hasNoLibraryRole | The selected Library node has no role property. |
| app.selection.folder | A single Folder node is selected. |
| app.selection.folder.canUpdate | User has permissions to update the selected folder. |
| repository.isQuickShareEnabled | Whether the quick share repository option is enabled or not. |
Expand All @@ -163,18 +166,18 @@ You can also negate any rule by utilizing a `!` prefix:
| --------------------------------- | ------------------------------------------------------- |
| app.navigation.folder.canCreate | User can create content in the currently opened folder. |
| app.navigation.folder.canUpload | User can upload content to the currently opened folder. |
| app.navigation.isTrashcan | User is using the **Trashcan** page. |
| app.navigation.isTrashcan | User is using the **Trashcan** page. |
| app.navigation.isNotTrashcan | Current page is not a **Trashcan**. |
| app.navigation.isLibraries | User is using the **Libraries** page. |
| app.navigation.isNotLibraries | Current page is not **Libraries**. |
| app.navigation.isSharedFiles | User is using the **Shared Files** page. |
| app.navigation.isLibraries | User is using a **Libraries** page. |
| app.navigation.isNotLibraries | Current page is not a **Libraries** page. |
| app.navigation.isSharedFiles | User is using the **Shared Files** page. |
| app.navigation.isNotSharedFiles | Current page is not **Shared Files**. |
| app.navigation.isFavorites | User is using the **Favorites** page. |
| app.navigation.isFavorites | User is using the **Favorites** page. |
| app.navigation.isNotFavorites | Current page is not **Favorites** |
| app.navigation.isRecentFiles | User is using the **Recent Files** page. |
| app.navigation.isRecentFiles | User is using the **Recent Files** page. |
| app.navigation.isNotRecentFiles | Current page is not **Recent Files**. |
| app.navigation.isSearchResults | User is using the **Search Results** page. |
| app.navigation.isNotSearchResults | Current page is not the **Search Results**. |
| app.navigation.isSearchResults | User is using the **Search Results** page. |
| app.navigation.isNotSearchResults | Current page is not the **Search Results**. |

<p class="tip">
See [Registration](/extending/registration) section for more details
Expand All @@ -187,7 +190,7 @@ The rule in the example below evaluates to `true` if all the conditions are met:

- user has selected node(s)
- user is not using the **Trashcan** page
- user is not using the **Libraries** page
- user is not using a **Libraries** page (**My Libraries**, **Favorite Libraries** or **Libraries Search Results** pages)

```json
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,73 @@
*/

import { SearchInputControlComponent } from './search-input-control.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AppTestingModule } from '../../../testing/app-testing.module';
import { NO_ERRORS_SCHEMA } from '@angular/core';

describe('SearchInputControlComponent', () => {
it('should be defined', () => {
expect(SearchInputControlComponent).toBeDefined();
let fixture: ComponentFixture<SearchInputControlComponent>;
let component: SearchInputControlComponent;

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [AppTestingModule],
declarations: [SearchInputControlComponent],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(SearchInputControlComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
}));

it('should emit submit event on searchSubmit', async () => {
const keyboardEvent = { target: { value: 'a' } };

let eventArgs = null;
component.submit.subscribe(args => (eventArgs = args));

await component.searchSubmit(keyboardEvent);
expect(eventArgs).toBe(keyboardEvent);
});

it('should emit searchChange event on inputChange', async () => {
const searchTerm = 'b';

let eventArgs = null;
component.searchChange.subscribe(args => (eventArgs = args));

await component.inputChange(searchTerm);
expect(eventArgs).toBe(searchTerm);
});

it('should emit searchChange event on clear', async () => {
let eventArgs = null;
component.searchChange.subscribe(args => (eventArgs = args));

await component.clear();
expect(eventArgs).toBe('');
});

it('should clear searchTerm', async () => {
component.searchTerm = 'c';
fixture.detectChanges();

await component.clear();
expect(component.searchTerm).toBe('');
});

it('should check if searchTerm has a length less than 2', () => {
expect(component.isTermTooShort()).toBe(false);

component.searchTerm = 'd';
fixture.detectChanges();
expect(component.isTermTooShort()).toBe(true);

component.searchTerm = 'dd';
fixture.detectChanges();
expect(component.isTermTooShort()).toBe(false);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,6 @@ export class SearchInputControlComponent implements OnDestroy {
}

isTermTooShort() {
const alphanumericTerm = this.searchTerm.replace(/[^0-9a-z]/gi, '');

return this.searchTerm.length && alphanumericTerm.length < 2;
return !!(this.searchTerm && this.searchTerm.length < 2);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ import { SEARCH_BY_TERM, SearchByTermAction } from '../../../store/actions';
import { map } from 'rxjs/operators';
import { SearchQueryBuilderService } from '@alfresco/adf-content-services';
import { SearchLibrariesQueryBuilderService } from '../search-libraries-results/search-libraries-query-builder.service';
import { ContentManagementService } from '../../../services/content-management.service';

describe('SearchInputComponent', () => {
let fixture: ComponentFixture<SearchInputComponent>;
let component: SearchInputComponent;
let actions$: Actions;
let content: ContentManagementService;

beforeEach(async(() => {
TestBed.configureTestingModule({
Expand All @@ -56,11 +58,33 @@ describe('SearchInputComponent', () => {
.then(() => {
actions$ = TestBed.get(Actions);
fixture = TestBed.createComponent(SearchInputComponent);
content = TestBed.get(ContentManagementService);
component = fixture.componentInstance;
fixture.detectChanges();
});
}));

it('should change flag on library400Error event', () => {
expect(component.has400LibraryError).toBe(false);
content.library400Error.next();

expect(component.has400LibraryError).toBe(true);
});

it('should have no library constraint by default', () => {
expect(component.hasLibraryConstraint()).toBe(false);
});

it('should have library constraint on 400 error received', () => {
const libItem = component.searchOptions.find(
item => item.key.toLowerCase().indexOf('libraries') > 0
);
libItem.value = true;
content.library400Error.next();

expect(component.hasLibraryConstraint()).toBe(true);
});

describe('onSearchSubmit()', () => {
it('should call search action with correct search options', fakeAsync(done => {
const searchedTerm = 's';
Expand Down
37 changes: 33 additions & 4 deletions src/app/components/search/search-input/search-input.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/

import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import {
Component,
OnDestroy,
OnInit,
ViewChild,
ViewEncapsulation
} from '@angular/core';
import {
NavigationEnd,
PRIMARY_OUTLET,
Expand All @@ -37,9 +43,11 @@ import { SearchInputControlComponent } from '../search-input-control/search-inpu
import { Store } from '@ngrx/store';
import { AppStore } from '../../../store/states/app.state';
import { SearchByTermAction } from '../../../store/actions';
import { filter } from 'rxjs/operators';
import { filter, takeUntil } from 'rxjs/operators';
import { SearchLibrariesQueryBuilderService } from '../search-libraries-results/search-libraries-query-builder.service';
import { SearchQueryBuilderService } from '@alfresco/adf-content-services';
import { ContentManagementService } from '../../../services/content-management.service';
import { Subject } from 'rxjs';

export enum SearchOptionIds {
Files = 'files',
Expand All @@ -53,10 +61,12 @@ export enum SearchOptionIds {
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-search-input' }
})
export class SearchInputComponent implements OnInit {
export class SearchInputComponent implements OnInit, OnDestroy {
onDestroy$: Subject<boolean> = new Subject<boolean>();
hasOneChange = false;
hasNewChange = false;
navigationTimer: any;
has400LibraryError = false;

searchedWord = null;
searchOptions: Array<any> = [
Expand Down Expand Up @@ -86,6 +96,7 @@ export class SearchInputComponent implements OnInit {
constructor(
private librariesQueryBuilder: SearchLibrariesQueryBuilderService,
private queryBuilder: SearchQueryBuilderService,
private content: ContentManagementService,
private router: Router,
private store: Store<AppStore>
) {}
Expand All @@ -94,15 +105,23 @@ export class SearchInputComponent implements OnInit {
this.showInputValue();

this.router.events
.pipe(takeUntil(this.onDestroy$))
.pipe(filter(e => e instanceof RouterEvent))
.subscribe(event => {
if (event instanceof NavigationEnd) {
this.showInputValue();
}
});

this.content.library400Error
.pipe(takeUntil(this.onDestroy$))
.subscribe(() => {
this.has400LibraryError = true;
});
}

showInputValue() {
this.has400LibraryError = false;
this.searchedWord = '';

if (this.onSearchResults || this.onLibrariesSearchResults) {
Expand All @@ -121,12 +140,18 @@ export class SearchInputComponent implements OnInit {
}
}

ngOnDestroy(): void {
this.onDestroy$.next(true);
this.onDestroy$.complete();
}

/**
* Called when the user submits the search, e.g. hits enter or clicks submit
*
* @param event Parameters relating to the search
*/
onSearchSubmit(event: KeyboardEvent) {
this.has400LibraryError = false;
const searchTerm = (event.target as HTMLInputElement).value;
if (searchTerm) {
this.store.dispatch(
Expand All @@ -136,6 +161,7 @@ export class SearchInputComponent implements OnInit {
}

onSearchChange(searchTerm: string) {
this.has400LibraryError = false;
if (this.hasOneChange) {
this.hasNewChange = true;
} else {
Expand All @@ -158,6 +184,7 @@ export class SearchInputComponent implements OnInit {
}

onOptionChange() {
this.has400LibraryError = false;
if (this.searchedWord) {
if (this.isLibrariesChecked()) {
if (this.onLibrariesSearchResults) {
Expand Down Expand Up @@ -213,7 +240,9 @@ export class SearchInputComponent implements OnInit {

hasLibraryConstraint(): boolean {
if (this.isLibrariesChecked()) {
return this.searchInputControl.isTermTooShort();
return (
this.has400LibraryError || this.searchInputControl.isTermTooShort()
);
}
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,18 @@ describe('SearchLibrariesQueryBuilderService', () => {
const compiled = builder.buildQuery();
expect(compiled.opts).toEqual({ maxItems: 5, skipCount: 5 });
});

it('should raise an event on error', async () => {
const err = '{"error": {"statusCode": 400}}';
spyOn(queriesApi, 'findSites').and.returnValue(Promise.reject(err));

const query = {};
spyOn(builder, 'buildQuery').and.returnValue(query);

let eventArgs = null;
builder.hadError.subscribe(args => (eventArgs = args));

await builder.execute();
expect(eventArgs).toBe(err);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class SearchLibrariesQueryBuilderService {

updated: Subject<any> = new Subject();
executed: Subject<any> = new Subject();
hadError: Subject<any> = new Subject();

paging: { maxItems?: number; skipCount?: number } = null;

Expand Down Expand Up @@ -77,10 +78,13 @@ export class SearchLibrariesQueryBuilderService {
return null;
}

private findLibraries(libraryQuery: { term; opts }): Promise<SitePaging> {
private findLibraries(libraryQuery): Promise<SitePaging> {
return this.alfrescoApiService
.getInstance()
.core.queriesApi.findSites(libraryQuery.term, libraryQuery.opts)
.catch(() => ({ list: { pagination: { totalItems: 0 }, entries: [] } }));
.catch(err => {
this.hadError.next(err);
return { list: { pagination: { totalItems: 0 }, entries: [] } };
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ export class SearchLibrariesResultsComponent extends PageComponent
this.isLoading = false;
}),

this.librariesQueryBuilder.hadError.subscribe(err => {
try {
const {
error: { statusCode }
} = JSON.parse(err.message);
if (statusCode === 400) {
this.content.library400Error.next();
}
} catch (e) {}
}),

this.breakpointObserver
.observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
.subscribe(result => {
Expand Down
Loading