Browse Source

Merge branch 'feature/entities-version-control' of github.com:thingsboard/thingsboard into feature/entities-version-control

pull/6615/head
Andrii Shvaika 4 years ago
parent
commit
8d57010a7b
  1. 9
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java
  2. 2
      common/cluster-api/src/main/proto/queue.proto
  3. 11
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java
  4. 2
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java
  5. 17
      ui-ngx/src/app/core/http/admin.service.ts
  6. 45
      ui-ngx/src/app/core/http/entities-version-control.service.ts
  7. 24
      ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html
  8. 33
      ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts
  9. 8
      ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.html
  10. 140
      ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.ts
  11. 3
      ui-ngx/src/assets/locale/locale.constant-en_US.json

9
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java

@ -150,6 +150,9 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
.setBranchName(branch) .setBranchName(branch)
.setPageSize(pageLink.getPageSize()) .setPageSize(pageLink.getPageSize())
.setPage(pageLink.getPage()) .setPage(pageLink.getPage())
.setSortProperty(pageLink.getSortOrder() != null ? pageLink.getSortOrder().getProperty() : null)
.setSortDirection(pageLink.getSortOrder() != null && pageLink.getSortOrder().getDirection() != null
? pageLink.getSortOrder().getDirection().name() : null)
.build()); .build());
} }
@ -159,6 +162,9 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
.setBranchName(branch).setEntityType(entityType.name()) .setBranchName(branch).setEntityType(entityType.name())
.setPageSize(pageLink.getPageSize()) .setPageSize(pageLink.getPageSize())
.setPage(pageLink.getPage()) .setPage(pageLink.getPage())
.setSortProperty(pageLink.getSortOrder() != null ? pageLink.getSortOrder().getProperty() : null)
.setSortDirection(pageLink.getSortOrder() != null && pageLink.getSortOrder().getDirection() != null
? pageLink.getSortOrder().getDirection().name() : null)
.build()); .build());
} }
@ -171,6 +177,9 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
.setEntityIdLSB(entityId.getId().getLeastSignificantBits()) .setEntityIdLSB(entityId.getId().getLeastSignificantBits())
.setPageSize(pageLink.getPageSize()) .setPageSize(pageLink.getPageSize())
.setPage(pageLink.getPage()) .setPage(pageLink.getPage())
.setSortProperty(pageLink.getSortOrder() != null ? pageLink.getSortOrder().getProperty() : null)
.setSortDirection(pageLink.getSortOrder() != null && pageLink.getSortOrder().getDirection() != null
? pageLink.getSortOrder().getDirection().name() : null)
.build()); .build());
} }

2
common/cluster-api/src/main/proto/queue.proto

