TSK-1647: Implemented global frontend error handling using backend error keys
Co-authored-by: Tristan Eisermann<19949441+Tristan2357@users.noreply.github.com> Co-authored-by: Tim Gerversmann<72377965+tge20@users.noreply.github.com> Co-authored-by: Sofie Hofmann<29145005+sofie29@users.noreply.github.com>
This commit is contained in:
parent
34d2bbfa92
commit
8edb488bd3
|
@ -43,12 +43,13 @@ class ServiceLevelHandler {
|
|||
ServiceLevelHandler(
|
||||
InternalTaskanaEngine taskanaEngine,
|
||||
TaskMapper taskMapper,
|
||||
AttachmentMapper attachmentMapper) {
|
||||
AttachmentMapper attachmentMapper,
|
||||
TaskServiceImpl taskServiceImpl) {
|
||||
this.taskanaEngine = taskanaEngine;
|
||||
this.taskMapper = taskMapper;
|
||||
this.attachmentMapper = attachmentMapper;
|
||||
converter = taskanaEngine.getEngine().getWorkingDaysToDaysConverter();
|
||||
taskServiceImpl = (TaskServiceImpl) taskanaEngine.getEngine().getTaskService();
|
||||
this.taskServiceImpl = taskServiceImpl;
|
||||
}
|
||||
|
||||
// use the same algorithm as setPlannedPropertyOfTasksImpl to refresh
|
||||
|
|
|
@ -113,7 +113,8 @@ public class TaskServiceImpl implements TaskService {
|
|||
this.createTaskPreprocessorManager = taskanaEngine.getCreateTaskPreprocessorManager();
|
||||
this.taskTransferrer = new TaskTransferrer(taskanaEngine, taskMapper, this);
|
||||
this.taskCommentService = new TaskCommentServiceImpl(taskanaEngine, taskCommentMapper, this);
|
||||
this.serviceLevelHandler = new ServiceLevelHandler(taskanaEngine, taskMapper, attachmentMapper);
|
||||
this.serviceLevelHandler =
|
||||
new ServiceLevelHandler(taskanaEngine, taskMapper, attachmentMapper, this);
|
||||
this.attachmentHandler = new AttachmentHandler(attachmentMapper, classificationService);
|
||||
}
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ context('TASKANA Workbaskets', () => {
|
|||
cy.wait(Cypress.env('dropdownWait'));
|
||||
cy.get('mat-option').contains('Clearance').click();
|
||||
cy.saveWorkbaskets();
|
||||
cy.wait(3050); //wait for toasts to disappear
|
||||
|
||||
// assure that its Clearance now
|
||||
cy.get('mat-form-field').contains('mat-form-field', 'Type').contains('Clearance').should('be.visible');
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
"@circlon/angular-tree-component": "11.0.4",
|
||||
"@ngxs/store": "3.7.2",
|
||||
"angular-svg-icon": "12.0.0",
|
||||
"@ngneat/hot-toast": "3.1.0",
|
||||
"@ngneat/overview": "2.0.2",
|
||||
"chart.js": "2.9.4",
|
||||
"core-js": "3.15.1",
|
||||
"file-saver": "2.0.5",
|
||||
|
|
|
@ -12,7 +12,6 @@ import { ClassificationCategoriesService } from '../../../shared/services/classi
|
|||
import { AccessItemsManagementState } from '../../../shared/store/access-items-management-store/access-items-management.state';
|
||||
import { Observable } from 'rxjs';
|
||||
import { GetAccessItems } from '../../../shared/store/access-items-management-store/access-items-management.actions';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||
import { TypeAheadComponent } from '../../../shared/components/type-ahead/type-ahead.component';
|
||||
import { TypeaheadModule } from 'ngx-bootstrap/typeahead';
|
||||
|
@ -78,7 +77,6 @@ describe('AccessItemsManagementComponent', () => {
|
|||
NgxsModule.forRoot([EngineConfigurationState, AccessItemsManagementState]),
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatSnackBarModule,
|
||||
MatDialogModule,
|
||||
TypeaheadModule.forRoot(),
|
||||
BrowserAnimationsModule,
|
||||
|
|
|
@ -145,7 +145,8 @@ export class AccessItemsManagementComponent implements OnInit {
|
|||
|
||||
revokeAccess() {
|
||||
this.notificationService.showDialog(
|
||||
`You are going to delete all access related: ${this.accessId.accessId}. Can you confirm this action?`,
|
||||
'ACCESS_ITEM_MANAGEMENT_REVOKE_ACCESS',
|
||||
{ accessId: this.accessId.accessId },
|
||||
() => {
|
||||
this.store
|
||||
.dispatch(new RemoveAccessItemsPermissions(this.accessId.accessId))
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
<mat-icon class="action-toolbar__aquamarine-button">content_copy</mat-icon>
|
||||
<span>Copy</span>
|
||||
</button>
|
||||
<button mat-menu-item class="action-toolbar__dropdown" matTooltip="Delete this classification" (click)="onRemoveClassification()">
|
||||
<button *ngIf="this.classification?.classificationId" mat-menu-item class="action-toolbar__dropdown" matTooltip="Delete this classification" (click)="onRemoveClassification()">
|
||||
<mat-icon class="action-toolbar__red-button">delete</mat-icon>
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
|
|
|
@ -90,9 +90,9 @@ const formsValidatorServiceSpy: Partial<FormsValidatorService> = {
|
|||
};
|
||||
|
||||
const notificationServiceSpy: Partial<NotificationService> = {
|
||||
showToast: jest.fn().mockReturnValue(of()),
|
||||
showDialog: jest.fn().mockReturnValue(of()),
|
||||
triggerError: jest.fn().mockReturnValue(of())
|
||||
showWarning: jest.fn().mockReturnValue(of()),
|
||||
showSuccess: jest.fn().mockReturnValue(of()),
|
||||
showDialog: jest.fn().mockReturnValue(of())
|
||||
};
|
||||
|
||||
describe('ClassificationDetailsComponent', () => {
|
||||
|
@ -157,9 +157,9 @@ describe('ClassificationDetailsComponent', () => {
|
|||
it('should show warning when onCopy() is called and isCreatingNewClassification is true', () => {
|
||||
component.isCreatingNewClassification = true;
|
||||
const notificationService = TestBed.inject(NotificationService);
|
||||
const showToastSpy = jest.spyOn(notificationService, 'showToast');
|
||||
const showWarningSpy = jest.spyOn(notificationService, 'showWarning');
|
||||
component.onCopy();
|
||||
expect(showToastSpy).toHaveBeenCalled();
|
||||
expect(showWarningSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should dispatch action when onCopy() is called and isCreatingNewClassification is false', async () => {
|
||||
|
@ -203,22 +203,6 @@ describe('ClassificationDetailsComponent', () => {
|
|||
expect(isActionDispatched).toBe(true);
|
||||
});
|
||||
|
||||
it('should trigger an error in removeClassificationConfirmation() when classification does not exist', () => {
|
||||
component.classification = undefined;
|
||||
const notificationService = TestBed.inject(NotificationService);
|
||||
const triggerErrorSpy = jest.spyOn(notificationService, 'triggerError');
|
||||
component.removeClassificationConfirmation();
|
||||
expect(triggerErrorSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should trigger an error in removeClassificationConfirmation() when classificationId does not exist', () => {
|
||||
component.classification = { key: 'Key01' };
|
||||
const notificationService = TestBed.inject(NotificationService);
|
||||
const triggerErrorSpy = jest.spyOn(notificationService, 'triggerError');
|
||||
component.removeClassificationConfirmation();
|
||||
expect(triggerErrorSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should dispatch action in removeClassificationConfirmation() when classification and classificationId exist', () => {
|
||||
component.classification = { classificationId: 'ID01' };
|
||||
const requestInProgressService = TestBed.inject(RequestInProgressService);
|
||||
|
@ -298,6 +282,16 @@ describe('ClassificationDetailsComponent', () => {
|
|||
expect(buttonsInDropdown.length).toEqual(3);
|
||||
});
|
||||
|
||||
it('should not show delete button when creating or copying a Classification', () => {
|
||||
component.classification.classificationId = null;
|
||||
const button = debugElement.nativeElement.querySelector('#action-toolbar__more-buttons');
|
||||
expect(button).toBeTruthy();
|
||||
button.click();
|
||||
fixture.detectChanges();
|
||||
const buttonsInDropdown = debugElement.queryAll(By.css('.action-toolbar__dropdown'));
|
||||
expect(buttonsInDropdown.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should call onCopy() when button is clicked', () => {
|
||||
const button = debugElement.nativeElement.querySelector('#action-toolbar__more-buttons');
|
||||
expect(button).toBeTruthy();
|
||||
|
|
|
@ -14,7 +14,6 @@ import { map, take, takeUntil } from 'rxjs/operators';
|
|||
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
|
||||
import { ClassificationSelectors } from 'app/shared/store/classification-store/classification.selectors';
|
||||
import { Location } from '@angular/common';
|
||||
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
|
||||
import { NotificationService } from '../../../shared/services/notifications/notification.service';
|
||||
import { ClassificationCategoryImages, CustomField, getCustomFields } from '../../../shared/models/customisation';
|
||||
import { Classification } from '../../../shared/models/classification';
|
||||
|
@ -121,13 +120,13 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
|||
.dispatch(new RestoreSelectedClassification(this.classification.classificationId))
|
||||
.pipe(take(1))
|
||||
.subscribe(() => {
|
||||
this.notificationsService.showToast(NOTIFICATION_TYPES.INFO_ALERT);
|
||||
this.notificationsService.showSuccess('CLASSIFICATION_RESTORE');
|
||||
});
|
||||
}
|
||||
|
||||
onCopy() {
|
||||
if (this.isCreatingNewClassification) {
|
||||
this.notificationsService.showToast(NOTIFICATION_TYPES.WARNING_CANT_COPY);
|
||||
this.notificationsService.showWarning('CLASSIFICATION_COPY_NOT_CREATED');
|
||||
} else {
|
||||
this.store.dispatch(new CopyClassification());
|
||||
}
|
||||
|
@ -165,27 +164,20 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
|||
this.store
|
||||
.dispatch(new SaveCreatedClassification(this.classification))
|
||||
.pipe(take(1))
|
||||
.subscribe(
|
||||
(store) => {
|
||||
this.notificationsService.showToast(
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_2,
|
||||
new Map<string, string>([['classificationKey', store.classification.selectedClassification.key]])
|
||||
);
|
||||
this.location.go(
|
||||
this.location
|
||||
.path()
|
||||
.replace(
|
||||
/(classifications).*/g,
|
||||
`classifications/(detail:${store.classification.selectedClassification.classificationId})`
|
||||
)
|
||||
);
|
||||
this.afterRequest();
|
||||
},
|
||||
(error) => {
|
||||
this.notificationsService.triggerError(NOTIFICATION_TYPES.CREATE_ERR, error);
|
||||
this.afterRequest();
|
||||
}
|
||||
);
|
||||
.subscribe((store) => {
|
||||
this.notificationsService.showSuccess('CLASSIFICATION_CREATE', {
|
||||
classificationKey: store.classification.selectedClassification.key
|
||||
});
|
||||
this.location.go(
|
||||
this.location
|
||||
.path()
|
||||
.replace(
|
||||
/(classifications).*/g,
|
||||
`classifications/(detail:${store.classification.selectedClassification.classificationId})`
|
||||
)
|
||||
);
|
||||
this.afterRequest();
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
this.store
|
||||
|
@ -193,13 +185,11 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
|||
.pipe(take(1))
|
||||
.subscribe(() => {
|
||||
this.afterRequest();
|
||||
this.notificationsService.showToast(
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_3,
|
||||
new Map<string, string>([['classificationKey', this.classification.key]])
|
||||
);
|
||||
this.notificationsService.showSuccess('CLASSIFICATION_UPDATE', {
|
||||
classificationKey: this.classification.key
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
this.notificationsService.triggerError(NOTIFICATION_TYPES.SAVE_ERR, error);
|
||||
this.afterRequest();
|
||||
}
|
||||
}
|
||||
|
@ -207,26 +197,20 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
|||
|
||||
onRemoveClassification() {
|
||||
this.notificationsService.showDialog(
|
||||
`You are going to delete classification: ${this.classification.key}. Can you confirm this action?`,
|
||||
'CLASSIFICATION_DELETE',
|
||||
{ classificationKey: this.classification.key },
|
||||
this.removeClassificationConfirmation.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
removeClassificationConfirmation() {
|
||||
if (!this.classification || !this.classification.classificationId) {
|
||||
this.notificationsService.triggerError(NOTIFICATION_TYPES.SELECT_ERR);
|
||||
return;
|
||||
}
|
||||
this.requestInProgressService.setRequestInProgress(true);
|
||||
|
||||
this.store
|
||||
.dispatch(new RemoveSelectedClassification())
|
||||
.pipe(take(1))
|
||||
.subscribe(() => {
|
||||
this.notificationsService.showToast(
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_4,
|
||||
new Map<string, string>([['classificationKey', this.classification.key]])
|
||||
);
|
||||
this.notificationsService.showSuccess('CLASSIFICATION_REMOVE', { classificationKey: this.classification.key });
|
||||
this.afterRequest();
|
||||
});
|
||||
this.location.go(this.location.path().replace(/(classifications).*/g, 'classifications'));
|
||||
|
|
|
@ -42,8 +42,7 @@ xdescribe('ImportExportComponent', () => {
|
|||
const notificationServiceSpy = jest.fn().mockImplementation(
|
||||
(): Partial<NotificationService> => ({
|
||||
showDialog: showDialogFn,
|
||||
showToast: showDialogFn,
|
||||
triggerError: showDialogFn
|
||||
showSuccess: showDialogFn
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -6,8 +6,6 @@ import { TaskanaType } from 'app/shared/models/taskana-type';
|
|||
import { environment } from 'environments/environment';
|
||||
import { UploadService } from 'app/shared/services/upload/upload.service';
|
||||
import { ImportExportService } from 'app/administration/services/import-export.service';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
|
||||
import { NotificationService } from '../../../shared/services/notifications/notification.service';
|
||||
|
||||
@Component({
|
||||
|
@ -23,15 +21,13 @@ export class ImportExportComponent implements OnInit {
|
|||
selectedFileInput;
|
||||
|
||||
domains: string[] = [];
|
||||
errorWhileUploadingText: string;
|
||||
|
||||
constructor(
|
||||
private domainService: DomainService,
|
||||
private workbasketDefinitionService: WorkbasketDefinitionService,
|
||||
private classificationDefinitionService: ClassificationDefinitionService,
|
||||
private notificationsService: NotificationService,
|
||||
public uploadService: UploadService,
|
||||
private errorsService: NotificationService,
|
||||
private notificationService: NotificationService,
|
||||
private importExportService: ImportExportService
|
||||
) {}
|
||||
|
||||
|
@ -85,7 +81,7 @@ export class ImportExportComponent implements OnInit {
|
|||
check = true;
|
||||
} else {
|
||||
file.value = '';
|
||||
this.errorsService.triggerError(NOTIFICATION_TYPES.FILE_ERR);
|
||||
this.notificationService.showError('IMPORT_EXPORT_UPLOAD_FILE_FORMAT');
|
||||
}
|
||||
return check;
|
||||
}
|
||||
|
@ -98,32 +94,32 @@ export class ImportExportComponent implements OnInit {
|
|||
|
||||
private onReadyStateChangeHandler(event) {
|
||||
if (event.readyState === 4 && event.status >= 400) {
|
||||
let title;
|
||||
let key: NOTIFICATION_TYPES;
|
||||
let key = 'FALLBACK';
|
||||
|
||||
if (event.status === 401) {
|
||||
key = NOTIFICATION_TYPES.IMPORT_ERR_1;
|
||||
title = 'Import was not successful, you have no access to apply this operation.';
|
||||
key = 'IMPORT_EXPORT_UPLOAD_FAILED_AUTH';
|
||||
} else if (event.status === 404) {
|
||||
key = NOTIFICATION_TYPES.IMPORT_ERR_2;
|
||||
key = 'IMPORT_EXPORT_UPLOAD_FAILED_NOT_FOUND';
|
||||
} else if (event.status === 409) {
|
||||
key = NOTIFICATION_TYPES.IMPORT_ERR_3;
|
||||
key = 'IMPORT_EXPORT_UPLOAD_FAILED_CONFLICTS';
|
||||
} else if (event.status === 413) {
|
||||
key = NOTIFICATION_TYPES.IMPORT_ERR_4;
|
||||
key = 'IMPORT_EXPORT_UPLOAD_FAILED_SIZE';
|
||||
}
|
||||
this.errorHandler(key, event);
|
||||
} else if (event.readyState === 4 && event.status === 200) {
|
||||
this.notificationsService.showToast(NOTIFICATION_TYPES.SUCCESS_ALERT_6);
|
||||
this.errorHandler(key);
|
||||
} else if (event.readyState === 4 && event.status === 204) {
|
||||
const message = this.currentSelection === TaskanaType.WORKBASKETS ? 'WORKBASKET_IMPORT' : 'CLASSIFICATION_IMPORT';
|
||||
this.notificationService.showSuccess(message);
|
||||
this.importExportService.setImportingFinished(true);
|
||||
this.resetProgress();
|
||||
}
|
||||
}
|
||||
|
||||
private onFailedResponse() {
|
||||
this.errorHandler(NOTIFICATION_TYPES.UPLOAD_ERR);
|
||||
this.errorHandler('IMPORT_EXPORT_UPLOAD_FAILED');
|
||||
}
|
||||
|
||||
private errorHandler(key: NOTIFICATION_TYPES, passedError?: HttpErrorResponse) {
|
||||
this.errorsService.triggerError(key, passedError);
|
||||
private errorHandler(key: string) {
|
||||
this.notificationService.showError(key);
|
||||
delete this.selectedFileInput.files;
|
||||
this.resetProgress();
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import { Select, Store } from '@ngxs/store';
|
|||
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
|
||||
|
||||
import { Location } from '@angular/common';
|
||||
import { NOTIFICATION_TYPES } from 'app/shared/models/notifications';
|
||||
import { NotificationService } from 'app/shared/services/notifications/notification.service';
|
||||
import { Classification } from '../../../shared/models/classification';
|
||||
import { ClassificationsService } from '../../../shared/services/classifications/classifications.service';
|
||||
|
@ -246,10 +245,7 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
|
|||
|
||||
private updateClassification(classification: Classification) {
|
||||
this.store.dispatch(new UpdateClassification(classification)).subscribe(() => {
|
||||
this.notificationsService.showToast(
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_5,
|
||||
new Map<string, string>([['classificationKey', classification.key]])
|
||||
);
|
||||
this.notificationsService.showSuccess('CLASSIFICATION_MOVE', { classificationKey: classification.key });
|
||||
this.switchTaskanaSpinner(false);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -51,8 +51,7 @@ const requestInProgressServiceSpy: Partial<RequestInProgressService> = {
|
|||
|
||||
const showDialogFn = jest.fn().mockReturnValue(true);
|
||||
const notificationServiceSpy: Partial<NotificationService> = {
|
||||
triggerError: showDialogFn,
|
||||
showToast: showDialogFn
|
||||
showSuccess: showDialogFn
|
||||
};
|
||||
|
||||
const validateFormInformationFn = jest.fn().mockImplementation((): Promise<any> => Promise.resolve(true));
|
||||
|
|
|
@ -22,7 +22,6 @@ import { FormsValidatorService } from 'app/shared/services/forms-validator/forms
|
|||
import { AccessIdDefinition } from 'app/shared/models/access-id';
|
||||
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
|
||||
import { filter, take, takeUntil, tap } from 'rxjs/operators';
|
||||
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
|
||||
import { NotificationService } from '../../../shared/services/notifications/notification.service';
|
||||
import { AccessItemsCustomisation, CustomField, getCustomFields } from '../../../shared/models/customisation';
|
||||
import {
|
||||
|
@ -272,7 +271,7 @@ export class WorkbasketAccessItemsComponent implements OnInit, OnChanges, OnDest
|
|||
this.AccessItemsForm.reset();
|
||||
this.setAccessItemsGroups(this.accessItemsResetClone);
|
||||
this.accessItemsClone = this.cloneAccessItems(this.accessItemsResetClone);
|
||||
this.notificationsService.showToast(NOTIFICATION_TYPES.INFO_ALERT);
|
||||
this.notificationsService.showSuccess('WORKBASKET_ACCESS_ITEM_RESTORE');
|
||||
}
|
||||
|
||||
isFieldValid(field: string, index: number): boolean {
|
||||
|
|
|
@ -13,7 +13,6 @@ import { WorkbasketService } from '../../../shared/services/workbasket/workbaske
|
|||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { RequestInProgressService } from '../../../shared/services/request-in-progress/request-in-progress.service';
|
||||
import { SelectedRouteService } from '../../../shared/services/selected-route/selected-route';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { selectedWorkbasketMock, workbasketReadStateMock } from '../../../shared/store/mock-data/mock-store';
|
||||
import { StartupService } from '../../../shared/services/startup/startup.service';
|
||||
|
@ -83,7 +82,6 @@ describe('WorkbasketDetailsComponent', () => {
|
|||
NgxsModule.forRoot([WorkbasketState]),
|
||||
HttpClientTestingModule,
|
||||
RouterTestingModule.withRoutes([]),
|
||||
MatSnackBarModule,
|
||||
MatDialogModule,
|
||||
MatIconModule,
|
||||
MatProgressBarModule,
|
||||
|
|
|
@ -47,7 +47,7 @@ const workbasketServiceSpy: Partial<WorkbasketService> = {
|
|||
};
|
||||
|
||||
const notificationsServiceSpy: Partial<NotificationService> = {
|
||||
showToast: jest.fn().mockReturnValue(true)
|
||||
showSuccess: jest.fn().mockReturnValue(true)
|
||||
};
|
||||
const requestInProgressServiceSpy: Partial<RequestInProgressService> = {
|
||||
setRequestInProgress: jest.fn().mockReturnValue(of())
|
||||
|
|
|
@ -8,7 +8,6 @@ import { WorkbasketDistributionTargets } from 'app/shared/models/workbasket-dist
|
|||
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
|
||||
import { Actions, ofActionCompleted, Select, Store } from '@ngxs/store';
|
||||
import { filter, take, takeUntil } from 'rxjs/operators';
|
||||
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
|
||||
import { NotificationService } from '../../../shared/services/notifications/notification.service';
|
||||
import {
|
||||
GetAvailableDistributionTargets,
|
||||
|
@ -294,7 +293,7 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy
|
|||
}
|
||||
|
||||
onClear() {
|
||||
this.notificationsService.showToast(NOTIFICATION_TYPES.INFO_ALERT);
|
||||
this.notificationsService.showSuccess('WORKBASKET_DISTRIBUTION_TARGET_RESTORE');
|
||||
this.availableDistributionTargets = Object.assign([], this.availableDistributionTargetsUndoClone);
|
||||
this.availableDistributionTargetsFilterClone = Object.assign([], this.availableDistributionTargetsUndoClone);
|
||||
this.selectedDistributionTargets = Object.assign([], this.selectedDistributionTargetsUndoClone);
|
||||
|
|
|
@ -12,7 +12,6 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
|
|||
import { RequestInProgressService } from '../../../shared/services/request-in-progress/request-in-progress.service';
|
||||
import { FormsValidatorService } from '../../../shared/services/forms-validator/forms-validator.service';
|
||||
import { NotificationService } from '../../../shared/services/notifications/notification.service';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { EngineConfigurationState } from '../../../shared/store/engine-configuration-store/engine-configuration.state';
|
||||
import { WorkbasketState } from '../../../shared/store/workbasket-store/workbasket.state';
|
||||
|
@ -79,8 +78,7 @@ const formValidatorServiceMock: Partial<FormsValidatorService> = {
|
|||
const showDialogFn = jest.fn().mockReturnValue(true);
|
||||
const notificationServiceMock: Partial<NotificationService> = {
|
||||
showDialog: showDialogFn,
|
||||
showToast: showDialogFn,
|
||||
triggerError: showDialogFn
|
||||
showSuccess: showDialogFn
|
||||
};
|
||||
|
||||
describe('WorkbasketInformationComponent', () => {
|
||||
|
@ -95,7 +93,6 @@ describe('WorkbasketInformationComponent', () => {
|
|||
imports: [
|
||||
FormsModule,
|
||||
HttpClientTestingModule,
|
||||
MatSnackBarModule,
|
||||
MatDialogModule,
|
||||
NgxsModule.forRoot([EngineConfigurationState, WorkbasketState]),
|
||||
TypeaheadModule.forRoot(),
|
||||
|
@ -171,9 +168,9 @@ describe('WorkbasketInformationComponent', () => {
|
|||
it('should reset workbasket information when onUndo is called', () => {
|
||||
component.workbasketClone = selectedWorkbasketMock;
|
||||
const notificationService = TestBed.inject(NotificationService);
|
||||
const toastSpy = jest.spyOn(notificationService, 'showToast');
|
||||
const showSuccessSpy = jest.spyOn(notificationService, 'showSuccess');
|
||||
component.onUndo();
|
||||
expect(toastSpy).toHaveBeenCalled();
|
||||
expect(showSuccessSpy).toHaveBeenCalled();
|
||||
expect(component.workbasket).toMatchObject(component.workbasketClone);
|
||||
});
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import { RequestInProgressService } from 'app/shared/services/request-in-progres
|
|||
import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service';
|
||||
import { filter, map, takeUntil } from 'rxjs/operators';
|
||||
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
|
||||
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
|
||||
import { NotificationService } from '../../../shared/services/notifications/notification.service';
|
||||
import { CustomField, getCustomFields, WorkbasketsCustomisation } from '../../../shared/models/customisation';
|
||||
import {
|
||||
|
@ -136,13 +135,14 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
|
|||
|
||||
onUndo() {
|
||||
this.formsValidatorService.formSubmitAttempt = false;
|
||||
this.notificationService.showToast(NOTIFICATION_TYPES.INFO_ALERT);
|
||||
this.notificationService.showSuccess('WORKBASKET_RESTORE');
|
||||
this.workbasket = { ...this.workbasketClone };
|
||||
}
|
||||
|
||||
removeWorkbasket() {
|
||||
this.notificationService.showDialog(
|
||||
`You are going to delete workbasket: ${this.workbasket.key}. Can you confirm this action?`,
|
||||
'WORKBASKET_DELETE',
|
||||
{ workbasketKey: this.workbasket.key },
|
||||
this.onRemoveConfirmed.bind(this)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import { Direction, Sorting, WorkbasketQuerySortParameter } from '../../../share
|
|||
import { ACTION } from '../../../shared/models/action';
|
||||
import { TaskanaType } from '../../../shared/models/taskana-type';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { RequestInProgressService } from '../../../shared/services/request-in-progress/request-in-progress.service';
|
||||
|
@ -59,7 +58,6 @@ describe('WorkbasketListToolbarComponent', () => {
|
|||
NgxsModule.forRoot([WorkbasketState]),
|
||||
BrowserAnimationsModule,
|
||||
MatIconModule,
|
||||
MatSnackBarModule,
|
||||
MatDialogModule
|
||||
],
|
||||
declarations: [WorkbasketListToolbarComponent, ImportExportStub, SortStub, FilterStub],
|
||||
|
|
|
@ -5,7 +5,6 @@ import { Actions, NgxsModule, ofActionDispatched, Store } from '@ngxs/store';
|
|||
import { Observable, of } from 'rxjs';
|
||||
import { WorkbasketState } from '../../../shared/store/workbasket-store/workbasket.state';
|
||||
import { WorkbasketService } from '../../../shared/services/workbasket/workbasket.service';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { OrientationService } from '../../../shared/services/orientation/orientation.service';
|
||||
import { ImportExportService } from '../../services/import-export.service';
|
||||
|
@ -95,7 +94,6 @@ describe('WorkbasketListComponent', () => {
|
|||
imports: [
|
||||
NgxsModule.forRoot([WorkbasketState]),
|
||||
RouterTestingModule,
|
||||
MatSnackBarModule,
|
||||
MatDialogModule,
|
||||
FormsModule,
|
||||
MatProgressBarModule,
|
||||
|
|
|
@ -22,8 +22,7 @@ import { take } from 'rxjs/operators';
|
|||
|
||||
const showDialogFn = jest.fn().mockReturnValue(true);
|
||||
const NotificationServiceSpy: Partial<NotificationService> = {
|
||||
triggerError: showDialogFn,
|
||||
showToast: showDialogFn
|
||||
showSuccess: showDialogFn
|
||||
};
|
||||
|
||||
const domainServiceSpy: Partial<DomainService> = {
|
||||
|
|
|
@ -7,7 +7,6 @@ import { RequestInProgressService } from './shared/services/request-in-progress/
|
|||
import { OrientationService } from './shared/services/orientation/orientation.service';
|
||||
import { SelectedRouteService } from './shared/services/selected-route/selected-route';
|
||||
import { UploadService } from './shared/services/upload/upload.service';
|
||||
import { ErrorModel } from './shared/models/error-model';
|
||||
import { TaskanaEngineService } from './shared/services/taskana-engine/taskana-engine.service';
|
||||
import { WindowRefService } from 'app/shared/services/window/window.service';
|
||||
import { environment } from 'environments/environment';
|
||||
|
@ -26,7 +25,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
requestInProgress = false;
|
||||
currentProgressValue = 0;
|
||||
|
||||
error: ErrorModel;
|
||||
version: string;
|
||||
toggle: boolean = false;
|
||||
|
||||
|
|
|
@ -1,17 +1,7 @@
|
|||
<h4 class="modal-title" mat-dialog-title id="errorModalLabel">{{title}}</h4>
|
||||
<div mat-dialog-content class="modal-body">
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<span class="material-icons md-20" data-toggle="tooltip">error</span>
|
||||
<span *ngIf="!isDialog" class="sr-only">Error:</span>
|
||||
{{message}}
|
||||
</div>
|
||||
</div>
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-dialog-close mat-stroked-button data-dismiss="modal" type="button">
|
||||
<span data-toggle="tooltip" class="material-icons md-20 red">cancel</span>
|
||||
</button>
|
||||
<button *ngIf="isDialog" [mat-dialog-close]="callback" mat-raised-button color="primary"
|
||||
data-dismiss="modal" type="button">
|
||||
<span data-toggle="tooltip" class="material-icons md-20 white">done</span>
|
||||
</button>
|
||||
</div>
|
||||
<mat-dialog-content class="mat-typography">
|
||||
<h4> {{message}} </h4>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions style="justify-content: flex-end">
|
||||
<button mat-dialog-close mat-stroked-button> Cancel </button>
|
||||
<button *ngIf="isDataSpecified" [mat-dialog-close]="callback" mat-raised-button color="primary"> Delete </button>
|
||||
</mat-dialog-actions>
|
||||
|
|
|
@ -1,8 +1 @@
|
|||
.alert {
|
||||
color: #a94442;
|
||||
background-color: #f2dede;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #ebccd1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { notifications } from '../../models/notifications';
|
||||
import { ObtainMessageService } from '../../services/obtain-message/obtain-message.service';
|
||||
import { messageTypes } from '../../services/obtain-message/message-types';
|
||||
|
||||
@Component({
|
||||
selector: 'taskana-shared-dialog-pop-up',
|
||||
|
@ -8,43 +9,19 @@ import { notifications } from '../../models/notifications';
|
|||
styleUrls: ['./dialog-pop-up.component.scss']
|
||||
})
|
||||
export class DialogPopUpComponent implements OnInit {
|
||||
title: string;
|
||||
message: string;
|
||||
isDialog: false;
|
||||
callback: Function;
|
||||
isDataSpecified: boolean;
|
||||
|
||||
constructor(@Inject(MAT_DIALOG_DATA) private data: any) {}
|
||||
constructor(@Inject(MAT_DIALOG_DATA) private data: any, private obtainMessageService: ObtainMessageService) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.data) {
|
||||
this.isDialog = this.data.isDialog;
|
||||
if (this.isDialog) {
|
||||
this.initDialog();
|
||||
} else {
|
||||
this.initError();
|
||||
}
|
||||
this.isDataSpecified = this.data?.message && this.data?.callback;
|
||||
if (this.isDataSpecified) {
|
||||
this.message = this.data.message;
|
||||
this.callback = this.data.callback;
|
||||
} else {
|
||||
this.message = 'There was an error with this PopUp. \nPlease contact your administrator.';
|
||||
this.message = this.obtainMessageService.getMessage('POPUP_CONFIGURATION', {}, messageTypes.DIALOG);
|
||||
}
|
||||
}
|
||||
|
||||
initError() {
|
||||
this.title = notifications.get(this.data.key).left || '';
|
||||
this.message =
|
||||
notifications.get(this.data.key).right || (this.data && this.data.passedError && this.data.passedError.error)
|
||||
? this.data.passedError.error.message
|
||||
: '';
|
||||
if (this.data.additions) {
|
||||
this.data.additions.forEach((value: string, replacementKey: string) => {
|
||||
this.message = this.message.replace(`{${replacementKey}}`, value);
|
||||
this.title = this.title.replace(`{${replacementKey}}`, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
initDialog() {
|
||||
this.message = this.data.message;
|
||||
this.title = 'Please confirm your action';
|
||||
this.callback = this.data.callback;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Component, EventEmitter, Input, OnDestroy, Output, ViewChild } from '@angular/core';
|
||||
import { NOTIFICATION_TYPES } from '../../models/notifications';
|
||||
import { NotificationService } from '../../services/notifications/notification.service';
|
||||
|
||||
declare let $: any;
|
||||
|
@ -29,7 +28,7 @@ export class SpinnerComponent implements OnDestroy {
|
|||
@ViewChild('spinnerModal', { static: true })
|
||||
private modal;
|
||||
|
||||
constructor(private errorsService: NotificationService) {}
|
||||
constructor(private notificationService: NotificationService) {}
|
||||
|
||||
set isDelayedRunning(value: boolean) {
|
||||
this.showSpinner = value;
|
||||
|
@ -62,7 +61,7 @@ export class SpinnerComponent implements OnDestroy {
|
|||
this.isDelayedRunning = value;
|
||||
this.cancelTimeout();
|
||||
this.requestTimeout = setTimeout(() => {
|
||||
this.errorsService.triggerError(NOTIFICATION_TYPES.TIMEOUT_ERR);
|
||||
this.notificationService.showError('SPINNER_TIMEOUT');
|
||||
this.cancelTimeout();
|
||||
this.isRunning = false;
|
||||
}, this.maxRequestTimeout);
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
<span id="alert-icon" class="material-icons md-20">{{ type }}</span>
|
||||
{{ message }}
|
|
@ -1,35 +0,0 @@
|
|||
import { Component, Inject, Input, OnInit } from '@angular/core';
|
||||
import { MAT_SNACK_BAR_DATA } from '@angular/material/snack-bar';
|
||||
import { NOTIFICATION_TYPES, notifications } from '../../models/notifications';
|
||||
|
||||
@Component({
|
||||
selector: 'taskana-shared-toast',
|
||||
templateUrl: './toast.component.html',
|
||||
styleUrls: ['./toast.component.scss']
|
||||
})
|
||||
export class ToastComponent implements OnInit {
|
||||
message: string;
|
||||
type: string = 'info';
|
||||
|
||||
constructor(@Inject(MAT_SNACK_BAR_DATA) private data: any) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.data) {
|
||||
this.message = notifications.get(this.data.key).right;
|
||||
if (this.data.additions) {
|
||||
this.data.additions.forEach((value: string, replacementKey: string) => {
|
||||
this.message = this.message.replace(`{${replacementKey}}`, value);
|
||||
});
|
||||
}
|
||||
this.type = NOTIFICATION_TYPES[this.data.key].split('_')[0].toLowerCase();
|
||||
if (this.type === 'danger') {
|
||||
this.type = 'error';
|
||||
}
|
||||
if (this.type === 'success') {
|
||||
this.type = 'done';
|
||||
}
|
||||
} else {
|
||||
this.message = 'There was an error with this toast. \nPlease contact your administrator.';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,18 +3,15 @@ import { CanActivate } from '@angular/router';
|
|||
import { Injectable } from '@angular/core';
|
||||
import { DomainService } from 'app/shared/services/domain/domain.service';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { NotificationService } from '../services/notifications/notification.service';
|
||||
import { NOTIFICATION_TYPES } from '../models/notifications';
|
||||
|
||||
@Injectable()
|
||||
export class DomainGuard implements CanActivate {
|
||||
constructor(private domainService: DomainService, private errorsService: NotificationService) {}
|
||||
constructor(private domainService: DomainService) {}
|
||||
|
||||
canActivate() {
|
||||
return this.domainService.getDomains().pipe(
|
||||
map((domain) => true),
|
||||
map(() => true),
|
||||
catchError(() => {
|
||||
this.errorsService.triggerError(NOTIFICATION_TYPES.FETCH_ERR_5);
|
||||
return of(false);
|
||||
})
|
||||
);
|
||||
|
|
|
@ -3,18 +3,12 @@ import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from
|
|||
import { Observable, of } from 'rxjs';
|
||||
import { TaskanaEngineService } from 'app/shared/services/taskana-engine/taskana-engine.service';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { NOTIFICATION_TYPES } from '../models/notifications';
|
||||
import { NotificationService } from '../services/notifications/notification.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class HistoryGuard implements CanActivate {
|
||||
constructor(
|
||||
private taskanaEngineService: TaskanaEngineService,
|
||||
public router: Router,
|
||||
private errorsService: NotificationService
|
||||
) {}
|
||||
constructor(private taskanaEngineService: TaskanaEngineService, public router: Router) {}
|
||||
|
||||
canActivate(
|
||||
next: ActivatedRouteSnapshot,
|
||||
|
@ -28,7 +22,6 @@ export class HistoryGuard implements CanActivate {
|
|||
return this.navigateToWorkplace();
|
||||
}),
|
||||
catchError(() => {
|
||||
this.errorsService.triggerError(NOTIFICATION_TYPES.FETCH_ERR_6);
|
||||
return of(this.navigateToWorkplace());
|
||||
})
|
||||
);
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { NOTIFICATION_TYPES, notifications } from './notifications';
|
||||
|
||||
export class ErrorModel {
|
||||
public readonly errObj: HttpErrorResponse;
|
||||
public readonly title: string;
|
||||
public readonly message: string;
|
||||
|
||||
constructor(key: NOTIFICATION_TYPES, passedError?: HttpErrorResponse, addition?: Map<String, String>) {
|
||||
this.title = notifications.get(key).left;
|
||||
let messageTemp = notifications.get(key).right;
|
||||
this.errObj = passedError;
|
||||
if (addition) {
|
||||
addition.forEach((value: string, replacementKey: string) => {
|
||||
messageTemp = messageTemp.replace(`{${replacementKey}}`, value);
|
||||
this.title.replace(`{${replacementKey}}`, value);
|
||||
});
|
||||
}
|
||||
this.message = messageTemp;
|
||||
}
|
||||
}
|
|
@ -1,252 +0,0 @@
|
|||
import { Pair } from './pair';
|
||||
|
||||
export enum NOTIFICATION_TYPES {
|
||||
// ERRORS
|
||||
FETCH_ERR,
|
||||
FETCH_ERR_2,
|
||||
FETCH_ERR_3,
|
||||
FETCH_ERR_4,
|
||||
FETCH_ERR_5,
|
||||
FETCH_ERR_6,
|
||||
FETCH_ERR_7,
|
||||
DELETE_ERR,
|
||||
DELETE_ERR_2,
|
||||
CREATE_ERR,
|
||||
CREATE_ERR_2,
|
||||
REMOVE_ERR,
|
||||
REMOVE_ERR_2,
|
||||
SAVE_ERR,
|
||||
SAVE_ERR_2,
|
||||
SAVE_ERR_3,
|
||||
SAVE_ERR_4,
|
||||
SELECT_ERR,
|
||||
FILE_ERR,
|
||||
IMPORT_ERR_1,
|
||||
IMPORT_ERR_2,
|
||||
IMPORT_ERR_3,
|
||||
IMPORT_ERR_4,
|
||||
UPLOAD_ERR,
|
||||
TIMEOUT_ERR,
|
||||
GENERAL_ERR,
|
||||
ACCESS_ERR,
|
||||
MARK_ERR,
|
||||
|
||||
// ALERTS
|
||||
// currently their names are used as a way to determine the type of the alert
|
||||
// e.g. we extract from 'SUCCESS_ALERT_2' in notification.service, that this is a success alert
|
||||
// and should therefore have the color green, so please __keep this in mind when refactoring__
|
||||
// usages of this undocumented sideffect: notification.service.ts and toast.component.ts
|
||||
INFO_ALERT,
|
||||
INFO_ALERT_2,
|
||||
DANGER_ALERT,
|
||||
DANGER_ALERT_2,
|
||||
SUCCESS_ALERT,
|
||||
SUCCESS_ALERT_2,
|
||||
SUCCESS_ALERT_3,
|
||||
SUCCESS_ALERT_4,
|
||||
SUCCESS_ALERT_5,
|
||||
SUCCESS_ALERT_6,
|
||||
SUCCESS_ALERT_7,
|
||||
SUCCESS_ALERT_8,
|
||||
SUCCESS_ALERT_9,
|
||||
SUCCESS_ALERT_10,
|
||||
SUCCESS_ALERT_11,
|
||||
SUCCESS_ALERT_12,
|
||||
SUCCESS_ALERT_13,
|
||||
SUCCESS_ALERT_14,
|
||||
WARNING_ALERT,
|
||||
WARNING_ALERT_2,
|
||||
WARNING_CANT_COPY
|
||||
}
|
||||
|
||||
export const notifications = new Map<NOTIFICATION_TYPES, Pair<string, string>>([
|
||||
// access-items-management.component.ts
|
||||
[
|
||||
NOTIFICATION_TYPES.FETCH_ERR,
|
||||
{ left: 'There was an error while retrieving your access ids with groups.', right: '' }
|
||||
],
|
||||
// access-items-management.component.ts
|
||||
[NOTIFICATION_TYPES.FETCH_ERR_2, { left: 'There was an error while retrieving your access items ', right: '' }],
|
||||
// access-items-management.component.ts
|
||||
[NOTIFICATION_TYPES.DELETE_ERR, { left: "You can't delete a group", right: '' }],
|
||||
// classification-details.component
|
||||
[NOTIFICATION_TYPES.CREATE_ERR, { left: 'There was an error while creating this classification', right: '' }],
|
||||
// classification-details.component
|
||||
[NOTIFICATION_TYPES.REMOVE_ERR, { left: 'There was an error while removing your classification', right: '' }],
|
||||
// classification-details.component
|
||||
[NOTIFICATION_TYPES.SAVE_ERR, { left: 'There was an error while saving your classification', right: '' }],
|
||||
// classification-details.component
|
||||
[
|
||||
NOTIFICATION_TYPES.SELECT_ERR,
|
||||
{ left: 'There is no classification selected', right: 'Please check if you are creating a classification' }
|
||||
],
|
||||
// import-export.component
|
||||
[
|
||||
NOTIFICATION_TYPES.FILE_ERR,
|
||||
{ left: 'Wrong format', right: 'This file format is not allowed! Please use a .json file.' }
|
||||
],
|
||||
// import-export.component
|
||||
[
|
||||
NOTIFICATION_TYPES.IMPORT_ERR_1,
|
||||
{
|
||||
left: 'Import was not successful',
|
||||
right: 'Import was not successful, you have no access to apply this operation.'
|
||||
}
|
||||
],
|
||||
// import-export.component
|
||||
[
|
||||
NOTIFICATION_TYPES.IMPORT_ERR_2,
|
||||
{ left: 'Import was not successful', right: 'Import was not successful, operation was not found.' }
|
||||
],
|
||||
// import-export.component
|
||||
[
|
||||
NOTIFICATION_TYPES.IMPORT_ERR_3,
|
||||
{ left: 'Import was not successful', right: 'Import was not successful, operation has some conflicts.' }
|
||||
],
|
||||
// import-export.component
|
||||
[
|
||||
NOTIFICATION_TYPES.IMPORT_ERR_4,
|
||||
{ left: 'Import was not successful', right: 'Import was not successful, maximum file size exceeded.' }
|
||||
],
|
||||
// import-export.component
|
||||
[
|
||||
NOTIFICATION_TYPES.UPLOAD_ERR,
|
||||
{
|
||||
left: 'Upload failed',
|
||||
right: `The upload didn't proceed sucessfully.
|
||||
\n The uploaded file probably exceeded the maximum file size of 10 MB.`
|
||||
}
|
||||
],
|
||||
// task-details.component
|
||||
[NOTIFICATION_TYPES.FETCH_ERR_3, { left: '', right: 'An error occurred while fetching the task' }],
|
||||
// workbasket-details.component
|
||||
[NOTIFICATION_TYPES.FETCH_ERR_4, { left: '', right: 'An error occurred while fetching the workbasket' }],
|
||||
// access-items.component
|
||||
[
|
||||
NOTIFICATION_TYPES.SAVE_ERR_2,
|
||||
{ left: "There was an error while saving your workbasket's access items", right: '' }
|
||||
],
|
||||
// workbaskets-distribution-targets.component
|
||||
[
|
||||
NOTIFICATION_TYPES.SAVE_ERR_3,
|
||||
{ left: "There was an error while saving your workbasket's distribution targets", right: '' }
|
||||
],
|
||||
// workbasket-information.component
|
||||
[
|
||||
NOTIFICATION_TYPES.REMOVE_ERR_2,
|
||||
{ left: 'There was an error removing distribution target for {workbasketId}.', right: '' }
|
||||
],
|
||||
// workbasket-information.component
|
||||
[NOTIFICATION_TYPES.SAVE_ERR_4, { left: 'There was an error while saving your workbasket', right: '' }],
|
||||
// workbasket-information.component
|
||||
[NOTIFICATION_TYPES.CREATE_ERR_2, { left: 'There was an error while creating this workbasket', right: '' }],
|
||||
// workbasket-information.component
|
||||
[
|
||||
NOTIFICATION_TYPES.MARK_ERR,
|
||||
{
|
||||
left: 'Workbasket was marked for deletion.',
|
||||
right:
|
||||
'The Workbasket {workbasketId} still contains completed tasks and could not be deleted.' +
|
||||
' Instead is was marked for deletion and will be deleted automatically ' +
|
||||
'as soon as the completed tasks are cleared from the database.'
|
||||
}
|
||||
],
|
||||
// domain.guard
|
||||
[
|
||||
NOTIFICATION_TYPES.FETCH_ERR_5,
|
||||
{ left: 'There was an error, please contact your administrator', right: 'There was an error getting Domains' }
|
||||
],
|
||||
// history.guard
|
||||
[
|
||||
NOTIFICATION_TYPES.FETCH_ERR_6,
|
||||
{
|
||||
left: 'There was an error, please contact your administrator',
|
||||
right: 'There was an error getting history provider'
|
||||
}
|
||||
],
|
||||
// http-client-interceptor.service
|
||||
[NOTIFICATION_TYPES.ACCESS_ERR, { left: 'You have no access to this resource', right: '' }],
|
||||
// http-client-interceptor.service
|
||||
[NOTIFICATION_TYPES.GENERAL_ERR, { left: 'There was an error, please contact your administrator', right: '' }],
|
||||
// spinner.component
|
||||
[
|
||||
NOTIFICATION_TYPES.TIMEOUT_ERR,
|
||||
{
|
||||
left: 'There was an error with your request, please make sure you have internet connection',
|
||||
right: 'Request time exceeded'
|
||||
}
|
||||
],
|
||||
// task-details.component
|
||||
[NOTIFICATION_TYPES.FETCH_ERR_7, { left: 'An error occurred while fetching the task', right: '' }],
|
||||
// task-details.component
|
||||
[NOTIFICATION_TYPES.DELETE_ERR_2, { left: 'An error occurred while deleting the task', right: '' }],
|
||||
|
||||
// ALERTS
|
||||
|
||||
// access-items-management.component
|
||||
[NOTIFICATION_TYPES.SUCCESS_ALERT, { left: '', right: '{accessId} was removed successfully' }],
|
||||
// classification-details.component
|
||||
[
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_2,
|
||||
{ left: '', right: 'Classification {classificationKey} was created successfully' }
|
||||
],
|
||||
// classification-details.component
|
||||
[
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_3,
|
||||
{ left: '', right: 'Classification {classificationKey} was saved successfully' }
|
||||
],
|
||||
// classification-details.component
|
||||
// access-items.component
|
||||
// workbasket.distribution-targets.component
|
||||
// workbasket-information.component
|
||||
// task-details.component
|
||||
[NOTIFICATION_TYPES.INFO_ALERT, { left: '', right: 'Information restored' }],
|
||||
// classification-details.component
|
||||
[
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_4,
|
||||
{ left: '', right: 'Classification {classificationKey} was removed successfully' }
|
||||
],
|
||||
// classification-list.component
|
||||
[
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_5,
|
||||
{ left: '', right: 'Classification {classificationKey} was moved successfully' }
|
||||
],
|
||||
// import-export.component
|
||||
[NOTIFICATION_TYPES.SUCCESS_ALERT_6, { left: '', right: 'Import was successful' }],
|
||||
// access-items.component
|
||||
[
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_7,
|
||||
{ left: '', right: 'Workbasket {workbasketKey} Access items were saved successfully' }
|
||||
],
|
||||
// workbasket.distribution-targets.component
|
||||
[
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_8,
|
||||
{ left: '', right: 'Workbasket {workbasketName} Distribution targets were saved successfully' }
|
||||
],
|
||||
// workbasket-information.component
|
||||
[
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_9,
|
||||
{ left: '', right: 'DistributionTargets for workbasketID {workbasketId} was removed successfully' }
|
||||
],
|
||||
// workbasket-information.component
|
||||
[NOTIFICATION_TYPES.SUCCESS_ALERT_10, { left: '', right: 'Workbasket {workbasketKey} was saved successfully' }],
|
||||
// workbasket-information.component
|
||||
[NOTIFICATION_TYPES.SUCCESS_ALERT_11, { left: '', right: 'Workbasket {workbasketKey} was created successfully' }],
|
||||
// workbasket-information.component
|
||||
[NOTIFICATION_TYPES.SUCCESS_ALERT_12, { left: '', right: 'The Workbasket {workbasketId} has been deleted.' }],
|
||||
// forms-validator.service
|
||||
[NOTIFICATION_TYPES.WARNING_ALERT, { left: '', right: 'There are some empty fields which are required.' }],
|
||||
// forms-validator.service x2
|
||||
[NOTIFICATION_TYPES.WARNING_ALERT_2, { left: '', right: 'The {owner} introduced is not valid.' }],
|
||||
// task-details.component
|
||||
[NOTIFICATION_TYPES.DANGER_ALERT, { left: '', right: 'There was an error while updating.' }],
|
||||
// task-details.component
|
||||
[NOTIFICATION_TYPES.SUCCESS_ALERT_13, { left: '', right: 'Task {taskId} was created successfully.' }],
|
||||
// task-details.component
|
||||
[NOTIFICATION_TYPES.SUCCESS_ALERT_14, { left: '', right: 'Updating was successful.' }],
|
||||
// task-details.component
|
||||
[NOTIFICATION_TYPES.DANGER_ALERT_2, { left: '', right: 'There was an error while creating a new task.' }],
|
||||
// task-master.component
|
||||
[NOTIFICATION_TYPES.INFO_ALERT_2, { left: '', right: 'The selected Workbasket is empty!' }],
|
||||
[NOTIFICATION_TYPES.WARNING_CANT_COPY, { left: '', right: "Can't copy a not created classification" }]
|
||||
]);
|
|
@ -1,7 +1,6 @@
|
|||
import { FormArray, NgForm, NgModel } from '@angular/forms';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AccessIdsService } from 'app/shared/services/access-ids/access-ids.service';
|
||||
import { NOTIFICATION_TYPES } from '../../models/notifications';
|
||||
import { NotificationService } from '../notifications/notification.service';
|
||||
import { Observable, Subject, Subscription, timer } from 'rxjs';
|
||||
|
||||
|
@ -54,12 +53,9 @@ export class FormsValidatorService {
|
|||
const responseOwner = new ResponseOwner(values[1]);
|
||||
if (!(values[0] && responseOwner.valid)) {
|
||||
if (!responseOwner.valid) {
|
||||
this.notificationsService.showToast(
|
||||
NOTIFICATION_TYPES.WARNING_ALERT_2,
|
||||
new Map<string, string>([['owner', responseOwner.field]])
|
||||
);
|
||||
this.notificationsService.showWarning('OWNER_NOT_VALID', { owner: responseOwner.field });
|
||||
} else {
|
||||
this.notificationsService.showToast(NOTIFICATION_TYPES.WARNING_ALERT);
|
||||
this.notificationsService.showWarning('EMPTY_FIELDS');
|
||||
}
|
||||
}
|
||||
return values[0] && responseOwner.valid;
|
||||
|
@ -88,10 +84,9 @@ export class FormsValidatorService {
|
|||
result = result && responseOwner.valid;
|
||||
});
|
||||
if (!result) {
|
||||
this.notificationsService.showToast(
|
||||
NOTIFICATION_TYPES.WARNING_ALERT_2,
|
||||
new Map<string, string>([['owner', responseOwner ? responseOwner.field : 'owner']])
|
||||
);
|
||||
this.notificationsService.showWarning('OWNER_NOT_VALID', {
|
||||
owner: responseOwner ? responseOwner.field : 'owner'
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -12,14 +12,13 @@ import { RequestInProgressService } from 'app/shared/services/request-in-progres
|
|||
import { environment } from 'environments/environment';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { NotificationService } from '../notifications/notification.service';
|
||||
import { NOTIFICATION_TYPES } from '../../models/notifications';
|
||||
|
||||
@Injectable()
|
||||
export class HttpClientInterceptor implements HttpInterceptor {
|
||||
constructor(
|
||||
private requestInProgressService: RequestInProgressService,
|
||||
private errorsService: NotificationService,
|
||||
private tokenExtractor: HttpXsrfTokenExtractor
|
||||
private tokenExtractor: HttpXsrfTokenExtractor,
|
||||
private notificationService: NotificationService
|
||||
) {}
|
||||
|
||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
|
@ -36,22 +35,16 @@ export class HttpClientInterceptor implements HttpInterceptor {
|
|||
() => {},
|
||||
(error) => {
|
||||
this.requestInProgressService.setRequestInProgress(false);
|
||||
if (error instanceof HttpErrorResponse && (error.status === 401 || error.status === 403)) {
|
||||
this.errorsService.triggerError(NOTIFICATION_TYPES.ACCESS_ERR, error);
|
||||
} else if (
|
||||
error instanceof HttpErrorResponse &&
|
||||
error.status === 404 &&
|
||||
error.url.indexOf('environment-information.json')
|
||||
if (
|
||||
error.status !== 404 ||
|
||||
!(error instanceof HttpErrorResponse) ||
|
||||
error.url.indexOf('environment-information.json') === -1
|
||||
) {
|
||||
// ignore this error
|
||||
} else if (
|
||||
(error.status === 409 && error.error.exception.endsWith('WorkbasketAccessItemAlreadyExistException')) ||
|
||||
error.error.exception.endsWith('WorkbasketAlreadyExistException') ||
|
||||
error.error.exception.endsWith('ClassificationAlreadyExistException')
|
||||
) {
|
||||
return;
|
||||
} else {
|
||||
this.errorsService.triggerError(NOTIFICATION_TYPES.GENERAL_ERR, error);
|
||||
const { key, messageVariables } = error.error.error || {
|
||||
key: 'FALLBACK',
|
||||
messageVariables: {}
|
||||
};
|
||||
this.notificationService.showError(key, messageVariables);
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -1,34 +1,71 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||
import { ErrorModel } from '../../models/error-model';
|
||||
import { NOTIFICATION_TYPES } from '../../models/notifications';
|
||||
import { ToastComponent } from '../../components/toast/toast.component';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { DialogPopUpComponent } from '../../components/popup/dialog-pop-up.component';
|
||||
import { HotToastService } from '@ngneat/hot-toast';
|
||||
import { ObtainMessageService } from '../obtain-message/obtain-message.service';
|
||||
import { messageTypes } from '../obtain-message/message-types';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class NotificationService {
|
||||
constructor(private matSnack: MatSnackBar, private popup: MatDialog) {}
|
||||
constructor(
|
||||
private popup: MatDialog,
|
||||
private toastService: HotToastService,
|
||||
private obtainMessageService: ObtainMessageService
|
||||
) {}
|
||||
|
||||
triggerError(key: NOTIFICATION_TYPES, passedError?: HttpErrorResponse, additions?: Map<String, String>): void {
|
||||
this.popup.open(DialogPopUpComponent, {
|
||||
data: { key, passedError, additions, isDialog: false },
|
||||
backdropClass: 'backdrop',
|
||||
position: { top: '3em' },
|
||||
autoFocus: true,
|
||||
maxWidth: '50em'
|
||||
generateToastId(errorKey: string, messageVariables: object): string {
|
||||
let id = errorKey;
|
||||
for (const [replacementKey, value] of Object.entries(messageVariables)) {
|
||||
id = id.concat(replacementKey, value);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
showError(errorKey: string, messageVariables: object = {}) {
|
||||
this.toastService.error(this.obtainMessageService.getMessage(errorKey, messageVariables, messageTypes.ERROR), {
|
||||
dismissible: true,
|
||||
autoClose: false,
|
||||
id: this.generateToastId(errorKey, messageVariables)
|
||||
});
|
||||
}
|
||||
|
||||
showDialog(message: string, callback?: Function): MatDialogRef<DialogPopUpComponent> {
|
||||
showSuccess(successKey: string, messageVariables: object = {}) {
|
||||
this.toastService.success(
|
||||
this.obtainMessageService.getMessage(successKey, messageVariables, messageTypes.SUCCESS),
|
||||
{
|
||||
duration: 5000
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
showWarning(warningKey: string, messageVariables: object = {}) {
|
||||
this.toastService.warning(this.obtainMessageService.getMessage(warningKey, messageVariables, messageTypes.WARNING));
|
||||
}
|
||||
|
||||
showInformation(informationKey: string, messageVariables: object = {}) {
|
||||
this.toastService.show(
|
||||
`
|
||||
<span class="material-icons">info</span> ${this.obtainMessageService.getMessage(
|
||||
informationKey,
|
||||
messageVariables,
|
||||
messageTypes.INFORMATION
|
||||
)}
|
||||
`,
|
||||
// prevents duplicated toast because of double call in task-master
|
||||
// TODO: delete while frontend refactoring
|
||||
{ id: 'empty-workbasket' }
|
||||
);
|
||||
}
|
||||
|
||||
showDialog(key: string, messageVariables: object = {}, callback: Function) {
|
||||
const message = this.obtainMessageService.getMessage(key, messageVariables, messageTypes.DIALOG);
|
||||
|
||||
const ref = this.popup.open(DialogPopUpComponent, {
|
||||
data: { isDialog: true, message, callback },
|
||||
data: { message: message, callback },
|
||||
backdropClass: 'backdrop',
|
||||
position: { top: '3em' },
|
||||
position: { top: '5em' },
|
||||
autoFocus: true,
|
||||
maxWidth: '50em'
|
||||
});
|
||||
|
@ -39,30 +76,4 @@ export class NotificationService {
|
|||
});
|
||||
return ref;
|
||||
}
|
||||
|
||||
showToast(key: NOTIFICATION_TYPES, additions?: Map<string, string>) {
|
||||
let colorClass: string[];
|
||||
const type = NOTIFICATION_TYPES[key].split('_')[0].toLowerCase();
|
||||
switch (type) {
|
||||
case 'danger':
|
||||
colorClass = ['red', 'background-white'];
|
||||
break;
|
||||
case 'success':
|
||||
colorClass = ['white', 'background-bluegreen'];
|
||||
break;
|
||||
case 'info':
|
||||
colorClass = ['white', 'background-darkgreen'];
|
||||
break;
|
||||
case 'warning':
|
||||
colorClass = ['brown', 'background-white'];
|
||||
break;
|
||||
default:
|
||||
colorClass = ['white', 'background-darkgreen'];
|
||||
}
|
||||
return this.matSnack.openFromComponent(ToastComponent, {
|
||||
duration: 5000,
|
||||
data: { key, additions },
|
||||
panelClass: colorClass
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
import { messageTypes } from './message-types';
|
||||
|
||||
export const messageByErrorCode = {
|
||||
[messageTypes.ERROR]: {
|
||||
FALLBACK:
|
||||
'An error occurred, but there is no error code. Please contact your administrator to specify the error code.',
|
||||
CRITICAL_SYSTEM_ERROR: 'A system error occurred. Please contact your administrator.',
|
||||
UNKNOWN_ERROR: 'An unknown error occurred. Please contact your administrator.',
|
||||
|
||||
ENTITY_NOT_UP_TO_DATE:
|
||||
'Cannot be saved because there has been a modification while editing. Please reload to get the current version.',
|
||||
DOMAIN_NOT_FOUND: 'Domain {domain} cannot be found',
|
||||
ROLE_MISMATCHED: 'Current user {currentUserId} is not authorized. User must be member of role(s) {roles}.',
|
||||
SPINNER_TIMEOUT: 'Request time exceeded. Please make sure you have internet connection.',
|
||||
HISTORY_EVENT_NOT_FOUND: 'History Event with id {historyEventId} cannot be found',
|
||||
PAYLOAD_TOO_LARGE: 'Maximum upload size was exceeded',
|
||||
CLASSIFICATION_SERVICE_LEVEL_MALFORMED:
|
||||
'Service level {serviceLevel} of Classification with key {classificationKey} and domain {domain} is invalid. ' +
|
||||
'The service level has to be a positive ISO-8601 duration format and only whole days are supported. ' +
|
||||
"The format must be 'PnD'.",
|
||||
INVALID_ARGUMENT: 'A method was called with an invalid argument.',
|
||||
|
||||
CLASSIFICATION_IN_USE:
|
||||
'Classification with key {classificationKey} in domain {domain} cannot be deleted since there are Tasks associated with this Classification.',
|
||||
CLASSIFICATION_ALREADY_EXISTS:
|
||||
'Classification with key {classificationKey} cannot be saved since a Classification with the same key already exists in domain {domain}',
|
||||
CLASSIFICATION_WITH_ID_NOT_FOUND: 'Classification with id {classificationId} cannot be found',
|
||||
|
||||
WORKBASKET_WITH_ID_NOT_FOUND: 'Workbasket with id {workbasketId} cannot be found',
|
||||
WORKBASKET_WITH_KEY_NOT_FOUND: 'Workbasket with key {workbasketKey} cannot be found in domain {domain}',
|
||||
WORKBASKET_ALREADY_EXISTS:
|
||||
'Workbasket with key {workbasketKey} cannot be saved since a Workbasket with the same key already exists in domain {domain}',
|
||||
WORKBASKET_IN_USE: 'Workbasket with id {workbasketId} cannot be deleted since it contains non-completed Tasks',
|
||||
WORKBASKET_ACCESS_ITEM_ALREADY_EXISTS:
|
||||
'Workbasket Access Item with access id {accessId} for Workbasket with id {workbasketId} cannot be created since it already exists',
|
||||
WORKBASKET_WITH_ID_MISMATCHED_PERMISSION:
|
||||
'Current user {currentUserId} has no permission for Workbasket with id {workbasketId}. Required permission(s): {requiredPermissions}.',
|
||||
WORKBASKET_WITH_KEY_MISMATCHED_PERMISSION:
|
||||
'Current user {currentUserId} has no permission for Workbasket with key {workbasketKey}. Required permission(s): {requiredPermissions}.',
|
||||
|
||||
TASK_ALREADY_EXISTS:
|
||||
'Task with external id {externalTaskId} cannot be created, because a Task with the same external id already exists.',
|
||||
TASK_NOT_FOUND: 'Task with id {taskId} cannot be found',
|
||||
TASK_INVALID_CALLBACK_STATE:
|
||||
'Callback state {taskCallbackState} for Task with id {taskId} is invalid. Required callback states: {requiredCallbackStates}',
|
||||
TASK_INVALID_OWNER: 'Current user {currentUserId} is not the owner of the Task with id {taskId}',
|
||||
TASK_INVALID_STATE: 'Task with id {taskId} is in state {taskState}. Required state(s): {requiredTaskStates}.',
|
||||
|
||||
IMPORT_EXPORT_UPLOAD_FAILED: 'Upload failed. The uploaded file probably exceeded the maximum file size of 10 MB.',
|
||||
IMPORT_EXPORT_UPLOAD_FAILED_AUTH: 'Upload failed because you have no access to apply this operation.',
|
||||
IMPORT_EXPORT_UPLOAD_FAILED_NOT_FOUND: 'Upload failed because operation was not found',
|
||||
IMPORT_EXPORT_UPLOAD_FAILED_CONFLICTS: 'Upload failed because operation has conflicts',
|
||||
IMPORT_EXPORT_UPLOAD_FAILED_SIZE: 'Upload failed because maximum file size exceeded',
|
||||
IMPORT_EXPORT_UPLOAD_FILE_FORMAT: 'File format is not allowed. Please use a .json file.'
|
||||
},
|
||||
|
||||
[messageTypes.SUCCESS]: {
|
||||
FALLBACK:
|
||||
'Action was completed successfully, but this success message was not configured properly. ' +
|
||||
'Please ask your administrator to configure this message.',
|
||||
|
||||
CLASSIFICATION_CREATE: 'Classification with key {classificationKey} was created',
|
||||
CLASSIFICATION_UPDATE: 'Classification with key {classificationKey} was updated',
|
||||
CLASSIFICATION_REMOVE: 'Classification with key {classificationKey} was removed',
|
||||
CLASSIFICATION_MOVE: 'Classification with key {classificationKey} was moved',
|
||||
CLASSIFICATION_RESTORE: 'Classification restored',
|
||||
CLASSIFICATION_IMPORT: 'Classifications imported',
|
||||
|
||||
WORKBASKET_CREATE: 'Workbasket with key {workbasketKey} was created',
|
||||
WORKBASKET_UPDATE: 'Workbasket with key {workbasketKey} was updated',
|
||||
WORKBASKET_REMOVE: 'Workbasket with key {workbasketKey} was removed',
|
||||
WORKBASKET_RESTORE: 'Workbasket restored',
|
||||
WORKBASKET_IMPORT: 'Workbaskets imported',
|
||||
WORKBASKET_ACCESS_ITEM_SAVE: 'Workbasket Access Items were saved',
|
||||
WORKBASKET_ACCESS_ITEM_RESTORE: 'Workbasket Access Items restored',
|
||||
WORKBASKET_DISTRIBUTION_TARGET_SAVE: 'Workbasket Distribution Targets were saved',
|
||||
WORKBASKET_DISTRIBUTION_TARGET_RESTORE: 'Workbasket Distribution Targets restored',
|
||||
WORKBASKET_DISTRIBUTION_TARGET_REMOVE:
|
||||
'Workbasket with key {workbasketKey} was removed as Workbasket Distribution Target',
|
||||
WORKBASKET_ACCESS_ITEM_REMOVE_PERMISSION: '{accessId} was removed',
|
||||
|
||||
TASK_CREATE: 'Task with name {taskName} was created',
|
||||
TASK_UPDATE: 'Task with name {taskName} was updated',
|
||||
TASK_DELETE: 'Task with name {taskName} was deleted',
|
||||
TASK_RESTORE: 'Task restored'
|
||||
},
|
||||
|
||||
[messageTypes.WARNING]: {
|
||||
CLASSIFICATION_COPY_NOT_CREATED: 'Cannot copy a not created Classification',
|
||||
EMPTY_FIELDS: 'There are empty fields which are required',
|
||||
OWNER_NOT_VALID: 'The {owner} introduced is not valid'
|
||||
},
|
||||
|
||||
[messageTypes.INFORMATION]: {
|
||||
EMPTY_WORKBASKET: 'Selected Workbasket is empty'
|
||||
},
|
||||
|
||||
[messageTypes.DIALOG]: {
|
||||
POPUP_CONFIGURATION: 'This Popup was not configured properly for this request. Please contact your administrator.',
|
||||
|
||||
WORKBASKET_DELETE: 'Delete Workbasket with key {workbasketKey}?',
|
||||
CLASSIFICATION_DELETE: 'Delete Classification with key {classificationKey}?',
|
||||
TASK_DELETE: 'Delete Task with id {taskId}?',
|
||||
ACCESS_ITEM_MANAGEMENT_REVOKE_ACCESS: 'Delete all access related to {accessId}?'
|
||||
}
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
export enum messageTypes {
|
||||
ERROR,
|
||||
SUCCESS,
|
||||
WARNING,
|
||||
INFORMATION,
|
||||
DIALOG
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { messageByErrorCode } from './message-by-error-code';
|
||||
import { messageTypes } from './message-types';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ObtainMessageService {
|
||||
getMessage(key: string, messageVariables: object = {}, type: messageTypes): string {
|
||||
let message =
|
||||
messageByErrorCode[type][key] ||
|
||||
messageByErrorCode[type]['FALLBACK'] ||
|
||||
`The message with type '${type}' and key '${key}' is not configured`;
|
||||
|
||||
for (const [replacementKey, value] of Object.entries(messageVariables)) {
|
||||
message = message.replace(`{${replacementKey}}`, `'${value}'`);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ import { TreeModule } from '@circlon/angular-tree-component';
|
|||
import { AlertModule } from 'ngx-bootstrap/alert';
|
||||
import { TypeaheadModule } from 'ngx-bootstrap/typeahead';
|
||||
import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
|
||||
|
||||
import { HotToastModule } from '@ngneat/hot-toast';
|
||||
import { AccordionModule } from 'ngx-bootstrap/accordion';
|
||||
|
||||
/**
|
||||
|
@ -20,7 +20,6 @@ import { TaskanaTreeComponent } from 'app/administration/components/tree/tree.co
|
|||
import { TypeAheadComponent } from 'app/shared/components/type-ahead/type-ahead.component';
|
||||
import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component';
|
||||
import { FieldErrorDisplayComponent } from 'app/shared/components/field-error-display/field-error-display.component';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatRadioModule } from '@angular/material/radio';
|
||||
|
@ -43,7 +42,6 @@ import { DateTimeZonePipe } from './pipes/date-time-zone.pipe';
|
|||
* Services
|
||||
*/
|
||||
import { HttpClientInterceptor } from './services/http-client-interceptor/http-client-interceptor.service';
|
||||
import { ToastComponent } from './components/toast/toast.component';
|
||||
import { DialogPopUpComponent } from './components/popup/dialog-pop-up.component';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
|
@ -56,6 +54,10 @@ import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
|||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { WorkbasketFilterComponent } from './components/workbasket-filter/workbasket-filter.component';
|
||||
import { TaskFilterComponent } from './components/task-filter/task-filter.component';
|
||||
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
|
||||
import { ClassificationsService } from 'app/shared/services/classifications/classifications.service';
|
||||
import { ObtainMessageService } from './services/obtain-message/obtain-message.service';
|
||||
import { AccessIdsService } from './services/access-ids/access-ids.service';
|
||||
|
||||
const MODULES = [
|
||||
CommonModule,
|
||||
|
@ -66,12 +68,16 @@ const MODULES = [
|
|||
BsDatepickerModule.forRoot(),
|
||||
AngularSvgIconModule,
|
||||
HttpClientModule,
|
||||
MatSnackBarModule,
|
||||
MatDialogModule,
|
||||
MatButtonModule,
|
||||
RouterModule,
|
||||
TreeModule,
|
||||
MatAutocompleteModule
|
||||
MatAutocompleteModule,
|
||||
HotToastModule.forRoot({
|
||||
style: {
|
||||
'max-width': '520px'
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
const DECLARATIONS = [
|
||||
|
@ -91,7 +97,6 @@ const DECLARATIONS = [
|
|||
FieldErrorDisplayComponent,
|
||||
PaginationComponent,
|
||||
ProgressSpinnerComponent,
|
||||
ToastComponent,
|
||||
DialogPopUpComponent,
|
||||
WorkbasketFilterComponent,
|
||||
TaskFilterComponent
|
||||
|
@ -118,8 +123,12 @@ const DECLARATIONS = [
|
|||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: HttpClientInterceptor,
|
||||
multi: true
|
||||
}
|
||||
},
|
||||
AccessIdsService,
|
||||
ClassificationsService,
|
||||
WorkbasketService,
|
||||
ObtainMessageService
|
||||
],
|
||||
entryComponents: [DialogPopUpComponent, ToastComponent]
|
||||
entryComponents: [DialogPopUpComponent]
|
||||
})
|
||||
export class SharedModule {}
|
||||
|
|
|
@ -9,7 +9,6 @@ import { Observable, of } from 'rxjs';
|
|||
import { AccessIdsService } from '../../services/access-ids/access-ids.service';
|
||||
import { take, tap } from 'rxjs/operators';
|
||||
import { AccessIdDefinition } from '../../models/access-id';
|
||||
import { NOTIFICATION_TYPES } from '../../models/notifications';
|
||||
import { NotificationService } from '../../services/notifications/notification.service';
|
||||
import { WorkbasketAccessItemsRepresentation } from '../../models/workbasket-access-items-representation';
|
||||
import { RequestInProgressService } from '../../services/request-in-progress/request-in-progress.service';
|
||||
|
@ -50,9 +49,8 @@ export class AccessItemsManagementState implements NgxsAfterBootstrap {
|
|||
groups
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
() => {
|
||||
this.requestInProgressService.setRequestInProgress(false);
|
||||
this.notificationService.triggerError(NOTIFICATION_TYPES.FETCH_ERR, error);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -72,9 +70,8 @@ export class AccessItemsManagementState implements NgxsAfterBootstrap {
|
|||
accessItemsResource
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
() => {
|
||||
this.requestInProgressService.setRequestInProgress(false);
|
||||
this.notificationService.triggerError(NOTIFICATION_TYPES.FETCH_ERR_2, error);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -91,14 +88,12 @@ export class AccessItemsManagementState implements NgxsAfterBootstrap {
|
|||
tap(
|
||||
() => {
|
||||
this.requestInProgressService.setRequestInProgress(false);
|
||||
this.notificationService.showToast(
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT,
|
||||
new Map<string, string>([['accessId', action.accessId]])
|
||||
);
|
||||
this.notificationService.showSuccess('WORKBASKET_ACCESS_ITEM_REMOVE_PERMISSION', {
|
||||
accessId: action.accessId
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
() => {
|
||||
this.requestInProgressService.setRequestInProgress(false);
|
||||
this.notificationService.triggerError(NOTIFICATION_TYPES.DELETE_ERR, error);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
|
|
@ -25,7 +25,6 @@ import {
|
|||
} from './workbasket.actions';
|
||||
import { WorkbasketSummaryRepresentation } from '../../models/workbasket-summary-representation';
|
||||
import { ACTION } from '../../models/action';
|
||||
import { NOTIFICATION_TYPES } from '../../models/notifications';
|
||||
import { NotificationService } from '../../services/notifications/notification.service';
|
||||
import { WorkbasketAccessItemsRepresentation } from '../../models/workbasket-access-items-representation';
|
||||
import { WorkbasketDistributionTargets } from '../../models/workbasket-distribution-targets';
|
||||
|
@ -195,19 +194,11 @@ export class WorkbasketState implements NgxsAfterBootstrap {
|
|||
ctx.dispatch(new OnButtonPressed(undefined));
|
||||
return this.workbasketService.createWorkbasket(action.workbasket).pipe(
|
||||
take(1),
|
||||
tap(
|
||||
(workbasketUpdated) => {
|
||||
this.notificationService.showToast(
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_11,
|
||||
new Map<string, string>([['workbasketKey', workbasketUpdated.key]])
|
||||
);
|
||||
tap((workbasketUpdated) => {
|
||||
this.notificationService.showSuccess('WORKBASKET_CREATE', { workbasketKey: workbasketUpdated.key });
|
||||
|
||||
this.location.go(this.location.path().replace(/(workbaskets).*/g, 'workbaskets'));
|
||||
},
|
||||
(error) => {
|
||||
this.notificationService.triggerError(NOTIFICATION_TYPES.CREATE_ERR_2, error);
|
||||
}
|
||||
),
|
||||
this.location.go(this.location.path().replace(/(workbaskets).*/g, 'workbaskets'));
|
||||
}),
|
||||
concatMap((workbasketUpdated) => ctx.dispatch(new SelectWorkbasket(workbasketUpdated.workbasketId)))
|
||||
);
|
||||
}
|
||||
|
@ -276,27 +267,19 @@ export class WorkbasketState implements NgxsAfterBootstrap {
|
|||
ctx.dispatch(new OnButtonPressed(undefined));
|
||||
return this.workbasketService.updateWorkbasket(action.url, action.workbasket).pipe(
|
||||
take(1),
|
||||
tap(
|
||||
(updatedWorkbasket) => {
|
||||
this.notificationService.showToast(
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_10,
|
||||
new Map<string, string>([['workbasketKey', updatedWorkbasket.key]])
|
||||
);
|
||||
tap((updatedWorkbasket) => {
|
||||
this.notificationService.showSuccess('WORKBASKET_UPDATE', { workbasketKey: updatedWorkbasket.key });
|
||||
|
||||
const paginatedWorkbasketSummary = { ...ctx.getState().paginatedWorkbasketsSummary };
|
||||
paginatedWorkbasketSummary.workbaskets = updateWorkbasketSummaryRepresentation(
|
||||
paginatedWorkbasketSummary.workbaskets,
|
||||
action.workbasket
|
||||
);
|
||||
ctx.patchState({
|
||||
selectedWorkbasket: updatedWorkbasket,
|
||||
paginatedWorkbasketsSummary: paginatedWorkbasketSummary
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
this.notificationService.triggerError(NOTIFICATION_TYPES.SAVE_ERR_4, error);
|
||||
}
|
||||
)
|
||||
const paginatedWorkbasketSummary = { ...ctx.getState().paginatedWorkbasketsSummary };
|
||||
paginatedWorkbasketSummary.workbaskets = updateWorkbasketSummaryRepresentation(
|
||||
paginatedWorkbasketSummary.workbaskets,
|
||||
action.workbasket
|
||||
);
|
||||
ctx.patchState({
|
||||
selectedWorkbasket: updatedWorkbasket,
|
||||
paginatedWorkbasketsSummary: paginatedWorkbasketSummary
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -305,21 +288,11 @@ export class WorkbasketState implements NgxsAfterBootstrap {
|
|||
ctx.dispatch(new OnButtonPressed(undefined));
|
||||
return this.workbasketService.removeDistributionTarget(action.url).pipe(
|
||||
take(1),
|
||||
tap(
|
||||
() => {
|
||||
this.notificationService.showToast(
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_9,
|
||||
new Map<string, string>([['workbasketId', ctx.getState().selectedWorkbasket.workbasketId]])
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this.notificationService.triggerError(
|
||||
NOTIFICATION_TYPES.REMOVE_ERR_2,
|
||||
error,
|
||||
new Map<String, String>([['workbasketId', ctx.getState().selectedWorkbasket.workbasketId]])
|
||||
);
|
||||
}
|
||||
)
|
||||
tap(() => {
|
||||
this.notificationService.showSuccess('WORKBASKET_DISTRIBUTION_TARGET_REMOVE', {
|
||||
workbasketKey: ctx.getState().selectedWorkbasket.key
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -329,17 +302,10 @@ export class WorkbasketState implements NgxsAfterBootstrap {
|
|||
return this.workbasketService.markWorkbasketForDeletion(action.url).pipe(
|
||||
take(1),
|
||||
tap((response) => {
|
||||
if (response.status === 202) {
|
||||
this.notificationService.triggerError(
|
||||
NOTIFICATION_TYPES.MARK_ERR,
|
||||
undefined,
|
||||
new Map<String, String>([['workbasketId', ctx.getState().selectedWorkbasket.workbasketId]])
|
||||
);
|
||||
} else {
|
||||
this.notificationService.showToast(
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_12,
|
||||
new Map<string, string>([['workbasketId', ctx.getState().selectedWorkbasket.workbasketId]])
|
||||
);
|
||||
if (response.status !== 202) {
|
||||
this.notificationService.showSuccess('WORKBASKET_REMOVE', {
|
||||
workbasketKey: ctx.getState().selectedWorkbasket.key
|
||||
});
|
||||
|
||||
ctx.dispatch(new DeselectWorkbasket());
|
||||
}
|
||||
|
@ -369,21 +335,15 @@ export class WorkbasketState implements NgxsAfterBootstrap {
|
|||
.updateWorkBasketAccessItem(action.url, { accessItems: action.workbasketAccessItems })
|
||||
.pipe(
|
||||
take(1),
|
||||
tap(
|
||||
(workbasketAccessItems) => {
|
||||
ctx.patchState({
|
||||
workbasketAccessItems
|
||||
});
|
||||
this.notificationService.showToast(
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_7,
|
||||
new Map<string, string>([['workbasketKey', ctx.getState().selectedWorkbasket.key]])
|
||||
);
|
||||
return of(null);
|
||||
},
|
||||
(error) => {
|
||||
this.notificationService.triggerError(NOTIFICATION_TYPES.SAVE_ERR_2, error);
|
||||
}
|
||||
)
|
||||
tap((workbasketAccessItems) => {
|
||||
ctx.patchState({
|
||||
workbasketAccessItems
|
||||
});
|
||||
this.notificationService.showSuccess('WORKBASKET_ACCESS_ITEM_SAVE', {
|
||||
workbasketKey: ctx.getState().selectedWorkbasket.key
|
||||
});
|
||||
return of(null);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -440,15 +400,13 @@ export class WorkbasketState implements NgxsAfterBootstrap {
|
|||
});
|
||||
}
|
||||
this.requestInProgressService.setRequestInProgress(false);
|
||||
this.notificationService.showToast(
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_8,
|
||||
new Map<string, string>([['workbasketName', ctx.getState().selectedWorkbasket.name]])
|
||||
);
|
||||
this.notificationService.showSuccess('WORKBASKET_DISTRIBUTION_TARGET_SAVE', {
|
||||
workbasketName: ctx.getState().selectedWorkbasket.name
|
||||
});
|
||||
|
||||
return of(null);
|
||||
},
|
||||
(error) => {
|
||||
this.notificationService.triggerError(NOTIFICATION_TYPES.SAVE_ERR_3, error);
|
||||
() => {
|
||||
this.requestInProgressService.setRequestInProgress(false);
|
||||
}
|
||||
)
|
||||
|
|
|
@ -10,9 +10,8 @@ import { ObjectReference } from 'app/workplace/models/object-reference';
|
|||
import { Workbasket } from 'app/shared/models/workbasket';
|
||||
import { WorkplaceService } from 'app/workplace/services/workplace.service';
|
||||
import { MasterAndDetailService } from 'app/shared/services/master-and-detail/master-and-detail.service';
|
||||
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
|
||||
import { NotificationService } from '../../../shared/services/notifications/notification.service';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { take, takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'taskana-task-details',
|
||||
|
@ -75,7 +74,7 @@ export class TaskDetailsComponent implements OnInit, OnDestroy {
|
|||
this.task.customAttributes = this.taskClone.customAttributes.slice(0);
|
||||
this.task.callbackInfo = this.taskClone.callbackInfo.slice(0);
|
||||
this.task.primaryObjRef = { ...this.taskClone.primaryObjRef };
|
||||
this.notificationService.showToast(NOTIFICATION_TYPES.INFO_ALERT);
|
||||
this.notificationService.showSuccess('TASK_RESTORE');
|
||||
}
|
||||
|
||||
getTask(): void {
|
||||
|
@ -91,8 +90,8 @@ export class TaskDetailsComponent implements OnInit, OnDestroy {
|
|||
this.cloneTask();
|
||||
this.taskService.selectTask(task);
|
||||
},
|
||||
(error) => {
|
||||
this.notificationService.triggerError(NOTIFICATION_TYPES.FETCH_ERR_7, error);
|
||||
() => {
|
||||
this.requestInProgressService.setRequestInProgress(false);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -111,22 +110,22 @@ export class TaskDetailsComponent implements OnInit, OnDestroy {
|
|||
|
||||
deleteTask(): void {
|
||||
this.notificationService.showDialog(
|
||||
`You are going to delete Task: ${this.currentId}. Can you confirm this action?`,
|
||||
'TASK_DELETE',
|
||||
{ taskId: this.currentId },
|
||||
this.deleteTaskConfirmation.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
deleteTaskConfirmation(): void {
|
||||
this.deleteTaskSubscription = this.taskService.deleteTask(this.task).subscribe(
|
||||
() => {
|
||||
this.deleteTaskSubscription = this.taskService
|
||||
.deleteTask(this.task)
|
||||
.pipe(take(1))
|
||||
.subscribe(() => {
|
||||
this.notificationService.showSuccess('TASK_DELETE', { taskName: this.task.name });
|
||||
this.taskService.publishTaskDeletion();
|
||||
this.task = null;
|
||||
this.router.navigate(['taskana/workplace/tasks'], { queryParamsHandling: 'merge' });
|
||||
},
|
||||
(error) => {
|
||||
this.notificationService.triggerError(NOTIFICATION_TYPES.DELETE_ERR_2, error);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
selectTab(tab: string): void {
|
||||
|
@ -169,11 +168,10 @@ export class TaskDetailsComponent implements OnInit, OnDestroy {
|
|||
this.task = task;
|
||||
this.cloneTask();
|
||||
this.taskService.publishUpdatedTask(task);
|
||||
this.notificationService.showToast(NOTIFICATION_TYPES.SUCCESS_ALERT_14);
|
||||
this.notificationService.showSuccess('TASK_UPDATE', { taskName: task.name });
|
||||
},
|
||||
() => {
|
||||
this.requestInProgressService.setRequestInProgress(false);
|
||||
this.notificationService.showToast(NOTIFICATION_TYPES.DANGER_ALERT);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -184,10 +182,7 @@ export class TaskDetailsComponent implements OnInit, OnDestroy {
|
|||
this.taskService.createTask(this.task).subscribe(
|
||||
(task) => {
|
||||
this.requestInProgressService.setRequestInProgress(false);
|
||||
this.notificationService.showToast(
|
||||
NOTIFICATION_TYPES.SUCCESS_ALERT_13,
|
||||
new Map<string, string>([['taskId', task.name]])
|
||||
);
|
||||
this.notificationService.showSuccess('TASK_CREATE', { taskName: task.name });
|
||||
this.task = task;
|
||||
this.taskService.selectTask(this.task);
|
||||
this.taskService.publishUpdatedTask(task);
|
||||
|
@ -195,7 +190,6 @@ export class TaskDetailsComponent implements OnInit, OnDestroy {
|
|||
},
|
||||
() => {
|
||||
this.requestInProgressService.setRequestInProgress(false);
|
||||
this.notificationService.showToast(NOTIFICATION_TYPES.DANGER_ALERT_2);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import { Page } from 'app/shared/models/page';
|
|||
import { take, takeUntil } from 'rxjs/operators';
|
||||
import { Search } from '../task-list-toolbar/task-list-toolbar.component';
|
||||
import { NotificationService } from '../../../shared/services/notifications/notification.service';
|
||||
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
|
||||
import { QueryPagingParameter } from '../../../shared/models/query-paging-parameter';
|
||||
import { TaskQueryFilterParameter } from '../../../shared/models/task-query-filter-parameter';
|
||||
import { Select, Store } from '@ngxs/store';
|
||||
|
@ -144,7 +143,7 @@ export class TaskMasterComponent implements OnInit, OnDestroy {
|
|||
} else {
|
||||
this.tasks = [];
|
||||
if (this.selectedSearchType === Search.byWorkbasket) {
|
||||
this.notificationsService.showToast(NOTIFICATION_TYPES.INFO_ALERT_2);
|
||||
this.notificationsService.showInformation('EMPTY_WORKBASKET');
|
||||
}
|
||||
}
|
||||
this.tasksPageInformation = taskResource.page;
|
||||
|
|
|
@ -502,3 +502,7 @@ li.list-group-item:hover {
|
|||
.mat-select-value-text {
|
||||
color: #4a5568 !important;
|
||||
}
|
||||
|
||||
.hot-toast-icon {
|
||||
align-self: center !important
|
||||
}
|
||||
|
|
|
@ -1502,6 +1502,20 @@
|
|||
merge-source-map "^1.1.0"
|
||||
schema-utils "^2.7.0"
|
||||
|
||||
"@ngneat/hot-toast@3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@ngneat/hot-toast/-/hot-toast-3.1.0.tgz#1338530c70d77643abf9fc344c1e6f600f2e8683"
|
||||
integrity sha512-lbBEkPf2/I6L7yXFK3bssP/yNwT/uV5P/fuMgWeB8t4gHIPl9aTwF+j2DWhYEJGoDDlztvaXaimV6Yt2C8l0+Q==
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@ngneat/overview@2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@ngneat/overview/-/overview-2.0.2.tgz#557dbb801e8b3e42d3cda57f5feb79319ef2f120"
|
||||
integrity sha512-BARS4lUrWW5BidZS6jKtZc3okV7ro+3QKxJKs1FgeDDSomNdNACEjB0BMITeulEQ+agE4n9U+sbGpF2guOXDQA==
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@ngtools/webpack@12.0.5":
|
||||
version "12.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-12.0.5.tgz#3d858c1df3a4f5a13450fb0cffe0d5db8e61d0e1"
|
||||
|
|
Loading…
Reference in New Issue