Merge pull request #4727 from GabrielInTheWorld/csv-import

Adds import of tags to csv import
This commit is contained in:
Emanuel Schütze 2019-05-21 15:17:47 +02:00 committed by GitHub
commit 294d324419
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 150 additions and 5 deletions

View File

@ -11,6 +11,8 @@ export class CreateMotion extends Motion {
public motion_block_id: number;
public tags_id: number[];
public constructor(input?: any) {
super(input);
}

View File

@ -35,6 +35,11 @@ export class ViewCsvCreateMotion extends ViewCreateMotion {
*/
public csvSubmitters: CsvMapping[];
/**
* Mapping for new/existing tags.
*/
public csvTags: CsvMapping[];
public constructor(motion?: CreateMotion) {
super(motion);
}
@ -116,4 +121,35 @@ export class ViewCsvCreateMotion extends ViewCreateMotion {
this.motion.submitters_id = ids;
return open;
}
/**
* Function to iterate over the found tags.
*
* @param tags The mapping of the read tags.
*
* @returns {number} the number of open tags.
*/
public solveTags(tags: CsvMapping[]): number {
let open = 0;
const ids: number[] = [];
for (const tag of this.csvTags) {
if (tag.id) {
ids.push(tag.id);
continue;
}
if (!tags.length) {
++open;
continue;
}
const mapped = tags.find(_tag => _tag.name === tag.name);
if (mapped) {
tag.id = mapped.id;
ids.push(mapped.id);
} else {
++open;
}
}
this.motion.tags_id = ids;
return open;
}
}

View File

@ -228,6 +228,26 @@
</mat-cell>
</ng-container>
<!-- tag column -->
<ng-container matColumnDef="tags">
<mat-header-cell *matHeaderCellDef translate>Tags</mat-header-cell>
<mat-cell *matCellDef="let entry">
<div *ngIf="entry.newEntry.csvTags">
<mat-icon
color="warn"
*ngIf="hasError(entry, 'Tags')"
matTooltip="{{ getVerboseError('Tags') | translate }}"
>
warning
</mat-icon>
<div *ngFor="let tag of entry.newEntry.csvTags">
{{ tag.name }}
<mat-icon class="newBadge" color="accent" inline *ngIf="!tag.id">add</mat-icon>
</div>
</div>
</mat-cell>
</ng-container>
<!-- origin column -->
<ng-container matColumnDef="origin">
<mat-header-cell *matHeaderCellDef translate>Origin</mat-header-cell>

View File

@ -26,7 +26,7 @@ export const noMetaData: string[] = ['identifier', 'title', 'text', 'reason'];
* Subset of {@link motionImportExportHeaderOrder} properties that are
* restricted to export only due to database or workflow limitations
*/
export const motionExportOnly: string[] = ['id', 'recommendation', 'state', 'tags'];
export const motionExportOnly: string[] = ['id', 'recommendation', 'state'];
/**
* reorders the exported properties according to motionImportExportHeaderOrder

View File

@ -145,11 +145,31 @@ export class MotionCsvExportService {
// TODO does not reflect updated export order. any more. Hard coded for now
public exportDummyMotion(): void {
const headerRow = ['Identifier', 'Title', 'Text', 'Reason', 'Submitters', 'Category', 'Origin', 'Motion block'];
const headerRow = [
'Identifier',
'Title',
'Text',
'Reason',
'Submitters',
'Category',
'Tags',
'Origin',
'Motion block'
];
const rows = [
['A1', 'Title 1', 'Text 1', 'Reason 1', 'Submitter A', 'Category A', 'Last Year Conference A', 'Block A'],
['B1', 'Title 2', 'Text 2', 'Reason 2', 'Submitter B', 'Category B', null, 'Block A'],
[null, 'Title 3', 'Text 3', null, null, null, null, null]
[
'A1',
'Title 1',
'Text 1',
'Reason 1',
'Submitter A',
'Category A',
'Tag 1, Tag 2',
'Last Year Conference A',
'Block A'
],
['B1', 'Title 2', 'Text 2', 'Reason 2', 'Submitter B', 'Category B', null, null, 'Block A'],
[null, 'Title 3', 'Text 3', null, null, null, null, null, null]
];
this.csvExport.dummyCSVExport(headerRow, rows, `${this.translate.instant('motions-example')}.csv`);
}

View File

@ -15,6 +15,8 @@ import { MotionRepositoryService } from 'app/core/repositories/motions/motion-re
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
import { ViewCsvCreateMotion, CsvMapping } from '../models/view-csv-create-motion';
import { ViewMotion } from '../models/view-motion';
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
import { Tag } from 'app/shared/models/core/tag';
/**
* Service for motion imports
@ -30,6 +32,7 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
MotionBlock: 'Could not resolve the motion block',
Category: 'Could not resolve the category',
Submitters: 'Could not resolve the submitters',
Tags: 'Could not resolve the tags',
Title: 'A title is required',
Text: "A content in the 'text' column is required",
Duplicates: 'A motion with this identifier already exists.'
@ -55,6 +58,11 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
*/
public newMotionBlocks: CsvMapping[] = [];
/**
* Mapping of the new tags for the imported motion.
*/
public newTags: CsvMapping[] = [];
/**
* Constructor. Defines the headers expected and calls the abstract class
* @param repo: The repository for motions.
@ -70,6 +78,7 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
private categoryRepo: CategoryRepositoryService,
private motionBlockRepo: MotionBlockRepositoryService,
private userRepo: UserRepositoryService,
private tagRepo: TagRepositoryService,
translate: TranslateService,
papa: Papa,
matSnackbar: MatSnackBar
@ -85,6 +94,7 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
this.newSubmitters = [];
this.newCategories = [];
this.newMotionBlocks = [];
this.newTags = [];
}
/**
@ -108,6 +118,9 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
case 'motion_block':
newEntry.csvMotionblock = this.getMotionBlock(line[idx]);
break;
case 'tags':
newEntry.csvTags = this.getTags(line[idx]);
break;
default:
newEntry.motion[this.expectedHeader[idx]] = line[idx];
}
@ -138,6 +151,7 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
this.newMotionBlocks = await this.createNewMotionBlocks();
this.newCategories = await this.createNewCategories();
this.newSubmitters = await this.createNewUsers();
this.newTags = await this.createNewTags();
for (const entry of this.entries) {
if (entry.status !== 'new') {
@ -161,6 +175,12 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
this.updatePreview();
continue;
}
const openTags = (entry.newEntry as ViewCsvCreateMotion).solveTags(this.newTags);
if (openTags) {
this.setError(entry, 'Tags');
this.updatePreview();
continue;
}
await this.repo.create((entry.newEntry as ViewCsvCreateMotion).motion);
entry.status = 'done';
}
@ -276,6 +296,36 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
}
}
/**
* Iterates over the given string separated by ','
* Creates for every found string a tag.
*
* @param tagList The list of tags as string.
*
* @returns {CsvMapping[]} The list of tags as csv-mapping.
*/
public getTags(tagList: string): CsvMapping[] {
const result: CsvMapping[] = [];
if (!tagList) {
return result;
}
const tagArray = tagList.split(',');
for (let tag of tagArray) {
tag = tag.trim();
const existingTag = this.tagRepo.getViewModelList().find(tagInRepo => tagInRepo.name === tag);
if (existingTag) {
result.push({ id: existingTag.id, name: existingTag.name });
} else {
if (!this.newTags.find(entry => entry.name === tag)) {
this.newTags.push({ name: tag });
}
result.push({ name: tag });
}
}
return result;
}
/**
* Creates all new Users needed for the import.
*
@ -331,6 +381,23 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
return await Promise.all(promises);
}
/**
* Combines all tags which are new created to one promise.
*
* @returns {Promise} One promise containing all promises to create a new tag.
*/
private async createNewTags(): Promise<CsvMapping[]> {
const promises: Promise<CsvMapping>[] = [];
for (const tag of this.newTags) {
promises.push(
this.tagRepo
.create(new Tag({ name: tag.name }))
.then(identifiable => ({ name: tag.name, id: identifiable.id }))
);
}
return await Promise.all(promises);
}
/**
* Helper to separate a category string from its' prefix. Assumes that a prefix is no longer
* than 5 chars and separated by a ' - '