Skip to content

Commit

Permalink
feat(payout_link): return total_count in filtered payouts list API re…
Browse files Browse the repository at this point in the history
…sponse (#5538)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
  • Loading branch information
kashif-m and hyperswitch-bot[bot] authored Aug 14, 2024
1 parent 92a07cf commit 34f648e
Show file tree
Hide file tree
Showing 11 changed files with 349 additions and 37 deletions.
9 changes: 8 additions & 1 deletion api-reference-v2/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -14253,7 +14253,14 @@
"type": "array",
"items": {
"$ref": "#/components/schemas/PayoutCreateResponse"
}
},
"description": "The list of payouts response objects"
},
"total_count": {
"type": "integer",
"format": "int64",
"description": "The total number of available payouts for given constraints",
"nullable": true
}
}
},
Expand Down
9 changes: 8 additions & 1 deletion api-reference/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -19129,7 +19129,14 @@
"type": "array",
"items": {
"$ref": "#/components/schemas/PayoutCreateResponse"
}
},
"description": "The list of payouts response objects"
},
"total_count": {
"type": "integer",
"format": "int64",
"description": "The total number of available payouts for given constraints",
"nullable": true
}
}
},
Expand Down
5 changes: 4 additions & 1 deletion crates/api_models/src/payouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -715,8 +715,11 @@ pub struct PayoutListFilterConstraints {
pub struct PayoutListResponse {
/// The number of payouts included in the list
pub size: usize,
// The list of payouts response objects
/// The list of payouts response objects
pub data: Vec<PayoutCreateResponse>,
/// The total number of available payouts for given constraints
#[serde(skip_serializing_if = "Option::is_none")]
pub total_count: Option<i64>,
}

#[derive(Clone, Debug, serde::Serialize, ToSchema)]
Expand Down
21 changes: 16 additions & 5 deletions crates/diesel_models/src/query/payout_attempt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ impl PayoutAttempt {
Vec<enums::PayoutStatus>,
Vec<enums::PayoutType>,
)> {
let active_attempts: Vec<String> = payouts
let active_attempt_ids = payouts
.iter()
.map(|payout| {
format!(
Expand All @@ -156,11 +156,20 @@ impl PayoutAttempt {
payout.attempt_count.clone()
)
})
.collect();
.collect::<Vec<String>>();

let active_payout_ids = payouts
.iter()
.map(|payout| payout.payout_id.clone())
.collect::<Vec<String>>();

let filter = <Self as HasTable>::table()
.filter(dsl::merchant_id.eq(merchant_id.to_owned()))
.filter(dsl::payout_attempt_id.eq_any(active_attempts));
.filter(dsl::payout_attempt_id.eq_any(active_attempt_ids));

let payouts_filter = <Payouts as HasTable>::table()
.filter(payout_dsl::merchant_id.eq(merchant_id.to_owned()))
.filter(payout_dsl::payout_id.eq_any(active_payout_ids));

let payout_status: Vec<enums::PayoutStatus> = payouts
.iter()
Expand All @@ -181,7 +190,8 @@ impl PayoutAttempt {
.flatten()
.collect::<Vec<String>>();

let filter_currency = <Payouts as HasTable>::table()
let filter_currency = payouts_filter
.clone()
.select(payout_dsl::destination_currency)
.distinct()
.get_results_async::<enums::Currency>(conn)
Expand All @@ -191,7 +201,8 @@ impl PayoutAttempt {
.into_iter()
.collect::<Vec<enums::Currency>>();

let filter_payout_method = Payouts::table()
let filter_payout_method = payouts_filter
.clone()
.select(payout_dsl::payout_type)
.distinct()
.get_results_async::<Option<enums::PayoutType>>(conn)
Expand Down
52 changes: 48 additions & 4 deletions crates/diesel_models/src/query/payouts.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};
use error_stack::report;
use async_bb8_diesel::AsyncRunQueryDsl;
use diesel::{
associations::HasTable, debug_query, pg::Pg, BoolExpressionMethods, ExpressionMethods,
JoinOnDsl, QueryDsl,
};
use error_stack::{report, ResultExt};

use super::generics;
use crate::{
errors,
enums, errors,
payouts::{Payouts, PayoutsNew, PayoutsUpdate, PayoutsUpdateInternal},
schema::payouts::dsl,
query::generics::db_metrics,
schema::{payout_attempt, payouts::dsl},
PgPooledConn, StorageResult,
};

Expand Down Expand Up @@ -87,4 +92,43 @@ impl Payouts {
)
.await
}

pub async fn get_total_count_of_payouts(
conn: &PgPooledConn,
merchant_id: &common_utils::id_type::MerchantId,
active_payout_ids: &[String],
connector: Option<Vec<String>>,
currency: Option<Vec<enums::Currency>>,
status: Option<Vec<enums::PayoutStatus>>,
payout_type: Option<Vec<enums::PayoutType>>,
) -> StorageResult<i64> {
let mut filter = <Self as HasTable>::table()
.inner_join(payout_attempt::table.on(payout_attempt::dsl::payout_id.eq(dsl::payout_id)))
.count()
.filter(dsl::merchant_id.eq(merchant_id.to_owned()))
.filter(dsl::payout_id.eq_any(active_payout_ids.to_owned()))
.into_boxed();

if let Some(connector) = connector {
filter = filter.filter(payout_attempt::dsl::connector.eq_any(connector));
}
if let Some(currency) = currency {
filter = filter.filter(dsl::destination_currency.eq_any(currency));
}
if let Some(status) = status {
filter = filter.filter(dsl::status.eq_any(status));
}
if let Some(payout_type) = payout_type {
filter = filter.filter(dsl::payout_type.eq_any(payout_type));
}
router_env::logger::debug!(query = %debug_query::<Pg, _>(&filter).to_string());

db_metrics::track_database_call::<<Self as HasTable>::Table, _, _>(
filter.get_result_async::<i64>(conn),
db_metrics::DatabaseOperation::Filter,
)
.await
.change_context(errors::DatabaseError::Others)
.attach_printable("Error filtering count of payouts")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ pub trait PayoutAttemptInterface {

async fn get_filters_for_payouts(
&self,
payout: &[Payouts],
merchant_id: &id_type::MerchantId,
storage_scheme: MerchantStorageScheme,
_payout: &[Payouts],
_merchant_id: &id_type::MerchantId,
_storage_scheme: MerchantStorageScheme,
) -> error_stack::Result<PayoutListFilters, errors::StorageError>;
}

Expand Down
25 changes: 22 additions & 3 deletions crates/hyperswitch_domain_models/src/payouts/payouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,29 @@ pub trait PayoutsInterface {
#[cfg(feature = "olap")]
async fn filter_payouts_by_time_range_constraints(
&self,
merchant_id: &id_type::MerchantId,
time_range: &api_models::payments::TimeRange,
storage_scheme: MerchantStorageScheme,
_merchant_id: &id_type::MerchantId,
_time_range: &api_models::payments::TimeRange,
_storage_scheme: MerchantStorageScheme,
) -> error_stack::Result<Vec<Payouts>, errors::StorageError>;

#[cfg(feature = "olap")]
#[allow(clippy::too_many_arguments)]
async fn get_total_count_of_filtered_payouts(
&self,
_merchant_id: &id_type::MerchantId,
_active_payout_ids: &[String],
_connector: Option<Vec<api_models::enums::PayoutConnectors>>,
_currency: Option<Vec<storage_enums::Currency>>,
_status: Option<Vec<storage_enums::PayoutStatus>>,
_payout_method: Option<Vec<storage_enums::PayoutType>>,
) -> error_stack::Result<i64, errors::StorageError>;

#[cfg(feature = "olap")]
async fn filter_active_payout_ids_by_constraints(
&self,
_merchant_id: &id_type::MerchantId,
_constraints: &PayoutFetchConstraints,
) -> error_stack::Result<Vec<String>, errors::StorageError>;
}

#[derive(Clone, Debug, Eq, PartialEq)]
Expand Down
29 changes: 28 additions & 1 deletion crates/router/src/core/payouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,7 @@ pub async fn payouts_list_core(
api::PayoutListResponse {
size: data.len(),
data,
total_count: None,
},
))
}
Expand All @@ -877,14 +878,15 @@ pub async fn payouts_filtered_list_core(
let limit = &filters.limit;
validator::validate_payout_list_request_for_joins(*limit)?;
let db = state.store.as_ref();
let constraints = filters.clone().into();
let list: Vec<(
storage::Payouts,
storage::PayoutAttempt,
Option<diesel_models::Customer>,
)> = db
.filter_payouts_and_attempts(
merchant_account.get_id(),
&filters.clone().into(),
&constraints,
merchant_account.storage_scheme,
)
.await
Expand Down Expand Up @@ -915,10 +917,35 @@ pub async fn payouts_filtered_list_core(
.map(ForeignFrom::foreign_from)
.collect();

let active_payout_ids = db
.filter_active_payout_ids_by_constraints(merchant_account.get_id(), &constraints)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to filter active payout ids based on the constraints")?;

let total_count = db
.get_total_count_of_filtered_payouts(
merchant_account.get_id(),
&active_payout_ids,
filters.connector.clone(),
filters.currency.clone(),
filters.status.clone(),
filters.payout_method.clone(),
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable_lazy(|| {
format!(
"Failed to fetch total count of filtered payouts for the given constraints - {:?}",
filters
)
})?;

Ok(services::ApplicationResponse::Json(
api::PayoutListResponse {
size: data.len(),
data,
total_count: Some(total_count),
},
))
}
Expand Down
33 changes: 33 additions & 0 deletions crates/router/src/db/kafka_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1975,6 +1975,39 @@ impl PayoutsInterface for KafkaStore {
.filter_payouts_by_time_range_constraints(merchant_id, time_range, storage_scheme)
.await
}

#[cfg(feature = "olap")]
async fn get_total_count_of_filtered_payouts(
&self,
merchant_id: &id_type::MerchantId,
active_payout_ids: &[String],
connector: Option<Vec<api_models::enums::PayoutConnectors>>,
currency: Option<Vec<enums::Currency>>,
status: Option<Vec<enums::PayoutStatus>>,
payout_method: Option<Vec<enums::PayoutType>>,
) -> CustomResult<i64, errors::DataStorageError> {
self.diesel_store
.get_total_count_of_filtered_payouts(
merchant_id,
active_payout_ids,
connector,
currency,
status,
payout_method,
)
.await
}

#[cfg(feature = "olap")]
async fn filter_active_payout_ids_by_constraints(
&self,
merchant_id: &id_type::MerchantId,
constraints: &hyperswitch_domain_models::payouts::PayoutFetchConstraints,
) -> CustomResult<Vec<String>, errors::DataStorageError> {
self.diesel_store
.filter_active_payout_ids_by_constraints(merchant_id, constraints)
.await
}
}

#[async_trait::async_trait]
Expand Down
24 changes: 24 additions & 0 deletions crates/storage_impl/src/mock_db/payouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,28 @@ impl PayoutsInterface for MockDb {
// TODO: Implement function for `MockDb`
Err(StorageError::MockDbError)?
}

#[cfg(feature = "olap")]
async fn get_total_count_of_filtered_payouts(
&self,
_merchant_id: &common_utils::id_type::MerchantId,
_active_payout_ids: &[String],
_connector: Option<Vec<api_models::enums::PayoutConnectors>>,
_currency: Option<Vec<storage_enums::Currency>>,
_status: Option<Vec<storage_enums::PayoutStatus>>,
_payout_method: Option<Vec<storage_enums::PayoutType>>,
) -> CustomResult<i64, StorageError> {
// TODO: Implement function for `MockDb`
Err(StorageError::MockDbError)?
}

#[cfg(feature = "olap")]
async fn filter_active_payout_ids_by_constraints(
&self,
_merchant_id: &common_utils::id_type::MerchantId,
_constraints: &hyperswitch_domain_models::payouts::PayoutFetchConstraints,
) -> CustomResult<Vec<String>, StorageError> {
// TODO: Implement function for `MockDb`
Err(StorageError::MockDbError)?
}
}
Loading

0 comments on commit 34f648e

Please sign in to comment.