-
Notifications
You must be signed in to change notification settings - Fork 2
Add Support Tickets + Eligibility endpoints #743
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from all commits
61f2b62
4835455
4a98907
d08e206
4549f53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
use wp_derive_request_builder::WpDerivedRequest; | ||
|
||
use crate::{ | ||
request::endpoint::{AsNamespace, DerivedRequest}, | ||
wp_com::{WpComNamespace, support_eligibility::SupportEligibility}, | ||
}; | ||
|
||
#[derive(WpDerivedRequest)] | ||
enum SupportEligibilityRequest { | ||
#[get(url = "/mobile-support/eligibility", output = SupportEligibility)] | ||
GetSupportEligibility, | ||
} | ||
|
||
impl DerivedRequest for SupportEligibilityRequest { | ||
fn namespace() -> impl AsNamespace { | ||
WpComNamespace::V2 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
use crate::{ | ||
request::endpoint::{AsNamespace, DerivedRequest}, | ||
wp_com::{ | ||
WpComNamespace, | ||
support_tickets::{ | ||
AddMessageToSupportConversationParams, ConversationId, CreateSupportTicketParams, | ||
SupportConversation, SupportConversationSummary, | ||
}, | ||
}, | ||
}; | ||
use wp_derive_request_builder::WpDerivedRequest; | ||
|
||
#[derive(WpDerivedRequest)] | ||
enum SupportTicketsRequest { | ||
#[post(url = "/mobile-support/conversations", params = &CreateSupportTicketParams, output = SupportConversation)] | ||
CreateSupportTicket, | ||
#[get(url = "/mobile-support/conversations", output = Vec<SupportConversationSummary>)] | ||
GetSupportConversationList, | ||
#[get(url = "/mobile-support/conversations/<conversation_id>", output = SupportConversation)] | ||
GetSupportConversation, | ||
#[post(url = "/mobile-support/conversations/<conversation_id>", params = &AddMessageToSupportConversationParams, output = SupportConversation)] | ||
AddMessageToSupportConversation, | ||
} | ||
|
||
impl DerivedRequest for SupportTicketsRequest { | ||
fn namespace() -> impl AsNamespace { | ||
WpComNamespace::V2 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
use serde::{Deserialize, Serialize}; | ||
|
||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)] | ||
pub struct SupportEligibility { | ||
pub is_user_eligible: bool, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
use std::collections::HashMap; | ||
|
||
use serde::{Deserialize, Serialize}; | ||
|
||
use crate::{date::WpGmtDateTime, impl_as_query_value_for_new_type}; | ||
|
||
#[derive(Debug, PartialEq, Eq, Serialize, uniffi::Record)] | ||
pub struct CreateSupportTicketParams { | ||
pub subject: String, | ||
pub message: String, | ||
pub application: String, | ||
#[uniffi(default = None)] | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub wpcom_site_id: Option<u64>, | ||
#[uniffi(default = [])] | ||
pub tags: Vec<String>, | ||
#[uniffi(default = [])] | ||
pub attachments: Vec<String>, | ||
} | ||
|
||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)] | ||
pub struct SupportConversationSummary { | ||
pub id: ConversationId, | ||
pub title: String, | ||
pub description: String, | ||
pub status: String, | ||
pub created_at: WpGmtDateTime, | ||
pub updated_at: WpGmtDateTime, | ||
} | ||
|
||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)] | ||
pub struct SupportConversation { | ||
pub id: ConversationId, | ||
pub title: String, | ||
pub description: String, | ||
pub status: String, | ||
pub created_at: WpGmtDateTime, | ||
pub updated_at: WpGmtDateTime, | ||
pub messages: Vec<SupportMessage>, | ||
} | ||
|
||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)] | ||
pub struct SupportMessage { | ||
pub id: u64, | ||
pub content: String, | ||
pub author: SupportMessageAuthor, | ||
pub role: String, | ||
pub author_is_current_user: bool, | ||
pub created_at: WpGmtDateTime, | ||
pub attachments: Vec<SupportAttachment>, | ||
} | ||
|
||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)] | ||
pub struct SupportUserIdentity { | ||
pub id: u64, | ||
pub email: String, | ||
pub display_name: String, | ||
pub avatar_url: String, | ||
} | ||
|
||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)] | ||
pub struct SupportAttachment { | ||
pub id: u64, | ||
pub filename: String, | ||
pub content_type: String, | ||
pub size: u64, | ||
pub url: String, | ||
pub metadata: HashMap<String, AttachmentMetadataValue>, | ||
} | ||
|
||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)] | ||
#[serde(untagged)] | ||
pub enum SupportMessageAuthor { | ||
User(SupportUserIdentity), | ||
SupportAgent(SupportAgentIdentity), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should there be an "other" variant, just in case the "role" value is some other value, like "system" messages? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In order to do this, we'd need to parse it as a json value because we don't know the structure of the type. If we know for sure that there are other types of authors, but we don't know the structure of the type, then I think this makes sense to do. Otherwise, it's probably leave it off and implement it when we need to. |
||
} | ||
|
||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum, strum_macros::Display)] | ||
#[strum(serialize_all = "snake_case")] | ||
pub enum AttachmentMetadataKey { | ||
Width, | ||
Height, | ||
Other(String), | ||
} | ||
|
||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)] | ||
#[serde(untagged)] | ||
pub enum AttachmentMetadataValue { | ||
String(String), | ||
Number(u64), | ||
Boolean(bool), | ||
} | ||
|
||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)] | ||
pub struct SupportAgentIdentity { | ||
pub id: u64, | ||
pub name: String, | ||
} | ||
|
||
#[derive(Debug, PartialEq, Eq, Serialize, uniffi::Record)] | ||
pub struct AddMessageToSupportConversationParams { | ||
pub message: String, | ||
#[uniffi(default = [])] | ||
pub attachments: Vec<String>, | ||
} | ||
|
||
impl_as_query_value_for_new_type!(ConversationId); | ||
uniffi::custom_newtype!(ConversationId, u64); | ||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] | ||
pub struct ConversationId(pub u64); | ||
|
||
impl std::str::FromStr for ConversationId { | ||
type Err = std::num::ParseIntError; | ||
|
||
fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
s.parse().map(Self) | ||
} | ||
} | ||
|
||
impl std::fmt::Display for ConversationId { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "{}", self.0) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_support_conversation_deserialization() { | ||
let json = include_str!("../../tests/wpcom/support_tickets/single-conversation.json"); | ||
let conversation: SupportConversation = | ||
serde_json::from_str(json).expect("Failed to deserialize support conversation"); | ||
assert_eq!(conversation.messages.len(), 7); | ||
} | ||
|
||
#[test] | ||
fn test_support_conversation_list_deserialization() { | ||
let json = include_str!("../../tests/wpcom/support_tickets/conversation-list.json"); | ||
let conversation_list: Vec<SupportConversationSummary> = | ||
serde_json::from_str(json).expect("Failed to deserialize support conversation list"); | ||
assert_eq!(conversation_list.len(), 11); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment how the
author
is parsed id depending on theSupportMessageAuthor
enum variant order: it's a "User" if the JSON can be parsed as an "User"; then it's a "Agent" if the JSON can be parsed as an "Agent". Would it be more correct to parseauthor
based on therole
value, instead of how theSupportMessageAuthor
enum type is declared?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although this would be better, it's also more tricky to do. At least, I couldn't think of an immediate solution to this except for either using a custom parser or separating the
SupportMessage
type asSupportUserMessage
andSupportAgentMessage
. (which doesn't sound like a bad idea 🤔 )Since
SupportUserIdentity
andSupportAgentIdentity
don't have the same fields, I think the way it's implemented is mostly safe. I say mostly, because future changes to these types may make it unsafe as in they might be incorrectly parsed to one type as described in Tony's comment. So, maybe we should separate the message types if we can. (I haven't fully investigated this yet)What do you think @crazytonyli?