Skip to content

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

Open
wants to merge 3 commits into
base: trunk
Choose a base branch
from
Open
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
17 changes: 17 additions & 0 deletions 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"wp_api_integration_tests",
"wp_api_integration_tests_backend",
"wp_cli",
"wp_com_e2e",
"wp_contextual",
"wp_derive",
"wp_derive_request_builder",
Expand Down
4 changes: 4 additions & 0 deletions native/swift/Sources/wordpress-api/Exports.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,7 @@ public typealias CreateBotConversationParams = WordPressAPIInternal.CreateBotCon
public typealias AddMessageToBotConversationParams = WordPressAPIInternal.AddMessageToBotConversationParams
public typealias GetBotConversationParams = WordPressAPIInternal.GetBotConversationParams
public typealias CreateBotConversationFeedbackParams = WordPressAPIInternal.CreateBotConversationFeedbackParams

// MARK: Support Tickets
public typealias CreateSupportTicketParams = WordPressAPIInternal.CreateSupportTicketParams
public typealias AddMessageToSupportConversationParams = WordPressAPIInternal.AddMessageToSupportConversationParams
8 changes: 8 additions & 0 deletions native/swift/Sources/wordpress-api/WPComApiClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,12 @@ public class WPComApiClient {
public var supportBots: SupportBotsRequestExecutor {
internalClient.supportBots()
}

public var supportEligibility: SupportEligibilityRequestExecutor {
internalClient.supportEligibility()
}

public var supportTickets: SupportTicketsRequestExecutor {
internalClient.supportTickets()
}
}
18 changes: 16 additions & 2 deletions wp_api/src/wp_com/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ use super::endpoint::{
oauth2::{Oauth2RequestBuilder, Oauth2RequestExecutor},
subscribers::{SubscribersRequestBuilder, SubscribersRequestExecutor},
support_bots_endpoint::{SupportBotsRequestBuilder, SupportBotsRequestExecutor},
support_eligibility_endpoint::{
SupportEligibilityRequestBuilder, SupportEligibilityRequestExecutor,
},
support_tickets_endpoint::{SupportTicketsRequestBuilder, SupportTicketsRequestExecutor},
};
use crate::{
WpApiClientDelegate, api_client_generate_api_client, api_client_generate_endpoint_impl,
Expand Down Expand Up @@ -33,6 +37,8 @@ pub struct WpComApiRequestBuilder {
oauth2: Arc<Oauth2RequestBuilder>,
subscribers: Arc<SubscribersRequestBuilder>,
support_bots: Arc<SupportBotsRequestBuilder>,
support_eligibility: Arc<SupportEligibilityRequestBuilder>,
support_tickets: Arc<SupportTicketsRequestBuilder>,
}

impl WpComApiRequestBuilder {
Expand All @@ -45,7 +51,9 @@ impl WpComApiRequestBuilder {
jetpack_connection,
oauth2,
subscribers,
support_bots
support_bots,
support_eligibility,
support_tickets
)
}
}
Expand All @@ -70,6 +78,8 @@ pub struct WpComApiClient {
oauth2: Arc<Oauth2RequestExecutor>,
subscribers: Arc<SubscribersRequestExecutor>,
support_bots: Arc<SupportBotsRequestExecutor>,
support_eligibility: Arc<SupportEligibilityRequestExecutor>,
support_tickets: Arc<SupportTicketsRequestExecutor>,
}

impl WpComApiClient {
Expand All @@ -83,11 +93,15 @@ impl WpComApiClient {
jetpack_connection,
oauth2,
subscribers,
support_bots
support_bots,
support_eligibility,
support_tickets
)
}
}
api_client_generate_endpoint_impl!(WpComApi, jetpack_connection);
api_client_generate_endpoint_impl!(WpComApi, oauth2);
api_client_generate_endpoint_impl!(WpComApi, subscribers);
api_client_generate_endpoint_impl!(WpComApi, support_bots);
api_client_generate_endpoint_impl!(WpComApi, support_eligibility);
api_client_generate_endpoint_impl!(WpComApi, support_tickets);
2 changes: 2 additions & 0 deletions wp_api/src/wp_com/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub mod jetpack_connection_endpoint;
pub mod oauth2;
pub mod subscribers;
pub mod support_bots_endpoint;
pub mod support_eligibility_endpoint;
pub mod support_tickets_endpoint;

#[derive(uniffi::Object)]
pub struct WpComDotOrgApiUrlResolver {
Expand Down
18 changes: 18 additions & 0 deletions wp_api/src/wp_com/endpoint/support_eligibility_endpoint.rs
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
}
}
29 changes: 29 additions & 0 deletions wp_api/src/wp_com/endpoint/support_tickets_endpoint.rs
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
}
}
2 changes: 2 additions & 0 deletions wp_api/src/wp_com/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub mod jetpack_connection;
pub mod oauth2;
pub mod subscribers;
pub mod support_bots;
pub mod support_eligibility;
pub mod support_tickets;

impl_as_query_value_for_new_type!(WpComSiteId);
uniffi::custom_newtype!(WpComSiteId, u64);
Expand Down
6 changes: 6 additions & 0 deletions wp_api/src/wp_com/support_eligibility.rs
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,
}
145 changes: 145 additions & 0 deletions wp_api/src/wp_com/support_tickets.rs
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,
Copy link
Contributor

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 the SupportMessageAuthor 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 parse author based on the role value, instead of how the SupportMessageAuthor enum type is declared?

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),
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

}

#[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);
}
}
Loading