UI: Add entity versions text search support. Minor improvements and fixes.

This commit is contained in:
Igor Kulikov 2022-05-26 11:11:58 +03:00
parent 80456b9b7d
commit 65dc70c51b
13 changed files with 185 additions and 42 deletions

View File

@ -135,6 +135,8 @@ public class ControllerConstants {
protected static final String EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION = "Assignment works in async way - first, notification event pushed to edge service queue on platform. ";
protected static final String EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION = "(Edge will receive this instantly, if it's currently connected, or once it's going to be connected to platform). ";
protected static final String ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the entity version name.";
protected static final String MARKDOWN_CODE_BLOCK_START = "```json\n";
protected static final String MARKDOWN_CODE_BLOCK_END = "\n```";
protected static final String EVENT_ERROR_FILTER_OBJ = MARKDOWN_CODE_BLOCK_START +

View File

@ -53,7 +53,12 @@ import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static org.thingsboard.server.controller.ControllerConstants.*;
import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES;
@RestController
@ -131,13 +136,15 @@ public class EntitiesVersionControlController extends BaseController {
@RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
@ApiParam(value = ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "timestamp")
@RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
EntityId externalEntityId = EntityIdFactory.getByTypeAndUuid(entityType, externalEntityUuid);
PageLink pageLink = createPageLink(pageSize, page, null, sortProperty, sortOrder);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return wrapFuture(versionControlService.listEntityVersions(getTenantId(), branch, externalEntityId, pageLink));
} catch (Exception e) {
throw handleException(e);
@ -158,12 +165,14 @@ public class EntitiesVersionControlController extends BaseController {
@RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
@ApiParam(value = ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "timestamp")
@RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, null, sortProperty, sortOrder);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return wrapFuture(versionControlService.listEntityTypeVersions(getTenantId(), branch, entityType, pageLink));
} catch (Exception e) {
throw handleException(e);
@ -191,12 +200,14 @@ public class EntitiesVersionControlController extends BaseController {
@RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
@ApiParam(value = ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "timestamp")
@RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, null, sortProperty, sortOrder);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return wrapFuture(versionControlService.listVersions(getTenantId(), branch, pageLink));
} catch (Exception e) {
throw handleException(e);

View File

@ -146,41 +146,54 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
@Override
public ListenableFuture<PageData<EntityVersion>> listVersions(TenantId tenantId, String branch, PageLink pageLink) {
return listVersions(tenantId, ListVersionsRequestMsg.newBuilder()
.setBranchName(branch)
.setPageSize(pageLink.getPageSize())
.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());
return listVersions(tenantId,
applyPageLinkParameters(
ListVersionsRequestMsg.newBuilder()
.setBranchName(branch),
pageLink
).build());
}
@Override
public ListenableFuture<PageData<EntityVersion>> listVersions(TenantId tenantId, String branch, EntityType entityType, PageLink pageLink) {
return listVersions(tenantId, ListVersionsRequestMsg.newBuilder()
.setBranchName(branch).setEntityType(entityType.name())
.setPageSize(pageLink.getPageSize())
.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());
return listVersions(tenantId,
applyPageLinkParameters(
ListVersionsRequestMsg.newBuilder()
.setBranchName(branch)
.setEntityType(entityType.name()),
pageLink
).build());
}
@Override
public ListenableFuture<PageData<EntityVersion>> listVersions(TenantId tenantId, String branch, EntityId entityId, PageLink pageLink) {
return listVersions(tenantId, ListVersionsRequestMsg.newBuilder()
.setBranchName(branch)
.setEntityType(entityId.getEntityType().name())
.setEntityIdMSB(entityId.getId().getMostSignificantBits())
.setEntityIdLSB(entityId.getId().getLeastSignificantBits())
.setPageSize(pageLink.getPageSize())
.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());
return listVersions(tenantId,
applyPageLinkParameters(
ListVersionsRequestMsg.newBuilder()
.setBranchName(branch)
.setEntityType(entityId.getEntityType().name())
.setEntityIdMSB(entityId.getId().getMostSignificantBits())
.setEntityIdLSB(entityId.getId().getLeastSignificantBits()),
pageLink
).build());
}
private ListVersionsRequestMsg.Builder applyPageLinkParameters(ListVersionsRequestMsg.Builder builder, PageLink pageLink) {
builder.setPageSize(pageLink.getPageSize())
.setPage(pageLink.getPage());
if (pageLink.getTextSearch() != null) {
builder.setTextSearch(pageLink.getTextSearch());
}
if (pageLink.getSortOrder() != null) {
if (pageLink.getSortOrder().getProperty() != null) {
builder.setSortProperty(pageLink.getSortOrder().getProperty());
}
if (pageLink.getSortOrder().getDirection() != null) {
builder.setSortDirection(pageLink.getSortOrder().getDirection().name());
}
}
return builder;
}
private ListenableFuture<PageData<EntityVersion>> listVersions(TenantId tenantId, ListVersionsRequestMsg requestMsg) {

View File

@ -54,7 +54,7 @@ public class DefaultTbVersionControlSettingsService implements TbVersionControlS
@Override
public EntitiesVersionControlSettings get(TenantId tenantId) {
return cache.getAndPutInTransaction(tenantId, () -> {
EntitiesVersionControlSettings settings = cache.getAndPutInTransaction(tenantId, () -> {
AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(tenantId, SETTINGS_KEY);
if (adminSettings != null) {
try {
@ -65,6 +65,10 @@ public class DefaultTbVersionControlSettingsService implements TbVersionControlS
}
return null;
}, true);
if (settings != null) {
settings = new EntitiesVersionControlSettings(settings);
}
return settings;
}
@Override

View File

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

View File

@ -31,4 +31,17 @@ public class EntitiesVersionControlSettings implements Serializable {
private String privateKey;
private String privateKeyPassword;
private String defaultBranch;
public EntitiesVersionControlSettings() {}
public EntitiesVersionControlSettings(EntitiesVersionControlSettings settings) {
this.repositoryUri = settings.getRepositoryUri();
this.authMethod = settings.getAuthMethod();
this.username = settings.getUsername();
this.password = settings.getPassword();
this.privateKeyFileName = settings.getPrivateKeyFileName();
this.privateKey = settings.getPrivateKey();
this.privateKeyPassword = settings.getPrivateKeyPassword();
this.defaultBranch = settings.getDefaultBranch();
}
}

View File

@ -300,7 +300,8 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe
}
sortOrder = new SortOrder(request.getSortProperty(), direction);
}
var data = vcService.listVersions(ctx.getTenantId(), request.getBranchName(), path, new PageLink(request.getPageSize(), request.getPage(), null, sortOrder));
var data = vcService.listVersions(ctx.getTenantId(), request.getBranchName(), path,
new PageLink(request.getPageSize(), request.getPage(), request.getTextSearch(), sortOrder));
reply(ctx, Optional.empty(), builder ->
builder.setListVersionsResponse(ListVersionsResponseMsg.newBuilder()
.setTotalPages(data.getTotalPages())

View File

@ -36,6 +36,7 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.RefSpec;
@ -173,11 +174,18 @@ public class GitRepository {
return new PageData<>();
}
LogCommand command = git.log()
.add(branchId)
.setRevFilter(RevFilter.NO_MERGES);
.add(branchId);
if (StringUtils.isNotEmpty(pageLink.getTextSearch())) {
command.setRevFilter(new NoMergesAndCommitMessageFilter(pageLink.getTextSearch()));
} else {
command.setRevFilter(RevFilter.NO_MERGES);
}
if (StringUtils.isNotEmpty(path)) {
command.addPath(path);
}
Iterable<RevCommit> commits = execute(command);
return iterableToPageData(commits, this::toCommit, pageLink, revCommitComparatorFunction);
}
@ -391,6 +399,35 @@ public class GitRepository {
return keyPairs;
}
private static class NoMergesAndCommitMessageFilter extends RevFilter {
private final String textSearch;
NoMergesAndCommitMessageFilter(String textSearch) {
this.textSearch = textSearch.toLowerCase();
}
@Override
public boolean include(RevWalk walker, RevCommit c) {
return c.getParentCount() < 2 && c.getFullMessage().toLowerCase().contains(this.textSearch);
}
@Override
public RevFilter clone() {
return this;
}
@Override
public boolean requiresCommitBody() {
return false;
}
@Override
public String toString() {
return "NO_MERGES_AND_COMMIT_MESSAGE";
}
}
@Data
public static class Commit {
private final long timestamp;

View File

@ -285,6 +285,7 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
this.attributeScopeSelectionReadonly = true;
}
this.mode = 'default';
this.textSearchMode = false;
this.selectedWidgetsBundleAlias = null;
this.attributeScope = this.defaultAttributeScope;
this.pageLink.textSearch = null;

View File

@ -547,6 +547,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa
}
resetSortAndFilter(update: boolean = true, preserveTimewindow: boolean = false) {
this.textSearchMode = false;
this.pageLink.textSearch = null;
if (this.entitiesTableConfig.useTimePageLink && !preserveTimewindow) {
this.timewindow = this.entitiesTableConfig.defaultTimewindowInterval;

View File

@ -17,7 +17,7 @@
-->
<div class="mat-padding tb-entity-table tb-absolute-fill">
<div fxFlex fxLayout="column" class="mat-elevation-z1 tb-entity-table-content">
<mat-toolbar class="mat-table-toolbar">
<mat-toolbar class="mat-table-toolbar" [fxShow]="!textSearchMode">
<div class="mat-toolbar-tools">
<div fxLayout="row" fxLayoutAlign="start center" fxLayout.xs="column" fxLayoutAlign.xs="center start" class="title-container">
<span class="tb-entity-table-title">{{(singleEntityMode ? 'version-control.entity-versions' : 'version-control.versions') | translate}}</span>
@ -38,6 +38,33 @@
<mat-icon>update</mat-icon>
{{'version-control.create-version' | translate }}
</button>
<button mat-icon-button
[disabled]="isLoading$ | async"
(click)="enterFilterMode()"
matTooltip="{{ 'action.search' | translate }}"
matTooltipPosition="above">
<mat-icon>search</mat-icon>
</button>
</div>
</mat-toolbar>
<mat-toolbar class="mat-table-toolbar" [fxShow]="textSearchMode">
<div class="mat-toolbar-tools">
<button mat-icon-button
matTooltip="{{ 'action.search' | translate }}"
matTooltipPosition="above">
<mat-icon>search</mat-icon>
</button>
<mat-form-field fxFlex>
<mat-label>&nbsp;</mat-label>
<input #searchInput matInput
[(ngModel)]="pageLink.textSearch"
placeholder="{{ 'common.enter-search' | translate }}"/>
</mat-form-field>
<button mat-icon-button (click)="exitFilterMode()"
matTooltip="{{ 'action.close' | translate }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div>
</mat-toolbar>
<div fxFlex class="table-container">

View File

@ -29,10 +29,10 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { EntityId } from '@shared/models/id/entity-id';
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { BehaviorSubject, merge, Observable, of, ReplaySubject } from 'rxjs';
import { BehaviorSubject, fromEvent, merge, Observable, of, ReplaySubject } from 'rxjs';
import { emptyPageData, PageData } from '@shared/models/page/page-data';
import { PageLink } from '@shared/models/page/page-link';
import { catchError, map, tap } from 'rxjs/operators';
import { catchError, debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators';
import { EntityVersion, VersionCreationResult } from '@shared/models/vc.models';
import { EntitiesVersionControlService } from '@core/http/entities-version-control.service';
import { MatPaginator } from '@angular/material/paginator';
@ -61,6 +61,7 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
displayedColumns = ['timestamp', 'id', 'name'];
pageLink: PageLink;
textSearchMode = false;
dataSource: EntityVersionsDatasource;
hidePageSize = false;
@ -103,6 +104,8 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
@Input()
entityId: EntityId;
@ViewChild('searchInput') searchInputField: ElementRef;
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
@ -148,6 +151,17 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
}
ngAfterViewInit() {
fromEvent(this.searchInputField.nativeElement, 'keyup')
.pipe(
debounceTime(400),
distinctUntilChanged(),
tap(() => {
this.paginator.pageIndex = 0;
this.updateData();
})
)
.subscribe();
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
merge(this.sort.sortChange, this.paginator.page)
.pipe(
@ -155,7 +169,7 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
)
.subscribe();
this.viewsInited = true;
if (!this.singleEntityMode) {
if (!this.singleEntityMode || (this.activeValue && this.externalEntityIdValue)) {
this.initFromDefaultBranch();
}
}
@ -200,6 +214,22 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
return versionId;
}
enterFilterMode() {
this.textSearchMode = true;
this.pageLink.textSearch = '';
setTimeout(() => {
this.searchInputField.nativeElement.focus();
this.searchInputField.nativeElement.setSelectionRange(0, 0);
}, 10);
}
exitFilterMode() {
this.textSearchMode = false;
this.pageLink.textSearch = null;
this.paginator.pageIndex = 0;
this.updateData();
}
private initFromDefaultBranch() {
if (this.branchAutocompleteComponent.isDefaultBranchSelected()) {
this.paginator.pageIndex = 0;
@ -220,6 +250,7 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni
}
private resetSortAndFilter(update: boolean) {
this.textSearchMode = false;
this.pageLink.textSearch = null;
if (this.viewsInited) {
this.paginator.pageIndex = 0;

View File

@ -33,7 +33,7 @@ import { DialogService } from '@core/services/dialog.service';
import { ActionSettingsChangeLanguage } from '@core/settings/settings.actions';
import { ActionAuthUpdateHasVersionControl } from '@core/auth/auth.actions';
import { selectHasVersionControl } from '@core/auth/auth.selectors';
import { catchError, mergeMap } from 'rxjs/operators';
import { catchError, mergeMap, take } from 'rxjs/operators';
import { of } from 'rxjs';
@Component({
@ -87,6 +87,7 @@ export class VersionControlSettingsComponent extends PageComponent implements On
});
this.store.pipe(
select(selectHasVersionControl),
take(1),
mergeMap((hasVersionControl) => {
if (hasVersionControl) {
return this.adminService.getEntitiesVersionControlSettings({ignoreErrors: true}).pipe(