Commit 54f72340 authored by Samuel Schepp's avatar Samuel Schepp

Merge branch 'develop' into performance-tests

* develop: (114 commits)
  enabled review apps
  removed labels of generic table view
  Update CONTRIBUTING.md
  Update README.md
  Change link
  fixed webapp-v3 deploy
  Fix npm integrity
  Use mocainfo infrastructure
  Fix lint error
  Let drawPois|Tags remove old ones
  Fix multiple rendering of tags and pois when using modal
  refactored map functions to meet lint requierments
  repplaced wifi icon a new wifi icon
  replaced old @1x marker with un downscaled version of the @2x marker
  added a user icon to username
  New tag icon
  Toggle tag visibility independantly
  Remove unused CSS
  Render tags in map view
  Show image preview larger and in separate row
  ...
parents 62e77a08 4f169ddd
Pipeline #18463 canceled with stages
in 3 seconds
{
"projects": {
"default": "mocainfo-develop"
"default": "web-app-v3"
}
}
......@@ -12,9 +12,11 @@ Tests:
paths:
- coverage
tags:
- nodejs
- node
- ng
- google-chrome
script:
- npm install
- npm install --no-shrinkwrap
- npm run test
- npm run e2e
......@@ -31,9 +33,9 @@ Static:
paths:
- documentation
tags:
- nodejs
- node
script:
- npm install
- npm install --no-shrinkwrap
- npm run lint
- npm outdated || true
- npm audit || true
......@@ -41,33 +43,41 @@ Static:
Deploy Develop:
stage: deploy
artifacts:
paths:
- dist
only:
- develop
environment:
name: develop
url: https://mocainfo-develop.firebaseapp.com
url: https://web-app-v3.firebaseapp.com/
tags:
- nodejs
- node
- ng
- firebase
script:
- npm install
- npm install --no-shrinkwrap
- echo "$VERSION_FILE" > "src/assets/version.json"
- npm run build -- --configuration=production
- node_modules/.bin/firebase deploy --token $FRBTKN --non-interactive
Deploy Review:
stage: deploy
artifacts:
paths:
- dist
tags:
- review
script:
- npm install
- echo "$VERSION_FILE" > "src/assets/version.json"
- npm run build -- --base-href /mocainfo/$CI_ENVIRONMENT_SLUG/
- rm -r /var/www/html/mocainfo/$CI_ENVIRONMENT_SLUG || true
- cp -R dist /var/www/html/mocainfo/$CI_ENVIRONMENT_SLUG
- echo "$HTACCESS" > /var/www/html/mocainfo/$CI_ENVIRONMENT_SLUG/.htaccess
- npm run build -- --base-href /web-app-v3/$CI_ENVIRONMENT_SLUG/
- rm -r /home/mocainfo/review_apps/web-app-v3/$CI_ENVIRONMENT_SLUG || true
- cp -R dist /home/mocainfo/review_apps/web-app-v3/$CI_ENVIRONMENT_SLUG
- echo "$HTACCESS" > /home/mocainfo/review_apps/web-app-v3/$CI_ENVIRONMENT_SLUG/.htaccess
environment:
name: review_$CI_COMMIT_REF_NAME
url: http://195.201.227.204/mocainfo/$CI_ENVIRONMENT_SLUG
url: http://mocainfo.thm.de:8090/web-app-v3/$CI_ENVIRONMENT_SLUG
on_stop: Stop Review
Stop Review:
......@@ -77,8 +87,8 @@ Stop Review:
variables:
GIT_STRATEGY: none
script:
- rm -r /var/www/html/mocainfo/$CI_ENVIRONMENT_SLUG || true
- rm -r /home/mocainfo/review_apps/web-app-v3/$CI_ENVIRONMENT_SLUG || true
when: manual
environment:
name: review_$CI_COMMIT_REF_NAME
action: stop
action: stop
\ No newline at end of file
......@@ -12,7 +12,7 @@ Im folgenden wird erklärt, wie man das Backend aufsetzt und das Frontend damit
* Angular CLI 1.7.4
#### Backend
* Java JDK 8
* Java JDK 8 (neuere Versionen funktionieren NICHT)
* Maven
#### Optional
......
......@@ -3,9 +3,10 @@
# MoCaInfo Browser Client
![Screenshot_2018-06-16_12.08.08](/uploads/eeac4598c974ec04af3b230022b0545e/Screenshot_2018-06-16_12.08.08.png)
## Live Demo
:fire: [https://mocainfo-develop.firebaseapp.com](https://mocainfo-develop.firebaseapp.com) :fire:
:fire: [https://web-app-v3.firebaseapp.com](https://web-app-v3.firebaseapp.com) :fire:
Einloggen:
......@@ -20,5 +21,5 @@ Einloggen:
* MoCaInfo Infrastruktur: [https://mocainfo.thm.de](https://mocainfo.thm.de)
## Development Services
* GitLab CI: [https://git.thm.de/sspp77/web-app-v3/pipelines](https://git.thm.de/sspp77/web-app-v3/pipelines)
* Firebase: [https://console.firebase.google.com/project/mocainfo-develop/overview](https://console.firebase.google.com/project/mocainfo-develop/overview)
* GitLab CI: [https://git.thm.de/mocainfo/web-app-v3/pipelines](https://git.thm.de/mocainfo/web-app-v3/pipelines)
* Firebase: [https://console.firebase.google.com/u/1/project/web-app-v3/overview](https://console.firebase.google.com/u/1/project/web-app-v3/overview)
This diff is collapsed.
......@@ -14,6 +14,7 @@
"private": true,
"dependencies": {
"@angular/animations": "6.0.4",
"@angular/cdk": "^6.2.1",
"@angular/common": "6.0.4",
"@angular/compiler": "6.0.4",
"@angular/core": "6.0.4",
......@@ -35,6 +36,7 @@
"leaflet": "^1.3.1",
"moment": "^2.22.1",
"moment-timezone": "^0.5.17",
"ngx-contextmenu": "^5.0.1",
"npm": "^6.1.0",
"rxjs": "^6.2.0",
"ts-node": "^6.0.5",
......
sonar.projectKey=mocainfo-web
sonar.sources=src,e2e
sonar.inclusions=**/*.ts
sonar.exclusions=**/*.spec.ts
sonar.typescript.lcov.reportPaths=coverage/lcov.info
......@@ -36,8 +36,8 @@ import {CommonAddEditService} from './services/common-add-edit/common-add-edit.s
import {AddEditCategoryComponent} from './components/add-edit/add-edit-category/add-edit-category.component';
import {AddEditPoiComponent} from './components/add-edit/add-edit-poi/add-edit-poi.component';
import {AddEditEventComponent} from './components/add-edit/add-edit-event/add-edit-event.component';
import {AddEditHeaderViewComponent} from './components/common/add-edit-header-view/add-edit-header-view.component';
import {AddEditBottomViewComponent} from './components/common/add-edit-bottom-view/add-edit-bottom-view.component';
import {AddEditHeaderViewComponent} from './components/add-edit/common/add-edit-header-view/add-edit-header-view.component';
import {AddEditBottomViewComponent} from './components/add-edit/common/add-edit-bottom-view/add-edit-bottom-view.component';
import {AddEditPersonComponent} from './components/add-edit/add-edit-person/add-edit-person.component';
import {AddEditContentComponent} from './components/add-edit/add-edit-content/add-edit-content.component';
import {AddEditUserComponent} from './components/add-edit/add-edit-user/add-edit-user.component';
......@@ -45,7 +45,16 @@ import {RestoreComponent} from './components/pages/restore/restore.component';
import {ConfirmComponent} from './components/common/confirm/confirm.component';
import {ActiveModalMockService} from './services/active-modal-mock/active-modal-mock.service';
import {HttpClientMockService} from './services/httpclient-mock/http-client-mock.service';
import { DateComponent } from './components/common/date/date.component';
import {MultiModelInputComponent} from './components/add-edit/common/multi-model-input/multi-model-input.component';
import {SingleModelInputComponent} from './components/add-edit/common/single-model-input/single-model-input.component';
import {InlineConfirmationComponent} from './components/common/inline-confirmation/inline-confirmation.component';
import {BooleanInputComponent} from './components/add-edit/common/boolean-input/boolean-input.component';
import { DateInputComponent } from './components/add-edit/common/date-input/date-input.component';
import {ContextMenuModule} from 'ngx-contextmenu';
import {TagInputComponent} from './components/add-edit/common/tag-input/tag-input.component';
import {ContactInputComponent} from './components/add-edit/common/contact-input/contact-input.component';
import {SingleEnumInputComponent} from './components/add-edit/common/single-enum-input/single-enum-input.component';
import {JsonInputComponent} from './components/add-edit/common/json-input/json-input.component';
export function testModule(): NgModule {
return {
......@@ -82,7 +91,15 @@ export function testModule(): NgModule {
RestoreComponent,
ConfirmComponent,
LoginComponent,
DateComponent,
MultiModelInputComponent,
SingleModelInputComponent,
DateInputComponent,
InlineConfirmationComponent,
BooleanInputComponent,
TagInputComponent,
ContactInputComponent,
SingleEnumInputComponent,
JsonInputComponent,
],
imports: [
BrowserModule,
......@@ -91,6 +108,9 @@ export function testModule(): NgModule {
RouterModule.forRoot(routes),
FormsModule,
HttpClientModule,
ContextMenuModule.forRoot({
useBootstrap4: true,
}),
],
providers: [
RoutingService,
......@@ -143,7 +163,15 @@ export function testModule(): NgModule {
AddEditUserComponent,
RestoreComponent,
ConfirmComponent,
DateComponent,
MultiModelInputComponent,
SingleModelInputComponent,
DateInputComponent,
InlineConfirmationComponent,
BooleanInputComponent,
TagInputComponent,
ContactInputComponent,
SingleEnumInputComponent,
JsonInputComponent,
],
imports: [
BrowserModule,
......@@ -152,6 +180,9 @@ export function testModule(): NgModule {
RouterModule.forRoot(routes),
FormsModule,
HttpClientModule,
ContextMenuModule.forRoot({
useBootstrap4: true,
}),
],
providers: [
RoutingService,
......@@ -165,6 +196,13 @@ export function testModule(): NgModule {
entryComponents: [
LoginComponent,
ConfirmComponent,
AddEditPoiComponent,
AddEditEventComponent,
AddEditCategoryComponent,
AddEditContentComponent,
AddEditPersonComponent,
AddEditTagComponent,
AddEditUserComponent,
],
})
export class AppModule {
......
import {ContentRangeParser} from './ContentRangeParser';
/* tslint:disable:no-identical-functions */
describe('ContentRangeParser', () => {
describe('parse', () => {
it('parse value', () => {
const contentRange = ContentRangeParser.parse('count 0-99/270');
expect(contentRange.unit).toEqual('count');
expect(contentRange.first).toEqual(0);
expect(contentRange.last).toEqual(99);
expect(contentRange.length).toEqual(270);
});
});
});
export interface IContentRange {
unit: string;
first: number;
last: number;
length: number;
}
export class ContentRangeParser {
static parse(header: string): IContentRange {
if (!header) {
return null;
}
const matches = header.match(/^(\w+) (\d+)-(\d+)\/(\d+|\*)/);
if (matches) {
return {
unit: matches[1],
first: Number.parseInt(matches[2]),
last: Number.parseInt(matches[3]),
length: matches[4] === '*' ? null : Number.parseInt(matches[4]),
};
}
return null;
}
}
import {expect} from 'chai';
import {PaginationManager} from './PaginationManager';
import {IModel} from '../interfaces/IModel';
import {personModelDefinition} from '../model-definitions/PersonModelDefinition';
/* tslint:disable:no-identical-functions */
......@@ -11,7 +13,7 @@ for (let i = 1; i <= 154; i++) {
describe('PaginationManager', () => {
describe('getPage', () => {
it('should deliver correct page', () => {
const paginationManager = new PaginationManager(localStorage);
const paginationManager = new PaginationManager<IModel>(localStorage, personModelDefinition);
paginationManager.rowsPerPage = 20;
paginationManager.selectedPage = 1;
......@@ -24,7 +26,7 @@ describe('PaginationManager', () => {
});
it('should deliver last page', () => {
const paginationManager = new PaginationManager(localStorage);
const paginationManager = new PaginationManager<IModel>(localStorage, personModelDefinition);
paginationManager.rowsPerPage = 20;
paginationManager.selectedPage = 8;
......@@ -39,7 +41,7 @@ describe('PaginationManager', () => {
});
it('should deliver correct page size', () => {
const paginationManager = new PaginationManager(localStorage);
const paginationManager = new PaginationManager<IModel>(localStorage, personModelDefinition);
paginationManager.rowsPerPage = 20;
paginationManager.selectedPage = 2;
......@@ -49,7 +51,7 @@ describe('PaginationManager', () => {
});
it('should deliver empty page because of wrong selectedPage', () => {
const paginationManager = new PaginationManager(localStorage);
const paginationManager = new PaginationManager<IModel>(localStorage, personModelDefinition);
paginationManager.rowsPerPage = 20;
paginationManager.selectedPage = 0;
......@@ -59,7 +61,7 @@ describe('PaginationManager', () => {
});
it('should deliver empty page because of high selectedPage', () => {
const paginationManager = new PaginationManager(localStorage);
const paginationManager = new PaginationManager<IModel>(localStorage, personModelDefinition);
paginationManager.rowsPerPage = 20;
paginationManager.selectedPage = 99;
......@@ -69,7 +71,7 @@ describe('PaginationManager', () => {
});
it('should deliver page of 5 because of wrong rowsPerPage', () => {
const paginationManager = new PaginationManager(localStorage);
const paginationManager = new PaginationManager<IModel>(localStorage, personModelDefinition);
paginationManager.rowsPerPage = 0;
paginationManager.selectedPage = 2;
......@@ -79,7 +81,7 @@ describe('PaginationManager', () => {
});
it('should deliver empty page because of null data', () => {
const paginationManager = new PaginationManager(localStorage);
const paginationManager = new PaginationManager<IModel>(localStorage, personModelDefinition);
paginationManager.rowsPerPage = 20;
paginationManager.selectedPage = 2;
......@@ -92,7 +94,7 @@ describe('PaginationManager', () => {
describe('pageCount', () => {
it('should deliver correct page count', () => {
const paginationManager = new PaginationManager(localStorage);
const paginationManager = new PaginationManager<IModel>(localStorage, personModelDefinition);
paginationManager.rowsPerPage = 20;
paginationManager.selectedPage = 2;
......@@ -102,7 +104,7 @@ describe('PaginationManager', () => {
});
it('should deliver 31 because of wrong rowsPerPage, which become 5', () => {
const paginationManager = new PaginationManager(localStorage);
const paginationManager = new PaginationManager<IModel>(localStorage, personModelDefinition);
paginationManager.rowsPerPage = 0;
paginationManager.selectedPage = 2;
......@@ -112,7 +114,7 @@ describe('PaginationManager', () => {
});
it('should deliver 0 because of null data', () => {
const paginationManager = new PaginationManager(localStorage);
const paginationManager = new PaginationManager<IModel>(localStorage, personModelDefinition);
paginationManager.rowsPerPage = 20;
paginationManager.selectedPage = 2;
......
......@@ -2,7 +2,18 @@
* The PaginationManager implements a system to managing pagination.
* It only holds meta data about the pages, but not the actual data.
*/
export class PaginationManager {
import {IModel} from '../interfaces/IModel';
import {IModelDefinition} from '../interfaces/IModelDefinition';
type SelectedPageLocalStorage = {[modelKey: string]: number};
export class PaginationManager<M extends IModel> {
/**
* Key of the local storage, holding the datastructure of selected page
* @type {string}
*/
static SELECTED_PAGE_LOCAL_STORAGE = 'selected_page';
/**
* All the possible rows-per-page counts, that can be selected by the user.
......@@ -12,25 +23,25 @@ export class PaginationManager {
5, 20, 100, 1000,
];
/**
* The currently selected page.
*/
public selectedPage: number;
/**
* The local storage key, to save the current rows-per-page preference.
* @type {string}
*/
private readonly ROWS_PER_PAGE_LOCAL_STORAGE = 'rows_per_page';
/**
* The model definition to get the selected page of
*/
private modelDefinition: IModelDefinition<M>;
/**
* Constructor.
* The localStorage is injected, to be able to unit test it with an node.js implementation.
* @param localStorage The injected localStorage system.
*/
constructor(private localStorage: any) {
this.selectedPage = 1;
constructor(private localStorage: any, modelDefinition: IModelDefinition<M>) {
this.localStorage = localStorage;
this.modelDefinition = modelDefinition;
}
/**
......@@ -89,4 +100,31 @@ export class PaginationManager {
return Math.ceil(data.length / this.rowsPerPage);
}
public get selectedPage(): number {
try {
const data = localStorage.getItem(PaginationManager.SELECTED_PAGE_LOCAL_STORAGE);
const obj = JSON.parse(data) as SelectedPageLocalStorage;
const selectedPage = obj[this.modelDefinition.viewPagePath];
if (Number.isInteger(selectedPage)) {
return selectedPage;
}
} catch (err) { }
return 1;
}
public set selectedPage(selectedPage: number) {
let obj: SelectedPageLocalStorage;
try {
const data = localStorage.getItem(PaginationManager.SELECTED_PAGE_LOCAL_STORAGE);
obj = JSON.parse(data) as SelectedPageLocalStorage;
if (!obj) {
obj = {};
}
} catch (err) {
obj = {};
}
obj[this.modelDefinition.viewPagePath] = selectedPage;
localStorage.setItem(PaginationManager.SELECTED_PAGE_LOCAL_STORAGE, JSON.stringify(obj));
}
}
import {SearchManager} from './SearchManager';
import {expect} from 'chai';
import {UserModel} from '../models/user.model';
import {PersonModel} from '../models/person.model';
/* tslint:disable:no-identical-functions */
......@@ -105,5 +107,21 @@ describe('SearchManager', () => {
expect(result.length).to.deep.equal(0);
});
it('should deliver result because of value from toString()', () => {
const searchManager = new SearchManager();
searchManager.searchTerm = 'max mustermann';
const result = searchManager.getFilteredData([
new PersonModel({
firstname: 'Max',
lastname: 'Mustermann',
}),
]);
expect(result[0].firstname).to.deep.equal('Max');
expect(result[0].lastname).to.deep.equal('Mustermann');
expect(result.length).to.deep.equal(1);
});
});
});
......@@ -28,6 +28,10 @@ export class SearchManager {
* @returns {boolean} True, if the object matches the search term, otherwise false.
*/
private filterEntry<T extends object>(entry: T): boolean {
if ((entry as any).toString().toLowerCase().includes(this.searchTerm.toLowerCase())) {
return true;
}
return Object.keys(entry).reduce((akku: boolean, column: string, index: number, array: string[]) => {
const field = `${DataReader.readKeySafe(entry, column, '')}`;
......
<div class="nav-clear">
<div class="pl-3 pt-3 pr-3">
<div class="form-container" *ngIf="category">
<app-add-edit-header-view></app-add-edit-header-view>
<app-add-edit-header-view [modelDefinition]="modelDefinition"></app-add-edit-header-view>
<div class="form-group row" *ngIf="commonAddEditService.isEditMode()">
<label class="col-3 col-form-label">ID</label>
<div class="col-9">
<input class="form-control" type="text" disabled [(ngModel)]="category.id">
</div>
<div class="modal-body">
<div class="form-container" *ngIf="category">
<div class="form-group row" *ngIf="commonAddEditService.isEditMode()">
<label class="col-3 col-form-label col-form-label-sm">ID</label>
<div class="col-9">
<input class="form-control form-control-sm" type="text" disabled [(ngModel)]="category.id">
</div>
</div>
<div class="form-group row" *ngIf="commonAddEditService.isEditMode()">
<label class="col-3 col-form-label">Revision</label>
<div class="col-9">
<input class="form-control" type="text" disabled [(ngModel)]="category.revision">
</div>
<div class="form-group row" *ngIf="commonAddEditService.isEditMode()">
<label class="col-3 col-form-label col-form-label-sm">Revision</label>
<div class="col-9">
<input class="form-control form-control-sm" type="text" disabled [(ngModel)]="category.revision">
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label">Name</label>
<div class="col-9">
<input class="form-control" appFocus type="text" [(ngModel)]="category.name">
</div>
<div class="form-group row">
<label class="col-3 col-form-label col-form-label-sm">Name</label>
<div class="col-9">
<input class="form-control form-control-sm" appFocus type="text" [(ngModel)]="category.name">
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label">Icon URL</label>
<div class="col-9">
<input class="form-control" type="text" [(ngModel)]="category.icon">
</div>
<div class="form-group row">
<label class="col-3 col-form-label col-form-label-sm">Icon URL</label>
<div class="col-9">
<input class="form-control form-control-sm" type="text" [(ngModel)]="category.icon">
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label">Icon Preview</label>
<div class="col-9">
<img class="image-preview" [src]="category.icon">
</div>
<div class="form-group row">
<label class="col-3 col-form-label col-form-label-sm">Icon Preview</label>
<div class="col-9">
<img class="image-preview" [src]="category.icon">
</div>
<app-add-edit-bottom-view [modelDefinition]="modelDefinition"></app-add-edit-bottom-view>
</div>
</div>
</div>
<app-add-edit-bottom-view [modelDefinition]="modelDefinition"></app-add-edit-bottom-view>
\ No newline at end of file
import { Component, OnInit } from '@angular/core';
import {Component, Input, OnInit} from '@angular/core';
import {categoryModelDefinition} from '../../../model-definitions/CategoryModelDefinition';
import {CommonAddEditService} from '../../../services/common-add-edit/common-add-edit.service';
import {CategoryModel} from '../../../models/category.model';
......@@ -12,12 +12,13 @@ import {CategoryModel} from '../../../models/category.model';
export class AddEditCategoryComponent implements OnInit {
public readonly modelDefinition = categoryModelDefinition;
@Input() id: string;
constructor(public commonAddEditService: CommonAddEditService) {
}
ngOnInit() {
this.commonAddEditService.init(this.modelDefinition);
async ngOnInit() {
await this.commonAddEditService.init(this.modelDefinition, this.id);
}
get category(): CategoryModel {
......
<div class="nav-clear">
<div class="pl-3 pt-3 pr-3">
<div class="form-container" *ngIf="content">
<app-add-edit-header-view></app-add-edit-header-view>
<app-add-edit-header-view [modelDefinition]="modelDefinition"></app-add-edit-header-view>
<div class="modal-body">
<div class="form-container" *ngIf="content">
<div class="form-group row" *ngIf="commonAddEditService.isEditMode()">
<label class="col-3 col-form-label">ID</label>
<label class="col-3 col-form-label col-form-label-sm">ID</label>
<div class="col-9">
<input class="form-control" type="text" disabled [(ngModel)]="content.id">
<input class="form-control form-control-sm" type="text" disabled [(ngModel)]="content.id">
</div>
</div>
<div class="form-group row" *ngIf="commonAddEditService.isEditMode()">
<label class="col-3 col-form-label">Revision</label>
<label class="col-3 col-form-label col-form-label-sm">Revision</label>
<div class="col-9">
<input class="form-control form-control-sm" type="text" disabled [(ngModel)]="content.revision">
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label col-form-label-sm">Name</label>
<div class="col-9">
<input class="form-control form-control-sm" appFocus type="text" [(ngModel)]="content.name">
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label col-form-label-sm">Active</label>
<div class="col-9">
<app-boolean-input [(active)]="content.active" falseLabel="Inactive" trueLabel="Active"></app-boolean-input>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label col-form-label-sm"><span class="align-middle">Publish Start</span></label>
<div class="col-9">
<app-date-input [(date)]="content.publishStart"></app-date-input>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label col-form-label-sm">Publish End</label>
<div class="col-9">
<app-date-input [(date)]="content.publishEnd"></app-date-input>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label col-form-label-sm">MIME Type</label>