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

Autocomplete git repository name #3372

Merged
merged 13 commits into from
Feb 14, 2019
Merged
Prev Previous commit
Next Next commit
Git Repo autocomplete
  • Loading branch information
nwmac committed Nov 23, 2018
commit 2d2757c9d7119db7d3ed063a4bd3b8a80674828a
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
<div class="github-project-details">
<div>
<mat-form-field>
<input matInput [disabled]="isRedeploy" [(ngModel)]="repository" placeholder="Project" name="projectName" [appGithubProjectExists]="sourceType.id" required>
<input type="text" matInput [matAutocomplete]="auto" [disabled]="isRedeploy" [(ngModel)]="repository" placeholder="Project" name="projectName" [appGithubProjectExists]="sourceType.id" required>
<!-- Repository auto complete helper -->
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
<mat-option *ngFor="let repo of suggestedRepos$ | async" [value]="repo">{{repo}}</mat-option>
</mat-autocomplete>

<mat-error *ngIf="sourceSelectionForm.controls.projectName?.errors?.githubProjectDoesNotExist && !sourceSelectionForm.controls.projectName?.errors?.githubProjectError">
Project does not exist
</mat-error>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { AfterContentInit, Component, Inject, Input, OnDestroy, OnInit, ViewChil
import { NgForm } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { Store } from '@ngrx/store';
import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
import { filter, map, take, tap, withLatestFrom } from 'rxjs/operators';
import { combineLatest as observableCombineLatest, Observable, timer as observableTimer, of as observableOf, Subscription } from 'rxjs';
import { filter, map, take, tap, withLatestFrom, switchMap } from 'rxjs/operators';

import { EntityServiceFactory } from '../../../../core/entity-service-factory.service';
import { StepOnNextFunction } from '../../../../shared/components/stepper/step/step.component';
Expand Down Expand Up @@ -76,9 +76,16 @@ export class DeployApplicationStep2Component

scm: GitSCM;

// We don't have any repositories to suggest initially - need user to start typing
suggestedRepos$: Observable<string[]>;

cachedSuggestions = {};

// Local FS data when file or folder upload
// @Input('fsSourceData') fsSourceData;

lastProjectName: string = null;

@ViewChild('sourceSelectionForm') sourceSelectionForm: NgForm;
subscriptions: Array<Subscription> = [];

Expand Down Expand Up @@ -274,6 +281,31 @@ export class DeployApplicationStep2Component
this.subscriptions.push(setInitialSourceType$.subscribe());
this.subscriptions.push(setSourceTypeModel$.subscribe());
this.subscriptions.push(setProjectName.subscribe());

this.subscriptions.push(this.sourceSelectionForm.valueChanges.subscribe(form => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be about to do something like

this.suggestedRepos$ = this.sourceSelectionForm.valueChanges.pipe(
map(form => form.projectName),
startWith(''),
pairwise(),
filter(([oldName, newName]) => oldName !=== newName),
switchMap(([oldName, newName]) => this.updateSuggestedRepositories(newName))
)

and avoid the need for the extra subscription and lastProjectName.

if (form.projectName !== this.lastProjectName) {
// Go and fetch the matching list of repositories and make that the auto-complete list
this.suggestedRepos$ = this.updateSuggestedRepositories(form.projectName);
}
this.lastProjectName = form.projectName;
}));
}

updateSuggestedRepositories(name: string): Observable<string[]> {
if (!name || name.length < 3) {
return observableOf([] as string[]);
}

const cacheName = this.scm.getType() + ':' + name;
if (this.cachedSuggestions[cacheName]) {
return observableOf(this.cachedSuggestions[cacheName]);
}

return observableTimer(500).pipe(
take(1),
switchMap(() => this.scm.getMacthingRepositories(name)),
tap(suggestions => this.cachedSuggestions[cacheName] = suggestions)
);
}

