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

Added a UI to import data types and responses. #1067

Merged
merged 1 commit into from Feb 6, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion front-end/studio/src/app/app.module.ts
Expand Up @@ -74,6 +74,9 @@ import {TagListComponent} from "./components/common/tag-list.component";
import {SharingDialogComponent} from "./pages/apis/{apiId}/_components/sharing.dialog";
import {ApiTextEditorPageComponent} from "./pages/apis/{apiId}/editor/api-teditor.page";
import {DownloadDialogComponent} from "./pages/apis/{apiId}/_components/download.dialog";
import {ImportComponentsWizard} from "./pages/apis/{apiId}/_components/import-components.wizard";
import {DataTableComponent} from "./components/common/data-table.component";
import {LoadingComponent} from "./components/common/loading.component";

@NgModule({
imports: [
Expand All @@ -90,7 +93,8 @@ import {DownloadDialogComponent} from "./pages/apis/{apiId}/_components/download
PublishPageComponent, GitHubResourceComponent, GitLabResourceComponent, BitbucketResourceComponent,
GenerateProjectWizardComponent, ActivityItemComponent, EditorDisconnectedDialogComponent, MockPageComponent,
DefaultPageComponent, ConfigureValidationComponent, ProfileEditorComponent, TagListComponent,
SharingDialogComponent, ApiTextEditorPageComponent, DownloadDialogComponent
SharingDialogComponent, ApiTextEditorPageComponent, DownloadDialogComponent, ImportComponentsWizard,
DataTableComponent, LoadingComponent
],
providers: [
ApisService, AuthenticationServiceProvider, ConfigService, LinkedAccountsService, ValidationService,
Expand Down
Expand Up @@ -21,6 +21,7 @@ import { MockReference } from "../../models/mock-api.model";
import {ICommand, MarshallCompat} from "apicurio-data-models";
import {PublishApi} from "../../models/publish-api.model";
import * as moment from "moment";
import {componentTypeToString} from "../../pages/apis/{apiId}/editor/_models/component-type.model";


@Component({
Expand Down Expand Up @@ -288,6 +289,11 @@ export class ActivityItemComponent {
case "NewResponseWithRef":
rval = "reply-all";
break;
case "ImportedComponents":
rval = "plus";
break;
default:
console.warn("[ActivityItemComponent] Unhandled AggregateCommand change item (ICON): %s", name);
}
return rval;
}
Expand Down Expand Up @@ -646,8 +652,11 @@ export class ActivityItemComponent {
case "NewResponseWithRef":
rval = `created a Response that references the Response Definition at '${ this.command()["info"].$ref }'.`;
break;
case "ImportedComponents":
rval = `imported ${ this.command()["info"].numComponents } external components of type '${ componentTypeToString(this.command()["info"].type) }'.`;
break;
default:
console.info("[ActivityItemComponent] WARNING - unhandled AggregateCommand change item: %s", name);
console.warn("[ActivityItemComponent] Unhandled AggregateCommand change item: %s", name);
rval = "performed some unknown action...";
}
return rval;
Expand Down
@@ -0,0 +1,35 @@
table.dataTable {
width: 100%;
}
table.dataTable thead {
}
table.dataTable thead tr {
}
table.dataTable thead tr th.header-select-all {
width: 1px;
}
table.dataTable thead tr th.header-select-all input {
visibility: hidden;
}
table.dataTable tbody {
}
table.dataTable tbody tr {
}
table.dataTable tbody tr td {
max-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.filter-group {
display: flex;
}

.filter-group .btn-find {
margin-left: 5px;
}

.table-view-pf-select-results {
line-height: 28px;
}
@@ -0,0 +1,57 @@
<!-- Toolbar -->
<div class="row toolbar-pf table-view-pf-toolbar" id="toolbar1">
<div class="col-sm-12">
<form class="toolbar-pf-actions">
<div class="form-group toolbar-pf-filter">
<div class="input-group filter-group">
<input name="filterCriteria" type="text" class="form-control" placeholder="Filter..." autocomplete="off" id="filterInput" [(ngModel)]="filterCriteria" (change)="filter()">
<button class="btn btn-link btn-find" type="button" (click)="filter()">
<span class="fa fa-search"></span>
</button>
</div>
</div>
<div class="toolbar-pf-action-right">
<div class="table-view-pf-select-results">
<strong>{{ selectedRows.length }}</strong> of <strong>{{ rows.length }}</strong> selected
</div>
</div>
</form>
</div>
</div>
<!-- Table HTML -->
<table class="table table-striped table-bordered table-hover dataTable" id="table1">
<thead>
<tr>
<th class="header-select-all"><label class="sr-only" for="selectAll">Select all rows</label><input type="checkbox" id="selectAll" name="selectAll" (change)="selectAll()"></th>
<th *ngFor="let col of columns" [class.sorting_asc]="isColSortAsc(col)" [class.sorting_desc]="isColSortDesc(col)" width="{{ col.width ? col.width : '' }}" nowrap="nowrap" (click)="sortBy(col)">{{ col.displayName }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of displayRows" [class.selected]="isSelected(row)">
<td class="table-view-pf-select">
<input type="checkbox" [checked]="isSelected(row)" (change)="select(row)">
</td>
<td *ngFor="let cell of row.cells" [colSpan]="cell.colspan"><span title="{{ cell.displayName }}">{{ cell.displayName }}</span></td>
</tr>
<tr *ngIf="displayRows.length == 0">
<td [colSpan]="columns.length + 1" class="dataTables_empty" valign="top">No records found</td>
</tr>
</tbody>
</table>
<!-- Pagination Footer -->
<form class="content-view-pf-pagination table-view-pf-pagination clearfix">
<div class="form-group">
<span><span class="pagination-pf-items-current">{{ start+1 }}-{{ end }}</span> of <span class="pagination-pf-items-total">{{ filteredRows.length }}</span></span>
<ul class="pagination pagination-pf-back">
<li [class.disabled]="page == 1"><a title="First Page" (click)="firstPage()"><span class="i fa fa-angle-double-left"></span></a></li>
<li [class.disabled]="page == 1"><a title="Previous Page" (click)="previousPage()"><span class="i fa fa-angle-left"></span></a></li>
</ul>
<label for="pagination1-page" class="sr-only">Current Page</label>
<input name="pageNumber" class="pagination-pf-page" type="text" [(ngModel)]="page" (ngModelChange)="showPage($event)" id="pagination1-page"/>
<span>of <span class="pagination-pf-pages">{{ numPages }}</span></span>
<ul class="pagination pagination-pf-forward">
<li [class.disabled]="page == numPages"><a title="Next Page" (click)="nextPage()"><span class="i fa fa-angle-right"></span></a></li>
<li [class.disabled]="page == numPages"><a title="Last Page" (click)="lastPage()"><span class="i fa fa-angle-double-right"></span></a></li>
</ul>
</div>
</form>
218 changes: 218 additions & 0 deletions front-end/studio/src/app/components/common/data-table.component.ts
@@ -0,0 +1,218 @@
/**
* @license
* Copyright 2020 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from "@angular/core";

export interface DataTableColumn {
displayName: string;
width?: string;
}

export interface DataTableCell {
displayName: string;
colspan?: number;
}

export interface DataTableRow {
value: any;
cells: DataTableCell[];
}


@Component({
moduleId: module.id,
selector: "data-table",
templateUrl: "data-table.component.html",
styleUrls: [ "data-table.component.css" ]
})
export class DataTableComponent implements OnChanges {

@Input() columns: DataTableColumn[];
@Input() rows: DataTableRow[];
@Output() onChange: EventEmitter<any[]> = new EventEmitter<any[]>();
@Input() selectedValues: any[];
@Output() selectedValuesChange: EventEmitter<any[]> = new EventEmitter<any[]>();
@Input() pageSize: number = 15;

filteredRows: DataTableRow[] = [];
displayRows: DataTableRow[] = [];
selectedRows: DataTableRow[] = [];
sortColumn: number = 0;
sortAsc: boolean = true;
filterCriteria: string = "";
page: number = 1;
numPages: number = 0;
start: number;
end: number;

constructor() {}

ngOnChanges(changes: SimpleChanges): void {
if (changes["rows"]) {
this.selectedRows = [];
this.sortAndFilter();
}
if (changes["selectedValues"]) {
this.selectedRows = this.getSelectedRowsFrom(this.selectedValues);
}

this.numPages = Math.ceil(this.rows.length / this.pageSize);
}

select(row: DataTableRow): void {
if (this.selectedRows.indexOf(row) === -1) {
this.selectedRows.push(row);
} else {
this.selectedRows.splice(this.selectedRows.indexOf(row), 1);
}
this.fireChanges();
}

private fireChanges(): void {
let event: any[] = this.getSelectedValues();
this.onChange.emit(event);
this.selectedValuesChange.emit(event);
}

selectAll(): void {
// TODO implement this!
}

isSelected(row: DataTableRow): boolean {
return this.selectedRows.indexOf(row) !== -1;
}

private getSelectedValues(): any[] {
let rval: any[] = [];
this.selectedRows.forEach( row => {
rval.push(row.value);
});
return rval;
}

isColSortAsc(col: DataTableColumn): boolean {
return this.sortColumn == this.columns.indexOf(col) && this.sortAsc;
}

isColSortDesc(col: DataTableColumn): boolean {
return this.sortColumn == this.columns.indexOf(col) && !this.sortAsc;
}

filter(): void {
this.page = 1;
this.sortAndFilter();
}

sortBy(col: DataTableColumn): void {
this.page = 1;
let newColIdx: number = this.columns.indexOf(col);
if (newColIdx == this.sortColumn) {
this.sortAsc = !this.sortAsc;
} else {
this.sortColumn = newColIdx;
this.sortAsc = true;
}
this.sortAndFilter();
}

sortAndFilter(): void {
this.filteredRows = this.rows.slice();
// First, maybe filter.
if (this.filterCriteria) {
this.filteredRows = this.filteredRows.filter( row => {
let accepted: boolean = false;
row.cells.forEach( cell => {
let cellVal: string = cell.displayName;
if (cellVal == null) { cellVal = ""; }
cellVal = cellVal.toLowerCase();
if (cellVal.indexOf(this.filterCriteria.toLowerCase()) != -1) {
accepted = true;
}
});
return accepted;
});
}
// Then sort.
this.filteredRows.sort((row1, row2) => {
let c1: string = row1.cells[this.sortColumn].displayName;
let c2: string = row2.cells[this.sortColumn].displayName;
if (!c1) { c1 = ""; }
if (!c2) { c2 = ""; }
c1 = c1.toLowerCase();
c2 = c2.toLowerCase();
let rval: number = c1.localeCompare(c2);
if (!this.sortAsc) {
rval *= -1;
}
return rval;
});
// Then paginate.
this.start = (this.page - 1) * this.pageSize;
this.end = this.start + this.pageSize;
let maxEnd: number = this.filteredRows.length;
if (this.end > maxEnd) {
this.end = maxEnd;
}
this.displayRows = this.filteredRows.slice(this.start, this.end);
}

showPage(page: number): void {
this.page = page;
this.sortAndFilter();
}

private getSelectedRowsFrom(values: any[]): DataTableRow[] {
if (!values) {
return [];
}
return values.map( value => {
try {
return this.rows.filter(row => {
return row.value === value;
})[0];
} catch (e) {
return null;
}
});
}

private goToPage(page: number): void {
this.page = page;
if (this.page < 1) {
this.page = 1;
} else if (this.page > this.numPages) {
this.page = this.numPages;
}
this.sortAndFilter();
}

firstPage(): void {
this.goToPage(1);
}

nextPage(): void {
this.goToPage(this.page + 1);
}

previousPage(): void {
this.goToPage(this.page - 1);
}

lastPage(): void {
this.goToPage(this.numPages);
}
}
Empty file.
@@ -0,0 +1 @@
<span class="spinner spinner-xs spinner-inline"></span> <span>{{ message }}</span>