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

#87 Builder tool #89

Draft
wants to merge 57 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
186a6fb
keep theme tags in index
R0tenur Mar 23, 2022
025eed6
add helper css
R0tenur Mar 23, 2022
821a826
ignore fake service from code coverage
R0tenur Mar 23, 2022
0490166
add basic state handling with history
R0tenur Mar 23, 2022
5c6bdaa
add basic state handling with history
R0tenur Mar 23, 2022
f0d78d7
ignore tokens in code coverage
R0tenur Mar 23, 2022
0b80ac2
throw away alertservice and use generic state instead
R0tenur Mar 23, 2022
7141437
add support for context menu
R0tenur Mar 23, 2022
143e81b
add position
R0tenur Mar 24, 2022
627d707
add basic and probably temporary menu to reach builder
R0tenur Mar 28, 2022
264d0b0
get rid of input from mermaid viewer and make it self sustained
R0tenur Mar 28, 2022
393da17
add border to prevent button rezise on hover
R0tenur Mar 28, 2022
661fd57
add basic add and rename features
R0tenur Mar 30, 2022
11c9706
remove unsused test file for dev stuff
R0tenur Mar 30, 2022
985df8e
ignore testing module from coverage
R0tenur Mar 30, 2022
4bc248f
set more suitable constants for the chart
R0tenur Mar 30, 2022
8461c4f
add models for new builder
R0tenur Mar 31, 2022
0c064ce
add component for basic context menu
R0tenur Mar 31, 2022
d203fcd
add more stuff to mocked window
R0tenur Mar 31, 2022
9693f1c
add service for context menu
R0tenur Mar 31, 2022
56c63c4
add rename modal
R0tenur Mar 31, 2022
e27f9dd
WIP first steps to the new svg engine
R0tenur Mar 31, 2022
fb9d9b7
docs: add more to the description in package.json
R0tenur Mar 31, 2022
ae4b2cd
add top component for the builder
R0tenur Mar 31, 2022
f183053
WIP component for adding relations
R0tenur Mar 31, 2022
508c78e
add routes to new components
R0tenur Mar 31, 2022
a18cf68
get rid of duplicate border
R0tenur Mar 31, 2022
b0ee4b3
make use of sliced history
R0tenur Mar 31, 2022
523091c
clean up code smells
R0tenur Mar 31, 2022
f076b67
remove unused vars
R0tenur Mar 31, 2022
b7530c9
get rid of smells
R0tenur Mar 31, 2022
d21e1da
ignore smell of sanitizer
R0tenur Mar 31, 2022
6639a76
get rid of unused import
R0tenur Mar 31, 2022
0fd8437
get rid of unused stuff
R0tenur Mar 31, 2022
9f97189
add overlay reset for relation modal
R0tenur Mar 31, 2022
f3a261c
renmove unused stuff
R0tenur Mar 31, 2022
1ffa514
add di for window to make testable
R0tenur Apr 1, 2022
7dd9e4e
get rid of smells
R0tenur Apr 1, 2022
eefe70f
extract add relation to builder service
R0tenur Apr 1, 2022
ad603b6
get rid of unused imports
R0tenur Apr 1, 2022
cba9b69
remove unused scaffolded scss
R0tenur Apr 1, 2022
02ddbdb
get rid of unused stuff
R0tenur Apr 1, 2022
5fd3d5d
add wall of badges
R0tenur Apr 1, 2022
44876e0
fix some smells
R0tenur Apr 1, 2022
569f2f6
add highlight state when altering stuff
R0tenur Apr 5, 2022
2ce8dd5
make stuff private
R0tenur Apr 5, 2022
b57b557
test: add event handling to fake window
R0tenur Apr 8, 2022
b9a33c9
feat: add highlightning to tables and columns
R0tenur Apr 8, 2022
1f39356
feat: add shortcuts
R0tenur Apr 8, 2022
f72dcce
fix: remove type fiddling
R0tenur Apr 8, 2022
841a90f
refactor: extract adding of colum to own func
R0tenur Apr 8, 2022
fb16fa5
refactor: rename to change component
R0tenur Apr 9, 2022
18a26e8
feat: add sql type and constraint to columns
R0tenur Apr 9, 2022
bd74a87
feat: add change forms for table and columns
R0tenur Apr 10, 2022
1969f99
Merge branch 'master' into 87-add-builder
R0tenur Jul 8, 2022
7df036b
Merge branch 'master' of github.com:R0tenur/visualization into 87-add…
R0tenur Jul 20, 2022
e206977
test: add column data to test
R0tenur Jul 22, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
![GitHub All Releases](https://img.shields.io/github/downloads/r0tenur/visualization/total)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=R0tenur_visualization&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=R0tenur_visualization)
[![codecov](https://codecov.io/gh/R0tenur/visualization/branch/master/graph/badge.svg?token=COOVEK6DW1)](https://codecov.io/gh/R0tenur/visualization)
[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=R0tenur_visualization&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=R0tenur_visualization)
[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=R0tenur_visualization&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=R0tenur_visualization)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=R0tenur_visualization&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=R0tenur_visualization)
[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=R0tenur_visualization&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=R0tenur_visualization)
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=R0tenur_visualization&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=R0tenur_visualization)
[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=R0tenur_visualization&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=R0tenur_visualization)
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=R0tenur_visualization&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=R0tenur_visualization)
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=R0tenur_visualization&metric=bugs)](https://sonarcloud.io/summary/new_code?id=R0tenur_visualization)
# Schema Visualization

Visulizes databases in "Azure data studio" using mermaid.js
Expand Down
2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "schema-visualization",
"displayName": "Schema Visualization",
"description": "Visualization of databases",
"description": "Entity relationship diagram, visualization of databases. Create ERD diagrams from a database schema. Or, create a schema from an ERD diagram.",
"version": "0.8.5",
"publisher": "R0tenur",
"license": "MIT",
Expand Down
48 changes: 28 additions & 20 deletions frontend/src/app/app-testing.module.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,57 @@
/* istanbul ignore file */
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';

import { ButtonComponent } from './components/button/button.component';
import { MERMAID } from './services/mermaid.token';
import { WINDOW } from './services/window.token';
let list: (e: Event) => void;
const fakeWindowProvider = ({
provide: WINDOW, useFactory: () => ({
removeEventListener: (type: string, listener: () => void) => {
innerHeight: 100,
innerWidth: 100,
removeEventListener: (_: string, __: () => void) => {
list = undefined as any as (e: Event) => void;
},
addEventListener: (type: string, listener: () => void) => {
addEventListener: (_: string, listener: () => void) => {
list = listener;
},
dispatchEvent: (e: Event) => list(e),
document: {
addEventListener: (type: string, listener: () => void) => {
addEventListener: (_: string, listener: () => void) => {
list = listener;
},
getElementsByTagName: (tag: string) => [
{
hasAttribute: (attribute: string) => false,
getAttribute: (attribute: string) => '',

}
]
removeEventListener: (_: string, __: () => void) => {
list = undefined as any as (e: Event) => void;
},
dispatchEvent: (e: Event) => list(e),
getElementsByTagName: (_: string) => [
fakeElement
],
getElementById: (__: string) => fakeElement,
querySelector: (__: string) => fakeElement
}
})
});
const fakeElement = {
clientWidth: 100,
clientHeight: 100,
hasAttribute: (___: string) => false,
getAttribute: (___: string) => '',
removeAttribute: (___: string) => { }, // NOSONAR - removeAttribute is for testing
setAttribute: (___: string, ____: string) => { }, // NOSONAR - setAttibute is for testing
};

const fakeMermaidProvider = ({
provide: MERMAID, useFactory: () => ({
render: (id: string, markdown: string, callback: () => void) => jasmine.createSpy(),
initialize: jasmine.createSpy(),
})
});
@NgModule({
declarations: [
ButtonComponent,
],
imports: [
FormsModule,
RouterTestingModule.withRoutes([]),
ReactiveFormsModule
],
providers: [fakeWindowProvider],
exports: [ButtonComponent, FormsModule]
exports: [ButtonComponent, RouterTestingModule, ReactiveFormsModule]
})
export class AppTestingModule { }

13 changes: 7 additions & 6 deletions frontend/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<app-context-menu></app-context-menu>
<app-change-modal></app-change-modal>
<app-relation></app-relation>
<div (click)="clearOverlays()">
<app-alert></app-alert>
<app-bar></app-bar>
<app-status></app-status>
<app-dev-bar></app-dev-bar>

<div *ngIf="Database$ | async as database">
<app-button (clicked)="exportSvg(database.svg, database.mermaid)">Export</app-button>

<h1 *ngIf="dataStudioService.DatabaseName$ | async as databaseName">{{databaseName}}</h1>
<app-mermaid-viewer [svg]="database.svg"></app-mermaid-viewer>
<h1 *ngIf="dataStudioService.DatabaseName$ | async as databaseName">{{databaseName}}</h1>
<router-outlet></router-outlet>
</div>
Empty file.
44 changes: 40 additions & 4 deletions frontend/src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AppTestingModule } from './app-testing.module';
import { AppComponent } from './app.component';
import { AddRelation, addRelationKey } from './models/add-relation.model';
import { Rename, renameKey } from './models/rename.model';
import { ContextMenuService } from './services/context-menu.service';
import { StateInjector } from './services/state.token';
import { State } from './state/state';

describe('AppComponent', () => {
let renameState: State<Rename>;
let addRelationState: State<AddRelation>;
let contextMenu: ContextMenuService;
let component: AppComponent;
let fixture: ComponentFixture<AppComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
Expand All @@ -12,14 +23,39 @@ describe('AppComponent', () => {
imports: [
AppTestingModule
],
providers: [
{ provide: StateInjector(renameKey), useValue: new State<Rename>() },
{ provide: StateInjector(addRelationKey), useValue: new State<AddRelation>() },
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
});

beforeEach(() => {
renameState = TestBed.inject<State<Rename>>(StateInjector(renameKey));
addRelationState = TestBed.inject<State<AddRelation>>(StateInjector(addRelationKey));
contextMenu = TestBed.inject(ContextMenuService);
spyOn(renameState, 'clear');
spyOn(addRelationState, 'clear');
spyOn(contextMenu, 'clear');
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
});

it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();

expect(component).toBeTruthy();
});
describe('clearOverlays', () => {
it('should clear all modal states', () => {
// Act
component.clearOverlays();

// Assert
expect(renameState.clear).toHaveBeenCalled();
expect(addRelationState.clear).toHaveBeenCalled();
expect(contextMenu.clear).toHaveBeenCalled();
});
});

});
30 changes: 20 additions & 10 deletions frontend/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { Component, Inject } from '@angular/core';
import { DataStudioService } from './services/data-studio.service';
import { Exportable } from './models/exportable.model';
import { ContextMenuService } from './services/context-menu.service';
import { StateInjector } from './services/state.token';
import { Rename, renameKey } from './models/rename.model';
import { State } from './state/state';
import { AddRelation, addRelationKey } from './models/add-relation.model';
import { shortcutsDisabledKey } from './services/shortcut.service';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {

public get Database$(): Observable<Exportable> {
return this.dataStudioService.Database$;
}
constructor(
public readonly dataStudioService: DataStudioService) { }
public readonly dataStudioService: DataStudioService,
public readonly contextMenu: ContextMenuService,
@Inject(StateInjector(renameKey)) private readonly rename: State<Rename>,
@Inject(StateInjector(addRelationKey)) private readonly addRelation: State<AddRelation>,
@Inject(StateInjector(shortcutsDisabledKey)) private readonly shortcutDisabledState: State<boolean>,

) { }

public exportSvg(svg: string, markdown: string): void {
this.dataStudioService.saveCommand({ chart: svg, mermaid: markdown });
public clearOverlays(): void {
this.contextMenu.clear();
this.rename.clear();
this.addRelation.clear();
this.shortcutDisabledState.set(false);
}

}
20 changes: 18 additions & 2 deletions frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { MermaidViewerComponent } from './components/mermaid-viewer/mermaid-viewer.component';
import { ButtonComponent } from './components/button/button.component';
import { AlertComponent } from './components/alert/alert.component';
import { StatusComponent } from './components/status/status.component';
import { DevBarComponent } from './components/dev-bar/dev-bar.component';
import { BarComponent } from './components/bar/bar.component';

import { RouterModule } from '@angular/router';
import { routes } from './settings/routes';
import { BuilderComponent } from './components/builder/builder.component';
import { ChartComponent } from './components/chart/chart.component';
import { ContextMenuComponent } from './components/context-menu/context-menu.component';
import { ChangeModalComponent } from './components/change-modal/change-modal.component';
import { RelationComponent } from './components/relation/relation.component';
@NgModule({
declarations: [
AppComponent,
Expand All @@ -17,12 +25,20 @@ import { DevBarComponent } from './components/dev-bar/dev-bar.component';
AlertComponent,
StatusComponent,
DevBarComponent,
BarComponent,
BuilderComponent,
ChartComponent,
ContextMenuComponent,
ChangeModalComponent,
RelationComponent
],
imports: [
BrowserModule,
ReactiveFormsModule,
FormsModule,
RouterModule.forRoot(routes),
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

4 changes: 2 additions & 2 deletions frontend/src/app/components/alert/alert.component.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div id="alert" *ngIf="alertService.Alert$ | async as alert" class="alert">
<div id="alert" *ngIf="state.select$ | async as alert" class="alert">
<h4 class="title">
An error occured!
</h4>
Expand Down Expand Up @@ -34,5 +34,5 @@ <h4 class="title">
<p>
Report it here: https://github.com/R0tenur/visualization/issues
</p>
<app-button (clicked)="alertService.dismissError()">Dismiss</app-button>
<app-button (clicked)="state.clear()">Dismiss</app-button>
</div>
12 changes: 7 additions & 5 deletions frontend/src/app/components/alert/alert.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,34 @@ import { By } from '@angular/platform-browser';
import { BehaviorSubject, Subject } from 'rxjs';
import { Status } from '../../../../../shared/models/status.enum';
import { AppTestingModule } from '../../app-testing.module';
import { ChartError } from '../../models/error.model';
import { AlertService } from '../../services/alert.service';
import { ChartError, ChartErrorKey } from '../../models/error.model';
import { DataStudioService } from '../../services/data-studio.service';
import { StateInjector } from '../../services/state.token';
import { State } from '../../state/state';

import { AlertComponent } from './alert.component';

describe('AlertComponent', () => {
let component: AlertComponent;
let fixture: ComponentFixture<AlertComponent>;
let alertService: AlertService;
let alertService: State<ChartError>;
let dataStudioService: DataStudioService;
const alertSubject: Subject<ChartError> = new Subject<ChartError>();
const markdownSubject: BehaviorSubject<string> = new BehaviorSubject<string>(undefined as any as string);

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AlertComponent],
providers: [{ provide: StateInjector(ChartErrorKey), useValue: new State<ChartError>()}],
imports: [AppTestingModule],
})
.compileComponents();
});
afterEach(() => alertSubject.next(undefined));

beforeEach(() => {
alertService = TestBed.inject(AlertService);
spyOnProperty(alertService, 'Alert$').and.returnValue(alertSubject.asObservable());
alertService = TestBed.inject<State<ChartError>>(StateInjector(ChartErrorKey));
spyOnProperty(alertService, 'select$').and.returnValue(alertSubject.asObservable());

dataStudioService = TestBed.inject(DataStudioService);
spyOnProperty(dataStudioService, 'Markdown$').and.returnValue(markdownSubject.asObservable());
Expand Down
12 changes: 8 additions & 4 deletions frontend/src/app/components/alert/alert.component.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { Component } from '@angular/core';
import { ChartError } from '../../models/error.model';
import { AlertService } from '../../services/alert.service';
import { Component, Inject } from '@angular/core';
import { ChartError, ChartErrorKey } from '../../models/error.model';
import { DataStudioService } from '../../services/data-studio.service';
import { StateInjector } from '../../services/state.token';
import { State} from '../../state/state';

@Component({
selector: 'app-alert',
templateUrl: './alert.component.html',
styleUrls: ['./alert.component.scss']
})
export class AlertComponent {
constructor(public readonly alertService: AlertService, public readonly az: DataStudioService) {}
constructor(
@Inject(StateInjector(ChartErrorKey)) public readonly state: State<ChartError>,
public readonly az: DataStudioService) { }

public exportMarkdown(markdown: string): void {
this.az.saveCommand({ mermaid: markdown });
}
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/app/components/bar/bar.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<app-button (clicked)="navigate('')">Overview</app-button><app-button (clicked)="navigate('builder')">Builder (Beta)</app-button>
<hr>
37 changes: 37 additions & 0 deletions frontend/src/app/components/bar/bar.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { AppTestingModule } from '../../app-testing.module';

import { BarComponent } from './bar.component';

describe('BarComponent', () => {
let component: BarComponent;
let fixture: ComponentFixture<BarComponent>;
let router: Router;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppTestingModule],
declarations: [BarComponent]
})
.compileComponents();
});

beforeEach(() => {
router = TestBed.inject(Router);
spyOn(router, 'navigate');
fixture = TestBed.createComponent(BarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
it('should navigate to path', () => {
// Act
component.navigate('/builder');
// Assert
expect(router.navigate).toHaveBeenCalledWith(['/builder']);
});
});
Loading