Skip to content

Commit

Permalink
Merge pull request #223 from msupply-foundation/203-notification-even…
Browse files Browse the repository at this point in the history
…ts-view

203 notification events view
  • Loading branch information
jmbrunskill authored Nov 9, 2023
2 parents 35456c8 + 55e837b commit 9a67ce1
Show file tree
Hide file tree
Showing 43 changed files with 1,569 additions and 4 deletions.
21 changes: 21 additions & 0 deletions backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/graphql/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ graphql_types = { path = "types" }
graphql_general = { path = "general" }
graphql_notification_config = { path = "notification_config" }
graphql_notification_query = { path = "notification_query" }
graphql_notification_event = { path = "notification_event" }
graphql_user_account = { path = "user_account" }
graphql_recipient = { path = "recipient" }
graphql_recipient_list = { path = "recipient_list" }
Expand Down
13 changes: 12 additions & 1 deletion backend/graphql/core/src/loader/loader_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use async_graphql::dataloader::DataLoader;
use repository::StorageConnectionManager;
use service::service_provider::ServiceProvider;

use super::{user_permission::UserPermissionLoader, AuditLogLoader, RecipientsLoader};
use super::{
user_permission::UserPermissionLoader, AuditLogLoader, NotificationConfigLoader,
RecipientsLoader,
};

pub type LoaderMap = Map<AnyLoader>;
pub type AnyLoader = dyn Any + Send + Sync;
Expand Down Expand Up @@ -61,5 +64,13 @@ pub async fn get_loaders(
);
loaders.insert(audit_log_loader);

let notification_config_loader = DataLoader::new(
NotificationConfigLoader {
connection_manager: connection_manager.clone(),
},
async_std::task::spawn,
);
loaders.insert(notification_config_loader);

loaders
}
2 changes: 2 additions & 0 deletions backend/graphql/core/src/loader/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
mod audit_log;
mod loader_registry;
mod notification_config;
mod recipient;
mod user;
mod user_permission;

pub use audit_log::*;
pub use loader_registry::{get_loaders, LoaderMap, LoaderRegistry};
pub use notification_config::*;
pub use recipient::*;
pub use user::*;
pub use user_permission::*;
35 changes: 35 additions & 0 deletions backend/graphql/core/src/loader/notification_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use repository::{EqualFilter, NotificationConfigFilter, NotificationConfigRepository, Pagination};
use repository::{NotificationConfigRow, StorageConnectionManager};

use async_graphql::dataloader::*;
use async_graphql::*;
use std::collections::HashMap;

pub struct NotificationConfigLoader {
pub connection_manager: StorageConnectionManager,
}

#[async_trait::async_trait]
impl Loader<String> for NotificationConfigLoader {
type Value = NotificationConfigRow;
type Error = async_graphql::Error;

async fn load(
&self,
config_ids: &[String],
) -> Result<HashMap<String, Self::Value>, Self::Error> {
let connection = self.connection_manager.connection()?;
let repo = NotificationConfigRepository::new(&connection);
Ok(repo
.query(
Pagination::all(),
Some(
NotificationConfigFilter::new().id(EqualFilter::equal_any(config_ids.to_vec())),
),
None,
)?
.into_iter()
.map(|config| (config.id.clone(), config))
.collect())
}
}
3 changes: 3 additions & 0 deletions backend/graphql/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use graphql_datasource::DatasourceQueries;
use graphql_general::GeneralQueries;

use graphql_notification_config::{NotificationConfigMutations, NotificationConfigQueries};
use graphql_notification_event::NotificationEventQueries;
use graphql_notification_query::{NotificationQueryMutations, NotificationQueryQueries};
use graphql_recipient::{RecipientMutations, RecipientQueries};
use graphql_recipient_list::{RecipientListMutations, RecipientListQueries};
Expand All @@ -36,6 +37,7 @@ pub struct FullQuery(
pub TelegramQueries,
pub NotificationConfigQueries,
pub NotificationQueryQueries,
pub NotificationEventQueries,
pub DatasourceQueries,
);

Expand All @@ -61,6 +63,7 @@ pub fn full_query() -> FullQuery {
TelegramQueries,
NotificationConfigQueries,
NotificationQueryQueries,
NotificationEventQueries,
DatasourceQueries,
)
}
Expand Down
30 changes: 30 additions & 0 deletions backend/graphql/notification_event/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "graphql_notification_event"
version = "0.1.0"
edition = "2018"

[lib]
path = "src/lib.rs"
doctest = false

[dependencies]

repository = { path = "../../repository" }
service = { path = "../../service" }
util = { path = "../../util" }
graphql_core = { path = "../core" }
graphql_types = { path = "../types" }

actix-web = { version = "4.0.1", default-features = false, features = [
"macros",
] }
async-graphql = { version = "3.0.35", features = ["dataloader", "chrono"] }
async-graphql-actix-web = "3.0.35"
async-trait = "0.1.30"
serde = "1.0.126"
serde_json = "1.0.66"
chrono = { version = "0.4", features = ["serde"] }

[dev-dependencies]
actix-rt = "2.6.0"
assert-json-diff = "2.0.1"
54 changes: 54 additions & 0 deletions backend/graphql/notification_event/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
mod types;
use self::types::*;

