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
13 changes: 2 additions & 11 deletions AMW_angular/io/src/app/base/base.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,7 @@ export class BaseService {
}

public handleError(response: HttpErrorResponse) {
let errorMsg = 'Error retrieving your data';
if (response.error) {
try {
errorMsg = response.error.message;
} catch (e) {
console.log(e);
}
}
console.error(response);
// throw an application level error
return throwError(() => errorMsg);
// Return the full error response so services can check status codes and handle appropriately
return throwError(() => response);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { BaseService } from 'src/app/base/base.service';
import { DeploymentLog } from './deployment-log';
Expand All @@ -26,4 +26,10 @@ export class DeploymentLogsService extends BaseService {
})
.pipe(catchError(this.handleError));
}

// Override to maintain backward compatibility - extract message string
override handleError(response: HttpErrorResponse) {
const errorMessage = response.error?.message || response.message || 'An error occurred';
return throwError(() => errorMessage);
}
}
17 changes: 5 additions & 12 deletions AMW_angular/io/src/app/resources/models/properties-action.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
export type PropertiesAction =
| { type: 'valueChange'; name: string; value: string }
| { type: 'resetToggle'; name: string; checked: boolean }
| { type: 'validationChange'; name: string; invalid: boolean }
| { type: 'editPropertyDescriptor'; id: number }
| { type: 'deletePropertyDescriptor'; id: number };

