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 motion_block_id: number;
|
||||||
|
|
||||||
|
public tags_id: number[];
|
||||||
|
|
||||||
public constructor(input?: any) {
|
public constructor(input?: any) {
|
||||||
super(input);
|
super(input);
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,11 @@ export class ViewCsvCreateMotion extends ViewCreateMotion {
|
|||||||
*/
|
*/
|
||||||
public csvSubmitters: CsvMapping[];
|
public csvSubmitters: CsvMapping[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping for new/existing tags.
|
||||||
|
*/
|
||||||
|
public csvTags: CsvMapping[];
|
||||||
|
|
||||||
public constructor(motion?: CreateMotion) {
|
public constructor(motion?: CreateMotion) {
|
||||||
super(motion);
|
super(motion);
|
||||||
}
|
}
|
||||||
@ -116,4 +121,35 @@ export class ViewCsvCreateMotion extends ViewCreateMotion {
|
|||||||
this.motion.submitters_id = ids;
|
this.motion.submitters_id = ids;
|
||||||
return open;
|
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>
|
</mat-cell>
|
||||||
</ng-container>
|
</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 -->
|
<!-- origin column -->
|
||||||
<ng-container matColumnDef="origin">
|
<ng-container matColumnDef="origin">
|
||||||
<mat-header-cell *matHeaderCellDef translate>Origin</mat-header-cell>
|
<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
|
* Subset of {@link motionImportExportHeaderOrder} properties that are
|
||||||
* restricted to export only due to database or workflow limitations
|
* 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
|
* 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
|
// TODO does not reflect updated export order. any more. Hard coded for now
|
||||||
public exportDummyMotion(): void {
|
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 = [
|
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'],
|
'A1',
|
||||||
[null, 'Title 3', 'Text 3', null, null, null, null, null]
|
'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`);
|
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 { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
|
||||||
import { ViewCsvCreateMotion, CsvMapping } from '../models/view-csv-create-motion';
|
import { ViewCsvCreateMotion, CsvMapping } from '../models/view-csv-create-motion';
|
||||||
import { ViewMotion } from '../models/view-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
|
* Service for motion imports
|
||||||
@ -30,6 +32,7 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
|
|||||||
MotionBlock: 'Could not resolve the motion block',
|
MotionBlock: 'Could not resolve the motion block',
|
||||||
Category: 'Could not resolve the category',
|
Category: 'Could not resolve the category',
|
||||||
Submitters: 'Could not resolve the submitters',
|
Submitters: 'Could not resolve the submitters',
|
||||||
|
Tags: 'Could not resolve the tags',
|
||||||
Title: 'A title is required',
|
Title: 'A title is required',
|
||||||
Text: "A content in the 'text' column is required",
|
Text: "A content in the 'text' column is required",
|
||||||
Duplicates: 'A motion with this identifier already exists.'
|
Duplicates: 'A motion with this identifier already exists.'
|
||||||
@ -55,6 +58,11 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
|
|||||||
*/
|
*/
|
||||||
public newMotionBlocks: CsvMapping[] = [];
|
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
|
* Constructor. Defines the headers expected and calls the abstract class
|
||||||
* @param repo: The repository for motions.
|
* @param repo: The repository for motions.
|
||||||
@ -70,6 +78,7 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
|
|||||||
private categoryRepo: CategoryRepositoryService,
|
private categoryRepo: CategoryRepositoryService,
|
||||||
private motionBlockRepo: MotionBlockRepositoryService,
|
private motionBlockRepo: MotionBlockRepositoryService,
|
||||||
private userRepo: UserRepositoryService,
|
private userRepo: UserRepositoryService,
|
||||||
|
private tagRepo: TagRepositoryService,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
papa: Papa,
|
papa: Papa,
|
||||||
matSnackbar: MatSnackBar
|
matSnackbar: MatSnackBar
|
||||||
@ -85,6 +94,7 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
|
|||||||
this.newSubmitters = [];
|
this.newSubmitters = [];
|
||||||
this.newCategories = [];
|
this.newCategories = [];
|
||||||
this.newMotionBlocks = [];
|
this.newMotionBlocks = [];
|
||||||
|
this.newTags = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,6 +118,9 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
|
|||||||
case 'motion_block':
|
case 'motion_block':
|
||||||
newEntry.csvMotionblock = this.getMotionBlock(line[idx]);
|
newEntry.csvMotionblock = this.getMotionBlock(line[idx]);
|
||||||
break;
|
break;
|
||||||
|
case 'tags':
|
||||||
|
newEntry.csvTags = this.getTags(line[idx]);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
newEntry.motion[this.expectedHeader[idx]] = line[idx];
|
newEntry.motion[this.expectedHeader[idx]] = line[idx];
|
||||||
}
|
}
|
||||||
@ -138,6 +151,7 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
|
|||||||
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.newTags = await this.createNewTags();
|
||||||
|
|
||||||
for (const entry of this.entries) {
|
for (const entry of this.entries) {
|
||||||
if (entry.status !== 'new') {
|
if (entry.status !== 'new') {
|
||||||
@ -161,6 +175,12 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
|
|||||||
this.updatePreview();
|
this.updatePreview();
|
||||||
continue;
|
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);
|
await this.repo.create((entry.newEntry as ViewCsvCreateMotion).motion);
|
||||||
entry.status = 'done';
|
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.
|
* Creates all new Users needed for the import.
|
||||||
*
|
*
|
||||||
@ -331,6 +381,23 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
|
|||||||
return await Promise.all(promises);
|
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
|
* Helper to separate a category string from its' prefix. Assumes that a prefix is no longer
|
||||||
* than 5 chars and separated by a ' - '
|
* than 5 chars and separated by a ' - '
|
||||||
|
Loading…
Reference in New Issue
Block a user