This is the codeAbility Sharing Platform! Learn more about the codeAbility Sharing Platform.

Skip to content
Snippets Groups Projects
search.component.ts 8.61 KiB
Newer Older
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subject, Subscription } from 'rxjs';

import { LoginModalService } from 'app/core/login/login-modal.service';
import { AccountService } from 'app/core/auth/account.service';
import { Account } from 'app/core/user/account.model';
import { HttpResponse } from '@angular/common/http';
import { ActivatedRoute, Router } from '@angular/router';
import { QueryParam, QueryParamBuilder, QueryParamGroup } from '@ngqp/core';
import { takeUntil } from 'rxjs/operators';
import { IFrequency } from 'app/shared/model/frequency.model';
import { IGitFilesPageDetails, GitFilesPageDetails } from 'app/shared/model/git-files-page-details.model';
import { IGitFilesAggregation } from 'app/shared/model/git-files-aggregation';
import { IGitFiles } from 'app/shared/model/git-files.model';
import { SearchInput } from 'app/shared/model/search-input.model';
import { SearchResultService } from 'app/search/search/search-result.service';
Daniel Rainer's avatar
Daniel Rainer committed
import { MetadataMessageService } from 'app/search/metadata/metadata-message.service';
import { IMetadataSelection } from 'app/search/metadata/metadata-selection.component';
Lukas Kaltenbrunner's avatar
Lukas Kaltenbrunner committed

@Component({
  selector: 'jhi-search',
  templateUrl: './search.component.html',
  styleUrls: ['search.scss'],
})
export class SearchComponent implements OnInit, OnDestroy {
  private static DEBOUNCE_TIME = 200;
  public pageSize = 4;

  account: Account | null = null;
  authSubscription?: Subscription;
Daniel Rainer's avatar
Daniel Rainer committed

  // Stores all search results for the current query.
  // Used to locally apply filters for e.g. repository and file format.
  // When local filters change, the results are recalculated from this variable.
Daniel Rainer's avatar
Daniel Rainer committed
  private queryResult?: IGitFilesPageDetails;
Lukas Kaltenbrunner's avatar
Lukas Kaltenbrunner committed
  gitFilesPageDetails?: IGitFilesPageDetails;
  gitFilesAggregation?: IGitFilesAggregation;

  private selectedFiltersSubscription: Subscription = new Subscription();
  private selectedRepository: string | null = null;
  private selectedUniversity: string | null = null;
  private selectedFileFormat: string | null = null;
Lukas Kaltenbrunner's avatar
Lukas Kaltenbrunner committed

  public paramGroup: QueryParamGroup;
  private componentDestroyed$ = new Subject<void>();

  public searchInput: SearchInput;

  constructor(
    protected searchResultService: SearchResultService,
    private accountService: AccountService,
    private loginModalService: LoginModalService,
    protected activatedRoute: ActivatedRoute,
    private router: Router,
    private qpb: QueryParamBuilder,
Daniel Rainer's avatar
Daniel Rainer committed
    private filterSelectionService: MetadataMessageService
Lukas Kaltenbrunner's avatar
Lukas Kaltenbrunner committed
  ) {
    this.searchInput = new SearchInput();
Lukas Kaltenbrunner's avatar
Lukas Kaltenbrunner committed

    this.paramGroup = qpb.group({
      searchText: qpb.stringParam('q', {
        debounceTime: SearchComponent.DEBOUNCE_TIME,
        emptyOn: '',
      }),
      page: qpb.numberParam('page', {
        emptyOn: 1,
      }),
      programmingLanguage: qpb.stringParam('pl', {
        debounceTime: SearchComponent.DEBOUNCE_TIME,
        emptyOn: '',
      }),
      keywords: qpb.stringParam('kw', {
        debounceTime: SearchComponent.DEBOUNCE_TIME,
        emptyOn: '',
      }),
      author: qpb.stringParam('a', {
        debounceTime: SearchComponent.DEBOUNCE_TIME,
        emptyOn: '',
      }),
      license: qpb.stringParam('l', {
        debounceTime: SearchComponent.DEBOUNCE_TIME,
        emptyOn: '',
      }),
    });

    this.paramGroup.valueChanges.pipe(takeUntil(this.componentDestroyed$)).subscribe(value => {
      this.searchInput.setValues(value);
      this.search();
    });
  }

  filter(): void {
    if (!this.gitFilesPageDetails?.hitCount || this.gitFilesPageDetails.hitCount <= 0) {
      return;
    }
    this.loadPageDetails();
  }

  search(): void {
    if (this.searchInput.fulltextQuery === '') {
      this.gitFilesAggregation = undefined;
      this.gitFilesPageDetails = undefined;
      return;
    }
    this.loadAggregation();
    this.loadPageDetails();
  }

