web/storagenode: notifications domain and app types separations
WHAT: notifications store, api, types refactoring; service creation; notifications module store tests creation; WHY: to separate domain and app types; to be able to reuse code more easier; Change-Id: I01c6584fc41bbf73e0b6f84501cc66bbebd50ace
This commit is contained in:
parent
074784e1b4
commit
3b388c21cf
@ -68,7 +68,6 @@ import { APPSTATE_ACTIONS } from '@/app/store/modules/appState';
|
||||
import { NODE_ACTIONS } from '@/app/store/modules/node';
|
||||
import { NOTIFICATIONS_ACTIONS } from '@/app/store/modules/notifications';
|
||||
import { PAYOUT_ACTIONS } from '@/app/store/modules/payout';
|
||||
import { NotificationsCursor } from '@/app/types/notifications';
|
||||
|
||||
const {
|
||||
GET_NODE_INFO,
|
||||
@ -90,18 +89,23 @@ const {
|
||||
export default class SNOHeader extends Vue {
|
||||
public isNotificationPopupShown: boolean = false;
|
||||
public isOptionsShown: boolean = false;
|
||||
private readonly FIRST_PAGE: number = 1;
|
||||
|
||||
/**
|
||||
* Lifecycle hook before render.
|
||||
* Fetches first page of notifications.
|
||||
*/
|
||||
public beforeMount(): void {
|
||||
public async beforeMount(): Promise<void> {
|
||||
await this.$store.dispatch(APPSTATE_ACTIONS.SET_LOADING, true);
|
||||
|
||||
try {
|
||||
this.$store.dispatch(NODE_ACTIONS.GET_NODE_INFO);
|
||||
this.$store.dispatch(NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS, new NotificationsCursor(1));
|
||||
await this.$store.dispatch(NODE_ACTIONS.GET_NODE_INFO);
|
||||
await this.$store.dispatch(NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS, this.FIRST_PAGE);
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
|
||||
await this.$store.dispatch(APPSTATE_ACTIONS.SET_LOADING, false);
|
||||
}
|
||||
|
||||
public get nodeId(): string {
|
||||
@ -192,7 +196,7 @@ export default class SNOHeader extends Vue {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.$store.dispatch(NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS, new NotificationsCursor(1));
|
||||
await this.$store.dispatch(NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS, this.FIRST_PAGE);
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
|
@ -12,10 +12,10 @@
|
||||
<div
|
||||
class="notification-popup-container__content"
|
||||
:class="{'collapsed': isCollapsed}"
|
||||
v-if="latestNotifications.length"
|
||||
v-if="latest.length"
|
||||
>
|
||||
<SNONotification
|
||||
v-for="notification in latestNotifications"
|
||||
v-for="notification in latest"
|
||||
:key="notification.id"
|
||||
is-small="true"
|
||||
:notification="notification"
|
||||
@ -34,6 +34,7 @@ import { Component, Vue } from 'vue-property-decorator';
|
||||
import SNONotification from '@/app/components/notifications/SNONotification.vue';
|
||||
|
||||
import { RouteConfig } from '@/app/router';
|
||||
import { UINotification } from '@/app/types/notifications';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
@ -49,7 +50,7 @@ export default class NotificationsPopup extends Vue {
|
||||
/**
|
||||
* Represents first page of notifications.
|
||||
*/
|
||||
public get latestNotifications(): Notification[] {
|
||||
public get latest(): UINotification[] {
|
||||
return this.$store.state.notificationsModule.latestNotifications;
|
||||
}
|
||||
|
||||
@ -57,7 +58,7 @@ export default class NotificationsPopup extends Vue {
|
||||
* Indicates if popup is smaller than with scroll.
|
||||
*/
|
||||
public get isCollapsed(): boolean {
|
||||
return this.latestNotifications.length < 4;
|
||||
return this.latest.length < 4;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -30,12 +30,12 @@
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
|
||||
import { NOTIFICATIONS_ACTIONS } from '@/app/store/modules/notifications';
|
||||
import { Notification } from '@/app/types/notifications';
|
||||
import { UINotification } from '@/app/types/notifications';
|
||||
|
||||
@Component
|
||||
export default class SNONotification extends Vue {
|
||||
@Prop({default: () => new Notification()})
|
||||
public readonly notification: Notification;
|
||||
@Prop({default: () => new UINotification()})
|
||||
public readonly notification: UINotification;
|
||||
|
||||
/**
|
||||
* isSmall props indicates if component used in popup.
|
||||
|
@ -4,17 +4,19 @@
|
||||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
|
||||
import { makeNotificationsModule } from '@/app/store/modules/notifications';
|
||||
import { newNotificationsModule } from '@/app/store/modules/notifications';
|
||||
import { makePayoutModule } from '@/app/store/modules/payout';
|
||||
import { NotificationsHttpApi } from '@/storagenode/api/notifications';
|
||||
import { PayoutHttpApi } from '@/storagenode/api/payout';
|
||||
import { SNOApi } from '@/storagenode/api/storagenode';
|
||||
import { NotificationsService } from '@/storagenode/notifications/service';
|
||||
import { PayoutService } from '@/storagenode/payouts/service';
|
||||
|
||||
import { appStateModule } from './modules/appState';
|
||||
import { makeNodeModule } from './modules/node';
|
||||
|
||||
const notificationsApi = new NotificationsHttpApi();
|
||||
const notificationsService = new NotificationsService(notificationsApi);
|
||||
const payoutApi = new PayoutHttpApi();
|
||||
const payoutService = new PayoutService(payoutApi);
|
||||
const nodeApi = new SNOApi();
|
||||
@ -28,7 +30,7 @@ export const store = new Vuex.Store({
|
||||
modules: {
|
||||
node: makeNodeModule(nodeApi),
|
||||
appStateModule,
|
||||
notificationsModule: makeNotificationsModule(notificationsApi),
|
||||
notificationsModule: newNotificationsModule(notificationsService),
|
||||
payoutModule: makePayoutModule(payoutApi, payoutService),
|
||||
},
|
||||
});
|
||||
|
@ -1,12 +1,8 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import {
|
||||
Notification,
|
||||
NotificationsApi,
|
||||
NotificationsCursor,
|
||||
NotificationsState,
|
||||
} from '@/app/types/notifications';
|
||||
import { NotificationsState, UINotification } from '@/app/types/notifications';
|
||||
import { NotificationsService } from '@/storagenode/notifications/service';
|
||||
|
||||
export const NOTIFICATIONS_MUTATIONS = {
|
||||
SET_NOTIFICATIONS: 'SET_NOTIFICATIONS',
|
||||
@ -24,22 +20,22 @@ export const NOTIFICATIONS_ACTIONS = {
|
||||
/**
|
||||
* creates notifications module with all dependencies
|
||||
*
|
||||
* @param api - payments api
|
||||
* @param service - payments service
|
||||
*/
|
||||
export function makeNotificationsModule(api: NotificationsApi) {
|
||||
export function newNotificationsModule(service: NotificationsService) {
|
||||
return {
|
||||
state: new NotificationsState(),
|
||||
mutations: {
|
||||
[NOTIFICATIONS_MUTATIONS.SET_NOTIFICATIONS](state: NotificationsState, notificationsResponse: NotificationsState): void {
|
||||
state.notifications = notificationsResponse.notifications;
|
||||
state.pageCount = notificationsResponse.pageCount;
|
||||
state.unreadCount = notificationsResponse.unreadCount;
|
||||
[NOTIFICATIONS_MUTATIONS.SET_NOTIFICATIONS](state: NotificationsState, notificationsState: NotificationsState): void {
|
||||
state.notifications = notificationsState.notifications;
|
||||
state.pageCount = notificationsState.pageCount;
|
||||
state.unreadCount = notificationsState.unreadCount;
|
||||
},
|
||||
[NOTIFICATIONS_MUTATIONS.SET_LATEST](state: NotificationsState, notificationsResponse: NotificationsState): void {
|
||||
state.latestNotifications = notificationsResponse.notifications;
|
||||
[NOTIFICATIONS_MUTATIONS.SET_LATEST](state: NotificationsState, notificationsState: NotificationsState): void {
|
||||
state.latestNotifications = notificationsState.notifications;
|
||||
},
|
||||
[NOTIFICATIONS_MUTATIONS.MARK_AS_READ](state: NotificationsState, id: string): void {
|
||||
state.notifications = state.notifications.map((notification: Notification) => {
|
||||
state.notifications = state.notifications.map((notification: UINotification) => {
|
||||
if (notification.id === id) {
|
||||
notification.markAsRead();
|
||||
}
|
||||
@ -48,7 +44,7 @@ export function makeNotificationsModule(api: NotificationsApi) {
|
||||
});
|
||||
},
|
||||
[NOTIFICATIONS_MUTATIONS.READ_ALL](state: NotificationsState): void {
|
||||
state.notifications = state.notifications.map((notification: Notification) => {
|
||||
state.notifications = state.notifications.map((notification: UINotification) => {
|
||||
notification.markAsRead();
|
||||
|
||||
return notification;
|
||||
@ -58,24 +54,26 @@ export function makeNotificationsModule(api: NotificationsApi) {
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
[NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS]: async function ({commit}: any, cursor: NotificationsCursor): Promise<NotificationsState> {
|
||||
const notificationsResponse = await api.get(cursor);
|
||||
[NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS]: async function ({commit}: any, pageIndex: number): Promise<void> {
|
||||
const notificationsResponse = await service.notifications(pageIndex);
|
||||
|
||||
commit(NOTIFICATIONS_MUTATIONS.SET_NOTIFICATIONS, notificationsResponse);
|
||||
const notifications = notificationsResponse.page.notifications.map(notification => new UINotification(notification));
|
||||
|
||||
if (cursor.page === 1) {
|
||||
commit(NOTIFICATIONS_MUTATIONS.SET_LATEST, notificationsResponse);
|
||||
const notificationState = new NotificationsState(notifications, notificationsResponse.page.pageCount, notificationsResponse.unreadCount);
|
||||
|
||||
commit(NOTIFICATIONS_MUTATIONS.SET_NOTIFICATIONS, notificationState);
|
||||
|
||||
if (pageIndex === 1) {
|
||||
commit(NOTIFICATIONS_MUTATIONS.SET_LATEST, notificationState);
|
||||
}
|
||||
|
||||
return notificationsResponse;
|
||||
},
|
||||
[NOTIFICATIONS_ACTIONS.MARK_AS_READ]: async function ({commit}: any, id: string): Promise<any> {
|
||||
await api.read(id);
|
||||
[NOTIFICATIONS_ACTIONS.MARK_AS_READ]: async function ({commit}: any, id: string): Promise<void> {
|
||||
await service.readSingeNotification(id);
|
||||
|
||||
commit(NOTIFICATIONS_MUTATIONS.MARK_AS_READ, id);
|
||||
},
|
||||
[NOTIFICATIONS_ACTIONS.READ_ALL]: async function ({commit}: any): Promise<any> {
|
||||
await api.readAll();
|
||||
[NOTIFICATIONS_ACTIONS.READ_ALL]: async function ({commit}: any): Promise<void> {
|
||||
await service.readAllNotifications();
|
||||
|
||||
commit(NOTIFICATIONS_MUTATIONS.READ_ALL);
|
||||
},
|
||||
|
@ -2,23 +2,39 @@
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { NotificationIcon } from '@/app/utils/notificationIcons';
|
||||
import { Notification, NotificationTypes } from '@/storagenode/notifications/notifications';
|
||||
|
||||
/**
|
||||
* Holds all notifications module state.
|
||||
*/
|
||||
export class NotificationsState {
|
||||
public latestNotifications: UINotification[] = [];
|
||||
|
||||
public constructor(
|
||||
public notifications: UINotification[] = [],
|
||||
public pageCount: number = 0,
|
||||
public unreadCount: number = 0,
|
||||
) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes notification entity.
|
||||
*/
|
||||
export class Notification {
|
||||
export class UINotification {
|
||||
public icon: NotificationIcon;
|
||||
public isRead: boolean;
|
||||
public id: string;
|
||||
public senderId: string;
|
||||
public type: NotificationTypes;
|
||||
public title: string;
|
||||
public message: string;
|
||||
public readAt: Date | null;
|
||||
public createdAt: Date;
|
||||
|
||||
public constructor(
|
||||
public id: string = '',
|
||||
public senderId: string = '',
|
||||
public type: NotificationTypes = NotificationTypes.Custom,
|
||||
public title: string = '',
|
||||
public message: string = '',
|
||||
public isRead: boolean = false,
|
||||
public createdAt: Date = new Date(),
|
||||
) {
|
||||
public constructor(notification: Partial<UINotification> = new Notification()) {
|
||||
Object.assign(this, notification);
|
||||
this.setIcon();
|
||||
this.isRead = !!this.readAt;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,60 +86,3 @@ export class Notification {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes all current notifications types.
|
||||
*/
|
||||
export enum NotificationTypes {
|
||||
Custom = 0,
|
||||
AuditCheckFailure = 1,
|
||||
UptimeCheckFailure = 2,
|
||||
Disqualification = 3,
|
||||
Suspension = 4,
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes page offset for pagination.
|
||||
*/
|
||||
export class NotificationsCursor {
|
||||
public constructor(
|
||||
public page: number = 0,
|
||||
public limit: number = 7,
|
||||
) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds all notifications module state.
|
||||
*/
|
||||
export class NotificationsState {
|
||||
public latestNotifications: Notification[] = [];
|
||||
|
||||
public constructor(
|
||||
public notifications: Notification[] = [],
|
||||
public pageCount: number = 0,
|
||||
public unreadCount: number = 0,
|
||||
) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes all notifications-related functionality.
|
||||
*/
|
||||
export interface NotificationsApi {
|
||||
/**
|
||||
* Fetches notifications.
|
||||
* @throws Error
|
||||
*/
|
||||
get(cursor: NotificationsCursor): Promise<NotificationsState>;
|
||||
|
||||
/**
|
||||
* Marks single notification as read.
|
||||
* @throws Error
|
||||
*/
|
||||
read(id: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Marks all notification as read.
|
||||
* @throws Error
|
||||
*/
|
||||
readAll(): Promise<void>;
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import { APPSTATE_ACTIONS } from '@/app/store/modules/appState';
|
||||
import { NODE_ACTIONS } from '@/app/store/modules/node';
|
||||
import { NOTIFICATIONS_ACTIONS } from '@/app/store/modules/notifications';
|
||||
import { PAYOUT_ACTIONS } from '@/app/store/modules/payout';
|
||||
import { NotificationsCursor } from '@/app/types/notifications';
|
||||
|
||||
@Component ({
|
||||
components: {
|
||||
@ -43,7 +42,7 @@ export default class Dashboard extends Vue {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.$store.dispatch(NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS, new NotificationsCursor(1));
|
||||
await this.$store.dispatch(NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS, 1);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ import VPagination from '@/app/components/VPagination.vue';
|
||||
import BackArrowIcon from '@/../static/images/notifications/backArrow.svg';
|
||||
|
||||
import { NOTIFICATIONS_ACTIONS } from '@/app/store/modules/notifications';
|
||||
import { Notification, NotificationsCursor } from '@/app/types/notifications';
|
||||
import { UINotification } from '@/app/types/notifications';
|
||||
|
||||
@Component ({
|
||||
components: {
|
||||
@ -67,7 +67,7 @@ export default class NotificationsArea extends Vue {
|
||||
/**
|
||||
* Returns notification of current page.
|
||||
*/
|
||||
public get notifications(): Notification[] {
|
||||
public get notifications(): UINotification[] {
|
||||
return this.$store.state.notificationsModule.notifications;
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ export default class NotificationsArea extends Vue {
|
||||
*/
|
||||
public async onPageClick(index: number): Promise<void> {
|
||||
try {
|
||||
await this.$store.dispatch(NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS, new NotificationsCursor(index));
|
||||
await this.$store.dispatch(NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS, index);
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
|
@ -57,7 +57,6 @@ import { APPSTATE_ACTIONS } from '@/app/store/modules/appState';
|
||||
import { NODE_ACTIONS } from '@/app/store/modules/node';
|
||||
import { NOTIFICATIONS_ACTIONS } from '@/app/store/modules/notifications';
|
||||
import { PAYOUT_ACTIONS } from '@/app/store/modules/payout';
|
||||
import { NotificationsCursor } from '@/app/types/notifications';
|
||||
import { PayoutPeriod, TotalHeldAndPaid } from '@/storagenode/payouts/payouts';
|
||||
|
||||
@Component ({
|
||||
@ -88,7 +87,7 @@ export default class PayoutArea extends Vue {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.$store.dispatch(NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS, new NotificationsCursor(1));
|
||||
await this.$store.dispatch(NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS, 1);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { Notification, NotificationsApi, NotificationsCursor, NotificationsState } from '@/app/types/notifications';
|
||||
import {
|
||||
NotificationsApi,
|
||||
NotificationsCursor,
|
||||
NotificationsPage,
|
||||
NotificationsResponse,
|
||||
} from '@/storagenode/notifications/notifications';
|
||||
import { HttpClient } from '@/storagenode/utils/httpClient';
|
||||
|
||||
/**
|
||||
@ -15,10 +20,10 @@ export class NotificationsHttpApi implements NotificationsApi {
|
||||
/**
|
||||
* Fetch notifications.
|
||||
*
|
||||
* @returns notifications state
|
||||
* @returns notifications response.
|
||||
* @throws Error
|
||||
*/
|
||||
public async get(cursor: NotificationsCursor): Promise<NotificationsState> {
|
||||
public async get(cursor: NotificationsCursor): Promise<NotificationsResponse> {
|
||||
const path = `${this.ROOT_PATH}/list?page=${cursor.page}&limit=${cursor.limit}`;
|
||||
const response = await this.client.get(path);
|
||||
|
||||
@ -27,28 +32,12 @@ export class NotificationsHttpApi implements NotificationsApi {
|
||||
}
|
||||
|
||||
const notificationResponse = await response.json();
|
||||
let notifications: Notification[] = [];
|
||||
let pageCount: number = 0;
|
||||
let unreadCount: number = 0;
|
||||
|
||||
if (notificationResponse) {
|
||||
notifications = notificationResponse.page.notifications.map(item =>
|
||||
new Notification(
|
||||
item.id,
|
||||
item.senderId,
|
||||
item.type,
|
||||
item.title,
|
||||
item.message,
|
||||
!!item.readAt,
|
||||
new Date(item.createdAt),
|
||||
),
|
||||
return new NotificationsResponse(
|
||||
new NotificationsPage(notificationResponse.page.notifications, notificationResponse.page.pageCount),
|
||||
notificationResponse.unreadCount,
|
||||
notificationResponse.totalCount,
|
||||
);
|
||||
|
||||
pageCount = notificationResponse.page.pageCount;
|
||||
unreadCount = notificationResponse.unreadCount;
|
||||
}
|
||||
|
||||
return new NotificationsState(notifications, pageCount, unreadCount);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,88 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
/**
|
||||
* Exposes all notifications-related functionality.
|
||||
*/
|
||||
export interface NotificationsApi {
|
||||
/**
|
||||
* Fetches notifications.
|
||||
* @throws Error
|
||||
*/
|
||||
get(cursor: NotificationsCursor): Promise<NotificationsResponse>;
|
||||
|
||||
/**
|
||||
* Marks single notification as read.
|
||||
* @throws Error
|
||||
*/
|
||||
read(id: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Marks all notification as read.
|
||||
* @throws Error
|
||||
*/
|
||||
readAll(): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes notification entity.
|
||||
*/
|
||||
export class Notification {
|
||||
public constructor(
|
||||
public id: string = '',
|
||||
public senderId: string = '',
|
||||
public type: NotificationTypes = NotificationTypes.Custom,
|
||||
public title: string = '',
|
||||
public message: string = '',
|
||||
public readAt: Date | null = null,
|
||||
public createdAt: Date = new Date(),
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes all current notifications types.
|
||||
*/
|
||||
export enum NotificationTypes {
|
||||
Custom = 0,
|
||||
AuditCheckFailure = 1,
|
||||
UptimeCheckFailure = 2,
|
||||
Disqualification = 3,
|
||||
Suspension = 4,
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes page offset for pagination.
|
||||
*/
|
||||
export class NotificationsCursor {
|
||||
private DEFAULT_LIMIT: number = 7;
|
||||
|
||||
public constructor(
|
||||
public page: number = 0,
|
||||
public limit: number = 0,
|
||||
) {
|
||||
if (!this.limit) {
|
||||
this.limit = this.DEFAULT_LIMIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes response object from server.
|
||||
*/
|
||||
export class NotificationsResponse {
|
||||
public constructor(
|
||||
public page: NotificationsPage = new NotificationsPage(),
|
||||
public unreadCount: number = 0,
|
||||
public totalCount: number = 0,
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes page related notification information.
|
||||
*/
|
||||
export class NotificationsPage {
|
||||
public constructor(
|
||||
public notifications: Notification[] = [],
|
||||
public pageCount: number = 0,
|
||||
) {}
|
||||
}
|
43
web/storagenode/src/storagenode/notifications/service.ts
Normal file
43
web/storagenode/src/storagenode/notifications/service.ts
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { NotificationsApi, NotificationsCursor, NotificationsResponse } from '@/storagenode/notifications/notifications';
|
||||
|
||||
/**
|
||||
* PayoutService is used to store and handle node paystub information.
|
||||
* PayoutService exposes a business logic related to payouts.
|
||||
*/
|
||||
export class NotificationsService {
|
||||
private readonly api: NotificationsApi;
|
||||
|
||||
public constructor(api: NotificationsApi) {
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch notifications.
|
||||
*
|
||||
* @returns notifications response.
|
||||
* @throws Error
|
||||
*/
|
||||
public async notifications(index: number, limit?: number): Promise<NotificationsResponse> {
|
||||
const cursor = new NotificationsCursor(index, limit);
|
||||
|
||||
return await this.api.get(cursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks single notification as read on server.
|
||||
* @param id
|
||||
*/
|
||||
public async readSingeNotification(id: string): Promise<void> {
|
||||
await this.api.read(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks all notifications as read on server.
|
||||
*/
|
||||
public async readAllNotifications(): Promise<void> {
|
||||
await this.api.readAll();
|
||||
}
|
||||
}
|
147
web/storagenode/tests/unit/store/notifications.spec.ts
Normal file
147
web/storagenode/tests/unit/store/notifications.spec.ts
Normal file
@ -0,0 +1,147 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import Vuex from 'vuex';
|
||||
|
||||
import { newNotificationsModule, NOTIFICATIONS_ACTIONS, NOTIFICATIONS_MUTATIONS } from '@/app/store/modules/notifications';
|
||||
import { NotificationsState, UINotification } from '@/app/types/notifications';
|
||||
import { NotificationsHttpApi } from '@/storagenode/api/notifications';
|
||||
import {
|
||||
Notification,
|
||||
NotificationsPage,
|
||||
NotificationsResponse,
|
||||
NotificationTypes,
|
||||
} from '@/storagenode/notifications/notifications';
|
||||
import { NotificationsService } from '@/storagenode/notifications/service';
|
||||
import { createLocalVue } from '@vue/test-utils';
|
||||
|
||||
const Vue = createLocalVue();
|
||||
|
||||
const notificationsApi = new NotificationsHttpApi();
|
||||
const notificationsService = new NotificationsService(notificationsApi);
|
||||
|
||||
const notificationsModule = newNotificationsModule(notificationsService);
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
const store = new Vuex.Store({ modules: { notificationsModule } });
|
||||
|
||||
const state = store.state as any;
|
||||
|
||||
let notifications;
|
||||
|
||||
describe('mutations', () => {
|
||||
beforeEach(() => {
|
||||
createLocalVue().use(Vuex);
|
||||
notifications = [
|
||||
new UINotification(new Notification('1', '1', NotificationTypes.Disqualification, 'title1', 'message1', null)),
|
||||
new UINotification(new Notification('2', '1', NotificationTypes.UptimeCheckFailure, 'title2', 'message2', null)),
|
||||
];
|
||||
});
|
||||
|
||||
it('sets notification state', (): void => {
|
||||
const notificationsState = new NotificationsState(notifications, 2, 1);
|
||||
|
||||
store.commit(NOTIFICATIONS_MUTATIONS.SET_NOTIFICATIONS, notificationsState);
|
||||
|
||||
expect(state.notificationsModule.notifications.length).toBe(notifications.length);
|
||||
expect(state.notificationsModule.pageCount).toBe(2);
|
||||
expect(state.notificationsModule.unreadCount).toBe(1);
|
||||
|
||||
store.commit(NOTIFICATIONS_MUTATIONS.SET_LATEST, notificationsState);
|
||||
|
||||
expect(state.notificationsModule.latestNotifications.length).toBe(notifications.length);
|
||||
});
|
||||
|
||||
it('sets single notification as read', (): void => {
|
||||
store.commit(NOTIFICATIONS_MUTATIONS.MARK_AS_READ, '1');
|
||||
|
||||
const unreadNotificationsCount = state.notificationsModule.notifications.filter(e => !e.isRead).length;
|
||||
|
||||
expect(unreadNotificationsCount).toBe(1);
|
||||
});
|
||||
|
||||
it('sets all notification as read', (): void => {
|
||||
store.commit(NOTIFICATIONS_MUTATIONS.READ_ALL);
|
||||
|
||||
const unreadNotificationsCount = state.notificationsModule.notifications.filter(e => !e.isRead).length;
|
||||
|
||||
expect(unreadNotificationsCount).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
notifications = [
|
||||
new UINotification(new Notification('1', '1', NotificationTypes.Disqualification, 'title1', 'message1', null)),
|
||||
new UINotification(new Notification('2', '1', NotificationTypes.UptimeCheckFailure, 'title2', 'message2', null)),
|
||||
];
|
||||
});
|
||||
|
||||
it('throws error on failed notifications fetch', async (): Promise<void> => {
|
||||
jest.spyOn(notificationsApi, 'get').mockImplementation(() => { throw new Error(); });
|
||||
|
||||
try {
|
||||
await store.dispatch(NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS, 1);
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
expect(state.notificationsModule.latestNotifications.length).toBe(notifications.length);
|
||||
}
|
||||
});
|
||||
|
||||
it('success fetches notifications', async (): Promise<void> => {
|
||||
jest.spyOn(notificationsService, 'notifications')
|
||||
.mockReturnValue(Promise.resolve(new NotificationsResponse(new NotificationsPage(notifications, 1), 2, 1)));
|
||||
|
||||
await store.dispatch(NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS, 1);
|
||||
|
||||
expect(state.notificationsModule.latestNotifications.length).toBe(notifications.length);
|
||||
});
|
||||
|
||||
it('throws error on failed single notification read', async (): Promise<void> => {
|
||||
jest.spyOn(notificationsApi, 'read').mockImplementation(() => { throw new Error(); });
|
||||
|
||||
try {
|
||||
await store.dispatch(NOTIFICATIONS_ACTIONS.MARK_AS_READ, '1');
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
const unreadNotificationsCount = state.notificationsModule.notifications.filter(e => !e.isRead).length;
|
||||
|
||||
expect(unreadNotificationsCount).toBe(notifications.length);
|
||||
}
|
||||
});
|
||||
|
||||
it('success marks single notification as read', async (): Promise<void> => {
|
||||
jest.spyOn(notificationsService, 'notifications')
|
||||
.mockReturnValue(Promise.resolve(new NotificationsResponse(new NotificationsPage(notifications, 1), 2, 1)));
|
||||
|
||||
await store.dispatch(NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS, 1);
|
||||
await store.dispatch(NOTIFICATIONS_ACTIONS.MARK_AS_READ, '1');
|
||||
|
||||
const unreadNotificationsCount = state.notificationsModule.notifications.filter(e => !e.isRead).length;
|
||||
|
||||
expect(unreadNotificationsCount).toBe(1);
|
||||
});
|
||||
|
||||
it('throws error on failed all notifications read', async (): Promise<void> => {
|
||||
jest.spyOn(notificationsApi, 'readAll').mockImplementation(() => { throw new Error(); });
|
||||
|
||||
try {
|
||||
await store.dispatch(NOTIFICATIONS_ACTIONS.READ_ALL);
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
const unreadNotificationsCount = state.notificationsModule.notifications.filter(e => !e.isRead).length;
|
||||
|
||||
expect(unreadNotificationsCount).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('success marks all notifications as read', async (): Promise<void> => {
|
||||
await store.dispatch(NOTIFICATIONS_ACTIONS.READ_ALL);
|
||||
|
||||
const unreadNotificationsCount = state.notificationsModule.notifications.filter(e => !e.isRead).length;
|
||||
|
||||
expect(unreadNotificationsCount).toBe(0);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user