Add CSV import for supporters

Supporters can now be imported via csv
this was forgotten before
This commit is contained in:
Sean 2021-07-27 15:18:45 +02:00
parent 759e23e15b
commit 802ac1aee8
5 changed files with 118 additions and 32 deletions

View File

@ -32,6 +32,11 @@ export class ImportCreateMotion extends CreateMotion {
*/ */
public csvSubmitters: CsvMapping[]; public csvSubmitters: CsvMapping[];
/**
* Mapping for new/existing supporters.
*/
public csvSupporters: CsvMapping[];
/** /**
* Mapping for new/existing tags. * Mapping for new/existing tags.
*/ */
@ -115,6 +120,30 @@ export class ImportCreateMotion extends CreateMotion {
return open; return open;
} }
public solveSupporters(supporters: CsvMapping[]): number {
let open = 0;
const ids: number[] = [];
this.csvSupporters.forEach(csvSupporter => {
if (csvSupporter.id) {
ids.push(csvSupporter.id);
return;
}
if (!supporters.length) {
open += 1;
return;
}
const mapped = supporters.find(newSupporter => newSupporter.name === csvSupporter.name);
if (mapped) {
csvSupporter.id = mapped.id;
ids.push(mapped.id);
} else {
open += 1;
}
});
this.supporters_id = ids;
return open;
}
/** /**
* Function to iterate over the found tags. * Function to iterate over the found tags.
* *

View File

@ -18,8 +18,7 @@
<br /> <br />
<div class="code red-warning-text"> <div class="code red-warning-text">
<span *ngFor="let header of this.expectedHeader; let last = last"> <span *ngFor="let header of this.expectedHeader; let last = last">
<span>{{ header | translate }}</span {{ header | translate }}<span *ngIf="!last">,&nbsp;</span>
><span *ngIf="!last">,&nbsp;</span>
</span> </span>
</div> </div>
<ul> <ul>
@ -41,11 +40,7 @@
<div class="wrapper"> <div class="wrapper">
<mat-form-field> <mat-form-field>
<mat-label>{{ 'Encoding of the file' | translate }}</mat-label> <mat-label>{{ 'Encoding of the file' | translate }}</mat-label>
<mat-select <mat-select class="selection" (selectionChange)="selectEncoding($event)" [value]="encodings[0].value">
class="selection"
(selectionChange)="selectEncoding($event)"
[value]="encodings[0].value"
>
<mat-option *ngFor="let option of encodings" [value]="option.value"> <mat-option *ngFor="let option of encodings" [value]="option.value">
{{ option.label | translate }} {{ option.label | translate }}
</mat-option> </mat-option>
@ -220,6 +215,27 @@
</mat-cell> </mat-cell>
</ng-container> </ng-container>
<!-- supporters column -->
<ng-container matColumnDef="supporters">
<mat-header-cell *matHeaderCellDef>{{ 'Supporters' | translate }}</mat-header-cell>
<mat-cell *matCellDef="let entry">
<div *ngIf="entry.newEntry?.csvSupporters?.length">
<mat-icon
color="warn"
*ngIf="hasError(entry, 'Supporters')"
matTooltip="{{ getVerboseError('Supporters') | translate }}"
>
warning
</mat-icon>
<div *ngFor="let supporters of entry.newEntry.csvSupporters">
{{ supporters.name }}
<mat-icon class="newBadge" color="accent" inline *ngIf="!supporters.id">add</mat-icon>
&nbsp;
</div>
</div>
</mat-cell>
</ng-container>
<!-- category column --> <!-- category column -->
<ng-container matColumnDef="category"> <ng-container matColumnDef="category">
<mat-header-cell *matHeaderCellDef>{{ 'Category' | translate }}</mat-header-cell> <mat-header-cell *matHeaderCellDef>{{ 'Category' | translate }}</mat-header-cell>

View File

@ -175,6 +175,7 @@ export class MotionCsvExportService {
const headerRow = [ const headerRow = [
'Identifier', 'Identifier',
'Submitters', 'Submitters',
'Supporters',
'Title', 'Title',
'Text', 'Text',
'Reason', 'Reason',
@ -187,6 +188,7 @@ export class MotionCsvExportService {
[ [
'A1', 'A1',
'Submitter A', 'Submitter A',
'Supporter A',
'Title 1', 'Title 1',
'Text 1', 'Text 1',
'Reason 1', 'Reason 1',
@ -195,8 +197,19 @@ export class MotionCsvExportService {
'Block A', 'Block A',
'Last Year Conference A' 'Last Year Conference A'
], ],
['B1', 'Submitter B', 'Title 2', 'Text 2', 'Reason 2', 'Category B', null, 'Block A', 'Origin B'], [
['C2', null, 'Title 3', 'Text 3', null, null, null, null, null] 'B1',
'Submitter B',
'Supporter B',
'Title 2',
'Text 2',
'Reason 2',
'Category B',
null,
'Block A',
'Origin B'
],
['C2', null, null, 'Title 3', 'Text 3', null, null, null, null, null]
]; ];
this.csvExport.dummyCSVExport(headerRow, rows, `${this.translate.instant('motions-example')}.csv`); this.csvExport.dummyCSVExport(headerRow, rows, `${this.translate.instant('motions-example')}.csv`);
} }

View File

@ -48,6 +48,11 @@ export class MotionImportService extends BaseImportService<Motion> {
*/ */
public newSubmitters: CsvMapping[] = []; public newSubmitters: CsvMapping[] = [];
/**
* supporters that need to be created prior to importing
*/
public newSupporters: CsvMapping[] = [];
/** /**
* Categories that need to be created prior to importing * Categories that need to be created prior to importing
*/ */
@ -92,6 +97,7 @@ export class MotionImportService extends BaseImportService<Motion> {
*/ */
public clearData(): void { public clearData(): void {
this.newSubmitters = []; this.newSubmitters = [];
this.newSupporters = [];
this.newCategories = []; this.newCategories = [];
this.newMotionBlocks = []; this.newMotionBlocks = [];
this.newTags = []; this.newTags = [];
@ -110,7 +116,10 @@ export class MotionImportService extends BaseImportService<Motion> {
for (let idx = 0; idx < headerLength; idx++) { for (let idx = 0; idx < headerLength; idx++) {
switch (this.expectedHeader[idx]) { switch (this.expectedHeader[idx]) {
case 'submitters': case 'submitters':
newEntry.csvSubmitters = this.getSubmitters(line[idx]); newEntry.csvSubmitters = this.getUsers(line[idx], 'submitter');
break;
case 'supporters':
newEntry.csvSupporters = this.getUsers(line[idx], 'supporter');
break; break;
case 'category': case 'category':
newEntry.csvCategory = this.getCategory(line[idx]); newEntry.csvCategory = this.getCategory(line[idx]);
@ -150,7 +159,8 @@ export class MotionImportService extends BaseImportService<Motion> {
public async doImport(): Promise<void> { public async doImport(): Promise<void> {
this.newMotionBlocks = await this.createNewMotionBlocks(); this.newMotionBlocks = await this.createNewMotionBlocks();
this.newCategories = await this.createNewCategories(); this.newCategories = await this.createNewCategories();
this.newSubmitters = await this.createNewUsers(); this.newSubmitters = await this.createNewUsers(this.newSubmitters);
this.newSupporters = await this.createNewUsers(this.newSupporters);
this.newTags = await this.createNewTags(); this.newTags = await this.createNewTags();
for (const entry of this.entries) { for (const entry of this.entries) {
@ -169,12 +179,18 @@ export class MotionImportService extends BaseImportService<Motion> {
this.updatePreview(); this.updatePreview();
continue; continue;
} }
const openUsers = (entry.newEntry as ImportCreateMotion).solveSubmitters(this.newSubmitters); const openSubmitters = (entry.newEntry as ImportCreateMotion).solveSubmitters(this.newSubmitters);
if (openUsers) { if (openSubmitters) {
this.setError(entry, 'Submitters'); this.setError(entry, 'Submitters');
this.updatePreview(); this.updatePreview();
continue; continue;
} }
const openSupporters = (entry.newEntry as ImportCreateMotion).solveSupporters(this.newSupporters);
if (openSupporters) {
this.setError(entry, 'Supporters');
this.updatePreview();
continue;
}
const openTags = (entry.newEntry as ImportCreateMotion).solveTags(this.newTags); const openTags = (entry.newEntry as ImportCreateMotion).solveTags(this.newTags);
if (openTags) { if (openTags) {
this.setError(entry, 'Tags'); this.setError(entry, 'Tags');
@ -191,39 +207,51 @@ export class MotionImportService extends BaseImportService<Motion> {
* Checks the provided submitter(s) and returns an object with mapping of * Checks the provided submitter(s) and returns an object with mapping of
* existing users and of users that need to be created * existing users and of users that need to be created
* *
* @param submitterlist * @param userList
* @returns a list of submitters mapped with (if already existing) their id * @returns a list of submitters mapped with (if already existing) their id
*/ */
public getSubmitters(submitterlist: string): CsvMapping[] { public getUsers(userList: string, kind: 'submitter' | 'supporter'): CsvMapping[] {
console.log('kind: ', kind);
const result: CsvMapping[] = []; const result: CsvMapping[] = [];
if (!submitterlist) { if (!userList) {
return result; return result;
} }
const submitterArray = submitterlist.split(','); // TODO fails with 'full name' const userArray = userList.split(','); // TODO fails with 'full name'
for (const submitter of submitterArray) {
const existingSubmitters = this.userRepo.getUsersByName(submitter.trim()); console.log('userArray: ', userList);
if (!existingSubmitters.length) { for (const user of userArray) {
if (!this.newSubmitters.find(listedSubmitter => listedSubmitter.name === submitter)) { const existingUsers = this.userRepo.getUsersByName(user.trim());
this.newSubmitters.push({ name: submitter }); if (!existingUsers.length) {
if (kind === 'submitter') {
if (!this.newSubmitters.find(listedSubmitter => listedSubmitter.name === user)) {
this.newSubmitters.push({ name: user });
}
result.push({ name: user });
} else if (kind === 'supporter') {
if (!this.newSupporters.find(listedSupporter => listedSupporter.name === user)) {
this.newSupporters.push({ name: user });
}
result.push({ name: user });
} }
result.push({ name: submitter });
} }
if (existingSubmitters.length === 1) { if (existingUsers.length === 1) {
result.push({ result.push({
name: existingSubmitters[0].short_name, name: existingUsers[0].short_name,
id: existingSubmitters[0].id id: existingUsers[0].id
}); });
} }
if (existingSubmitters.length > 1) { if (existingUsers.length > 1) {
result.push({ result.push({
name: submitter, name: user,
multiId: existingSubmitters.map(ex => ex.id) multiId: existingUsers.map(ex => ex.id)
}); });
this.matSnackbar.open('TODO: multiple possible users found for this string', 'ok'); this.matSnackbar.open('TODO: multiple possible users found for this string', 'ok');
// TODO How to handle several submitters ? Is this possible? // TODO How to handle several submitters ? Is this possible?
// should have some kind of choice dialog there // should have some kind of choice dialog there
} }
} }
console.log('res: ', result);
return result; return result;
} }
@ -331,9 +359,9 @@ export class MotionImportService extends BaseImportService<Motion> {
* *
* @returns a promise with list of new Submitters, updated with newly created ids * @returns a promise with list of new Submitters, updated with newly created ids
*/ */
private async createNewUsers(): Promise<CsvMapping[]> { private async createNewUsers(list: CsvMapping[]): Promise<CsvMapping[]> {
const promises: Promise<CsvMapping>[] = []; const promises: Promise<CsvMapping>[] = [];
for (const user of this.newSubmitters) { for (const user of list) {
promises.push(this.userRepo.createFromString(user.name)); promises.push(this.userRepo.createFromString(user.name));
} }
return await Promise.all(promises); return await Promise.all(promises);

View File

@ -700,7 +700,7 @@ button.mat-menu-item.selected {
.first-column { .first-column {
flex: 1; flex: 1;
min-width: 0px; min-width: 40px;
} }
.filter-imports { .filter-imports {