From 7a7e39a2f58c3d367bf8d2e8bbd4e993f4b8ecc5 Mon Sep 17 00:00:00 2001 From: Loris Sauter Date: Tue, 10 Oct 2023 21:13:40 +0200 Subject: [PATCH] #337: Added support for importing from templates on the server --- .../rest/types/template/tasks/ApiTaskType.kt | 8 +- .../data/model/template/task/DbTaskType.kt | 10 +- .../kotlin/dev/dres/mgmt/TemplateManager.kt | 38 +---- .../template-import-tree.component.ts | 155 +++++++++--------- .../template-builder.component.html | 2 +- .../template-builder.component.ts | 12 +- .../template-builder.service.ts | 28 +++- 7 files changed, 116 insertions(+), 137 deletions(-) diff --git a/backend/src/main/kotlin/dev/dres/api/rest/types/template/tasks/ApiTaskType.kt b/backend/src/main/kotlin/dev/dres/api/rest/types/template/tasks/ApiTaskType.kt index b98874b4e..f7f11f417 100644 --- a/backend/src/main/kotlin/dev/dres/api/rest/types/template/tasks/ApiTaskType.kt +++ b/backend/src/main/kotlin/dev/dres/api/rest/types/template/tasks/ApiTaskType.kt @@ -11,7 +11,6 @@ import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardOpenOption -typealias TaskTypeId = String /** * The RESTful API equivalent of a [DbTaskType]. @@ -20,7 +19,6 @@ typealias TaskTypeId = String * @version 1.1.0 */ data class ApiTaskType( - val id: TaskTypeId? = null, val name: String, val duration: Long, val targetOption: ApiTargetOption, @@ -31,7 +29,7 @@ data class ApiTaskType( val configuration: Map ) { - constructor() : this("---NO_ID---","---Default TaskType DO NOT USE!---", + constructor() : this("---Default TaskType DO NOT USE!---", 1, ApiTargetOption.TEXT, listOf(ApiHintOption.TEXT), listOf(ApiSubmissionOption.TEXTUAL_SUBMISSION), @@ -50,8 +48,4 @@ data class ApiTaskType( } } - val taskTypeId: TaskTypeId - @JsonIgnore - @OpenApiIgnore - get() = this.id ?: "N/A" } diff --git a/backend/src/main/kotlin/dev/dres/data/model/template/task/DbTaskType.kt b/backend/src/main/kotlin/dev/dres/data/model/template/task/DbTaskType.kt index e0b973994..0090c4046 100644 --- a/backend/src/main/kotlin/dev/dres/data/model/template/task/DbTaskType.kt +++ b/backend/src/main/kotlin/dev/dres/data/model/template/task/DbTaskType.kt @@ -1,8 +1,6 @@ package dev.dres.data.model.template.task import dev.dres.api.rest.types.template.tasks.ApiTaskType -import dev.dres.api.rest.types.template.tasks.TaskTypeId -import dev.dres.data.model.PersistentEntity import dev.dres.data.model.template.DbEvaluationTemplate import dev.dres.data.model.template.task.options.* import dev.dres.data.model.template.task.options.DbConfiguredOption @@ -17,7 +15,7 @@ import kotlinx.dnq.simple.min * @author Luca Rossetto & Ralph Gasser * @version 2.0.0 */ -class DbTaskType(entity: Entity) : PersistentEntity(entity) { +class DbTaskType(entity: Entity) : XdEntity(entity) { /** Combination of [DbTaskType] name / competition must be unique. */ companion object: XdNaturalEntityType() { override val compositeIndices = listOf( @@ -25,11 +23,6 @@ class DbTaskType(entity: Entity) : PersistentEntity(entity) { ) } - /** The [TaskTypeId] of this [DbTaskType]. */ - var taskTypeId: TaskTypeId - get() = this.id - set(value) { this.id = value } - /** The name of this [DbTaskType]. */ var name by xdRequiredStringProp(unique = false, trimmed = false) @@ -63,7 +56,6 @@ class DbTaskType(entity: Entity) : PersistentEntity(entity) { * @return [ApiTaskType] */ fun toApi(): ApiTaskType = ApiTaskType( - id=this.xdId, name = this.name, duration = this.duration, targetOption = this.target.toApi(), diff --git a/backend/src/main/kotlin/dev/dres/mgmt/TemplateManager.kt b/backend/src/main/kotlin/dev/dres/mgmt/TemplateManager.kt index 8b8f5193a..c63b4e3e9 100644 --- a/backend/src/main/kotlin/dev/dres/mgmt/TemplateManager.kt +++ b/backend/src/main/kotlin/dev/dres/mgmt/TemplateManager.kt @@ -11,9 +11,6 @@ import dev.dres.data.model.template.DbEvaluationTemplate import dev.dres.data.model.template.TemplateId import dev.dres.data.model.template.task.* import dev.dres.data.model.template.task.options.DbConfiguredOption -import dev.dres.data.model.template.task.options.DbHintOption -import dev.dres.data.model.template.task.options.DbSubmissionOption -import dev.dres.data.model.template.task.options.DbTaskOption import dev.dres.data.model.template.team.DbTeam import dev.dres.data.model.template.team.DbTeamGroup import dev.dres.data.model.template.team.TeamId @@ -95,40 +92,9 @@ object TemplateManager { dbEvaluationTemplate.modified = DateTime.now() /* Update task type information. */ - - /* Update task type information: Remove deleted types. */ - val typesIds = apiEvaluationTemplate.taskTypes.mapNotNull { it.id }.toTypedArray() - val typesToDeleteQuery = DbTaskType.query( - DbTaskType::evaluation eq dbEvaluationTemplate and not( - DbTaskType::id.containsIn(*typesIds) - ) - ) - val hintOptsToDelIds = typesToDeleteQuery.toList().map { - it.hints.toList().map { hint -> hint.entityId } - }.flatten().toTypedArray() - val submissionOptsToDelIds = typesToDeleteQuery.toList().map{ - it.submission.toList().map{target -> target.entityId} - }.flatten().toTypedArray() - val taskOptsToDelIds = typesToDeleteQuery.toList().map{ - it.options.toList().map{target -> target.entityId} - }.flatten().toTypedArray() - val configOptsToDelIds = typesToDeleteQuery.toList().map{ - it.configurations.toList().map{target -> target.entityId} - }.flatten().toTypedArray() - - /* - DbTaskTemplate has children relationships with both, DbHint and DbTaskTarget. - Despite being written in the documentation, for some reason the .removeAll above does not - delete the children, hence we have to take care of it ourselves. - https://jetbrains.github.io/xodus-dnq/properties.html - */ - DbHintOption.all().toList().filter{hintOptsToDelIds.contains(it.entityId)}.forEach { it.delete() } - DbSubmissionOption.all().toList().filter{submissionOptsToDelIds.contains(it.entityId)}.forEach{it.delete()} - DbTaskOption.all().toList().filter{taskOptsToDelIds.contains(it.entityId)}.forEach{it.delete()} - DbConfiguredOption.all().toList().filter{configOptsToDelIds.contains(it.entityId)}.forEach{it.delete()} - + val taskTypes = apiEvaluationTemplate.taskTypes.map { it.name }.toTypedArray() dbEvaluationTemplate.taskTypes.removeAll( - typesToDeleteQuery + DbTaskType.query(DbTaskType::evaluation eq dbEvaluationTemplate and not(DbTaskType::name.containsIn(*taskTypes))) ) for (apiTaskType in apiEvaluationTemplate.taskTypes) { val taskType = diff --git a/frontend/src/app/template/template-builder/components/template-import-tree/template-import-tree.component.ts b/frontend/src/app/template/template-builder/components/template-import-tree/template-import-tree.component.ts index 43de238b0..88a4187f3 100644 --- a/frontend/src/app/template/template-builder/components/template-import-tree/template-import-tree.component.ts +++ b/frontend/src/app/template/template-builder/components/template-import-tree/template-import-tree.component.ts @@ -23,7 +23,7 @@ export enum TemplateImportTreeBranch { TEAMS = 1 << 3, // 001000 TEAM_GROUPS = 1 << 4, // 010000 JUDGES = 1 << 5, // 100000 - ALL = ~(~0<<6) // 111111 + ALL = ~(~0 << 6) // 111111 } /** @@ -34,7 +34,7 @@ export class TemplateTreeFlatNode { expandable: boolean; item: T; label: string; - branch: TemplateImportTreeBranch + branch: TemplateImportTreeBranch; } export class TemplateTreeNode { @@ -50,7 +50,7 @@ export class TemplateTreeNode { templateUrl: "./template-import-tree.component.html", styleUrls: ["./template-import-tree.component.scss"] }) -export class TemplateImportTreeComponent implements OnInit{ +export class TemplateImportTreeComponent implements OnInit { flatNodeMap = new Map, TemplateTreeNode>(); nestedNodeMap = new Map, TemplateTreeFlatNode>(); @@ -59,9 +59,9 @@ export class TemplateImportTreeComponent implements OnInit{ selectedParent: TemplateTreeFlatNode | null = null; - treeControl: FlatTreeControl> - treeFlattener: MatTreeFlattener, TemplateTreeFlatNode> - dataSource: MatTreeFlatDataSource, TemplateTreeFlatNode> + treeControl: FlatTreeControl>; + treeFlattener: MatTreeFlattener, TemplateTreeFlatNode>; + dataSource: MatTreeFlatDataSource, TemplateTreeFlatNode>; selection = new SelectionModel>(true); @@ -73,21 +73,21 @@ export class TemplateImportTreeComponent implements OnInit{ constructor() { this.treeFlattener = new MatTreeFlattener, TemplateTreeFlatNode>( this.transformer, this.getLevel, this.isExpandable, this.getChildren - ) + ); this.treeControl = new FlatTreeControl>(this.getLevel, this.isExpandable); this.dataSource = new MatTreeFlatDataSource, TemplateTreeFlatNode>(this.treeControl, this.treeFlattener); } ngOnInit(): void { - this.dataSource.data = TemplateImportTreeComponent.buildTrees(this.templates, this.branches); - this.templates.forEach(it => this.templatesMap.set(it.id, it)); - console.log(this.templatesMap) - } + this.dataSource.data = TemplateImportTreeComponent.buildTrees(this.templates, this.branches); + this.templates.forEach(it => this.templatesMap.set(it.id, it)); + console.log(this.templatesMap); + } getLevel = (node: TemplateTreeFlatNode) => node.level; isExpandable = (node: TemplateTreeFlatNode) => node.expandable; getChildren = (node: TemplateTreeNode) => node.children; - hasChild = (_: number, node: TemplateTreeFlatNode) => node.expandable + hasChild = (_: number, node: TemplateTreeFlatNode) => node.expandable; transformer = (node: TemplateTreeNode, level: number) => { const existingNode = this.nestedNodeMap.get(node); @@ -100,7 +100,7 @@ export class TemplateImportTreeComponent implements OnInit{ this.flatNodeMap.set(flatNode, node); this.nestedNodeMap.set(node, flatNode); return flatNode; - } + }; /** Whether all the descendants of the node are selected. */ descendantsAllSelected(node: TemplateTreeFlatNode): boolean { @@ -182,7 +182,7 @@ export class TemplateImportTreeComponent implements OnInit{ return null; } - public getImportTemplate(){ + public getImportTemplate(): ApiEvaluationTemplate | null { const types: ApiTaskType[] = []; const taskGroups: ApiTaskGroup[] = []; const tasks: ApiTaskTemplate[] = []; @@ -192,34 +192,42 @@ export class TemplateImportTreeComponent implements OnInit{ /** Sanitation */ /* Tasks require task groups which in turn require types*/ this.getAllSelectedTaskTemplates().forEach(it => { - it[0].id = undefined + it[0].id = undefined; tasks.push(it[0]); - const group = this.templatesMap.get(it[1]).taskGroups.find((g:ApiTaskGroup) => g.name === it[0].taskGroup) - if(!taskGroups.includes(group)){ + const group = this.templatesMap.get(it[1]).taskGroups.find((g: ApiTaskGroup) => g.name === it[0].taskGroup); + if (!taskGroups.includes(group)) { taskGroups.push(group); } - const type = this.templatesMap.get(it[1]).taskTypes.find((t:ApiTaskType) => t.name === group.type) - if(!types.includes(type)){ - types.push(type) + const type = this.templatesMap.get(it[1]).taskTypes.find((t: ApiTaskType) => t.name === group.type); + if (!types.includes(type)) { + types.push(type); } - }) + }); /* TaskGroup requires TaskType */ this.getAllSelectedTaskGroups().forEach(it => { - if(!taskGroups.includes(it[0])){ - taskGroups.push(it[0]) - const type = this.templatesMap.get(it[1]).taskTypes.find((t: ApiTaskType) => t.name === it[0].type) - if(!types.includes(type)){ - types.push(type) + if (!taskGroups.includes(it[0])) { + taskGroups.push(it[0]); + const type = this.templatesMap.get(it[1]).taskTypes.find((t: ApiTaskType) => t.name === it[0].type); + if (!types.includes(type)) { + types.push(type); } } - }) + }); this.getAllSelectedTaskTypes().forEach(it => { - if(!types.includes(it[0])){ - types.push(it[0]) + if (!types.includes(it[0])) { + types.push(it[0]); } - }) + }); + + /* Catch no selection */ + + if ( + this.selection.isEmpty() + ) { + return null; + } return { @@ -231,32 +239,32 @@ export class TemplateImportTreeComponent implements OnInit{ teams: teams, teamGroups: teamGroups, judges: judges, - id:"---IMPORT_TEMPLATE_NO_ID---" - } as ApiEvaluationTemplate + id: "---IMPORT_TEMPLATE_NO_ID---" + } as ApiEvaluationTemplate; } - public getAllSelectedTaskTypes(){ - return this.getSelectedItemsForBranch(TemplateImportTreeBranch.TASK_TYPES) as [ApiTaskType, string][] + public getAllSelectedTaskTypes() { + return this.getSelectedItemsForBranch(TemplateImportTreeBranch.TASK_TYPES) as [ApiTaskType, string][]; } - public getAllSelectedTaskGroups(){ - return this.getSelectedItemsForBranch(TemplateImportTreeBranch.TASK_GROUPS) as [ApiTaskGroup,string][]; + public getAllSelectedTaskGroups() { + return this.getSelectedItemsForBranch(TemplateImportTreeBranch.TASK_GROUPS) as [ApiTaskGroup, string][]; } - public getAllSelectedTaskTemplates(){ - return this.getSelectedItemsForBranch(TemplateImportTreeBranch.TASK_TEMPLATES) as [ApiTaskTemplate, string][] + public getAllSelectedTaskTemplates() { + return this.getSelectedItemsForBranch(TemplateImportTreeBranch.TASK_TEMPLATES) as [ApiTaskTemplate, string][]; } - public getAllSelectedTeams(){ - return this.getSelectedItemsForBranch(TemplateImportTreeBranch.TEAMS) as [ApiTeam, string][] + public getAllSelectedTeams() { + return this.getSelectedItemsForBranch(TemplateImportTreeBranch.TEAMS) as [ApiTeam, string][]; } - public getAllSelectedTeamGroups(){ - return this.getSelectedItemsForBranch(TemplateImportTreeBranch.TEAM_GROUPS) as [ApiTeamGroup,string][] + public getAllSelectedTeamGroups() { + return this.getSelectedItemsForBranch(TemplateImportTreeBranch.TEAM_GROUPS) as [ApiTeamGroup, string][]; } - public getAllSelectedJudges(){ - return this.getSelectedItemsForBranch(TemplateImportTreeBranch.JUDGES) as [ApiUser,string][] + public getAllSelectedJudges() { + return this.getSelectedItemsForBranch(TemplateImportTreeBranch.JUDGES) as [ApiUser, string][]; } /** @@ -264,44 +272,43 @@ export class TemplateImportTreeComponent implements OnInit{ * @param branch A single branch, do not use ALL or NONE here (or any combination) * @private */ - private getSelectedItemsForBranch(branch: TemplateImportTreeBranch){ + private getSelectedItemsForBranch(branch: TemplateImportTreeBranch) { /* Filter appropriately */ - const items = this.selection.selected.filter(it => TemplateImportTreeComponent.checkForBranch(it.branch, branch)).map(it => this.flatNodeMap.get(it)) - switch(branch){ + const items = this.selection.selected.filter(it => TemplateImportTreeComponent.checkForBranch(it.branch, branch)).map(it => this.flatNodeMap.get(it)); + switch (branch) { case TemplateImportTreeBranch.NONE: case TemplateImportTreeBranch.ALL: - throw new Error("Cannot type set for TemplateImportTreeBanches ALL and NONE. This is a programmer's error") + throw new Error("Cannot type set for TemplateImportTreeBanches ALL and NONE. This is a programmer's error"); case TemplateImportTreeBranch.TASK_TYPES: - return items.map<[ApiTaskType, string]>(it => [it.item, it.origin]) + return items.map<[ApiTaskType, string]>(it => [it.item, it.origin]); case TemplateImportTreeBranch.TASK_GROUPS: - return items.map<[ApiTaskGroup, string]>(it => [it.item, it.origin]) + return items.map<[ApiTaskGroup, string]>(it => [it.item, it.origin]); case TemplateImportTreeBranch.TASK_TEMPLATES: return items.map<[ApiTaskTemplate, string]>(it => { /* Warning: collectionId remains and therefore must exist */ const newItem = it.item as ApiTaskTemplate; // newItem.id = undefined; // We need the id for sanitation purposes (to resolve for the parent evaluation template */ - return [newItem, it.origin] - }) + return [newItem, it.origin]; + }); case TemplateImportTreeBranch.TEAMS: return items.map<[ApiTeam, string]>(it => { - const newItem = it.item as ApiTeam + const newItem = it.item as ApiTeam; newItem.id = undefined; - return [newItem, it.origin] - }) + return [newItem, it.origin]; + }); case TemplateImportTreeBranch.TEAM_GROUPS: return items.map<[ApiTeamGroup, string]>(it => { - const newItem = it.item as ApiTeamGroup - newItem.id = undefined - return [newItem, it.origin] - }) + const newItem = it.item as ApiTeamGroup; + newItem.id = undefined; + return [newItem, it.origin]; + }); case TemplateImportTreeBranch.JUDGES: - return items.map<[ApiUser, string]>(it => [it.item,it.origin]) + return items.map<[ApiUser, string]>(it => [it.item, it.origin]); } } - - public static buildTrees(templates: ApiEvaluationTemplate[], branches: TemplateImportTreeBranch): TemplateTreeNode[]{ + public static buildTrees(templates: ApiEvaluationTemplate[], branches: TemplateImportTreeBranch): TemplateTreeNode[] { return templates.map(it => this.buildTree(it, branches)); } @@ -310,19 +317,19 @@ export class TemplateImportTreeComponent implements OnInit{ root.item = template; root.label = template.name; root.children = [] as TemplateTreeNode[]; - if(this.checkForBranch(branches, TemplateImportTreeBranch.TASK_TYPES) && template.taskTypes.length > 0){ + if (this.checkForBranch(branches, TemplateImportTreeBranch.TASK_TYPES) && template.taskTypes.length > 0) { root.children.push(this.buildTaskTypesBranch(template)); } - if(this.checkForBranch(branches, TemplateImportTreeBranch.TASK_GROUPS) && template.taskGroups.length > 0){ + if (this.checkForBranch(branches, TemplateImportTreeBranch.TASK_GROUPS) && template.taskGroups.length > 0) { root.children.push(this.buildTaskGroupsBranch(template)); } - if(this.checkForBranch(branches, TemplateImportTreeBranch.TASK_TEMPLATES) && template.tasks.length > 0){ + if (this.checkForBranch(branches, TemplateImportTreeBranch.TASK_TEMPLATES) && template.tasks.length > 0) { root.children.push(this.buildTaskTemplatesBranch(template)); } - if(this.checkForBranch(branches, TemplateImportTreeBranch.TEAMS) && template.teams.length > 0){ + if (this.checkForBranch(branches, TemplateImportTreeBranch.TEAMS) && template.teams.length > 0) { root.children.push(this.buildTeamsBranch(template)); } - if(this.checkForBranch(branches, TemplateImportTreeBranch.TEAM_GROUPS) && template.teamGroups.length > 0){ + if (this.checkForBranch(branches, TemplateImportTreeBranch.TEAM_GROUPS) && template.teamGroups.length > 0) { root.children.push(this.buildTeamGroupsBranch(template)); } // TODO load apiusers for judges on the fly in future version @@ -332,24 +339,24 @@ export class TemplateImportTreeComponent implements OnInit{ return root; } - public static checkForBranch(branches: TemplateImportTreeBranch, test: TemplateImportTreeBranch): boolean{ - return (branches & test) === test + public static checkForBranch(branches: TemplateImportTreeBranch, test: TemplateImportTreeBranch): boolean { + return (branches & test) === test; } public static buildTaskTypesBranch(template: ApiEvaluationTemplate): TemplateTreeNode { - return this.buildBranch(template, "taskTypes", "Task Types", "name",TemplateImportTreeBranch.TASK_TYPES); + return this.buildBranch(template, "taskTypes", "Task Types", "name", TemplateImportTreeBranch.TASK_TYPES); } public static buildTaskGroupsBranch(template: ApiEvaluationTemplate): TemplateTreeNode { - return this.buildBranch(template, "taskGroups", "Task Groups", "name",TemplateImportTreeBranch.TASK_GROUPS); + return this.buildBranch(template, "taskGroups", "Task Groups", "name", TemplateImportTreeBranch.TASK_GROUPS); } public static buildTaskTemplatesBranch(template: ApiEvaluationTemplate): TemplateTreeNode { - return this.buildBranch(template, "tasks", "Task Templates","name", TemplateImportTreeBranch.TASK_TEMPLATES); + return this.buildBranch(template, "tasks", "Task Templates", "name", TemplateImportTreeBranch.TASK_TEMPLATES); } public static buildTeamsBranch(template: ApiEvaluationTemplate): TemplateTreeNode { - return this.buildBranch(template, "teams", "Teams","name", TemplateImportTreeBranch.TEAMS); + return this.buildBranch(template, "teams", "Teams", "name", TemplateImportTreeBranch.TEAMS); } public static buildTeamGroupsBranch(template: ApiEvaluationTemplate): TemplateTreeNode { @@ -371,7 +378,7 @@ export class TemplateImportTreeComponent implements OnInit{ item.item = it; item.children = null; item.branch = branch; - item.origin = template.id + item.origin = template.id; //console.log("THE CHILD", item) return item; }); diff --git a/frontend/src/app/template/template-builder/template-builder.component.html b/frontend/src/app/template/template-builder/template-builder.component.html index 6023d3cb3..a83f8ac58 100644 --- a/frontend/src/app/template/template-builder/template-builder.component.html +++ b/frontend/src/app/template/template-builder/template-builder.component.html @@ -37,7 +37,7 @@

