Skip to content
This repository has been archived by the owner on Mar 30, 2021. It is now read-only.

Implement basic notification system #43

Merged
merged 9 commits into from
Jul 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ docker-compose run node yarn test:unit
docker-compose run node yarn lint
```

### Show build log
```
docker-compose logs -f node
```

## Contributing

You are welcome to contribute to SoftDocLinker if you have improvements or bug fixes.
Expand Down
9 changes: 8 additions & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<template>
<v-app id="softdoclinker-app">
<!-- Notification management -->
<notifications-component />

<!-- Drawer for documentation view -->
<v-navigation-drawer
:clipped="$vuetify.breakpoint.lgAndUp"
v-model="drawer"
Expand All @@ -10,6 +14,7 @@

<navigation-component :drawer="drawer" @toggleDrawer="toggleDrawer" />

<!-- Content area for loaded documentation -->
<v-content>
<v-container class="fill-height" fluid>
<v-row align="center" justify="center" />
Expand All @@ -22,6 +27,7 @@
<script lang="ts">
import NavigationComponent from "@/components/NavigationComponent.vue";
import RefreshDataComponent from "@/components/RefreshDataComponent.vue";
import NotificationsComponent from "@/components/NotificationsComponent.vue";
import defaultSharedState from "@/model/defaultSharedState";
import SharedStateInterface from "@/model/SharedStateInterface";
import StateManagementInterface from "@/model/StateManagementInterface";
Expand All @@ -31,7 +37,8 @@ export default Vue.extend({
name: "App",
components: {
NavigationComponent,
RefreshDataComponent
RefreshDataComponent,
NotificationsComponent
},
data: function() {
return {
Expand Down
25 changes: 24 additions & 1 deletion src/SoftDocLinker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import DocCollectionInterface from "@/model/doc/DocCollectionInterface";
import StateManagementFactory from "@/model/StateManagementFactory";
import StateManagementInterface from "@/model/StateManagementInterface";
import SoftDocLinkerInterface from "@/SoftDocLinkerInterface";
import NotificationManagementInterface from "@/service/notification/NotificationManagementInterface";
import NotificationManagementFactory from "@/service/notification/NotificationManagementFactory";
import Vue from "vue";

/**
Expand Down Expand Up @@ -77,6 +79,11 @@ export class SoftDocLinker implements SoftDocLinkerInterface {
*/
protected _stateManagement?: StateManagementInterface;

/**
* Manager for notifications that are currently displayed
*/
protected _notificationManagement: NotificationManagementInterface;

/**
* Constructor
*
Expand All @@ -93,7 +100,8 @@ export class SoftDocLinker implements SoftDocLinkerInterface {
cacheManagementFactory: CacheManagementFactory = new CacheManagementFactory(),
docCollectionDataProviderFactory: DocCollectionDataProviderFactory = new DocCollectionDataProviderFactory(),
docCollectionDataRepositoryFactory: DocCollectionDataRepositoryFactory = new DocCollectionDataRepositoryFactory(),
stateManagementFactory: StateManagementFactory = new StateManagementFactory()
stateManagementFactory: StateManagementFactory = new StateManagementFactory(),
notificationManagement: NotificationManagementInterface = new NotificationManagementFactory().create()
) {
this._configDataProviderFactory = configDataProviderFactory;
this._configDataRepositoryFactory = configDataRepositoryFactory;
Expand All @@ -102,6 +110,7 @@ export class SoftDocLinker implements SoftDocLinkerInterface {
this._docCollectionDataProviderFactory = docCollectionDataProviderFactory;
this._docCollectionDataRepositoryFactory = docCollectionDataRepositoryFactory;
this._stateManagementFactory = stateManagementFactory;
this._notificationManagement = notificationManagement;
}

/**
Expand Down Expand Up @@ -161,9 +170,23 @@ export class SoftDocLinker implements SoftDocLinkerInterface {
this._stateManagement = this._stateManagementFactory.create(
await this.getConfigDataRepository(),
await this.getDocCollectionDataRepository(),
this.notificationManagement,
Vue.prototype.$sharedState
);

return this._stateManagement;
}

/**
* Getter: _notificationManagement.
*
* Get the notification management instance used to manage the
* the current notifications.
*
* @inheritdoc
*/
/* istanbul ignore next */
public get notificationManagement(): NotificationManagementInterface {
return this._notificationManagement;
}
}
9 changes: 9 additions & 0 deletions src/SoftDocLinkerInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import ConfigDataInterface from "@/model/config/ConfigDataInterface";
import DataRepositoryInterface from "@/model/DataRepositoryInterface";
import DocCollectionInterface from "@/model/doc/DocCollectionInterface";
import StateManagementInterface from "@/model/StateManagementInterface";
import NotificationManagementInterface from "@/service/notification/NotificationManagementInterface";

/**
* Defines the interface between our Vue components and
Expand Down Expand Up @@ -37,4 +38,12 @@ export default interface SoftDocLinkerInterface {
* the sharing of the applications state.
*/
getStateManagement(): Promise<StateManagementInterface>;

/**
* Get the notification management that is currently used by SoftDocLinker to
* handle the shown notifications.
*
* Should be implemented using a get accessor in implementations.
*/
readonly notificationManagement: NotificationManagementInterface;
}
58 changes: 58 additions & 0 deletions src/components/NotificationsComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<template>
<div>
<v-snackbar
v-for="notification in notifications"
:key="notification.id"
:bottom="true"
v-model="notification.show"
>
{{ notification.type }}: {{ notification.message }}
<v-btn
color="blue"
text
@click="submitCloseNotification(notification)"
>
Close
</v-btn>
</v-snackbar>
</div>
</template>

<script lang="ts">
import NotificationInterface from "@/model/notification/NotificationInterface";
import { Component, Vue } from "vue-property-decorator";
/**
* Component that handles notification display by making use of the
* Vuetify Snackbar component
*
* @since 2.0.0
*/
@Component({
name: "NotificationsComponent"
})
export default class NotificationsComponent extends Vue {
/**
* A computed property that provides the notifications array
* that provides all notifications that should be created by this component.
*
* @returns The (reactive) array that contains all visible notifications
*/
get notifications(): NotificationInterface[] {
return this.$softDocLinker.notificationManagement.notifications;
}
/**
* Called when a notification is being closed manually by clicking
* on the close button.
*
* @param notification The notification of which a close was triggered
*/
private submitCloseNotification(notification: NotificationInterface): void {
notification.show = false;
this.$softDocLinker.notificationManagement.removeNotification(
notification
);
}
}
</script>
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import defaultSharedState from "./model/defaultSharedState";

// Set instance of data layer management with initial default values
Vue.prototype.$softDocLinker = new SoftDocLinker();
// Inilize shared state with default value
// Initialize shared state with default value
Vue.prototype.$sharedState = Vue.observable(defaultSharedState());

new Vue({
Expand Down
2 changes: 1 addition & 1 deletion src/model/AbstractDataRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import DataProviderInterface from "@/dataprovider/DataProviderInterface";
import DataRepositoryInterface from "@/model/DataRepositoryInterface";

/**
* Abstract data repository that can be implemented get
* Abstract data repository that can be implemented to get
* data from a DataProviderInterface while using caching
* mechanisms to improve performance.
*
Expand Down
30 changes: 26 additions & 4 deletions src/model/StateManagement.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import ConfigDataInterface from "@/model/config/ConfigDataInterface";
import ConfigDataRepository from "@/model/config/ConfigDataRepository";
import DataRepositoryInterface from "@/model/DataRepositoryInterface";
import defaultSharedState from "@/model/defaultSharedState";
import DocCollectionDataRepository from "@/model/doc/DocCollectionDataRepository";
import DocCollectionInterface from "@/model/doc/DocCollectionInterface";
import NotificationFactory from "@/model/notification/NotificationFactory";
import NotificationType from "@/model/notification/NotificationType";
import SharedStateInterface from "@/model/SharedStateInterface";
import StateManagementInterface from "@/model/StateManagementInterface";
import DocCollectionInterface from "@/model/doc/DocCollectionInterface";
import ConfigDataRepository from "@/model/config/ConfigDataRepository";
import DocCollectionDataRepository from "@/model/doc/DocCollectionDataRepository";
import NotificationManagementInterface from "@/service/notification/NotificationManagementInterface";

/**
* State management that manages the data used by the rendering.
Expand All @@ -27,6 +30,16 @@ export default class StateManagement implements StateManagementInterface {
DocCollectionInterface
>;

/**
* The notification management used to display notifications to the user
*/
protected _notificationManagement: NotificationManagementInterface;

/**
* Factory used to build notifications.
*/
protected _notificationFactory: NotificationFactory;

/**
* The shared state of SoftDocLinker
*/
Expand All @@ -42,10 +55,14 @@ export default class StateManagement implements StateManagementInterface {
constructor(
configDataRepository: DataRepositoryInterface<ConfigDataInterface>,
docDataRepository: DataRepositoryInterface<DocCollectionInterface>,
notificationManagement: NotificationManagementInterface,
notificationFactory: NotificationFactory = new NotificationFactory(),
sharedState: SharedStateInterface = defaultSharedState()
) {
this._configDataRepository = configDataRepository;
this._docDataRepository = docDataRepository;
this._notificationManagement = notificationManagement;
this._notificationFactory = notificationFactory;
this._sharedState = sharedState;
}

Expand All @@ -66,7 +83,12 @@ export default class StateManagement implements StateManagementInterface {
forceRefresh
);
} catch (error) {
throw error;
this._notificationManagement.notify(
this._notificationFactory.create(
"Failed to refresh data!",
NotificationType.ERROR
)
);
} finally {
this._sharedState.loading = false;
}
Expand Down
5 changes: 5 additions & 0 deletions src/model/StateManagementFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,23 @@ import StateManagement from "@/model/StateManagement";
import StateManagementInterface from "@/model/StateManagementInterface";
import SharedStateInterface from "@/model/SharedStateInterface";
import defaultSharedState from "@/model/defaultSharedState";
import NotificationManagementInterface from "@/service/notification/NotificationManagementInterface";
import NotificationFactory from "./notification/NotificationFactory";

export default class StateManagementFactory {
public create(
configDataRepository: DataRepositoryInterface<ConfigDataInterface>,
docCollectionRepository: DataRepositoryInterface<
DocCollectionInterface
>,
notificationManagement: NotificationManagementInterface,
sharedState: SharedStateInterface = defaultSharedState()
): StateManagementInterface {
return new StateManagement(
configDataRepository,
docCollectionRepository,
notificationManagement,
new NotificationFactory(),
sharedState
);
}
Expand Down
25 changes: 25 additions & 0 deletions src/model/notification/NotificationFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* istanbul ignore file */

import NotificationInterface from "./NotificationInterface";

/**
* Factory to create notifications.
*
* @since 2.0.0
*/
export default class NotificationFactory {
/**
* Create a new notification.
*
* @param type The type of the notification to create
* @param message The message of the notification
* @returns A new notification with the given data
*/
public create(type: string, message: string): NotificationInterface {
return {
type: type,
message: message,
show: false
};
}
}
25 changes: 25 additions & 0 deletions src/model/notification/NotificationInterface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* istanbul ignore file */

/**
* Model of the data required to display a notification.
*
* @since 2.0.0
*/
export default interface NotificationInterface {
/**
* The message that is displayed by the notification.
*/
message: string;

/**
* The type the displayed notification should have
*
* @see ./NotificationType
*/
type: string;

/**
* State of this notification should be shown in the frontend.
*/
show: boolean;
}
24 changes: 24 additions & 0 deletions src/model/notification/NotificationType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* istanbul ignore file */

/**
* Notification type that provides different types for notifications
* to categorize them.
*
* @since 2.0.0
*/
export default class NotificationType {
/**
* Notification level: INFO
*/
public static readonly INFO = "INFO";

/**
* Notification level: SUCCESS
*/
public static readonly SUCCESS = "SUCCESS";

/**
* Notification level: ERROR
*/
public static readonly ERROR = "ERROR";
}
Loading