Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
22.16.0
22.21.1
111 changes: 74 additions & 37 deletions src/app/dataClass/data-class/data-class.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ limitations under the License.
SPDX-License-Identifier: Apache-2.0
*/
import { AfterViewChecked, Component, OnInit, ViewChild } from '@angular/core';
import { switchMap } from 'rxjs/operators';
import { MdmResourcesService } from '@mdm/modules/resources';
import { MessageService } from '@mdm/services/message.service';
import { SharedService } from '@mdm/services/shared.service';
Expand Down Expand Up @@ -50,6 +51,7 @@ import { ProfileDataViewComponent } from '@mdm/shared/profile-data-view/profile-
import { ModelHeaderComponent } from '@mdm/model-header/model-header.component';
import { MatProgressBar } from '@angular/material/progress-bar';
import { NgIf } from '@angular/common';
import { BroadcastService } from '@mdm/services/broadcast.service';

@Component({
selector: 'mdm-data-class',
Expand Down Expand Up @@ -98,7 +100,8 @@ export class DataClassComponent
private securityHandler: SecurityHandlerService,
private title: Title,
private editingService: EditingService,
private messageHandler: MessageHandlerService
private messageHandler: MessageHandlerService,
private broadcast: BroadcastService
) {
super();
}
Expand Down Expand Up @@ -208,6 +211,10 @@ export class DataClassComponent

save(saveItems: DefaultProfileItem[]) {
this.error = '';
const previousParentDataClassId = this.dataClass.parentDataClass ?? null;

// parentDataClass is handled via the move() API call, not the update payload
let newParentDataClassId: string | null = previousParentDataClassId;

const resource: DataClass = {
id: this.dataClass.id,
Expand All @@ -228,51 +235,81 @@ export class DataClassComponent
resource.minMultiplicity = item.minMultiplicity as number;
resource.maxMultiplicity = item.maxMultiplicity;
}
else {
else if (item.propertyName === 'parentDataClass') {
// Capture separately - do not include in the update resource body
newParentDataClassId = (item.value as string | null) ?? null;
}
else {
resource[item.propertyName] = item.value;
}
});

if (!this.dataClass.parentDataClass) {
this.resourcesService.dataClass
.update(this.dataClass.model, this.dataClass.id, resource)
.subscribe(
(result: DataClassDetailResponse) => {
this.dataClass = result.body;
this.catalogueItem = result.body;
this.messageHandler.showSuccess('Data Class updated successfully.');
this.editingService.stop();
this.messageService.dataChanged(result.body);
},
(error) => {
this.messageHandler.showError(
'There was a problem updating the Data Class.',
error
);
}
);
}
else {
this.resourcesService.dataClass
const parentDataClassChanged = previousParentDataClassId !== newParentDataClassId;

const currentTab = this.tabs.getByIndex(this.activeTab)?.name ?? 'description';

const onSaveSuccess = (result: DataClassDetailResponse) => {
const updated = result.body;

this.dataClass = updated;
this.catalogueItem = updated;
this.messageHandler.showSuccess('Data Class updated successfully.');
this.editingService.stop();
this.messageService.dataChanged(updated);

if (parentDataClassChanged) {
this.broadcast.reloadCatalogueTree();
this.stateHandler.Go('dataClass', {
dataModelId: updated.model,
id: updated.id,
dataClassId: newParentDataClassId,
tabView: currentTab
});
return;
}

this.dataClassDetails(
updated.model,
updated.id,
newParentDataClassId ?? undefined
);
};

const onError = (error) => {
this.messageHandler.showError(
'There was a problem updating the Data Class.',
error
);
};

// Build the update call using the new parent context for the URL
const doUpdate = () => {
if (!newParentDataClassId) {
return this.resourcesService.dataClass
.update(this.dataClass.model, this.dataClass.id, resource);
}
return this.resourcesService.dataClass
.updateChildDataClass(
this.dataClass.model,
this.dataClass.parentDataClass,
newParentDataClassId,
this.dataClass.id,
resource
)
.subscribe(
(result: DataClassDetailResponse) => {
this.messageHandler.showSuccess('Data Class updated successfully.');
this.editingService.stop();
this.dataClass = result.body;
},
(error) => {
this.messageHandler.showError(
'There was a problem updating the Data Class.',
error
);
}
);
};

if (parentDataClassChanged) {
// Move to the new parent first, then run the regular update
this.resourcesService.dataClass
.move(
this.dataClass.model,
this.dataClass.id,
newParentDataClassId ?? undefined
)
.pipe(switchMap(() => doUpdate()))
.subscribe(onSaveSuccess, onError);
}
else {
doUpdate().subscribe(onSaveSuccess, onError);
}
}

Expand Down
35 changes: 28 additions & 7 deletions src/app/dataElement/data-element/data-element.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { ElementLinkListComponent } from '@mdm/shared/element-link-list/element-
import { ProfileDataViewComponent } from '@mdm/shared/profile-data-view/profile-data-view.component';
import { ModelHeaderComponent } from '@mdm/model-header/model-header.component';
import { NgIf } from '@angular/common';
import { BroadcastService } from '@mdm/services/broadcast.service';

@Component({
selector: 'mdm-data-element',
Expand Down Expand Up @@ -108,7 +109,8 @@ export class DataElementComponent
private title: Title,
private securityHandler: SecurityHandlerService,
private editingService: EditingService,
private elementTypes: ElementTypesService
private elementTypes: ElementTypesService,
private broadcast: BroadcastService
) {
super();
if (
Expand Down Expand Up @@ -200,6 +202,8 @@ export class DataElementComponent
}

save(saveItems: DefaultProfileItem[]) {
const previousDataClassId = this.dataClass.id as string;

const resource: DataElement = {
id: this.dataElementOutput.id,
label: this.dataElementOutput.label,
Expand Down Expand Up @@ -247,14 +251,31 @@ export class DataElementComponent
)
.subscribe(
(result: DataElementDetailResponse) => {
this.dataElementOutput = null;

setTimeout(() => {
this.dataElementOutput = result.body;
}, 250);
const updated = result.body;
const currentTab = this.tabs.getByIndex(this.activeTab)?.name ?? 'description';
const parentDataClassChanged = previousDataClassId !== updated.dataClass;

this.catalogueItem = result.body;
this.catalogueItem = updated;
this.dataModel.id = updated.model;
this.dataClass.id = updated.dataClass;
this.messageHandler.showSuccess('Data Element updated successfully.');

if (parentDataClassChanged) {
this.broadcast.reloadCatalogueTree();
this.stateHandler.Go('dataElement', {
dataModelId: updated.model,
dataClassId: updated.dataClass,
id: updated.id,
tabView: currentTab
});
return;
}

this.dataElementDetails(
updated.model as string,
updated.dataClass as string,
updated.id as string
);
},
(error) => {
this.messageHandler.showError(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!--
Copyright 2020-2025 University of Oxford and NHS England

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

SPDX-License-Identifier: Apache-2.0
-->

<h4 mat-dialog-title>Move Data Class</h4>
<mat-dialog-content class="mat-typography">
<p>Select the new parent Data Class. Clear selection to move to the Data Model root.</p>

<mdm-model-selector-tree
[ngModel]="selectedParent ? [selectedParent] : []"
[defaultElements]="selectedParent"
[root]="data.parentCatalogueItem"
[accepts]="dataClassDomainTypes"
[filterByDomainType]="dataClassDomainTypes"
[usedInModalDialogue]="true"
[placeholder]="'Select a Parent Data Class'"
(selectChange)="onDataClassSelect($event)"
></mdm-model-selector-tree>

<span class="help-block errorMessage" *ngIf="error"><small>{{ error }}</small></span>
</mat-dialog-content>

<mat-dialog-actions align="end" class="pt-2 pb-2">
<button mat-button color="warn" type="button" mat-dialog-close>Cancel</button>
<button
mat-flat-button
color="primary"
type="button"
[disabled]="!canSubmit || !!error"
(click)="submit()"
>
Submit
</button>
</mat-dialog-actions>

Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
Copyright 2020-2025 University of Oxford and NHS England

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

SPDX-License-Identifier: Apache-2.0
*/

import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogActions, MatDialogClose, MatDialogContent, MatDialogRef, MatDialogTitle } from '@angular/material/dialog';
import { CatalogueItemDomainType, MdmTreeItem } from '@maurodatamapper/mdm-resources';
import { ModalDialogStatus } from '@mdm/constants/modal-dialog-status';
import { ModelSelectorTreeComponent } from '../../model-selector-tree/model-selector-tree.component';
import { MatButton } from '@angular/material/button';
import { NgIf } from '@angular/common';

export interface DataClassMoveModalData {
parentCatalogueItem: { id: string }
currentDataClassId: string
currentParentDataClassId?: string
currentParentDataClassLabel?: string
}

export interface DataClassMoveModalResult {
status: ModalDialogStatus
newParentDataClassId?: string | null
}

@Component({
selector: 'mdm-data-class-move-modal',
templateUrl: './data-class-move-modal.component.html',
standalone: true,
imports: [
MatDialogTitle,
MatDialogContent,
MatDialogActions,
ModelSelectorTreeComponent,
MatButton,
MatDialogClose,
NgIf
]
})
export class DataClassMoveModalComponent {
readonly dataClassDomainTypes: CatalogueItemDomainType[] = [CatalogueItemDomainType.DataClass];

selectedParent: MdmTreeItem | null = null;
error = '';

constructor(
private dialogRef: MatDialogRef<DataClassMoveModalComponent, DataClassMoveModalResult>,
@Inject(MAT_DIALOG_DATA) public data: DataClassMoveModalData
) {
if (data.currentParentDataClassId) {
this.selectedParent = {
id: data.currentParentDataClassId,
label: data.currentParentDataClassLabel ?? data.currentParentDataClassId,
domainType: CatalogueItemDomainType.DataClass,
availableActions: []
};
}
}

get canSubmit() {
const selectedId = this.selectedParent?.id ?? null;
const currentId = this.data.currentParentDataClassId ?? null;
return selectedId !== currentId;
}

onDataClassSelect(selectedItems: MdmTreeItem[]) {
this.error = '';

if (!selectedItems || selectedItems.length === 0) {
this.selectedParent = null;
return;
}

const selected = selectedItems[0];
if (selected.id === this.data.currentDataClassId) {
this.error = 'A Data Class cannot be moved under itself.';
return;
}

this.selectedParent = {
id: selected.id,
label: selected.label,
domainType: selected.domainType,
availableActions: selected.availableActions ?? []
};
}

submit() {
if (!this.canSubmit || this.error) {
return;
}

this.dialogRef.close({
status: ModalDialogStatus.Ok,
newParentDataClassId: this.selectedParent?.id ?? null
});
}
}
Loading
Loading