use async_graphql::*;
use graphql_core::{
pagination::PaginationInput,
standard_graphql_error::{validate_auth, StandardGraphqlError},
ContextExt,
};

use repository::NotificationEventFilter;
use repository::PaginationOption;
use service::auth::{Resource, ResourceAccessRequest};

#[derive(Default, Clone)]
pub struct NotificationEventQueries;

#[Object]
impl NotificationEventQueries {
pub async fn notification_events(
&self,
ctx: &Context<'_>,
#[graphql(desc = "Pagination option (first and offset)")] page: Option<PaginationInput>,
#[graphql(desc = "Filter option")] filter: Option<NotificationEventFilterInput>,
#[graphql(desc = "Sort options (only first sort input is evaluated for this endpoint)")]
sort: Option<Vec<NotificationEventSortInput>>,
) -> Result<NotificationEventsResponse> {
let user = validate_auth(
ctx,
&ResourceAccessRequest {
resource: Resource::ServerAdmin,
},
)?;

let service_context = ctx.service_context(Some(&user))?;

let configs = service_context
.service_provider
.notification_event_service
.get_notification_events(
&service_context,
page.map(PaginationOption::from),
filter.map(NotificationEventFilter::from),
// Currently only one sort option is supported, use the first from the list.
sort.and_then(|mut sort_list| sort_list.pop())
.map(|sort| sort.to_domain()),
)
.map_err(StandardGraphqlError::from_list_error)?;

Ok(NotificationEventsResponse::Response(
NotificationEventConnector::from_domain(configs),
))
}
}
32 changes: 32 additions & 0 deletions backend/graphql/notification_event/src/types/event_status.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use async_graphql::Enum;
use repository::NotificationEventStatus;
use serde::Serialize;

#[derive(Enum, Copy, Clone, PartialEq, Eq, Debug, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum EventStatus {
Queued,
Sent,
Errored, // Errored will be re-tried
Failed, // Failed will not be re-tried
}

impl EventStatus {
pub fn to_domain(self) -> NotificationEventStatus {
match self {
EventStatus::Queued => NotificationEventStatus::Queued,
EventStatus::Sent => NotificationEventStatus::Sent,
EventStatus::Errored => NotificationEventStatus::Errored,
EventStatus::Failed => NotificationEventStatus::Failed,
}
}

pub fn from_domain(status: &NotificationEventStatus) -> EventStatus {
match status {
NotificationEventStatus::Queued => EventStatus::Queued,
NotificationEventStatus::Sent => EventStatus::Sent,
NotificationEventStatus::Errored => EventStatus::Errored,
NotificationEventStatus::Failed => EventStatus::Failed,
}
}
}
78 changes: 78 additions & 0 deletions backend/graphql/notification_event/src/types/inputs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use async_graphql::{Enum, InputObject};
use graphql_core::{
generic_filters::{DatetimeFilterInput, EqualFilterStringInput},
map_filter,
};
use repository::{
DatetimeFilter, EqualFilter, NotificationEventFilter, NotificationEventSort,
NotificationEventSortField,
};

use super::EventStatus;

#[derive(Enum, Copy, Clone, PartialEq, Eq)]
#[graphql(rename_items = "camelCase")]
pub enum NotificationEventSortFieldInput {
Title,
CreatedAt,
ToAddress,
Message,
NotificationType,
Status,
ErrorMessage,
}

#[derive(InputObject, Clone)]
pub struct EqualFilterEventStatusInput {
pub equal_to: Option<EventStatus>,
pub equal_any: Option<Vec<EventStatus>>,
pub not_equal_to: Option<EventStatus>,
}

#[derive(InputObject)]
pub struct NotificationEventSortInput {
/// Sort query result by `key`
key: NotificationEventSortFieldInput,
/// Sort query result is sorted descending or ascending (if not provided the default is
/// ascending)
desc: Option<bool>,
}
impl NotificationEventSortInput {
pub fn to_domain(self) -> NotificationEventSort {
use NotificationEventSortField as to;
use NotificationEventSortFieldInput as from;
let key = match self.key {
from::Title => to::Title,
from::CreatedAt => to::CreatedAt,
from::ToAddress => to::ToAddress,
from::Message => to::Message,
from::NotificationType => to::NotificationType,
from::Status => to::Status,
from::ErrorMessage => to::ErrorMessage,
};

NotificationEventSort {
key,
desc: self.desc,
}
}
}

#[derive(Clone, InputObject)]
pub struct NotificationEventFilterInput {
pub id: Option<EqualFilterStringInput>,
pub search: Option<String>,
pub status: Option<EqualFilterEventStatusInput>,
pub created_at: Option<DatetimeFilterInput>,
}

impl From<NotificationEventFilterInput> for NotificationEventFilter {
fn from(f: NotificationEventFilterInput) -> Self {
NotificationEventFilter {
id: f.id.map(EqualFilter::from),
search: f.search,
status: f.status.map(|t| map_filter!(t, EventStatus::to_domain)),
created_at: f.created_at.map(DatetimeFilter::from),
}
}
}
6 changes: 6 additions & 0 deletions backend/graphql/notification_event/src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
mod inputs;
pub use inputs::*;
mod event_status;
pub use event_status::*;
mod notification_event;
pub use notification_event::*;
Loading

0 comments on commit 9a67ce1

Please sign in to comment.