Edit evaluation template {{(builderService.templateAsObservable() | async)?. aria-label="Import from other evaluation templates" matTooltip="Import from other evaluation templates" (click)="import()"> - file_upload + merge diff --git a/frontend/src/app/template/template-builder/template-builder.component.ts b/frontend/src/app/template/template-builder/template-builder.component.ts index c79739052..9932110a5 100644 --- a/frontend/src/app/template/template-builder/template-builder.component.ts +++ b/frontend/src/app/template/template-builder/template-builder.component.ts @@ -98,9 +98,11 @@ export class TemplateBuilderComponent extends AbstractTemplateBuilderComponent i switchMap(templateList => forkJoin(...templateList)) ); templateList.subscribe(templates => { - console.log("Templates ", templates) - const ownIdx = templates.indexOf(this.builderService.getTemplate()) - templates.splice(ownIdx,1) + const ownIdx = templates.map(it => it.id).indexOf(this.builderService.getTemplate().id) + if(ownIdx !== -1){ + /* In case something went wrong, ownIdx results in -1, which with the splice would result in splicing the last element, which in this case we dont want */ + templates.splice(ownIdx,1) + } const dialogref = this.dialg.open(TemplateImportDialogComponent, {width: '800px', data: {templates: templates, branches: TemplateImportTreeBranch.ALL} as TemplateImportDialogData}) dialogref.afterClosed().subscribe( d => { this.onImport(d) @@ -111,7 +113,9 @@ export class TemplateBuilderComponent extends AbstractTemplateBuilderComponent i public onImport(templateToImportFrom: ApiEvaluationTemplate){ console.log("Importing...", templateToImportFrom) - this.builderService.importFrom(templateToImportFrom); + if(templateToImportFrom){ + this.builderService.importFrom(templateToImportFrom); + } } public save(){ diff --git a/frontend/src/app/template/template-builder/template-builder.service.ts b/frontend/src/app/template/template-builder/template-builder.service.ts index acd0e153f..77798c321 100644 --- a/frontend/src/app/template/template-builder/template-builder.service.ts +++ b/frontend/src/app/template/template-builder/template-builder.service.ts @@ -263,38 +263,54 @@ export class TemplateBuilderService { } importFrom(from: ApiEvaluationTemplate) { + /* Import check is currently on name, may switch to completely UUID based matching */ /* types */ from.taskTypes.forEach(it => { - if(!this.getTemplate().taskTypes.includes(it)){ + if(!this.getTemplate().taskTypes.map(that => that.name).includes(it.name)){ + this.getTemplate().taskTypes.push(it) + }else{ + it.name = `${it.name} (Imported)` this.getTemplate().taskTypes.push(it) } }) /* task groups */ from.taskGroups.forEach(it => { - if(!this.getTemplate().taskGroups.includes(it)){ + if(!this.getTemplate().taskGroups.map(that => that.name).includes(it.name)){ + this.getTemplate().taskGroups.push(it) + }else{ + it.name = `${it.name} (Imported)` this.getTemplate().taskGroups.push(it) } }) /* tasks */ from.tasks.forEach(it => { - if(!this.getTemplate().tasks.includes(it)){ + if(!this.getTemplate().tasks.map(that => that.name).includes(it.name)){ + this.getTemplate().tasks.push(it) + }else{ + it.name = `${it.name} (Imported)` this.getTemplate().tasks.push(it) } }) /* teams */ from.teams.forEach(it => { - if(!this.getTemplate().teams.includes(it)){ + if(!this.getTemplate().teams.map(that => that.name).includes(it.name)){ + this.getTemplate().teams.push(it) + }else{ + it.name = `${it.name} (Imported)` this.getTemplate().teams.push(it) } }) /* team groups */ from.teamGroups.forEach(it => { - if(!this.getTemplate().teamGroups.includes(it)){ + if(!this.getTemplate().teamGroups.map(that => that.name).includes(it.name)){ + this.getTemplate().teamGroups.push(it) + }else{ + it.name = `${it.name} (Imported)` this.getTemplate().teamGroups.push(it) } }) - /* judges */ + /* judges are userids, hence no renaming */ from.judges.forEach(it => { if(!this.getTemplate().judges.includes(it)){ this.getTemplate().judges.push(it)