mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
committed by
Lorenz Hilpert
parent
50cc17a44b
commit
39147d7afa
@@ -1,7 +1,7 @@
|
||||
import { Inject, Injectable, InjectionToken } from '@angular/core';
|
||||
import { SignalrHub, SignalRHubOptions } from '@core/signalr';
|
||||
import { merge, of } from 'rxjs';
|
||||
import { filter, shareReplay, tap } from 'rxjs/operators';
|
||||
import { filter, map, publishReplay, shareReplay, tap } from 'rxjs/operators';
|
||||
import { EnvelopeDTO, MessageBoardItemDTO } from './defs';
|
||||
|
||||
export const NOTIFICATIONS_HUB_OPTIONS = new InjectionToken<SignalRHubOptions>('hub.notifications.options');
|
||||
@@ -33,4 +33,21 @@ export class NotificationsHub extends SignalrHub {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
updateNotification() {
|
||||
this.notifications$ = this.notifications$.pipe(
|
||||
map((data) => {
|
||||
const notifications = data;
|
||||
if (!!notifications?.data && !notifications?.data?.find((notification) => notification?.category === 'ISA-Update')) {
|
||||
notifications.data.push({
|
||||
category: 'ISA-Update',
|
||||
type: 'update',
|
||||
headline: 'Update Benachrichtigung',
|
||||
text: 'Es steht eine aktuellere Version der ISA bereit. Bitte aktualisieren Sie die Anwendung.',
|
||||
});
|
||||
}
|
||||
return notifications;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,22 @@ import { ApplicationService } from '@core/application';
|
||||
import { of } from 'rxjs';
|
||||
import { Renderer2 } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ServiceWorkerModule, SwUpdate } from '@angular/service-worker';
|
||||
import { NotificationsHub } from '@hub/notifications';
|
||||
import { discardPeriodicTasks, fakeAsync, flush, tick } from '@angular/core/testing';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let spectator: Spectator<AppComponent>;
|
||||
let config: SpyObject<Config>;
|
||||
let renderer: SpyObject<Renderer2>;
|
||||
let applicationServiceMock: SpyObject<ApplicationService>;
|
||||
let notificationsHubMock: SpyObject<NotificationsHub>;
|
||||
let swUpdateMock: SpyObject<SwUpdate>;
|
||||
const createComponent = createComponentFactory({
|
||||
component: AppComponent,
|
||||
imports: [CommonModule, RouterTestingModule],
|
||||
providers: [],
|
||||
mocks: [Config],
|
||||
mocks: [Config, SwUpdate],
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -25,6 +30,10 @@ describe('AppComponent', () => {
|
||||
applicationServiceMock.getActivatedProcessId$.and.returnValue(of(undefined));
|
||||
renderer = jasmine.createSpyObj('Renderer2', ['addClass', 'removeClass']);
|
||||
|
||||
notificationsHubMock = createSpyObject(NotificationsHub);
|
||||
notificationsHubMock.notifications$ = of({});
|
||||
swUpdateMock = createSpyObject(SwUpdate);
|
||||
|
||||
spectator = createComponent({
|
||||
providers: [
|
||||
{ provide: ApplicationService, useValue: applicationServiceMock },
|
||||
@@ -32,6 +41,8 @@ describe('AppComponent', () => {
|
||||
provide: Renderer2,
|
||||
useValue: renderer,
|
||||
},
|
||||
{ provide: NotificationsHub, useValue: notificationsHubMock },
|
||||
{ provide: SwUpdate, useValue: swUpdateMock },
|
||||
],
|
||||
});
|
||||
config = spectator.inject(Config);
|
||||
@@ -77,13 +88,45 @@ describe('AppComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// describe('sectionChangeHandler', () => {
|
||||
// fit('should add class customer and remove class branch from body when section is customer', () => {
|
||||
// spectator.component.sectionChangeHandler('customer');
|
||||
// console.log(renderer);
|
||||
// console.log('expect');
|
||||
// expect(renderer.removeClass).toHaveBeenCalled();
|
||||
// expect(renderer.addClass).toHaveBeenCalled();
|
||||
// });
|
||||
// });
|
||||
describe('updateClient()', () => {
|
||||
it('should call checkForUpdate() if SwUpdate.isEnabled is True', () => {
|
||||
spyOn(spectator.component, 'checkForUpdate');
|
||||
spyOn(spectator.component, 'initialCheckForUpdate');
|
||||
(swUpdateMock as any).isEnabled = true;
|
||||
spectator.component.updateClient();
|
||||
expect(spectator.component.initialCheckForUpdate).toHaveBeenCalled();
|
||||
expect(spectator.component.checkForUpdate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call checkForUpdate() if SwUpdate.isEnabled is False', () => {
|
||||
spyOn(spectator.component, 'checkForUpdate');
|
||||
spyOn(spectator.component, 'initialCheckForUpdate');
|
||||
(swUpdateMock as any).isEnabled = false;
|
||||
spectator.component.updateClient();
|
||||
expect(spectator.component.initialCheckForUpdate).not.toHaveBeenCalled();
|
||||
expect(spectator.component.checkForUpdate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkForUpdate', () => {
|
||||
it('should call swUpdate.checkForUpdate() and notifications.updateNotification() every second', fakeAsync(() => {
|
||||
swUpdateMock.checkForUpdate.and.returnValue(Promise.resolve());
|
||||
spectator.component.checkForUpdates = 1000;
|
||||
spectator.component.checkForUpdate();
|
||||
|
||||
spectator.detectChanges();
|
||||
tick(1100);
|
||||
|
||||
expect(notificationsHubMock.updateNotification).toHaveBeenCalled();
|
||||
discardPeriodicTasks();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('initialCheckForUpdate', () => {
|
||||
it('should call swUpdate.checkForUpdate()', () => {
|
||||
swUpdateMock.checkForUpdate.and.returnValue(new Promise(undefined));
|
||||
spectator.component.initialCheckForUpdate();
|
||||
expect(swUpdateMock.checkForUpdate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { Component, Inject, OnInit, Renderer2 } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { Config } from '@core/config';
|
||||
import { NotificationsHub } from '@hub/notifications';
|
||||
import packageInfo from 'package';
|
||||
import { interval } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -11,13 +14,28 @@ import packageInfo from 'package';
|
||||
styleUrls: ['./app.component.scss'],
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
_checkForUpdates: number = this._config.get('checkForUpdates');
|
||||
|
||||
get checkForUpdates(): number {
|
||||
return this._checkForUpdates;
|
||||
}
|
||||
|
||||
// For Unit Testing
|
||||
set checkForUpdates(time: number) {
|
||||
this._checkForUpdates = time;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly _config: Config,
|
||||
private readonly _title: Title,
|
||||
private readonly _appService: ApplicationService,
|
||||
@Inject(DOCUMENT) private readonly _document: Document,
|
||||
private readonly _renderer: Renderer2
|
||||
) {}
|
||||
private readonly _renderer: Renderer2,
|
||||
private readonly _swUpdate: SwUpdate,
|
||||
private readonly _notifications: NotificationsHub
|
||||
) {
|
||||
this.updateClient();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.setTitle();
|
||||
@@ -43,4 +61,26 @@ export class AppComponent implements OnInit {
|
||||
this._renderer.addClass(this._document.body, 'branch');
|
||||
}
|
||||
}
|
||||
|
||||
updateClient() {
|
||||
this.checkForUpdate();
|
||||
if (!this._swUpdate.isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.initialCheckForUpdate();
|
||||
this.checkForUpdate();
|
||||
}
|
||||
|
||||
checkForUpdate() {
|
||||
interval(this._checkForUpdates).subscribe(() => {
|
||||
this._swUpdate.checkForUpdate().then(() => {
|
||||
this._notifications.updateNotification();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
initialCheckForUpdate() {
|
||||
this._swUpdate.checkForUpdate().then(() => location.reload());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,7 +343,7 @@ describe('ShellComponent', () => {
|
||||
applicationServiceMock.getSection$.and.returnValue(of('customer'));
|
||||
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
|
||||
await spectator.component.closeProcess(1);
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
expect(router.navigate).not.toHaveBeenCalledWith(['/kunde', 'dashboard']);
|
||||
});
|
||||
|
||||
it('should activate the next process when it was not the last process', async () => {
|
||||
|
||||
@@ -97,7 +97,7 @@ export class ShellComponent {
|
||||
}
|
||||
|
||||
async activateProcess(activatedProcessId: number) {
|
||||
const latestCrumb = await this._breadcrumbService.getLastActivatedBreadcrumbByKey$(activatedProcessId).pipe(first()).toPromise();
|
||||
const latestCrumb = await this._breadcrumbService?.getLastActivatedBreadcrumbByKey$(activatedProcessId)?.pipe(first()).toPromise();
|
||||
|
||||
if (latestCrumb) {
|
||||
await this._router.navigate([latestCrumb.path], { queryParams: latestCrumb.params });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"title": "ISA - Local",
|
||||
"title": "ISA - Feature",
|
||||
"@cdn/product-image": {
|
||||
"url": "https://produktbilder.paragon-data.net"
|
||||
},
|
||||
@@ -58,5 +58,6 @@
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000
|
||||
}
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000
|
||||
}
|
||||
@@ -17,5 +17,6 @@
|
||||
"skipNegotiation": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000
|
||||
}
|
||||
@@ -58,5 +58,6 @@
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000
|
||||
}
|
||||
|
||||
@@ -17,5 +17,6 @@
|
||||
"skipNegotiation": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000
|
||||
}
|
||||
@@ -17,5 +17,6 @@
|
||||
"skipNegotiation": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000
|
||||
}
|
||||
@@ -58,5 +58,6 @@
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000
|
||||
}
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<div class="header">
|
||||
<div class="notification-icon">
|
||||
<span class="notification-counter">{{ notifications.length }}</span>
|
||||
<ui-icon icon="notification" size="26px"></ui-icon>
|
||||
</div>
|
||||
|
||||
<h2>ISA-Update</h2>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div class="notification-list scroll-bar">
|
||||
<ng-container *ngFor="let notification of notifications">
|
||||
<div class="notification-headline">
|
||||
<h1>{{ notification.headline }}</h1>
|
||||
</div>
|
||||
<div class="notification-text">{{ notification.text }}</div>
|
||||
<hr />
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="cta-primary" (click)="reload()">
|
||||
Aktualisieren
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,41 @@
|
||||
import { createComponentFactory, Spectator } from '@ngneat/spectator';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { ModalNotificationsUpdateGroupComponent } from './notifications-update-group.component';
|
||||
|
||||
describe('ModalNotificationsUpdateGroupComponent', () => {
|
||||
let spectator: Spectator<ModalNotificationsUpdateGroupComponent>;
|
||||
|
||||
const createComponent = createComponentFactory({
|
||||
component: ModalNotificationsUpdateGroupComponent,
|
||||
imports: [CommonModule, UiIconModule],
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
spectator = createComponent({ props: { notifications: [] } });
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(spectator.component).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('notifications input', () => {
|
||||
it('should display the right notification-counter value based on the length of the input array', () => {
|
||||
spectator.setInput({ notifications: [{}, {}, {}] });
|
||||
expect(spectator.query('.notification-counter')).toHaveText('3');
|
||||
});
|
||||
|
||||
it('should not display notification-counter if input array has length 0', () => {
|
||||
spectator.setInput({ notifications: [] });
|
||||
expect(spectator.query('.notification-counter')).toHaveText('');
|
||||
});
|
||||
|
||||
it('should render notification-headline and notification-text based on the input array', () => {
|
||||
const notifications = [{}, {}];
|
||||
spectator.setInput({ notifications });
|
||||
spectator.detectComponentChanges();
|
||||
expect(spectator.queryAll('.notification-headline')).toHaveLength(notifications.length);
|
||||
expect(spectator.queryAll('.notification-text')).toHaveLength(notifications.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
|
||||
|
||||
@Component({
|
||||
selector: 'modal-notifications-update-group',
|
||||
templateUrl: 'notifications-update-group.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ModalNotificationsUpdateGroupComponent {
|
||||
@Input()
|
||||
notifications: MessageBoardItemDTO[];
|
||||
|
||||
constructor() {}
|
||||
|
||||
reload() {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,10 @@
|
||||
</ng-container>
|
||||
|
||||
<ng-container [ngSwitch]="activeCard$ | async">
|
||||
<modal-notifications-update-group
|
||||
*ngSwitchCase="'ISA-Update'"
|
||||
[notifications]="activeNotifications$ | async"
|
||||
></modal-notifications-update-group>
|
||||
<modal-notifications-reservation-group
|
||||
*ngSwitchCase="'Reservierungsanfragen'"
|
||||
[notifications]="activeNotifications$ | async"
|
||||
|
||||
@@ -12,7 +12,8 @@ modal-notifications {
|
||||
|
||||
modal-notifications-remission-group,
|
||||
modal-notifications-reservation-group,
|
||||
modal-notifications-task-calendar-group {
|
||||
modal-notifications-task-calendar-group,
|
||||
modal-notifications-update-group {
|
||||
@apply flex flex-col relative pb-2;
|
||||
|
||||
.header {
|
||||
@@ -57,7 +58,8 @@ modal-notifications {
|
||||
}
|
||||
}
|
||||
|
||||
modal-notifications-list-item {
|
||||
modal-notifications-list-item,
|
||||
modal-notifications-update-group {
|
||||
@apply flex flex-col relative py-1 px-4;
|
||||
|
||||
.notification-headline {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ModalNotificationsListItemComponent } from './notifications-list-item/n
|
||||
import { ModalNotificationsRemissionGroupComponent } from './notifications-remission-group/notifications-remission-group.component';
|
||||
import { ModalNotificationsReservationGroupComponent } from './notifications-reservation-group/notifications-reservation-group.component';
|
||||
import { ModalNotificationsTaskCalendarGroupComponent } from './notifications-task-calendar-group/notifications-task-calendar-group.component';
|
||||
import { ModalNotificationsUpdateGroupComponent } from './notifications-update-group/notifications-update-group.component';
|
||||
import { ModalNotificationsComponent } from './notifications.component';
|
||||
|
||||
@NgModule({
|
||||
@@ -16,6 +17,7 @@ import { ModalNotificationsComponent } from './notifications.component';
|
||||
ModalNotificationsReservationGroupComponent,
|
||||
ModalNotificationsRemissionGroupComponent,
|
||||
ModalNotificationsTaskCalendarGroupComponent,
|
||||
ModalNotificationsUpdateGroupComponent,
|
||||
ModalNotificationsListItemComponent,
|
||||
],
|
||||
exports: [
|
||||
@@ -23,6 +25,7 @@ import { ModalNotificationsComponent } from './notifications.component';
|
||||
ModalNotificationsReservationGroupComponent,
|
||||
ModalNotificationsRemissionGroupComponent,
|
||||
ModalNotificationsTaskCalendarGroupComponent,
|
||||
ModalNotificationsUpdateGroupComponent,
|
||||
ModalNotificationsListItemComponent,
|
||||
],
|
||||
})
|
||||
|
||||
@@ -115,7 +115,6 @@ jobs:
|
||||
demands:
|
||||
- Agent.OS -equals Linux
|
||||
- docker
|
||||
# condition: eq(variables['Build.SourceBranch'], 'refs/heads/develop')
|
||||
condition: and(ne(variables['Build.SourceBranch'], 'refs/heads/integration'), ne(variables['Build.SourceBranch'], 'refs/heads/master'), not(startsWith(variables['Build.SourceBranch'], 'refs/heads/hotfix/')), not(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/')))
|
||||
steps:
|
||||
- task: npmAuthenticate@0
|
||||
|
||||
Reference in New Issue
Block a user