Skip to content

Commit

Permalink
Merge pull request #1 from sulea/feature/merge-columns
Browse files Browse the repository at this point in the history
The MergeColumns feature
  • Loading branch information
sulea authored Dec 14, 2022
2 parents fd5d026 + 79cfe00 commit d802993
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 6 deletions.
8 changes: 6 additions & 2 deletions src/app/components/components.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ import {JsonTextComponent} from './data-view/json-text/json-text.component';
import {NgxJsonViewerModule} from 'ngx-json-viewer';
import {JsonEditorComponent} from './json/json-editor.component';
import {JsonElemComponent} from './json/json-elem/json-elem.component';
import {ListPickerComponent} from './list-picker/list-picker.component';
import {DragDropModule} from '@angular/cdk/drag-drop';
import {DataGraphComponent} from './data-view/data-graph/data-graph.component';
import {MultipleSwitchPipe} from './data-view/multiple-switch.pipe';
import {DatesPipeModule} from './data-view/shared-module';

//import 'hammerjs';
Expand All @@ -49,6 +50,7 @@ import {DatesPipeModule} from './data-view/shared-module';
RouterModule,
CommonModule,
ChartsModule,
DragDropModule,
TypeaheadModule.forRoot(),
AppBreadcrumbModule.forRoot(),
TreeModule,
Expand All @@ -73,6 +75,7 @@ import {DatesPipeModule} from './data-view/shared-module';
InformationManagerComponent,
RenderItemComponent,
InputComponent,
ListPickerComponent,
EditorComponent,
JsonEditorComponent,
DataViewComponent,
Expand All @@ -99,10 +102,11 @@ import {DatesPipeModule} from './data-view/shared-module';
ToastComponent,
InformationManagerComponent,
InputComponent,
ListPickerComponent,
JsonEditorComponent,
EditorComponent,
DeleteConfirmComponent
]
})
export class ComponentsModule {
}
}
21 changes: 21 additions & 0 deletions src/app/components/list-picker/list-picker.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div class="source-column">
<span>{{ sourceTitle }}</span>
<div class="list-container">
<div *ngFor="let option of _getSourceOptions()" class="list-item" (click)="_onAddOption(option)">
<span>{{ option[labelProperty] }}</span>
<em class="fa fa-solid fa-chevron-right"></em>
</div>
</div>
</div>

<div class="target-column">
<span>{{ targetTitle }}</span>
<div id="target-container" class="list-container" cdkDropList (cdkDropListDropped)="_onMoveOption($event)">
<div *ngFor="let option of _value; let first = first; let last = last;" class="list-item"
(click)="_onRemoveOption(option)" cdkDrag [cdkDragData]="option" [cdkDragDisabled]="disableSort"
cdkDragBoundary="#target-container">
<span>
<em *ngIf="!disableSort" class="fa fa-solid fa-sort"></em> {{ option[labelProperty] }}</span>
</div>
</div>
</div>
66 changes: 66 additions & 0 deletions src/app/components/list-picker/list-picker.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
.app-list-picker {
display: flex;
gap: 10px;

.source-column,
.target-column {
display: flex;
flex-direction: column;
}

.list-container {
min-width: 200px;
min-height: 200px;
max-height: 200px;
border: 1px #e4e7ea solid;
flex: 1 1 auto;
border-radius: 0.2rem;
overflow-y: scroll;
}

.list-container .list-item:not(.cdk-drag-dragging) {
opacity: 1;
padding: 0.2rem 0.5rem;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: space-between;
border: 1px var(--white) solid;
border-radius: 0;
box-shadow: none;
border: 1x var(--white) solid;
background: var(--white);

em {
color: var(--gray);
font-size: 10px;
}
}

.list-container .list-item:not(.cdk-drag-dragging):hover {
background: var(--light);
cursor: pointer;
}

.list-container .list-item.cdk-drag-placeholder {
opacity: 0.2;
}
}

.cdk-drag.list-item {
opacity: 1;
padding: 0.2rem 0.5rem;
transition: all 0.1s;
display: flex;
align-items: center;
justify-content: space-between;
background: var(--light);
border: 1px var(--gray) solid;
border-radius: 0.2rem;
box-shadow: 0px 0px 5px var(--light);

em {
color: var(--gray);
font-size: 10px;
}
}
116 changes: 116 additions & 0 deletions src/app/components/list-picker/list-picker.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { Component, EventEmitter, forwardRef, HostBinding, Input, Output, ViewEncapsulation } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

