diff --git a/src/main/webapp/app/admin/admin.route.ts b/src/main/webapp/app/admin/admin.route.ts index 0c2099494d4a..81c3a096f66f 100644 --- a/src/main/webapp/app/admin/admin.route.ts +++ b/src/main/webapp/app/admin/admin.route.ts @@ -20,6 +20,7 @@ import { BuildAgentSummaryComponent } from 'app/localci/build-agents/build-agent import { StandardizedCompetencyManagementComponent } from 'app/admin/standardized-competencies/standardized-competency-management.component'; import { BuildAgentDetailsComponent } from 'app/localci/build-agents/build-agent-details/build-agent-details/build-agent-details.component'; import { AdminImportStandardizedCompetenciesComponent } from 'app/admin/standardized-competencies/import/admin-import-standardized-competencies.component'; +import { PendingChangesGuard } from 'app/shared/guard/pending-changes.guard'; export const adminState: Routes = [ { @@ -116,6 +117,7 @@ export const adminState: Routes = [ data: { pageTitle: 'artemisApp.standardizedCompetency.title', }, + canDeactivate: [PendingChangesGuard], }, { // Create a new path without a component defined to prevent the StandardizedCompetencyManagementComponent from being always rendered diff --git a/src/main/webapp/app/admin/standardized-competencies/standardized-competency-management.component.ts b/src/main/webapp/app/admin/standardized-competencies/standardized-competency-management.component.ts index fb73da984d1b..d23fe7213f6d 100644 --- a/src/main/webapp/app/admin/standardized-competencies/standardized-competency-management.component.ts +++ b/src/main/webapp/app/admin/standardized-competencies/standardized-competency-management.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { faChevronRight, faDownLeftAndUpRightToCenter, faEye, faFileExport, faFileImport, faPlus, faUpRightAndDownLeftFromCenter } from '@fortawesome/free-solid-svg-icons'; import { KnowledgeAreaDTO, @@ -576,14 +576,4 @@ export class StandardizedCompetencyManagementComponent extends StandardizedCompe get canDeactivateWarning(): string { return this.translateService.instant('pendingChanges'); } - - /** - * Displays the alert for confirming refreshing or closing the page if there are unsaved changes - */ - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any) { - if (!this.canDeactivate()) { - event.returnValue = this.canDeactivateWarning; - } - } } diff --git a/src/main/webapp/app/course/competencies/generate-competencies/generate-competencies.component.ts b/src/main/webapp/app/course/competencies/generate-competencies/generate-competencies.component.ts index 9501484fe9b5..19d3d33b6124 100644 --- a/src/main/webapp/app/course/competencies/generate-competencies/generate-competencies.component.ts +++ b/src/main/webapp/app/course/competencies/generate-competencies/generate-competencies.component.ts @@ -1,4 +1,4 @@ -import { Component, HostListener, OnInit, ViewChild } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; import { CompetencyService } from 'app/course/competencies/competency.service'; import { AlertService } from 'app/core/util/alert.service'; import { onError } from 'app/shared/util/global.utils'; @@ -249,14 +249,4 @@ export class GenerateCompetenciesComponent implements OnInit, ComponentCanDeacti get canDeactivateWarning(): string { return this.translateService.instant('pendingChanges'); } - - /** - * Only allow to refresh the page if no pending changes exist - */ - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any) { - if (!this.canDeactivate()) { - event.returnValue = this.canDeactivateWarning; - } - } } diff --git a/src/main/webapp/app/course/competencies/import-standardized-competencies/course-import-standardized-course-competencies.component.ts b/src/main/webapp/app/course/competencies/import-standardized-competencies/course-import-standardized-course-competencies.component.ts index 1c369bea4657..63b74465efe7 100644 --- a/src/main/webapp/app/course/competencies/import-standardized-competencies/course-import-standardized-course-competencies.component.ts +++ b/src/main/webapp/app/course/competencies/import-standardized-competencies/course-import-standardized-course-competencies.component.ts @@ -10,7 +10,7 @@ import { } from 'app/entities/competency/standardized-competency.model'; import { faBan, faDownLeftAndUpRightToCenter, faFileImport, faSort, faTrash, faUpRightAndDownLeftFromCenter } from '@fortawesome/free-solid-svg-icons'; import { ActivatedRoute, Router } from '@angular/router'; -import { Component, HostListener, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { onError } from 'app/shared/util/global.utils'; import { forkJoin, map } from 'rxjs'; import { HttpErrorResponse } from '@angular/common/http'; @@ -168,16 +168,6 @@ export abstract class CourseImportStandardizedCourseCompetenciesComponent extend return this.translateService.instant('pendingChanges'); } - /** - * Displays the alert for confirming refreshing or closing the page if there are unsaved changes - */ - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any) { - if (!this.canDeactivate()) { - event.returnValue = this.canDeactivateWarning; - } - } - private convertToKnowledgeAreaForImport(knowledgeAreaDTO: KnowledgeAreaDTO, isVisible = true, level = 0, selected = false): KnowledgeAreaForImport { const children = knowledgeAreaDTO.children?.map((child) => this.convertToKnowledgeAreaForImport(child, isVisible, level + 1)); const competencies = knowledgeAreaDTO.competencies?.map((competency) => diff --git a/src/main/webapp/app/course/competencies/import/import-course-competencies.component.ts b/src/main/webapp/app/course/competencies/import/import-course-competencies.component.ts index 7cb8bbb30cf1..0190d71b8b5e 100644 --- a/src/main/webapp/app/course/competencies/import/import-course-competencies.component.ts +++ b/src/main/webapp/app/course/competencies/import/import-course-competencies.component.ts @@ -4,7 +4,7 @@ import { CourseCompetency, CourseCompetencyType } from 'app/entities/competency. import { AlertService } from 'app/core/util/alert.service'; import { SortService } from 'app/shared/service/sort.service'; import { onError } from 'app/shared/util/global.utils'; -import { Component, HostListener, OnInit, inject } from '@angular/core'; +import { Component, OnInit, inject } from '@angular/core'; import { faBan, faFileImport, faSave, faTrash } from '@fortawesome/free-solid-svg-icons'; import { ButtonType } from 'app/shared/components/button.component'; import { ActivatedRoute, Router } from '@angular/router'; @@ -204,14 +204,4 @@ export abstract class ImportCourseCompetenciesComponent implements OnInit, Compo get canDeactivateWarning(): string { return this.translateService.instant('pendingChanges'); } - - /** - * Displays the alert for confirming refreshing or closing the page if there are unsaved changes - */ - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any) { - if (!this.canDeactivate()) { - event.returnValue = this.canDeactivateWarning; - } - } } diff --git a/src/main/webapp/app/exam/participate/exam-participation.component.ts b/src/main/webapp/app/exam/participate/exam-participation.component.ts index a26170db25ca..7871ce88d681 100644 --- a/src/main/webapp/app/exam/participate/exam-participation.component.ts +++ b/src/main/webapp/app/exam/participate/exam-participation.component.ts @@ -1,4 +1,4 @@ -import { Component, HostListener, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; +import { Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { JhiWebsocketService } from 'app/core/websocket/websocket.service'; import { ExamParticipationService } from 'app/exam/participate/exam-participation.service'; @@ -246,14 +246,6 @@ export class ExamParticipationComponent implements OnInit, OnDestroy, ComponentC return this.translateService.instant('artemisApp.examParticipation.pendingChanges'); } - // displays the alert for confirming leaving the page if there are unsaved changes - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any): void { - if (!this.canDeactivate()) { - event.returnValue = this.canDeactivateWarning; - } - } - get activePageIndex(): number { if (!this.activeExamPage || this.activeExamPage.isOverviewPage) { return -1; diff --git a/src/main/webapp/app/exercises/modeling/participate/modeling-submission.component.ts b/src/main/webapp/app/exercises/modeling/participate/modeling-submission.component.ts index e4684d85321b..e9c54a294c31 100644 --- a/src/main/webapp/app/exercises/modeling/participate/modeling-submission.component.ts +++ b/src/main/webapp/app/exercises/modeling/participate/modeling-submission.component.ts @@ -1,5 +1,5 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, HostListener, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Patch, Selection, UMLDiagramType, UMLElementType, UMLModel, UMLRelationshipType } from '@ls1intum/apollon'; import { TranslateService } from '@ngx-translate/core'; @@ -642,14 +642,6 @@ export class ModelingSubmissionComponent implements OnInit, OnDestroy, Component return false; } - // displays the alert for confirming leaving the page if there are unsaved changes - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any): void { - if (!this.canDeactivate()) { - event.returnValue = this.translateService.instant('pendingChanges'); - } - } - /** * counts the number of model elements * is used in the submit() function diff --git a/src/main/webapp/app/exercises/programming/shared/code-editor/container/code-editor-container.component.ts b/src/main/webapp/app/exercises/programming/shared/code-editor/container/code-editor-container.component.ts index f8e9607545db..b2b81406b23a 100644 --- a/src/main/webapp/app/exercises/programming/shared/code-editor/container/code-editor-container.component.ts +++ b/src/main/webapp/app/exercises/programming/shared/code-editor/container/code-editor-container.component.ts @@ -1,8 +1,7 @@ -import { Component, EventEmitter, HostListener, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { isEmpty as _isEmpty, fromPairs, toPairs, uniq } from 'lodash-es'; import { CodeEditorFileService } from 'app/exercises/programming/shared/code-editor/service/code-editor-file.service'; -import { ComponentCanDeactivate } from 'app/shared/guard/can-deactivate.model'; import { CodeEditorGridComponent } from 'app/exercises/programming/shared/code-editor/layout/code-editor-grid.component'; import { CommitState, @@ -37,7 +36,7 @@ export enum CollapsableCodeEditorElement { templateUrl: './code-editor-container.component.html', styleUrls: ['./code-editor-container.component.scss'], }) -export class CodeEditorContainerComponent implements OnChanges, ComponentCanDeactivate { +export class CodeEditorContainerComponent implements OnChanges { readonly CommitState = CommitState; readonly EditorState = EditorState; readonly CollapsableCodeEditorElement = CollapsableCodeEditorElement; @@ -262,13 +261,6 @@ export class CodeEditorContainerComponent implements OnChanges, ComponentCanDeac this.alertService.error(`artemisApp.editor.errors.${errorTranslationKey}`, translationParams); } - /** - * The user will be warned if there are unsaved changes when trying to leave the code-editor. - */ - canDeactivate() { - return _isEmpty(this.unsavedFiles); - } - getText(): string { return this.monacoEditor.getText() ?? ''; } @@ -286,14 +278,6 @@ export class CodeEditorContainerComponent implements OnChanges, ComponentCanDeac this.monacoEditor.highlightLines(startLine, endLine); } - // displays the alert for confirming refreshing or closing the page if there are unsaved changes - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any) { - if (!this.canDeactivate()) { - event.returnValue = this.translateService.instant('pendingChanges'); - } - } - onToggleCollapse(event: InteractableEvent, collapsableElement: CollapsableCodeEditorElement) { this.grid.toggleCollapse(event, collapsableElement); } diff --git a/src/main/webapp/app/exercises/quiz/manage/quiz-exercise-update.component.ts b/src/main/webapp/app/exercises/quiz/manage/quiz-exercise-update.component.ts index b6fda2b6e1ee..f212b3469e6c 100644 --- a/src/main/webapp/app/exercises/quiz/manage/quiz-exercise-update.component.ts +++ b/src/main/webapp/app/exercises/quiz/manage/quiz-exercise-update.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, OnChanges, OnInit, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnChanges, OnInit, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core'; import { QuizExerciseService } from './quiz-exercise.service'; import { ActivatedRoute, Router } from '@angular/router'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; @@ -355,16 +355,6 @@ export class QuizExerciseUpdateComponent extends QuizExerciseValidationDirective return !this.pendingChangesCache; } - /** - * Displays the alert for confirming refreshing or closing the page if there are unsaved changes - */ - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any) { - if (!this.canDeactivate()) { - event.returnValue = this.translateService.instant('pendingChanges'); - } - } - /** * @desc Callback for datepicker to decide whether given date should be disabled * All dates which are in the past (< today) are disabled diff --git a/src/main/webapp/app/exercises/text/participate/text-editor.component.ts b/src/main/webapp/app/exercises/text/participate/text-editor.component.ts index ca6401b6f59a..e270ad1d191a 100644 --- a/src/main/webapp/app/exercises/text/participate/text-editor.component.ts +++ b/src/main/webapp/app/exercises/text/participate/text-editor.component.ts @@ -1,4 +1,4 @@ -import { Component, HostListener, Input, OnDestroy, OnInit } from '@angular/core'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { ActivatedRoute } from '@angular/router'; import { HttpErrorResponse } from '@angular/common/http'; @@ -248,14 +248,6 @@ export class TextEditorComponent implements OnInit, OnDestroy, ComponentCanDeact return this.stringCountService.countCharacters(this.answer); } - // Displays the alert for confirming refreshing or closing the page if there are unsaved changes - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any) { - if (!this.canDeactivate()) { - event.returnValue = this.translateService.instant('pendingChanges'); - } - } - canDeactivate(): boolean { if (!this.submission) { return true; diff --git a/src/main/webapp/app/exercises/text/participate/text-editor.route.ts b/src/main/webapp/app/exercises/text/participate/text-editor.route.ts index 1fa9cff2a527..8ed5f35e65d6 100644 --- a/src/main/webapp/app/exercises/text/participate/text-editor.route.ts +++ b/src/main/webapp/app/exercises/text/participate/text-editor.route.ts @@ -2,6 +2,7 @@ import { Routes } from '@angular/router'; import { UserRouteAccessService } from 'app/core/auth/user-route-access-service'; import { TextEditorComponent } from './text-editor.component'; import { Authority } from 'app/shared/constants/authority.constants'; +import { PendingChangesGuard } from 'app/shared/guard/pending-changes.guard'; export const textEditorRoute: Routes = [ { @@ -12,5 +13,6 @@ export const textEditorRoute: Routes = [ pageTitle: 'artemisApp.textExercise.home.title', }, canActivate: [UserRouteAccessService], + canDeactivate: [PendingChangesGuard], }, ]; diff --git a/src/test/javascript/spec/component/competencies/generate-competencies/generate-competencies.component.spec.ts b/src/test/javascript/spec/component/competencies/generate-competencies/generate-competencies.component.spec.ts index 6888c6d994ea..94e72e39d3de 100644 --- a/src/test/javascript/spec/component/competencies/generate-competencies/generate-competencies.component.spec.ts +++ b/src/test/javascript/spec/component/competencies/generate-competencies/generate-competencies.component.spec.ts @@ -259,11 +259,10 @@ describe('GenerateCompetenciesComponent', () => { expect(warnMock).toHaveBeenCalled(); }); - it('should send a warning when trying to reload', () => { + it('should not deactivate when loading', () => { generateCompetenciesComponent.isLoading = true; - const event = { returnValue: undefined }; - generateCompetenciesComponent.unloadNotification(event); - expect(event.returnValue).toBe(generateCompetenciesComponent.canDeactivateWarning); + const canDeactivate = generateCompetenciesComponent.canDeactivate(); + expect(canDeactivate).toBeFalse(); }); function createCompetencyFormGroup(title?: string, description?: string, taxonomy?: CompetencyTaxonomy, viewed = false): FormGroup { diff --git a/src/test/javascript/spec/component/competencies/import/import-course-competencies.component.spec.ts b/src/test/javascript/spec/component/competencies/import/import-course-competencies.component.spec.ts index 3c92ae65d93c..619054449eba 100644 --- a/src/test/javascript/spec/component/competencies/import/import-course-competencies.component.spec.ts +++ b/src/test/javascript/spec/component/competencies/import/import-course-competencies.component.spec.ts @@ -181,16 +181,15 @@ describe('ImportCourseCompetenciesComponent', () => { expect(component.disabledIds).toHaveLength(3); }); - it('should not unload with pending changes', () => { - const deactivateWarningSpy = jest.spyOn(component, 'canDeactivateWarning', 'get'); - + it('should not deactivate with pending changes', () => { + let canDeactivate; component['isSubmitted'] = true; component.selectedCourseCompetencies = { resultsOnPage: [{ id: 1 }], numberOfPages: 0 }; - component['unloadNotification']({ returnValue: '' }); - expect(deactivateWarningSpy).not.toHaveBeenCalled(); + canDeactivate = component.canDeactivate(); + expect(canDeactivate).toBeTrue(); component['isSubmitted'] = false; - component['unloadNotification']({ returnValue: '' }); - expect(deactivateWarningSpy).toHaveBeenCalled(); + canDeactivate = component.canDeactivate(); + expect(canDeactivate).toBeFalse(); }); }); diff --git a/src/test/javascript/spec/component/exam/participate/exam-participation.component.spec.ts b/src/test/javascript/spec/component/exam/participate/exam-participation.component.spec.ts index de433ab78169..1e4bda79525a 100644 --- a/src/test/javascript/spec/component/exam/participate/exam-participation.component.spec.ts +++ b/src/test/javascript/spec/component/exam/participate/exam-participation.component.spec.ts @@ -767,16 +767,6 @@ describe('ExamParticipationComponent', () => { }); }); - describe('unloadNotification', () => { - it('should set event return value', () => { - jest.spyOn(comp, 'canDeactivate').mockReturnValue(false); - jest.spyOn(comp, 'canDeactivateWarning', 'get').mockReturnValue('warning'); - const event = { returnValue: undefined }; - comp.unloadNotification(event); - expect(event.returnValue).toBe('warning'); - }); - }); - describe('isOver', () => { it('should return true if exam has ended', () => { const studentExam = new StudentExam(); diff --git a/src/test/javascript/spec/component/quiz-exercise/quiz-exercise-update.component.spec.ts b/src/test/javascript/spec/component/quiz-exercise/quiz-exercise-update.component.spec.ts index a93c87bdf3f3..c271a8deb04e 100644 --- a/src/test/javascript/spec/component/quiz-exercise/quiz-exercise-update.component.spec.ts +++ b/src/test/javascript/spec/component/quiz-exercise/quiz-exercise-update.component.spec.ts @@ -633,18 +633,16 @@ describe('QuizExerciseUpdateComponent', () => { expect(comp.canDeactivate()).toBeTrue(); }); - it('should set event return value to translate if not canDeactivate', () => { + it('should not deactivate with pending changes', () => { comp.pendingChangesCache = true; - const ev = { returnValue: undefined }; - comp.unloadNotification(ev); - expect(ev.returnValue).toBe('pendingChanges'); + const canDeactivate = comp.canDeactivate(); + expect(canDeactivate).toBeFalse(); }); - it('should not set event return value to translate if canDeactivate', () => { + it('should deactivate with no pending changes', () => { comp.pendingChangesCache = false; - const ev = { returnValue: undefined }; - comp.unloadNotification(ev); - expect(ev.returnValue).toBeUndefined(); + const canDeactivate = comp.canDeactivate(); + expect(canDeactivate).toBeTrue(); }); }); diff --git a/src/test/javascript/spec/component/standardized-competencies/course-import-standardized-competencies.spec.ts b/src/test/javascript/spec/component/standardized-competencies/course-import-standardized-competencies.spec.ts index c780c1c252c6..a32795a28d18 100644 --- a/src/test/javascript/spec/component/standardized-competencies/course-import-standardized-competencies.spec.ts +++ b/src/test/javascript/spec/component/standardized-competencies/course-import-standardized-competencies.spec.ts @@ -205,14 +205,14 @@ describe('CourseImportStandardizedCompetenciesComponent', () => { }); it('should not deactivate with pending changes', () => { - const deactivateWarningSpy = jest.spyOn(component as any, 'canDeactivateWarning', 'get'); + let canDeactivate; component['isLoading'] = false; - component['unloadNotification']({ returnValue: '' }); - expect(deactivateWarningSpy).not.toHaveBeenCalled(); + canDeactivate = component.canDeactivate(); + expect(canDeactivate).toBeTrue(); component['isLoading'] = true; - component['unloadNotification']({ returnValue: '' }); - expect(deactivateWarningSpy).toHaveBeenCalled(); + canDeactivate = component.canDeactivate(); + expect(canDeactivate).toBeFalse(); }); }); diff --git a/src/test/javascript/spec/component/standardized-competencies/course-import-standardized-prerequisites.spec.ts b/src/test/javascript/spec/component/standardized-competencies/course-import-standardized-prerequisites.spec.ts index d6a319b34c84..78e5c6e90306 100644 --- a/src/test/javascript/spec/component/standardized-competencies/course-import-standardized-prerequisites.spec.ts +++ b/src/test/javascript/spec/component/standardized-competencies/course-import-standardized-prerequisites.spec.ts @@ -205,14 +205,14 @@ describe('CourseImportStandardizedPrerequisitesComponent', () => { }); it('should not deactivate with pending changes', () => { - const deactivateWarningSpy = jest.spyOn(component as any, 'canDeactivateWarning', 'get'); + let canDeactivate; component['isLoading'] = false; - component['unloadNotification']({ returnValue: '' }); - expect(deactivateWarningSpy).not.toHaveBeenCalled(); + canDeactivate = component.canDeactivate(); + expect(canDeactivate).toBeTrue(); component['isLoading'] = true; - component['unloadNotification']({ returnValue: '' }); - expect(deactivateWarningSpy).toHaveBeenCalled(); + canDeactivate = component.canDeactivate(); + expect(canDeactivate).toBeFalse(); }); }); diff --git a/src/test/javascript/spec/component/standardized-competencies/standardized-competency-management.spec.ts b/src/test/javascript/spec/component/standardized-competencies/standardized-competency-management.spec.ts index 3e46a8a4add1..9c2b7f487793 100644 --- a/src/test/javascript/spec/component/standardized-competencies/standardized-competency-management.spec.ts +++ b/src/test/javascript/spec/component/standardized-competencies/standardized-competency-management.spec.ts @@ -479,15 +479,15 @@ describe('StandardizedCompetencyManagementComponent', () => { }); it('should not deactivate with pending changes', () => { - const deactivateWarningSpy = jest.spyOn(component, 'canDeactivateWarning', 'get'); + let canDeactivate; component['isEditing'] = false; - component['unloadNotification']({ returnValue: '' }); - expect(deactivateWarningSpy).not.toHaveBeenCalled(); + canDeactivate = component.canDeactivate(); + expect(canDeactivate).toBeTrue(); component['isEditing'] = true; - component['unloadNotification']({ returnValue: '' }); - expect(deactivateWarningSpy).toHaveBeenCalled(); + canDeactivate = component.canDeactivate(); + expect(canDeactivate).toBeFalse(); }); function prepareAndExecuteCompetencyUpdate(tree: KnowledgeAreaDTO[], competencyToUpdate: StandardizedCompetencyDTO, updatedCompetency: StandardizedCompetencyDTO) {