  ngOnInit(): void {
    this.loadAggregation();
    this.loadPageDetails();
    this.authSubscription = this.accountService.getAuthenticationState().subscribe(account => (this.account = account));
    this.selectedFiltersSubscription.add(
      this.filterSelectionService.filterSelection$.subscribe(selection => this.updateSearchInfoFilter(selection))
Lukas Kaltenbrunner's avatar
Lukas Kaltenbrunner committed
  }

  loadAggregation(): void {
    if (this.searchInput.fulltextQuery) {
      this.searchResultService
        .searchAggregation({
          query: this.searchInput.fulltextQuery,
        })
        .subscribe((res: HttpResponse<IGitFilesAggregation>) => {
          this.gitFilesAggregation = res.body || undefined;
        });
    }
  }

  loadPageDetails(): void {
    if (this.searchInput.fulltextQuery) {
      this.searchResultService.searchPageDetails(this.searchInput, this.pageSize).subscribe((res: HttpResponse<IGitFilesPageDetails>) => {
        if (res.body === null) {
Daniel Rainer's avatar
Daniel Rainer committed
          this.queryResult = undefined;
        } else {
Daniel Rainer's avatar
Daniel Rainer committed
          this.queryResult = new GitFilesPageDetails(res.body.gitFiles, res.body.gitFiles.length);
          this.updatePageDetails();
        }
      });
Daniel Rainer's avatar
Daniel Rainer committed
  // Applies local filters to search results
  // and sets this.gitFilesPageDetails accordingly.
  private updatePageDetails(): void {
Daniel Rainer's avatar
Daniel Rainer committed
    if (this.queryResult !== undefined) {
      const matchingResults = this.queryResult.gitFiles.filter(element => this.matchesSelection(element));
      if (matchingResults.length > 0) {
        this.gitFilesPageDetails = new GitFilesPageDetails(matchingResults, matchingResults.length);
      } else {
        this.gitFilesPageDetails = undefined;
      }
    }
  }

Daniel Rainer's avatar
Daniel Rainer committed
  // When the user updates the metadata filter selection
  // this function is called.
  // It updates the local filters.
  // Currently, for any category (at the moment repository and file format)
  // there is either one value all search results have to satisfy
  // (e.g. display only files from a certain repository)
  // or the filter is not set.
  // Clicking a filter value which is already being applied
  // results in this filter to be reset. (Does not filter by this category)
  public updateSearchInfoFilter(selection: IMetadataSelection): void {
    switch (selection.category) {
      case 'repository': {
        if (this.selectedRepository === selection.value) {
          this.selectedRepository = null;
        } else {
          this.selectedRepository = selection.value;
        }
        break;
      }
      case 'university': {
Daniel Rainer's avatar
Daniel Rainer committed
        // University seems to be a derived attribute.
        // Currently not implemented.
        alert('Filtering by university is not supported at the moment. Cause: University is not in the interface IGitFiles.');
        /* if (this.selectedUniversity === selection.value) {
          this.selectedUniversity = null;
        } else {
          this.selectedUniversity = selection.value;
        } */
        break;
      }
      case 'fileFormat': {
        if (this.selectedFileFormat === selection.value) {
          this.selectedFileFormat = null;
        } else {
          this.selectedFileFormat = selection.value;
        }
        break;
      }
    }
    this.updatePageDetails();
  }

Daniel Rainer's avatar
Daniel Rainer committed
  // Checks if a given file matches the local filters.
  matchesSelection(file: IGitFiles): boolean {
    if (this.selectedRepository && file.repository !== this.selectedRepository) {
      return false;
    }
    /* if (this.selectedUniversity && file.university !== this.selectedUniversity) {
      return false;
    } */
    if (this.selectedFileFormat && file.fileFormat !== this.selectedFileFormat) {
      return false;
Lukas Kaltenbrunner's avatar
Lukas Kaltenbrunner committed
  isAuthenticated(): boolean {
    return this.accountService.isAuthenticated();
  }

  login(): void {
    this.loginModalService.open();
  }

  ngOnDestroy(): void {
    if (this.authSubscription) {
      this.authSubscription.unsubscribe();
    }
    if (this.selectedFiltersSubscription) {
      this.selectedFiltersSubscription.unsubscribe();
    }
Lukas Kaltenbrunner's avatar
Lukas Kaltenbrunner committed
    this.componentDestroyed$.next();
    this.componentDestroyed$.complete();
  }

  onClickMe(gitFiles: IGitFiles): void {
    window.location.href = gitFiles.gitUrl;
  }

  public get gitFiles(): IGitFiles[] {
    return this.gitFilesPageDetails?.gitFiles || [];
  }

  public get repos(): IFrequency<string>[] {
    return this.gitFilesAggregation?.repositories || [];
  }

  public get university(): IFrequency<string>[] {
    return this.gitFilesAggregation?.university || [];
  }

  public get fileFormat(): IFrequency<string>[] {
    return this.gitFilesAggregation?.fileFormat || [];
  }

  public get pageParam(): QueryParam<number> {
    return this.paramGroup.get('page') as QueryParam<number>;
  }

  public onPageChange(page: number): void {
    this.pageParam.setValue(page);
  }

  public get hitCount(): number {
    const hits = this.gitFilesPageDetails?.hitCount;
    return hits ? hits : 0;
  }
}