Merge pull request #4727 from GabrielInTheWorld/csv-import
Adds import of tags to csv import
This commit is contained in:
commit
294d324419
@ -11,6 +11,8 @@ export class CreateMotion extends Motion {
|
||||
|
||||
public motion_block_id: number;
|
||||
|
||||
public tags_id: number[];
|
||||
|
||||
public constructor(input?: any) {
|
||||
super(input);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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`);
|
||||
}
|
||||
|
@ -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 ' - '
|
||||
|
Loading…
Reference in New Issue
Block a user