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-8779] Keep selections and question after going to the previous page #4147

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
3 changes: 2 additions & 1 deletion projects/aca-content/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,8 @@
"SEARCH_INPUT": {
"ASK_BUTTON_LABEL": "Ask",
"DEFAULT_PLACEHOLDER": "Please ask your question with as much detail as possible...",
"HIDE_INPUT": "Hide input"
"HIDE_INPUT": "Hide input",
"HIDE_ANSWER": "Hide answer"
},
"ERRORS": {
"AGENTS_FETCHING": "Error while fetching agents.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ <h1 class="aca-page-title">
[sorting]="['title', 'asc']"
[sortingMode]="'client'"
[displayCheckboxesOnHover]="true"
[preselectNodes]="selectedNodesState?.nodes"
(node-dblclick)="handleNodeClick($event)"
[imageResolver]="imageResolver"
(selectedItemsCountChanged)="onSelectedItemsCountChanged($event)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ <h1 class="aca-page-title">
[multiselect]="true"
[navigate]="false"
[sorting]="['modifiedAt', 'desc']"
[preselectNodes]="selectedNodesState?.nodes"
[sortingMode]="'client'"
[imageResolver]="imageResolver"
[displayCheckboxesOnHover]="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
[node]="nodeResult"
[allowDropFiles]="true"
[displayCheckboxesOnHover]="true"
[preselectNodes]="selectedNodesState?.nodes"
[navigate]="false"
[sorting]="['name', 'asc']"
[imageResolver]="imageResolver"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<aca-search-ai-input
(searchSubmitted)="hideSearchInput()"
[searchTerm]="(inputState$ | async).searchTerm"
[placeholder]="placeholder"
[agentId]="agentId"
[useStoredNodes]="useStoredNodes">
Expand All @@ -12,7 +12,7 @@
mat-icon-button
(click)="leaveSearchInput()"
data-automation-id="aca-search-ai-input-container-leaving-search-button"
[title]="'KNOWLEDGE_RETRIEVAL.SEARCH.SEARCH_INPUT.HIDE_INPUT' | translate"
[title]="(isKnowledgeRetrievalPage ? 'KNOWLEDGE_RETRIEVAL.SEARCH.SEARCH_INPUT.HIDE_ANSWER' : 'KNOWLEDGE_RETRIEVAL.SEARCH.SEARCH_INPUT.HIDE_INPUT') | translate"
class="aca-search-ai-input-container-close">
<mat-icon>close</mat-icon>
</button>
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,40 @@ import { DebugElement } from '@angular/core';
import { MatIconButton } from '@angular/material/button';
import { MatIcon } from '@angular/material/icon';
import { SearchAiNavigationService } from '../../../../services/search-ai-navigation.service';
import { NavigationEnd, NavigationStart, Router, RouterEvent } from '@angular/router';
import { NavigationEnd, Router, RouterEvent } from '@angular/router';
import { getAppSelection } from '@alfresco/aca-shared/store';

describe('SearchAiInputContainerComponent', () => {
const routingEvents$: Subject<RouterEvent> = new Subject();

let component: SearchAiInputContainerComponent;
let fixture: ComponentFixture<SearchAiInputContainerComponent>;
let routingEvents$: Subject<RouterEvent>;
let searchAiService: SearchAiService;
let store: MockStore;
let mockSearchAiService: jasmine.SpyObj<SearchAiService>;
let searchNavigationService: SearchAiNavigationService;
let mockRouter: any;

beforeEach(() => {
mockSearchAiService = jasmine.createSpyObj('SearchAiService', ['updateSearchAiInputState'], {
toggleSearchAiInput$: of(true)
});

mockRouter = {
url: '/some-url',
events: routingEvents$.asObservable(),
routerState: {
root: {}
},
snapshot: {}
};

TestBed.configureTestingModule({
imports: [SearchAiInputContainerComponent, ContentTestingModule],
providers: [
{ provide: Router, useValue: mockRouter },
provideMockStore(),
{ provide: SearchAiService, useValue: mockSearchAiService },
{
provide: AgentService,
useValue: {
Expand All @@ -70,15 +89,14 @@ describe('SearchAiInputContainerComponent', () => {
component = fixture.componentInstance;
store = TestBed.inject(MockStore);
searchAiService = TestBed.inject(SearchAiService);
searchNavigationService = TestBed.inject(SearchAiNavigationService);
store.overrideSelector(getAppSelection, {
nodes: [],
isEmpty: true,
count: 0,
libraries: []
});
component.agentId = '1';
routingEvents$ = new Subject<RouterEvent>();
spyOnProperty(TestBed.inject(Router), 'events').and.returnValue(routingEvents$);
fixture.detectChanges();
});

Expand Down Expand Up @@ -115,13 +133,10 @@ describe('SearchAiInputContainerComponent', () => {
expect(inputComponent.useStoredNodes).toBeTrue();
});

it('should call updateSearchAiInputState on SearchAiService when triggered searchSubmitted event', () => {
spyOn(searchAiService, 'updateSearchAiInputState');
inputComponent.searchSubmitted.emit();
it('should set inputState$ to toggleSearchAiInput$ from the service on ngOnInit', () => {
component.ngOnInit();

expect(searchAiService.updateSearchAiInputState).toHaveBeenCalledWith({
active: false
});
expect(component.inputState$).toBe(mockSearchAiService.toggleSearchAiInput$);
});
});

Expand All @@ -140,44 +155,35 @@ describe('SearchAiInputContainerComponent', () => {
button = fixture.debugElement.query(By.directive(MatIconButton));
});

it('should have correct title', () => {
it('should have correct title when page is not knowledge-retrieval', () => {
mockRouter.url = '/other-page';

component.ngOnInit();

expect(button.nativeElement.title).toBe('KNOWLEDGE_RETRIEVAL.SEARCH.SEARCH_INPUT.HIDE_INPUT');
});

it('should contain close icon', () => {
expect(button.query(By.directive(MatIcon)).nativeElement.textContent).toBe('close');
});
it('should have correct title when page is knowledge-retrieval', () => {
mockRouter.url = '/knowledge-retrieval/some-data';

it('should call updateSearchAiInputState on SearchAiService when clicked', () => {
spyOn(searchAiService, 'updateSearchAiInputState');
button.nativeElement.click();
component.ngOnInit();
fixture.detectChanges();

expect(searchAiService.updateSearchAiInputState).toHaveBeenCalledWith({
active: false
});
expect(button.nativeElement.title).toBe('KNOWLEDGE_RETRIEVAL.SEARCH.SEARCH_INPUT.HIDE_ANSWER');
});

it('should call navigateToPreviousRoute on SearchAiNavigationService when clicked', () => {
const searchNavigationService = TestBed.inject(SearchAiNavigationService);
spyOn(searchNavigationService, 'navigateToPreviousRoute');
button.nativeElement.click();

expect(searchNavigationService.navigateToPreviousRoute).toHaveBeenCalled();
it('should contain close icon', () => {
expect(button.query(By.directive(MatIcon)).nativeElement.textContent).toBe('close');
});
});

describe('Navigation', () => {
it('should call updateSearchAiInputState on SearchAiService when navigation starts', () => {
spyOn(searchAiService, 'updateSearchAiInputState');
routingEvents$.next(new NavigationStart(1, ''));
it('should call navigateToPreviousRoute on SearchAiService when clicked', () => {
spyOn(searchNavigationService, 'navigateToPreviousRouteOrCloseInput');
button.nativeElement.click();

expect(searchAiService.updateSearchAiInputState).toHaveBeenCalledWith({
active: false
});
expect(searchNavigationService.navigateToPreviousRouteOrCloseInput).toHaveBeenCalled();
});

it('should not call updateSearchAiInputState on SearchAiService when there is different event than navigation starts', () => {
spyOn(searchAiService, 'updateSearchAiInputState');
routingEvents$.next(new NavigationEnd(1, '', ''));

expect(searchAiService.updateSearchAiInputState).not.toHaveBeenCalled();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,60 +22,45 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/

import { Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { SearchAiInputComponent } from '../search-ai-input/search-ai-input.component';
import { MatDividerModule } from '@angular/material/divider';
import { SearchAiNavigationService } from '../../../../services/search-ai-navigation.service';
import { NavigationStart, Router } from '@angular/router';
import { filter, takeUntil } from 'rxjs/operators';
import { SearchAiService } from '@alfresco/adf-content-services';
import { SearchAiService, SearchAiInputState } from '@alfresco/adf-content-services';
import { TranslateModule } from '@ngx-translate/core';
import { Subject } from 'rxjs';
import { Observable } from 'rxjs';
import { AsyncPipe } from '@angular/common';
import { Router } from '@angular/router';

@Component({
standalone: true,
imports: [SearchAiInputComponent, MatIconModule, MatDividerModule, MatButtonModule, TranslateModule],
imports: [SearchAiInputComponent, MatIconModule, MatDividerModule, MatButtonModule, TranslateModule, AsyncPipe],
selector: 'aca-search-ai-input-container',
templateUrl: './search-ai-input-container.component.html',
styleUrls: ['./search-ai-input-container.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class SearchAiInputContainerComponent implements OnInit, OnDestroy {
export class SearchAiInputContainerComponent implements OnInit {
@Input()
placeholder = 'KNOWLEDGE_RETRIEVAL.SEARCH.SEARCH_INPUT.DEFAULT_PLACEHOLDER';
@Input()
agentId: string;
@Input()
useStoredNodes: boolean;

private onDestroy$ = new Subject<void>();
inputState$: Observable<SearchAiInputState>;
isKnowledgeRetrievalPage = false;

constructor(private searchAiService: SearchAiService, private searchNavigationService: SearchAiNavigationService, private router: Router) {}

ngOnInit(): void {
this.router.events
.pipe(
filter((event) => event instanceof NavigationStart),
takeUntil(this.onDestroy$)
)
.subscribe(() => this.hideSearchInput());
}

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

hideSearchInput(): void {
this.searchAiService.updateSearchAiInputState({
active: false
});
this.isKnowledgeRetrievalPage = this.router.url.startsWith('/knowledge-retrieval');
this.inputState$ = this.searchAiService.toggleSearchAiInput$;
}

leaveSearchInput(): void {
this.searchNavigationService.navigateToPreviousRoute();
this.hideSearchInput();
this.searchNavigationService.navigateToPreviousRouteOrCloseInput();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { MatSelect, MatSelectModule } from '@angular/material/select';
import { By } from '@angular/platform-browser';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { AgentService, ContentTestingModule, SearchAiService } from '@alfresco/adf-content-services';
import { getAppSelection, SearchByTermAiAction } from '@alfresco/aca-shared/store';
import { getAppSelection, SearchByTermAiAction, ToggleAISearchInput } from '@alfresco/aca-shared/store';
import { of, Subject } from 'rxjs';
import { Agent, NodeEntry } from '@alfresco/js-api';
import { FormControlDirective } from '@angular/forms';
Expand Down Expand Up @@ -70,6 +70,7 @@ describe('SearchAiInputComponent', () => {
let store: MockStore;
let agents$: Subject<Agent[]>;
let dialog: MatDialog;
let activatedRoute: ActivatedRoute;

const prepareBeforeTest = (): void => {
selectionState = {
Expand Down Expand Up @@ -101,6 +102,7 @@ describe('SearchAiInputComponent', () => {
]
});

activatedRoute = TestBed.inject(ActivatedRoute);
fixture = TestBed.createComponent(SearchAiInputComponent);
component = fixture.componentInstance;
store = TestBed.inject(MockStore);
Expand Down Expand Up @@ -133,6 +135,23 @@ describe('SearchAiInputComponent', () => {
expect(selectElement.componentInstance.hideSingleSelectionIndicator).toBeTrue();
});

it('should set queryControl value to searchTerm if searchTerm is defined', () => {
const query = 'some new query';
component.searchTerm = query;

component.ngOnInit();

expect(component.queryControl.value).toBe(query);
});

it('should set queryControl value to query param if searchTerm is not defined', () => {
component.searchTerm = undefined;

component.ngOnInit();

expect(component.queryControl.value).toBe('some query');
});

it('should get agents on init', () => {
agents$.next(agentList);
component.ngOnInit();
Expand Down Expand Up @@ -239,6 +258,11 @@ describe('SearchAiInputComponent', () => {
});

it('should be disabled by default', () => {
activatedRoute.snapshot.queryParams = { query: '' };

component.ngOnInit();
fixture.detectChanges();

expect(submitButton.nativeElement.disabled).toBeTrue();
});

Expand Down Expand Up @@ -371,12 +395,13 @@ describe('SearchAiInputComponent', () => {
spyOn(store, 'dispatch');
submittingTrigger();

expect(store.dispatch).toHaveBeenCalledOnceWith(
expect(store.dispatch).toHaveBeenCalledWith(
new SearchByTermAiAction({
searchTerm: query,
agentId: component.agentId
})
);
expect(store.dispatch).toHaveBeenCalledWith(new ToggleAISearchInput('2', 'some query'));
});

it('should call dispatch on store with correct parameter if selected agent was changed', async () => {
Expand All @@ -388,26 +413,13 @@ describe('SearchAiInputComponent', () => {
});
submittingTrigger();

expect(store.dispatch).toHaveBeenCalledOnceWith(
expect(store.dispatch).toHaveBeenCalledWith(
new SearchByTermAiAction({
searchTerm: query,
agentId: '1'
})
);
});

it('should reset query input', () => {
spyOn(component.queryControl, 'reset');
submittingTrigger();

expect(component.queryControl.reset).toHaveBeenCalled();
});

it('should emit searchSubmitted event', () => {
spyOn(component.searchSubmitted, 'emit');
submittingTrigger();

expect(component.searchSubmitted.emit).toHaveBeenCalled();
expect(store.dispatch).toHaveBeenCalledWith(new ToggleAISearchInput('1', 'some query'));
});

it('should call open modal if there was a previous search phrase in url', () => {
Expand All @@ -418,13 +430,11 @@ describe('SearchAiInputComponent', () => {

it('should open Unsaved Changes Modal and run callback successfully', () => {
const modalAiSpy = spyOn(modalAiService, 'openUnsavedChangesModal').and.callThrough();
spyOn(component.searchSubmitted, 'emit');

fixture.detectChanges();

submittingTrigger();
expect(modalAiSpy).toHaveBeenCalledWith(jasmine.any(Function));
expect(component.searchSubmitted.emit).toHaveBeenCalled();
});
});
}
Expand Down
Loading
Loading