setSourceType = event => this.store.dispatch(new SetAppSourceDetails(event));
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/app/shared/components/list/list.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<div class="list-component__header__left--text" *ngIf="(dataSource.isSelecting$ | async)">{{dataSource.selectedRows.size}} Selected</div>
<!-- Multi filters, such as filter app wall by cf/org/space -->
<div class="list-component__header__left--multi-filters" [hidden]="(!(hasRows$ | async) && !filter) || (isAddingOrSelecting$ | async)">
<mat-form-field *ngFor="let multiFilterConfig of multiFilterConfigs" [floatLabel]="'never'">
<mat-form-field *ngFor="let multiFilterConfig of multiFilterConfigs" [floatLabel]="'never'" id="list-component__header-filter_{{multiFilterConfig.key}}">
<mat-select id="{{multiFilterConfig.key}}" matInput [(value)]="multiFilters[multiFilterConfig.key]" [disabled]="(dataSource.isLoadingPage$ | async) || (multiFilterConfig.loading$ | async) || !(multiFilterConfig.list$ | async) || !(multiFilterConfig.list$ | async).length" (selectionChange)="multiFilterConfig.select.next($event.value)">
<mat-option>All</mat-option>
<mat-option *ngFor="let selectItem of multiFilterConfig.list$ | async" [value]="selectItem.value">
Expand All @@ -29,7 +29,7 @@
<div class="list-component__header__right">
<!-- Filter by text input -->
<div class="filter" [hidden]="!config.enableTextFilter || (!(hasRows$ | async) && !filter) || (dataSource.isAdding$ | async)">
<mat-form-field floatLabel="never">
<mat-form-field floatLabel="never" class="list-component__header__right-filter">
<input matInput [ngModel]="filterString" #filter="ngModel" [disabled]="(dataSource.isLoadingPage$ | async)" name="filter" placeholder="{{config.text?.filter || 'Filter'}}">
</mat-form-field>
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/frontend/app/shared/components/list/list.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@
justify-content: flex-end;
}

&__right-filter {
width: auto;
}

&__left {
flex: 1;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,11 @@
}
}
}

.list-component__header__right-filter .mat-form-field-infix {
width: auto;
}

#list-component__header-filter_cf .mat-form-field-infix {
width: 140px;
}
17 changes: 16 additions & 1 deletion src/frontend/app/shared/data-services/scm/github-scm.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { GitSCM, SCMIcon } from './scm';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { map, filter } from 'rxjs/operators';
import { Http } from '@angular/http';
import { GitSCMType } from './scm.service';
import { GitRepo, GitCommit, GitBranch } from '../../../store/types/git.types';
Expand Down Expand Up @@ -60,4 +60,19 @@ export class GitHubSCM implements GitSCM {
return `https://github.com/${projectName}/compare/${commitSha1}...${commitSha2}`;
}

getMacthingRepositories(projectName: string): Observable<string[]> {
const prjParts = projectName.split('/');
let url = `${this.gitHubURL}/search/repositories?q=${projectName}+in:name`;
if (prjParts.length > 1) {
url = `${this.gitHubURL}/search/repositories?q=${prjParts[1]}+in:name+user:${prjParts[0]}`;
}
return this.http.get(url).pipe(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use httpClient (see my review comment below)

map(response => response.json()),
filter(repos => !!repos.items),
map(repos => {
return repos.items.map(item => item.full_name);
})
);
}

}
14 changes: 14 additions & 0 deletions src/frontend/app/shared/data-services/scm/gitlab-scm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,20 @@ export class GitLabSCM implements GitSCM {
return `https://gitlab.com/${projectName}/compare/${commitSha1}...${commitSha2}`;
}

getMacthingRepositories(projectName: string): Observable<string[]> {
const prjParts = projectName.split('/');
let url = `${gitLabAPIUrl}/projects?search=${projectName}`;
if (prjParts.length > 1) {
url = `${gitLabAPIUrl}/users/${prjParts[0]}/projects?search=${prjParts[1]}`;
}
return this.http.get(url).pipe(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use angular's httpClient (https://angular.io/guide/http) as the old Http is deprecated. httpClient removes the need to do response.json()

map(response => response.json()),
map(repos => {
return repos.map(item => item.path_with_namespace);
})
);
}

private convertProject(prj: any): GitRepo {
return {
...prj,
Expand Down
1 change: 1 addition & 0 deletions src/frontend/app/shared/data-services/scm/scm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ export interface GitSCM {
getCloneURL(projectName: string): string;
getCommitURL(projectName: string, commitSha: string): string;
getCompareCommitURL(projectName: string, commitSha1: string, commitSha2: string): string;
getMacthingRepositories(projectName: string): Observable<string[]>;
}
Binary file modified src/frontend/assets/fonts/stratos/stratos-icons.eot
Binary file not shown.
43 changes: 17 additions & 26 deletions src/frontend/assets/fonts/stratos/stratos-icons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/frontend/assets/fonts/stratos/stratos-icons.ttf
Binary file not shown.
Binary file modified src/frontend/assets/fonts/stratos/stratos-icons.woff
Binary file not shown.
Binary file modified src/frontend/assets/fonts/stratos/stratos-icons.woff2
Binary file not shown.