export type PropertiesValueChangeAction = Extract<PropertiesAction, { type: 'valueChange' }>;
export type PropertiesResetToggleAction = Extract<PropertiesAction, { type: 'resetToggle' }>;
export type PropertiesValidationChangeAction = Extract<PropertiesAction, { type: 'validationChange' }>;
export type PropertiesEditAction = Extract<PropertiesAction, { type: 'editPropertyDescriptor' }>;
export type PropertiesDeleteAction = Extract<PropertiesAction, { type: 'deletePropertyDescriptor' }>;
export type PropertiesValueChangeAction = { name: string; value: string };
export type PropertiesResetToggleAction = { name: string; checked: boolean };
export type PropertiesValidationChangeAction = { name: string; invalid: boolean };
export type PropertiesEditAction = { id: number };
export type PropertiesDeleteAction = { id: number };
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@
[canEdit]="canEdit()"
[canDecrypt]="canDecrypt()"
[canDelete]="canDelete()"
(valueChange)="valueChange.emit({ type: 'valueChange', name: property.name, value: $event })"
(resetChange)="resetToggled.emit({ type: 'resetToggle', name: property.name, checked: $event })"
(validationChange)="validationChanged.emit({ type: 'validationChange', name: property.name, invalid: $event })"
(editClicked)="editClicked.emit({ type: 'editPropertyDescriptor', id: property.descriptorId })"
(deleteClicked)="deleteClicked.emit({ type: 'deletePropertyDescriptor', id: property.descriptorId })"
(valueChange)="valueChange.emit({ name: property.name, value: $event })"
(resetChange)="resetToggled.emit({ name: property.name, checked: $event })"
(validationChange)="validationChanged.emit({ name: property.name, invalid: $event })"
(editClicked)="editClicked.emit({ id: property.descriptorId })"
(deleteClicked)="deleteClicked.emit({ id: property.descriptorId })"
></app-property-field>
</ng-template>
Original file line number Diff line number Diff line change
@@ -1,123 +1,116 @@
<app-modal-header [title]="title()" (cancel)="cancel()"></app-modal-header>
<app-loading-indicator [isLoading]="isLoadingDescriptor()"></app-loading-indicator>
<div class="modal-body">
@if (isLoadingDescriptor()) {
<div class="d-flex justify-content-center align-items-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
<form [formGroup]="form" class="form-horizontal mx-2">
@if (errorMessage()) {
<div class="alert alert-warning mb-3" role="alert">
<div [innerHTML]="errorMessage()"></div>
</div>
</div>
} @else {
<form [formGroup]="form" class="form-horizontal mx-2">
@if (errorMessage()) {
<div class="alert alert-warning mb-3" role="alert">
<div [innerHTML]="errorMessage()"></div>
</div>
}
}

<div class="mb-3">
<label for="propertyName" class="form-label">Technical Key <span class="text-danger">*</span></label>
<input
type="text"
class="form-control"
id="propertyName"
formControlName="name"
[class.is-invalid]="form.controls.name.invalid && form.controls.name.touched"
/>
@if (form.controls.name.invalid && form.controls.name.touched) {
<div class="invalid-feedback d-block">Technical Key is required</div>
}
</div>

<div class="mb-3">
<label for="displayName" class="form-label">Display name</label>
<input type="text" class="form-control" id="displayName" formControlName="displayName" />
</div>
<div class="mb-3">
<label for="propertyName" class="form-label">Technical Key <span class="text-danger">*</span></label>
<input
type="text"
class="form-control"
id="propertyName"
formControlName="name"
[class.is-invalid]="form.controls.name.invalid && form.controls.name.touched"
/>
@if (form.controls.name.invalid && form.controls.name.touched) {
<div class="invalid-feedback d-block">Technical Key is required</div>
}
</div>

<div class="mb-3">
<label for="propertyType" class="form-label">Property type</label>
<ng-select
id="propertyType"
[ngModel]="selectedPropertyType()?.id"
[ngModelOptions]="{ standalone: true }"
[clearable]="false"
(change)="onPropertyTypeChange($event)"
>
@for (type of propertyTypes(); track type.id) {
<ng-option [value]="type.id">{{ type.name }}</ng-option>
}
</ng-select>
</div>
<div class="mb-3">
<label for="displayName" class="form-label">Display name</label>
<input type="text" class="form-control" id="displayName" formControlName="displayName" />
</div>

<div class="mb-3">
<label for="validationLogic" class="form-label">Validation <span class="text-danger">*</span></label>
<input
type="text"
class="form-control"
id="validationLogic"
formControlName="validationRegex"
[class.is-invalid]="form.controls.validationRegex.invalid && form.controls.validationRegex.touched"
/>
@if (form.controls.validationRegex.invalid && form.controls.validationRegex.touched) {
<div class="invalid-feedback d-block">Validation is required</div>
<div class="mb-3">
<label for="propertyType" class="form-label">Property type</label>
<ng-select
id="propertyType"
[ngModel]="selectedPropertyType()?.id"
[ngModelOptions]="{ standalone: true }"
[clearable]="false"
(change)="onPropertyTypeChange($event)"
>
@for (type of propertyTypes(); track type.id) {
<ng-option [value]="type.id">{{ type.name }}</ng-option>
}
</div>
</ng-select>
</div>

<fieldset class="mb-3">
<legend class="form-label">Options</legend>
<div class="d-flex gap-4">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="nullable" formControlName="nullable" />
<label class="form-check-label" for="nullable">Value optional</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="optional" formControlName="optional" />
<label class="form-check-label" for="optional">Key optional</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="encrypt" formControlName="encrypted" />
<label class="form-check-label" for="encrypt">Encrypted</label>
</div>
</div>
</fieldset>
<div class="mb-3">
<label for="validationLogic" class="form-label">Validation <span class="text-danger">*</span></label>
<input
type="text"
class="form-control"
id="validationLogic"
formControlName="validationRegex"
[class.is-invalid]="form.controls.validationRegex.invalid && form.controls.validationRegex.touched"
/>
@if (form.controls.validationRegex.invalid && form.controls.validationRegex.touched) {
<div class="invalid-feedback d-block">Validation is required</div>
}
</div>

<div class="mb-3">
<label for="machineInterpretationKey" class="form-label">MIK</label>
@if (isLongMik()) {
<textarea class="form-control" id="machineInterpretationKey" formControlName="mik" rows="1"></textarea>
} @else {
<input type="text" class="form-control" id="machineInterpretationKey" formControlName="mik" />
}
<fieldset class="mb-3">
<legend class="form-label">Options</legend>
<div class="d-flex gap-4">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="nullable" formControlName="nullable" />
<label class="form-check-label" for="nullable">Value optional</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="optional" formControlName="optional" />
<label class="form-check-label" for="optional">Key optional</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="encrypt" formControlName="encrypted" />
<label class="form-check-label" for="encrypt">Encrypted</label>
</div>
</div>
</fieldset>

<div class="mb-3">
<label for="defaultValue" class="form-label">Default value</label>
@if (isLongDefaultValue()) {
<textarea class="form-control" id="defaultValue" formControlName="defaultValue" rows="1"></textarea>
} @else {
<input type="text" class="form-control" id="defaultValue" formControlName="defaultValue" />
}
</div>
<div class="mb-3">
<label for="machineInterpretationKey" class="form-label">MIK</label>
@if (isLongMik()) {
<textarea class="form-control" id="machineInterpretationKey" formControlName="mik" rows="1"></textarea>
} @else {
<input type="text" class="form-control" id="machineInterpretationKey" formControlName="mik" />
}
</div>

<div class="mb-3">
<label for="exampleValue" class="form-label">Example value</label>
@if (isLongExampleValue()) {
<textarea class="form-control" id="exampleValue" formControlName="exampleValue" rows="1"></textarea>
} @else {
<input type="text" class="form-control" id="exampleValue" formControlName="exampleValue" />
}
</div>
<div class="mb-3">
<label for="defaultValue" class="form-label">Default value</label>
@if (isLongDefaultValue()) {
<textarea class="form-control" id="defaultValue" formControlName="defaultValue" rows="1"></textarea>
} @else {
<input type="text" class="form-control" id="defaultValue" formControlName="defaultValue" />
}
</div>

<div class="mb-3">
<label for="propertyComment" class="form-label">Comment</label>
<textarea class="form-control" id="propertyComment" formControlName="comment" rows="1"></textarea>
</div>
<div class="mb-3">
<label for="exampleValue" class="form-label">Example value</label>
@if (isLongExampleValue()) {
<textarea class="form-control" id="exampleValue" formControlName="exampleValue" rows="1"></textarea>
} @else {
<input type="text" class="form-control" id="exampleValue" formControlName="exampleValue" />
}
</div>

<div class="mb-3">
<label for="tags" class="form-label">Tags</label>
<app-tag-input [tags]="tags()" [canEdit]="canEdit()" (tagsChange)="onTagsChange($event)"></app-tag-input>
</div>
</form>
}
<div class="mb-3">
<label for="propertyComment" class="form-label">Comment</label>
<textarea class="form-control" id="propertyComment" formControlName="comment" rows="1"></textarea>
</div>

<div class="mb-3">
<label for="tags" class="form-label">Tags</label>
<app-tag-input [tags]="tags()" [canEdit]="canEdit()" (tagsChange)="onTagsChange($event)"></app-tag-input>
</div>
</form>
</div>
<div class="modal-footer">
<div class="flex-grow-1"></div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { TagInputComponent } from '../../shared/tag-input/tag-input.component';
import { map, startWith } from 'rxjs/operators';
import { toSignal } from '@angular/core/rxjs-interop';
import { ButtonComponent } from '../../shared/button/button.component';
import { LoadingIndicatorComponent } from '../../shared/elements/loading-indicator.component';

interface PropertyDescriptorForm {
name: FormControl<string>;
Expand All @@ -30,6 +31,7 @@ interface PropertyDescriptorForm {
selector: 'app-property-edit',
imports: [
ButtonComponent,
LoadingIndicatorComponent,
ModalHeaderComponent,
NgOptionComponent,
NgSelectComponent,
Expand Down Expand Up @@ -81,20 +83,20 @@ export class PropertyEditComponent {
isNewMode = computed(() => !this.propertyDescriptor()?.id);
title = computed(() => (this.isNewMode() ? 'New Property Descriptor' : 'Edit Property Descriptor'));

isLongDefaultValue = computed(() => {
isLongDefaultValue(): boolean {
const val = this.form.controls.defaultValue.value;
return val && val.length > 70;
});
return !!val && val.length > 70;
}

isLongExampleValue = computed(() => {
isLongExampleValue(): boolean {
const val = this.form.controls.exampleValue.value;
return val && val.length > 70;
});
return !!val && val.length > 70;
}

isLongMik = computed(() => {
isLongMik(): boolean {
const val = this.form.controls.mik.value;
return val && val.length > 70;
});
return !!val && val.length > 70;
}

constructor() {
effect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,24 +93,30 @@ export class PropertyDeleteModalService {
return;
}

// Extract error message from backend ExceptionDto structure
const errorMessage = err.error?.message || err.message || '';

// Check for 409 Conflict status or error message indicating force delete is needed
const needsForceDelete =
err.status === 409 ||
err.message?.includes('marked to be deleted') ||
err.message?.includes('still in use') ||
err.message?.includes('cannot be deleted');
errorMessage.includes('marked to be deleted') ||
errorMessage.includes('still in use') ||
errorMessage.includes('cannot be deleted') ||
errorMessage.includes('force the deletion');

if (needsForceDelete) {
// Update descriptor with error message to show force delete option
// Modal stays open to allow user to confirm force delete
const descriptor = this.descriptorToDelete();
if (descriptor) {
const errorMsg =
err.error?.message || err.message || 'This property descriptor is still in use and cannot be deleted.';
this.descriptorToDelete.set({ ...descriptor, errorMessage: errorMsg });
this.descriptorToDelete.set({
...descriptor,
errorMessage: errorMessage || 'This property descriptor is still in use and cannot be deleted.'
});
}
} else {
this.toastService.error(
'Failed to delete property descriptor: ' + (err.error?.message || err.message || 'Unknown error'),
'Failed to delete property descriptor: ' + (errorMessage || 'Unknown error'),
);
modal.close();
}
Expand Down
Loading