Put/Post motions to server

Temporarily over dataStore, will need own service
This commit is contained in:
Sean Engelhardt 2018-08-22 11:26:53 +02:00
parent 133ecb4724
commit de61505b00
8 changed files with 166 additions and 62 deletions

View File

@ -29,6 +29,12 @@ interface Storage {
* Use this.DS in an OpenSlides Component to Access the store. * Use this.DS in an OpenSlides Component to Access the store.
* Used by a lot of components, classes and services. * Used by a lot of components, classes and services.
* Changes can be observed * Changes can be observed
*
* FIXME: The injector does not init the HttpClient Service.
* Either remove it from DataStore and make an own Service
* fix it somehow
* or just do-not let the OpenSlidesComponent inject DataStore to it's
* children.
*/ */
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -49,7 +55,9 @@ export class DataStoreService {
* Empty constructor for dataStore * Empty constructor for dataStore
* @param http use HttpClient to send models back to the server * @param http use HttpClient to send models back to the server
*/ */
constructor(private http: HttpClient) {} constructor(private http: HttpClient) {
console.log('constructor of dataStore. http: ', this.http);
}
/** /**
* Read one, multiple or all ID's from dataStore * Read one, multiple or all ID's from dataStore
@ -157,21 +165,29 @@ export class DataStoreService {
/** /**
* Saves the given model on the server * Saves the given model on the server
* @param model the BaseModel that shall be removed * @param model the BaseModel that shall be saved
* @return Observable of BaseModel * @return Observable of BaseModel
*/ */
save(model: BaseModel): Observable<BaseModel> { save(model: BaseModel): Observable<BaseModel> {
if (!model.id) { if (!model.id) {
throw new ImproperlyConfiguredError('The model must have an id!'); return this.http.post<BaseModel>('rest/' + model.collectionString + '/', model).pipe(
tap(
response => {
console.log('New Model added. Response : ', response);
},
error => console.log('error. ', error)
)
);
} else {
return this.http.put<BaseModel>('rest/' + model.collectionString + '/' + model.id, model).pipe(
tap(
response => {
console.log('Update model. Response : ', response);
},
error => console.log('error. ', error)
)
);
} }
// TODO not tested
return this.http.post<BaseModel>(model.collectionString + '/', model).pipe(
tap(response => {
console.log('the response: ', response);
this.add(model);
})
);
} }
/** /**

View File

@ -4,6 +4,7 @@ import { HttpClient } from '@angular/common/http';
import { tap, catchError, share } from 'rxjs/operators'; import { tap, catchError, share } from 'rxjs/operators';
import { OpenSlidesComponent } from 'app/openslides.component'; import { OpenSlidesComponent } from 'app/openslides.component';
import { Group } from 'app/shared/models/users/group'; import { Group } from 'app/shared/models/users/group';
import { User } from '../../shared/models/users/user';
/** /**
* The operator represents the user who is using OpenSlides. * The operator represents the user who is using OpenSlides.
@ -38,6 +39,8 @@ export class OperatorService extends OpenSlidesComponent {
username: string; username: string;
logged_in: boolean; logged_in: boolean;
private _user: User;
/** /**
* The subject that can be observed by other instances using observing functions. * The subject that can be observed by other instances using observing functions.
*/ */
@ -65,7 +68,7 @@ export class OperatorService extends OpenSlidesComponent {
} }
} }
// observe the datastore now to avoid race conditions. Ensures to // observe the DataStore now to avoid race conditions. Ensures to
// find the groups in time // find the groups in time
this.observeDataStore(); this.observeDataStore();
} }
@ -77,7 +80,7 @@ export class OperatorService extends OpenSlidesComponent {
return this.http.get<any>('/users/whoami/').pipe( return this.http.get<any>('/users/whoami/').pipe(
tap(whoami => { tap(whoami => {
if (whoami && whoami.user) { if (whoami && whoami.user) {
this.storeUser(whoami.user); this.storeUser(whoami.user as User);
} }
}), }),
catchError(this.handleError()) catchError(this.handleError())
@ -87,8 +90,11 @@ export class OperatorService extends OpenSlidesComponent {
/** /**
* Store the user Information in the operator, the localStorage and update the Observable * Store the user Information in the operator, the localStorage and update the Observable
* @param user usually a http response that represents a user. * @param user usually a http response that represents a user.
*
* Todo: Could be refractored to use the actual User Object.
* Operator is older than user, so this is still a traditional JS way
*/ */
public storeUser(user: any): void { public storeUser(user: User): void {
// store in file // store in file
this.about_me = user.about_me; this.about_me = user.about_me;
this.comment = user.comment; this.comment = user.comment;
@ -106,6 +112,7 @@ export class OperatorService extends OpenSlidesComponent {
this.structure_level = user.structure_level; this.structure_level = user.structure_level;
this.title = user.title; this.title = user.title;
this.username = user.username; this.username = user.username;
// also store in localstorrage // also store in localstorrage
this.updateLocalStorage(); this.updateLocalStorage();
// update mode to inform observers // update mode to inform observers
@ -187,6 +194,10 @@ export class OperatorService extends OpenSlidesComponent {
if (newModel instanceof Group) { if (newModel instanceof Group) {
this.addGroup(newModel); this.addGroup(newModel);
} }
if (newModel instanceof User && this.id === newModel.id) {
this._user = newModel;
}
}); });
} }
@ -243,4 +254,11 @@ export class OperatorService extends OpenSlidesComponent {
this.setObservable(newGroup); this.setObservable(newGroup);
} }
} }
/**
* get the user that corresponds to operator.
*/
get user(): User {
return this._user;
}
} }

