Very annoying refactor

This commit is contained in:
shamoon
2025-03-04 08:53:11 -08:00
parent 4488da6d3d
commit f28accb28f
81 changed files with 1315 additions and 1151 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
<pngx-toasts></pngx-toasts> <pngx-notification-list></pngx-notification-list>
<pngx-file-drop> <pngx-file-drop>
<ng-container content> <ng-container content>

View File

@@ -14,14 +14,17 @@ import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap'
import { Subject } from 'rxjs' import { Subject } from 'rxjs'
import { routes } from './app-routing.module' import { routes } from './app-routing.module'
import { AppComponent } from './app.component' import { AppComponent } from './app.component'
import { ToastsComponent } from './components/common/toasts/toasts.component' import { NotificationListComponent } from './components/common/notification-list/notification-list.component'
import { FileDropComponent } from './components/file-drop/file-drop.component' import { FileDropComponent } from './components/file-drop/file-drop.component'
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard' import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
import { PermissionsGuard } from './guards/permissions.guard' import { PermissionsGuard } from './guards/permissions.guard'
import { HotKeyService } from './services/hot-key.service' import { HotKeyService } from './services/hot-key.service'
import {
Notification,
NotificationService,
} from './services/notification.service'
import { PermissionsService } from './services/permissions.service' import { PermissionsService } from './services/permissions.service'
import { SettingsService } from './services/settings.service' import { SettingsService } from './services/settings.service'
import { Toast, ToastService } from './services/toast.service'
import { import {
FileStatus, FileStatus,
WebsocketStatusService, WebsocketStatusService,
@@ -33,7 +36,7 @@ describe('AppComponent', () => {
let tourService: TourService let tourService: TourService
let websocketStatusService: WebsocketStatusService let websocketStatusService: WebsocketStatusService
let permissionsService: PermissionsService let permissionsService: PermissionsService
let toastService: ToastService let notificationService: NotificationService
let router: Router let router: Router
let settingsService: SettingsService let settingsService: SettingsService
let hotKeyService: HotKeyService let hotKeyService: HotKeyService
@@ -46,7 +49,7 @@ describe('AppComponent', () => {
NgxFileDropModule, NgxFileDropModule,
NgbModalModule, NgbModalModule,
AppComponent, AppComponent,
ToastsComponent, NotificationListComponent,
FileDropComponent, FileDropComponent,
NgxBootstrapIconsModule.pick(allIcons), NgxBootstrapIconsModule.pick(allIcons),
], ],
@@ -62,7 +65,7 @@ describe('AppComponent', () => {
websocketStatusService = TestBed.inject(WebsocketStatusService) websocketStatusService = TestBed.inject(WebsocketStatusService)
permissionsService = TestBed.inject(PermissionsService) permissionsService = TestBed.inject(PermissionsService)
settingsService = TestBed.inject(SettingsService) settingsService = TestBed.inject(SettingsService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
router = TestBed.inject(Router) router = TestBed.inject(Router)
hotKeyService = TestBed.inject(HotKeyService) hotKeyService = TestBed.inject(HotKeyService)
fixture = TestBed.createComponent(AppComponent) fixture = TestBed.createComponent(AppComponent)
@@ -82,12 +85,14 @@ describe('AppComponent', () => {
expect(document.body.classList).not.toContain('tour-active') expect(document.body.classList).not.toContain('tour-active')
})) }))
it('should display toast on document consumed with link if user has access', () => { it('should display notification on document consumed with link if user has access', () => {
const navigateSpy = jest.spyOn(router, 'navigate') const navigateSpy = jest.spyOn(router, 'navigate')
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
let toast: Toast let notification: Notification
toastService.getToasts().subscribe((toasts) => (toast = toasts[0])) notificationService
const toastSpy = jest.spyOn(toastService, 'show') .getNotifications()
.subscribe((notifications) => (notification = notifications[0]))
const notificationSpy = jest.spyOn(notificationService, 'show')
const fileStatusSubject = new Subject<FileStatus>() const fileStatusSubject = new Subject<FileStatus>()
jest jest
.spyOn(websocketStatusService, 'onDocumentConsumptionFinished') .spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
@@ -96,63 +101,65 @@ describe('AppComponent', () => {
const status = new FileStatus() const status = new FileStatus()
status.documentId = 1 status.documentId = 1
fileStatusSubject.next(status) fileStatusSubject.next(status)
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
expect(toast.action).not.toBeUndefined() expect(notification.action).not.toBeUndefined()
toast.action() notification.action()
expect(navigateSpy).toHaveBeenCalledWith(['documents', status.documentId]) expect(navigateSpy).toHaveBeenCalledWith(['documents', status.documentId])
}) })
it('should display toast on document consumed without link if user does not have access', () => { it('should display notification on document consumed without link if user does not have access', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(false) jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(false)
let toast: Toast let notification: Notification
toastService.getToasts().subscribe((toasts) => (toast = toasts[0])) notificationService
const toastSpy = jest.spyOn(toastService, 'show') .getNotifications()
.subscribe((notifications) => (notification = notifications[0]))
const notificationSpy = jest.spyOn(notificationService, 'show')
const fileStatusSubject = new Subject<FileStatus>() const fileStatusSubject = new Subject<FileStatus>()
jest jest
.spyOn(websocketStatusService, 'onDocumentConsumptionFinished') .spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
.mockReturnValue(fileStatusSubject) .mockReturnValue(fileStatusSubject)
component.ngOnInit() component.ngOnInit()
fileStatusSubject.next(new FileStatus()) fileStatusSubject.next(new FileStatus())
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
expect(toast.action).toBeUndefined() expect(notification.action).toBeUndefined()
}) })
it('should display toast on document added', () => { it('should display notification on document added', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
const toastSpy = jest.spyOn(toastService, 'show') const notificationSpy = jest.spyOn(notificationService, 'show')
const fileStatusSubject = new Subject<FileStatus>() const fileStatusSubject = new Subject<FileStatus>()
jest jest
.spyOn(websocketStatusService, 'onDocumentDetected') .spyOn(websocketStatusService, 'onDocumentDetected')
.mockReturnValue(fileStatusSubject) .mockReturnValue(fileStatusSubject)
component.ngOnInit() component.ngOnInit()
fileStatusSubject.next(new FileStatus()) fileStatusSubject.next(new FileStatus())
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
}) })
it('should suppress dashboard notifications if set', () => { it('should suppress dashboard notifications if set', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest.spyOn(settingsService, 'get').mockReturnValue(true) jest.spyOn(settingsService, 'get').mockReturnValue(true)
jest.spyOn(router, 'url', 'get').mockReturnValue('/dashboard') jest.spyOn(router, 'url', 'get').mockReturnValue('/dashboard')
const toastSpy = jest.spyOn(toastService, 'show') const notificationSpy = jest.spyOn(notificationService, 'show')
const fileStatusSubject = new Subject<FileStatus>() const fileStatusSubject = new Subject<FileStatus>()
jest jest
.spyOn(websocketStatusService, 'onDocumentDetected') .spyOn(websocketStatusService, 'onDocumentDetected')
.mockReturnValue(fileStatusSubject) .mockReturnValue(fileStatusSubject)
component.ngOnInit() component.ngOnInit()
fileStatusSubject.next(new FileStatus()) fileStatusSubject.next(new FileStatus())
expect(toastSpy).not.toHaveBeenCalled() expect(notificationSpy).not.toHaveBeenCalled()
}) })
it('should display toast on document failed', () => { it('should display notification on document failed', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
const toastSpy = jest.spyOn(toastService, 'showError') const notificationSpy = jest.spyOn(notificationService, 'showError')
const fileStatusSubject = new Subject<FileStatus>() const fileStatusSubject = new Subject<FileStatus>()
jest jest
.spyOn(websocketStatusService, 'onDocumentConsumptionFailed') .spyOn(websocketStatusService, 'onDocumentConsumptionFailed')
.mockReturnValue(fileStatusSubject) .mockReturnValue(fileStatusSubject)
component.ngOnInit() component.ngOnInit()
fileStatusSubject.next(new FileStatus()) fileStatusSubject.next(new FileStatus())
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
}) })
it('should support hotkeys', () => { it('should support hotkeys', () => {

View File

@@ -2,11 +2,12 @@ import { Component, OnDestroy, OnInit, Renderer2 } from '@angular/core'
import { Router, RouterOutlet } from '@angular/router' import { Router, RouterOutlet } from '@angular/router'
import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap' import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap'
import { first, Subscription } from 'rxjs' import { first, Subscription } from 'rxjs'
import { ToastsComponent } from './components/common/toasts/toasts.component' import { NotificationListComponent } from './components/common/notification-list/notification-list.component'
import { FileDropComponent } from './components/file-drop/file-drop.component' import { FileDropComponent } from './components/file-drop/file-drop.component'
import { SETTINGS_KEYS } from './data/ui-settings' import { SETTINGS_KEYS } from './data/ui-settings'
import { ComponentRouterService } from './services/component-router.service' import { ComponentRouterService } from './services/component-router.service'
import { HotKeyService } from './services/hot-key.service' import { HotKeyService } from './services/hot-key.service'
import { NotificationService } from './services/notification.service'
import { import {
PermissionAction, PermissionAction,
PermissionsService, PermissionsService,
@@ -14,7 +15,6 @@ import {
} from './services/permissions.service' } from './services/permissions.service'
import { SettingsService } from './services/settings.service' import { SettingsService } from './services/settings.service'
import { TasksService } from './services/tasks.service' import { TasksService } from './services/tasks.service'
import { ToastService } from './services/toast.service'
import { WebsocketStatusService } from './services/websocket-status.service' import { WebsocketStatusService } from './services/websocket-status.service'
@Component({ @Component({
@@ -23,7 +23,7 @@ import { WebsocketStatusService } from './services/websocket-status.service'
styleUrls: ['./app.component.scss'], styleUrls: ['./app.component.scss'],
imports: [ imports: [
FileDropComponent, FileDropComponent,
ToastsComponent, NotificationListComponent,
TourNgBootstrapModule, TourNgBootstrapModule,
RouterOutlet, RouterOutlet,
], ],
@@ -36,7 +36,7 @@ export class AppComponent implements OnInit, OnDestroy {
constructor( constructor(
private settings: SettingsService, private settings: SettingsService,
private websocketStatusService: WebsocketStatusService, private websocketStatusService: WebsocketStatusService,
private toastService: ToastService, private notificationService: NotificationService,
private router: Router, private router: Router,
private tasksService: TasksService, private tasksService: TasksService,
public tourService: TourService, public tourService: TourService,
@@ -91,7 +91,7 @@ export class AppComponent implements OnInit, OnDestroy {
PermissionType.Document PermissionType.Document
) )
) { ) {
this.toastService.show({ this.notificationService.show({
content: $localize`Document ${status.filename} was added to Paperless-ngx.`, content: $localize`Document ${status.filename} was added to Paperless-ngx.`,
delay: 10000, delay: 10000,
actionName: $localize`Open document`, actionName: $localize`Open document`,
@@ -100,7 +100,7 @@ export class AppComponent implements OnInit, OnDestroy {
}, },
}) })
} else { } else {
this.toastService.show({ this.notificationService.show({
content: $localize`Document ${status.filename} was added to Paperless-ngx.`, content: $localize`Document ${status.filename} was added to Paperless-ngx.`,
delay: 10000, delay: 10000,
}) })
@@ -115,7 +115,7 @@ export class AppComponent implements OnInit, OnDestroy {
if ( if (
this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED) this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED)
) { ) {
this.toastService.showError( this.notificationService.showError(
$localize`Could not add ${status.filename}\: ${status.message}` $localize`Could not add ${status.filename}\: ${status.message}`
) )
} }
@@ -130,7 +130,7 @@ export class AppComponent implements OnInit, OnDestroy {
SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT
) )
) { ) {
this.toastService.show({ this.notificationService.show({
content: $localize`Document ${status.filename} is being processed by Paperless-ngx.`, content: $localize`Document ${status.filename} is being processed by Paperless-ngx.`,
delay: 5000, delay: 5000,
}) })

View File

@@ -10,8 +10,8 @@ import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs' import { of, throwError } from 'rxjs'
import { OutputTypeConfig } from 'src/app/data/paperless-config' import { OutputTypeConfig } from 'src/app/data/paperless-config'
import { ConfigService } from 'src/app/services/config.service' import { ConfigService } from 'src/app/services/config.service'
import { NotificationService } from 'src/app/services/notification.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { FileComponent } from '../../common/input/file/file.component' import { FileComponent } from '../../common/input/file/file.component'
import { NumberComponent } from '../../common/input/number/number.component' import { NumberComponent } from '../../common/input/number/number.component'
import { SelectComponent } from '../../common/input/select/select.component' import { SelectComponent } from '../../common/input/select/select.component'
@@ -24,7 +24,7 @@ describe('ConfigComponent', () => {
let component: ConfigComponent let component: ConfigComponent
let fixture: ComponentFixture<ConfigComponent> let fixture: ComponentFixture<ConfigComponent>
let configService: ConfigService let configService: ConfigService
let toastService: ToastService let notificationService: NotificationService
let settingService: SettingsService let settingService: SettingsService
beforeEach(async () => { beforeEach(async () => {
@@ -51,7 +51,7 @@ describe('ConfigComponent', () => {
}).compileComponents() }).compileComponents()
configService = TestBed.inject(ConfigService) configService = TestBed.inject(ConfigService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
settingService = TestBed.inject(SettingsService) settingService = TestBed.inject(SettingsService)
fixture = TestBed.createComponent(ConfigComponent) fixture = TestBed.createComponent(ConfigComponent)
component = fixture.componentInstance component = fixture.componentInstance
@@ -60,7 +60,7 @@ describe('ConfigComponent', () => {
it('should load config on init, show error if necessary', () => { it('should load config on init, show error if necessary', () => {
const getSpy = jest.spyOn(configService, 'getConfig') const getSpy = jest.spyOn(configService, 'getConfig')
const errorSpy = jest.spyOn(toastService, 'showError') const errorSpy = jest.spyOn(notificationService, 'showError')
getSpy.mockReturnValueOnce( getSpy.mockReturnValueOnce(
throwError(() => new Error('Error getting config')) throwError(() => new Error('Error getting config'))
) )
@@ -78,7 +78,7 @@ describe('ConfigComponent', () => {
it('should save config, show error if necessary', () => { it('should save config, show error if necessary', () => {
const saveSpy = jest.spyOn(configService, 'saveConfig') const saveSpy = jest.spyOn(configService, 'saveConfig')
const errorSpy = jest.spyOn(toastService, 'showError') const errorSpy = jest.spyOn(notificationService, 'showError')
saveSpy.mockReturnValueOnce( saveSpy.mockReturnValueOnce(
throwError(() => new Error('Error saving config')) throwError(() => new Error('Error saving config'))
) )
@@ -112,7 +112,7 @@ describe('ConfigComponent', () => {
it('should upload file, show error if necessary', () => { it('should upload file, show error if necessary', () => {
const uploadSpy = jest.spyOn(configService, 'uploadFile') const uploadSpy = jest.spyOn(configService, 'uploadFile')
const errorSpy = jest.spyOn(toastService, 'showError') const errorSpy = jest.spyOn(notificationService, 'showError')
uploadSpy.mockReturnValueOnce( uploadSpy.mockReturnValueOnce(
throwError(() => new Error('Error uploading file')) throwError(() => new Error('Error uploading file'))
) )

View File

@@ -25,8 +25,8 @@ import {
PaperlessConfigOptions, PaperlessConfigOptions,
} from 'src/app/data/paperless-config' } from 'src/app/data/paperless-config'
import { ConfigService } from 'src/app/services/config.service' import { ConfigService } from 'src/app/services/config.service'
import { NotificationService } from 'src/app/services/notification.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { FileComponent } from '../../common/input/file/file.component' import { FileComponent } from '../../common/input/file/file.component'
import { NumberComponent } from '../../common/input/number/number.component' import { NumberComponent } from '../../common/input/number/number.component'
import { SelectComponent } from '../../common/input/select/select.component' import { SelectComponent } from '../../common/input/select/select.component'
@@ -79,7 +79,7 @@ export class ConfigComponent
constructor( constructor(
private configService: ConfigService, private configService: ConfigService,
private toastService: ToastService, private notificationService: NotificationService,
private settingsService: SettingsService private settingsService: SettingsService
) { ) {
super() super()
@@ -100,7 +100,10 @@ export class ConfigComponent
}, },
error: (e) => { error: (e) => {
this.loading = false this.loading = false
this.toastService.showError($localize`Error retrieving config`, e) this.notificationService.showError(
$localize`Error retrieving config`,
e
)
}, },
}) })
@@ -170,11 +173,11 @@ export class ConfigComponent
this.initialize(config) this.initialize(config)
this.store.next(config) this.store.next(config)
this.settingsService.initializeSettings().subscribe() this.settingsService.initializeSettings().subscribe()
this.toastService.showInfo($localize`Configuration updated`) this.notificationService.showInfo($localize`Configuration updated`)
}, },
error: (e) => { error: (e) => {
this.loading = false this.loading = false
this.toastService.showError( this.notificationService.showError(
$localize`An error occurred updating configuration`, $localize`An error occurred updating configuration`,
e e
) )
@@ -197,11 +200,13 @@ export class ConfigComponent
this.initialize(config) this.initialize(config)
this.store.next(config) this.store.next(config)
this.settingsService.initializeSettings().subscribe() this.settingsService.initializeSettings().subscribe()
this.toastService.showInfo($localize`File successfully updated`) this.notificationService.showInfo(
$localize`File successfully updated`
)
}, },
error: (e) => { error: (e) => {
this.loading = false this.loading = false
this.toastService.showError( this.notificationService.showError(
$localize`An error occurred uploading file`, $localize`An error occurred uploading file`,
e e
) )

View File

@@ -29,12 +29,15 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import {
Notification,
NotificationService,
} from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { GroupService } from 'src/app/services/rest/group.service' import { GroupService } from 'src/app/services/rest/group.service'
import { UserService } from 'src/app/services/rest/user.service' import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { SystemStatusService } from 'src/app/services/system-status.service' import { SystemStatusService } from 'src/app/services/system-status.service'
import { Toast, ToastService } from 'src/app/services/toast.service'
import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component' import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { CheckComponent } from '../../common/input/check/check.component' import { CheckComponent } from '../../common/input/check/check.component'
@@ -66,7 +69,7 @@ describe('SettingsComponent', () => {
let settingsService: SettingsService let settingsService: SettingsService
let activatedRoute: ActivatedRoute let activatedRoute: ActivatedRoute
let viewportScroller: ViewportScroller let viewportScroller: ViewportScroller
let toastService: ToastService let notificationService: NotificationService
let userService: UserService let userService: UserService
let permissionsService: PermissionsService let permissionsService: PermissionsService
let groupService: GroupService let groupService: GroupService
@@ -115,7 +118,7 @@ describe('SettingsComponent', () => {
router = TestBed.inject(Router) router = TestBed.inject(Router)
activatedRoute = TestBed.inject(ActivatedRoute) activatedRoute = TestBed.inject(ActivatedRoute)
viewportScroller = TestBed.inject(ViewportScroller) viewportScroller = TestBed.inject(ViewportScroller)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
settingsService = TestBed.inject(SettingsService) settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = users[0] settingsService.currentUser = users[0]
userService = TestBed.inject(UserService) userService = TestBed.inject(UserService)
@@ -194,8 +197,8 @@ describe('SettingsComponent', () => {
it('should support save local settings updating appearance settings and calling API, show error', () => { it('should support save local settings updating appearance settings and calling API, show error', () => {
completeSetup() completeSetup()
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastSpy = jest.spyOn(toastService, 'show') const notificationSpy = jest.spyOn(notificationService, 'show')
const storeSpy = jest.spyOn(settingsService, 'storeSettings') const storeSpy = jest.spyOn(settingsService, 'storeSettings')
const appearanceSettingsSpy = jest.spyOn( const appearanceSettingsSpy = jest.spyOn(
settingsService, settingsService,
@@ -209,7 +212,7 @@ describe('SettingsComponent', () => {
) )
component.saveSettings() component.saveSettings()
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
expect(storeSpy).toHaveBeenCalled() expect(storeSpy).toHaveBeenCalled()
expect(appearanceSettingsSpy).not.toHaveBeenCalled() expect(appearanceSettingsSpy).not.toHaveBeenCalled()
expect(setSpy).toHaveBeenCalledTimes(29) expect(setSpy).toHaveBeenCalledTimes(29)
@@ -217,14 +220,14 @@ describe('SettingsComponent', () => {
// succeed // succeed
storeSpy.mockReturnValueOnce(of(true)) storeSpy.mockReturnValueOnce(of(true))
component.saveSettings() component.saveSettings()
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
expect(appearanceSettingsSpy).toHaveBeenCalled() expect(appearanceSettingsSpy).toHaveBeenCalled()
}) })
it('should offer reload if settings changes require', () => { it('should offer reload if settings changes require', () => {
completeSetup() completeSetup()
let toast: Toast let toast: Notification
toastService.getToasts().subscribe((t) => (toast = t[0])) notificationService.getNotifications().subscribe((t) => (toast = t[0]))
component.initialize(true) // reset component.initialize(true) // reset
component.store.getValue()['displayLanguage'] = 'en-US' component.store.getValue()['displayLanguage'] = 'en-US'
component.store.getValue()['updateCheckingEnabled'] = false component.store.getValue()['updateCheckingEnabled'] = false
@@ -258,7 +261,7 @@ describe('SettingsComponent', () => {
}) })
it('should show errors on load if load users failure', () => { it('should show errors on load if load users failure', () => {
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
jest jest
.spyOn(userService, 'listAll') .spyOn(userService, 'listAll')
.mockImplementation(() => .mockImplementation(() =>
@@ -266,11 +269,11 @@ describe('SettingsComponent', () => {
) )
completeSetup(userService) completeSetup(userService)
fixture.detectChanges() fixture.detectChanges()
expect(toastErrorSpy).toBeCalled() expect(notificationErrorSpy).toBeCalled()
}) })
it('should show errors on load if load groups failure', () => { it('should show errors on load if load groups failure', () => {
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
jest jest
.spyOn(groupService, 'listAll') .spyOn(groupService, 'listAll')
.mockImplementation(() => .mockImplementation(() =>
@@ -278,7 +281,7 @@ describe('SettingsComponent', () => {
) )
completeSetup(groupService) completeSetup(groupService)
fixture.detectChanges() fixture.detectChanges()
expect(toastErrorSpy).toBeCalled() expect(notificationErrorSpy).toBeCalled()
}) })
it('should load system status on initialize, show errors if needed', () => { it('should load system status on initialize, show errors if needed', () => {

View File

@@ -43,6 +43,10 @@ import { User } from 'src/app/data/user'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import {
Notification,
NotificationService,
} from 'src/app/services/notification.service'
import { import {
PermissionAction, PermissionAction,
PermissionType, PermissionType,
@@ -55,7 +59,6 @@ import {
SettingsService, SettingsService,
} from 'src/app/services/settings.service' } from 'src/app/services/settings.service'
import { SystemStatusService } from 'src/app/services/system-status.service' import { SystemStatusService } from 'src/app/services/system-status.service'
import { Toast, ToastService } from 'src/app/services/toast.service'
import { CheckComponent } from '../../common/input/check/check.component' import { CheckComponent } from '../../common/input/check/check.component'
import { ColorComponent } from '../../common/input/color/color.component' import { ColorComponent } from '../../common/input/color/color.component'
import { PermissionsGroupComponent } from '../../common/input/permissions/permissions-group/permissions-group.component' import { PermissionsGroupComponent } from '../../common/input/permissions/permissions-group/permissions-group.component'
@@ -181,7 +184,7 @@ export class SettingsComponent
constructor( constructor(
private documentListViewService: DocumentListViewService, private documentListViewService: DocumentListViewService,
private toastService: ToastService, private notificationService: NotificationService,
private settings: SettingsService, private settings: SettingsService,
@Inject(LOCALE_ID) public currentLocale: string, @Inject(LOCALE_ID) public currentLocale: string,
private viewportScroller: ViewportScroller, private viewportScroller: ViewportScroller,
@@ -217,7 +220,10 @@ export class SettingsComponent
this.users = r.results this.users = r.results
}, },
error: (e) => { error: (e) => {
this.toastService.showError($localize`Error retrieving users`, e) this.notificationService.showError(
$localize`Error retrieving users`,
e
)
}, },
}) })
} }
@@ -236,7 +242,10 @@ export class SettingsComponent
this.groups = r.results this.groups = r.results
}, },
error: (e) => { error: (e) => {
this.toastService.showError($localize`Error retrieving groups`, e) this.notificationService.showError(
$localize`Error retrieving groups`,
e
)
}, },
}) })
} }
@@ -531,7 +540,7 @@ export class SettingsComponent
this.store.next(this.settingsForm.value) this.store.next(this.settingsForm.value)
this.settings.updateAppearanceSettings() this.settings.updateAppearanceSettings()
this.settings.initializeDisplayFields() this.settings.initializeDisplayFields()
let savedToast: Toast = { let savedToast: Notification = {
content: $localize`Settings were saved successfully.`, content: $localize`Settings were saved successfully.`,
delay: 5000, delay: 5000,
} }
@@ -543,10 +552,10 @@ export class SettingsComponent
} }
} }
this.toastService.show(savedToast) this.notificationService.show(savedToast)
}, },
error: (error) => { error: (error) => {
this.toastService.showError( this.notificationService.showError(
$localize`An error occurred while saving settings.`, $localize`An error occurred while saving settings.`,
error error
) )

View File

@@ -12,7 +12,7 @@ import {
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs' import { of, throwError } from 'rxjs'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { ToastService } from 'src/app/services/toast.service' import { NotificationService } from 'src/app/services/notification.service'
import { TrashService } from 'src/app/services/trash.service' import { TrashService } from 'src/app/services/trash.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component' import { PageHeaderComponent } from '../../common/page-header/page-header.component'
@@ -38,7 +38,7 @@ describe('TrashComponent', () => {
let fixture: ComponentFixture<TrashComponent> let fixture: ComponentFixture<TrashComponent>
let trashService: TrashService let trashService: TrashService
let modalService: NgbModal let modalService: NgbModal
let toastService: ToastService let notificationService: NotificationService
let router: Router let router: Router
beforeEach(async () => { beforeEach(async () => {
@@ -60,7 +60,7 @@ describe('TrashComponent', () => {
fixture = TestBed.createComponent(TrashComponent) fixture = TestBed.createComponent(TrashComponent)
trashService = TestBed.inject(TrashService) trashService = TestBed.inject(TrashService)
modalService = TestBed.inject(NgbModal) modalService = TestBed.inject(NgbModal)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
router = TestBed.inject(Router) router = TestBed.inject(Router)
component = fixture.componentInstance component = fixture.componentInstance
fixture.detectChanges() fixture.detectChanges()
@@ -88,13 +88,13 @@ describe('TrashComponent', () => {
modalService.activeInstances.subscribe((instances) => { modalService.activeInstances.subscribe((instances) => {
modal = instances[0] modal = instances[0]
}) })
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
// fail first // fail first
trashSpy.mockReturnValue(throwError(() => 'Error')) trashSpy.mockReturnValue(throwError(() => 'Error'))
component.delete(documentsInTrash[0]) component.delete(documentsInTrash[0])
modal.componentInstance.confirmClicked.next() modal.componentInstance.confirmClicked.next()
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
trashSpy.mockReturnValue(of('OK')) trashSpy.mockReturnValue(of('OK'))
component.delete(documentsInTrash[0]) component.delete(documentsInTrash[0])
@@ -109,13 +109,13 @@ describe('TrashComponent', () => {
modalService.activeInstances.subscribe((instances) => { modalService.activeInstances.subscribe((instances) => {
modal = instances[instances.length - 1] modal = instances[instances.length - 1]
}) })
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
// fail first // fail first
trashSpy.mockReturnValue(throwError(() => 'Error')) trashSpy.mockReturnValue(throwError(() => 'Error'))
component.emptyTrash() component.emptyTrash()
modal.componentInstance.confirmClicked.next() modal.componentInstance.confirmClicked.next()
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
trashSpy.mockReturnValue(of('OK')) trashSpy.mockReturnValue(of('OK'))
component.emptyTrash() component.emptyTrash()
@@ -131,12 +131,12 @@ describe('TrashComponent', () => {
it('should support restore document, show error if needed', () => { it('should support restore document, show error if needed', () => {
const restoreSpy = jest.spyOn(trashService, 'restoreDocuments') const restoreSpy = jest.spyOn(trashService, 'restoreDocuments')
const reloadSpy = jest.spyOn(component, 'reload') const reloadSpy = jest.spyOn(component, 'reload')
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
// fail first // fail first
restoreSpy.mockReturnValue(throwError(() => 'Error')) restoreSpy.mockReturnValue(throwError(() => 'Error'))
component.restore(documentsInTrash[0]) component.restore(documentsInTrash[0])
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
expect(reloadSpy).not.toHaveBeenCalled() expect(reloadSpy).not.toHaveBeenCalled()
restoreSpy.mockReturnValue(of('OK')) restoreSpy.mockReturnValue(of('OK'))
@@ -148,12 +148,12 @@ describe('TrashComponent', () => {
it('should support restore all documents, show error if needed', () => { it('should support restore all documents, show error if needed', () => {
const restoreSpy = jest.spyOn(trashService, 'restoreDocuments') const restoreSpy = jest.spyOn(trashService, 'restoreDocuments')
const reloadSpy = jest.spyOn(component, 'reload') const reloadSpy = jest.spyOn(component, 'reload')
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
// fail first // fail first
restoreSpy.mockReturnValue(throwError(() => 'Error')) restoreSpy.mockReturnValue(throwError(() => 'Error'))
component.restoreAll() component.restoreAll()
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
expect(reloadSpy).not.toHaveBeenCalled() expect(reloadSpy).not.toHaveBeenCalled()
restoreSpy.mockReturnValue(of('OK')) restoreSpy.mockReturnValue(of('OK'))
@@ -167,7 +167,7 @@ describe('TrashComponent', () => {
it('should offer link to restored document', () => { it('should offer link to restored document', () => {
let toasts let toasts
const navigateSpy = jest.spyOn(router, 'navigate') const navigateSpy = jest.spyOn(router, 'navigate')
toastService.getToasts().subscribe((allToasts) => { notificationService.getNotifications().subscribe((allToasts) => {
toasts = [...allToasts] toasts = [...allToasts]
}) })
jest.spyOn(trashService, 'restoreDocuments').mockReturnValue(of('OK')) jest.spyOn(trashService, 'restoreDocuments').mockReturnValue(of('OK'))

View File

@@ -10,8 +10,8 @@ import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { delay, takeUntil, tap } from 'rxjs' import { delay, takeUntil, tap } from 'rxjs'
import { Document } from 'src/app/data/document' import { Document } from 'src/app/data/document'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings' import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { NotificationService } from 'src/app/services/notification.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { TrashService } from 'src/app/services/trash.service' import { TrashService } from 'src/app/services/trash.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component' import { PageHeaderComponent } from '../../common/page-header/page-header.component'
@@ -44,7 +44,7 @@ export class TrashComponent
constructor( constructor(
private trashService: TrashService, private trashService: TrashService,
private toastService: ToastService, private notificationService: NotificationService,
private modalService: NgbModal, private modalService: NgbModal,
private settingsService: SettingsService, private settingsService: SettingsService,
private router: Router private router: Router
@@ -86,14 +86,14 @@ export class TrashComponent
modal.componentInstance.buttonsEnabled = false modal.componentInstance.buttonsEnabled = false
this.trashService.emptyTrash([document.id]).subscribe({ this.trashService.emptyTrash([document.id]).subscribe({
next: () => { next: () => {
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Document "${document.title}" deleted` $localize`Document "${document.title}" deleted`
) )
modal.close() modal.close()
this.reload() this.reload()
}, },
error: (err) => { error: (err) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error deleting document "${document.title}"`, $localize`Error deleting document "${document.title}"`,
err err
) )
@@ -121,13 +121,13 @@ export class TrashComponent
.emptyTrash(documents ? Array.from(documents) : null) .emptyTrash(documents ? Array.from(documents) : null)
.subscribe({ .subscribe({
next: () => { next: () => {
this.toastService.showInfo($localize`Document(s) deleted`) this.notificationService.showInfo($localize`Document(s) deleted`)
this.allToggled = false this.allToggled = false
modal.close() modal.close()
this.reload() this.reload()
}, },
error: (err) => { error: (err) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error deleting document(s)`, $localize`Error deleting document(s)`,
err err
) )
@@ -140,7 +140,7 @@ export class TrashComponent
restore(document: Document) { restore(document: Document) {
this.trashService.restoreDocuments([document.id]).subscribe({ this.trashService.restoreDocuments([document.id]).subscribe({
next: () => { next: () => {
this.toastService.show({ this.notificationService.show({
content: $localize`Document "${document.title}" restored`, content: $localize`Document "${document.title}" restored`,
delay: 5000, delay: 5000,
actionName: $localize`Open document`, actionName: $localize`Open document`,
@@ -151,7 +151,7 @@ export class TrashComponent
this.reload() this.reload()
}, },
error: (err) => { error: (err) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error restoring document "${document.title}"`, $localize`Error restoring document "${document.title}"`,
err err
) )
@@ -164,12 +164,12 @@ export class TrashComponent
.restoreDocuments(documents ? Array.from(documents) : null) .restoreDocuments(documents ? Array.from(documents) : null)
.subscribe({ .subscribe({
next: () => { next: () => {
this.toastService.showInfo($localize`Document(s) restored`) this.notificationService.showInfo($localize`Document(s) restored`)
this.allToggled = false this.allToggled = false
this.reload() this.reload()
}, },
error: (err) => { error: (err) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error restoring document(s)`, $localize`Error restoring document(s)`,
err err
) )

View File

@@ -14,11 +14,11 @@ import { Group } from 'src/app/data/group'
import { User } from 'src/app/data/user' import { User } from 'src/app/data/user'
import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { GroupService } from 'src/app/services/rest/group.service' import { GroupService } from 'src/app/services/rest/group.service'
import { UserService } from 'src/app/services/rest/user.service' import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component' import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component' import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
@@ -38,7 +38,7 @@ describe('UsersAndGroupsComponent', () => {
let fixture: ComponentFixture<UsersAndGroupsComponent> let fixture: ComponentFixture<UsersAndGroupsComponent>
let settingsService: SettingsService let settingsService: SettingsService
let modalService: NgbModal let modalService: NgbModal
let toastService: ToastService let notificationService: NotificationService
let userService: UserService let userService: UserService
let permissionsService: PermissionsService let permissionsService: PermissionsService
let groupService: GroupService let groupService: GroupService
@@ -59,7 +59,7 @@ describe('UsersAndGroupsComponent', () => {
settingsService.currentUser = users[0] settingsService.currentUser = users[0]
userService = TestBed.inject(UserService) userService = TestBed.inject(UserService)
modalService = TestBed.inject(NgbModal) modalService = TestBed.inject(NgbModal)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
permissionsService = TestBed.inject(PermissionsService) permissionsService = TestBed.inject(PermissionsService)
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest jest
@@ -104,13 +104,13 @@ describe('UsersAndGroupsComponent', () => {
modalService.activeInstances.subscribe((refs) => (modal = refs[0])) modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
component.editUser(users[0]) component.editUser(users[0])
const editDialog = modal.componentInstance as UserEditDialogComponent const editDialog = modal.componentInstance as UserEditDialogComponent
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
editDialog.failed.emit() editDialog.failed.emit()
expect(toastErrorSpy).toBeCalled() expect(notificationErrorSpy).toBeCalled()
settingsService.currentUser = users[1] // simulate logged in as different user settingsService.currentUser = users[1] // simulate logged in as different user
editDialog.succeeded.emit(users[0]) editDialog.succeeded.emit(users[0])
expect(toastInfoSpy).toHaveBeenCalledWith( expect(notificationInfoSpy).toHaveBeenCalledWith(
`Saved user "${users[0].username}".` `Saved user "${users[0].username}".`
) )
component.editUser() component.editUser()
@@ -123,18 +123,18 @@ describe('UsersAndGroupsComponent', () => {
component.deleteUser(users[0]) component.deleteUser(users[0])
const deleteDialog = modal.componentInstance as ConfirmDialogComponent const deleteDialog = modal.componentInstance as ConfirmDialogComponent
const deleteSpy = jest.spyOn(userService, 'delete') const deleteSpy = jest.spyOn(userService, 'delete')
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
const listAllSpy = jest.spyOn(userService, 'listAll') const listAllSpy = jest.spyOn(userService, 'listAll')
deleteSpy.mockReturnValueOnce( deleteSpy.mockReturnValueOnce(
throwError(() => new Error('error deleting user')) throwError(() => new Error('error deleting user'))
) )
deleteDialog.confirm() deleteDialog.confirm()
expect(toastErrorSpy).toBeCalled() expect(notificationErrorSpy).toBeCalled()
deleteSpy.mockReturnValueOnce(of(true)) deleteSpy.mockReturnValueOnce(of(true))
deleteDialog.confirm() deleteDialog.confirm()
expect(listAllSpy).toHaveBeenCalled() expect(listAllSpy).toHaveBeenCalled()
expect(toastInfoSpy).toHaveBeenCalledWith('Deleted user "user1"') expect(notificationInfoSpy).toHaveBeenCalledWith('Deleted user "user1"')
}) })
it('should logout current user if password changed, after delay', fakeAsync(() => { it('should logout current user if password changed, after delay', fakeAsync(() => {
@@ -163,12 +163,12 @@ describe('UsersAndGroupsComponent', () => {
modalService.activeInstances.subscribe((refs) => (modal = refs[0])) modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
component.editGroup(groups[0]) component.editGroup(groups[0])
const editDialog = modal.componentInstance as GroupEditDialogComponent const editDialog = modal.componentInstance as GroupEditDialogComponent
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
editDialog.failed.emit() editDialog.failed.emit()
expect(toastErrorSpy).toBeCalled() expect(notificationErrorSpy).toBeCalled()
editDialog.succeeded.emit(groups[0]) editDialog.succeeded.emit(groups[0])
expect(toastInfoSpy).toHaveBeenCalledWith( expect(notificationInfoSpy).toHaveBeenCalledWith(
`Saved group "${groups[0].name}".` `Saved group "${groups[0].name}".`
) )
component.editGroup() component.editGroup()
@@ -181,18 +181,18 @@ describe('UsersAndGroupsComponent', () => {
component.deleteGroup(groups[0]) component.deleteGroup(groups[0])
const deleteDialog = modal.componentInstance as ConfirmDialogComponent const deleteDialog = modal.componentInstance as ConfirmDialogComponent
const deleteSpy = jest.spyOn(groupService, 'delete') const deleteSpy = jest.spyOn(groupService, 'delete')
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
const listAllSpy = jest.spyOn(groupService, 'listAll') const listAllSpy = jest.spyOn(groupService, 'listAll')
deleteSpy.mockReturnValueOnce( deleteSpy.mockReturnValueOnce(
throwError(() => new Error('error deleting group')) throwError(() => new Error('error deleting group'))
) )
deleteDialog.confirm() deleteDialog.confirm()
expect(toastErrorSpy).toBeCalled() expect(notificationErrorSpy).toBeCalled()
deleteSpy.mockReturnValueOnce(of(true)) deleteSpy.mockReturnValueOnce(of(true))
deleteDialog.confirm() deleteDialog.confirm()
expect(listAllSpy).toHaveBeenCalled() expect(listAllSpy).toHaveBeenCalled()
expect(toastInfoSpy).toHaveBeenCalledWith('Deleted group "group1"') expect(notificationInfoSpy).toHaveBeenCalledWith('Deleted group "group1"')
}) })
it('should get group name', () => { it('should get group name', () => {
@@ -202,7 +202,7 @@ describe('UsersAndGroupsComponent', () => {
}) })
it('should show errors on load if load users failure', () => { it('should show errors on load if load users failure', () => {
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
jest jest
.spyOn(userService, 'listAll') .spyOn(userService, 'listAll')
.mockImplementation(() => .mockImplementation(() =>
@@ -210,11 +210,11 @@ describe('UsersAndGroupsComponent', () => {
) )
completeSetup(userService) completeSetup(userService)
fixture.detectChanges() fixture.detectChanges()
expect(toastErrorSpy).toBeCalled() expect(notificationErrorSpy).toBeCalled()
}) })
it('should show errors on load if load groups failure', () => { it('should show errors on load if load groups failure', () => {
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
jest jest
.spyOn(groupService, 'listAll') .spyOn(groupService, 'listAll')
.mockImplementation(() => .mockImplementation(() =>
@@ -222,6 +222,6 @@ describe('UsersAndGroupsComponent', () => {
) )
completeSetup(groupService) completeSetup(groupService)
fixture.detectChanges() fixture.detectChanges()
expect(toastErrorSpy).toBeCalled() expect(notificationErrorSpy).toBeCalled()
}) })
}) })

View File

@@ -5,11 +5,11 @@ import { Subject, first, takeUntil } from 'rxjs'
import { Group } from 'src/app/data/group' import { Group } from 'src/app/data/group'
import { User } from 'src/app/data/user' import { User } from 'src/app/data/user'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { GroupService } from 'src/app/services/rest/group.service' import { GroupService } from 'src/app/services/rest/group.service'
import { UserService } from 'src/app/services/rest/user.service' import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component' import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component' import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
@@ -39,7 +39,7 @@ export class UsersAndGroupsComponent
constructor( constructor(
private usersService: UserService, private usersService: UserService,
private groupsService: GroupService, private groupsService: GroupService,
private toastService: ToastService, private notificationService: NotificationService,
private modalService: NgbModal, private modalService: NgbModal,
public permissionsService: PermissionsService, public permissionsService: PermissionsService,
private settings: SettingsService private settings: SettingsService
@@ -56,7 +56,10 @@ export class UsersAndGroupsComponent
this.users = r.results this.users = r.results
}, },
error: (e) => { error: (e) => {
this.toastService.showError($localize`Error retrieving users`, e) this.notificationService.showError(
$localize`Error retrieving users`,
e
)
}, },
}) })
@@ -68,7 +71,10 @@ export class UsersAndGroupsComponent
this.groups = r.results this.groups = r.results
}, },
error: (e) => { error: (e) => {
this.toastService.showError($localize`Error retrieving groups`, e) this.notificationService.showError(
$localize`Error retrieving groups`,
e
)
}, },
}) })
} }
@@ -93,14 +99,14 @@ export class UsersAndGroupsComponent
newUser.id === this.settings.currentUser.id && newUser.id === this.settings.currentUser.id &&
(modal.componentInstance as UserEditDialogComponent).passwordIsSet (modal.componentInstance as UserEditDialogComponent).passwordIsSet
) { ) {
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Password has been changed, you will be logged out momentarily.` $localize`Password has been changed, you will be logged out momentarily.`
) )
setTimeout(() => { setTimeout(() => {
window.location.href = `${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/` window.location.href = `${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/`
}, 2500) }, 2500)
} else { } else {
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Saved user "${newUser.username}".` $localize`Saved user "${newUser.username}".`
) )
this.usersService.listAll().subscribe((r) => { this.usersService.listAll().subscribe((r) => {
@@ -111,7 +117,7 @@ export class UsersAndGroupsComponent
modal.componentInstance.failed modal.componentInstance.failed
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((e) => { .subscribe((e) => {
this.toastService.showError($localize`Error saving user.`, e) this.notificationService.showError($localize`Error saving user.`, e)
}) })
} }
@@ -129,13 +135,15 @@ export class UsersAndGroupsComponent
this.usersService.delete(user).subscribe({ this.usersService.delete(user).subscribe({
next: () => { next: () => {
modal.close() modal.close()
this.toastService.showInfo($localize`Deleted user "${user.username}"`) this.notificationService.showInfo(
$localize`Deleted user "${user.username}"`
)
this.usersService.listAll().subscribe((r) => { this.usersService.listAll().subscribe((r) => {
this.users = r.results this.users = r.results
}) })
}, },
error: (e) => { error: (e) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error deleting user "${user.username}".`, $localize`Error deleting user "${user.username}".`,
e e
) )
@@ -156,7 +164,9 @@ export class UsersAndGroupsComponent
modal.componentInstance.succeeded modal.componentInstance.succeeded
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((newGroup) => { .subscribe((newGroup) => {
this.toastService.showInfo($localize`Saved group "${newGroup.name}".`) this.notificationService.showInfo(
$localize`Saved group "${newGroup.name}".`
)
this.groupsService.listAll().subscribe((r) => { this.groupsService.listAll().subscribe((r) => {
this.groups = r.results this.groups = r.results
}) })
@@ -164,7 +174,7 @@ export class UsersAndGroupsComponent
modal.componentInstance.failed modal.componentInstance.failed
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((e) => { .subscribe((e) => {
this.toastService.showError($localize`Error saving group.`, e) this.notificationService.showError($localize`Error saving group.`, e)
}) })
} }
@@ -182,13 +192,15 @@ export class UsersAndGroupsComponent
this.groupsService.delete(group).subscribe({ this.groupsService.delete(group).subscribe({
next: () => { next: () => {
modal.close() modal.close()
this.toastService.showInfo($localize`Deleted group "${group.name}"`) this.notificationService.showInfo(
$localize`Deleted group "${group.name}"`
)
this.groupsService.listAll().subscribe((r) => { this.groupsService.listAll().subscribe((r) => {
this.groups = r.results this.groups = r.results
}) })
}, },
error: (e) => { error: (e) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error deleting group "${group.name}".`, $localize`Error deleting group "${group.name}".`,
e e
) )

View File

@@ -30,7 +30,7 @@
</div> </div>
</div> </div>
<ul ngbNav class="order-sm-3"> <ul ngbNav class="order-sm-3">
<pngx-toasts-dropdown></pngx-toasts-dropdown> <pngx-notifications-dropdown></pngx-notifications-dropdown>
<li ngbDropdown class="nav-item dropdown"> <li ngbDropdown class="nav-item dropdown">
<button class="btn ps-1 border-0" id="userDropdown" ngbDropdownToggle> <button class="btn ps-1 border-0" id="userDropdown" ngbDropdownToggle>
<i-bs width="1.3em" height="1.3em" name="person-circle"></i-bs> <i-bs width="1.3em" height="1.3em" name="person-circle"></i-bs>

View File

@@ -26,13 +26,13 @@ import {
DjangoMessageLevel, DjangoMessageLevel,
DjangoMessagesService, DjangoMessagesService,
} from 'src/app/services/django-messages.service' } from 'src/app/services/django-messages.service'
import { NotificationService } from 'src/app/services/notification.service'
import { OpenDocumentsService } from 'src/app/services/open-documents.service' import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { RemoteVersionService } from 'src/app/services/rest/remote-version.service' import { RemoteVersionService } from 'src/app/services/rest/remote-version.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service' import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SearchService } from 'src/app/services/rest/search.service' import { SearchService } from 'src/app/services/rest/search.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component' import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component'
import { DocumentDetailComponent } from '../document-detail/document-detail.component' import { DocumentDetailComponent } from '../document-detail/document-detail.component'
@@ -86,7 +86,7 @@ describe('AppFrameComponent', () => {
let settingsService: SettingsService let settingsService: SettingsService
let permissionsService: PermissionsService let permissionsService: PermissionsService
let remoteVersionService: RemoteVersionService let remoteVersionService: RemoteVersionService
let toastService: ToastService let notificationService: NotificationService
let messagesService: DjangoMessagesService let messagesService: DjangoMessagesService
let openDocumentsService: OpenDocumentsService let openDocumentsService: OpenDocumentsService
let router: Router let router: Router
@@ -126,7 +126,7 @@ describe('AppFrameComponent', () => {
PermissionsService, PermissionsService,
RemoteVersionService, RemoteVersionService,
IfPermissionsDirective, IfPermissionsDirective,
ToastService, NotificationService,
DjangoMessagesService, DjangoMessagesService,
OpenDocumentsService, OpenDocumentsService,
SearchService, SearchService,
@@ -157,7 +157,7 @@ describe('AppFrameComponent', () => {
const savedViewService = TestBed.inject(SavedViewService) const savedViewService = TestBed.inject(SavedViewService)
permissionsService = TestBed.inject(PermissionsService) permissionsService = TestBed.inject(PermissionsService)
remoteVersionService = TestBed.inject(RemoteVersionService) remoteVersionService = TestBed.inject(RemoteVersionService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
messagesService = TestBed.inject(DjangoMessagesService) messagesService = TestBed.inject(DjangoMessagesService)
openDocumentsService = TestBed.inject(OpenDocumentsService) openDocumentsService = TestBed.inject(OpenDocumentsService)
modalService = TestBed.inject(NgbModal) modalService = TestBed.inject(NgbModal)
@@ -216,7 +216,7 @@ describe('AppFrameComponent', () => {
it('should show error on toggle update checking if store settings fails', () => { it('should show error on toggle update checking if store settings fails', () => {
jest.spyOn(console, 'warn').mockImplementation(() => {}) jest.spyOn(console, 'warn').mockImplementation(() => {})
const toastSpy = jest.spyOn(toastService, 'showError') const notificationSpy = jest.spyOn(notificationService, 'showError')
settingsService.set(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, false) settingsService.set(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, false)
component.setUpdateChecking(true) component.setUpdateChecking(true)
httpTestingController httpTestingController
@@ -225,7 +225,7 @@ describe('AppFrameComponent', () => {
status: 500, status: 500,
statusText: 'error', statusText: 'error',
}) })
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
}) })
it('should support toggling slim sidebar and saving', fakeAsync(() => { it('should support toggling slim sidebar and saving', fakeAsync(() => {
@@ -245,7 +245,7 @@ describe('AppFrameComponent', () => {
it('should show error on toggle slim sidebar if store settings fails', () => { it('should show error on toggle slim sidebar if store settings fails', () => {
jest.spyOn(console, 'warn').mockImplementation(() => {}) jest.spyOn(console, 'warn').mockImplementation(() => {})
const toastSpy = jest.spyOn(toastService, 'showError') const notificationSpy = jest.spyOn(notificationService, 'showError')
component.toggleSlimSidebar() component.toggleSlimSidebar()
httpTestingController httpTestingController
.expectOne(`${environment.apiBaseUrl}ui_settings/`) .expectOne(`${environment.apiBaseUrl}ui_settings/`)
@@ -253,7 +253,7 @@ describe('AppFrameComponent', () => {
status: 500, status: 500,
statusText: 'error', statusText: 'error',
}) })
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
}) })
it('should support collapsible menu', () => { it('should support collapsible menu', () => {
@@ -305,7 +305,7 @@ describe('AppFrameComponent', () => {
it('should update saved view sorting on drag + drop, show info', () => { it('should update saved view sorting on drag + drop, show info', () => {
const settingsSpy = jest.spyOn(settingsService, 'updateSidebarViewsSort') const settingsSpy = jest.spyOn(settingsService, 'updateSidebarViewsSort')
const toastSpy = jest.spyOn(toastService, 'showInfo') const notificationSpy = jest.spyOn(notificationService, 'showInfo')
jest.spyOn(settingsService, 'storeSettings').mockReturnValue(of(true)) jest.spyOn(settingsService, 'storeSettings').mockReturnValue(of(true))
component.onDrop({ previousIndex: 0, currentIndex: 1 } as CdkDragDrop< component.onDrop({ previousIndex: 0, currentIndex: 1 } as CdkDragDrop<
SavedView[] SavedView[]
@@ -315,7 +315,7 @@ describe('AppFrameComponent', () => {
saved_views[0], saved_views[0],
saved_views[3], saved_views[3],
]) ])
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
}) })
it('should update saved view sorting on drag + drop, show error', () => { it('should update saved view sorting on drag + drop, show error', () => {
@@ -326,14 +326,14 @@ describe('AppFrameComponent', () => {
fixture = TestBed.createComponent(AppFrameComponent) fixture = TestBed.createComponent(AppFrameComponent)
component = fixture.componentInstance component = fixture.componentInstance
fixture.detectChanges() fixture.detectChanges()
const toastSpy = jest.spyOn(toastService, 'showError') const notificationSpy = jest.spyOn(notificationService, 'showError')
jest jest
.spyOn(settingsService, 'storeSettings') .spyOn(settingsService, 'storeSettings')
.mockReturnValue(throwError(() => new Error('unable to save'))) .mockReturnValue(throwError(() => new Error('unable to save')))
component.onDrop({ previousIndex: 0, currentIndex: 2 } as CdkDragDrop< component.onDrop({ previousIndex: 0, currentIndex: 2 } as CdkDragDrop<
SavedView[] SavedView[]
>) >)
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
}) })
it('should support edit profile', () => { it('should support edit profile', () => {
@@ -345,9 +345,9 @@ describe('AppFrameComponent', () => {
}) })
}) })
it('should show toasts for django messages', () => { it('should show notifications for django messages', () => {
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
jest.spyOn(messagesService, 'get').mockReturnValue([ jest.spyOn(messagesService, 'get').mockReturnValue([
{ level: DjangoMessageLevel.WARNING, message: 'Test warning' }, { level: DjangoMessageLevel.WARNING, message: 'Test warning' },
{ level: DjangoMessageLevel.ERROR, message: 'Test error' }, { level: DjangoMessageLevel.ERROR, message: 'Test error' },
@@ -356,7 +356,7 @@ describe('AppFrameComponent', () => {
{ level: DjangoMessageLevel.DEBUG, message: 'Test debug' }, { level: DjangoMessageLevel.DEBUG, message: 'Test debug' },
]) ])
component.ngOnInit() component.ngOnInit()
expect(toastErrorSpy).toHaveBeenCalledTimes(2) expect(notificationErrorSpy).toHaveBeenCalledTimes(2)
expect(toastInfoSpy).toHaveBeenCalledTimes(3) expect(notificationInfoSpy).toHaveBeenCalledTimes(3)
}) })
}) })

View File

@@ -29,6 +29,7 @@ import {
DjangoMessageLevel, DjangoMessageLevel,
DjangoMessagesService, DjangoMessagesService,
} from 'src/app/services/django-messages.service' } from 'src/app/services/django-messages.service'
import { NotificationService } from 'src/app/services/notification.service'
import { OpenDocumentsService } from 'src/app/services/open-documents.service' import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { import {
PermissionAction, PermissionAction,
@@ -42,13 +43,12 @@ import {
import { SavedViewService } from 'src/app/services/rest/saved-view.service' import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { TasksService } from 'src/app/services/tasks.service' import { TasksService } from 'src/app/services/tasks.service'
import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component' import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component'
import { DocumentDetailComponent } from '../document-detail/document-detail.component' import { DocumentDetailComponent } from '../document-detail/document-detail.component'
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component' import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
import { GlobalSearchComponent } from './global-search/global-search.component' import { GlobalSearchComponent } from './global-search/global-search.component'
import { ToastsDropdownComponent } from './toasts-dropdown/toasts-dropdown.component' import { NotificationsDropdownComponent } from './notifications-dropdown/notifications-dropdown.component'
@Component({ @Component({
selector: 'pngx-app-frame', selector: 'pngx-app-frame',
@@ -58,7 +58,7 @@ import { ToastsDropdownComponent } from './toasts-dropdown/toasts-dropdown.compo
GlobalSearchComponent, GlobalSearchComponent,
DocumentTitlePipe, DocumentTitlePipe,
IfPermissionsDirective, IfPermissionsDirective,
ToastsDropdownComponent, NotificationsDropdownComponent,
RouterModule, RouterModule,
NgClass, NgClass,
NgbDropdownModule, NgbDropdownModule,
@@ -89,7 +89,7 @@ export class AppFrameComponent
private remoteVersionService: RemoteVersionService, private remoteVersionService: RemoteVersionService,
public settingsService: SettingsService, public settingsService: SettingsService,
public tasksService: TasksService, public tasksService: TasksService,
private readonly toastService: ToastService, private readonly notificationService: NotificationService,
private modalService: NgbModal, private modalService: NgbModal,
public permissionsService: PermissionsService, public permissionsService: PermissionsService,
private djangoMessagesService: DjangoMessagesService private djangoMessagesService: DjangoMessagesService
@@ -123,12 +123,12 @@ export class AppFrameComponent
switch (message.level) { switch (message.level) {
case DjangoMessageLevel.ERROR: case DjangoMessageLevel.ERROR:
case DjangoMessageLevel.WARNING: case DjangoMessageLevel.WARNING:
this.toastService.showError(message.message) this.notificationService.showError(message.message)
break break
case DjangoMessageLevel.SUCCESS: case DjangoMessageLevel.SUCCESS:
case DjangoMessageLevel.INFO: case DjangoMessageLevel.INFO:
case DjangoMessageLevel.DEBUG: case DjangoMessageLevel.DEBUG:
this.toastService.showInfo(message.message) this.notificationService.showInfo(message.message)
break break
} }
}) })
@@ -157,7 +157,7 @@ export class AppFrameComponent
.pipe(first()) .pipe(first())
.subscribe({ .subscribe({
error: (error) => { error: (error) => {
this.toastService.showError( this.notificationService.showError(
$localize`An error occurred while saving settings.` $localize`An error occurred while saving settings.`
) )
console.warn(error) console.warn(error)
@@ -242,10 +242,13 @@ export class AppFrameComponent
this.settingsService.updateSidebarViewsSort(sidebarViews).subscribe({ this.settingsService.updateSidebarViewsSort(sidebarViews).subscribe({
next: () => { next: () => {
this.toastService.showInfo($localize`Sidebar views updated`) this.notificationService.showInfo($localize`Sidebar views updated`)
}, },
error: (e) => { error: (e) => {
this.toastService.showError($localize`Error updating sidebar views`, e) this.notificationService.showError(
$localize`Error updating sidebar views`,
e
)
}, },
}) })
} }
@@ -265,7 +268,7 @@ export class AppFrameComponent
.pipe(first()) .pipe(first())
.subscribe({ .subscribe({
error: (error) => { error: (error) => {
this.toastService.showError( this.notificationService.showError(
$localize`An error occurred while saving update checking settings.` $localize`An error occurred while saving update checking settings.`
) )
console.warn(error) console.warn(error)

View File

@@ -28,10 +28,10 @@ import {
} from 'src/app/data/filter-rule-type' } from 'src/app/data/filter-rule-type'
import { GlobalSearchType, SETTINGS_KEYS } from 'src/app/data/ui-settings' import { GlobalSearchType, SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { NotificationService } from 'src/app/services/notification.service'
import { DocumentService } from 'src/app/services/rest/document.service' import { DocumentService } from 'src/app/services/rest/document.service'
import { SearchService } from 'src/app/services/rest/search.service' import { SearchService } from 'src/app/services/rest/search.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { queryParamsFromFilterRules } from 'src/app/utils/query-params' import { queryParamsFromFilterRules } from 'src/app/utils/query-params'
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component' import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
@@ -133,7 +133,7 @@ describe('GlobalSearchComponent', () => {
let modalService: NgbModal let modalService: NgbModal
let documentService: DocumentService let documentService: DocumentService
let documentListViewService: DocumentListViewService let documentListViewService: DocumentListViewService
let toastService: ToastService let notificationService: NotificationService
let settingsService: SettingsService let settingsService: SettingsService
beforeEach(async () => { beforeEach(async () => {
@@ -157,7 +157,7 @@ describe('GlobalSearchComponent', () => {
modalService = TestBed.inject(NgbModal) modalService = TestBed.inject(NgbModal)
documentService = TestBed.inject(DocumentService) documentService = TestBed.inject(DocumentService)
documentListViewService = TestBed.inject(DocumentListViewService) documentListViewService = TestBed.inject(DocumentListViewService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
settingsService = TestBed.inject(SettingsService) settingsService = TestBed.inject(SettingsService)
fixture = TestBed.createComponent(GlobalSearchComponent) fixture = TestBed.createComponent(GlobalSearchComponent)
@@ -397,16 +397,16 @@ describe('GlobalSearchComponent', () => {
}) })
const editDialog = modal.componentInstance as CustomFieldEditDialogComponent const editDialog = modal.componentInstance as CustomFieldEditDialogComponent
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
// fail first // fail first
editDialog.failed.emit({ error: 'error creating item' }) editDialog.failed.emit({ error: 'error creating item' })
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
// succeed // succeed
editDialog.succeeded.emit(true) editDialog.succeeded.emit(true)
expect(toastInfoSpy).toHaveBeenCalled() expect(notificationInfoSpy).toHaveBeenCalled()
}) })
it('should support secondary action', () => { it('should support secondary action', () => {
@@ -448,16 +448,16 @@ describe('GlobalSearchComponent', () => {
}) })
const editDialog = modal.componentInstance as CustomFieldEditDialogComponent const editDialog = modal.componentInstance as CustomFieldEditDialogComponent
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
// fail first // fail first
editDialog.failed.emit({ error: 'error creating item' }) editDialog.failed.emit({ error: 'error creating item' })
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
// succeed // succeed
editDialog.succeeded.emit(true) editDialog.succeeded.emit(true)
expect(toastInfoSpy).toHaveBeenCalled() expect(notificationInfoSpy).toHaveBeenCalled()
}) })
it('should support reset', () => { it('should support reset', () => {

View File

@@ -31,6 +31,7 @@ import { GlobalSearchType, SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { HotKeyService } from 'src/app/services/hot-key.service' import { HotKeyService } from 'src/app/services/hot-key.service'
import { NotificationService } from 'src/app/services/notification.service'
import { import {
PermissionAction, PermissionAction,
PermissionsService, PermissionsService,
@@ -41,7 +42,6 @@ import {
SearchService, SearchService,
} from 'src/app/services/rest/search.service' } from 'src/app/services/rest/search.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { paramsFromViewState } from 'src/app/utils/query-params' import { paramsFromViewState } from 'src/app/utils/query-params'
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component' import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
@@ -97,7 +97,7 @@ export class GlobalSearchComponent implements OnInit {
private documentService: DocumentService, private documentService: DocumentService,
private documentListViewService: DocumentListViewService, private documentListViewService: DocumentListViewService,
private permissionsService: PermissionsService, private permissionsService: PermissionsService,
private toastService: ToastService, private notificationService: NotificationService,
private hotkeyService: HotKeyService, private hotkeyService: HotKeyService,
private settingsService: SettingsService private settingsService: SettingsService
) { ) {
@@ -206,10 +206,15 @@ export class GlobalSearchComponent implements OnInit {
modalRef.componentInstance.dialogMode = EditDialogMode.EDIT modalRef.componentInstance.dialogMode = EditDialogMode.EDIT
modalRef.componentInstance.object = object modalRef.componentInstance.object = object
modalRef.componentInstance.succeeded.subscribe(() => { modalRef.componentInstance.succeeded.subscribe(() => {
this.toastService.showInfo($localize`Successfully updated object.`) this.notificationService.showInfo(
$localize`Successfully updated object.`
)
}) })
modalRef.componentInstance.failed.subscribe((e) => { modalRef.componentInstance.failed.subscribe((e) => {
this.toastService.showError($localize`Error occurred saving object.`, e) this.notificationService.showError(
$localize`Error occurred saving object.`,
e
)
}) })
} }
} }
@@ -244,10 +249,15 @@ export class GlobalSearchComponent implements OnInit {
modalRef.componentInstance.dialogMode = EditDialogMode.EDIT modalRef.componentInstance.dialogMode = EditDialogMode.EDIT
modalRef.componentInstance.object = object modalRef.componentInstance.object = object
modalRef.componentInstance.succeeded.subscribe(() => { modalRef.componentInstance.succeeded.subscribe(() => {
this.toastService.showInfo($localize`Successfully updated object.`) this.notificationService.showInfo(
$localize`Successfully updated object.`
)
}) })
modalRef.componentInstance.failed.subscribe((e) => { modalRef.componentInstance.failed.subscribe((e) => {
this.toastService.showError($localize`Error occurred saving object.`, e) this.notificationService.showError(
$localize`Error occurred saving object.`,
e
)
}) })
} }
} }

View File

@@ -1,7 +1,7 @@
<li ngbDropdown class="nav-item" (openChange)="onOpenChange($event)"> <li ngbDropdown class="nav-item" (openChange)="onOpenChange($event)">
@if (toasts.length) { @if (notifications.length) {
<span class="badge rounded-pill z-3 pe-none bg-secondary me-2 position-absolute top-0 left-0">{{ toasts.length }}</span> <span class="badge rounded-pill z-3 pe-none bg-secondary me-2 position-absolute top-0 left-0">{{ notifications.length }}</span>
} }
<button class="btn border-0" id="notificationsDropdown" ngbDropdownToggle> <button class="btn border-0" id="notificationsDropdown" ngbDropdownToggle>
<i-bs width="1.3em" height="1.3em" name="bell"></i-bs> <i-bs width="1.3em" height="1.3em" name="bell"></i-bs>
@@ -11,17 +11,17 @@
<h6 i18n>Notifications</h6> <h6 i18n>Notifications</h6>
<div class="btn-group ms-auto"> <div class="btn-group ms-auto">
<button class="btn btn-sm btn-outline-secondary mb-2 ms-auto" <button class="btn btn-sm btn-outline-secondary mb-2 ms-auto"
(click)="toastService.clearToasts()" (click)="notificationService.clearNotifications()"
[disabled]="toasts.length === 0" [disabled]="notifications.length === 0"
i18n>Clear All</button> i18n>Clear All</button>
</div> </div>
</div> </div>
@if (toasts.length === 0) { @if (notifications.length === 0) {
<p class="text-center mb-0 small text-muted"><em i18n>No notifications</em></p> <p class="text-center mb-0 small text-muted"><em i18n>No notifications</em></p>
} }
<div class="scroll-list"> <div class="scroll-list">
@for (toast of toasts; track toast.id) { @for (notification of notifications; track notification.id) {
<pngx-toast [autohide]="false" [toast]="toast" (hidden)="onHidden(toast)" (close)="toastService.closeToast(toast)"></pngx-toast> <pngx-notification [autohide]="false" [notification]="notification" (hidden)="onHidden(notification)" (close)="notificationService.closeNotification(notification)"></pngx-notification>
} }
</div> </div>
</div> </div>

View File

@@ -1,5 +1,5 @@
.dropdown-menu { .dropdown-menu {
width: var(--pngx-toast-max-width); width: var(--pngx-notification-max-width);
} }
.dropdown-menu .scroll-list { .dropdown-menu .scroll-list {

View File

@@ -9,10 +9,13 @@ import {
} from '@angular/core/testing' } from '@angular/core/testing'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { Subject } from 'rxjs' import { Subject } from 'rxjs'
import { Toast, ToastService } from 'src/app/services/toast.service' import {
import { ToastsDropdownComponent } from './toasts-dropdown.component' Notification,
NotificationService,
} from 'src/app/services/notification.service'
import { NotificationsDropdownComponent } from './notifications-dropdown.component'
const toasts = [ const notifications = [
{ {
id: 'abc-123', id: 'abc-123',
content: 'foo bar', content: 'foo bar',
@@ -38,16 +41,16 @@ const toasts = [
}, },
] ]
describe('ToastsDropdownComponent', () => { describe('NotificationsDropdownComponent', () => {
let component: ToastsDropdownComponent let component: NotificationsDropdownComponent
let fixture: ComponentFixture<ToastsDropdownComponent> let fixture: ComponentFixture<NotificationsDropdownComponent>
let toastService: ToastService let notificationService: NotificationService
let toastsSubject: Subject<Toast[]> = new Subject() let notificationsSubject: Subject<Notification[]> = new Subject()
beforeEach(async () => { beforeEach(async () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
ToastsDropdownComponent, NotificationsDropdownComponent,
NgxBootstrapIconsModule.pick(allIcons), NgxBootstrapIconsModule.pick(allIcons),
], ],
providers: [ providers: [
@@ -56,24 +59,26 @@ describe('ToastsDropdownComponent', () => {
], ],
}).compileComponents() }).compileComponents()
fixture = TestBed.createComponent(ToastsDropdownComponent) fixture = TestBed.createComponent(NotificationsDropdownComponent)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
jest.spyOn(toastService, 'getToasts').mockReturnValue(toastsSubject) jest
.spyOn(notificationService, 'getNotifications')
.mockReturnValue(notificationsSubject)
component = fixture.componentInstance component = fixture.componentInstance
fixture.detectChanges() fixture.detectChanges()
}) })
it('should call getToasts and return toasts', fakeAsync(() => { it('should call getNotifications and return notifications', fakeAsync(() => {
const spy = jest.spyOn(toastService, 'getToasts') const spy = jest.spyOn(notificationService, 'getNotifications')
component.ngOnInit() component.ngOnInit()
toastsSubject.next(toasts) notificationsSubject.next(notifications)
fixture.detectChanges() fixture.detectChanges()
expect(spy).toHaveBeenCalled() expect(spy).toHaveBeenCalled()
expect(component.toasts).toContainEqual({ expect(component.notifications).toContainEqual({
id: 'abc-123', id: 'abc-123',
content: 'foo bar', content: 'foo bar',
delay: 5000, delay: 5000,
@@ -84,9 +89,9 @@ describe('ToastsDropdownComponent', () => {
discardPeriodicTasks() discardPeriodicTasks()
})) }))
it('should show a toast', fakeAsync(() => { it('should show a notification', fakeAsync(() => {
component.ngOnInit() component.ngOnInit()
toastsSubject.next(toasts) notificationsSubject.next(notifications)
fixture.detectChanges() fixture.detectChanges()
expect(fixture.nativeElement.textContent).toContain('foo bar') expect(fixture.nativeElement.textContent).toContain('foo bar')
@@ -96,12 +101,16 @@ describe('ToastsDropdownComponent', () => {
discardPeriodicTasks() discardPeriodicTasks()
})) }))
it('should toggle suppressPopupToasts', fakeAsync((finish) => { it('should toggle suppressPopupNotifications', fakeAsync((finish) => {
component.ngOnInit() component.ngOnInit()
fixture.detectChanges() fixture.detectChanges()
toastsSubject.next(toasts) notificationsSubject.next(notifications)
const spy = jest.spyOn(toastService, 'suppressPopupToasts', 'set') const spy = jest.spyOn(
notificationService,
'suppressPopupNotifications',
'set'
)
component.onOpenChange(true) component.onOpenChange(true)
expect(spy).toHaveBeenCalledWith(true) expect(spy).toHaveBeenCalledWith(true)

View File

@@ -0,0 +1,47 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import {
NgbDropdownModule,
NgbProgressbarModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { Subscription } from 'rxjs'
import {
Notification,
NotificationService,
} from 'src/app/services/notification.service'
import { NotificationComponent } from '../../common/notification/notification.component'
@Component({
selector: 'pngx-notifications-dropdown',
templateUrl: './notifications-dropdown.component.html',
styleUrls: ['./notifications-dropdown.component.scss'],
imports: [
NotificationComponent,
NgbDropdownModule,
NgbProgressbarModule,
NgxBootstrapIconsModule,
],
})
export class NotificationsDropdownComponent implements OnInit, OnDestroy {
constructor(public notificationService: NotificationService) {}
private subscription: Subscription
public notifications: Notification[] = []
ngOnDestroy(): void {
this.subscription?.unsubscribe()
}
ngOnInit(): void {
this.subscription = this.notificationService
.getNotifications()
.subscribe((notifications) => {
this.notifications = [...notifications]
})
}
onOpenChange(open: boolean): void {
this.notificationService.suppressPopupNotifications = open
}
}

View File

@@ -1,42 +0,0 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import {
NgbDropdownModule,
NgbProgressbarModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { Subscription } from 'rxjs'
import { Toast, ToastService } from 'src/app/services/toast.service'
import { ToastComponent } from '../../common/toast/toast.component'
@Component({
selector: 'pngx-toasts-dropdown',
templateUrl: './toasts-dropdown.component.html',
styleUrls: ['./toasts-dropdown.component.scss'],
imports: [
ToastComponent,
NgbDropdownModule,
NgbProgressbarModule,
NgxBootstrapIconsModule,
],
})
export class ToastsDropdownComponent implements OnInit, OnDestroy {
constructor(public toastService: ToastService) {}
private subscription: Subscription
public toasts: Toast[] = []
ngOnDestroy(): void {
this.subscription?.unsubscribe()
}
ngOnInit(): void {
this.subscription = this.toastService.getToasts().subscribe((toasts) => {
this.toasts = [...toasts]
})
}
onOpenChange(open: boolean): void {
this.toastService.suppressPopupToasts = open
}
}

View File

@@ -18,9 +18,9 @@ import { NgSelectModule } from '@ng-select/ng-select'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of } from 'rxjs' import { of } from 'rxjs'
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field' import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
import { NotificationService } from 'src/app/services/notification.service'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
import { SelectComponent } from '../input/select/select.component' import { SelectComponent } from '../input/select/select.component'
import { CustomFieldsDropdownComponent } from './custom-fields-dropdown.component' import { CustomFieldsDropdownComponent } from './custom-fields-dropdown.component'
@@ -42,7 +42,7 @@ describe('CustomFieldsDropdownComponent', () => {
let component: CustomFieldsDropdownComponent let component: CustomFieldsDropdownComponent
let fixture: ComponentFixture<CustomFieldsDropdownComponent> let fixture: ComponentFixture<CustomFieldsDropdownComponent>
let customFieldService: CustomFieldsService let customFieldService: CustomFieldsService
let toastService: ToastService let notificationService: NotificationService
let modalService: NgbModal let modalService: NgbModal
let settingsService: SettingsService let settingsService: SettingsService
@@ -64,7 +64,7 @@ describe('CustomFieldsDropdownComponent', () => {
], ],
}) })
customFieldService = TestBed.inject(CustomFieldsService) customFieldService = TestBed.inject(CustomFieldsService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
modalService = TestBed.inject(NgbModal) modalService = TestBed.inject(NgbModal)
jest.spyOn(customFieldService, 'listAll').mockReturnValue( jest.spyOn(customFieldService, 'listAll').mockReturnValue(
of({ of({
@@ -113,8 +113,8 @@ describe('CustomFieldsDropdownComponent', () => {
it('should support creating field, show error if necessary, then add', fakeAsync(() => { it('should support creating field, show error if necessary, then add', fakeAsync(() => {
let modal: NgbModalRef let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
const getFieldsSpy = jest.spyOn( const getFieldsSpy = jest.spyOn(
CustomFieldsDropdownComponent.prototype as any, CustomFieldsDropdownComponent.prototype as any,
'getFields' 'getFields'
@@ -129,13 +129,13 @@ describe('CustomFieldsDropdownComponent', () => {
// fail first // fail first
editDialog.failed.emit({ error: 'error creating field' }) editDialog.failed.emit({ error: 'error creating field' })
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
expect(getFieldsSpy).not.toHaveBeenCalled() expect(getFieldsSpy).not.toHaveBeenCalled()
// succeed // succeed
editDialog.succeeded.emit(fields[0]) editDialog.succeeded.emit(fields[0])
tick(100) tick(100)
expect(toastInfoSpy).toHaveBeenCalled() expect(notificationInfoSpy).toHaveBeenCalled()
expect(getFieldsSpy).toHaveBeenCalled() expect(getFieldsSpy).toHaveBeenCalled()
expect(addFieldSpy).toHaveBeenCalled() expect(addFieldSpy).toHaveBeenCalled()
})) }))

View File

@@ -14,13 +14,13 @@ import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { first, takeUntil } from 'rxjs' import { first, takeUntil } from 'rxjs'
import { CustomField, DATA_TYPE_LABELS } from 'src/app/data/custom-field' import { CustomField, DATA_TYPE_LABELS } from 'src/app/data/custom-field'
import { CustomFieldInstance } from 'src/app/data/custom-field-instance' import { CustomFieldInstance } from 'src/app/data/custom-field-instance'
import { NotificationService } from 'src/app/services/notification.service'
import { import {
PermissionAction, PermissionAction,
PermissionType, PermissionType,
PermissionsService, PermissionsService,
} from 'src/app/services/permissions.service' } from 'src/app/services/permissions.service'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { ToastService } from 'src/app/services/toast.service'
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component' import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
@@ -78,7 +78,7 @@ export class CustomFieldsDropdownComponent extends LoadingComponentWithPermissio
constructor( constructor(
private customFieldsService: CustomFieldsService, private customFieldsService: CustomFieldsService,
private modalService: NgbModal, private modalService: NgbModal,
private toastService: ToastService, private notificationService: NotificationService,
private permissionsService: PermissionsService private permissionsService: PermissionsService
) { ) {
super() super()
@@ -123,7 +123,9 @@ export class CustomFieldsDropdownComponent extends LoadingComponentWithPermissio
modal.componentInstance.succeeded modal.componentInstance.succeeded
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((newField) => { .subscribe((newField) => {
this.toastService.showInfo($localize`Saved field "${newField.name}".`) this.notificationService.showInfo(
$localize`Saved field "${newField.name}".`
)
this.customFieldsService.clearCache() this.customFieldsService.clearCache()
this.getFields() this.getFields()
this.created.emit(newField) this.created.emit(newField)
@@ -132,7 +134,7 @@ export class CustomFieldsDropdownComponent extends LoadingComponentWithPermissio
modal.componentInstance.failed modal.componentInstance.failed
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((e) => { .subscribe((e) => {
this.toastService.showError($localize`Error saving field.`, e) this.notificationService.showError($localize`Error saving field.`, e)
}) })
} }

View File

@@ -12,11 +12,11 @@ import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs' import { of, throwError } from 'rxjs'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive' import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { GroupService } from 'src/app/services/rest/group.service' import { GroupService } from 'src/app/services/rest/group.service'
import { UserService } from 'src/app/services/rest/user.service' import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { PasswordComponent } from '../../input/password/password.component' import { PasswordComponent } from '../../input/password/password.component'
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component' import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
import { SelectComponent } from '../../input/select/select.component' import { SelectComponent } from '../../input/select/select.component'
@@ -29,7 +29,7 @@ describe('UserEditDialogComponent', () => {
let component: UserEditDialogComponent let component: UserEditDialogComponent
let settingsService: SettingsService let settingsService: SettingsService
let permissionsService: PermissionsService let permissionsService: PermissionsService
let toastService: ToastService let notificationService: NotificationService
let fixture: ComponentFixture<UserEditDialogComponent> let fixture: ComponentFixture<UserEditDialogComponent>
beforeEach(async () => { beforeEach(async () => {
@@ -75,7 +75,7 @@ describe('UserEditDialogComponent', () => {
settingsService = TestBed.inject(SettingsService) settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = { id: 99, username: 'user99' } settingsService.currentUser = { id: 99, username: 'user99' }
permissionsService = TestBed.inject(PermissionsService) permissionsService = TestBed.inject(PermissionsService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
component = fixture.componentInstance component = fixture.componentInstance
fixture.detectChanges() fixture.detectChanges()
@@ -133,22 +133,22 @@ describe('UserEditDialogComponent', () => {
component['service'] as UserService, component['service'] as UserService,
'deactivateTotp' 'deactivateTotp'
) )
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
deactivateSpy.mockReturnValueOnce(throwError(() => new Error('error'))) deactivateSpy.mockReturnValueOnce(throwError(() => new Error('error')))
component.deactivateTotp() component.deactivateTotp()
expect(deactivateSpy).toHaveBeenCalled() expect(deactivateSpy).toHaveBeenCalled()
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
deactivateSpy.mockReturnValueOnce(of(false)) deactivateSpy.mockReturnValueOnce(of(false))
component.deactivateTotp() component.deactivateTotp()
expect(deactivateSpy).toHaveBeenCalled() expect(deactivateSpy).toHaveBeenCalled()
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
deactivateSpy.mockReturnValueOnce(of(true)) deactivateSpy.mockReturnValueOnce(of(true))
component.deactivateTotp() component.deactivateTotp()
expect(deactivateSpy).toHaveBeenCalled() expect(deactivateSpy).toHaveBeenCalled()
expect(toastInfoSpy).toHaveBeenCalled() expect(notificationInfoSpy).toHaveBeenCalled()
}) })
it('should check superuser status of current user', () => { it('should check superuser status of current user', () => {

View File

@@ -10,11 +10,11 @@ import { first } from 'rxjs'
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component' import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
import { Group } from 'src/app/data/group' import { Group } from 'src/app/data/group'
import { User } from 'src/app/data/user' import { User } from 'src/app/data/user'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { GroupService } from 'src/app/services/rest/group.service' import { GroupService } from 'src/app/services/rest/group.service'
import { UserService } from 'src/app/services/rest/user.service' import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { PasswordComponent } from '../../input/password/password.component' import { PasswordComponent } from '../../input/password/password.component'
import { SelectComponent } from '../../input/select/select.component' import { SelectComponent } from '../../input/select/select.component'
import { TextComponent } from '../../input/text/text.component' import { TextComponent } from '../../input/text/text.component'
@@ -46,7 +46,7 @@ export class UserEditDialogComponent
activeModal: NgbActiveModal, activeModal: NgbActiveModal,
groupsService: GroupService, groupsService: GroupService,
settingsService: SettingsService, settingsService: SettingsService,
private toastService: ToastService, private notificationService: NotificationService,
private permissionsService: PermissionsService private permissionsService: PermissionsService
) { ) {
super(service, activeModal, service, settingsService) super(service, activeModal, service, settingsService)
@@ -128,15 +128,20 @@ export class UserEditDialogComponent
next: (result) => { next: (result) => {
this.totpLoading = false this.totpLoading = false
if (result) { if (result) {
this.toastService.showInfo($localize`Totp deactivated`) this.notificationService.showInfo($localize`Totp deactivated`)
this.object.is_mfa_enabled = false this.object.is_mfa_enabled = false
} else { } else {
this.toastService.showError($localize`Totp deactivation failed`) this.notificationService.showError(
$localize`Totp deactivation failed`
)
} }
}, },
error: (e) => { error: (e) => {
this.totpLoading = false this.totpLoading = false
this.toastService.showError($localize`Totp deactivation failed`, e) this.notificationService.showError(
$localize`Totp deactivation failed`,
e
)
}, },
}) })
} }

View File

@@ -6,9 +6,9 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs' import { of, throwError } from 'rxjs'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { DocumentService } from 'src/app/services/rest/document.service' import { DocumentService } from 'src/app/services/rest/document.service'
import { ToastService } from 'src/app/services/toast.service'
import { EmailDocumentDialogComponent } from './email-document-dialog.component' import { EmailDocumentDialogComponent } from './email-document-dialog.component'
describe('EmailDocumentDialogComponent', () => { describe('EmailDocumentDialogComponent', () => {
@@ -16,7 +16,7 @@ describe('EmailDocumentDialogComponent', () => {
let fixture: ComponentFixture<EmailDocumentDialogComponent> let fixture: ComponentFixture<EmailDocumentDialogComponent>
let documentService: DocumentService let documentService: DocumentService
let permissionsService: PermissionsService let permissionsService: PermissionsService
let toastService: ToastService let notificationService: NotificationService
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
@@ -34,7 +34,7 @@ describe('EmailDocumentDialogComponent', () => {
fixture = TestBed.createComponent(EmailDocumentDialogComponent) fixture = TestBed.createComponent(EmailDocumentDialogComponent)
documentService = TestBed.inject(DocumentService) documentService = TestBed.inject(DocumentService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
component = fixture.componentInstance component = fixture.componentInstance
fixture.detectChanges() fixture.detectChanges()
}) })
@@ -47,8 +47,8 @@ describe('EmailDocumentDialogComponent', () => {
}) })
it('should support sending document via email, showing error if needed', () => { it('should support sending document via email, showing error if needed', () => {
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastSuccessSpy = jest.spyOn(toastService, 'showInfo') const notificationSuccessSpy = jest.spyOn(notificationService, 'showInfo')
component.emailAddress = 'hello@paperless-ngx.com' component.emailAddress = 'hello@paperless-ngx.com'
component.emailSubject = 'Hello' component.emailSubject = 'Hello'
component.emailMessage = 'World' component.emailMessage = 'World'
@@ -56,11 +56,11 @@ describe('EmailDocumentDialogComponent', () => {
.spyOn(documentService, 'emailDocument') .spyOn(documentService, 'emailDocument')
.mockReturnValue(throwError(() => new Error('Unable to email document'))) .mockReturnValue(throwError(() => new Error('Unable to email document')))
component.emailDocument() component.emailDocument()
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
jest.spyOn(documentService, 'emailDocument').mockReturnValue(of(true)) jest.spyOn(documentService, 'emailDocument').mockReturnValue(of(true))
component.emailDocument() component.emailDocument()
expect(toastSuccessSpy).toHaveBeenCalled() expect(notificationSuccessSpy).toHaveBeenCalled()
}) })
it('should close the dialog', () => { it('should close the dialog', () => {

View File

@@ -2,8 +2,8 @@ import { Component, Input } from '@angular/core'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { NotificationService } from 'src/app/services/notification.service'
import { DocumentService } from 'src/app/services/rest/document.service' import { DocumentService } from 'src/app/services/rest/document.service'
import { ToastService } from 'src/app/services/toast.service'
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component' import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
@Component({ @Component({
@@ -40,7 +40,7 @@ export class EmailDocumentDialogComponent extends LoadingComponentWithPermission
constructor( constructor(
private activeModal: NgbActiveModal, private activeModal: NgbActiveModal,
private documentService: DocumentService, private documentService: DocumentService,
private toastService: ToastService private notificationService: NotificationService
) { ) {
super() super()
this.loading = false this.loading = false
@@ -62,11 +62,14 @@ export class EmailDocumentDialogComponent extends LoadingComponentWithPermission
this.emailAddress = '' this.emailAddress = ''
this.emailSubject = '' this.emailSubject = ''
this.emailMessage = '' this.emailMessage = ''
this.toastService.showInfo($localize`Email sent`) this.notificationService.showInfo($localize`Email sent`)
}, },
error: (e) => { error: (e) => {
this.loading = false this.loading = false
this.toastService.showError($localize`Error emailing document`, e) this.notificationService.showError(
$localize`Error emailing document`,
e
)
}, },
}) })
} }

View File

@@ -0,0 +1,3 @@
@for (notification of notifications; track notification.id) {
<pngx-notification [notification]="notification" [autohide]="true" (close)="closeNotification()"></pngx-notification>
}

View File

@@ -1,7 +1,7 @@
:host { :host {
position: fixed; position: fixed;
top: 0; top: 0;
right: calc(50% - (var(--pngx-toast-max-width) / 2)); right: calc(50% - (var(--pngx-notification-max-width) / 2));
margin: 0.3em; margin: 0.3em;
z-index: 1200; z-index: 1200;
} }

View File

@@ -0,0 +1,84 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { Subject } from 'rxjs'
import {
Notification,
NotificationService,
} from 'src/app/services/notification.service'
import { NotificationListComponent } from './notification-list.component'
const notification = {
content: 'Error 2 content',
delay: 5000,
error: {
url: 'https://example.com',
status: 500,
statusText: 'Internal Server Error',
message: 'Internal server error 500 message',
error: { detail: 'Error 2 message details' },
},
}
describe('NotificationListComponent', () => {
let component: NotificationListComponent
let fixture: ComponentFixture<NotificationListComponent>
let notificationService: NotificationService
let notificationSubject: Subject<Notification> = new Subject()
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [
NotificationListComponent,
NgxBootstrapIconsModule.pick(allIcons),
],
providers: [
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
}).compileComponents()
fixture = TestBed.createComponent(NotificationListComponent)
notificationService = TestBed.inject(NotificationService)
jest.replaceProperty(
notificationService,
'showNotification',
notificationSubject
)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
it('should close notification', () => {
component.notifications = [notification]
const closenotificationSpy = jest.spyOn(
notificationService,
'closeNotification'
)
component.closeNotification()
expect(component.notifications).toEqual([])
expect(closenotificationSpy).toHaveBeenCalledWith(notification)
})
it('should unsubscribe', () => {
const unsubscribeSpy = jest.spyOn(
(component as any).subscription,
'unsubscribe'
)
component.ngOnDestroy()
expect(unsubscribeSpy).toHaveBeenCalled()
})
it('should subscribe to notificationService', () => {
component.ngOnInit()
notificationSubject.next(notification)
expect(component.notifications).toEqual([notification])
})
})

View File

@@ -0,0 +1,48 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import {
NgbAccordionModule,
NgbProgressbarModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { Subscription } from 'rxjs'
import {
Notification,
NotificationService,
} from 'src/app/services/notification.service'
import { NotificationComponent } from '../notification/notification.component'
@Component({
selector: 'pngx-notification-list',
templateUrl: './notification-list.component.html',
styleUrls: ['./notification-list.component.scss'],
imports: [
NotificationComponent,
NgbAccordionModule,
NgbProgressbarModule,
NgxBootstrapIconsModule,
],
})
export class NotificationListComponent implements OnInit, OnDestroy {
constructor(public notificationService: NotificationService) {}
private subscription: Subscription
public notifications: Notification[] = [] // array to force change detection
ngOnDestroy(): void {
this.subscription?.unsubscribe()
}
ngOnInit(): void {
this.subscription = this.notificationService.showNotification.subscribe(
(notification) => {
this.notifications = notification ? [notification] : []
}
)
}
closeNotification() {
this.notificationService.closeNotification(this.notifications[0])
this.notifications = []
}
}

View File

@@ -1,39 +1,39 @@
<ngb-toast <ngb-toast
[autohide]="autohide" [autohide]="autohide"
[delay]="toast.delay" [delay]="notification.delay"
[class]="toast.classname" [class]="notification.classname"
[class.mb-2]="true" [class.mb-2]="true"
(shown)="onShown(toast)" (shown)="onShown(notification)"
(hidden)="hidden.emit(toast)"> (hidden)="hidden.emit(notification)">
@if (autohide) { @if (autohide) {
<ngb-progressbar class="position-absolute h-100 w-100 top-90 start-0 bottom-0 end-0 pe-none" type="dark" [max]="toast.delay" [value]="toast.delayRemaining"></ngb-progressbar> <ngb-progressbar class="position-absolute h-100 w-100 top-90 start-0 bottom-0 end-0 pe-none" type="dark" [max]="notification.delay" [value]="notification.delayRemaining"></ngb-progressbar>
<span class="visually-hidden">{{ toast.delayRemaining / 1000 | number: '1.0-0' }} seconds</span> <span class="visually-hidden">{{ notification.delayRemaining / 1000 | number: '1.0-0' }} seconds</span>
} }
<div class="d-flex align-items-top"> <div class="d-flex align-items-top">
@if (!toast.error) { @if (!notification.error) {
<i-bs width="0.9em" height="0.9em" name="info-circle"></i-bs> <i-bs width="0.9em" height="0.9em" name="info-circle"></i-bs>
} }
@if (toast.error) { @if (notification.error) {
<i-bs width="0.9em" height="0.9em" name="exclamation-triangle"></i-bs> <i-bs width="0.9em" height="0.9em" name="exclamation-triangle"></i-bs>
} }
<div> <div>
<p class="ms-2 mb-0">{{toast.content}}</p> <p class="ms-2 mb-0">{{notification.content}}</p>
@if (toast.error) { @if (notification.error) {
<details class="ms-2"> <details class="ms-2">
<div class="mt-2 ms-n4 me-n2 small"> <div class="mt-2 ms-n4 me-n2 small">
@if (isDetailedError(toast.error)) { @if (isDetailedError(notification.error)) {
<dl class="row mb-0"> <dl class="row mb-0">
<dt class="col-sm-3 fw-normal text-end">URL</dt> <dt class="col-sm-3 fw-normal text-end">URL</dt>
<dd class="col-sm-9">{{ toast.error.url }}</dd> <dd class="col-sm-9">{{ notification.error.url }}</dd>
<dt class="col-sm-3 fw-normal text-end" i18n>Status</dt> <dt class="col-sm-3 fw-normal text-end" i18n>Status</dt>
<dd class="col-sm-9">{{ toast.error.status }} <em>{{ toast.error.statusText }}</em></dd> <dd class="col-sm-9">{{ notification.error.status }} <em>{{ notification.error.statusText }}</em></dd>
<dt class="col-sm-3 fw-normal text-end" i18n>Error</dt> <dt class="col-sm-3 fw-normal text-end" i18n>Error</dt>
<dd class="col-sm-9">{{ getErrorText(toast.error) }}</dd> <dd class="col-sm-9">{{ getErrorText(notification.error) }}</dd>
</dl> </dl>
} }
<div class="row"> <div class="row">
<div class="col offset-sm-3"> <div class="col offset-sm-3">
<button class="btn btn-sm btn-outline-secondary" (click)="copyError(toast.error)"> <button class="btn btn-sm btn-outline-secondary" (click)="copyError(notification.error)">
@if (!copied) { @if (!copied) {
<i-bs name="clipboard"></i-bs>&nbsp; <i-bs name="clipboard"></i-bs>&nbsp;
} }
@@ -47,10 +47,10 @@
</div> </div>
</details> </details>
} }
@if (toast.action) { @if (notification.action) {
<p class="mb-0 mt-2"><button class="btn btn-sm btn-outline-secondary" (click)="close.emit(toast); toast.action()">{{toast.actionName}}</button></p> <p class="mb-0 mt-2"><button class="btn btn-sm btn-outline-secondary" (click)="close.emit(notification); notification.action()">{{notification.actionName}}</button></p>
} }
</div> </div>
<button type="button" class="btn-close ms-auto flex-shrink-0" data-bs-dismiss="toast" aria-label="Close" (click)="close.emit(toast);"></button> <button type="button" class="btn-close ms-auto flex-shrink-0" data-bs-dismiss="notification" aria-label="Close" (click)="close.emit(notification);"></button>
</div> </div>
</ngb-toast> </ngb-toast>

View File

@@ -9,15 +9,15 @@ import {
import { Clipboard } from '@angular/cdk/clipboard' import { Clipboard } from '@angular/cdk/clipboard'
import { allIcons, NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { allIcons, NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { ToastComponent } from './toast.component' import { NotificationComponent } from './notification.component'
const toast1 = { const notification1 = {
content: 'Error 1 content', content: 'Error 1 content',
delay: 5000, delay: 5000,
error: 'Error 1 string', error: 'Error 1 string',
} }
const toast2 = { const notification2 = {
content: 'Error 2 content', content: 'Error 2 content',
delay: 5000, delay: 5000,
error: { error: {
@@ -29,17 +29,17 @@ const toast2 = {
}, },
} }
describe('ToastComponent', () => { describe('NotificationComponent', () => {
let component: ToastComponent let component: NotificationComponent
let fixture: ComponentFixture<ToastComponent> let fixture: ComponentFixture<NotificationComponent>
let clipboard: Clipboard let clipboard: Clipboard
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [ToastComponent, NgxBootstrapIconsModule.pick(allIcons)], imports: [NotificationComponent, NgxBootstrapIconsModule.pick(allIcons)],
}).compileComponents() }).compileComponents()
fixture = TestBed.createComponent(ToastComponent) fixture = TestBed.createComponent(NotificationComponent)
clipboard = TestBed.inject(Clipboard) clipboard = TestBed.inject(Clipboard)
component = fixture.componentInstance component = fixture.componentInstance
}) })
@@ -48,18 +48,18 @@ describe('ToastComponent', () => {
expect(component).toBeTruthy() expect(component).toBeTruthy()
}) })
it('should countdown toast', fakeAsync(() => { it('should countdown notification', fakeAsync(() => {
component.toast = toast2 component.notification = notification2
fixture.detectChanges() fixture.detectChanges()
component.onShown(toast2) component.onShown(notification2)
tick(5000) tick(5000)
expect(component.toast.delayRemaining).toEqual(0) expect(component.notification.delayRemaining).toEqual(0)
flush() flush()
discardPeriodicTasks() discardPeriodicTasks()
})) }))
it('should show an error if given with toast', fakeAsync(() => { it('should show an error if given with notification', fakeAsync(() => {
component.toast = toast1 component.notification = notification1
fixture.detectChanges() fixture.detectChanges()
expect(fixture.nativeElement.querySelector('details')).not.toBeNull() expect(fixture.nativeElement.querySelector('details')).not.toBeNull()
@@ -70,7 +70,7 @@ describe('ToastComponent', () => {
})) }))
it('should show error details, support copy', fakeAsync(() => { it('should show error details, support copy', fakeAsync(() => {
component.toast = toast2 component.notification = notification2
fixture.detectChanges() fixture.detectChanges()
expect(fixture.nativeElement.querySelector('details')).not.toBeNull() expect(fixture.nativeElement.querySelector('details')).not.toBeNull()
@@ -79,7 +79,7 @@ describe('ToastComponent', () => {
) )
const copySpy = jest.spyOn(clipboard, 'copy') const copySpy = jest.spyOn(clipboard, 'copy')
component.copyError(toast2.error) component.copyError(notification2.error)
expect(copySpy).toHaveBeenCalled() expect(copySpy).toHaveBeenCalled()
flush() flush()
@@ -87,7 +87,7 @@ describe('ToastComponent', () => {
})) }))
it('should parse error text, add ellipsis', () => { it('should parse error text, add ellipsis', () => {
expect(component.getErrorText(toast2.error)).toEqual( expect(component.getErrorText(notification2.error)).toEqual(
'Error 2 message details' 'Error 2 message details'
) )
expect(component.getErrorText({ error: 'Error string no detail' })).toEqual( expect(component.getErrorText({ error: 'Error string no detail' })).toEqual(

View File

@@ -7,42 +7,43 @@ import {
} from '@ng-bootstrap/ng-bootstrap' } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { interval, take } from 'rxjs' import { interval, take } from 'rxjs'
import { Toast } from 'src/app/services/toast.service' import { Notification } from 'src/app/services/notification.service'
@Component({ @Component({
selector: 'pngx-toast', selector: 'pngx-notification',
imports: [ imports: [
DecimalPipe, DecimalPipe,
NgbToastModule, NgbToastModule,
NgbProgressbarModule, NgbProgressbarModule,
NgxBootstrapIconsModule, NgxBootstrapIconsModule,
], ],
templateUrl: './toast.component.html', templateUrl: './notification.component.html',
styleUrl: './toast.component.scss', styleUrl: './notification.component.scss',
}) })
export class ToastComponent { export class NotificationComponent {
@Input() toast: Toast @Input() notification: Notification
@Input() autohide: boolean = true @Input() autohide: boolean = true
@Output() hidden: EventEmitter<Toast> = new EventEmitter<Toast>() @Output() hidden: EventEmitter<Notification> =
new EventEmitter<Notification>()
@Output() close: EventEmitter<Toast> = new EventEmitter<Toast>() @Output() close: EventEmitter<Notification> = new EventEmitter<Notification>()
public copied: boolean = false public copied: boolean = false
constructor(private clipboard: Clipboard) {} constructor(private clipboard: Clipboard) {}
onShown(toast: Toast) { onShown(notification: Notification) {
if (!this.autohide) return if (!this.autohide) return
const refreshInterval = 150 const refreshInterval = 150
const delay = toast.delay - 500 // for fade animation const delay = notification.delay - 500 // for fade animation
interval(refreshInterval) interval(refreshInterval)
.pipe(take(Math.round(delay / refreshInterval))) .pipe(take(Math.round(delay / refreshInterval)))
.subscribe((count) => { .subscribe((count) => {
toast.delayRemaining = Math.max( notification.delayRemaining = Math.max(
0, 0,
delay - refreshInterval * (count + 1) delay - refreshInterval * (count + 1)
) )

View File

@@ -16,8 +16,8 @@ import {
} from '@ng-bootstrap/ng-bootstrap' } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs' import { of, throwError } from 'rxjs'
import { NotificationService } from 'src/app/services/notification.service'
import { ProfileService } from 'src/app/services/profile.service' import { ProfileService } from 'src/app/services/profile.service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmButtonComponent } from '../confirm-button/confirm-button.component' import { ConfirmButtonComponent } from '../confirm-button/confirm-button.component'
import { PasswordComponent } from '../input/password/password.component' import { PasswordComponent } from '../input/password/password.component'
import { TextComponent } from '../input/text/text.component' import { TextComponent } from '../input/text/text.component'
@@ -44,7 +44,7 @@ describe('ProfileEditDialogComponent', () => {
let component: ProfileEditDialogComponent let component: ProfileEditDialogComponent
let fixture: ComponentFixture<ProfileEditDialogComponent> let fixture: ComponentFixture<ProfileEditDialogComponent>
let profileService: ProfileService let profileService: ProfileService
let toastService: ToastService let notificationService: NotificationService
let clipboard: Clipboard let clipboard: Clipboard
beforeEach(() => { beforeEach(() => {
@@ -64,7 +64,7 @@ describe('ProfileEditDialogComponent', () => {
providers: [NgbActiveModal, provideHttpClient(withInterceptorsFromDi())], providers: [NgbActiveModal, provideHttpClient(withInterceptorsFromDi())],
}) })
profileService = TestBed.inject(ProfileService) profileService = TestBed.inject(ProfileService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
clipboard = TestBed.inject(Clipboard) clipboard = TestBed.inject(Clipboard)
fixture = TestBed.createComponent(ProfileEditDialogComponent) fixture = TestBed.createComponent(ProfileEditDialogComponent)
component = fixture.componentInstance component = fixture.componentInstance
@@ -94,13 +94,13 @@ describe('ProfileEditDialogComponent', () => {
auth_token: profile.auth_token, auth_token: profile.auth_token,
} }
const updateSpy = jest.spyOn(profileService, 'update') const updateSpy = jest.spyOn(profileService, 'update')
const errorSpy = jest.spyOn(toastService, 'showError') const errorSpy = jest.spyOn(notificationService, 'showError')
updateSpy.mockReturnValueOnce(throwError(() => new Error('failed to save'))) updateSpy.mockReturnValueOnce(throwError(() => new Error('failed to save')))
component.save() component.save()
expect(errorSpy).toHaveBeenCalled() expect(errorSpy).toHaveBeenCalled()
updateSpy.mockClear() updateSpy.mockClear()
const infoSpy = jest.spyOn(toastService, 'showInfo') const infoSpy = jest.spyOn(notificationService, 'showInfo')
component.form.patchValue(newProfile) component.form.patchValue(newProfile)
updateSpy.mockReturnValueOnce(of(newProfile)) updateSpy.mockReturnValueOnce(of(newProfile))
component.save() component.save()
@@ -239,7 +239,7 @@ describe('ProfileEditDialogComponent', () => {
getSpy.mockReturnValue(of(profile)) getSpy.mockReturnValue(of(profile))
const generateSpy = jest.spyOn(profileService, 'generateAuthToken') const generateSpy = jest.spyOn(profileService, 'generateAuthToken')
const errorSpy = jest.spyOn(toastService, 'showError') const errorSpy = jest.spyOn(notificationService, 'showError')
generateSpy.mockReturnValueOnce( generateSpy.mockReturnValueOnce(
throwError(() => new Error('failed to generate')) throwError(() => new Error('failed to generate'))
) )
@@ -275,7 +275,7 @@ describe('ProfileEditDialogComponent', () => {
getSpy.mockImplementation(() => of(profile)) getSpy.mockImplementation(() => of(profile))
component.ngOnInit() component.ngOnInit()
const errorSpy = jest.spyOn(toastService, 'showError') const errorSpy = jest.spyOn(notificationService, 'showError')
expect(component.socialAccounts).toContainEqual(socialAccount) expect(component.socialAccounts).toContainEqual(socialAccount)
@@ -300,13 +300,13 @@ describe('ProfileEditDialogComponent', () => {
secret: 'secret', secret: 'secret',
} }
const getSpy = jest.spyOn(profileService, 'getTotpSettings') const getSpy = jest.spyOn(profileService, 'getTotpSettings')
const toastSpy = jest.spyOn(toastService, 'showError') const notificationSpy = jest.spyOn(notificationService, 'showError')
getSpy.mockReturnValueOnce( getSpy.mockReturnValueOnce(
throwError(() => new Error('failed to get settings')) throwError(() => new Error('failed to get settings'))
) )
component.gettotpSettings() component.gettotpSettings()
expect(getSpy).toHaveBeenCalled() expect(getSpy).toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
getSpy.mockReturnValue(of(settings)) getSpy.mockReturnValue(of(settings))
component.gettotpSettings() component.gettotpSettings()
@@ -316,8 +316,8 @@ describe('ProfileEditDialogComponent', () => {
it('should activate totp', () => { it('should activate totp', () => {
const activateSpy = jest.spyOn(profileService, 'activateTotp') const activateSpy = jest.spyOn(profileService, 'activateTotp')
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
const error = new Error('failed to activate totp') const error = new Error('failed to activate totp')
activateSpy.mockReturnValueOnce(throwError(() => error)) activateSpy.mockReturnValueOnce(throwError(() => error))
component.totpSettings = { component.totpSettings = {
@@ -331,38 +331,44 @@ describe('ProfileEditDialogComponent', () => {
component.totpSettings.secret, component.totpSettings.secret,
component.form.get('totp_code').value component.form.get('totp_code').value
) )
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
activateSpy.mockReturnValueOnce(of({ success: false, recovery_codes: [] })) activateSpy.mockReturnValueOnce(of({ success: false, recovery_codes: [] }))
component.activateTotp() component.activateTotp()
expect(toastErrorSpy).toHaveBeenCalledWith('Error activating TOTP', error) expect(notificationErrorSpy).toHaveBeenCalledWith(
'Error activating TOTP',
error
)
activateSpy.mockReturnValueOnce( activateSpy.mockReturnValueOnce(
of({ success: true, recovery_codes: ['1', '2', '3'] }) of({ success: true, recovery_codes: ['1', '2', '3'] })
) )
component.activateTotp() component.activateTotp()
expect(toastInfoSpy).toHaveBeenCalled() expect(notificationInfoSpy).toHaveBeenCalled()
expect(component.isTotpEnabled).toBeTruthy() expect(component.isTotpEnabled).toBeTruthy()
expect(component.recoveryCodes).toEqual(['1', '2', '3']) expect(component.recoveryCodes).toEqual(['1', '2', '3'])
}) })
it('should deactivate totp', () => { it('should deactivate totp', () => {
const deactivateSpy = jest.spyOn(profileService, 'deactivateTotp') const deactivateSpy = jest.spyOn(profileService, 'deactivateTotp')
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
const error = new Error('failed to deactivate totp') const error = new Error('failed to deactivate totp')
deactivateSpy.mockReturnValueOnce(throwError(() => error)) deactivateSpy.mockReturnValueOnce(throwError(() => error))
component.deactivateTotp() component.deactivateTotp()
expect(deactivateSpy).toHaveBeenCalled() expect(deactivateSpy).toHaveBeenCalled()
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
deactivateSpy.mockReturnValueOnce(of(false)) deactivateSpy.mockReturnValueOnce(of(false))
component.deactivateTotp() component.deactivateTotp()
expect(toastErrorSpy).toHaveBeenCalledWith('Error deactivating TOTP', error) expect(notificationErrorSpy).toHaveBeenCalledWith(
'Error deactivating TOTP',
error
)
deactivateSpy.mockReturnValueOnce(of(true)) deactivateSpy.mockReturnValueOnce(of(true))
component.deactivateTotp() component.deactivateTotp()
expect(toastInfoSpy).toHaveBeenCalled() expect(notificationInfoSpy).toHaveBeenCalled()
expect(component.isTotpEnabled).toBeFalsy() expect(component.isTotpEnabled).toBeFalsy()
}) })

View File

@@ -19,8 +19,8 @@ import {
TotpSettings, TotpSettings,
} from 'src/app/data/user-profile' } from 'src/app/data/user-profile'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { NotificationService } from 'src/app/services/notification.service'
import { ProfileService } from 'src/app/services/profile.service' import { ProfileService } from 'src/app/services/profile.service'
import { ToastService } from 'src/app/services/toast.service'
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component' import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
import { ConfirmButtonComponent } from '../confirm-button/confirm-button.component' import { ConfirmButtonComponent } from '../confirm-button/confirm-button.component'
import { PasswordComponent } from '../input/password/password.component' import { PasswordComponent } from '../input/password/password.component'
@@ -86,7 +86,7 @@ export class ProfileEditDialogComponent
constructor( constructor(
private profileService: ProfileService, private profileService: ProfileService,
public activeModal: NgbActiveModal, public activeModal: NgbActiveModal,
private toastService: ToastService, private notificationService: NotificationService,
private clipboard: Clipboard private clipboard: Clipboard
) { ) {
super() super()
@@ -192,9 +192,11 @@ export class ProfileEditDialogComponent
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe({ .subscribe({
next: () => { next: () => {
this.toastService.showInfo($localize`Profile updated successfully`) this.notificationService.showInfo(
$localize`Profile updated successfully`
)
if (passwordChanged) { if (passwordChanged) {
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Password has been changed, you will be logged out momentarily.` $localize`Password has been changed, you will be logged out momentarily.`
) )
setTimeout(() => { setTimeout(() => {
@@ -204,7 +206,10 @@ export class ProfileEditDialogComponent
this.activeModal.close() this.activeModal.close()
}, },
error: (error) => { error: (error) => {
this.toastService.showError($localize`Error saving profile`, error) this.notificationService.showError(
$localize`Error saving profile`,
error
)
this.networkActive = false this.networkActive = false
}, },
}) })
@@ -220,7 +225,7 @@ export class ProfileEditDialogComponent
this.form.patchValue({ auth_token: token }) this.form.patchValue({ auth_token: token })
}, },
error: (error) => { error: (error) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error generating auth token`, $localize`Error generating auth token`,
error error
) )
@@ -245,7 +250,7 @@ export class ProfileEditDialogComponent
this.socialAccounts = this.socialAccounts.filter((a) => a.id != id) this.socialAccounts = this.socialAccounts.filter((a) => a.id != id)
}, },
error: (error) => { error: (error) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error disconnecting social account`, $localize`Error disconnecting social account`,
error error
) )
@@ -264,7 +269,7 @@ export class ProfileEditDialogComponent
this.totpSettings = totpSettings this.totpSettings = totpSettings
}, },
error: (error) => { error: (error) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error fetching TOTP settings`, $localize`Error fetching TOTP settings`,
error error
) )
@@ -286,15 +291,20 @@ export class ProfileEditDialogComponent
this.recoveryCodes = activationResponse.recovery_codes this.recoveryCodes = activationResponse.recovery_codes
this.form.get('totp_code').enable() this.form.get('totp_code').enable()
if (activationResponse.success) { if (activationResponse.success) {
this.toastService.showInfo($localize`TOTP activated successfully`) this.notificationService.showInfo(
$localize`TOTP activated successfully`
)
} else { } else {
this.toastService.showError($localize`Error activating TOTP`) this.notificationService.showError($localize`Error activating TOTP`)
} }
}, },
error: (error) => { error: (error) => {
this.totpLoading = false this.totpLoading = false
this.form.get('totp_code').enable() this.form.get('totp_code').enable()
this.toastService.showError($localize`Error activating TOTP`, error) this.notificationService.showError(
$localize`Error activating TOTP`,
error
)
}, },
}) })
} }
@@ -310,14 +320,21 @@ export class ProfileEditDialogComponent
this.isTotpEnabled = !success this.isTotpEnabled = !success
this.recoveryCodes = null this.recoveryCodes = null
if (success) { if (success) {
this.toastService.showInfo($localize`TOTP deactivated successfully`) this.notificationService.showInfo(
$localize`TOTP deactivated successfully`
)
} else { } else {
this.toastService.showError($localize`Error deactivating TOTP`) this.notificationService.showError(
$localize`Error deactivating TOTP`
)
} }
}, },
error: (error) => { error: (error) => {
this.totpLoading = false this.totpLoading = false
this.toastService.showError($localize`Error deactivating TOTP`, error) this.notificationService.showError(
$localize`Error deactivating TOTP`,
error
)
}, },
}) })
} }

View File

@@ -15,8 +15,8 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs' import { of, throwError } from 'rxjs'
import { FileVersion, ShareLink } from 'src/app/data/share-link' import { FileVersion, ShareLink } from 'src/app/data/share-link'
import { NotificationService } from 'src/app/services/notification.service'
import { ShareLinkService } from 'src/app/services/rest/share-link.service' import { ShareLinkService } from 'src/app/services/rest/share-link.service'
import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
import { ShareLinksDialogComponent } from './share-links-dialog.component' import { ShareLinksDialogComponent } from './share-links-dialog.component'
@@ -24,7 +24,7 @@ describe('ShareLinksDialogComponent', () => {
let component: ShareLinksDialogComponent let component: ShareLinksDialogComponent
let fixture: ComponentFixture<ShareLinksDialogComponent> let fixture: ComponentFixture<ShareLinksDialogComponent>
let shareLinkService: ShareLinkService let shareLinkService: ShareLinkService
let toastService: ToastService let notificationService: NotificationService
let httpController: HttpTestingController let httpController: HttpTestingController
let clipboard: Clipboard let clipboard: Clipboard
@@ -43,7 +43,7 @@ describe('ShareLinksDialogComponent', () => {
fixture = TestBed.createComponent(ShareLinksDialogComponent) fixture = TestBed.createComponent(ShareLinksDialogComponent)
shareLinkService = TestBed.inject(ShareLinkService) shareLinkService = TestBed.inject(ShareLinkService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
httpController = TestBed.inject(HttpTestingController) httpController = TestBed.inject(HttpTestingController)
clipboard = TestBed.inject(Clipboard) clipboard = TestBed.inject(Clipboard)
@@ -89,7 +89,7 @@ describe('ShareLinksDialogComponent', () => {
}) })
it('should show error on refresh if needed', () => { it('should show error on refresh if needed', () => {
const toastSpy = jest.spyOn(toastService, 'showError') const notificationSpy = jest.spyOn(notificationService, 'showError')
jest jest
.spyOn(shareLinkService, 'getLinksForDocument') .spyOn(shareLinkService, 'getLinksForDocument')
.mockReturnValueOnce(throwError(() => new Error('Unable to get links'))) .mockReturnValueOnce(throwError(() => new Error('Unable to get links')))
@@ -97,7 +97,7 @@ describe('ShareLinksDialogComponent', () => {
component.ngOnInit() component.ngOnInit()
fixture.detectChanges() fixture.detectChanges()
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
}) })
it('should support link creation then refresh & copy url', fakeAsync(() => { it('should support link creation then refresh & copy url', fakeAsync(() => {
@@ -138,7 +138,7 @@ describe('ShareLinksDialogComponent', () => {
const expiration = new Date() const expiration = new Date()
expiration.setDate(expiration.getDate() + 7) expiration.setDate(expiration.getDate() + 7)
const toastSpy = jest.spyOn(toastService, 'showError') const notificationSpy = jest.spyOn(notificationService, 'showError')
component.createLink() component.createLink()
@@ -150,7 +150,7 @@ describe('ShareLinksDialogComponent', () => {
) )
fixture.detectChanges() fixture.detectChanges()
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
}) })
it('should support delete links & refresh', () => { it('should support delete links & refresh', () => {
@@ -165,13 +165,13 @@ describe('ShareLinksDialogComponent', () => {
}) })
it('should show error on delete if needed', () => { it('should show error on delete if needed', () => {
const toastSpy = jest.spyOn(toastService, 'showError') const notificationSpy = jest.spyOn(notificationService, 'showError')
jest jest
.spyOn(shareLinkService, 'delete') .spyOn(shareLinkService, 'delete')
.mockReturnValueOnce(throwError(() => new Error('Unable to delete link'))) .mockReturnValueOnce(throwError(() => new Error('Unable to delete link')))
component.delete(null) component.delete(null)
fixture.detectChanges() fixture.detectChanges()
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
}) })
it('should format days remaining', () => { it('should format days remaining', () => {

View File

@@ -5,8 +5,8 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { first } from 'rxjs' import { first } from 'rxjs'
import { FileVersion, ShareLink } from 'src/app/data/share-link' import { FileVersion, ShareLink } from 'src/app/data/share-link'
import { NotificationService } from 'src/app/services/notification.service'
import { ShareLinkService } from 'src/app/services/rest/share-link.service' import { ShareLinkService } from 'src/app/services/rest/share-link.service'
import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
@Component({ @Component({
@@ -61,7 +61,7 @@ export class ShareLinksDialogComponent implements OnInit {
constructor( constructor(
private activeModal: NgbActiveModal, private activeModal: NgbActiveModal,
private shareLinkService: ShareLinkService, private shareLinkService: ShareLinkService,
private toastService: ToastService, private notificationService: NotificationService,
private clipboard: Clipboard private clipboard: Clipboard
) {} ) {}
@@ -81,7 +81,7 @@ export class ShareLinksDialogComponent implements OnInit {
this.shareLinks = results this.shareLinks = results
}, },
error: (e) => { error: (e) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error retrieving links`, $localize`Error retrieving links`,
10000, 10000,
e e
@@ -130,7 +130,11 @@ export class ShareLinksDialogComponent implements OnInit {
this.refresh() this.refresh()
}, },
error: (e) => { error: (e) => {
this.toastService.showError($localize`Error deleting link`, 10000, e) this.notificationService.showError(
$localize`Error deleting link`,
10000,
e
)
}, },
}) })
} }
@@ -158,7 +162,11 @@ export class ShareLinksDialogComponent implements OnInit {
}, },
error: (e) => { error: (e) => {
this.loading = false this.loading = false
this.toastService.showError($localize`Error creating link`, 10000, e) this.notificationService.showError(
$localize`Error creating link`,
10000,
e
)
}, },
}) })
} }

View File

@@ -16,9 +16,9 @@ import {
SystemStatus, SystemStatus,
SystemStatusItemStatus, SystemStatusItemStatus,
} from 'src/app/data/system-status' } from 'src/app/data/system-status'
import { NotificationService } from 'src/app/services/notification.service'
import { SystemStatusService } from 'src/app/services/system-status.service' import { SystemStatusService } from 'src/app/services/system-status.service'
import { TasksService } from 'src/app/services/tasks.service' import { TasksService } from 'src/app/services/tasks.service'
import { ToastService } from 'src/app/services/toast.service'
import { SystemStatusDialogComponent } from './system-status-dialog.component' import { SystemStatusDialogComponent } from './system-status-dialog.component'
const status: SystemStatus = { const status: SystemStatus = {
@@ -61,7 +61,7 @@ describe('SystemStatusDialogComponent', () => {
let clipboard: Clipboard let clipboard: Clipboard
let tasksService: TasksService let tasksService: TasksService
let systemStatusService: SystemStatusService let systemStatusService: SystemStatusService
let toastService: ToastService let notificationService: NotificationService
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
@@ -82,7 +82,7 @@ describe('SystemStatusDialogComponent', () => {
clipboard = TestBed.inject(Clipboard) clipboard = TestBed.inject(Clipboard)
tasksService = TestBed.inject(TasksService) tasksService = TestBed.inject(TasksService)
systemStatusService = TestBed.inject(SystemStatusService) systemStatusService = TestBed.inject(SystemStatusService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
fixture.detectChanges() fixture.detectChanges()
}) })
@@ -116,9 +116,9 @@ describe('SystemStatusDialogComponent', () => {
expect(component.isRunning(PaperlessTaskName.SanityCheck)).toBeFalsy() expect(component.isRunning(PaperlessTaskName.SanityCheck)).toBeFalsy()
}) })
it('should support running tasks, refresh status and show toasts', () => { it('should support running tasks, refresh status and show notifications', () => {
const toastSpy = jest.spyOn(toastService, 'showInfo') const notificationSpy = jest.spyOn(notificationService, 'showInfo')
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const getStatusSpy = jest.spyOn(systemStatusService, 'get') const getStatusSpy = jest.spyOn(systemStatusService, 'get')
const runSpy = jest.spyOn(tasksService, 'run') const runSpy = jest.spyOn(tasksService, 'run')
@@ -126,7 +126,7 @@ describe('SystemStatusDialogComponent', () => {
runSpy.mockReturnValue(throwError(() => new Error('error'))) runSpy.mockReturnValue(throwError(() => new Error('error')))
component.runTask(PaperlessTaskName.IndexOptimize) component.runTask(PaperlessTaskName.IndexOptimize)
expect(runSpy).toHaveBeenCalledWith(PaperlessTaskName.IndexOptimize) expect(runSpy).toHaveBeenCalledWith(PaperlessTaskName.IndexOptimize)
expect(toastErrorSpy).toHaveBeenCalledWith( expect(notificationErrorSpy).toHaveBeenCalledWith(
`Failed to start task ${PaperlessTaskName.IndexOptimize}, see the logs for more details`, `Failed to start task ${PaperlessTaskName.IndexOptimize}, see the logs for more details`,
expect.any(Error) expect.any(Error)
) )
@@ -138,7 +138,7 @@ describe('SystemStatusDialogComponent', () => {
expect(runSpy).toHaveBeenCalledWith(PaperlessTaskName.IndexOptimize) expect(runSpy).toHaveBeenCalledWith(PaperlessTaskName.IndexOptimize)
expect(getStatusSpy).toHaveBeenCalled() expect(getStatusSpy).toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith( expect(notificationSpy).toHaveBeenCalledWith(
`Task ${PaperlessTaskName.IndexOptimize} started` `Task ${PaperlessTaskName.IndexOptimize} started`
) )
}) })

View File

@@ -14,10 +14,10 @@ import {
} from 'src/app/data/system-status' } from 'src/app/data/system-status'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { FileSizePipe } from 'src/app/pipes/file-size.pipe' import { FileSizePipe } from 'src/app/pipes/file-size.pipe'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { SystemStatusService } from 'src/app/services/system-status.service' import { SystemStatusService } from 'src/app/services/system-status.service'
import { TasksService } from 'src/app/services/tasks.service' import { TasksService } from 'src/app/services/tasks.service'
import { ToastService } from 'src/app/services/toast.service'
@Component({ @Component({
selector: 'pngx-system-status-dialog', selector: 'pngx-system-status-dialog',
@@ -51,7 +51,7 @@ export class SystemStatusDialogComponent {
private clipboard: Clipboard, private clipboard: Clipboard,
private systemStatusService: SystemStatusService, private systemStatusService: SystemStatusService,
private tasksService: TasksService, private tasksService: TasksService,
private toastService: ToastService, private notificationService: NotificationService,
private permissionsService: PermissionsService private permissionsService: PermissionsService
) {} ) {}
@@ -79,7 +79,7 @@ export class SystemStatusDialogComponent {
public runTask(taskName: PaperlessTaskName) { public runTask(taskName: PaperlessTaskName) {
this.runningTasks.add(taskName) this.runningTasks.add(taskName)
this.toastService.showInfo(`Task ${taskName} started`) this.notificationService.showInfo(`Task ${taskName} started`)
this.tasksService.run(taskName).subscribe({ this.tasksService.run(taskName).subscribe({
next: () => { next: () => {
this.runningTasks.delete(taskName) this.runningTasks.delete(taskName)
@@ -91,7 +91,7 @@ export class SystemStatusDialogComponent {
}, },
error: (err) => { error: (err) => {
this.runningTasks.delete(taskName) this.runningTasks.delete(taskName)
this.toastService.showError( this.notificationService.showError(
`Failed to start task ${taskName}, see the logs for more details`, `Failed to start task ${taskName}, see the logs for more details`,
err err
) )

View File

@@ -1,3 +0,0 @@
@for (toast of toasts; track toast.id) {
<pngx-toast [toast]="toast" [autohide]="true" (close)="closeToast()"></pngx-toast>
}

View File

@@ -1,71 +0,0 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { Subject } from 'rxjs'
import { Toast, ToastService } from 'src/app/services/toast.service'
import { ToastsComponent } from './toasts.component'
const toast = {
content: 'Error 2 content',
delay: 5000,
error: {
url: 'https://example.com',
status: 500,
statusText: 'Internal Server Error',
message: 'Internal server error 500 message',
error: { detail: 'Error 2 message details' },
},
}
describe('ToastsComponent', () => {
let component: ToastsComponent
let fixture: ComponentFixture<ToastsComponent>
let toastService: ToastService
let toastSubject: Subject<Toast> = new Subject()
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [ToastsComponent, NgxBootstrapIconsModule.pick(allIcons)],
providers: [
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
}).compileComponents()
fixture = TestBed.createComponent(ToastsComponent)
toastService = TestBed.inject(ToastService)
jest.replaceProperty(toastService, 'showToast', toastSubject)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
it('should close toast', () => {
component.toasts = [toast]
const closeToastSpy = jest.spyOn(toastService, 'closeToast')
component.closeToast()
expect(component.toasts).toEqual([])
expect(closeToastSpy).toHaveBeenCalledWith(toast)
})
it('should unsubscribe', () => {
const unsubscribeSpy = jest.spyOn(
(component as any).subscription,
'unsubscribe'
)
component.ngOnDestroy()
expect(unsubscribeSpy).toHaveBeenCalled()
})
it('should subscribe to toastService', () => {
component.ngOnInit()
toastSubject.next(toast)
expect(component.toasts).toEqual([toast])
})
})

View File

@@ -1,43 +0,0 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import {
NgbAccordionModule,
NgbProgressbarModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { Subscription } from 'rxjs'
import { Toast, ToastService } from 'src/app/services/toast.service'
import { ToastComponent } from '../toast/toast.component'
@Component({
selector: 'pngx-toasts',
templateUrl: './toasts.component.html',
styleUrls: ['./toasts.component.scss'],
imports: [
ToastComponent,
NgbAccordionModule,
NgbProgressbarModule,
NgxBootstrapIconsModule,
],
})
export class ToastsComponent implements OnInit, OnDestroy {
constructor(public toastService: ToastService) {}
private subscription: Subscription
public toasts: Toast[] = [] // array to force change detection
ngOnDestroy(): void {
this.subscription?.unsubscribe()
}
ngOnInit(): void {
this.subscription = this.toastService.showToast.subscribe((toast) => {
this.toasts = toast ? [toast] : []
})
}
closeToast() {
this.toastService.closeToast(this.toasts[0])
this.toasts = []
}
}

View File

@@ -12,10 +12,10 @@ import { SavedView } from 'src/app/data/saved-view'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings' import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service' import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { LogoComponent } from '../common/logo/logo.component' import { LogoComponent } from '../common/logo/logo.component'
import { PageHeaderComponent } from '../common/page-header/page-header.component' import { PageHeaderComponent } from '../common/page-header/page-header.component'
import { DashboardComponent } from './dashboard.component' import { DashboardComponent } from './dashboard.component'
@@ -68,7 +68,7 @@ describe('DashboardComponent', () => {
let fixture: ComponentFixture<DashboardComponent> let fixture: ComponentFixture<DashboardComponent>
let settingsService: SettingsService let settingsService: SettingsService
let tourService: TourService let tourService: TourService
let toastService: ToastService let notificationService: NotificationService
beforeEach(async () => { beforeEach(async () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -121,7 +121,7 @@ describe('DashboardComponent', () => {
if (key === SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER) return [0, 2, 3] if (key === SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER) return [0, 2, 3]
}) })
tourService = TestBed.inject(TourService) tourService = TestBed.inject(TourService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
fixture = TestBed.createComponent(DashboardComponent) fixture = TestBed.createComponent(DashboardComponent)
component = fixture.componentInstance component = fixture.componentInstance
@@ -166,7 +166,7 @@ describe('DashboardComponent', () => {
it('should update saved view sorting on drag + drop, show info', () => { it('should update saved view sorting on drag + drop, show info', () => {
const settingsSpy = jest.spyOn(settingsService, 'updateDashboardViewsSort') const settingsSpy = jest.spyOn(settingsService, 'updateDashboardViewsSort')
const toastSpy = jest.spyOn(toastService, 'showInfo') const notificationSpy = jest.spyOn(notificationService, 'showInfo')
jest.spyOn(settingsService, 'storeSettings').mockReturnValue(of(true)) jest.spyOn(settingsService, 'storeSettings').mockReturnValue(of(true))
component.onDrop({ previousIndex: 0, currentIndex: 1 } as CdkDragDrop< component.onDrop({ previousIndex: 0, currentIndex: 1 } as CdkDragDrop<
SavedView[] SavedView[]
@@ -176,7 +176,7 @@ describe('DashboardComponent', () => {
saved_views[0], saved_views[0],
saved_views[3], saved_views[3],
]) ])
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
}) })
it('should update saved view sorting on drag + drop, show error', () => { it('should update saved view sorting on drag + drop, show error', () => {
@@ -187,13 +187,13 @@ describe('DashboardComponent', () => {
fixture = TestBed.createComponent(DashboardComponent) fixture = TestBed.createComponent(DashboardComponent)
component = fixture.componentInstance component = fixture.componentInstance
fixture.detectChanges() fixture.detectChanges()
const toastSpy = jest.spyOn(toastService, 'showError') const notificationSpy = jest.spyOn(notificationService, 'showError')
jest jest
.spyOn(settingsService, 'storeSettings') .spyOn(settingsService, 'storeSettings')
.mockReturnValue(throwError(() => new Error('unable to save'))) .mockReturnValue(throwError(() => new Error('unable to save')))
component.onDrop({ previousIndex: 0, currentIndex: 2 } as CdkDragDrop< component.onDrop({ previousIndex: 0, currentIndex: 2 } as CdkDragDrop<
SavedView[] SavedView[]
>) >)
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
}) })
}) })

View File

@@ -9,9 +9,9 @@ import { Component } from '@angular/core'
import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap' import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap'
import { SavedView } from 'src/app/data/saved-view' import { SavedView } from 'src/app/data/saved-view'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { NotificationService } from 'src/app/services/notification.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service' import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
import { LogoComponent } from '../common/logo/logo.component' import { LogoComponent } from '../common/logo/logo.component'
import { PageHeaderComponent } from '../common/page-header/page-header.component' import { PageHeaderComponent } from '../common/page-header/page-header.component'
@@ -43,7 +43,7 @@ export class DashboardComponent extends ComponentWithPermissions {
public settingsService: SettingsService, public settingsService: SettingsService,
public savedViewService: SavedViewService, public savedViewService: SavedViewService,
private tourService: TourService, private tourService: TourService,
private toastService: ToastService private notificationService: NotificationService
) { ) {
super() super()
@@ -87,10 +87,13 @@ export class DashboardComponent extends ComponentWithPermissions {
.updateDashboardViewsSort(this.dashboardViews) .updateDashboardViewsSort(this.dashboardViews)
.subscribe({ .subscribe({
next: () => { next: () => {
this.toastService.showInfo($localize`Dashboard updated`) this.notificationService.showInfo($localize`Dashboard updated`)
}, },
error: (e) => { error: (e) => {
this.toastService.showError($localize`Error updating dashboard`, e) this.notificationService.showError(
$localize`Error updating dashboard`,
e
)
}, },
}) })
} }

View File

@@ -48,6 +48,7 @@ import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe' import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
import { ComponentRouterService } from 'src/app/services/component-router.service' import { ComponentRouterService } from 'src/app/services/component-router.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { NotificationService } from 'src/app/services/notification.service'
import { OpenDocumentsService } from 'src/app/services/open-documents.service' import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service' import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
@@ -58,7 +59,6 @@ import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { TagService } from 'src/app/services/rest/tag.service' import { TagService } from 'src/app/services/rest/tag.service'
import { UserService } from 'src/app/services/rest/user.service' import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
import { CustomFieldsDropdownComponent } from '../common/custom-fields-dropdown/custom-fields-dropdown.component' import { CustomFieldsDropdownComponent } from '../common/custom-fields-dropdown/custom-fields-dropdown.component'
@@ -127,7 +127,7 @@ describe('DocumentDetailComponent', () => {
let documentService: DocumentService let documentService: DocumentService
let openDocumentsService: OpenDocumentsService let openDocumentsService: OpenDocumentsService
let modalService: NgbModal let modalService: NgbModal
let toastService: ToastService let notificationService: NotificationService
let documentListViewService: DocumentListViewService let documentListViewService: DocumentListViewService
let settingsService: SettingsService let settingsService: SettingsService
let customFieldsService: CustomFieldsService let customFieldsService: CustomFieldsService
@@ -264,7 +264,7 @@ describe('DocumentDetailComponent', () => {
openDocumentsService = TestBed.inject(OpenDocumentsService) openDocumentsService = TestBed.inject(OpenDocumentsService)
documentService = TestBed.inject(DocumentService) documentService = TestBed.inject(DocumentService)
modalService = TestBed.inject(NgbModal) modalService = TestBed.inject(NgbModal)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
documentListViewService = TestBed.inject(DocumentListViewService) documentListViewService = TestBed.inject(DocumentListViewService)
settingsService = TestBed.inject(SettingsService) settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = { id: 1 } settingsService.currentUser = { id: 1 }
@@ -447,68 +447,68 @@ describe('DocumentDetailComponent', () => {
expect(navigateSpy).toHaveBeenCalledWith(['404'], { replaceUrl: true }) expect(navigateSpy).toHaveBeenCalledWith(['404'], { replaceUrl: true })
}) })
it('should support save, close and show success toast', () => { it('should support save, close and show success notification', () => {
initNormally() initNormally()
component.title = 'Foo Bar' component.title = 'Foo Bar'
const closeSpy = jest.spyOn(component, 'close') const closeSpy = jest.spyOn(component, 'close')
const updateSpy = jest.spyOn(documentService, 'update') const updateSpy = jest.spyOn(documentService, 'update')
const toastSpy = jest.spyOn(toastService, 'showInfo') const notificationSpy = jest.spyOn(notificationService, 'showInfo')
updateSpy.mockImplementation((o) => of(doc)) updateSpy.mockImplementation((o) => of(doc))
component.save(true) component.save(true)
expect(updateSpy).toHaveBeenCalled() expect(updateSpy).toHaveBeenCalled()
expect(closeSpy).toHaveBeenCalled() expect(closeSpy).toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith( expect(notificationSpy).toHaveBeenCalledWith(
'Document "Doc 3" saved successfully.' 'Document "Doc 3" saved successfully.'
) )
}) })
it('should support save without close and show success toast', () => { it('should support save without close and show success notification', () => {
initNormally() initNormally()
component.title = 'Foo Bar' component.title = 'Foo Bar'
const closeSpy = jest.spyOn(component, 'close') const closeSpy = jest.spyOn(component, 'close')
const updateSpy = jest.spyOn(documentService, 'update') const updateSpy = jest.spyOn(documentService, 'update')
const toastSpy = jest.spyOn(toastService, 'showInfo') const notificationSpy = jest.spyOn(notificationService, 'showInfo')
updateSpy.mockImplementation((o) => of(doc)) updateSpy.mockImplementation((o) => of(doc))
component.save() component.save()
expect(updateSpy).toHaveBeenCalled() expect(updateSpy).toHaveBeenCalled()
expect(closeSpy).not.toHaveBeenCalled() expect(closeSpy).not.toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith( expect(notificationSpy).toHaveBeenCalledWith(
'Document "Doc 3" saved successfully.' 'Document "Doc 3" saved successfully.'
) )
}) })
it('should show toast error on save if error occurs', () => { it('should show notification error on save if error occurs', () => {
currentUserHasObjectPermissions = true currentUserHasObjectPermissions = true
initNormally() initNormally()
component.title = 'Foo Bar' component.title = 'Foo Bar'
const closeSpy = jest.spyOn(component, 'close') const closeSpy = jest.spyOn(component, 'close')
const updateSpy = jest.spyOn(documentService, 'update') const updateSpy = jest.spyOn(documentService, 'update')
const toastSpy = jest.spyOn(toastService, 'showError') const notificationSpy = jest.spyOn(notificationService, 'showError')
const error = new Error('failed to save') const error = new Error('failed to save')
updateSpy.mockImplementation(() => throwError(() => error)) updateSpy.mockImplementation(() => throwError(() => error))
component.save() component.save()
expect(updateSpy).toHaveBeenCalled() expect(updateSpy).toHaveBeenCalled()
expect(closeSpy).not.toHaveBeenCalled() expect(closeSpy).not.toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith( expect(notificationSpy).toHaveBeenCalledWith(
'Error saving document "Doc 3"', 'Error saving document "Doc 3"',
error error
) )
}) })
it('should show error toast on save but close if user can no longer edit', () => { it('should show error notification on save but close if user can no longer edit', () => {
currentUserHasObjectPermissions = false currentUserHasObjectPermissions = false
initNormally() initNormally()
component.title = 'Foo Bar' component.title = 'Foo Bar'
const closeSpy = jest.spyOn(component, 'close') const closeSpy = jest.spyOn(component, 'close')
const updateSpy = jest.spyOn(documentService, 'update') const updateSpy = jest.spyOn(documentService, 'update')
const toastSpy = jest.spyOn(toastService, 'showInfo') const notificationSpy = jest.spyOn(notificationService, 'showInfo')
updateSpy.mockImplementation(() => updateSpy.mockImplementation(() =>
throwError(() => new Error('failed to save')) throwError(() => new Error('failed to save'))
) )
component.save(true) component.save(true)
expect(updateSpy).toHaveBeenCalled() expect(updateSpy).toHaveBeenCalled()
expect(closeSpy).toHaveBeenCalled() expect(closeSpy).toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith( expect(notificationSpy).toHaveBeenCalledWith(
'Document "Doc 3" saved successfully.' 'Document "Doc 3" saved successfully.'
) )
}) })
@@ -531,19 +531,19 @@ describe('DocumentDetailComponent', () => {
expect expect
}) })
it('should show toast error on save & next if error occurs', () => { it('should show notification error on save & next if error occurs', () => {
currentUserHasObjectPermissions = true currentUserHasObjectPermissions = true
initNormally() initNormally()
component.title = 'Foo Bar' component.title = 'Foo Bar'
const closeSpy = jest.spyOn(component, 'close') const closeSpy = jest.spyOn(component, 'close')
const updateSpy = jest.spyOn(documentService, 'update') const updateSpy = jest.spyOn(documentService, 'update')
const toastSpy = jest.spyOn(toastService, 'showError') const notificationSpy = jest.spyOn(notificationService, 'showError')
const error = new Error('failed to save') const error = new Error('failed to save')
updateSpy.mockImplementation(() => throwError(() => error)) updateSpy.mockImplementation(() => throwError(() => error))
component.saveEditNext() component.saveEditNext()
expect(updateSpy).toHaveBeenCalled() expect(updateSpy).toHaveBeenCalled()
expect(closeSpy).not.toHaveBeenCalled() expect(closeSpy).not.toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith('Error saving document', error) expect(notificationSpy).toHaveBeenCalledWith('Error saving document', error)
}) })
it('should show save button and save & close or save & next', () => { it('should show save button and save & close or save & next', () => {
@@ -668,13 +668,13 @@ describe('DocumentDetailComponent', () => {
let openModal: NgbModalRef let openModal: NgbModalRef
modalService.activeInstances.subscribe((modal) => (openModal = modal[0])) modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
const modalSpy = jest.spyOn(modalService, 'open') const modalSpy = jest.spyOn(modalService, 'open')
const toastSpy = jest.spyOn(toastService, 'showInfo') const notificationSpy = jest.spyOn(notificationService, 'showInfo')
component.reprocess() component.reprocess()
const modalCloseSpy = jest.spyOn(openModal, 'close') const modalCloseSpy = jest.spyOn(openModal, 'close')
openModal.componentInstance.confirmClicked.next() openModal.componentInstance.confirmClicked.next()
expect(bulkEditSpy).toHaveBeenCalledWith([doc.id], 'reprocess', {}) expect(bulkEditSpy).toHaveBeenCalledWith([doc.id], 'reprocess', {})
expect(modalSpy).toHaveBeenCalled() expect(modalSpy).toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
expect(modalCloseSpy).toHaveBeenCalled() expect(modalCloseSpy).toHaveBeenCalled()
}) })
@@ -683,12 +683,12 @@ describe('DocumentDetailComponent', () => {
const bulkEditSpy = jest.spyOn(documentService, 'bulkEdit') const bulkEditSpy = jest.spyOn(documentService, 'bulkEdit')
let openModal: NgbModalRef let openModal: NgbModalRef
modalService.activeInstances.subscribe((modal) => (openModal = modal[0])) modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
const toastSpy = jest.spyOn(toastService, 'showError') const notificationSpy = jest.spyOn(notificationService, 'showError')
component.reprocess() component.reprocess()
const modalCloseSpy = jest.spyOn(openModal, 'close') const modalCloseSpy = jest.spyOn(openModal, 'close')
bulkEditSpy.mockReturnValue(throwError(() => new Error('error occurred'))) bulkEditSpy.mockReturnValue(throwError(() => new Error('error occurred')))
openModal.componentInstance.confirmClicked.next() openModal.componentInstance.confirmClicked.next()
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
expect(modalCloseSpy).not.toHaveBeenCalled() expect(modalCloseSpy).not.toHaveBeenCalled()
}) })
@@ -942,9 +942,12 @@ describe('DocumentDetailComponent', () => {
jest jest
.spyOn(documentService, 'getMetadata') .spyOn(documentService, 'getMetadata')
.mockReturnValue(throwError(() => error)) .mockReturnValue(throwError(() => error))
const toastSpy = jest.spyOn(toastService, 'showError') const notificationSpy = jest.spyOn(notificationService, 'showError')
initNormally() initNormally()
expect(toastSpy).toHaveBeenCalledWith('Error retrieving metadata', error) expect(notificationSpy).toHaveBeenCalledWith(
'Error retrieving metadata',
error
)
}) })
it('should display custom fields', () => { it('should display custom fields', () => {
@@ -1028,7 +1031,7 @@ describe('DocumentDetailComponent', () => {
it('should show error if needed for get suggestions', () => { it('should show error if needed for get suggestions', () => {
const suggestionsSpy = jest.spyOn(documentService, 'getSuggestions') const suggestionsSpy = jest.spyOn(documentService, 'getSuggestions')
const errorSpy = jest.spyOn(toastService, 'showError') const errorSpy = jest.spyOn(notificationService, 'showError')
suggestionsSpy.mockImplementationOnce(() => suggestionsSpy.mockImplementationOnce(() =>
throwError(() => new Error('failed to get suggestions')) throwError(() => new Error('failed to get suggestions'))
) )

View File

@@ -63,6 +63,7 @@ import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
import { ComponentRouterService } from 'src/app/services/component-router.service' import { ComponentRouterService } from 'src/app/services/component-router.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { HotKeyService } from 'src/app/services/hot-key.service' import { HotKeyService } from 'src/app/services/hot-key.service'
import { NotificationService } from 'src/app/services/notification.service'
import { OpenDocumentsService } from 'src/app/services/open-documents.service' import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { import {
PermissionAction, PermissionAction,
@@ -76,7 +77,6 @@ import { DocumentService } from 'src/app/services/rest/document.service'
import { StoragePathService } from 'src/app/services/rest/storage-path.service' import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { UserService } from 'src/app/services/rest/user.service' import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter' import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
import * as UTIF from 'utif' import * as UTIF from 'utif'
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
@@ -268,7 +268,7 @@ export class DocumentDetailComponent
private openDocumentService: OpenDocumentsService, private openDocumentService: OpenDocumentsService,
private documentListViewService: DocumentListViewService, private documentListViewService: DocumentListViewService,
private documentTitlePipe: DocumentTitlePipe, private documentTitlePipe: DocumentTitlePipe,
private toastService: ToastService, private notificationService: NotificationService,
private settings: SettingsService, private settings: SettingsService,
private storagePathService: StoragePathService, private storagePathService: StoragePathService,
private permissionsService: PermissionsService, private permissionsService: PermissionsService,
@@ -628,7 +628,7 @@ export class DocumentDetailComponent
}, },
error: (error) => { error: (error) => {
this.metadata = {} // allow display to fallback to <object> tag this.metadata = {} // allow display to fallback to <object> tag
this.toastService.showError( this.notificationService.showError(
$localize`Error retrieving metadata`, $localize`Error retrieving metadata`,
error error
) )
@@ -657,7 +657,7 @@ export class DocumentDetailComponent
}, },
error: (error) => { error: (error) => {
this.suggestions = null this.suggestions = null
this.toastService.showError( this.notificationService.showError(
$localize`Error retrieving suggestions.`, $localize`Error retrieving suggestions.`,
error error
) )
@@ -809,7 +809,7 @@ export class DocumentDetailComponent
this.store.next(newValues) this.store.next(newValues)
this.openDocumentService.setDirty(this.document, false) this.openDocumentService.setDirty(this.document, false)
this.openDocumentService.save() this.openDocumentService.save()
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Document "${newValues.title}" saved successfully.` $localize`Document "${newValues.title}" saved successfully.`
) )
this.networkActive = false this.networkActive = false
@@ -825,13 +825,13 @@ export class DocumentDetailComponent
error: (error) => { error: (error) => {
this.networkActive = false this.networkActive = false
if (!this.userCanEdit) { if (!this.userCanEdit) {
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Document "${this.document.title}" saved successfully.` $localize`Document "${this.document.title}" saved successfully.`
) )
close && this.close() close && this.close()
} else { } else {
this.error = error.error this.error = error.error
this.toastService.showError( this.notificationService.showError(
$localize`Error saving document "${this.document.title}"`, $localize`Error saving document "${this.document.title}"`,
error error
) )
@@ -877,7 +877,10 @@ export class DocumentDetailComponent
error: (error) => { error: (error) => {
this.networkActive = false this.networkActive = false
this.error = error.error this.error = error.error
this.toastService.showError($localize`Error saving document`, error) this.notificationService.showError(
$localize`Error saving document`,
error
)
}, },
}) })
} }
@@ -931,7 +934,10 @@ export class DocumentDetailComponent
this.close() this.close()
}, },
error: (error) => { error: (error) => {
this.toastService.showError($localize`Error deleting document`, error) this.notificationService.showError(
$localize`Error deleting document`,
error
)
modal.componentInstance.buttonsEnabled = true modal.componentInstance.buttonsEnabled = true
this.subscribeModalDelete(modal) this.subscribeModalDelete(modal)
}, },
@@ -962,7 +968,7 @@ export class DocumentDetailComponent
.bulkEdit([this.document.id], 'reprocess', {}) .bulkEdit([this.document.id], 'reprocess', {})
.subscribe({ .subscribe({
next: () => { next: () => {
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Reprocess operation for "${this.document.title}" will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.` $localize`Reprocess operation for "${this.document.title}" will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.`
) )
if (modal) { if (modal) {
@@ -973,7 +979,7 @@ export class DocumentDetailComponent
if (modal) { if (modal) {
modal.componentInstance.buttonsEnabled = true modal.componentInstance.buttonsEnabled = true
} }
this.toastService.showError( this.notificationService.showError(
$localize`Error executing operation`, $localize`Error executing operation`,
error error
) )
@@ -1020,7 +1026,7 @@ export class DocumentDetailComponent
}, },
error: (error) => { error: (error) => {
this.downloading = false this.downloading = false
this.toastService.showError( this.notificationService.showError(
$localize`Error downloading document`, $localize`Error downloading document`,
error error
) )
@@ -1329,7 +1335,7 @@ export class DocumentDetailComponent
.pipe(first(), takeUntil(this.unsubscribeNotifier)) .pipe(first(), takeUntil(this.unsubscribeNotifier))
.subscribe({ .subscribe({
next: () => { next: () => {
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Split operation for "${this.document.title}" will begin in the background.` $localize`Split operation for "${this.document.title}" will begin in the background.`
) )
modal.close() modal.close()
@@ -1338,7 +1344,7 @@ export class DocumentDetailComponent
if (modal) { if (modal) {
modal.componentInstance.buttonsEnabled = true modal.componentInstance.buttonsEnabled = true
} }
this.toastService.showError( this.notificationService.showError(
$localize`Error executing split operation`, $localize`Error executing split operation`,
error error
) )
@@ -1368,7 +1374,7 @@ export class DocumentDetailComponent
.pipe(first(), takeUntil(this.unsubscribeNotifier)) .pipe(first(), takeUntil(this.unsubscribeNotifier))
.subscribe({ .subscribe({
next: () => { next: () => {
this.toastService.show({ this.notificationService.show({
content: $localize`Rotation of "${this.document.title}" will begin in the background. Close and re-open the document after the operation has completed to see the changes.`, content: $localize`Rotation of "${this.document.title}" will begin in the background. Close and re-open the document after the operation has completed to see the changes.`,
delay: 8000, delay: 8000,
action: this.close.bind(this), action: this.close.bind(this),
@@ -1380,7 +1386,7 @@ export class DocumentDetailComponent
if (modal) { if (modal) {
modal.componentInstance.buttonsEnabled = true modal.componentInstance.buttonsEnabled = true
} }
this.toastService.showError( this.notificationService.showError(
$localize`Error executing rotate operation`, $localize`Error executing rotate operation`,
error error
) )
@@ -1408,7 +1414,7 @@ export class DocumentDetailComponent
.pipe(first(), takeUntil(this.unsubscribeNotifier)) .pipe(first(), takeUntil(this.unsubscribeNotifier))
.subscribe({ .subscribe({
next: () => { next: () => {
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Delete pages operation for "${this.document.title}" will begin in the background. Close and re-open or reload this document after the operation has completed to see the changes.` $localize`Delete pages operation for "${this.document.title}" will begin in the background. Close and re-open or reload this document after the operation has completed to see the changes.`
) )
modal.close() modal.close()
@@ -1417,7 +1423,7 @@ export class DocumentDetailComponent
if (modal) { if (modal) {
modal.componentInstance.buttonsEnabled = true modal.componentInstance.buttonsEnabled = true
} }
this.toastService.showError( this.notificationService.showError(
$localize`Error executing delete pages operation`, $localize`Error executing delete pages operation`,
error error
) )

View File

@@ -16,6 +16,7 @@ import { StoragePath } from 'src/app/data/storage-path'
import { Tag } from 'src/app/data/tag' import { Tag } from 'src/app/data/tag'
import { FilterPipe } from 'src/app/pipes/filter.pipe' import { FilterPipe } from 'src/app/pipes/filter.pipe'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service' import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
@@ -29,7 +30,6 @@ import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { TagService } from 'src/app/services/rest/tag.service' import { TagService } from 'src/app/services/rest/tag.service'
import { UserService } from 'src/app/services/rest/user.service' import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component' import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
@@ -64,7 +64,7 @@ describe('BulkEditorComponent', () => {
let permissionsService: PermissionsService let permissionsService: PermissionsService
let documentListViewService: DocumentListViewService let documentListViewService: DocumentListViewService
let documentService: DocumentService let documentService: DocumentService
let toastService: ToastService let notificationService: NotificationService
let modalService: NgbModal let modalService: NgbModal
let tagService: TagService let tagService: TagService
let correspondentsService: CorrespondentService let correspondentsService: CorrespondentService
@@ -160,7 +160,7 @@ describe('BulkEditorComponent', () => {
permissionsService = TestBed.inject(PermissionsService) permissionsService = TestBed.inject(PermissionsService)
documentListViewService = TestBed.inject(DocumentListViewService) documentListViewService = TestBed.inject(DocumentListViewService)
documentService = TestBed.inject(DocumentService) documentService = TestBed.inject(DocumentService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
modalService = TestBed.inject(NgbModal) modalService = TestBed.inject(NgbModal)
tagService = TestBed.inject(TagService) tagService = TestBed.inject(TagService)
correspondentsService = TestBed.inject(CorrespondentService) correspondentsService = TestBed.inject(CorrespondentService)
@@ -884,7 +884,7 @@ describe('BulkEditorComponent', () => {
expect(button.nativeElement.disabled).toBeTruthy() expect(button.nativeElement.disabled).toBeTruthy()
}) })
it('should show a warning toast on bulk edit error', () => { it('should show a warning notification on bulk edit error', () => {
jest jest
.spyOn(documentService, 'bulkEdit') .spyOn(documentService, 'bulkEdit')
.mockReturnValue( .mockReturnValue(
@@ -902,12 +902,12 @@ describe('BulkEditorComponent', () => {
.mockReturnValue(true) .mockReturnValue(true)
component.showConfirmationDialogs = false component.showConfirmationDialogs = false
fixture.detectChanges() fixture.detectChanges()
const toastSpy = jest.spyOn(toastService, 'showError') const notificationSpy = jest.spyOn(notificationService, 'showError')
component.setTags({ component.setTags({
itemsToAdd: [{ id: 0 }], itemsToAdd: [{ id: 0 }],
itemsToRemove: [], itemsToRemove: [],
}) })
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
}) })
it('should support redo ocr', () => { it('should support redo ocr', () => {
@@ -1391,8 +1391,14 @@ describe('BulkEditorComponent', () => {
.spyOn(documentListViewService, 'selected', 'get') .spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4])) .mockReturnValue(new Set([3, 4]))
fixture.detectChanges() fixture.detectChanges()
const toastServiceShowInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationServiceShowInfoSpy = jest.spyOn(
const toastServiceShowErrorSpy = jest.spyOn(toastService, 'showError') notificationService,
'showInfo'
)
const notificationServiceShowErrorSpy = jest.spyOn(
notificationService,
'showError'
)
const listReloadSpy = jest.spyOn(documentListViewService, 'reload') const listReloadSpy = jest.spyOn(documentListViewService, 'reload')
component.customFields = [ component.customFields = [
@@ -1410,11 +1416,11 @@ describe('BulkEditorComponent', () => {
expect(modal.componentInstance.documents).toEqual([3, 4]) expect(modal.componentInstance.documents).toEqual([3, 4])
modal.componentInstance.failed.emit() modal.componentInstance.failed.emit()
expect(toastServiceShowErrorSpy).toHaveBeenCalled() expect(notificationServiceShowErrorSpy).toHaveBeenCalled()
expect(listReloadSpy).not.toHaveBeenCalled() expect(listReloadSpy).not.toHaveBeenCalled()
modal.componentInstance.succeeded.emit() modal.componentInstance.succeeded.emit()
expect(toastServiceShowInfoSpy).toHaveBeenCalled() expect(notificationServiceShowInfoSpy).toHaveBeenCalled()
expect(listReloadSpy).toHaveBeenCalled() expect(listReloadSpy).toHaveBeenCalled()
httpTestingController.match( httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true` `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`

View File

@@ -23,6 +23,7 @@ import { Tag } from 'src/app/data/tag'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings' import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { NotificationService } from 'src/app/services/notification.service'
import { OpenDocumentsService } from 'src/app/services/open-documents.service' import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { import {
PermissionAction, PermissionAction,
@@ -39,7 +40,6 @@ import {
import { StoragePathService } from 'src/app/services/rest/storage-path.service' import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { TagService } from 'src/app/services/rest/tag.service' import { TagService } from 'src/app/services/rest/tag.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { MergeConfirmDialogComponent } from '../../common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component' import { MergeConfirmDialogComponent } from '../../common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component'
import { RotateConfirmDialogComponent } from '../../common/confirm-dialog/rotate-confirm-dialog/rotate-confirm-dialog.component' import { RotateConfirmDialogComponent } from '../../common/confirm-dialog/rotate-confirm-dialog/rotate-confirm-dialog.component'
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component' import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
@@ -113,7 +113,7 @@ export class BulkEditorComponent
private modalService: NgbModal, private modalService: NgbModal,
private openDocumentService: OpenDocumentsService, private openDocumentService: OpenDocumentsService,
private settings: SettingsService, private settings: SettingsService,
private toastService: ToastService, private notificationService: NotificationService,
private storagePathService: StoragePathService, private storagePathService: StoragePathService,
private customFieldService: CustomFieldsService, private customFieldService: CustomFieldsService,
private permissionService: PermissionsService private permissionService: PermissionsService
@@ -284,7 +284,7 @@ export class BulkEditorComponent
if (modal) { if (modal) {
modal.componentInstance.buttonsEnabled = true modal.componentInstance.buttonsEnabled = true
} }
this.toastService.showError( this.notificationService.showError(
$localize`Error executing bulk operation`, $localize`Error executing bulk operation`,
error error
) )
@@ -859,7 +859,7 @@ export class BulkEditorComponent
} }
mergeDialog.buttonsEnabled = false mergeDialog.buttonsEnabled = false
this.executeBulkOperation(modal, 'merge', args, mergeDialog.documentIDs) this.executeBulkOperation(modal, 'merge', args, mergeDialog.documentIDs)
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Merged document will be queued for consumption.` $localize`Merged document will be queued for consumption.`
) )
}) })
@@ -882,7 +882,7 @@ export class BulkEditorComponent
dialog.documents = Array.from(this.list.selected) dialog.documents = Array.from(this.list.selected)
dialog.succeeded.subscribe((result) => { dialog.succeeded.subscribe((result) => {
this.toastService.showInfo($localize`Custom fields updated.`) this.notificationService.showInfo($localize`Custom fields updated.`)
this.list.reload() this.list.reload()
this.list.reduceSelectionToFilter() this.list.reduceSelectionToFilter()
this.list.selected.forEach((id) => { this.list.selected.forEach((id) => {
@@ -890,7 +890,7 @@ export class BulkEditorComponent
}) })
}) })
dialog.failed.subscribe((error) => { dialog.failed.subscribe((error) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error updating custom fields.`, $localize`Error updating custom fields.`,
error error
) )

View File

@@ -39,11 +39,11 @@ import { FilterPipe } from 'src/app/pipes/filter.pipe'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { UsernamePipe } from 'src/app/pipes/username.pipe' import { UsernamePipe } from 'src/app/pipes/username.pipe'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { DocumentService } from 'src/app/services/rest/document.service' import { DocumentService } from 'src/app/services/rest/document.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service' import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { import {
FileStatus, FileStatus,
WebsocketStatusService, WebsocketStatusService,
@@ -85,7 +85,7 @@ describe('DocumentListComponent', () => {
let savedViewService: SavedViewService let savedViewService: SavedViewService
let router: Router let router: Router
let activatedRoute: ActivatedRoute let activatedRoute: ActivatedRoute
let toastService: ToastService let notificationService: NotificationService
let modalService: NgbModal let modalService: NgbModal
let settingsService: SettingsService let settingsService: SettingsService
let permissionService: PermissionsService let permissionService: PermissionsService
@@ -116,7 +116,7 @@ describe('DocumentListComponent', () => {
savedViewService = TestBed.inject(SavedViewService) savedViewService = TestBed.inject(SavedViewService)
router = TestBed.inject(Router) router = TestBed.inject(Router)
activatedRoute = TestBed.inject(ActivatedRoute) activatedRoute = TestBed.inject(ActivatedRoute)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
modalService = TestBed.inject(NgbModal) modalService = TestBed.inject(NgbModal)
settingsService = TestBed.inject(SettingsService) settingsService = TestBed.inject(SettingsService)
permissionService = TestBed.inject(PermissionsService) permissionService = TestBed.inject(PermissionsService)
@@ -405,11 +405,11 @@ describe('DocumentListComponent', () => {
delete modifiedView.name delete modifiedView.name
const savedViewServicePatch = jest.spyOn(savedViewService, 'patch') const savedViewServicePatch = jest.spyOn(savedViewService, 'patch')
savedViewServicePatch.mockReturnValue(of(modifiedView)) savedViewServicePatch.mockReturnValue(of(modifiedView))
const toastSpy = jest.spyOn(toastService, 'showInfo') const notificationSpy = jest.spyOn(notificationService, 'showInfo')
component.saveViewConfig() component.saveViewConfig()
expect(savedViewServicePatch).toHaveBeenCalledWith(modifiedView) expect(savedViewServicePatch).toHaveBeenCalledWith(modifiedView)
expect(toastSpy).toHaveBeenCalledWith( expect(notificationSpy).toHaveBeenCalledWith(
`View "${view.name}" saved successfully.` `View "${view.name}" saved successfully.`
) )
}) })
@@ -427,12 +427,12 @@ describe('DocumentListComponent', () => {
}, },
], ],
}) })
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
jest jest
.spyOn(savedViewService, 'patch') .spyOn(savedViewService, 'patch')
.mockReturnValueOnce(throwError(() => new Error('Error saving view'))) .mockReturnValueOnce(throwError(() => new Error('Error saving view')))
component.saveViewConfig() component.saveViewConfig()
expect(toastErrorSpy).toHaveBeenCalledWith( expect(notificationErrorSpy).toHaveBeenCalledWith(
'Failed to save view "Saved View 10".', 'Failed to save view "Saved View 10".',
expect.any(Error) expect.any(Error)
) )
@@ -467,7 +467,7 @@ describe('DocumentListComponent', () => {
let openModal: NgbModalRef let openModal: NgbModalRef
modalService.activeInstances.subscribe((modal) => (openModal = modal[0])) modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
const modalSpy = jest.spyOn(modalService, 'open') const modalSpy = jest.spyOn(modalService, 'open')
const toastSpy = jest.spyOn(toastService, 'showInfo') const notificationSpy = jest.spyOn(notificationService, 'showInfo')
const savedViewServiceCreate = jest.spyOn(savedViewService, 'create') const savedViewServiceCreate = jest.spyOn(savedViewService, 'create')
savedViewServiceCreate.mockReturnValueOnce(of(modifiedView)) savedViewServiceCreate.mockReturnValueOnce(of(modifiedView))
component.saveViewConfigAs() component.saveViewConfigAs()
@@ -480,7 +480,7 @@ describe('DocumentListComponent', () => {
}) })
expect(savedViewServiceCreate).toHaveBeenCalled() expect(savedViewServiceCreate).toHaveBeenCalled()
expect(modalSpy).toHaveBeenCalled() expect(modalSpy).toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
expect(modalCloseSpy).toHaveBeenCalled() expect(modalCloseSpy).toHaveBeenCalled()
}) })

View File

@@ -45,11 +45,11 @@ import { StoragePathNamePipe } from 'src/app/pipes/storage-path-name.pipe'
import { UsernamePipe } from 'src/app/pipes/username.pipe' import { UsernamePipe } from 'src/app/pipes/username.pipe'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { HotKeyService } from 'src/app/services/hot-key.service' import { HotKeyService } from 'src/app/services/hot-key.service'
import { NotificationService } from 'src/app/services/notification.service'
import { OpenDocumentsService } from 'src/app/services/open-documents.service' import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service' import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { WebsocketStatusService } from 'src/app/services/websocket-status.service' import { WebsocketStatusService } from 'src/app/services/websocket-status.service'
import { import {
filterRulesDiffer, filterRulesDiffer,
@@ -111,7 +111,7 @@ export class DocumentListComponent
public savedViewService: SavedViewService, public savedViewService: SavedViewService,
public route: ActivatedRoute, public route: ActivatedRoute,
private router: Router, private router: Router,
private toastService: ToastService, private notificationService: NotificationService,
private modalService: NgbModal, private modalService: NgbModal,
private websocketStatusService: WebsocketStatusService, private websocketStatusService: WebsocketStatusService,
public openDocumentsService: OpenDocumentsService, public openDocumentsService: OpenDocumentsService,
@@ -380,13 +380,13 @@ export class DocumentListComponent
.subscribe({ .subscribe({
next: (view) => { next: (view) => {
this.unmodifiedSavedView = view this.unmodifiedSavedView = view
this.toastService.showInfo( this.notificationService.showInfo(
$localize`View "${this.list.activeSavedViewTitle}" saved successfully.` $localize`View "${this.list.activeSavedViewTitle}" saved successfully.`
) )
this.unmodifiedFilterRules = this.list.filterRules this.unmodifiedFilterRules = this.list.filterRules
}, },
error: (err) => { error: (err) => {
this.toastService.showError( this.notificationService.showError(
$localize`Failed to save view "${this.list.activeSavedViewTitle}".`, $localize`Failed to save view "${this.list.activeSavedViewTitle}".`,
err err
) )
@@ -430,7 +430,7 @@ export class DocumentListComponent
.subscribe({ .subscribe({
next: () => { next: () => {
modal.close() modal.close()
this.toastService.showInfo( this.notificationService.showInfo(
$localize`View "${savedView.name}" created successfully.` $localize`View "${savedView.name}" created successfully.`
) )
}, },

View File

@@ -9,10 +9,10 @@ import { of, throwError } from 'rxjs'
import { DocumentNote } from 'src/app/data/document-note' import { DocumentNote } from 'src/app/data/document-note'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { DocumentNotesService } from 'src/app/services/rest/document-notes.service' import { DocumentNotesService } from 'src/app/services/rest/document-notes.service'
import { UserService } from 'src/app/services/rest/user.service' import { UserService } from 'src/app/services/rest/user.service'
import { ToastService } from 'src/app/services/toast.service'
import { DocumentNotesComponent } from './document-notes.component' import { DocumentNotesComponent } from './document-notes.component'
const notes: DocumentNote[] = [ const notes: DocumentNote[] = [
@@ -52,7 +52,7 @@ describe('DocumentNotesComponent', () => {
let component: DocumentNotesComponent let component: DocumentNotesComponent
let fixture: ComponentFixture<DocumentNotesComponent> let fixture: ComponentFixture<DocumentNotesComponent>
let notesService: DocumentNotesService let notesService: DocumentNotesService
let toastService: ToastService let notificationService: NotificationService
beforeEach(async () => { beforeEach(async () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -103,7 +103,7 @@ describe('DocumentNotesComponent', () => {
}).compileComponents() }).compileComponents()
notesService = TestBed.inject(DocumentNotesService) notesService = TestBed.inject(DocumentNotesService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
fixture = TestBed.createComponent(DocumentNotesComponent) fixture = TestBed.createComponent(DocumentNotesComponent)
component = fixture.componentInstance component = fixture.componentInstance
fixture.detectChanges() fixture.detectChanges()
@@ -162,11 +162,11 @@ describe('DocumentNotesComponent', () => {
fixture.detectChanges() fixture.detectChanges()
const addSpy = jest.spyOn(notesService, 'addNote') const addSpy = jest.spyOn(notesService, 'addNote')
addSpy.mockReturnValueOnce(throwError(() => new Error('error saving note'))) addSpy.mockReturnValueOnce(throwError(() => new Error('error saving note')))
const toastsSpy = jest.spyOn(toastService, 'showError') const notificationsSpy = jest.spyOn(notificationService, 'showError')
const addButton = fixture.debugElement.query(By.css('button')) const addButton = fixture.debugElement.query(By.css('button'))
addButton.triggerEventHandler('click') addButton.triggerEventHandler('click')
expect(addSpy).toHaveBeenCalledWith(12, note) expect(addSpy).toHaveBeenCalledWith(12, note)
expect(toastsSpy).toHaveBeenCalled() expect(notificationsSpy).toHaveBeenCalled()
addSpy.mockReturnValueOnce( addSpy.mockReturnValueOnce(
of([...notes, { id: 31, note, user: { id: 1 } }]) of([...notes, { id: 31, note, user: { id: 1 } }])
@@ -194,7 +194,7 @@ describe('DocumentNotesComponent', () => {
fixture.detectChanges() fixture.detectChanges()
const deleteButton = fixture.debugElement.queryAll(By.css('button'))[1] // 0 is add button const deleteButton = fixture.debugElement.queryAll(By.css('button'))[1] // 0 is add button
const deleteSpy = jest.spyOn(notesService, 'deleteNote') const deleteSpy = jest.spyOn(notesService, 'deleteNote')
const toastsSpy = jest.spyOn(toastService, 'showError') const toastsSpy = jest.spyOn(notificationService, 'showError')
deleteSpy.mockReturnValueOnce( deleteSpy.mockReturnValueOnce(
throwError(() => new Error('error deleting note')) throwError(() => new Error('error deleting note'))
) )

View File

@@ -10,9 +10,9 @@ import { DocumentNote } from 'src/app/data/document-note'
import { User } from 'src/app/data/user' import { User } from 'src/app/data/user'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { NotificationService } from 'src/app/services/notification.service'
import { DocumentNotesService } from 'src/app/services/rest/document-notes.service' import { DocumentNotesService } from 'src/app/services/rest/document-notes.service'
import { UserService } from 'src/app/services/rest/user.service' import { UserService } from 'src/app/services/rest/user.service'
import { ToastService } from 'src/app/services/toast.service'
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component' import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
@Component({ @Component({
@@ -50,7 +50,7 @@ export class DocumentNotesComponent extends ComponentWithPermissions {
constructor( constructor(
private notesService: DocumentNotesService, private notesService: DocumentNotesService,
private toastService: ToastService, private notificationService: NotificationService,
private usersService: UserService private usersService: UserService
) { ) {
super() super()
@@ -78,7 +78,7 @@ export class DocumentNotesComponent extends ComponentWithPermissions {
}, },
error: (e) => { error: (e) => {
this.networkActive = false this.networkActive = false
this.toastService.showError($localize`Error saving note`, e) this.notificationService.showError($localize`Error saving note`, e)
}, },
}) })
} }
@@ -92,7 +92,7 @@ export class DocumentNotesComponent extends ComponentWithPermissions {
}, },
error: (e) => { error: (e) => {
this.networkActive = false this.networkActive = false
this.toastService.showError($localize`Error deleting note`, e) this.notificationService.showError($localize`Error deleting note`, e)
}, },
}) })
} }

View File

@@ -10,24 +10,28 @@ import {
} from '@angular/core/testing' } from '@angular/core/testing'
import { By } from '@angular/platform-browser' import { By } from '@angular/platform-browser'
import { NgxFileDropEntry, NgxFileDropModule } from 'ngx-file-drop' import { NgxFileDropEntry, NgxFileDropModule } from 'ngx-file-drop'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { UploadDocumentsService } from 'src/app/services/upload-documents.service' import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
import { ToastsComponent } from '../common/toasts/toasts.component' import { NotificationListComponent } from '../common/notification-list/notification-list.component'
import { FileDropComponent } from './file-drop.component' import { FileDropComponent } from './file-drop.component'
describe('FileDropComponent', () => { describe('FileDropComponent', () => {
let component: FileDropComponent let component: FileDropComponent
let fixture: ComponentFixture<FileDropComponent> let fixture: ComponentFixture<FileDropComponent>
let permissionsService: PermissionsService let permissionsService: PermissionsService
let toastService: ToastService let notificationService: NotificationService
let settingsService: SettingsService let settingsService: SettingsService
let uploadDocumentsService: UploadDocumentsService let uploadDocumentsService: UploadDocumentsService
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [NgxFileDropModule, FileDropComponent, ToastsComponent], imports: [
NgxFileDropModule,
FileDropComponent,
NotificationListComponent,
],
providers: [ providers: [
provideHttpClient(withInterceptorsFromDi()), provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(), provideHttpClientTesting(),
@@ -36,7 +40,7 @@ describe('FileDropComponent', () => {
permissionsService = TestBed.inject(PermissionsService) permissionsService = TestBed.inject(PermissionsService)
settingsService = TestBed.inject(SettingsService) settingsService = TestBed.inject(SettingsService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
uploadDocumentsService = TestBed.inject(UploadDocumentsService) uploadDocumentsService = TestBed.inject(UploadDocumentsService)
fixture = TestBed.createComponent(FileDropComponent) fixture = TestBed.createComponent(FileDropComponent)
@@ -101,7 +105,7 @@ describe('FileDropComponent', () => {
fixture.detectChanges() fixture.detectChanges()
expect(dropzone.classes['hide']).toBeTruthy() expect(dropzone.classes['hide']).toBeTruthy()
// drop // drop
const toastSpy = jest.spyOn(toastService, 'show') const notificationSpy = jest.spyOn(notificationService, 'show')
const uploadSpy = jest.spyOn( const uploadSpy = jest.spyOn(
UploadDocumentsService.prototype as any, UploadDocumentsService.prototype as any,
'uploadFile' 'uploadFile'
@@ -135,7 +139,7 @@ describe('FileDropComponent', () => {
} as unknown as NgxFileDropEntry, } as unknown as NgxFileDropEntry,
]) ])
tick(3000) tick(3000)
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
expect(uploadSpy).toHaveBeenCalled() expect(uploadSpy).toHaveBeenCalled()
discardPeriodicTasks() discardPeriodicTasks()
})) }))

View File

@@ -4,13 +4,13 @@ import {
NgxFileDropEntry, NgxFileDropEntry,
NgxFileDropModule, NgxFileDropModule,
} from 'ngx-file-drop' } from 'ngx-file-drop'
import { NotificationService } from 'src/app/services/notification.service'
import { import {
PermissionAction, PermissionAction,
PermissionsService, PermissionsService,
PermissionType, PermissionType,
} from 'src/app/services/permissions.service' } from 'src/app/services/permissions.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { UploadDocumentsService } from 'src/app/services/upload-documents.service' import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
@Component({ @Component({
@@ -26,7 +26,7 @@ export class FileDropComponent {
constructor( constructor(
private settings: SettingsService, private settings: SettingsService,
private toastService: ToastService, private notificationService: NotificationService,
private uploadDocumentsService: UploadDocumentsService, private uploadDocumentsService: UploadDocumentsService,
private permissionsService: PermissionsService private permissionsService: PermissionsService
) {} ) {}
@@ -90,7 +90,7 @@ export class FileDropComponent {
public dropped(files: NgxFileDropEntry[]) { public dropped(files: NgxFileDropEntry[]) {
this.uploadDocumentsService.onNgxFileDrop(files) this.uploadDocumentsService.onNgxFileDrop(files)
if (files.length > 0) if (files.length > 0)
this.toastService.showInfo($localize`Initiating upload...`, 3000) this.notificationService.showInfo($localize`Initiating upload...`, 3000)
} }
@HostListener('window:blur', ['$event']) public onWindowBlur() { @HostListener('window:blur', ['$event']) public onWindowBlur() {

View File

@@ -13,12 +13,12 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
import { SortableDirective } from 'src/app/directives/sortable.directive' import { SortableDirective } from 'src/app/directives/sortable.directive'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { NotificationService } from 'src/app/services/notification.service'
import { import {
PermissionsService, PermissionsService,
PermissionType, PermissionType,
} from 'src/app/services/permissions.service' } from 'src/app/services/permissions.service'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service' import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { ToastService } from 'src/app/services/toast.service'
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component' import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component' import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { ManagementListComponent } from '../management-list/management-list.component' import { ManagementListComponent } from '../management-list/management-list.component'
@@ -45,7 +45,7 @@ export class CorrespondentListComponent extends ManagementListComponent<Correspo
constructor( constructor(
correspondentsService: CorrespondentService, correspondentsService: CorrespondentService,
modalService: NgbModal, modalService: NgbModal,
toastService: ToastService, notificationService: NotificationService,
documentListViewService: DocumentListViewService, documentListViewService: DocumentListViewService,
permissionsService: PermissionsService, permissionsService: PermissionsService,
private datePipe: CustomDatePipe private datePipe: CustomDatePipe
@@ -54,7 +54,7 @@ export class CorrespondentListComponent extends ManagementListComponent<Correspo
correspondentsService, correspondentsService,
modalService, modalService,
CorrespondentEditDialogComponent, CorrespondentEditDialogComponent,
toastService, notificationService,
documentListViewService, documentListViewService,
permissionsService, permissionsService,
FILTER_HAS_CORRESPONDENT_ANY, FILTER_HAS_CORRESPONDENT_ANY,

View File

@@ -21,10 +21,10 @@ import {
import { FILTER_CUSTOM_FIELDS_QUERY } from 'src/app/data/filter-rule-type' import { FILTER_CUSTOM_FIELDS_QUERY } from 'src/app/data/filter-rule-type'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component' import { PageHeaderComponent } from '../../common/page-header/page-header.component'
@@ -48,7 +48,7 @@ describe('CustomFieldsComponent', () => {
let fixture: ComponentFixture<CustomFieldsComponent> let fixture: ComponentFixture<CustomFieldsComponent>
let customFieldsService: CustomFieldsService let customFieldsService: CustomFieldsService
let modalService: NgbModal let modalService: NgbModal
let toastService: ToastService let notificationService: NotificationService
let listViewService: DocumentListViewService let listViewService: DocumentListViewService
let settingsService: SettingsService let settingsService: SettingsService
@@ -89,7 +89,7 @@ describe('CustomFieldsComponent', () => {
}) })
) )
modalService = TestBed.inject(NgbModal) modalService = TestBed.inject(NgbModal)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
listViewService = TestBed.inject(DocumentListViewService) listViewService = TestBed.inject(DocumentListViewService)
settingsService = TestBed.inject(SettingsService) settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = { id: 0, username: 'test' } settingsService.currentUser = { id: 0, username: 'test' }
@@ -104,8 +104,8 @@ describe('CustomFieldsComponent', () => {
it('should support create, show notification on error / success', () => { it('should support create, show notification on error / success', () => {
let modal: NgbModalRef let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
const reloadSpy = jest.spyOn(component, 'reload') const reloadSpy = jest.spyOn(component, 'reload')
const createButton = fixture.debugElement.queryAll(By.css('button'))[1] const createButton = fixture.debugElement.queryAll(By.css('button'))[1]
@@ -116,12 +116,12 @@ describe('CustomFieldsComponent', () => {
// fail first // fail first
editDialog.failed.emit({ error: 'error creating item' }) editDialog.failed.emit({ error: 'error creating item' })
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
expect(reloadSpy).not.toHaveBeenCalled() expect(reloadSpy).not.toHaveBeenCalled()
// succeed // succeed
editDialog.succeeded.emit(fields[0]) editDialog.succeeded.emit(fields[0])
expect(toastInfoSpy).toHaveBeenCalled() expect(notificationInfoSpy).toHaveBeenCalled()
expect(reloadSpy).toHaveBeenCalled() expect(reloadSpy).toHaveBeenCalled()
jest.advanceTimersByTime(100) jest.advanceTimersByTime(100)
}) })
@@ -129,8 +129,8 @@ describe('CustomFieldsComponent', () => {
it('should support edit, show notification on error / success', () => { it('should support edit, show notification on error / success', () => {
let modal: NgbModalRef let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
const reloadSpy = jest.spyOn(component, 'reload') const reloadSpy = jest.spyOn(component, 'reload')
const editButton = fixture.debugElement.queryAll(By.css('button'))[2] const editButton = fixture.debugElement.queryAll(By.css('button'))[2]
@@ -142,19 +142,19 @@ describe('CustomFieldsComponent', () => {
// fail first // fail first
editDialog.failed.emit({ error: 'error editing item' }) editDialog.failed.emit({ error: 'error editing item' })
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
expect(reloadSpy).not.toHaveBeenCalled() expect(reloadSpy).not.toHaveBeenCalled()
// succeed // succeed
editDialog.succeeded.emit(fields[0]) editDialog.succeeded.emit(fields[0])
expect(toastInfoSpy).toHaveBeenCalled() expect(notificationInfoSpy).toHaveBeenCalled()
expect(reloadSpy).toHaveBeenCalled() expect(reloadSpy).toHaveBeenCalled()
}) })
it('should support delete, show notification on error / success', () => { it('should support delete, show notification on error / success', () => {
let modal: NgbModalRef let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const deleteSpy = jest.spyOn(customFieldsService, 'delete') const deleteSpy = jest.spyOn(customFieldsService, 'delete')
const reloadSpy = jest.spyOn(component, 'reload') const reloadSpy = jest.spyOn(component, 'reload')
@@ -167,7 +167,7 @@ describe('CustomFieldsComponent', () => {
// fail first // fail first
deleteSpy.mockReturnValueOnce(throwError(() => new Error('error deleting'))) deleteSpy.mockReturnValueOnce(throwError(() => new Error('error deleting')))
editDialog.confirmClicked.emit() editDialog.confirmClicked.emit()
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
expect(reloadSpy).not.toHaveBeenCalled() expect(reloadSpy).not.toHaveBeenCalled()
// succeed // succeed

View File

@@ -14,12 +14,12 @@ import {
import { FILTER_CUSTOM_FIELDS_QUERY } from 'src/app/data/filter-rule-type' import { FILTER_CUSTOM_FIELDS_QUERY } from 'src/app/data/filter-rule-type'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { DocumentService } from 'src/app/services/rest/document.service' import { DocumentService } from 'src/app/services/rest/document.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service' import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component' import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
@@ -48,7 +48,7 @@ export class CustomFieldsComponent
private customFieldsService: CustomFieldsService, private customFieldsService: CustomFieldsService,
public permissionsService: PermissionsService, public permissionsService: PermissionsService,
private modalService: NgbModal, private modalService: NgbModal,
private toastService: ToastService, private notificationService: NotificationService,
private documentListViewService: DocumentListViewService, private documentListViewService: DocumentListViewService,
private settingsService: SettingsService, private settingsService: SettingsService,
private documentService: DocumentService, private documentService: DocumentService,
@@ -86,7 +86,9 @@ export class CustomFieldsComponent
modal.componentInstance.succeeded modal.componentInstance.succeeded
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((newField) => { .subscribe((newField) => {
this.toastService.showInfo($localize`Saved field "${newField.name}".`) this.notificationService.showInfo(
$localize`Saved field "${newField.name}".`
)
this.customFieldsService.clearCache() this.customFieldsService.clearCache()
this.settingsService.initializeDisplayFields() this.settingsService.initializeDisplayFields()
this.documentService.reload() this.documentService.reload()
@@ -95,7 +97,7 @@ export class CustomFieldsComponent
modal.componentInstance.failed modal.componentInstance.failed
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((e) => { .subscribe((e) => {
this.toastService.showError($localize`Error saving field.`, e) this.notificationService.showError($localize`Error saving field.`, e)
}) })
} }
@@ -113,7 +115,9 @@ export class CustomFieldsComponent
this.customFieldsService.delete(field).subscribe({ this.customFieldsService.delete(field).subscribe({
next: () => { next: () => {
modal.close() modal.close()
this.toastService.showInfo($localize`Deleted field "${field.name}"`) this.notificationService.showInfo(
$localize`Deleted field "${field.name}"`
)
this.customFieldsService.clearCache() this.customFieldsService.clearCache()
this.settingsService.initializeDisplayFields() this.settingsService.initializeDisplayFields()
this.documentService.reload() this.documentService.reload()
@@ -121,7 +125,7 @@ export class CustomFieldsComponent
this.reload() this.reload()
}, },
error: (e) => { error: (e) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error deleting field "${field.name}".`, $localize`Error deleting field "${field.name}".`,
e e
) )

View File

@@ -12,12 +12,12 @@ import { FILTER_HAS_DOCUMENT_TYPE_ANY } from 'src/app/data/filter-rule-type'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { SortableDirective } from 'src/app/directives/sortable.directive' import { SortableDirective } from 'src/app/directives/sortable.directive'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { NotificationService } from 'src/app/services/notification.service'
import { import {
PermissionsService, PermissionsService,
PermissionType, PermissionType,
} from 'src/app/services/permissions.service' } from 'src/app/services/permissions.service'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service' import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { ToastService } from 'src/app/services/toast.service'
import { DocumentTypeEditDialogComponent } from '../../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component' import { DocumentTypeEditDialogComponent } from '../../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component' import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { ManagementListComponent } from '../management-list/management-list.component' import { ManagementListComponent } from '../management-list/management-list.component'
@@ -43,7 +43,7 @@ export class DocumentTypeListComponent extends ManagementListComponent<DocumentT
constructor( constructor(
documentTypeService: DocumentTypeService, documentTypeService: DocumentTypeService,
modalService: NgbModal, modalService: NgbModal,
toastService: ToastService, notificationService: NotificationService,
documentListViewService: DocumentListViewService, documentListViewService: DocumentListViewService,
permissionsService: PermissionsService permissionsService: PermissionsService
) { ) {
@@ -51,7 +51,7 @@ export class DocumentTypeListComponent extends ManagementListComponent<DocumentT
documentTypeService, documentTypeService,
modalService, modalService,
DocumentTypeEditDialogComponent, DocumentTypeEditDialogComponent,
toastService, notificationService,
documentListViewService, documentListViewService,
permissionsService, permissionsService,
FILTER_HAS_DOCUMENT_TYPE_ANY, FILTER_HAS_DOCUMENT_TYPE_ANY,

View File

@@ -24,11 +24,11 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { MailAccountService } from 'src/app/services/rest/mail-account.service' import { MailAccountService } from 'src/app/services/rest/mail-account.service'
import { MailRuleService } from 'src/app/services/rest/mail-rule.service' import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component' import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component' import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
@@ -63,7 +63,7 @@ describe('MailComponent', () => {
let mailAccountService: MailAccountService let mailAccountService: MailAccountService
let mailRuleService: MailRuleService let mailRuleService: MailRuleService
let modalService: NgbModal let modalService: NgbModal
let toastService: ToastService let notificationService: NotificationService
let permissionsService: PermissionsService let permissionsService: PermissionsService
let activatedRoute: ActivatedRoute let activatedRoute: ActivatedRoute
let settingsService: SettingsService let settingsService: SettingsService
@@ -111,7 +111,7 @@ describe('MailComponent', () => {
mailAccountService = TestBed.inject(MailAccountService) mailAccountService = TestBed.inject(MailAccountService)
mailRuleService = TestBed.inject(MailRuleService) mailRuleService = TestBed.inject(MailRuleService)
modalService = TestBed.inject(NgbModal) modalService = TestBed.inject(NgbModal)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
permissionsService = TestBed.inject(PermissionsService) permissionsService = TestBed.inject(PermissionsService)
activatedRoute = TestBed.inject(ActivatedRoute) activatedRoute = TestBed.inject(ActivatedRoute)
settingsService = TestBed.inject(SettingsService) settingsService = TestBed.inject(SettingsService)
@@ -157,25 +157,25 @@ describe('MailComponent', () => {
} }
it('should show errors on load if load mailAccounts failure', () => { it('should show errors on load if load mailAccounts failure', () => {
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
jest jest
.spyOn(mailAccountService, 'listAll') .spyOn(mailAccountService, 'listAll')
.mockImplementation(() => .mockImplementation(() =>
throwError(() => new Error('failed to load mail accounts')) throwError(() => new Error('failed to load mail accounts'))
) )
completeSetup(mailAccountService) completeSetup(mailAccountService)
expect(toastErrorSpy).toBeCalled() expect(notificationErrorSpy).toBeCalled()
}) })
it('should show errors on load if load mailRules failure', () => { it('should show errors on load if load mailRules failure', () => {
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
jest jest
.spyOn(mailRuleService, 'listAll') .spyOn(mailRuleService, 'listAll')
.mockImplementation(() => .mockImplementation(() =>
throwError(() => new Error('failed to load mail rules')) throwError(() => new Error('failed to load mail rules'))
) )
completeSetup(mailRuleService) completeSetup(mailRuleService)
expect(toastErrorSpy).toBeCalled() expect(notificationErrorSpy).toBeCalled()
}) })
it('should support edit / create mail account, show error if needed', () => { it('should support edit / create mail account, show error if needed', () => {
@@ -184,12 +184,12 @@ describe('MailComponent', () => {
modalService.activeInstances.subscribe((refs) => (modal = refs[0])) modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
component.editMailAccount(mailAccounts[0] as MailAccount) component.editMailAccount(mailAccounts[0] as MailAccount)
let editDialog = modal.componentInstance as MailAccountEditDialogComponent let editDialog = modal.componentInstance as MailAccountEditDialogComponent
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
editDialog.failed.emit() editDialog.failed.emit()
expect(toastErrorSpy).toBeCalled() expect(notificationErrorSpy).toBeCalled()
editDialog.succeeded.emit(mailAccounts[0]) editDialog.succeeded.emit(mailAccounts[0])
expect(toastInfoSpy).toHaveBeenCalledWith( expect(notificationInfoSpy).toHaveBeenCalledWith(
`Saved account "${mailAccounts[0].name}".` `Saved account "${mailAccounts[0].name}".`
) )
editDialog.cancel() editDialog.cancel()
@@ -203,35 +203,37 @@ describe('MailComponent', () => {
component.deleteMailAccount(mailAccounts[0] as MailAccount) component.deleteMailAccount(mailAccounts[0] as MailAccount)
const deleteDialog = modal.componentInstance as ConfirmDialogComponent const deleteDialog = modal.componentInstance as ConfirmDialogComponent
const deleteSpy = jest.spyOn(mailAccountService, 'delete') const deleteSpy = jest.spyOn(mailAccountService, 'delete')
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
const listAllSpy = jest.spyOn(mailAccountService, 'listAll') const listAllSpy = jest.spyOn(mailAccountService, 'listAll')
deleteSpy.mockReturnValueOnce( deleteSpy.mockReturnValueOnce(
throwError(() => new Error('error deleting mail account')) throwError(() => new Error('error deleting mail account'))
) )
deleteDialog.confirm() deleteDialog.confirm()
expect(toastErrorSpy).toBeCalled() expect(notificationErrorSpy).toBeCalled()
deleteSpy.mockReturnValueOnce(of(true)) deleteSpy.mockReturnValueOnce(of(true))
deleteDialog.confirm() deleteDialog.confirm()
expect(listAllSpy).toHaveBeenCalled() expect(listAllSpy).toHaveBeenCalled()
expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail account "account1"') expect(notificationInfoSpy).toHaveBeenCalledWith(
'Deleted mail account "account1"'
)
}) })
it('should support process mail account, show error if needed', () => { it('should support process mail account, show error if needed', () => {
completeSetup() completeSetup()
const processSpy = jest.spyOn(mailAccountService, 'processAccount') const processSpy = jest.spyOn(mailAccountService, 'processAccount')
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
component.processAccount(mailAccounts[0] as MailAccount) component.processAccount(mailAccounts[0] as MailAccount)
expect(processSpy).toHaveBeenCalled() expect(processSpy).toHaveBeenCalled()
processSpy.mockReturnValueOnce( processSpy.mockReturnValueOnce(
throwError(() => new Error('error processing mail account')) throwError(() => new Error('error processing mail account'))
) )
component.processAccount(mailAccounts[0] as MailAccount) component.processAccount(mailAccounts[0] as MailAccount)
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
processSpy.mockReturnValueOnce(of(true)) processSpy.mockReturnValueOnce(of(true))
component.processAccount(mailAccounts[0] as MailAccount) component.processAccount(mailAccounts[0] as MailAccount)
expect(toastInfoSpy).toHaveBeenCalledWith( expect(notificationInfoSpy).toHaveBeenCalledWith(
'Processing mail account "account1"' 'Processing mail account "account1"'
) )
}) })
@@ -242,12 +244,12 @@ describe('MailComponent', () => {
modalService.activeInstances.subscribe((refs) => (modal = refs[0])) modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
component.editMailRule(mailRules[0] as MailRule) component.editMailRule(mailRules[0] as MailRule)
const editDialog = modal.componentInstance as MailRuleEditDialogComponent const editDialog = modal.componentInstance as MailRuleEditDialogComponent
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
editDialog.failed.emit() editDialog.failed.emit()
expect(toastErrorSpy).toBeCalled() expect(notificationErrorSpy).toBeCalled()
editDialog.succeeded.emit(mailRules[0]) editDialog.succeeded.emit(mailRules[0])
expect(toastInfoSpy).toHaveBeenCalledWith( expect(notificationInfoSpy).toHaveBeenCalledWith(
`Saved rule "${mailRules[0].name}".` `Saved rule "${mailRules[0].name}".`
) )
editDialog.cancel() editDialog.cancel()
@@ -272,18 +274,20 @@ describe('MailComponent', () => {
component.deleteMailRule(mailRules[0] as MailRule) component.deleteMailRule(mailRules[0] as MailRule)
const deleteDialog = modal.componentInstance as ConfirmDialogComponent const deleteDialog = modal.componentInstance as ConfirmDialogComponent
const deleteSpy = jest.spyOn(mailRuleService, 'delete') const deleteSpy = jest.spyOn(mailRuleService, 'delete')
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
const listAllSpy = jest.spyOn(mailRuleService, 'listAll') const listAllSpy = jest.spyOn(mailRuleService, 'listAll')
deleteSpy.mockReturnValueOnce( deleteSpy.mockReturnValueOnce(
throwError(() => new Error('error deleting mail rule "rule1"')) throwError(() => new Error('error deleting mail rule "rule1"'))
) )
deleteDialog.confirm() deleteDialog.confirm()
expect(toastErrorSpy).toBeCalled() expect(notificationErrorSpy).toBeCalled()
deleteSpy.mockReturnValueOnce(of(true)) deleteSpy.mockReturnValueOnce(of(true))
deleteDialog.confirm() deleteDialog.confirm()
expect(listAllSpy).toHaveBeenCalled() expect(listAllSpy).toHaveBeenCalled()
expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail rule "rule1"') expect(notificationInfoSpy).toHaveBeenCalledWith(
'Deleted mail rule "rule1"'
)
}) })
it('should support edit permissions on mail rule objects', () => { it('should support edit permissions on mail rule objects', () => {
@@ -303,8 +307,8 @@ describe('MailComponent', () => {
} }
let modal: NgbModalRef let modal: NgbModalRef
modalService.activeInstances.subscribe((refs) => (modal = refs[0])) modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
const rulePatchSpy = jest.spyOn(mailRuleService, 'patch') const rulePatchSpy = jest.spyOn(mailRuleService, 'patch')
component.editPermissions(mailRules[0] as MailRule) component.editPermissions(mailRules[0] as MailRule)
expect(modal).not.toBeUndefined() expect(modal).not.toBeUndefined()
@@ -316,10 +320,10 @@ describe('MailComponent', () => {
) )
dialog.confirmClicked.emit({ permissions: perms, merge: true }) dialog.confirmClicked.emit({ permissions: perms, merge: true })
expect(rulePatchSpy).toHaveBeenCalled() expect(rulePatchSpy).toHaveBeenCalled()
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
rulePatchSpy.mockReturnValueOnce(of(mailRules[0] as MailRule)) rulePatchSpy.mockReturnValueOnce(of(mailRules[0] as MailRule))
dialog.confirmClicked.emit({ permissions: perms, merge: true }) dialog.confirmClicked.emit({ permissions: perms, merge: true })
expect(toastInfoSpy).toHaveBeenCalledWith('Permissions updated') expect(notificationInfoSpy).toHaveBeenCalledWith('Permissions updated')
modalService.dismissAll() modalService.dismissAll()
}) })
@@ -356,15 +360,15 @@ describe('MailComponent', () => {
const toggleInput = fixture.debugElement.query( const toggleInput = fixture.debugElement.query(
By.css('input[type="checkbox"]') By.css('input[type="checkbox"]')
) )
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
// fail first // fail first
patchSpy.mockReturnValueOnce( patchSpy.mockReturnValueOnce(
throwError(() => new Error('Error getting config')) throwError(() => new Error('Error getting config'))
) )
toggleInput.nativeElement.click() toggleInput.nativeElement.click()
expect(patchSpy).toHaveBeenCalled() expect(patchSpy).toHaveBeenCalled()
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
// succeed second // succeed second
patchSpy.mockReturnValueOnce(of(mailRules[0] as MailRule)) patchSpy.mockReturnValueOnce(of(mailRules[0] as MailRule))
toggleInput.nativeElement.click() toggleInput.nativeElement.click()
@@ -373,7 +377,7 @@ describe('MailComponent', () => {
) )
toggleInput.nativeElement.click() toggleInput.nativeElement.click()
expect(patchSpy).toHaveBeenCalled() expect(patchSpy).toHaveBeenCalled()
expect(toastInfoSpy).toHaveBeenCalled() expect(notificationInfoSpy).toHaveBeenCalled()
}) })
it('should show success message when oauth account is connected', () => { it('should show success message when oauth account is connected', () => {
@@ -381,9 +385,9 @@ describe('MailComponent', () => {
jest jest
.spyOn(activatedRoute, 'queryParamMap', 'get') .spyOn(activatedRoute, 'queryParamMap', 'get')
.mockReturnValue(of(convertToParamMap(queryParams))) .mockReturnValue(of(convertToParamMap(queryParams)))
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
completeSetup() completeSetup()
expect(toastInfoSpy).toHaveBeenCalled() expect(notificationInfoSpy).toHaveBeenCalled()
}) })
it('should show error message when oauth account connect fails', () => { it('should show error message when oauth account connect fails', () => {
@@ -391,9 +395,9 @@ describe('MailComponent', () => {
jest jest
.spyOn(activatedRoute, 'queryParamMap', 'get') .spyOn(activatedRoute, 'queryParamMap', 'get')
.mockReturnValue(of(convertToParamMap(queryParams))) .mockReturnValue(of(convertToParamMap(queryParams)))
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
completeSetup() completeSetup()
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
}) })
it('should open account edit dialog if oauth account is connected', () => { it('should open account edit dialog if oauth account is connected', () => {

View File

@@ -11,6 +11,7 @@ import { ObjectWithPermissions } from 'src/app/data/object-with-permissions'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings' import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive' import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { NotificationService } from 'src/app/services/notification.service'
import { import {
PermissionAction, PermissionAction,
PermissionsService, PermissionsService,
@@ -19,7 +20,6 @@ import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperle
import { MailAccountService } from 'src/app/services/rest/mail-account.service' import { MailAccountService } from 'src/app/services/rest/mail-account.service'
import { MailRuleService } from 'src/app/services/rest/mail-rule.service' import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component' import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component' import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
@@ -71,7 +71,7 @@ export class MailComponent
constructor( constructor(
public mailAccountService: MailAccountService, public mailAccountService: MailAccountService,
public mailRuleService: MailRuleService, public mailRuleService: MailRuleService,
private toastService: ToastService, private notificationService: NotificationService,
private modalService: NgbModal, private modalService: NgbModal,
public permissionsService: PermissionsService, public permissionsService: PermissionsService,
private settingsService: SettingsService, private settingsService: SettingsService,
@@ -104,7 +104,7 @@ export class MailComponent
this.showAccounts = true this.showAccounts = true
}, },
error: (e) => { error: (e) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error retrieving mail accounts`, $localize`Error retrieving mail accounts`,
e e
) )
@@ -127,7 +127,10 @@ export class MailComponent
this.showRules = true this.showRules = true
}, },
error: (e) => { error: (e) => {
this.toastService.showError($localize`Error retrieving mail rules`, e) this.notificationService.showError(
$localize`Error retrieving mail rules`,
e
)
}, },
}) })
@@ -135,7 +138,9 @@ export class MailComponent
if (params.get('oauth_success')) { if (params.get('oauth_success')) {
const success = params.get('oauth_success') === '1' const success = params.get('oauth_success') === '1'
if (success) { if (success) {
this.toastService.showInfo($localize`OAuth2 authentication success`) this.notificationService.showInfo(
$localize`OAuth2 authentication success`
)
this.oAuthAccountId = parseInt(params.get('account_id')) this.oAuthAccountId = parseInt(params.get('account_id'))
if (this.mailAccounts.length > 0) { if (this.mailAccounts.length > 0) {
this.editMailAccount( this.editMailAccount(
@@ -145,7 +150,7 @@ export class MailComponent
) )
} }
} else { } else {
this.toastService.showError( this.notificationService.showError(
$localize`OAuth2 authentication failed, see logs for details` $localize`OAuth2 authentication failed, see logs for details`
) )
} }
@@ -169,7 +174,7 @@ export class MailComponent
modal.componentInstance.succeeded modal.componentInstance.succeeded
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((newMailAccount) => { .subscribe((newMailAccount) => {
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Saved account "${newMailAccount.name}".` $localize`Saved account "${newMailAccount.name}".`
) )
this.mailAccountService.clearCache() this.mailAccountService.clearCache()
@@ -182,7 +187,7 @@ export class MailComponent
modal.componentInstance.failed modal.componentInstance.failed
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((e) => { .subscribe((e) => {
this.toastService.showError($localize`Error saving account.`, e) this.notificationService.showError($localize`Error saving account.`, e)
}) })
} }
@@ -200,7 +205,7 @@ export class MailComponent
this.mailAccountService.delete(account).subscribe({ this.mailAccountService.delete(account).subscribe({
next: () => { next: () => {
modal.close() modal.close()
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Deleted mail account "${account.name}"` $localize`Deleted mail account "${account.name}"`
) )
this.mailAccountService.clearCache() this.mailAccountService.clearCache()
@@ -211,7 +216,7 @@ export class MailComponent
}) })
}, },
error: (e) => { error: (e) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error deleting mail account "${account.name}".`, $localize`Error deleting mail account "${account.name}".`,
e e
) )
@@ -223,12 +228,12 @@ export class MailComponent
processAccount(account: MailAccount) { processAccount(account: MailAccount) {
this.mailAccountService.processAccount(account).subscribe({ this.mailAccountService.processAccount(account).subscribe({
next: () => { next: () => {
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Processing mail account "${account.name}"` $localize`Processing mail account "${account.name}"`
) )
}, },
error: (e) => { error: (e) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error processing mail account "${account.name}"`, $localize`Error processing mail account "${account.name}"`,
e e
) )
@@ -247,7 +252,9 @@ export class MailComponent
modal.componentInstance.succeeded modal.componentInstance.succeeded
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((newMailRule) => { .subscribe((newMailRule) => {
this.toastService.showInfo($localize`Saved rule "${newMailRule.name}".`) this.notificationService.showInfo(
$localize`Saved rule "${newMailRule.name}".`
)
this.mailRuleService.clearCache() this.mailRuleService.clearCache()
this.mailRuleService this.mailRuleService
.listAll(null, null, { full_perms: true }) .listAll(null, null, { full_perms: true })
@@ -258,7 +265,7 @@ export class MailComponent
modal.componentInstance.failed modal.componentInstance.failed
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((e) => { .subscribe((e) => {
this.toastService.showError($localize`Error saving rule.`, e) this.notificationService.showError($localize`Error saving rule.`, e)
}) })
} }
@@ -272,14 +279,14 @@ export class MailComponent
onMailRuleEnableToggled(rule: MailRule) { onMailRuleEnableToggled(rule: MailRule) {
this.mailRuleService.patch(rule).subscribe({ this.mailRuleService.patch(rule).subscribe({
next: () => { next: () => {
this.toastService.showInfo( this.notificationService.showInfo(
rule.enabled rule.enabled
? $localize`Rule "${rule.name}" enabled.` ? $localize`Rule "${rule.name}" enabled.`
: $localize`Rule "${rule.name}" disabled.` : $localize`Rule "${rule.name}" disabled.`
) )
}, },
error: (e) => { error: (e) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error toggling rule "${rule.name}".`, $localize`Error toggling rule "${rule.name}".`,
e e
) )
@@ -301,7 +308,7 @@ export class MailComponent
this.mailRuleService.delete(rule).subscribe({ this.mailRuleService.delete(rule).subscribe({
next: () => { next: () => {
modal.close() modal.close()
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Deleted mail rule "${rule.name}"` $localize`Deleted mail rule "${rule.name}"`
) )
this.mailRuleService.clearCache() this.mailRuleService.clearCache()
@@ -312,7 +319,7 @@ export class MailComponent
}) })
}, },
error: (e) => { error: (e) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error deleting mail rule "${rule.name}".`, $localize`Error deleting mail rule "${rule.name}".`,
e e
) )
@@ -337,11 +344,11 @@ export class MailComponent
object['set_permissions'] = permissions['set_permissions'] object['set_permissions'] = permissions['set_permissions']
service.patch(object).subscribe({ service.patch(object).subscribe({
next: () => { next: () => {
this.toastService.showInfo($localize`Permissions updated`) this.notificationService.showInfo($localize`Permissions updated`)
modal.close() modal.close()
}, },
error: (e) => { error: (e) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error updating permissions`, $localize`Error updating permissions`,
e e
) )

View File

@@ -35,13 +35,13 @@ import { SortableDirective } from 'src/app/directives/sortable.directive'
import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { NotificationService } from 'src/app/services/notification.service'
import { import {
PermissionAction, PermissionAction,
PermissionsService, PermissionsService,
} from 'src/app/services/permissions.service' } from 'src/app/services/permissions.service'
import { BulkEditObjectOperation } from 'src/app/services/rest/abstract-name-filter-service' import { BulkEditObjectOperation } from 'src/app/services/rest/abstract-name-filter-service'
import { TagService } from 'src/app/services/rest/tag.service' import { TagService } from 'src/app/services/rest/tag.service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { EditDialogComponent } from '../../common/edit-dialog/edit-dialog.component' import { EditDialogComponent } from '../../common/edit-dialog/edit-dialog.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component' import { PageHeaderComponent } from '../../common/page-header/page-header.component'
@@ -76,7 +76,7 @@ describe('ManagementListComponent', () => {
let fixture: ComponentFixture<ManagementListComponent<Tag>> let fixture: ComponentFixture<ManagementListComponent<Tag>>
let tagService: TagService let tagService: TagService
let modalService: NgbModal let modalService: NgbModal
let toastService: ToastService let notificationService: NotificationService
let documentListViewService: DocumentListViewService let documentListViewService: DocumentListViewService
let permissionsService: PermissionsService let permissionsService: PermissionsService
@@ -129,7 +129,7 @@ describe('ManagementListComponent', () => {
.spyOn(permissionsService, 'currentUserOwnsObject') .spyOn(permissionsService, 'currentUserOwnsObject')
.mockReturnValue(true) .mockReturnValue(true)
modalService = TestBed.inject(NgbModal) modalService = TestBed.inject(NgbModal)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
documentListViewService = TestBed.inject(DocumentListViewService) documentListViewService = TestBed.inject(DocumentListViewService)
fixture = TestBed.createComponent(TagListComponent) fixture = TestBed.createComponent(TagListComponent)
component = fixture.componentInstance component = fixture.componentInstance
@@ -160,8 +160,8 @@ describe('ManagementListComponent', () => {
it('should support create, show notification on error / success', () => { it('should support create, show notification on error / success', () => {
let modal: NgbModalRef let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
const reloadSpy = jest.spyOn(component, 'reloadData') const reloadSpy = jest.spyOn(component, 'reloadData')
const createButton = fixture.debugElement.queryAll(By.css('button'))[3] const createButton = fixture.debugElement.queryAll(By.css('button'))[3]
@@ -172,20 +172,20 @@ describe('ManagementListComponent', () => {
// fail first // fail first
editDialog.failed.emit({ error: 'error creating item' }) editDialog.failed.emit({ error: 'error creating item' })
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
expect(reloadSpy).not.toHaveBeenCalled() expect(reloadSpy).not.toHaveBeenCalled()
// succeed // succeed
editDialog.succeeded.emit() editDialog.succeeded.emit()
expect(toastInfoSpy).toHaveBeenCalled() expect(notificationInfoSpy).toHaveBeenCalled()
expect(reloadSpy).toHaveBeenCalled() expect(reloadSpy).toHaveBeenCalled()
}) })
it('should support edit, show notification on error / success', () => { it('should support edit, show notification on error / success', () => {
let modal: NgbModalRef let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
const reloadSpy = jest.spyOn(component, 'reloadData') const reloadSpy = jest.spyOn(component, 'reloadData')
const editButton = fixture.debugElement.queryAll(By.css('button'))[6] const editButton = fixture.debugElement.queryAll(By.css('button'))[6]
@@ -197,19 +197,19 @@ describe('ManagementListComponent', () => {
// fail first // fail first
editDialog.failed.emit({ error: 'error editing item' }) editDialog.failed.emit({ error: 'error editing item' })
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
expect(reloadSpy).not.toHaveBeenCalled() expect(reloadSpy).not.toHaveBeenCalled()
// succeed // succeed
editDialog.succeeded.emit() editDialog.succeeded.emit()
expect(toastInfoSpy).toHaveBeenCalled() expect(notificationInfoSpy).toHaveBeenCalled()
expect(reloadSpy).toHaveBeenCalled() expect(reloadSpy).toHaveBeenCalled()
}) })
it('should support delete, show notification on error / success', () => { it('should support delete, show notification on error / success', () => {
let modal: NgbModalRef let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const deleteSpy = jest.spyOn(tagService, 'delete') const deleteSpy = jest.spyOn(tagService, 'delete')
const reloadSpy = jest.spyOn(component, 'reloadData') const reloadSpy = jest.spyOn(component, 'reloadData')
@@ -222,7 +222,7 @@ describe('ManagementListComponent', () => {
// fail first // fail first
deleteSpy.mockReturnValueOnce(throwError(() => new Error('error deleting'))) deleteSpy.mockReturnValueOnce(throwError(() => new Error('error deleting')))
editDialog.confirmClicked.emit() editDialog.confirmClicked.emit()
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
expect(reloadSpy).not.toHaveBeenCalled() expect(reloadSpy).not.toHaveBeenCalled()
// succeed // succeed
@@ -293,22 +293,22 @@ describe('ManagementListComponent', () => {
bulkEditPermsSpy.mockReturnValueOnce( bulkEditPermsSpy.mockReturnValueOnce(
throwError(() => new Error('error setting permissions')) throwError(() => new Error('error setting permissions'))
) )
const errorToastSpy = jest.spyOn(toastService, 'showError') const errornotificationSpy = jest.spyOn(notificationService, 'showError')
modal.componentInstance.confirmClicked.emit({ modal.componentInstance.confirmClicked.emit({
permissions: {}, permissions: {},
merge: true, merge: true,
}) })
expect(bulkEditPermsSpy).toHaveBeenCalled() expect(bulkEditPermsSpy).toHaveBeenCalled()
expect(errorToastSpy).toHaveBeenCalled() expect(errornotificationSpy).toHaveBeenCalled()
const successToastSpy = jest.spyOn(toastService, 'showInfo') const successnotificationSpy = jest.spyOn(notificationService, 'showInfo')
bulkEditPermsSpy.mockReturnValueOnce(of('OK')) bulkEditPermsSpy.mockReturnValueOnce(of('OK'))
modal.componentInstance.confirmClicked.emit({ modal.componentInstance.confirmClicked.emit({
permissions: {}, permissions: {},
merge: true, merge: true,
}) })
expect(bulkEditPermsSpy).toHaveBeenCalled() expect(bulkEditPermsSpy).toHaveBeenCalled()
expect(successToastSpy).toHaveBeenCalled() expect(successnotificationSpy).toHaveBeenCalled()
}) })
it('should support bulk delete objects', () => { it('should support bulk delete objects', () => {
@@ -327,19 +327,19 @@ describe('ManagementListComponent', () => {
bulkEditSpy.mockReturnValueOnce( bulkEditSpy.mockReturnValueOnce(
throwError(() => new Error('error setting permissions')) throwError(() => new Error('error setting permissions'))
) )
const errorToastSpy = jest.spyOn(toastService, 'showError') const errornotificationSpy = jest.spyOn(notificationService, 'showError')
modal.componentInstance.confirmClicked.emit(null) modal.componentInstance.confirmClicked.emit(null)
expect(bulkEditSpy).toHaveBeenCalledWith( expect(bulkEditSpy).toHaveBeenCalledWith(
Array.from(selected), Array.from(selected),
BulkEditObjectOperation.Delete BulkEditObjectOperation.Delete
) )
expect(errorToastSpy).toHaveBeenCalled() expect(errornotificationSpy).toHaveBeenCalled()
const successToastSpy = jest.spyOn(toastService, 'showInfo') const successnotificationSpy = jest.spyOn(notificationService, 'showInfo')
bulkEditSpy.mockReturnValueOnce(of('OK')) bulkEditSpy.mockReturnValueOnce(of('OK'))
modal.componentInstance.confirmClicked.emit(null) modal.componentInstance.confirmClicked.emit(null)
expect(bulkEditSpy).toHaveBeenCalled() expect(bulkEditSpy).toHaveBeenCalled()
expect(successToastSpy).toHaveBeenCalled() expect(successnotificationSpy).toHaveBeenCalled()
}) })
it('should disallow bulk permissions or delete objects if no global perms', () => { it('should disallow bulk permissions or delete objects if no global perms', () => {

View File

@@ -27,6 +27,7 @@ import {
SortEvent, SortEvent,
} from 'src/app/directives/sortable.directive' } from 'src/app/directives/sortable.directive'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { NotificationService } from 'src/app/services/notification.service'
import { import {
PermissionAction, PermissionAction,
PermissionsService, PermissionsService,
@@ -36,7 +37,6 @@ import {
AbstractNameFilterService, AbstractNameFilterService,
BulkEditObjectOperation, BulkEditObjectOperation,
} from 'src/app/services/rest/abstract-name-filter-service' } from 'src/app/services/rest/abstract-name-filter-service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component' import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component' import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
@@ -63,7 +63,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
protected service: AbstractNameFilterService<T>, protected service: AbstractNameFilterService<T>,
private modalService: NgbModal, private modalService: NgbModal,
private editDialogComponent: any, private editDialogComponent: any,
private toastService: ToastService, private notificationService: NotificationService,
private documentListViewService: DocumentListViewService, private documentListViewService: DocumentListViewService,
private permissionsService: PermissionsService, private permissionsService: PermissionsService,
protected filterRuleType: number, protected filterRuleType: number,
@@ -173,12 +173,12 @@ export abstract class ManagementListComponent<T extends MatchingModel>
activeModal.componentInstance.dialogMode = EditDialogMode.CREATE activeModal.componentInstance.dialogMode = EditDialogMode.CREATE
activeModal.componentInstance.succeeded.subscribe(() => { activeModal.componentInstance.succeeded.subscribe(() => {
this.reloadData() this.reloadData()
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Successfully created ${this.typeName}.` $localize`Successfully created ${this.typeName}.`
) )
}) })
activeModal.componentInstance.failed.subscribe((e) => { activeModal.componentInstance.failed.subscribe((e) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error occurred while creating ${this.typeName}.`, $localize`Error occurred while creating ${this.typeName}.`,
e e
) )
@@ -193,12 +193,12 @@ export abstract class ManagementListComponent<T extends MatchingModel>
activeModal.componentInstance.dialogMode = EditDialogMode.EDIT activeModal.componentInstance.dialogMode = EditDialogMode.EDIT
activeModal.componentInstance.succeeded.subscribe(() => { activeModal.componentInstance.succeeded.subscribe(() => {
this.reloadData() this.reloadData()
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Successfully updated ${this.typeName} "${object.name}".` $localize`Successfully updated ${this.typeName} "${object.name}".`
) )
}) })
activeModal.componentInstance.failed.subscribe((e) => { activeModal.componentInstance.failed.subscribe((e) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error occurred while saving ${this.typeName}.`, $localize`Error occurred while saving ${this.typeName}.`,
e e
) )
@@ -234,7 +234,7 @@ export abstract class ManagementListComponent<T extends MatchingModel>
}, },
error: (error) => { error: (error) => {
activeModal.componentInstance.buttonsEnabled = true activeModal.componentInstance.buttonsEnabled = true
this.toastService.showError( this.notificationService.showError(
$localize`Error while deleting element`, $localize`Error while deleting element`,
error error
) )
@@ -313,14 +313,14 @@ export abstract class ManagementListComponent<T extends MatchingModel>
.subscribe({ .subscribe({
next: () => { next: () => {
modal.close() modal.close()
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Permissions updated successfully` $localize`Permissions updated successfully`
) )
this.reloadData() this.reloadData()
}, },
error: (error) => { error: (error) => {
modal.componentInstance.buttonsEnabled = true modal.componentInstance.buttonsEnabled = true
this.toastService.showError( this.notificationService.showError(
$localize`Error updating permissions`, $localize`Error updating permissions`,
error error
) )
@@ -349,12 +349,14 @@ export abstract class ManagementListComponent<T extends MatchingModel>
.subscribe({ .subscribe({
next: () => { next: () => {
modal.close() modal.close()
this.toastService.showInfo($localize`Objects deleted successfully`) this.notificationService.showInfo(
$localize`Objects deleted successfully`
)
this.reloadData() this.reloadData()
}, },
error: (error) => { error: (error) => {
modal.componentInstance.buttonsEnabled = true modal.componentInstance.buttonsEnabled = true
this.toastService.showError( this.notificationService.showError(
$localize`Error deleting objects`, $localize`Error deleting objects`,
error error
) )

View File

@@ -10,10 +10,10 @@ import { of, throwError } from 'rxjs'
import { SavedView } from 'src/app/data/saved-view' import { SavedView } from 'src/app/data/saved-view'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service' import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component' import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
import { CheckComponent } from '../../common/input/check/check.component' import { CheckComponent } from '../../common/input/check/check.component'
import { DragDropSelectComponent } from '../../common/input/drag-drop-select/drag-drop-select.component' import { DragDropSelectComponent } from '../../common/input/drag-drop-select/drag-drop-select.component'
@@ -32,7 +32,7 @@ describe('SavedViewsComponent', () => {
let component: SavedViewsComponent let component: SavedViewsComponent
let fixture: ComponentFixture<SavedViewsComponent> let fixture: ComponentFixture<SavedViewsComponent>
let savedViewService: SavedViewService let savedViewService: SavedViewService
let toastService: ToastService let notificationService: NotificationService
beforeEach(async () => { beforeEach(async () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -77,7 +77,7 @@ describe('SavedViewsComponent', () => {
}).compileComponents() }).compileComponents()
savedViewService = TestBed.inject(SavedViewService) savedViewService = TestBed.inject(SavedViewService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
fixture = TestBed.createComponent(SavedViewsComponent) fixture = TestBed.createComponent(SavedViewsComponent)
component = fixture.componentInstance component = fixture.componentInstance
@@ -93,8 +93,8 @@ describe('SavedViewsComponent', () => {
}) })
it('should support save saved views, show error', () => { it('should support save saved views, show error', () => {
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastSpy = jest.spyOn(toastService, 'show') const notificationSpy = jest.spyOn(notificationService, 'show')
const savedViewPatchSpy = jest.spyOn(savedViewService, 'patchMany') const savedViewPatchSpy = jest.spyOn(savedViewService, 'patchMany')
const toggle = fixture.debugElement.query( const toggle = fixture.debugElement.query(
@@ -108,16 +108,16 @@ describe('SavedViewsComponent', () => {
throwError(() => new Error('unable to save saved views')) throwError(() => new Error('unable to save saved views'))
) )
component.save() component.save()
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
expect(savedViewPatchSpy).toHaveBeenCalled() expect(savedViewPatchSpy).toHaveBeenCalled()
toastSpy.mockClear() notificationSpy.mockClear()
toastErrorSpy.mockClear() notificationErrorSpy.mockClear()
savedViewPatchSpy.mockClear() savedViewPatchSpy.mockClear()
// succeed saved views // succeed saved views
savedViewPatchSpy.mockReturnValueOnce(of(savedViews as SavedView[])) savedViewPatchSpy.mockReturnValueOnce(of(savedViews as SavedView[]))
component.save() component.save()
expect(toastErrorSpy).not.toHaveBeenCalled() expect(notificationErrorSpy).not.toHaveBeenCalled()
expect(savedViewPatchSpy).toHaveBeenCalled() expect(savedViewPatchSpy).toHaveBeenCalled()
}) })
@@ -150,12 +150,12 @@ describe('SavedViewsComponent', () => {
}) })
it('should support delete saved view', () => { it('should support delete saved view', () => {
const toastSpy = jest.spyOn(toastService, 'showInfo') const notificationSpy = jest.spyOn(notificationService, 'showInfo')
const deleteSpy = jest.spyOn(savedViewService, 'delete') const deleteSpy = jest.spyOn(savedViewService, 'delete')
deleteSpy.mockReturnValue(of(true)) deleteSpy.mockReturnValue(of(true))
component.deleteSavedView(savedViews[0] as SavedView) component.deleteSavedView(savedViews[0] as SavedView)
expect(deleteSpy).toHaveBeenCalled() expect(deleteSpy).toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith( expect(notificationSpy).toHaveBeenCalledWith(
`Saved view "${savedViews[0].name}" deleted.` `Saved view "${savedViews[0].name}" deleted.`
) )
}) })

View File

@@ -11,9 +11,9 @@ import { BehaviorSubject, Observable, takeUntil } from 'rxjs'
import { DisplayMode } from 'src/app/data/document' import { DisplayMode } from 'src/app/data/document'
import { SavedView } from 'src/app/data/saved-view' import { SavedView } from 'src/app/data/saved-view'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { NotificationService } from 'src/app/services/notification.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service' import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component' import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
import { DragDropSelectComponent } from '../../common/input/drag-drop-select/drag-drop-select.component' import { DragDropSelectComponent } from '../../common/input/drag-drop-select/drag-drop-select.component'
import { NumberComponent } from '../../common/input/number/number.component' import { NumberComponent } from '../../common/input/number/number.component'
@@ -58,7 +58,7 @@ export class SavedViewsComponent
constructor( constructor(
private savedViewService: SavedViewService, private savedViewService: SavedViewService,
private settings: SettingsService, private settings: SettingsService,
private toastService: ToastService private notificationService: NotificationService
) { ) {
super() super()
this.settings.organizingSidebarSavedViews = true this.settings.organizingSidebarSavedViews = true
@@ -129,7 +129,7 @@ export class SavedViewsComponent
this.savedViewService.delete(savedView).subscribe(() => { this.savedViewService.delete(savedView).subscribe(() => {
this.savedViewsGroup.removeControl(savedView.id.toString()) this.savedViewsGroup.removeControl(savedView.id.toString())
this.savedViews.splice(this.savedViews.indexOf(savedView), 1) this.savedViews.splice(this.savedViews.indexOf(savedView), 1)
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Saved view "${savedView.name}" deleted.` $localize`Saved view "${savedView.name}" deleted.`
) )
this.savedViewService.clearCache() this.savedViewService.clearCache()
@@ -155,11 +155,13 @@ export class SavedViewsComponent
if (changed.length) { if (changed.length) {
this.savedViewService.patchMany(changed).subscribe({ this.savedViewService.patchMany(changed).subscribe({
next: () => { next: () => {
this.toastService.showInfo($localize`Views saved successfully.`) this.notificationService.showInfo(
$localize`Views saved successfully.`
)
this.store.next(this.savedViewsForm.value) this.store.next(this.savedViewsForm.value)
}, },
error: (error) => { error: (error) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error while saving views.`, $localize`Error while saving views.`,
error error
) )

View File

@@ -13,12 +13,12 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
import { SortableDirective } from 'src/app/directives/sortable.directive' import { SortableDirective } from 'src/app/directives/sortable.directive'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { NotificationService } from 'src/app/services/notification.service'
import { import {
PermissionsService, PermissionsService,
PermissionType, PermissionType,
} from 'src/app/services/permissions.service' } from 'src/app/services/permissions.service'
import { StoragePathService } from 'src/app/services/rest/storage-path.service' import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { ToastService } from 'src/app/services/toast.service'
import { StoragePathEditDialogComponent } from '../../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component' import { StoragePathEditDialogComponent } from '../../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component' import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { ManagementListComponent } from '../management-list/management-list.component' import { ManagementListComponent } from '../management-list/management-list.component'
@@ -45,7 +45,7 @@ export class StoragePathListComponent extends ManagementListComponent<StoragePat
constructor( constructor(
directoryService: StoragePathService, directoryService: StoragePathService,
modalService: NgbModal, modalService: NgbModal,
toastService: ToastService, notificationService: NotificationService,
documentListViewService: DocumentListViewService, documentListViewService: DocumentListViewService,
permissionsService: PermissionsService permissionsService: PermissionsService
) { ) {
@@ -53,7 +53,7 @@ export class StoragePathListComponent extends ManagementListComponent<StoragePat
directoryService, directoryService,
modalService, modalService,
StoragePathEditDialogComponent, StoragePathEditDialogComponent,
toastService, notificationService,
documentListViewService, documentListViewService,
permissionsService, permissionsService,
FILTER_HAS_STORAGE_PATH_ANY, FILTER_HAS_STORAGE_PATH_ANY,

View File

@@ -13,12 +13,12 @@ import { IfPermissionsDirective } from 'src/app/directives/if-permissions.direct
import { SortableDirective } from 'src/app/directives/sortable.directive' import { SortableDirective } from 'src/app/directives/sortable.directive'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { NotificationService } from 'src/app/services/notification.service'
import { import {
PermissionsService, PermissionsService,
PermissionType, PermissionType,
} from 'src/app/services/permissions.service' } from 'src/app/services/permissions.service'
import { TagService } from 'src/app/services/rest/tag.service' import { TagService } from 'src/app/services/rest/tag.service'
import { ToastService } from 'src/app/services/toast.service'
import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component' import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component' import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { ManagementListComponent } from '../management-list/management-list.component' import { ManagementListComponent } from '../management-list/management-list.component'
@@ -45,7 +45,7 @@ export class TagListComponent extends ManagementListComponent<Tag> {
constructor( constructor(
tagService: TagService, tagService: TagService,
modalService: NgbModal, modalService: NgbModal,
toastService: ToastService, notificationService: NotificationService,
documentListViewService: DocumentListViewService, documentListViewService: DocumentListViewService,
permissionsService: PermissionsService permissionsService: PermissionsService
) { ) {
@@ -53,7 +53,7 @@ export class TagListComponent extends ManagementListComponent<Tag> {
tagService, tagService,
modalService, modalService,
TagEditDialogComponent, TagEditDialogComponent,
toastService, notificationService,
documentListViewService, documentListViewService,
permissionsService, permissionsService,
FILTER_HAS_TAGS_ALL, FILTER_HAS_TAGS_ALL,

View File

@@ -19,9 +19,9 @@ import {
WorkflowTriggerType, WorkflowTriggerType,
} from 'src/app/data/workflow-trigger' } from 'src/app/data/workflow-trigger'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { WorkflowService } from 'src/app/services/rest/workflow.service' import { WorkflowService } from 'src/app/services/rest/workflow.service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component' import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
import { WorkflowEditDialogComponent } from '../../common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component' import { WorkflowEditDialogComponent } from '../../common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component'
@@ -77,7 +77,7 @@ describe('WorkflowsComponent', () => {
let fixture: ComponentFixture<WorkflowsComponent> let fixture: ComponentFixture<WorkflowsComponent>
let workflowService: WorkflowService let workflowService: WorkflowService
let modalService: NgbModal let modalService: NgbModal
let toastService: ToastService let notificationService: NotificationService
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -116,7 +116,7 @@ describe('WorkflowsComponent', () => {
}) })
) )
modalService = TestBed.inject(NgbModal) modalService = TestBed.inject(NgbModal)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
jest.useFakeTimers() jest.useFakeTimers()
fixture = TestBed.createComponent(WorkflowsComponent) fixture = TestBed.createComponent(WorkflowsComponent)
component = fixture.componentInstance component = fixture.componentInstance
@@ -127,8 +127,8 @@ describe('WorkflowsComponent', () => {
it('should support create, show notification on error / success', () => { it('should support create, show notification on error / success', () => {
let modal: NgbModalRef let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
const reloadSpy = jest.spyOn(component, 'reload') const reloadSpy = jest.spyOn(component, 'reload')
const createButton = fixture.debugElement.queryAll(By.css('button'))[1] const createButton = fixture.debugElement.queryAll(By.css('button'))[1]
@@ -139,20 +139,20 @@ describe('WorkflowsComponent', () => {
// fail first // fail first
editDialog.failed.emit({ error: 'error creating item' }) editDialog.failed.emit({ error: 'error creating item' })
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
expect(reloadSpy).not.toHaveBeenCalled() expect(reloadSpy).not.toHaveBeenCalled()
// succeed // succeed
editDialog.succeeded.emit(workflows[0]) editDialog.succeeded.emit(workflows[0])
expect(toastInfoSpy).toHaveBeenCalled() expect(notificationInfoSpy).toHaveBeenCalled()
expect(reloadSpy).toHaveBeenCalled() expect(reloadSpy).toHaveBeenCalled()
}) })
it('should support edit, show notification on error / success', () => { it('should support edit, show notification on error / success', () => {
let modal: NgbModalRef let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
const reloadSpy = jest.spyOn(component, 'reload') const reloadSpy = jest.spyOn(component, 'reload')
const editButton = fixture.debugElement.queryAll(By.css('button'))[2] const editButton = fixture.debugElement.queryAll(By.css('button'))[2]
@@ -164,12 +164,12 @@ describe('WorkflowsComponent', () => {
// fail first // fail first
editDialog.failed.emit({ error: 'error editing item' }) editDialog.failed.emit({ error: 'error editing item' })
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
expect(reloadSpy).not.toHaveBeenCalled() expect(reloadSpy).not.toHaveBeenCalled()
// succeed // succeed
editDialog.succeeded.emit(workflows[0]) editDialog.succeeded.emit(workflows[0])
expect(toastInfoSpy).toHaveBeenCalled() expect(notificationInfoSpy).toHaveBeenCalled()
expect(reloadSpy).toHaveBeenCalled() expect(reloadSpy).toHaveBeenCalled()
}) })
@@ -240,7 +240,7 @@ describe('WorkflowsComponent', () => {
it('should support delete, show notification on error / success', () => { it('should support delete, show notification on error / success', () => {
let modal: NgbModalRef let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const deleteSpy = jest.spyOn(workflowService, 'delete') const deleteSpy = jest.spyOn(workflowService, 'delete')
const reloadSpy = jest.spyOn(component, 'reload') const reloadSpy = jest.spyOn(component, 'reload')
@@ -253,7 +253,7 @@ describe('WorkflowsComponent', () => {
// fail first // fail first
deleteSpy.mockReturnValueOnce(throwError(() => new Error('error deleting'))) deleteSpy.mockReturnValueOnce(throwError(() => new Error('error deleting')))
editDialog.confirmClicked.emit() editDialog.confirmClicked.emit()
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
expect(reloadSpy).not.toHaveBeenCalled() expect(reloadSpy).not.toHaveBeenCalled()
// succeed // succeed
@@ -267,21 +267,21 @@ describe('WorkflowsComponent', () => {
const toggleInput = fixture.debugElement.query( const toggleInput = fixture.debugElement.query(
By.css('input[type="checkbox"]') By.css('input[type="checkbox"]')
) )
const toastErrorSpy = jest.spyOn(toastService, 'showError') const notificationErrorSpy = jest.spyOn(notificationService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const notificationInfoSpy = jest.spyOn(notificationService, 'showInfo')
// fail first // fail first
patchSpy.mockReturnValueOnce( patchSpy.mockReturnValueOnce(
throwError(() => new Error('Error getting config')) throwError(() => new Error('Error getting config'))
) )
toggleInput.nativeElement.click() toggleInput.nativeElement.click()
expect(patchSpy).toHaveBeenCalled() expect(patchSpy).toHaveBeenCalled()
expect(toastErrorSpy).toHaveBeenCalled() expect(notificationErrorSpy).toHaveBeenCalled()
// succeed second // succeed second
patchSpy.mockReturnValueOnce(of(workflows[0])) patchSpy.mockReturnValueOnce(of(workflows[0]))
toggleInput.nativeElement.click() toggleInput.nativeElement.click()
patchSpy.mockReturnValueOnce(of({ ...workflows[0], enabled: false })) patchSpy.mockReturnValueOnce(of({ ...workflows[0], enabled: false }))
toggleInput.nativeElement.click() toggleInput.nativeElement.click()
expect(patchSpy).toHaveBeenCalled() expect(patchSpy).toHaveBeenCalled()
expect(toastInfoSpy).toHaveBeenCalled() expect(notificationInfoSpy).toHaveBeenCalled()
}) })
}) })

View File

@@ -5,9 +5,9 @@ import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { delay, takeUntil, tap } from 'rxjs' import { delay, takeUntil, tap } from 'rxjs'
import { Workflow } from 'src/app/data/workflow' import { Workflow } from 'src/app/data/workflow'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { NotificationService } from 'src/app/services/notification.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { WorkflowService } from 'src/app/services/rest/workflow.service' import { WorkflowService } from 'src/app/services/rest/workflow.service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component' import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
import { import {
@@ -40,7 +40,7 @@ export class WorkflowsComponent
private workflowService: WorkflowService, private workflowService: WorkflowService,
public permissionsService: PermissionsService, public permissionsService: PermissionsService,
private modalService: NgbModal, private modalService: NgbModal,
private toastService: ToastService private notificationService: NotificationService
) { ) {
super() super()
} }
@@ -90,7 +90,7 @@ export class WorkflowsComponent
modal.componentInstance.succeeded modal.componentInstance.succeeded
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((newWorkflow) => { .subscribe((newWorkflow) => {
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Saved workflow "${newWorkflow.name}".` $localize`Saved workflow "${newWorkflow.name}".`
) )
this.workflowService.clearCache() this.workflowService.clearCache()
@@ -99,7 +99,7 @@ export class WorkflowsComponent
modal.componentInstance.failed modal.componentInstance.failed
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((e) => { .subscribe((e) => {
this.toastService.showError($localize`Error saving workflow.`, e) this.notificationService.showError($localize`Error saving workflow.`, e)
}) })
} }
@@ -142,14 +142,14 @@ export class WorkflowsComponent
this.workflowService.delete(workflow).subscribe({ this.workflowService.delete(workflow).subscribe({
next: () => { next: () => {
modal.close() modal.close()
this.toastService.showInfo( this.notificationService.showInfo(
$localize`Deleted workflow "${workflow.name}".` $localize`Deleted workflow "${workflow.name}".`
) )
this.workflowService.clearCache() this.workflowService.clearCache()
this.reload() this.reload()
}, },
error: (e) => { error: (e) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error deleting workflow "${workflow.name}".`, $localize`Error deleting workflow "${workflow.name}".`,
e e
) )
@@ -161,7 +161,7 @@ export class WorkflowsComponent
toggleWorkflowEnabled(workflow: Workflow) { toggleWorkflowEnabled(workflow: Workflow) {
this.workflowService.patch(workflow).subscribe({ this.workflowService.patch(workflow).subscribe({
next: () => { next: () => {
this.toastService.showInfo( this.notificationService.showInfo(
workflow.enabled workflow.enabled
? $localize`Enabled workflow "${workflow.name}"` ? $localize`Enabled workflow "${workflow.name}"`
: $localize`Disabled workflow "${workflow.name}"` : $localize`Disabled workflow "${workflow.name}"`
@@ -170,7 +170,7 @@ export class WorkflowsComponent
this.reload() this.reload()
}, },
error: (e) => { error: (e) => {
this.toastService.showError( this.notificationService.showError(
$localize`Error toggling workflow "${workflow.name}".`, $localize`Error toggling workflow "${workflow.name}".`,
e e
) )

View File

@@ -1,12 +1,12 @@
import { TestBed } from '@angular/core/testing' import { TestBed } from '@angular/core/testing'
import { ActivatedRoute, RouterState } from '@angular/router' import { ActivatedRoute, RouterState } from '@angular/router'
import { TourService } from 'ngx-ui-tour-ng-bootstrap' import { TourService } from 'ngx-ui-tour-ng-bootstrap'
import { NotificationService } from '../services/notification.service'
import { import {
PermissionAction, PermissionAction,
PermissionType, PermissionType,
PermissionsService, PermissionsService,
} from '../services/permissions.service' } from '../services/permissions.service'
import { ToastService } from '../services/toast.service'
import { PermissionsGuard } from './permissions.guard' import { PermissionsGuard } from './permissions.guard'
describe('PermissionsGuard', () => { describe('PermissionsGuard', () => {
@@ -15,7 +15,7 @@ describe('PermissionsGuard', () => {
let route: ActivatedRoute let route: ActivatedRoute
let routerState: RouterState let routerState: RouterState
let tourService: TourService let tourService: TourService
let toastService: ToastService let notificationService: NotificationService
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -44,13 +44,13 @@ describe('PermissionsGuard', () => {
}, },
}, },
TourService, TourService,
ToastService, NotificationService,
], ],
}) })
permissionsService = TestBed.inject(PermissionsService) permissionsService = TestBed.inject(PermissionsService)
tourService = TestBed.inject(TourService) tourService = TestBed.inject(TourService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
guard = TestBed.inject(PermissionsGuard) guard = TestBed.inject(PermissionsGuard)
route = TestBed.inject(ActivatedRoute) route = TestBed.inject(ActivatedRoute)
routerState = TestBed.inject(RouterState) routerState = TestBed.inject(RouterState)
@@ -88,11 +88,11 @@ describe('PermissionsGuard', () => {
}) })
jest.spyOn(tourService, 'getStatus').mockImplementation(() => 2) jest.spyOn(tourService, 'getStatus').mockImplementation(() => 2)
const toastSpy = jest.spyOn(toastService, 'showError') const notificationSpy = jest.spyOn(notificationService, 'showError')
const canActivate = guard.canActivate(route.snapshot, routerState.snapshot) const canActivate = guard.canActivate(route.snapshot, routerState.snapshot)
expect(canActivate).toHaveProperty('root') // returns UrlTree expect(canActivate).toHaveProperty('root') // returns UrlTree
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
}) })
}) })

View File

@@ -6,15 +6,15 @@ import {
UrlTree, UrlTree,
} from '@angular/router' } from '@angular/router'
import { TourService } from 'ngx-ui-tour-ng-bootstrap' import { TourService } from 'ngx-ui-tour-ng-bootstrap'
import { NotificationService } from '../services/notification.service'
import { PermissionsService } from '../services/permissions.service' import { PermissionsService } from '../services/permissions.service'
import { ToastService } from '../services/toast.service'
@Injectable() @Injectable()
export class PermissionsGuard { export class PermissionsGuard {
constructor( constructor(
private permissionsService: PermissionsService, private permissionsService: PermissionsService,
private router: Router, private router: Router,
private toastService: ToastService, private notificationService: NotificationService,
private tourService: TourService private tourService: TourService
) {} ) {}
@@ -32,7 +32,7 @@ export class PermissionsGuard {
) { ) {
// Check if tour is running 1 = TourState.ON // Check if tour is running 1 = TourState.ON
if (this.tourService.getStatus() !== 1) { if (this.tourService.getStatus() !== 1) {
this.toastService.showError( this.notificationService.showError(
$localize`You don't have permissions to do that` $localize`You don't have permissions to do that`
) )
} }

View File

@@ -0,0 +1,109 @@
import { TestBed } from '@angular/core/testing'
import { NotificationService } from './notification.service'
describe('NotificationService', () => {
let notificationService: NotificationService
beforeEach(() => {
TestBed.configureTestingModule({
providers: [NotificationService],
})
notificationService = TestBed.inject(NotificationService)
})
it('adds notification on show', () => {
const notification = {
title: 'Title',
content: 'content',
delay: 5000,
}
notificationService.show(notification)
notificationService.getNotifications().subscribe((notifications) => {
expect(notifications).toContainEqual(notification)
})
})
it('adds a unique id to notification on show', () => {
const notification = {
title: 'Title',
content: 'content',
delay: 5000,
}
notificationService.show(notification)
notificationService.getNotifications().subscribe((notifications) => {
expect(notifications[0].id).toBeDefined()
})
})
it('parses error string to object on show', () => {
const notification = {
title: 'Title',
content: 'content',
delay: 5000,
error: 'Error string',
}
notificationService.show(notification)
notificationService.getNotifications().subscribe((notifications) => {
expect(notifications[0].error).toEqual('Error string')
})
})
it('creates notifications with defaults on showInfo and showError', () => {
notificationService.showInfo('Info notification')
notificationService.showError('Error notification')
notificationService.getNotifications().subscribe((notifications) => {
expect(notifications).toContainEqual({
content: 'Info notification',
delay: 5000,
})
expect(notifications).toContainEqual({
content: 'Error notification',
delay: 10000,
})
})
})
it('removes notification on close', () => {
const notification = {
title: 'Title',
content: 'content',
delay: 5000,
}
notificationService.show(notification)
notificationService.closeNotification(notification)
notificationService.getNotifications().subscribe((notifications) => {
expect(notifications).toHaveLength(0)
})
})
it('clears all notifications on clear', () => {
notificationService.showInfo('Info notification')
notificationService.showError('Error notification')
notificationService.clearNotifications()
notificationService.getNotifications().subscribe((notifications) => {
expect(notifications).toHaveLength(0)
})
})
it('suppresses popup notifications if suppressPopupNotifications is true', (finish) => {
notificationService.showNotification.subscribe((notification) => {
expect(notification).not.toBeNull()
})
notificationService.showInfo('Info notification')
notificationService.showNotification.subscribe((notification) => {
expect(notification).toBeNull()
finish()
})
notificationService.suppressPopupNotifications = true
notificationService.showInfo('Info notification')
})
})

View File

@@ -0,0 +1,87 @@
import { Injectable } from '@angular/core'
import { Subject } from 'rxjs'
import { v4 as uuidv4 } from 'uuid'
export interface Notification {
id?: string
content: string
delay: number
delayRemaining?: number
action?: any
actionName?: string
classname?: string
error?: any
}
@Injectable({
providedIn: 'root',
})
export class NotificationService {
constructor() {}
_suppressPopupNotifications: boolean
set suppressPopupNotifications(value: boolean) {
this._suppressPopupNotifications = value
this.showNotification.next(null)
}
private notifications: Notification[] = []
private notificationsSubject: Subject<Notification[]> = new Subject()
public showNotification: Subject<Notification> = new Subject()
show(notification: Notification) {
if (!notification.id) {
notification.id = uuidv4()
}
if (typeof notification.error === 'string') {
try {
notification.error = JSON.parse(notification.error)
} catch (e) {}
}
this.notifications.unshift(notification)
if (!this._suppressPopupNotifications) {
this.showNotification.next(notification)
}
this.notificationsSubject.next(this.notifications)
}
showError(content: string, error: any = null, delay: number = 10000) {
this.show({
content: content,
delay: delay,
classname: 'error',
error,
})
}
showInfo(content: string, delay: number = 5000) {
this.show({ content: content, delay: delay })
}
closeNotification(notification: Notification) {
let index = this.notifications.findIndex((t) => t.id == notification.id)
if (index > -1) {
this.notifications.splice(index, 1)
this.notificationsSubject.next(this.notifications)
}
}
getNotifications() {
return this.notificationsSubject
}
clearNotifications() {
this.notifications = []
this.notificationsSubject.next(this.notifications)
this.showNotification.next(null)
}
}

View File

@@ -14,10 +14,10 @@ import { CustomFieldDataType } from '../data/custom-field'
import { DEFAULT_DISPLAY_FIELDS, DisplayField } from '../data/document' import { DEFAULT_DISPLAY_FIELDS, DisplayField } from '../data/document'
import { SavedView } from '../data/saved-view' import { SavedView } from '../data/saved-view'
import { SETTINGS_KEYS, UiSettings } from '../data/ui-settings' import { SETTINGS_KEYS, UiSettings } from '../data/ui-settings'
import { NotificationService } from './notification.service'
import { PermissionsService } from './permissions.service' import { PermissionsService } from './permissions.service'
import { CustomFieldsService } from './rest/custom-fields.service' import { CustomFieldsService } from './rest/custom-fields.service'
import { SettingsService } from './settings.service' import { SettingsService } from './settings.service'
import { ToastService } from './toast.service'
const customFields = [ const customFields = [
{ {
@@ -41,7 +41,7 @@ describe('SettingsService', () => {
let customFieldsService: CustomFieldsService let customFieldsService: CustomFieldsService
let permissionService: PermissionsService let permissionService: PermissionsService
let subscription: Subscription let subscription: Subscription
let toastService: ToastService let notificationService: NotificationService
const ui_settings: UiSettings = { const ui_settings: UiSettings = {
user: { user: {
@@ -105,7 +105,7 @@ describe('SettingsService', () => {
customFieldsService = TestBed.inject(CustomFieldsService) customFieldsService = TestBed.inject(CustomFieldsService)
permissionService = TestBed.inject(PermissionsService) permissionService = TestBed.inject(PermissionsService)
settingsService = TestBed.inject(SettingsService) settingsService = TestBed.inject(SettingsService)
toastService = TestBed.inject(ToastService) notificationService = TestBed.inject(NotificationService)
// Normally done in app initializer // Normally done in app initializer
settingsService.initializeSettings().subscribe() settingsService.initializeSettings().subscribe()
}) })
@@ -122,8 +122,8 @@ describe('SettingsService', () => {
expect(req.request.method).toEqual('GET') expect(req.request.method).toEqual('GET')
}) })
it('should catch error and show toast on retrieve ui_settings error', fakeAsync(() => { it('should catch error and show notification on retrieve ui_settings error', fakeAsync(() => {
const toastSpy = jest.spyOn(toastService, 'showError') const notificationSpy = jest.spyOn(notificationService, 'showError')
httpTestingController httpTestingController
.expectOne(`${environment.apiBaseUrl}ui_settings/`) .expectOne(`${environment.apiBaseUrl}ui_settings/`)
.flush( .flush(
@@ -131,7 +131,7 @@ describe('SettingsService', () => {
{ status: 403, statusText: 'Forbidden' } { status: 403, statusText: 'Forbidden' }
) )
tick(500) tick(500)
expect(toastSpy).toHaveBeenCalled() expect(notificationSpy).toHaveBeenCalled()
})) }))
it('calls ui_settings api endpoint with POST on store', () => { it('calls ui_settings api endpoint with POST on store', () => {

View File

@@ -26,13 +26,13 @@ import {
UiSettings, UiSettings,
} from '../data/ui-settings' } from '../data/ui-settings'
import { User } from '../data/user' import { User } from '../data/user'
import { NotificationService } from './notification.service'
import { import {
PermissionAction, PermissionAction,
PermissionsService, PermissionsService,
PermissionType, PermissionType,
} from './permissions.service' } from './permissions.service'
import { CustomFieldsService } from './rest/custom-fields.service' import { CustomFieldsService } from './rest/custom-fields.service'
import { ToastService } from './toast.service'
export interface LanguageOption { export interface LanguageOption {
code: string code: string
@@ -294,7 +294,7 @@ export class SettingsService {
private meta: Meta, private meta: Meta,
@Inject(LOCALE_ID) private localeId: string, @Inject(LOCALE_ID) private localeId: string,
protected http: HttpClient, protected http: HttpClient,
private toastService: ToastService, private notificationService: NotificationService,
private permissionsService: PermissionsService, private permissionsService: PermissionsService,
private customFieldsService: CustomFieldsService private customFieldsService: CustomFieldsService
) { ) {
@@ -307,7 +307,7 @@ export class SettingsService {
first(), first(),
catchError((error) => { catchError((error) => {
setTimeout(() => { setTimeout(() => {
this.toastService.showError('Error loading settings', error) this.notificationService.showError('Error loading settings', error)
}, 500) }, 500)
return of({ return of({
settings: { settings: {
@@ -601,7 +601,7 @@ export class SettingsService {
this.cookieService.get(this.getLanguageCookieName()) this.cookieService.get(this.getLanguageCookieName())
) )
} catch (error) { } catch (error) {
this.toastService.showError(errorMessage) this.notificationService.showError(errorMessage)
console.log(error) console.log(error)
} }
@@ -610,10 +610,10 @@ export class SettingsService {
.subscribe({ .subscribe({
next: () => { next: () => {
this.updateAppearanceSettings() this.updateAppearanceSettings()
this.toastService.showInfo(successMessage) this.notificationService.showInfo(successMessage)
}, },
error: (e) => { error: (e) => {
this.toastService.showError(errorMessage) this.notificationService.showError(errorMessage)
console.log(e) console.log(e)
}, },
}) })
@@ -633,7 +633,7 @@ export class SettingsService {
.pipe(first()) .pipe(first())
.subscribe({ .subscribe({
error: (e) => { error: (e) => {
this.toastService.showError( this.notificationService.showError(
'Error migrating update checking setting' 'Error migrating update checking setting'
) )
console.log(e) console.log(e)
@@ -663,7 +663,7 @@ export class SettingsService {
this.storeSettings() this.storeSettings()
.pipe(first()) .pipe(first())
.subscribe(() => { .subscribe(() => {
this.toastService.showInfo( this.notificationService.showInfo(
$localize`You can restart the tour from the settings page.` $localize`You can restart the tour from the settings page.`
) )
}) })

View File

@@ -1,109 +0,0 @@
import { TestBed } from '@angular/core/testing'
import { ToastService } from './toast.service'
describe('ToastService', () => {
let toastService: ToastService
beforeEach(() => {
TestBed.configureTestingModule({
providers: [ToastService],
})
toastService = TestBed.inject(ToastService)
})
it('adds toast on show', () => {
const toast = {
title: 'Title',
content: 'content',
delay: 5000,
}
toastService.show(toast)
toastService.getToasts().subscribe((toasts) => {
expect(toasts).toContainEqual(toast)
})
})
it('adds a unique id to toast on show', () => {
const toast = {
title: 'Title',
content: 'content',
delay: 5000,
}
toastService.show(toast)
toastService.getToasts().subscribe((toasts) => {
expect(toasts[0].id).toBeDefined()
})
})
it('parses error string to object on show', () => {
const toast = {
title: 'Title',
content: 'content',
delay: 5000,
error: 'Error string',
}
toastService.show(toast)
toastService.getToasts().subscribe((toasts) => {
expect(toasts[0].error).toEqual('Error string')
})
})
it('creates toasts with defaults on showInfo and showError', () => {
toastService.showInfo('Info toast')
toastService.showError('Error toast')
toastService.getToasts().subscribe((toasts) => {
expect(toasts).toContainEqual({
content: 'Info toast',
delay: 5000,
})
expect(toasts).toContainEqual({
content: 'Error toast',
delay: 10000,
})
})
})
it('removes toast on close', () => {
const toast = {
title: 'Title',
content: 'content',
delay: 5000,
}
toastService.show(toast)
toastService.closeToast(toast)
toastService.getToasts().subscribe((toasts) => {
expect(toasts).toHaveLength(0)
})
})
it('clears all toasts on clearToasts', () => {
toastService.showInfo('Info toast')
toastService.showError('Error toast')
toastService.clearToasts()
toastService.getToasts().subscribe((toasts) => {
expect(toasts).toHaveLength(0)
})
})
it('suppresses popup toasts if suppressPopupToasts is true', (finish) => {
toastService.showToast.subscribe((toast) => {
expect(toast).not.toBeNull()
})
toastService.showInfo('Info toast')
toastService.showToast.subscribe((toast) => {
expect(toast).toBeNull()
finish()
})
toastService.suppressPopupToasts = true
toastService.showInfo('Info toast')
})
})

View File

@@ -1,87 +0,0 @@
import { Injectable } from '@angular/core'
import { Subject } from 'rxjs'
import { v4 as uuidv4 } from 'uuid'
export interface Toast {
id?: string
content: string
delay: number
delayRemaining?: number
action?: any
actionName?: string
classname?: string
error?: any
}
@Injectable({
providedIn: 'root',
})
export class ToastService {
constructor() {}
_suppressPopupToasts: boolean
set suppressPopupToasts(value: boolean) {
this._suppressPopupToasts = value
this.showToast.next(null)
}
private toasts: Toast[] = []
private toastsSubject: Subject<Toast[]> = new Subject()
public showToast: Subject<Toast> = new Subject()
show(toast: Toast) {
if (!toast.id) {
toast.id = uuidv4()
}
if (typeof toast.error === 'string') {
try {
toast.error = JSON.parse(toast.error)
} catch (e) {}
}
this.toasts.unshift(toast)
if (!this._suppressPopupToasts) {
this.showToast.next(toast)
}
this.toastsSubject.next(this.toasts)
}
showError(content: string, error: any = null, delay: number = 10000) {
this.show({
content: content,
delay: delay,
classname: 'error',
error,
})
}
showInfo(content: string, delay: number = 5000) {
this.show({ content: content, delay: delay })
}
closeToast(toast: Toast) {
let index = this.toasts.findIndex((t) => t.id == toast.id)
if (index > -1) {
this.toasts.splice(index, 1)
this.toastsSubject.next(this.toasts)
}
}
getToasts() {
return this.toastsSubject
}
clearToasts() {
this.toasts = []
this.toastsSubject.next(this.toasts)
this.showToast.next(null)
}
}

View File

@@ -571,7 +571,7 @@ table.table {
} }
.toast { .toast {
--bs-toast-max-width: var(--pngx-toast-max-width); --bs-toast-max-width: var(--pngx-notification-max-width);
} }
.alert-primary { .alert-primary {

View File

@@ -24,11 +24,11 @@
--pngx-bg-alt2: var(--bs-gray-200); // #e9ecef --pngx-bg-alt2: var(--bs-gray-200); // #e9ecef
--pngx-bg-disabled: #f7f7f7; --pngx-bg-disabled: #f7f7f7;
--pngx-focus-alpha: 0.3; --pngx-focus-alpha: 0.3;
--pngx-toast-max-width: 360px; --pngx-notification-max-width: 360px;
--bs-info: var(--pngx-bg-alt2); --bs-info: var(--pngx-bg-alt2);
--bs-info-rgb: 233, 236, 239; --bs-info-rgb: 233, 236, 239;
@media screen and (min-width: 1024px) { @media screen and (min-width: 1024px) {
--pngx-toast-max-width: 450px; --pngx-notification-max-width: 450px;
} }
} }