Merge pull request #5104 from jsangmeister/csv-import-fix
Fixed CSV import
This commit is contained in:
commit
d286378524
@ -209,9 +209,9 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User, UserTi
|
||||
*
|
||||
* @param newEntries
|
||||
*/
|
||||
public async bulkCreate(newEntries: NewEntry<ViewUser>[]): Promise<number[]> {
|
||||
public async bulkCreate(newEntries: NewEntry<User>[]): Promise<number[]> {
|
||||
const data = newEntries.map(entry => {
|
||||
return { ...entry.newEntry.user, importTrackId: entry.importTrackId };
|
||||
return { ...entry.newEntry, importTrackId: entry.importTrackId };
|
||||
});
|
||||
const response = (await this.httpService.post(`/rest/users/user/mass_import/`, { users: data })) as {
|
||||
detail: string;
|
||||
|
@ -5,7 +5,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { Papa, ParseConfig } from 'ngx-papaparse';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
import { BaseModel } from 'app/shared/models/base/base-model';
|
||||
|
||||
/**
|
||||
* Interface for value- Label combinations.
|
||||
@ -53,7 +53,7 @@ type CsvImportStatus = 'new' | 'error' | 'done';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export abstract class BaseImportService<V extends BaseViewModel> {
|
||||
export abstract class BaseImportService<M extends BaseModel> {
|
||||
/**
|
||||
* List of possible errors and their verbose explanation
|
||||
*/
|
||||
@ -127,12 +127,12 @@ export abstract class BaseImportService<V extends BaseViewModel> {
|
||||
/**
|
||||
* the list of parsed models that have been extracted from the opened file
|
||||
*/
|
||||
private _entries: NewEntry<V>[] = [];
|
||||
private _entries: NewEntry<M>[] = [];
|
||||
|
||||
/**
|
||||
* BehaviorSubject for displaying a preview for the currently selected entries
|
||||
*/
|
||||
public newEntries = new BehaviorSubject<NewEntry<V>[]>([]);
|
||||
public newEntries = new BehaviorSubject<NewEntry<M>[]>([]);
|
||||
|
||||
/**
|
||||
* Emits an error string to display if a file import cannot be done
|
||||
@ -159,7 +159,7 @@ export abstract class BaseImportService<V extends BaseViewModel> {
|
||||
* Returns the current entries. For internal use in extending classes, as it
|
||||
* might not be filled with data at all times (see {@link newEntries} for a BehaviorSubject)
|
||||
*/
|
||||
protected get entries(): NewEntry<V>[] {
|
||||
protected get entries(): NewEntry<M>[] {
|
||||
return this._entries;
|
||||
}
|
||||
|
||||
@ -220,7 +220,7 @@ export abstract class BaseImportService<V extends BaseViewModel> {
|
||||
*
|
||||
* @param entries: an array of prepared newEntry objects
|
||||
*/
|
||||
public setParsedEntries(entries: NewEntry<V>[]): void {
|
||||
public setParsedEntries(entries: NewEntry<M>[]): void {
|
||||
this.clearData();
|
||||
this.clearPreview();
|
||||
if (!entries) {
|
||||
@ -236,7 +236,7 @@ export abstract class BaseImportService<V extends BaseViewModel> {
|
||||
* returning a new entry object
|
||||
* @param line a line extracted by the CSV (not including the header)
|
||||
*/
|
||||
public abstract mapData(line: string): NewEntry<V>;
|
||||
public abstract mapData(line: string): NewEntry<M>;
|
||||
|
||||
/**
|
||||
* Trigger for executing the import.
|
||||
@ -279,7 +279,7 @@ export abstract class BaseImportService<V extends BaseViewModel> {
|
||||
*
|
||||
* @returns an observable BehaviorSubject
|
||||
*/
|
||||
public getNewEntries(): Observable<NewEntry<V>[]> {
|
||||
public getNewEntries(): Observable<NewEntry<M>[]> {
|
||||
return this.newEntries.asObservable();
|
||||
}
|
||||
|
||||
@ -357,7 +357,7 @@ export abstract class BaseImportService<V extends BaseViewModel> {
|
||||
/**
|
||||
* set a list of short names for error, indicating which column failed
|
||||
*/
|
||||
public setError(entry: NewEntry<V>, error: string): void {
|
||||
public setError(entry: NewEntry<M>, error: string): void {
|
||||
if (this.errorList.hasOwnProperty(error)) {
|
||||
if (!entry.errors) {
|
||||
entry.errors = [error];
|
||||
@ -385,7 +385,7 @@ export abstract class BaseImportService<V extends BaseViewModel> {
|
||||
* @param error The error to check for
|
||||
* @returns true if the error is present
|
||||
*/
|
||||
public hasError(entry: NewEntry<V>, error: string): boolean {
|
||||
public hasError(entry: NewEntry<M>, error: string): boolean {
|
||||
return entry.errors.includes(error);
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { AgendaImportListComponent } from './components/agenda-import-list/agenda-import-list.component';
|
||||
import { AgendaListComponent } from './components/agenda-list/agenda-list.component';
|
||||
import { AgendaSortComponent } from './components/agenda-sort/agenda-sort.component';
|
||||
import { WatchForChangesGuard } from 'app/shared/utils/watch-for-changes.guard';
|
||||
import { TopicImportListComponent } from 'app/site/topics/components/topic-import-list/topic-import-list.component';
|
||||
import { ListOfSpeakersComponent } from './components/list-of-speakers/list-of-speakers.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: AgendaListComponent, pathMatch: 'full' },
|
||||
{ path: 'import', component: AgendaImportListComponent, data: { basePerm: 'agenda.can_manage' } },
|
||||
{ path: 'import', component: TopicImportListComponent, data: { basePerm: 'agenda.can_manage' } },
|
||||
{
|
||||
path: 'sort-agenda',
|
||||
component: AgendaSortComponent,
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AgendaImportListComponent } from './components/agenda-import-list/agenda-import-list.component';
|
||||
import { AgendaListComponent } from './components/agenda-list/agenda-list.component';
|
||||
import { AgendaRoutingModule } from './agenda-routing.module';
|
||||
import { AgendaSortComponent } from './components/agenda-sort/agenda-sort.component';
|
||||
import { TopicImportListComponent } from 'app/site/topics/components/topic-import-list/topic-import-list.component';
|
||||
import { ItemInfoDialogComponent } from './components/item-info-dialog/item-info-dialog.component';
|
||||
import { ListOfSpeakersComponent } from './components/list-of-speakers/list-of-speakers.component';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
@ -18,7 +18,7 @@ import { SharedModule } from '../../shared/shared.module';
|
||||
declarations: [
|
||||
AgendaListComponent,
|
||||
ItemInfoDialogComponent,
|
||||
AgendaImportListComponent,
|
||||
TopicImportListComponent,
|
||||
AgendaSortComponent,
|
||||
ListOfSpeakersComponent
|
||||
]
|
||||
|
@ -8,15 +8,15 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { auditTime } from 'rxjs/operators';
|
||||
|
||||
import { BaseImportService, NewEntry, ValueLabelCombination } from 'app/core/ui-services/base-import.service';
|
||||
import { BaseModel } from 'app/shared/models/base/base-model';
|
||||
import { getLongPreview, getShortPreview } from 'app/shared/utils/previewStrings';
|
||||
import { BaseViewComponent } from './base-view';
|
||||
import { BaseViewModel } from './base-view-model';
|
||||
|
||||
export abstract class BaseImportListComponent<V extends BaseViewModel> extends BaseViewComponent implements OnInit {
|
||||
export abstract class BaseImportListComponent<M extends BaseModel> extends BaseViewComponent implements OnInit {
|
||||
/**
|
||||
* The data source for a table. Requires to be initialised with a BaseViewModel
|
||||
*/
|
||||
public dataSource: MatTableDataSource<NewEntry<V>>;
|
||||
public dataSource: MatTableDataSource<NewEntry<M>>;
|
||||
|
||||
/**
|
||||
* Helper function for previews
|
||||
@ -48,7 +48,7 @@ export abstract class BaseImportListComponent<V extends BaseViewModel> extends B
|
||||
* The table itself
|
||||
*/
|
||||
@ViewChild(MatTable, { static: false })
|
||||
protected table: MatTable<NewEntry<V>>;
|
||||
protected table: MatTable<NewEntry<M>>;
|
||||
|
||||
/**
|
||||
* @returns the amount of total item successfully parsed
|
||||
@ -112,7 +112,7 @@ export abstract class BaseImportListComponent<V extends BaseViewModel> extends B
|
||||
*/
|
||||
|
||||
public constructor(
|
||||
protected importer: BaseImportService<V>,
|
||||
protected importer: BaseImportService<M>,
|
||||
titleService: Title,
|
||||
translate: TranslateService,
|
||||
matSnackBar: MatSnackBar
|
||||
@ -204,7 +204,7 @@ export abstract class BaseImportListComponent<V extends BaseViewModel> extends B
|
||||
* @param row a newEntry object with a current status
|
||||
* @returns a css class name
|
||||
*/
|
||||
public getStateClass(row: NewEntry<V>): string {
|
||||
public getStateClass(row: NewEntry<M>): string {
|
||||
switch (row.status) {
|
||||
case 'done':
|
||||
return 'import-done import-decided';
|
||||
@ -220,7 +220,7 @@ export abstract class BaseImportListComponent<V extends BaseViewModel> extends B
|
||||
* @param entry a newEntry object with a current status
|
||||
* @eturn the icon for the action of the item
|
||||
*/
|
||||
public getActionIcon(entry: NewEntry<V>): string {
|
||||
public getActionIcon(entry: NewEntry<M>): string {
|
||||
switch (entry.status) {
|
||||
case 'error': // no import possible
|
||||
return 'block';
|
||||
@ -286,7 +286,7 @@ export abstract class BaseImportListComponent<V extends BaseViewModel> extends B
|
||||
* @param error An error as defined as key of {@link errorList}
|
||||
* @returns true if the error is present in the entry described in the row
|
||||
*/
|
||||
public hasError(row: NewEntry<V>, error: string): boolean {
|
||||
public hasError(row: NewEntry<M>, error: string): boolean {
|
||||
return this.importer.hasError(row, error);
|
||||
}
|
||||
}
|
||||
|
@ -6,14 +6,4 @@ import { Motion } from 'app/shared/models/motions/motion';
|
||||
*/
|
||||
export class CreateMotion extends Motion {
|
||||
public submitters_id: number[];
|
||||
|
||||
public category_id: number;
|
||||
|
||||
public motion_block_id: number;
|
||||
|
||||
public tags_id: number[];
|
||||
|
||||
public constructor(input?: any) {
|
||||
super(input);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { CreateMotion } from './create-motion';
|
||||
import { ViewCreateMotion } from './view-create-motion';
|
||||
|
||||
/**
|
||||
* Interface for correlating between strings representing BaseModels and existing
|
||||
@ -17,9 +16,7 @@ export interface CsvMapping {
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
export class ViewCsvCreateMotion extends ViewCreateMotion {
|
||||
protected _motion: CreateMotion;
|
||||
|
||||
export class ImportCreateMotion extends CreateMotion {
|
||||
/**
|
||||
* Mapping for a new/existing category.
|
||||
*/
|
||||
@ -40,10 +37,6 @@ export class ViewCsvCreateMotion extends ViewCreateMotion {
|
||||
*/
|
||||
public csvTags: CsvMapping[];
|
||||
|
||||
public constructor(motion?: CreateMotion) {
|
||||
super(motion);
|
||||
}
|
||||
|
||||
/**
|
||||
* takes a list of motion block mappings to update the current csvMotionblock.
|
||||
* Returns the amount of entries that remain unmatched
|
||||
@ -54,13 +47,13 @@ export class ViewCsvCreateMotion extends ViewCreateMotion {
|
||||
if (!this.csvMotionblock) {
|
||||
return 0;
|
||||
} else if (this.csvMotionblock.id) {
|
||||
this.motion.motion_block_id = this.csvMotionblock.id;
|
||||
this.motion_block_id = this.csvMotionblock.id;
|
||||
return 0;
|
||||
} else {
|
||||
const newBlock = motionBlocks.find(newMotionBlock => newMotionBlock.name === this.csvMotionblock.name);
|
||||
if (newBlock) {
|
||||
this.csvMotionblock = newBlock;
|
||||
this.motion.motion_block_id = newBlock.id;
|
||||
this.motion_block_id = newBlock.id;
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
@ -78,13 +71,13 @@ export class ViewCsvCreateMotion extends ViewCreateMotion {
|
||||
if (!this.csvCategory) {
|
||||
return 0;
|
||||
} else if (this.csvCategory.id) {
|
||||
this.motion.category_id = this.csvCategory.id;
|
||||
this.category_id = this.csvCategory.id;
|
||||
return 0;
|
||||
} else {
|
||||
const newCat = categories.find(newCategory => newCategory.name === this.csvCategory.name);
|
||||
if (newCat) {
|
||||
this.csvCategory = newCat;
|
||||
this.motion.category_id = newCat.id;
|
||||
this.category_id = newCat.id;
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
@ -118,7 +111,7 @@ export class ViewCsvCreateMotion extends ViewCreateMotion {
|
||||
open += 1;
|
||||
}
|
||||
});
|
||||
this.motion.submitters_id = ids;
|
||||
this.submitters_id = ids;
|
||||
return open;
|
||||
}
|
||||
|
||||
@ -149,7 +142,7 @@ export class ViewCsvCreateMotion extends ViewCreateMotion {
|
||||
++open;
|
||||
}
|
||||
}
|
||||
this.motion.tags_id = ids;
|
||||
this.tags_id = ids;
|
||||
return open;
|
||||
}
|
||||
}
|
@ -4,8 +4,8 @@ import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { Motion } from 'app/shared/models/motions/motion';
|
||||
import { BaseImportListComponent } from 'app/site/base/base-import-list';
|
||||
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
||||
import { MotionCsvExportService } from 'app/site/motions/services/motion-csv-export.service';
|
||||
import { MotionImportService } from 'app/site/motions/services/motion-import.service';
|
||||
|
||||
@ -16,7 +16,7 @@ import { MotionImportService } from 'app/site/motions/services/motion-import.ser
|
||||
selector: 'os-motion-import-list',
|
||||
templateUrl: './motion-import-list.component.html'
|
||||
})
|
||||
export class MotionImportListComponent extends BaseImportListComponent<ViewMotion> {
|
||||
export class MotionImportListComponent extends BaseImportListComponent<Motion> {
|
||||
/**
|
||||
* Fetach a list of the headers expected by the importer, and prepare them
|
||||
* to be translateable (upper case)
|
||||
|
@ -4,8 +4,8 @@ import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { StatuteParagraph } from 'app/shared/models/motions/statute-paragraph';
|
||||
import { BaseImportListComponent } from 'app/site/base/base-import-list';
|
||||
import { ViewStatuteParagraph } from 'app/site/motions/models/view-statute-paragraph';
|
||||
import { StatuteCsvExportService } from 'app/site/motions/services/statute-csv-export.service';
|
||||
import { StatuteImportService } from 'app/site/motions/services/statute-import.service';
|
||||
|
||||
@ -16,7 +16,7 @@ import { StatuteImportService } from 'app/site/motions/services/statute-import.s
|
||||
selector: 'os-statute-import-list',
|
||||
templateUrl: './statute-import-list.component.html'
|
||||
})
|
||||
export class StatuteImportListComponent extends BaseImportListComponent<ViewStatuteParagraph> {
|
||||
export class StatuteImportListComponent extends BaseImportListComponent<StatuteParagraph> {
|
||||
/**
|
||||
* Constructor for list view bases
|
||||
*
|
||||
|
@ -12,11 +12,11 @@ import { UserRepositoryService } from 'app/core/repositories/users/user-reposito
|
||||
import { BaseImportService, NewEntry } from 'app/core/ui-services/base-import.service';
|
||||
import { Tag } from 'app/shared/models/core/tag';
|
||||
import { Category } from 'app/shared/models/motions/category';
|
||||
import { Motion } from 'app/shared/models/motions/motion';
|
||||
import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
||||
import { CreateMotion } from '../models/create-motion';
|
||||
import { CsvMapping, ImportCreateMotion } from '../models/import-create-motion';
|
||||
import { motionExportOnly, motionImportExportHeaderOrder } from '../motions.constants';
|
||||
import { CsvMapping, ViewCsvCreateMotion } from '../models/view-csv-create-motion';
|
||||
import { ViewMotion } from '../models/view-motion';
|
||||
|
||||
/**
|
||||
* Service for motion imports
|
||||
@ -24,7 +24,7 @@ import { ViewMotion } from '../models/view-motion';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MotionImportService extends BaseImportService<ViewMotion> {
|
||||
export class MotionImportService extends BaseImportService<Motion> {
|
||||
/**
|
||||
* List of possible errors and their verbose explanation
|
||||
*/
|
||||
@ -104,8 +104,8 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
|
||||
* @param line
|
||||
* @returns a new Entry representing a Motion
|
||||
*/
|
||||
public mapData(line: string): NewEntry<ViewMotion> {
|
||||
const newEntry = new ViewCsvCreateMotion(new CreateMotion());
|
||||
public mapData(line: string): NewEntry<Motion> {
|
||||
const newEntry = new ImportCreateMotion(new CreateMotion());
|
||||
const headerLength = Math.min(this.expectedHeader.length, line.length);
|
||||
for (let idx = 0; idx < headerLength; idx++) {
|
||||
switch (this.expectedHeader[idx]) {
|
||||
@ -122,11 +122,11 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
|
||||
newEntry.csvTags = this.getTags(line[idx]);
|
||||
break;
|
||||
default:
|
||||
newEntry.motion[this.expectedHeader[idx]] = line[idx];
|
||||
newEntry[this.expectedHeader[idx]] = line[idx];
|
||||
}
|
||||
}
|
||||
const hasDuplicates = this.repo.getViewModelList().some(motion => motion.identifier === newEntry.identifier);
|
||||
const entry: NewEntry<ViewMotion> = {
|
||||
const entry: NewEntry<Motion> = {
|
||||
newEntry: newEntry,
|
||||
hasDuplicates: hasDuplicates,
|
||||
status: hasDuplicates ? 'error' : 'new',
|
||||
@ -157,31 +157,31 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
|
||||
if (entry.status !== 'new') {
|
||||
continue;
|
||||
}
|
||||
const openBlocks = (entry.newEntry as ViewCsvCreateMotion).solveMotionBlocks(this.newMotionBlocks);
|
||||
const openBlocks = (entry.newEntry as ImportCreateMotion).solveMotionBlocks(this.newMotionBlocks);
|
||||
if (openBlocks) {
|
||||
this.setError(entry, 'MotionBlock');
|
||||
this.updatePreview();
|
||||
continue;
|
||||
}
|
||||
const openCategories = (entry.newEntry as ViewCsvCreateMotion).solveCategory(this.newCategories);
|
||||
const openCategories = (entry.newEntry as ImportCreateMotion).solveCategory(this.newCategories);
|
||||
if (openCategories) {
|
||||
this.setError(entry, 'Category');
|
||||
this.updatePreview();
|
||||
continue;
|
||||
}
|
||||
const openUsers = (entry.newEntry as ViewCsvCreateMotion).solveSubmitters(this.newSubmitters);
|
||||
const openUsers = (entry.newEntry as ImportCreateMotion).solveSubmitters(this.newSubmitters);
|
||||
if (openUsers) {
|
||||
this.setError(entry, 'Submitters');
|
||||
this.updatePreview();
|
||||
continue;
|
||||
}
|
||||
const openTags = (entry.newEntry as ViewCsvCreateMotion).solveTags(this.newTags);
|
||||
const openTags = (entry.newEntry as ImportCreateMotion).solveTags(this.newTags);
|
||||
if (openTags) {
|
||||
this.setError(entry, 'Tags');
|
||||
this.updatePreview();
|
||||
continue;
|
||||
}
|
||||
await this.repo.create((entry.newEntry as ViewCsvCreateMotion).motion);
|
||||
await this.repo.create(entry.newEntry as ImportCreateMotion);
|
||||
entry.status = 'done';
|
||||
}
|
||||
this.updatePreview();
|
||||
|
@ -7,7 +7,6 @@ import { Papa } from 'ngx-papaparse';
|
||||
import { StatuteParagraphRepositoryService } from 'app/core/repositories/motions/statute-paragraph-repository.service';
|
||||
import { BaseImportService, NewEntry } from 'app/core/ui-services/base-import.service';
|
||||
import { StatuteParagraph } from 'app/shared/models/motions/statute-paragraph';
|
||||
import { ViewStatuteParagraph } from '../models/view-statute-paragraph';
|
||||
|
||||
/**
|
||||
* Service for motion imports
|
||||
@ -15,7 +14,7 @@ import { ViewStatuteParagraph } from '../models/view-statute-paragraph';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class StatuteImportService extends BaseImportService<ViewStatuteParagraph> {
|
||||
export class StatuteImportService extends BaseImportService<StatuteParagraph> {
|
||||
/**
|
||||
* List of possible errors and their verbose explanation
|
||||
*/
|
||||
@ -60,16 +59,16 @@ export class StatuteImportService extends BaseImportService<ViewStatuteParagraph
|
||||
* @param line
|
||||
* @returns a new Entry representing a Motion
|
||||
*/
|
||||
public mapData(line: string): NewEntry<ViewStatuteParagraph> {
|
||||
const newEntry = new ViewStatuteParagraph(new StatuteParagraph());
|
||||
public mapData(line: string): NewEntry<StatuteParagraph> {
|
||||
const newEntry = new StatuteParagraph(new StatuteParagraph());
|
||||
const headerLength = Math.min(this.expectedHeader.length, line.length);
|
||||
for (let idx = 0; idx < headerLength; idx++) {
|
||||
switch (this.expectedHeader[idx]) {
|
||||
case 'title':
|
||||
newEntry.statuteParagraph.title = line[idx];
|
||||
newEntry.title = line[idx];
|
||||
break;
|
||||
case 'text':
|
||||
newEntry.statuteParagraph.text = line[idx];
|
||||
newEntry.text = line[idx];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -91,7 +90,7 @@ export class StatuteImportService extends BaseImportService<ViewStatuteParagraph
|
||||
if (entry.status !== 'new') {
|
||||
continue;
|
||||
}
|
||||
await this.repo.create(entry.newEntry.statuteParagraph);
|
||||
await this.repo.create(entry.newEntry);
|
||||
entry.status = 'done';
|
||||
}
|
||||
this.updatePreview();
|
||||
|
@ -197,21 +197,21 @@
|
||||
</ng-container>
|
||||
|
||||
<!-- duration column -->
|
||||
<ng-container matColumnDef="duration">
|
||||
<ng-container matColumnDef="agenda_duration">
|
||||
<mat-header-cell *matHeaderCellDef translate>Duration</mat-header-cell>
|
||||
<mat-cell *matCellDef="let entry"> {{ getDuration(entry.newEntry.duration) }} </mat-cell>
|
||||
<mat-cell *matCellDef="let entry"> {{ getDuration(entry.newEntry.agenda_duration) }} </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- comment column-->
|
||||
<ng-container matColumnDef="comment">
|
||||
<ng-container matColumnDef="agenda_comment">
|
||||
<mat-header-cell *matHeaderCellDef translate>Comment</mat-header-cell>
|
||||
<mat-cell *matCellDef="let entry"> {{ entry.newEntry.comment }} </mat-cell>
|
||||
<mat-cell *matCellDef="let entry"> {{ entry.newEntry.agenda_comment }} </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- type column -->
|
||||
<ng-container matColumnDef="type">
|
||||
<ng-container matColumnDef="agenda_type">
|
||||
<mat-header-cell *matHeaderCellDef translate>Type</mat-header-cell>
|
||||
<mat-cell *matCellDef="let entry"> {{ getTypeString(entry.newEntry.type) | translate }} </mat-cell>
|
||||
<mat-cell *matCellDef="let entry"> {{ getTypeString(entry.newEntry.agenda_type) | translate }} </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
|
@ -2,21 +2,21 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
import { AgendaImportListComponent } from './agenda-import-list.component';
|
||||
import { TopicImportListComponent } from './topic-import-list.component';
|
||||
|
||||
describe('AgendaImportListComponent', () => {
|
||||
let component: AgendaImportListComponent;
|
||||
let fixture: ComponentFixture<AgendaImportListComponent>;
|
||||
describe('TopicImportListComponent', () => {
|
||||
let component: TopicImportListComponent;
|
||||
let fixture: ComponentFixture<TopicImportListComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AgendaImportListComponent],
|
||||
declarations: [TopicImportListComponent],
|
||||
imports: [E2EImportsModule]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AgendaImportListComponent);
|
||||
fixture = TestBed.createComponent(TopicImportListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@ -5,21 +5,21 @@ import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { AgendaImportService } from '../../services/agenda-import.service';
|
||||
import { CsvExportService } from 'app/core/ui-services/csv-export.service';
|
||||
import { DurationService } from 'app/core/ui-services/duration.service';
|
||||
import { ItemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { BaseImportListComponent } from 'app/site/base/base-import-list';
|
||||
import { ViewCreateTopic } from 'app/site/topics/models/view-create-topic';
|
||||
import { CreateTopic } from 'app/site/topics/models/create-topic';
|
||||
import { TopicImportService } from '../../../topics/services/topic-import.service';
|
||||
|
||||
/**
|
||||
* Component for the agenda import list view.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'os-agenda-import-list',
|
||||
templateUrl: './agenda-import-list.component.html'
|
||||
selector: 'os-topic-import-list',
|
||||
templateUrl: './topic-import-list.component.html'
|
||||
})
|
||||
export class AgendaImportListComponent extends BaseImportListComponent<ViewCreateTopic> {
|
||||
export class TopicImportListComponent extends BaseImportListComponent<CreateTopic> {
|
||||
/**
|
||||
* A form for text input
|
||||
*/
|
||||
@ -40,7 +40,7 @@ export class AgendaImportListComponent extends BaseImportListComponent<ViewCreat
|
||||
titleService: Title,
|
||||
matSnackBar: MatSnackBar,
|
||||
translate: TranslateService,
|
||||
importer: AgendaImportService,
|
||||
importer: TopicImportService,
|
||||
formBuilder: FormBuilder,
|
||||
private exporter: CsvExportService,
|
||||
private durationService: DurationService
|
||||
@ -81,7 +81,7 @@ export class AgendaImportListComponent extends BaseImportListComponent<ViewCreat
|
||||
* Sends the data in the text field input area to the importer
|
||||
*/
|
||||
public parseTextArea(): void {
|
||||
(this.importer as AgendaImportService).parseTextArea(this.textAreaForm.get('inputtext').value);
|
||||
(this.importer as TopicImportService).parseTextArea(this.textAreaForm.get('inputtext').value);
|
||||
}
|
||||
|
||||
/**
|
@ -14,4 +14,13 @@ export class CreateTopic extends Topic {
|
||||
public constructor(input?: any) {
|
||||
super(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the CreateTopic is valid. Currently only requires an existing title
|
||||
*
|
||||
* @returns true if it is a valid Topic
|
||||
*/
|
||||
public get isValid(): boolean {
|
||||
return this.title ? true : false;
|
||||
}
|
||||
}
|
||||
|
@ -1,114 +0,0 @@
|
||||
import { CreateTopic } from './create-topic';
|
||||
import { ViewTopic } from './view-topic';
|
||||
|
||||
/**
|
||||
* View model for Topic('Agenda item') creation.
|
||||
*
|
||||
*/
|
||||
export class ViewCreateTopic extends ViewTopic {
|
||||
public get topic(): CreateTopic {
|
||||
return this._model as CreateTopic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the field representing the new title
|
||||
*
|
||||
* @returns title string as set during import (may be different from getTitle)
|
||||
*/
|
||||
public get title(): string {
|
||||
return this.topic.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the title. Sets the title of the underlying CreateTopic
|
||||
*
|
||||
* @param title
|
||||
*/
|
||||
public set title(title: string) {
|
||||
this.topic.title = title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the duration in minutes
|
||||
*/
|
||||
public get duration(): number {
|
||||
return this.topic.agenda_duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the duration. Expects values as in {@link DurationService}
|
||||
*/
|
||||
public set duration(duration: number) {
|
||||
this.topic.agenda_duration = duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the comment string as set during the import
|
||||
*/
|
||||
public get comment(): string {
|
||||
return this.topic.agenda_comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the comment string of the underlying topic
|
||||
* @param comment A string to set as comment
|
||||
*/
|
||||
public set comment(comment: string) {
|
||||
this.topic.agenda_comment = comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns a number representing the item type
|
||||
*/
|
||||
public get type(): number {
|
||||
return this.topic.agenda_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the item type for the topic's agenda entry. No validation is done here.
|
||||
*
|
||||
* @param A number representing the item's type. See {@link itemVisibilityChoices}
|
||||
* for the interpretation of type numbers.
|
||||
*/
|
||||
public set type(type: number) {
|
||||
this.topic.agenda_type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text string of the underlying topic
|
||||
*
|
||||
* @param text A string.
|
||||
*/
|
||||
public set text(text: string) {
|
||||
this.topic.text = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the comment string of the underlying topic
|
||||
*/
|
||||
public get text(): string {
|
||||
return this.topic.text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the CreateTopic is valid. Currently only requires an existing title
|
||||
*
|
||||
* @returns true if it is a valid Topic
|
||||
*/
|
||||
public get isValid(): boolean {
|
||||
return this.title ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor. Empty
|
||||
*
|
||||
* @param topic A CreateTopic
|
||||
*/
|
||||
public constructor(topic: CreateTopic) {
|
||||
super(topic);
|
||||
}
|
||||
|
||||
public getVerboseName = () => {
|
||||
throw new Error('This should not be used');
|
||||
};
|
||||
}
|
@ -2,9 +2,9 @@ import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
import { AgendaImportService } from './agenda-import.service';
|
||||
import { TopicImportService } from './topic-import.service';
|
||||
|
||||
describe('AgendaImportService', () => {
|
||||
describe('TopicImportService', () => {
|
||||
beforeEach(() =>
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule]
|
||||
@ -12,7 +12,7 @@ describe('AgendaImportService', () => {
|
||||
);
|
||||
|
||||
it('should be created', () => {
|
||||
const service: AgendaImportService = TestBed.get(AgendaImportService);
|
||||
const service: TopicImportService = TestBed.get(TopicImportService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
@ -8,18 +8,17 @@ import { TopicRepositoryService } from 'app/core/repositories/topics/topic-repos
|
||||
import { BaseImportService, NewEntry } from 'app/core/ui-services/base-import.service';
|
||||
import { DurationService } from 'app/core/ui-services/duration.service';
|
||||
import { ItemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { ViewCreateTopic } from 'app/site/topics/models/view-create-topic';
|
||||
import { CreateTopic } from '../../topics/models/create-topic';
|
||||
import { CreateTopic } from '../models/create-topic';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AgendaImportService extends BaseImportService<ViewCreateTopic> {
|
||||
export class TopicImportService extends BaseImportService<CreateTopic> {
|
||||
/**
|
||||
* Helper for mapping the expected header in a typesafe way. Values will be passed to
|
||||
* {@link expectedHeader}
|
||||
*/
|
||||
public headerMap: (keyof ViewCreateTopic)[] = ['title', 'text', 'duration', 'comment', 'type'];
|
||||
public headerMap: (keyof CreateTopic)[] = ['title', 'text', 'agenda_duration', 'agenda_comment', 'agenda_type'];
|
||||
|
||||
/**
|
||||
* The minimimal number of header entries needed to successfully create an entry
|
||||
@ -31,7 +30,7 @@ export class AgendaImportService extends BaseImportService<ViewCreateTopic> {
|
||||
*/
|
||||
public errorList = {
|
||||
NoTitle: 'A Topic needs a title',
|
||||
Duplicates: 'A topic tiwh this title already exists',
|
||||
Duplicates: 'A topic with this title already exists',
|
||||
ParsingErrors: 'Some csv values could not be read correctly.'
|
||||
};
|
||||
|
||||
@ -67,17 +66,17 @@ export class AgendaImportService extends BaseImportService<ViewCreateTopic> {
|
||||
* @param line a line extracted by the CSV (without the header)
|
||||
* @returns a new entry for a Topic
|
||||
*/
|
||||
public mapData(line: string): NewEntry<ViewCreateTopic> {
|
||||
const newEntry = new ViewCreateTopic(new CreateTopic());
|
||||
public mapData(line: string): NewEntry<CreateTopic> {
|
||||
const newEntry = new CreateTopic();
|
||||
const headerLength = Math.min(this.expectedHeader.length, line.length);
|
||||
let hasErrors = false;
|
||||
for (let idx = 0; idx < headerLength; idx++) {
|
||||
switch (this.expectedHeader[idx]) {
|
||||
case 'duration':
|
||||
case 'agenda_duration':
|
||||
try {
|
||||
const duration = this.parseDuration(line[idx]);
|
||||
if (duration > 0) {
|
||||
newEntry.duration = duration;
|
||||
newEntry.agenda_duration = duration;
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof TypeError) {
|
||||
@ -86,9 +85,9 @@ export class AgendaImportService extends BaseImportService<ViewCreateTopic> {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'type':
|
||||
case 'agenda_type':
|
||||
try {
|
||||
newEntry.type = this.parseType(line[idx]);
|
||||
newEntry.agenda_type = this.parseType(line[idx]);
|
||||
} catch (e) {
|
||||
if (e instanceof TypeError) {
|
||||
hasErrors = true;
|
||||
@ -103,10 +102,10 @@ export class AgendaImportService extends BaseImportService<ViewCreateTopic> {
|
||||
const hasDuplicates = this.repo.getViewModelList().some(topic => topic.title === newEntry.title);
|
||||
|
||||
// set type to 'public' if none is given in import
|
||||
if (!newEntry.type) {
|
||||
newEntry.type = 1;
|
||||
if (!newEntry.agenda_type) {
|
||||
newEntry.agenda_type = 1;
|
||||
}
|
||||
const mappedEntry: NewEntry<ViewCreateTopic> = {
|
||||
const mappedEntry: NewEntry<CreateTopic> = {
|
||||
newEntry: newEntry,
|
||||
hasDuplicates: hasDuplicates,
|
||||
status: 'new',
|
||||
@ -133,7 +132,7 @@ export class AgendaImportService extends BaseImportService<ViewCreateTopic> {
|
||||
if (entry.status !== 'new') {
|
||||
continue;
|
||||
}
|
||||
await this.repo.create(entry.newEntry.topic);
|
||||
await this.repo.create(entry.newEntry);
|
||||
entry.status = 'done';
|
||||
}
|
||||
this.updatePreview();
|
||||
@ -183,7 +182,7 @@ export class AgendaImportService extends BaseImportService<ViewCreateTopic> {
|
||||
* @param data a string as produced by textArea input
|
||||
*/
|
||||
public parseTextArea(data: string): void {
|
||||
const newEntries: NewEntry<ViewCreateTopic>[] = [];
|
||||
const newEntries: NewEntry<CreateTopic>[] = [];
|
||||
this.clearData();
|
||||
this.clearPreview();
|
||||
const lines = data.split('\n');
|
||||
@ -191,14 +190,14 @@ export class AgendaImportService extends BaseImportService<ViewCreateTopic> {
|
||||
if (!line.length) {
|
||||
return;
|
||||
}
|
||||
const newTopic = new ViewCreateTopic(
|
||||
const newTopic = new CreateTopic(
|
||||
new CreateTopic({
|
||||
title: line,
|
||||
agenda_type: 1 // set type to 'public item' by default
|
||||
})
|
||||
);
|
||||
const hasDuplicates = this.repo.getViewModelList().some(topic => topic.title === newTopic.title);
|
||||
const newEntry: NewEntry<ViewCreateTopic> = {
|
||||
const newEntry: NewEntry<CreateTopic> = {
|
||||
newEntry: newTopic,
|
||||
hasDuplicates: hasDuplicates,
|
||||
status: 'new',
|
@ -12,6 +12,7 @@
|
||||
<mat-tab-group (selectedTabChange)="onTabChange()">
|
||||
<!-- textarea import tab -->
|
||||
<mat-tab label="{{ 'Text import' | translate }}">
|
||||
<br>
|
||||
<div [formGroup]="textAreaForm">
|
||||
<div>
|
||||
<span translate> Copy and paste your participant names in this textbox.</span>
|
||||
@ -36,19 +37,20 @@
|
||||
</mat-tab>
|
||||
<!-- CSV import tab -->
|
||||
<mat-tab label="{{ 'CSV import' | translate }}">
|
||||
<br>
|
||||
<span translate
|
||||
>Required comma or semicolon separated values with these column header names in the first row:</span
|
||||
>: <br />
|
||||
><br />
|
||||
<div class="code red-warning-text">
|
||||
<span translate>Title</span>, <span translate>Given name</span>, <span translate>Surname</span> ,
|
||||
<span translate>Title</span>, <span translate>Given name</span>, <span translate>Surname</span>,
|
||||
<span translate>Structure level</span>, <span translate>Participant number</span>,
|
||||
<span translate>Groups</span> , <span translate>Comment</span>, <span translate>Is active</span>,
|
||||
<span translate>Is present</span> , <span translate>Is committee</span>,
|
||||
<span translate>Initial password</span>, <span translate>Email</span>
|
||||
<span translate>Groups</span>, <span translate>Comment</span>, <span translate>Is active</span>,
|
||||
<span translate>Is present</span>, <span translate>Is committee</span>, <span translate>Initial password</span>,
|
||||
<span translate>Email</span>, <span translate>Username</span>, <span translate>Gender</span>
|
||||
</div>
|
||||
<ul>
|
||||
<li translate>
|
||||
At least given name or surname have to be filled in. All other fields are optional and may be empty.
|
||||
One of given name, surname and username has to be filled in. All other fields are optional and may be empty.
|
||||
</li>
|
||||
<li translate>
|
||||
Additional columns after the required ones may be present and won't affect the import.
|
||||
@ -224,7 +226,7 @@
|
||||
|
||||
<ng-container matColumnDef="number">
|
||||
<mat-header-cell *matHeaderCellDef translate>Participant number</mat-header-cell>
|
||||
<mat-cell *matCellDef="let entry"> {{ entry.newEntry.user.number }} </mat-cell>
|
||||
<mat-cell *matCellDef="let entry"> {{ entry.newEntry.number }} </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- groups column -->
|
||||
@ -277,6 +279,14 @@
|
||||
<mat-header-cell *matHeaderCellDef translate>Email</mat-header-cell>
|
||||
<mat-cell *matCellDef="let entry"> {{ entry.newEntry.email }} </mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="username">
|
||||
<mat-header-cell *matHeaderCellDef translate>Username</mat-header-cell>
|
||||
<mat-cell *matCellDef="let entry"> {{ entry.newEntry.username }} </mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="gender">
|
||||
<mat-header-cell *matHeaderCellDef translate>Gender</mat-header-cell>
|
||||
<mat-cell *matCellDef="let entry"> {{ entry.newEntry.gender }} </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
|
||||
<mat-row [ngClass]="getStateClass(row)" *matRowDef="let row; columns: getColumnDefinition()"> </mat-row>
|
||||
|
@ -7,9 +7,9 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { NewEntry } from 'app/core/ui-services/base-import.service';
|
||||
import { CsvExportService } from 'app/core/ui-services/csv-export.service';
|
||||
import { User } from 'app/shared/models/users/user';
|
||||
import { BaseImportListComponent } from 'app/site/base/base-import-list';
|
||||
import { UserImportService } from '../../services/user-import.service';
|
||||
import { ViewUser } from '../../models/view-user';
|
||||
|
||||
/**
|
||||
* Component for the user import list view.
|
||||
@ -18,7 +18,7 @@ import { ViewUser } from '../../models/view-user';
|
||||
selector: 'os-user-import-list',
|
||||
templateUrl: './user-import-list.component.html'
|
||||
})
|
||||
export class UserImportListComponent extends BaseImportListComponent<ViewUser> {
|
||||
export class UserImportListComponent extends BaseImportListComponent<User> {
|
||||
public textAreaForm: FormGroup;
|
||||
|
||||
/**
|
||||
@ -59,7 +59,9 @@ export class UserImportListComponent extends BaseImportListComponent<ViewUser> {
|
||||
'Is present',
|
||||
'Is a committee',
|
||||
'Initial password',
|
||||
'Email'
|
||||
'Email',
|
||||
'Username',
|
||||
'Gender'
|
||||
];
|
||||
const rows = [
|
||||
[
|
||||
@ -74,7 +76,9 @@ export class UserImportListComponent extends BaseImportListComponent<ViewUser> {
|
||||
1,
|
||||
,
|
||||
'initialPassword',
|
||||
null
|
||||
null,
|
||||
'mmustermann',
|
||||
'm'
|
||||
],
|
||||
[
|
||||
null,
|
||||
@ -88,10 +92,12 @@ export class UserImportListComponent extends BaseImportListComponent<ViewUser> {
|
||||
1,
|
||||
null,
|
||||
null,
|
||||
'john.doe@email.com'
|
||||
'john.doe@email.com',
|
||||
'jdoe',
|
||||
'diverse'
|
||||
],
|
||||
[null, 'Fred', 'Bloggs', 'London', null, null, null, null, null, null, null, null],
|
||||
[null, null, 'Executive Board', null, null, null, null, null, null, 1, null, null]
|
||||
[null, 'Julia', 'Bloggs', 'London', null, null, null, null, null, null, null, null, 'jbloggs', 'f'],
|
||||
[null, null, 'Executive Board', null, null, null, null, null, null, 1, null, null, 'executive', null]
|
||||
];
|
||||
this.exporter.dummyCSVExport(headerRow, rows, `${this.translate.instant('participants-example')}.csv`);
|
||||
}
|
||||
@ -102,7 +108,7 @@ export class UserImportListComponent extends BaseImportListComponent<ViewUser> {
|
||||
* @param row
|
||||
* @returns an error string similar to getVerboseError
|
||||
*/
|
||||
public nameErrors(row: NewEntry<ViewUser>): string {
|
||||
public nameErrors(row: NewEntry<User>): string {
|
||||
for (const name of ['NoName', 'Duplicates', 'DuplicateImport']) {
|
||||
if (this.importer.hasError(row, name)) {
|
||||
return this.importer.verbose(name);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { User } from 'app/shared/models/users/user';
|
||||
import { ViewUser } from './view-user';
|
||||
|
||||
/**
|
||||
* Interface for correlating between strings representing BaseModels and existing
|
||||
@ -17,7 +16,7 @@ export interface CsvMapping {
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
export class ViewCsvCreateUser extends ViewUser {
|
||||
export class ImportCreateUser extends User {
|
||||
/**
|
||||
* Mapping for a new/existing groups.
|
||||
*/
|
||||
@ -28,17 +27,10 @@ export class ViewCsvCreateUser extends ViewUser {
|
||||
/**
|
||||
* Getter if the minimum requrements for a user are met: A name
|
||||
*
|
||||
* @returns false if the user has neither first nor last name
|
||||
* @returns false if the user has neither first nor last name nor username
|
||||
*/
|
||||
public get isValid(): boolean {
|
||||
if (this.user && (this.first_name || this.last_name)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public constructor(user?: User) {
|
||||
super(user);
|
||||
return !!(this.first_name || this.last_name || this.username);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,7 +59,7 @@ export class ViewCsvCreateUser extends ViewUser {
|
||||
open += 1;
|
||||
}
|
||||
});
|
||||
this.user.groups_id = ids;
|
||||
this.groups_id = ids;
|
||||
return open;
|
||||
}
|
||||
}
|
@ -9,18 +9,17 @@ import { UserRepositoryService } from 'app/core/repositories/users/user-reposito
|
||||
import { BaseImportService, NewEntry } from 'app/core/ui-services/base-import.service';
|
||||
import { Group } from 'app/shared/models/users/group';
|
||||
import { User } from 'app/shared/models/users/user';
|
||||
import { CsvMapping, ViewCsvCreateUser } from '../models/view-csv-create-user';
|
||||
import { ViewUser } from '../models/view-user';
|
||||
import { CsvMapping, ImportCreateUser } from '../models/import-create-user';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserImportService extends BaseImportService<ViewUser> {
|
||||
export class UserImportService extends BaseImportService<User> {
|
||||
/**
|
||||
* Helper for mapping the expected header in a typesafe way. Values and order
|
||||
* will be passed to {@link expectedHeader}
|
||||
*/
|
||||
public headerMap: (keyof ViewCsvCreateUser)[] = [
|
||||
public headerMap: (keyof ImportCreateUser)[] = [
|
||||
'title',
|
||||
'first_name',
|
||||
'last_name',
|
||||
@ -32,7 +31,9 @@ export class UserImportService extends BaseImportService<ViewUser> {
|
||||
'is_present',
|
||||
'is_committee',
|
||||
'default_password',
|
||||
'email'
|
||||
'email',
|
||||
'username',
|
||||
'gender'
|
||||
];
|
||||
|
||||
/**
|
||||
@ -91,8 +92,8 @@ export class UserImportService extends BaseImportService<ViewUser> {
|
||||
* @param line
|
||||
* @returns a new entry representing an User
|
||||
*/
|
||||
public mapData(line: string): NewEntry<ViewUser> {
|
||||
const newViewUser = new ViewCsvCreateUser(new User());
|
||||
public mapData(line: string): NewEntry<User> {
|
||||
const newViewUser = new ImportCreateUser();
|
||||
const headerLength = Math.min(this.expectedHeader.length, line.length);
|
||||
let hasErrors = false;
|
||||
for (let idx = 0; idx < headerLength; idx++) {
|
||||
@ -104,7 +105,7 @@ export class UserImportService extends BaseImportService<ViewUser> {
|
||||
case 'is_committee':
|
||||
case 'is_present':
|
||||
try {
|
||||
newViewUser.user[this.expectedHeader[idx]] = this.toBoolean(line[idx]);
|
||||
newViewUser[this.expectedHeader[idx]] = this.toBoolean(line[idx]);
|
||||
} catch (e) {
|
||||
if (e instanceof TypeError) {
|
||||
console.log(e);
|
||||
@ -114,10 +115,10 @@ export class UserImportService extends BaseImportService<ViewUser> {
|
||||
}
|
||||
break;
|
||||
case 'number':
|
||||
newViewUser.user.number = line[idx];
|
||||
newViewUser.number = line[idx];
|
||||
break;
|
||||
default:
|
||||
newViewUser.user[this.expectedHeader[idx]] = line[idx];
|
||||
newViewUser[this.expectedHeader[idx]] = line[idx];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -136,13 +137,13 @@ export class UserImportService extends BaseImportService<ViewUser> {
|
||||
*/
|
||||
public async doImport(): Promise<void> {
|
||||
this.newGroups = await this.createNewGroups();
|
||||
const importUsers: NewEntry<ViewUser>[] = [];
|
||||
const importUsers: NewEntry<User>[] = [];
|
||||
let trackId = 1;
|
||||
for (const entry of this.entries) {
|
||||
if (entry.status !== 'new') {
|
||||
continue;
|
||||
}
|
||||
const openBlocks = (entry.newEntry as ViewCsvCreateUser).solveGroups(this.newGroups);
|
||||
const openBlocks = (entry.newEntry as ImportCreateUser).solveGroups(this.newGroups);
|
||||
if (openBlocks) {
|
||||
this.setError(entry, 'Group');
|
||||
this.updatePreview();
|
||||
@ -245,7 +246,7 @@ export class UserImportService extends BaseImportService<ViewUser> {
|
||||
* @param data a string as produced by textArea input
|
||||
*/
|
||||
public parseTextArea(data: string): void {
|
||||
const newEntries: NewEntry<ViewUser>[] = [];
|
||||
const newEntries: NewEntry<User>[] = [];
|
||||
this.clearData();
|
||||
this.clearPreview();
|
||||
const lines = data.split('\n');
|
||||
@ -254,7 +255,7 @@ export class UserImportService extends BaseImportService<ViewUser> {
|
||||
return;
|
||||
}
|
||||
const nameSchema = line.includes(',') ? 'lastCommaFirst' : 'firstSpaceLast';
|
||||
const newUser = new ViewCsvCreateUser(this.repo.parseUserString(line, nameSchema));
|
||||
const newUser = new ImportCreateUser(this.repo.parseUserString(line, nameSchema));
|
||||
const newEntry = this.userToEntry(newUser);
|
||||
newEntries.push(newEntry);
|
||||
});
|
||||
@ -267,15 +268,17 @@ export class UserImportService extends BaseImportService<ViewUser> {
|
||||
* @param newUser
|
||||
* @returns a NewEntry with duplicate/error information
|
||||
*/
|
||||
private userToEntry(newUser: ViewCsvCreateUser): NewEntry<ViewUser> {
|
||||
const newEntry: NewEntry<ViewUser> = {
|
||||
private userToEntry(newUser: ImportCreateUser): NewEntry<User> {
|
||||
const newEntry: NewEntry<User> = {
|
||||
newEntry: newUser,
|
||||
hasDuplicates: false,
|
||||
status: 'new',
|
||||
errors: []
|
||||
};
|
||||
if (newUser.isValid) {
|
||||
newEntry.hasDuplicates = this.repo.getViewModelList().some(user => user.full_name === newUser.full_name);
|
||||
newEntry.hasDuplicates = this.repo
|
||||
.getViewModelList()
|
||||
.some(user => user.full_name === this.repo.getFullName(newUser));
|
||||
if (newEntry.hasDuplicates) {
|
||||
this.setError(newEntry, 'Duplicates');
|
||||
}
|
||||
|
@ -352,6 +352,7 @@ class UserViewSet(ModelViewSet):
|
||||
serializer.is_valid(raise_exception=True)
|
||||
except ValidationError:
|
||||
# Skip invalid users.
|
||||
|
||||
continue
|
||||
data = serializer.prepare_password(serializer.data)
|
||||
groups = data["groups_id"]
|
||||
@ -364,7 +365,7 @@ class UserViewSet(ModelViewSet):
|
||||
if "importTrackId" in user:
|
||||
imported_track_ids.append(user["importTrackId"])
|
||||
|
||||
# Now infom all clients and send a response
|
||||
# Now inform all clients and send a response
|
||||
inform_changed_data(created_users)
|
||||
return Response(
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user