/**
* ListPickerComponent is used to select multiple values from a list of options.
*/
@Component({
selector: 'app-list-picker',
templateUrl: './list-picker.component.html',
styleUrls: ['./list-picker.component.scss'],
encapsulation: ViewEncapsulation.None,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ListPickerComponent),
multi: true,
},
],
})
export class ListPickerComponent<T> implements ControlValueAccessor {

@HostBinding("class")
classes = ["app-list-picker"]

/**
* Title of the source options
*/
@Input()
sourceTitle = "Source";

/**
* Title of the target options
*/
@Input()
targetTitle = "Target";

/**
* An array of objects to display as the available options.
*/
@Input()
sourceOptions: Array<T>

/**
* Name of the label field of an option.
*/
@Input()
labelProperty: keyof T | null;

/**
* Number of maximum options that can be selected.
*/
@Input()
selectionLimit = Infinity;

/**
* Disable drag and drop sorting
*/
@Input()
disableSort = false;

/**
* Callback to invoke when selection limit is reached.
*/
@Output()
selectionLimitReached = new EventEmitter<void>();

_value: Array<T> | null;

_disabled: boolean;

_onChange: (value: Array<T>) => void

_onTouched: () => void

writeValue(value: Array<T>): void {
this._value = value
}

registerOnChange(fn: (value: Array<T>) => void): void {
this._onChange = fn
}

registerOnTouched(fn: () => void): void {
this._onTouched = fn
}

setDisabledState?(isDisabled: boolean): void {
this._disabled = isDisabled;
}

_getSourceOptions = () => {
return this.sourceOptions?.filter(option => !this._value?.includes(option));
}

_onAddOption = (option: T) => {
this._onTouched();
if((this._value?.length ?? 0) < this.selectionLimit) {
this._value = [...this._value ?? [], option];
this._onChange(this._value);
} else {
this.selectionLimitReached.emit()
}
}

_onRemoveOption = (option: T) => {
this._value?.splice( this._value?.indexOf(option), 1);
this._onChange(this._value);
this._onTouched();
}


_onMoveOption = (event: CdkDragDrop<T, any>) => {
moveItemInArray(this._value, event.previousIndex, event.currentIndex);
}
}
20 changes: 20 additions & 0 deletions src/app/models/ui-request.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,26 @@ export class ColumnRequest extends UIRequest {
}
}


/**
* Merge columns within a relational namespace.
* Used for request where you want to merge multiple columns of a table.
*/
export class MergeColumnsRequest extends UIRequest {
sourceColumns: DbColumn[];
targetColumnName: string;
joinString: string;
tableType: string;
constructor( tableId: string, sourceColumns: DbColumn[], targetColumnName: string, joinString: string, tableType:string = 'table' ) {
super();
this.tableId = tableId;
this.sourceColumns = sourceColumns;
this.targetColumnName = targetColumnName;
this.joinString = joinString;
this.tableType = tableType;
}
}

export class MaterializedRequest extends UIRequest{
constructor(tableId: string) {
super();
Expand Down
13 changes: 10 additions & 3 deletions src/app/services/crud.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import {
Index,
ModifyPartitionRequest,
PartitionFunctionModel,
PartitioningRequest,
ResultSet
PartitioningRequest
} from '../components/data-view/models/result-set.model';
import {webSocket} from 'rxjs/webSocket';
import {
Expand All @@ -18,6 +17,7 @@ import {
ExploreTable,
GraphRequest,
MaterializedRequest,
MergeColumnsRequest,
MonitoringRequest,
QueryRequest,
RelAlgRequest,
Expand Down Expand Up @@ -204,6 +204,13 @@ export class CrudService {
return this._http.post(`${this.httpUrl}/dropColumn`, columnRequest, this.httpOptions);
}

/**
* Merge columns of a table in a relational namespace
*/
mergeColumns ( columnRequest: MergeColumnsRequest ) {
return this._http.post(`${this.httpUrl}/mergeColumns`, columnRequest, this.httpOptions);
}

/**
* Get list of tables of a schema to truncate/drop them
*/
Expand Down Expand Up @@ -618,4 +625,4 @@ export class CrudService {
return 'is-invalid';
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,35 @@ <h5 style="display: inline">Data placements</h5>
</div>
</div>
</div>
</div>

<div class="col-lg-12">
<hr>
<div class="mb-3" style="position: relative">
<h5 style="display: inline">Schema Evolutions</h5>
</div>
<div class="card">
<div class="card-header">
<span>Merge columns</span>
</div>
<div class="card-body">
<p>Currently, only the merge of non-primary, varchar columns is permitted.</p>
<div class="constraint-columns mb-2">
<app-list-picker [sourceOptions]="mergeableColumns" [(ngModel)]="columnsToMerge"
labelProperty="name" sourceTitle="Mergeable columns" targetTitle="Columns to merge (sortable)" selectionLimit="3"></app-list-picker>
</div>
<div class="form-inline mb-2">
<div class="form-group">
<label for="joinString">Join string</label>
<input id="joinString" type="text" class="form-control form-control-sm" [(ngModel)]="joinString">
<label for="mergedColumnName">New column name</label>
<input id="mergedColumnName" type="text" class="form-control form-control-sm" [(ngModel)]="mergedColumnName"
[ngClass]="_crud.getValidationClass(mergedColumnName)">
</div>
<button class="btn btn-sm btn-primary" (click)="mergeColumns()">merge</button>
</div>
</div>
</div>
</div>

</div>
Expand Down
Loading

0 comments on commit d802993

Please sign in to comment.