@ -724,6 +724,8 @@ message ListVersionsRequestMsg {
int64 entityIdLSB = 4; int64 entityIdLSB = 4;
int32 pageSize = 5; int32 pageSize = 5;
int32 page = 6; int32 page = 6;
string sortProperty = 7;
string sortDirection = 8;
} }
message EntityVersionProto { message EntityVersionProto {

11
common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java

@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder;
import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings; import org.thingsboard.server.common.data.sync.vc.EntitiesVersionControlSettings;
import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; import org.thingsboard.server.common.data.sync.vc.VersionCreationResult;
import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo;
@ -291,7 +292,15 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe
} else { } else {
path = null; path = null;
} }
var data = vcService.listVersions(ctx.getTenantId(), request.getBranchName(), path, new PageLink(request.getPageSize(), request.getPage())); SortOrder sortOrder = null;
if (StringUtils.isNotEmpty(request.getSortProperty())) {
var direction = SortOrder.Direction.DESC;
if (StringUtils.isNotEmpty(request.getSortDirection())) {
direction = SortOrder.Direction.valueOf(request.getSortDirection());
}
sortOrder = new SortOrder(request.getSortProperty(), direction);
}
var data = vcService.listVersions(ctx.getTenantId(), request.getBranchName(), path, new PageLink(request.getPageSize(), request.getPage(), null, sortOrder));
reply(ctx, Optional.empty(), builder -> reply(ctx, Optional.empty(), builder ->
builder.setListVersionsResponse(ListVersionsResponseMsg.newBuilder() builder.setListVersionsResponse(ListVersionsResponseMsg.newBuilder()
.setTotalPages(data.getTotalPages()) .setTotalPages(data.getTotalPages())

2
common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java

@ -281,7 +281,7 @@ public class GitRepository {
// } // }
private Commit toCommit(RevCommit revCommit) { private Commit toCommit(RevCommit revCommit) {
return new Commit(revCommit.getCommitTime() * 1000, revCommit.getName(), revCommit.getFullMessage(), revCommit.getAuthorIdent().getName()); return new Commit(revCommit.getCommitTime() * 1000l, revCommit.getName(), revCommit.getFullMessage(), revCommit.getAuthorIdent().getName());
} }
private RevCommit resolveCommit(String id) throws IOException { private RevCommit resolveCommit(String id) throws IOException {

17
ui-ngx/src/app/core/http/admin.service.ts

@ -26,6 +26,8 @@ import {
TestSmsRequest, TestSmsRequest,
UpdateMessage UpdateMessage
} from '@shared/models/settings.models'; } from '@shared/models/settings.models';
import { EntitiesVersionControlService } from '@core/http/entities-version-control.service';
import { tap } from 'rxjs/operators';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -33,7 +35,8 @@ import {
export class AdminService { export class AdminService {
constructor( constructor(
private http: HttpClient private http: HttpClient,
private entitiesVersionControlService: EntitiesVersionControlService
) { } ) { }
public getAdminSettings<T>(key: string, config?: RequestConfig): Observable<AdminSettings<T>> { public getAdminSettings<T>(key: string, config?: RequestConfig): Observable<AdminSettings<T>> {
@ -72,11 +75,19 @@ export class AdminService {
public saveEntitiesVersionControlSettings(versionControlSettings: EntitiesVersionControlSettings, public saveEntitiesVersionControlSettings(versionControlSettings: EntitiesVersionControlSettings,
config?: RequestConfig): Observable<EntitiesVersionControlSettings> { config?: RequestConfig): Observable<EntitiesVersionControlSettings> {
return this.http.post<EntitiesVersionControlSettings>('/api/admin/vcSettings', versionControlSettings, return this.http.post<EntitiesVersionControlSettings>('/api/admin/vcSettings', versionControlSettings,
defaultHttpOptionsFromConfig(config)); defaultHttpOptionsFromConfig(config)).pipe(
tap(() => {
this.entitiesVersionControlService.clearBranchList();
})
);
} }
public deleteEntitiesVersionControlSettings(config?: RequestConfig) { public deleteEntitiesVersionControlSettings(config?: RequestConfig) {
return this.http.delete('/api/admin/vcSettings', defaultHttpOptionsFromConfig(config)); return this.http.delete('/api/admin/vcSettings', defaultHttpOptionsFromConfig(config)).pipe(
tap(() => {
this.entitiesVersionControlService.clearBranchList();
})
);
} }
public checkVersionControlAccess(versionControlSettings: EntitiesVersionControlSettings, public checkVersionControlAccess(versionControlSettings: EntitiesVersionControlSettings,

45
ui-ngx/src/app/core/http/entities-version-control.service.ts

@ -17,30 +17,63 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
import { Observable } from 'rxjs'; import { combineLatest, Observable, of } from 'rxjs';
import { BranchInfo, EntityVersion, VersionCreateRequest, VersionCreationResult } from '@shared/models/vc.models'; import { BranchInfo, EntityVersion, VersionCreateRequest, VersionCreationResult } from '@shared/models/vc.models';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data'; import { PageData } from '@shared/models/page/page-data';
import { DeviceInfo } from '@shared/models/device.models';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { createSelector, select, Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { selectHasVersionControl, selectIsAuthenticated, selectIsUserLoaded } from '@core/auth/auth.selectors';
import { catchError, combineAll, tap } from 'rxjs/operators';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class EntitiesVersionControlService { export class EntitiesVersionControlService {
branchList: Array<BranchInfo> = null;
constructor( constructor(
private http: HttpClient private http: HttpClient,
private store: Store<AppState>
) { ) {
this.store.pipe(select(selectIsUserLoaded)).subscribe(
() => {
this.branchList = null;
}
);
}
public clearBranchList(): void {
this.branchList = null;
} }
public listBranches(config?: RequestConfig): Observable<Array<BranchInfo>> { public listBranches(): Observable<Array<BranchInfo>> {
return this.http.get<Array<BranchInfo>>('/api/entities/vc/branches', defaultHttpOptionsFromConfig(config)); if (!this.branchList) {
return this.http.get<Array<BranchInfo>>('/api/entities/vc/branches',
defaultHttpOptionsFromConfig({ignoreErrors: true, ignoreLoading: false})).pipe(
catchError(() => of([] as Array<BranchInfo>)),
tap((list) => {
this.branchList = list;
})
);
} else {
return of(this.branchList);
}
} }
public saveEntitiesVersion(request: VersionCreateRequest, config?: RequestConfig): Observable<VersionCreationResult> { public saveEntitiesVersion(request: VersionCreateRequest, config?: RequestConfig): Observable<VersionCreationResult> {
return this.http.post<VersionCreationResult>('/api/entities/vc/version', request, defaultHttpOptionsFromConfig(config)); return this.http.post<VersionCreationResult>('/api/entities/vc/version', request, defaultHttpOptionsFromConfig(config)).pipe(
tap(() => {
const branch = request.branch;
if (this.branchList && !this.branchList.find(b => b.name === branch)) {
this.branchList = null;
}
})
);
} }
public listEntityVersions(pageLink: PageLink, branch: string, public listEntityVersions(pageLink: PageLink, branch: string,

24
ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html

@ -48,27 +48,41 @@
</mat-cell> </mat-cell>
</ng-container> </ng-container>
<ng-container matColumnDef="id"> <ng-container matColumnDef="id">
<mat-header-cell *matHeaderCellDef style="width: 40%"> {{ 'version-control.version-id' | translate }} </mat-header-cell> <mat-header-cell *matHeaderCellDef style="min-width: 100px; max-width: 100px; width: 100px;"> {{ 'version-control.version-id' | translate }} </mat-header-cell>
<mat-cell *matCellDef="let entityVersion"> <mat-cell *matCellDef="let entityVersion">
{{ entityVersion.id }} <span style="display: inline-block; width: 54px;" [innerHTML]="versionIdContent(entityVersion)"></span>
<tb-copy-button
[disabled]="isLoading$ | async"
[copyText]="entityVersion.id"
tooltipText="{{ 'version-control.copy-full-version-id' | translate }}"
tooltipPosition="above"
icon="content_paste"
[style]="{
'font-size': '16px',
color: 'rgba(0,0,0,.87)'
}">
</tb-copy-button>
</mat-cell> </mat-cell>
</ng-container> </ng-container>
<ng-container matColumnDef="name"> <ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef style="width: 60%"> {{ 'version-control.version-name' | translate }} </mat-header-cell> <mat-header-cell *matHeaderCellDef style="width: 100%"> {{ 'version-control.version-name' | translate }} </mat-header-cell>
<mat-cell *matCellDef="let entityVersion"> <mat-cell *matCellDef="let entityVersion">
{{ entityVersion.name }} {{ entityVersion.name }}
</mat-cell> </mat-cell>
</ng-container> </ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row> <mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
<mat-row *matRowDef="let entityVersion; columns: displayedColumns;"></mat-row> <mat-row [fxShow]="!dataSource.dataLoading" *matRowDef="let entityVersion; columns: displayedColumns;"></mat-row>
</table> </table>
<span [fxShow]="dataSource.isEmpty() | async" <span [fxShow]="!(isLoading$ | async) && (dataSource.isEmpty() | async) && !dataSource.dataLoading"
fxLayoutAlign="center center" fxLayoutAlign="center center"
class="no-data-found" translate>{{ class="no-data-found" translate>{{
singleEntityMode singleEntityMode
? 'version-control.no-entity-versions-text' ? 'version-control.no-entity-versions-text'
: 'version-control.no-versions-text' : 'version-control.no-versions-text'
}}</span> }}</span>
<span [fxShow]="dataSource.dataLoading"
fxLayoutAlign="center center"
class="no-data-found">{{ 'common.loading' | translate }}</span>
</div> </div>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<mat-paginator [length]="dataSource.total() | async" <mat-paginator [length]="dataSource.total() | async"

33
ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts

@ -41,6 +41,7 @@ import { ResizeObserver } from '@juggle/resize-observer';
import { hidePageSizePixelValue } from '@shared/models/constants'; import { hidePageSizePixelValue } from '@shared/models/constants';
import { Direction, SortOrder } from '@shared/models/page/sort-order'; import { Direction, SortOrder } from '@shared/models/page/sort-order';
import { BranchAutocompleteComponent } from '@shared/components/vc/branch-autocomplete.component'; import { BranchAutocompleteComponent } from '@shared/components/vc/branch-autocomplete.component';
import { isNotEmptyStr } from '@core/utils';
@Component({ @Component({
selector: 'tb-entity-versions-table', selector: 'tb-entity-versions-table',
@ -125,10 +126,12 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
} }
branchChanged(newBranch: string) { branchChanged(newBranch: string) {
this.branch = newBranch; if (isNotEmptyStr(newBranch) && this.branch !== newBranch) {
this.paginator.pageIndex = 0; this.branch = newBranch;
if (this.activeValue) { this.paginator.pageIndex = 0;
this.updateData(); if (this.activeValue) {
this.updateData();
}
} }
} }
@ -151,8 +154,23 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
} }
} }
versionIdContent(entityVersion: EntityVersion): string {
let versionId = entityVersion.id;
if (versionId.length > 7) {
versionId = versionId.slice(0, 7);
}
return versionId;
}
private initFromDefaultBranch() { private initFromDefaultBranch() {
this.branchAutocompleteComponent.selectDefaultBranchIfNeeded(false, true); if (this.branchAutocompleteComponent.isDefaultBranchSelected()) {
this.paginator.pageIndex = 0;
if (this.activeValue) {
this.updateData();
}
} else {
this.branchAutocompleteComponent.selectDefaultBranchIfNeeded(true);
}
} }
private updateData() { private updateData() {
@ -164,7 +182,6 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
} }
private resetSortAndFilter(update: boolean) { private resetSortAndFilter(update: boolean) {
this.branch = null;
this.pageLink.textSearch = null; this.pageLink.textSearch = null;
if (this.viewsInited) { if (this.viewsInited) {
this.paginator.pageIndex = 0; this.paginator.pageIndex = 0;
@ -185,6 +202,8 @@ class EntityVersionsDatasource implements DataSource<EntityVersion> {
public pageData$ = this.pageDataSubject.asObservable(); public pageData$ = this.pageDataSubject.asObservable();
public dataLoading = true;
constructor(private entitiesVersionControlService: EntitiesVersionControlService) {} constructor(private entitiesVersionControlService: EntitiesVersionControlService) {}
connect(collectionViewer: CollectionViewer): Observable<EntityVersion[] | ReadonlyArray<EntityVersion>> { connect(collectionViewer: CollectionViewer): Observable<EntityVersion[] | ReadonlyArray<EntityVersion>> {
@ -199,6 +218,7 @@ class EntityVersionsDatasource implements DataSource<EntityVersion> {
loadEntityVersions(singleEntityMode: boolean, loadEntityVersions(singleEntityMode: boolean,
branch: string, externalEntityId: EntityId, branch: string, externalEntityId: EntityId,
pageLink: PageLink): Observable<PageData<EntityVersion>> { pageLink: PageLink): Observable<PageData<EntityVersion>> {
this.dataLoading = true;
const result = new ReplaySubject<PageData<EntityVersion>>(); const result = new ReplaySubject<PageData<EntityVersion>>();
this.fetchEntityVersions(singleEntityMode, branch, externalEntityId, pageLink).pipe( this.fetchEntityVersions(singleEntityMode, branch, externalEntityId, pageLink).pipe(
catchError(() => of(emptyPageData<EntityVersion>())), catchError(() => of(emptyPageData<EntityVersion>())),
@ -207,6 +227,7 @@ class EntityVersionsDatasource implements DataSource<EntityVersion> {
this.entityVersionsSubject.next(pageData.data); this.entityVersionsSubject.next(pageData.data);
this.pageDataSubject.next(pageData); this.pageDataSubject.next(pageData);
result.next(pageData); result.next(pageData);
this.dataLoading = false;
} }
); );
return result; return result;

8
ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.html

@ -17,12 +17,14 @@
--> -->
<mat-form-field [formGroup]="branchFormGroup" class="mat-block" [floatLabel]="selectionMode ? 'always' : 'auto'"> <mat-form-field [formGroup]="branchFormGroup" class="mat-block" [floatLabel]="selectionMode ? 'always' : 'auto'">
<mat-label>{{ 'version-control.branch' | translate }}</mat-label> <mat-label>{{ 'version-control.branch' | translate }}</mat-label>
<input matInput type="text" placeholder="{{ 'version-control.select-branch' | translate }}" <input matInput type="text" placeholder="{{(loading ? 'common.loading' : 'version-control.select-branch') | translate}}"
#branchInput #branchInput
formControlName="branch" formControlName="branch"
(keydown.enter)="branchInput.blur(); autoCompleteTrigger.closePanel();"
(focusin)="onFocus()" (focusin)="onFocus()"
(blur)="onBlur()"
[required]="required" [required]="required"
[matAutocomplete]="subTypeAutocomplete"> [matAutocomplete]="branchAutocomplete">
<button *ngIf="branchFormGroup.get('branch').value && !disabled" <button *ngIf="branchFormGroup.get('branch').value && !disabled"
type="button" type="button"
matSuffix mat-button mat-icon-button aria-label="Clear" matSuffix mat-button mat-icon-button aria-label="Clear"
@ -32,7 +34,7 @@
<mat-autocomplete <mat-autocomplete
class="tb-autocomplete" class="tb-autocomplete"
(closed)="onPanelClosed()" (closed)="onPanelClosed()"
#subTypeAutocomplete="matAutocomplete" #branchAutocomplete="matAutocomplete"
[displayWith]="displayBranchFn"> [displayWith]="displayBranchFn">
<mat-option *ngFor="let branch of filteredBranches | async" [value]="branch"> <mat-option *ngFor="let branch of filteredBranches | async" [value]="branch">
<span [innerHTML]="branch.name | highlight:searchText"></span> <span [innerHTML]="branch.name | highlight:searchText"></span>

140
ui-ngx/src/app/shared/components/vc/branch-autocomplete.component.ts

@ -14,36 +14,17 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { import { AfterViewInit, Component, ElementRef, forwardRef, Input, NgZone, OnInit, ViewChild } from '@angular/core';
AfterViewInit,
ChangeDetectorRef,
Component,
ElementRef,
forwardRef,
Input,
NgZone,
OnInit,
ViewChild
} from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { import { debounceTime, distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators';
catchError,
debounceTime,
distinctUntilChanged,
map,
publishReplay,
refCount,
share,
switchMap,
tap
} from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state'; import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { BranchInfo } from '@shared/models/vc.models'; import { BranchInfo } from '@shared/models/vc.models';
import { EntitiesVersionControlService } from '@core/http/entities-version-control.service'; import { EntitiesVersionControlService } from '@core/http/entities-version-control.service';
import { isNotEmptyStr } from '@core/utils'; import { isNotEmptyStr } from '@core/utils';
import { MatAutocomplete, MatAutocompleteTrigger } from '@angular/material/autocomplete';
@Component({ @Component({
selector: 'tb-branch-autocomplete', selector: 'tb-branch-autocomplete',
@ -94,19 +75,21 @@ export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit
@Input() @Input()
selectionMode = false; selectionMode = false;
@ViewChild('branchAutocomplete') matAutocomplete: MatAutocomplete;
@ViewChild('branchInput', { read: MatAutocompleteTrigger, static: true }) autoCompleteTrigger: MatAutocompleteTrigger;
@ViewChild('branchInput', {static: true}) branchInput: ElementRef; @ViewChild('branchInput', {static: true}) branchInput: ElementRef;
filteredBranches: Observable<Array<BranchInfo>>; filteredBranches: Observable<Array<BranchInfo>>;
branches: Observable<Array<BranchInfo>> = null;
defaultBranch: BranchInfo = null; defaultBranch: BranchInfo = null;
searchText = ''; searchText = '';
loading = false;
private dirty = false; private dirty = false;
private ignoreClosedPanel = false; private clearButtonClicked = false;
private propagateChange = (v: any) => { }; private propagateChange = (v: any) => { };
@ -140,7 +123,9 @@ export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit
} else { } else {
modelValue = value; modelValue = value;
} }
this.updateView(modelValue); if (!this.selectionMode || modelValue) {
this.updateView(modelValue);
}
}), }),
map(value => { map(value => {
if (value) { if (value) {
@ -167,17 +152,33 @@ export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit
this.disabled = isDisabled; this.disabled = isDisabled;
} }
selectDefaultBranchIfNeeded(ignoreLoading = true, force = false): void { isDefaultBranchSelected(): boolean {
return this.defaultBranch && this.defaultBranch.name === this.modelValue;
}
selectDefaultBranchIfNeeded(force = false): void {
if ((this.selectDefaultBranch && !this.modelValue) || force) { if ((this.selectDefaultBranch && !this.modelValue) || force) {
this.getBranches(ignoreLoading).subscribe( setTimeout(() => {
(data) => { if (this.defaultBranch) {
if (this.defaultBranch || force) { this.branchFormGroup.get('branch').patchValue(this.defaultBranch, {emitEvent: false});
this.branchFormGroup.get('branch').patchValue(this.defaultBranch, {emitEvent: false}); this.modelValue = this.defaultBranch?.name;
this.modelValue = this.defaultBranch?.name; this.propagateChange(this.modelValue);
this.propagateChange(this.modelValue); } else {
} this.loading = true;
this.getBranches().subscribe(
() => {
if (this.defaultBranch || force) {
this.branchFormGroup.get('branch').patchValue(this.defaultBranch, {emitEvent: false});
this.modelValue = this.defaultBranch?.name;
this.propagateChange(this.modelValue);
this.loading = false;
} else {
this.loading = false;
}
}
);
} }
); });
} }
} }
@ -200,14 +201,44 @@ export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit
} }
} }
onBlur() {
if (this.clearButtonClicked) {
this.clearButtonClicked = false;
} else if (!this.matAutocomplete.isOpen) {
this.selectAvailableValue();
}
}
onPanelClosed() { onPanelClosed() {
if (this.ignoreClosedPanel) { this.selectAvailableValue();
this.ignoreClosedPanel = false; }
} else {
if (this.selectionMode && !this.branchFormGroup.get('branch').value && this.defaultBranch) { selectAvailableValue() {
this.zone.run(() => { if (this.selectionMode) {
this.branchFormGroup.get('branch').patchValue(this.defaultBranch, {emitEvent: true}); const branch = this.branchFormGroup.get('branch').value;
}, 0); let branchInfo: Observable<BranchInfo> = null;
if (isNotEmptyStr(branch)) {
branchInfo = this.getBranches().pipe(
map(branches => {
let foundBranch = branches.find(b => b.name === branch);
if (!foundBranch && isNotEmptyStr(this.modelValue)) {
foundBranch = branches.find(b => b.name === this.modelValue);
}
return foundBranch;
})
);
} else if (!branch) {
branchInfo = of(null);
}
if (branchInfo) {
branchInfo.subscribe((val) => {
if (!val && this.defaultBranch) {
val = this.defaultBranch;
}
this.zone.run(() => {
this.branchFormGroup.get('branch').patchValue(val, {emitEvent: true});
}, 0);
});
} }
} }
} }
@ -223,7 +254,7 @@ export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit
return branch ? branch.name : undefined; return branch ? branch.name : undefined;
} }
fetchBranches(searchText?: string): Observable<Array<BranchInfo>> { private fetchBranches(searchText?: string): Observable<Array<BranchInfo>> {
this.searchText = searchText; this.searchText = searchText;
return this.getBranches().pipe( return this.getBranches().pipe(
map(branches => { map(branches => {
@ -239,25 +270,18 @@ export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit
); );
} }
getBranches(ignoreLoading = true): Observable<Array<BranchInfo>> { private getBranches(): Observable<Array<BranchInfo>> {
if (!this.branches) { return this.entitiesVersionControlService.listBranches().pipe(
const branchesObservable = this.entitiesVersionControlService.listBranches({ignoreLoading, ignoreErrors: true}); tap((data) => {
this.branches = branchesObservable.pipe( this.defaultBranch = data.find(branch => branch.default);
catchError(() => of([] as Array<BranchInfo>)), })
tap((data) => { );
this.defaultBranch = data.find(branch => branch.default);
}),
publishReplay(1),
refCount()
);
}
return this.branches;
} }
clear() { clear() {
this.ignoreClosedPanel = true; this.clearButtonClicked = true;
this.branchFormGroup.get('branch').patchValue(null, {emitEvent: true});
setTimeout(() => { setTimeout(() => {
this.branchFormGroup.get('branch').patchValue(null, {emitEvent: true});
this.branchInput.nativeElement.blur(); this.branchInput.nativeElement.blur();
this.branchInput.nativeElement.focus(); this.branchInput.nativeElement.focus();
}, 0); }, 0);

3
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3126,7 +3126,8 @@
"created-time": "Created time", "created-time": "Created time",
"version-id": "Version ID", "version-id": "Version ID",
"no-entity-versions-text": "No entity versions found", "no-entity-versions-text": "No entity versions found",
"no-versions-text": "No versions found" "no-versions-text": "No versions found",
"copy-full-version-id": "Copy full version id"
}, },
"widget": { "widget": {
"widget-library": "Widgets Library", "widget-library": "Widgets Library",

Loading…
Cancel
Save