View File

@ -1,4 +1,5 @@
import { Injector } from '@angular/core'; import { Injector } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { DataStoreService } from 'app/core/services/dataStore.service'; import { DataStoreService } from 'app/core/services/dataStore.service';

View File

@ -42,6 +42,10 @@ export class Motion extends BaseModel {
// by the config above // by the config above
workflow: Workflow; workflow: Workflow;
// for request
title: string;
text: string;
constructor( constructor(
id?: number, id?: number,
identifier?: string, identifier?: string,
@ -73,7 +77,7 @@ export class Motion extends BaseModel {
this.category_id = category_id; this.category_id = category_id;
this.motion_block_id = motion_block_id; this.motion_block_id = motion_block_id;
this.origin = origin || ''; this.origin = origin || '';
this.submitters = submitters || [new MotionSubmitter()]; this.submitters = submitters || [];
this.supporters_id = supporters_id; this.supporters_id = supporters_id;
this.comments = comments; this.comments = comments;
this.state_id = state_id; this.state_id = state_id;
@ -83,7 +87,7 @@ export class Motion extends BaseModel {
this.attachments_id = attachments_id; this.attachments_id = attachments_id;
this.polls = polls; this.polls = polls;
this.agenda_item_id = agenda_item_id; this.agenda_item_id = agenda_item_id;
this.log_messages = log_messages || [new MotionLog()]; this.log_messages = log_messages || [];
this.initDataStoreValues(); this.initDataStoreValues();
} }
@ -120,6 +124,13 @@ export class Motion extends BaseModel {
} }
} }
/** add a new motionSubmitter from user-object */
addSubmitter(user: User) {
const newSubmitter = new MotionSubmitter(null, user.id);
this.submitters.push(newSubmitter);
console.log('did addSubmitter. this.submitters: ', this.submitters);
}
/** /**
* returns the most current title from versions * returns the most current title from versions
*/ */
@ -176,7 +187,7 @@ export class Motion extends BaseModel {
*/ */
get submitterAsUser() { get submitterAsUser() {
const submitterIds = []; const submitterIds = [];
if (this.submitters) { if (this.submitters && this.submitters.length > 0) {
this.submitters.forEach(submitter => { this.submitters.forEach(submitter => {
submitterIds.push(submitter.user_id); submitterIds.push(submitter.user_id);
}); });
@ -187,18 +198,6 @@ export class Motion extends BaseModel {
} }
} }
/**
* returns the name of the first submitter
*/
get submitterName() {
const submitters = this.submitterAsUser;
if (submitters) {
return submitters[0];
} else {
return '';
}
}
/** /**
* get the category of a motion as object * get the category of a motion as object
*/ */

View File

@ -9,14 +9,14 @@
<div class='motion-title on-transition-fade'> <div class='motion-title on-transition-fade'>
<span *ngIf="newMotion">New </span> <span *ngIf="newMotion">New </span>
<span translate>Motion</span> <span translate>Motion</span>
<span *ngIf="!editMotion"> {{motion.identifier}}</span> <span *ngIf="motion && !editMotion"> {{motion.identifier}}</span>
<span *ngIf="editMotion && !newMotion"> {{metaInfoForm.get('identifier').value}}</span> <span *ngIf="editMotion && !newMotion"> {{metaInfoForm.get('identifier').value}}</span>
<span>:</span> <span>:</span>
<span *ngIf="!editMotion"> {{motion.currentTitle}}</span> <span *ngIf="motion && !editMotion"> {{motion.currentTitle}}</span>
<span *ngIf="editMotion"> {{contentForm.get('currentTitle').value}}</span> <span *ngIf="editMotion"> {{contentForm.get('currentTitle').value}}</span>
<br> <br>
<div class='motion-submitter'> <div *ngIf="motion" class='motion-submitter'>
<span translate>by</span> {{motion.submitterName}} <span translate>by</span> {{motion.submitterAsUser}}
</div> </div>
</div> </div>
</mat-toolbar> </mat-toolbar>
@ -49,19 +49,19 @@
</div> </div>
<!-- Submitter --> <!-- Submitter -->
<div *ngIf="motion.submitterName || editMotion"> <div *ngIf="motion && motion.submitters || editMotion">
<h3 translate>Submitters</h3> <h3 translate>Submitters</h3>
{{motion.submitterName}} {{motion.submitterAsUser}}
</div> </div>
<!-- Supporter --> <!-- Supporter -->
<div *ngIf='motion.supporters_id && motion.supporters_id.length > 0 || editMotion'> <div *ngIf='motion && motion.supporters_id && motion.supporters_id.length > 0 || editMotion'>
<h3 translate>Supporters</h3> <h3 translate>Supporters</h3>
<!-- print all motion supporters --> <!-- print all motion supporters -->
</div> </div>
<!-- State --> <!-- State -->
<div *ngIf='motion.state_id || editMotion'> <div *ngIf='motion && motion.state_id || editMotion'>
<div *ngIf='!editMotion'> <div *ngIf='!editMotion'>
<h3 translate>State</h3> <h3 translate>State</h3>
{{motion.state.name}} {{motion.state.name}}
@ -80,7 +80,7 @@
<!-- Recommendation --> <!-- Recommendation -->
<!-- The suggestion of the work group weather or not a motion should be accepted --> <!-- The suggestion of the work group weather or not a motion should be accepted -->
<div *ngIf='motion.recomBy && (motion.recommendation_id || editMotion)'> <div *ngIf='motion && motion.recomBy && (motion.recommendation_id || editMotion)'>
<div *ngIf='!editMotion'> <div *ngIf='!editMotion'>
<h3>{{motion.recomBy}}</h3> <h3>{{motion.recomBy}}</h3>
{{motion.recommendation.name}} {{motion.recommendation.name}}
@ -98,7 +98,7 @@
</div> </div>
<!-- Category --> <!-- Category -->
<div *ngIf="motion.category_id || editMotion"> <div *ngIf="motion && motion.category_id || editMotion">
<div *ngIf='!editMotion'> <div *ngIf='!editMotion'>
<h3 translate> Category</h3> <h3 translate> Category</h3>
{{motion.category}} {{motion.category}}
@ -113,7 +113,7 @@
</div> </div>
<!-- Origin --> <!-- Origin -->
<div *ngIf="motion.origin || editMotion"> <div *ngIf="motion && motion.origin || editMotion">
<div *ngIf='!editMotion'> <div *ngIf='!editMotion'>
<h3 translate> Origin</h3> <h3 translate> Origin</h3>
{{motion.origin}} {{motion.origin}}
@ -153,7 +153,7 @@
<form [formGroup]='contentForm' class='expansion-panel-custom-body' (ngSubmit)='saveMotion()'> <form [formGroup]='contentForm' class='expansion-panel-custom-body' (ngSubmit)='saveMotion()'>
<!-- Title --> <!-- Title -->
<div *ngIf="motion.currentTitle || editMotion"> <div *ngIf="motion && motion.currentTitle || editMotion">
<div *ngIf='!editMotion'> <div *ngIf='!editMotion'>
<h2>{{motion.currentTitle}}</h2> <h2>{{motion.currentTitle}}</h2>
</div> </div>
@ -165,15 +165,15 @@
<!-- Text --> <!-- Text -->
<!-- TODO: this is a config variable. Read it out --> <!-- TODO: this is a config variable. Read it out -->
<h3 translate>The assembly may decide:</h3> <h3 translate>The assembly may decide:</h3>
<div *ngIf='!editMotion'> <div *ngIf='motion && !editMotion'>
<div [innerHtml]='motion.currentText'></div> <div [innerHtml]='motion.currentText'></div>
</div> </div>
<mat-form-field *ngIf="editMotion" class='wide-text'> <mat-form-field *ngIf="motion && editMotion" class='wide-text'>
<textarea matInput placeholder='Motion Text' formControlName='currentText' [value]='motion.currentText'></textarea> <textarea matInput placeholder='Motion Text' formControlName='currentText' [value]='motion.currentText'></textarea>
</mat-form-field> </mat-form-field>
<!-- Reason --> <!-- Reason -->
<div *ngIf="motion.currentReason || editMotion"> <div *ngIf="motion && motion.currentReason || editMotion">
<div *ngIf='!editMotion'> <div *ngIf='!editMotion'>
<h4 translate>Reason</h4> <h4 translate>Reason</h4>
<div [innerHtml]='motion.currentReason'></div> <div [innerHtml]='motion.currentReason'></div>

View File

@ -1,37 +1,78 @@
import { Component, OnInit, ViewChild } from '@angular/core'; import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { BaseComponent } from '../../../base.component'; import { BaseComponent } from '../../../base.component';
import { Motion } from '../../../shared/models/motions/motion'; import { Motion } from '../../../shared/models/motions/motion';
import { Category } from '../../../shared/models/motions/category'; import { Category } from '../../../shared/models/motions/category';
import { FormGroup, FormBuilder } from '@angular/forms'; import { FormGroup, FormBuilder } from '@angular/forms';
import { MatExpansionPanel } from '@angular/material'; import { MatExpansionPanel } from '@angular/material';
import { DataStoreService } from '../../../core/services/dataStore.service';
import { OperatorService } from '../../../core/services/operator.service';
/**
* Component for the motion detail view
*/
@Component({ @Component({
selector: 'app-motion-detail', selector: 'app-motion-detail',
templateUrl: './motion-detail.component.html', templateUrl: './motion-detail.component.html',
styleUrls: ['./motion-detail.component.scss'] styleUrls: ['./motion-detail.component.scss']
}) })
export class MotionDetailComponent extends BaseComponent implements OnInit { // export class MotionDetailComponent extends BaseComponent implements OnInit {
export class MotionDetailComponent implements OnInit {
/**
* MatExpansionPanel for the meta info
*/
@ViewChild('metaInfoPanel') metaInfoPanel: MatExpansionPanel; @ViewChild('metaInfoPanel') metaInfoPanel: MatExpansionPanel;
/**
* MatExpansionPanel for the content panel
*/
@ViewChild('contentPanel') contentPanel: MatExpansionPanel; @ViewChild('contentPanel') contentPanel: MatExpansionPanel;
/**
* Target motion. Might be new or old
*/
motion: Motion; motion: Motion;
/**
* Motions meta-info
*/
metaInfoForm: FormGroup; metaInfoForm: FormGroup;
/**
* Motion content. Can be a new version
*/
contentForm: FormGroup; contentForm: FormGroup;
/**
* Determine if the motion is edited
*/
editMotion = false; editMotion = false;
/**
* Determine if the motion is new
*/
newMotion = false; newMotion = false;
/** /**
* Constuct the detail view.
*
* TODO: DataStore needs removed and added via the parent.
* Own service for put and post required
* *
* @param route determine if this is a new or an existing motion * @param route determine if this is a new or an existing motion
* @param formBuilder For reactive forms. Form Group and Form Control * @param formBuilder For reactive forms. Form Group and Form Control
*/ */
constructor(private route: ActivatedRoute, private formBuilder: FormBuilder) { constructor(
super(); private router: Router,
private route: ActivatedRoute,
private formBuilder: FormBuilder,
private operator: OperatorService,
private myDataStore: DataStoreService
) {
// TODO: Add super again
// super();
this.createForm(); this.createForm();
console.log('route: ', route.snapshot.url[0].path);
if (route.snapshot.url[0].path === 'new') { if (route.snapshot.url[0].path === 'new') {
this.newMotion = true; this.newMotion = true;
this.editMotion = true; this.editMotion = true;
@ -42,10 +83,10 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
console.log('params ', params); console.log('params ', params);
// has the motion of the DataStore was initialized before. // has the motion of the DataStore was initialized before.
this.motion = this.DS.get(Motion, params.id) as Motion; this.motion = this.myDataStore.get(Motion, params.id) as Motion;
// Observe motion to get the motion in the parameter and also get the changes // Observe motion to get the motion in the parameter and also get the changes
this.DS.getObservable().subscribe(newModel => { this.myDataStore.getObservable().subscribe(newModel => {
if (newModel instanceof Motion) { if (newModel instanceof Motion) {
if (newModel.id === +params.id) { if (newModel.id === +params.id) {
this.motion = newModel as Motion; this.motion = newModel as Motion;
@ -105,10 +146,15 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
const newMotionValues = { ...this.metaInfoForm.value, ...this.contentForm.value }; const newMotionValues = { ...this.metaInfoForm.value, ...this.contentForm.value };
this.motion.patchValues(newMotionValues); this.motion.patchValues(newMotionValues);
console.log('save motion: this: ', this); // TODO: This is DRAFT. Reads out Motion version directly. Potentially insecure.
this.motion.title = this.motion.currentTitle;
this.motion.text = this.motion.currentText;
this.DS.save(this.motion).subscribe(answer => { this.myDataStore.save(this.motion).subscribe(answer => {
console.log(answer); console.log('answer, ', answer);
if (answer && answer.id && this.newMotion) {
this.router.navigate(['./motions/' + answer.id]);
}
}); });
} }
@ -116,7 +162,7 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
* return all Categories. * return all Categories.
*/ */
getMotionCategories(): Category[] { getMotionCategories(): Category[] {
const categories = this.DS.get(Category); const categories = this.myDataStore.get(Category);
return categories as Category[]; return categories as Category[];
} }

View File

@ -7,6 +7,9 @@ import { Motion } from '../../../shared/models/motions/motion';
import { MatTable, MatPaginator, MatSort, MatTableDataSource } from '@angular/material'; import { MatTable, MatPaginator, MatSort, MatTableDataSource } from '@angular/material';
import { Workflow } from '../../../shared/models/motions/workflow'; import { Workflow } from '../../../shared/models/motions/workflow';
/**
* Component that displays all the motions in a Table using DataSource.
*/
@Component({ @Component({
selector: 'app-motion-list', selector: 'app-motion-list',
templateUrl: './motion-list.component.html', templateUrl: './motion-list.component.html',
@ -28,14 +31,26 @@ export class MotionListComponent extends BaseComponent implements OnInit {
*/ */
dataSource: MatTableDataSource<Motion>; dataSource: MatTableDataSource<Motion>;
/**
* The table itself.
*/
@ViewChild(MatTable) table: MatTable<Motion>; @ViewChild(MatTable) table: MatTable<Motion>;
/**
* Pagination. Might be turned off to all motions at once.
*/
@ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatPaginator) paginator: MatPaginator;
/**
* Sort the Table
*/
@ViewChild(MatSort) sort: MatSort; @ViewChild(MatSort) sort: MatSort;
/** /**
* Use for minimal width * Use for minimal width
*/ */
columnsToDisplayMinWidth = ['identifier', 'title', 'state']; columnsToDisplayMinWidth = ['identifier', 'title', 'state'];
/** /**
* Use for maximal width * Use for maximal width
*/ */
@ -43,13 +58,16 @@ export class MotionListComponent extends BaseComponent implements OnInit {
/** /**
* Constructor implements title and translation Module. * Constructor implements title and translation Module.
* @param titleService *
* @param translate * @param titleService Title
* @param translate Translation
* @param router Router
* @param route Current route
*/ */
constructor( constructor(
public router: Router, protected titleService: Title,
titleService: Title,
protected translate: TranslateService, protected translate: TranslateService,
private router: Router,
private route: ActivatedRoute private route: ActivatedRoute
) { ) {
super(titleService, translate); super(titleService, translate);
@ -78,6 +96,11 @@ export class MotionListComponent extends BaseComponent implements OnInit {
}); });
} }
/**
* Select a motion from list. Executed via click.
*
* @param motion The row the user clicked at
*/
selectMotion(motion) { selectMotion(motion) {
this.router.navigate(['./' + motion.id], { relativeTo: this.route }); this.router.navigate(['./' + motion.id], { relativeTo: this.route });
} }
@ -102,6 +125,8 @@ export class MotionListComponent extends BaseComponent implements OnInit {
/** /**
* Download all motions As PDF and DocX * Download all motions As PDF and DocX
*
* TODO: Currently does nothing
*/ */
downloadMotionsButton() { downloadMotionsButton() {
console.log('Download Motions Button'); console.log('Download Motions Button');

View File

@ -111,8 +111,7 @@ export class StartComponent extends BaseComponent implements OnInit {
* function to print datastore * function to print datastore
*/ */
giveDataStore() { giveDataStore() {
// this.DS.printWhole(); this.DS.printWhole();
console.log('only the motions: \n', this.DS.get(Motion) as Motion[]);
} }
/** /**