From c5c554284ede550bc527fbf11bd834d0276f7b26 Mon Sep 17 00:00:00 2001 From: Sagar naik Date: Mon, 9 Dec 2024 12:39:22 +0530 Subject: [PATCH] feat: smart retry amount analytics (#1872) --- .../PaymentsProcessedUtils.res | 9 - .../NewSmartRetryAnalyticsEntity.res | 33 ++ .../SmartRetryPaymentsProcessed.res | 301 ++++++++++++++++++ .../SmartRetryPaymentsProcessedTypes.res | 14 + .../SmartRetryPaymentsProcessedUtils.res | 240 ++++++++++++++ 5 files changed, 588 insertions(+), 9 deletions(-) create mode 100644 src/screens/NewAnalytics/SmartRetryAnalytics/NewSmartRetryAnalyticsEntity.res create mode 100644 src/screens/NewAnalytics/SmartRetryAnalytics/SmartRetryPaymentsProcessed/SmartRetryPaymentsProcessed.res create mode 100644 src/screens/NewAnalytics/SmartRetryAnalytics/SmartRetryPaymentsProcessed/SmartRetryPaymentsProcessedTypes.res create mode 100644 src/screens/NewAnalytics/SmartRetryAnalytics/SmartRetryPaymentsProcessed/SmartRetryPaymentsProcessedUtils.res diff --git a/src/screens/NewAnalytics/PaymentAnalytics/PaymentsProcessed/PaymentsProcessedUtils.res b/src/screens/NewAnalytics/PaymentAnalytics/PaymentsProcessed/PaymentsProcessedUtils.res index aca1e79c5..881a26ff2 100644 --- a/src/screens/NewAnalytics/PaymentAnalytics/PaymentsProcessed/PaymentsProcessedUtils.res +++ b/src/screens/NewAnalytics/PaymentAnalytics/PaymentsProcessed/PaymentsProcessedUtils.res @@ -93,15 +93,6 @@ let paymentsProcessedMapper = ( ), } } -// Need to modify -let getMetaData = json => - json - ->getArrayFromJson([]) - ->getValueFromArray(0, JSON.Encode.array([])) - ->getDictFromJsonObject - ->getArrayFromDict("metaData", []) - ->getValueFromArray(0, JSON.Encode.array([])) - ->getDictFromJsonObject let visibleColumns = [Time_Bucket] diff --git a/src/screens/NewAnalytics/SmartRetryAnalytics/NewSmartRetryAnalyticsEntity.res b/src/screens/NewAnalytics/SmartRetryAnalytics/NewSmartRetryAnalyticsEntity.res new file mode 100644 index 000000000..89d13a2a0 --- /dev/null +++ b/src/screens/NewAnalytics/SmartRetryAnalytics/NewSmartRetryAnalyticsEntity.res @@ -0,0 +1,33 @@ +open NewAnalyticsTypes +// Smart Retry Payments Processed +let smartRetryPaymentsProcessedEntity: moduleEntity = { + requestBodyConfig: { + delta: false, + metrics: [#sessionized_payment_processed_amount], + }, + title: "Smart Retry Payments Processed", + domain: #payments, +} + +let smartRetryPaymentsProcessedChartEntity: chartEntity< + LineGraphTypes.lineGraphPayload, + LineGraphTypes.lineGraphOptions, + JSON.t, +> = { + getObjects: SmartRetryPaymentsProcessedUtils.smartRetryPaymentsProcessedMapper, + getChatOptions: LineGraphUtils.getLineGraphOptions, +} + +let smartRetryPaymentsProcessedTableEntity = { + open SmartRetryPaymentsProcessedUtils + EntityType.makeEntity( + ~uri=``, + ~getObjects, + ~dataKey="queryData", + ~defaultColumns=visibleColumns, + ~requiredSearchFieldsList=[], + ~allColumns=visibleColumns, + ~getCell, + ~getHeading, + ) +} diff --git a/src/screens/NewAnalytics/SmartRetryAnalytics/SmartRetryPaymentsProcessed/SmartRetryPaymentsProcessed.res b/src/screens/NewAnalytics/SmartRetryAnalytics/SmartRetryPaymentsProcessed/SmartRetryPaymentsProcessed.res new file mode 100644 index 000000000..19855f502 --- /dev/null +++ b/src/screens/NewAnalytics/SmartRetryAnalytics/SmartRetryPaymentsProcessed/SmartRetryPaymentsProcessed.res @@ -0,0 +1,301 @@ +open NewAnalyticsTypes +open NewAnalyticsHelper +open LineGraphTypes +open SmartRetryPaymentsProcessedUtils +open SmartRetryPaymentsProcessedTypes +open NewSmartRetryAnalyticsEntity + +module TableModule = { + open LogicUtils + @react.component + let make = (~data, ~className="") => { + let (offset, setOffset) = React.useState(_ => 0) + let defaultSort: Table.sortedObject = { + key: "", + order: Table.INC, + } + let tableBorderClass = "border-collapse border border-jp-gray-940 border-solid border-2 border-opacity-30 dark:border-jp-gray-dark_table_border_color dark:border-opacity-30" + + let smartRetryPaymentsProcessed = + data + ->Array.map(item => { + item->getDictFromJsonObject->tableItemToObjMapper + }) + ->Array.map(Nullable.make) + + let defaultCols = [Payment_Processed_Amount, Payment_Processed_Count] + let visibleColumns = defaultCols->Array.concat(visibleColumns) + +
+ Array.length} + offset + setOffset + defaultSort + currrentFetchCount={smartRetryPaymentsProcessed->Array.length} + tableLocalFilter=false + tableheadingClass=tableBorderClass + tableBorderClass + ignoreHeaderBg=true + showSerialNumber=true + tableDataBorderClass=tableBorderClass + isAnalyticsModule=true + /> +
+ } +} + +module SmartRetryPaymentsProcessedHeader = { + open NewAnalyticsUtils + open LogicUtils + @react.component + let make = ( + ~data: JSON.t, + ~viewType, + ~setViewType, + ~selectedMetric, + ~setSelectedMetric, + ~granularity, + ~setGranularity, + ) => { + let {filterValueJson} = React.useContext(FilterContext.filterContext) + let comparison = filterValueJson->getString("comparison", "")->DateRangeUtils.comparisonMapprer + + let primaryValue = getMetaDataValue(~data, ~index=0, ~key=selectedMetric.value) + let secondaryValue = getMetaDataValue(~data, ~index=1, ~key=selectedMetric.value) + + let (primaryValue, secondaryValue) = if selectedMetric.value->isAmountMetric { + (primaryValue /. 100.0, secondaryValue /. 100.0) + } else { + (primaryValue, secondaryValue) + } + + let (value, direction) = calculatePercentageChange(~primaryValue, ~secondaryValue) + + let setViewType = value => { + setViewType(_ => value) + } + + let setSelectedMetric = value => { + setSelectedMetric(_ => value) + } + + let setGranularity = value => { + setGranularity(_ => value) + } + + let metricType = switch selectedMetric.value->getVariantValueFromString { + | Payment_Processed_Amount => Amount + | _ => Volume + } + + let suffix = metricType == Amount ? "USD" : "" + +
+
+
+ {`${primaryValue->valueFormatter(metricType)} ${suffix}`->React.string} // TODO:Currency need to be picked from filter +
+ + + +
+ // will enable it in future + +
+ +
+
+
+ + +
+
+ } +} + +@react.component +let make = ( + ~entity: moduleEntity, + ~chartEntity: chartEntity, +) => { + open LogicUtils + open APIUtils + let getURL = useGetURL() + let updateDetails = useUpdateMethod() + let (screenState, setScreenState) = React.useState(_ => PageLoaderWrapper.Loading) + let {filterValueJson} = React.useContext(FilterContext.filterContext) + let (smartRetryPaymentsProcessedData, setSmartRetryPaymentsProcessedData) = React.useState(_ => + JSON.Encode.array([]) + ) + let ( + smartRetryPaymentsProcessedTableData, + setSmartRetryPaymentsProcessedTableData, + ) = React.useState(_ => []) + let ( + smartRetryPaymentsProcessedMetaData, + setSmartRetryPaymentsProcessedMetaData, + ) = React.useState(_ => JSON.Encode.array([])) + let (selectedMetric, setSelectedMetric) = React.useState(_ => defaultMetric) + let (granularity, setGranularity) = React.useState(_ => defaulGranularity) + let (viewType, setViewType) = React.useState(_ => Graph) + let startTimeVal = filterValueJson->getString("startTime", "") + let endTimeVal = filterValueJson->getString("endTime", "") + let compareToStartTime = filterValueJson->getString("compareToStartTime", "") + let compareToEndTime = filterValueJson->getString("compareToEndTime", "") + let comparison = filterValueJson->getString("comparison", "")->DateRangeUtils.comparisonMapprer + + let getSmartRetryPaymentsProcessed = async () => { + setScreenState(_ => PageLoaderWrapper.Loading) + try { + let url = getURL( + ~entityName=ANALYTICS_PAYMENTS_V2, + ~methodType=Post, + ~id=Some((entity.domain: domain :> string)), + ) + + let primaryBody = NewAnalyticsUtils.requestBody( + ~dimensions=[], + ~startTime=startTimeVal, + ~endTime=endTimeVal, + ~delta=entity.requestBodyConfig.delta, + ~metrics=entity.requestBodyConfig.metrics, + ~granularity=granularity.value->Some, + ) + + let secondaryBody = NewAnalyticsUtils.requestBody( + ~dimensions=[], + ~startTime=compareToStartTime, + ~endTime=compareToEndTime, + ~delta=entity.requestBodyConfig.delta, + ~metrics=entity.requestBodyConfig.metrics, + ~granularity=granularity.value->Some, + ) + + let primaryResponse = await updateDetails(url, primaryBody, Post) + let primaryData = + primaryResponse + ->getDictFromJsonObject + ->getArrayFromDict("queryData", []) + ->PaymentsProcessedUtils.modifyQueryData + ->modifySmartRetryQueryData + let primaryMetaData = + primaryResponse + ->getDictFromJsonObject + ->getArrayFromDict("metaData", []) + ->modifySmartRetryMetaData + setSmartRetryPaymentsProcessedTableData(_ => primaryData) + + let (secondaryMetaData, secondaryModifiedData) = switch comparison { + | EnableComparison => { + let secondaryResponse = await updateDetails(url, secondaryBody, Post) + let secondaryData = + secondaryResponse + ->getDictFromJsonObject + ->getArrayFromDict("queryData", []) + ->PaymentsProcessedUtils.modifyQueryData + ->modifySmartRetryQueryData + let secondaryMetaData = + secondaryResponse + ->getDictFromJsonObject + ->getArrayFromDict("metaData", []) + ->modifySmartRetryMetaData + + let secondaryModifiedData = [secondaryData]->Array.map(data => { + NewAnalyticsUtils.fillMissingDataPoints( + ~data, + ~startDate=compareToStartTime, + ~endDate=compareToEndTime, + ~timeKey="time_bucket", + ~defaultValue={ + "payment_count": 0, + "payment_processed_amount": 0, + "time_bucket": startTimeVal, + }->Identity.genericTypeToJson, + ~granularity=granularity.value, + ) + }) + (secondaryMetaData, secondaryModifiedData) + } + | DisableComparison => ([], []) + } + + if primaryData->Array.length > 0 { + let primaryModifiedData = [primaryData]->Array.map(data => { + NewAnalyticsUtils.fillMissingDataPoints( + ~data, + ~startDate=startTimeVal, + ~endDate=endTimeVal, + ~timeKey="time_bucket", + ~defaultValue={ + "payment_count": 0, + "payment_processed_amount": 0, + "time_bucket": startTimeVal, + }->Identity.genericTypeToJson, + ~granularity=granularity.value, + ) + }) + + setSmartRetryPaymentsProcessedData(_ => + primaryModifiedData->Array.concat(secondaryModifiedData)->Identity.genericTypeToJson + ) + setSmartRetryPaymentsProcessedMetaData(_ => + primaryMetaData->Array.concat(secondaryMetaData)->Identity.genericTypeToJson + ) + setScreenState(_ => PageLoaderWrapper.Success) + } else { + setScreenState(_ => PageLoaderWrapper.Custom) + } + } catch { + | _ => setScreenState(_ => PageLoaderWrapper.Custom) + } + } + React.useEffect(() => { + if startTimeVal->isNonEmptyString && endTimeVal->isNonEmptyString { + getSmartRetryPaymentsProcessed()->ignore + } + None + }, (startTimeVal, endTimeVal, compareToStartTime, compareToEndTime, comparison)) + + let params = { + data: smartRetryPaymentsProcessedData, + xKey: selectedMetric.value, + yKey: Time_Bucket->getStringFromVariant, + comparison, + } + +
+ + + } customUI={}> + +
+ {switch viewType { + | Graph => + + | Table => + }} +
+
+
+
+} diff --git a/src/screens/NewAnalytics/SmartRetryAnalytics/SmartRetryPaymentsProcessed/SmartRetryPaymentsProcessedTypes.res b/src/screens/NewAnalytics/SmartRetryAnalytics/SmartRetryPaymentsProcessed/SmartRetryPaymentsProcessedTypes.res new file mode 100644 index 000000000..8890ff39d --- /dev/null +++ b/src/screens/NewAnalytics/SmartRetryAnalytics/SmartRetryPaymentsProcessed/SmartRetryPaymentsProcessedTypes.res @@ -0,0 +1,14 @@ +type smartRetryPaymentsProcessedCols = + | Payment_Processed_Amount + | Payment_Processed_Count + | Total_Payment_Processed_Amount + | Total_Payment_Processed_Count + | Time_Bucket + +type smartRetryPaymentsProcessedObject = { + smart_retry_payment_processed_amount_in_usd: float, + smart_retry_payment_processed_count: int, + total_payment_smart_retry_processed_amount_in_usd: float, + total_payment_smart_retry_processed_count: int, + time_bucket: string, +} diff --git a/src/screens/NewAnalytics/SmartRetryAnalytics/SmartRetryPaymentsProcessed/SmartRetryPaymentsProcessedUtils.res b/src/screens/NewAnalytics/SmartRetryAnalytics/SmartRetryPaymentsProcessed/SmartRetryPaymentsProcessedUtils.res new file mode 100644 index 000000000..903e96e70 --- /dev/null +++ b/src/screens/NewAnalytics/SmartRetryAnalytics/SmartRetryPaymentsProcessed/SmartRetryPaymentsProcessedUtils.res @@ -0,0 +1,240 @@ +open SmartRetryPaymentsProcessedTypes +open NewAnalyticsUtils +open LogicUtils + +let getStringFromVariant = value => { + switch value { + | Payment_Processed_Amount => "payment_processed_amount_in_usd" + | Payment_Processed_Count => "payment_processed_count" + | Total_Payment_Processed_Amount => "total_payment_processed_amount_in_usd" + | Total_Payment_Processed_Count => "total_payment_processed_count" + | Time_Bucket => "time_bucket" + } +} + +let getVariantValueFromString = value => { + switch value { + | "payment_processed_amount_in_usd" => Payment_Processed_Amount + | "payment_processed_count" => Payment_Processed_Count + | "total_payment_processed_amount_in_usd" => Total_Payment_Processed_Amount + | "total_payment_processed_count" => Total_Payment_Processed_Count + | "time_bucket" | _ => Time_Bucket + } +} + +let isAmountMetric = key => { + switch key->getVariantValueFromString { + | Payment_Processed_Amount + | Total_Payment_Processed_Amount => true + | _ => false + } +} + +let smartRetryPaymentsProcessedMapper = ( + ~params: NewAnalyticsTypes.getObjects, +): LineGraphTypes.lineGraphPayload => { + open LineGraphTypes + open NewPaymentAnalyticsUtils + let {data, xKey, yKey} = params + let comparison = switch params.comparison { + | Some(val) => Some(val) + | None => None + } + let primaryCategories = data->getCategories(0, yKey) + let secondaryCategories = data->getCategories(1, yKey) + + let lineGraphData = + data + ->getArrayFromJson([]) + ->Array.mapWithIndex((item, index) => { + let name = NewAnalyticsUtils.getLabelName(~key=yKey, ~index, ~points=item) + let color = index->getColor + getLineGraphObj( + ~array=item->getArrayFromJson([]), + ~key=xKey, + ~name, + ~color, + ~isAmount=xKey->isAmountMetric, + ) + }) + let title = { + text: "Smart Retry Payments Processed", + } + + open NewAnalyticsTypes + let metricType = switch xKey->getVariantValueFromString { + | Payment_Processed_Amount => Amount + | _ => Volume + } + + { + categories: primaryCategories, + data: lineGraphData, + title, + yAxisMaxValue: None, + tooltipFormatter: tooltipFormatter( + ~secondaryCategories, + ~title="Smart Retry Payments Processed", + ~metricType, + ~comparison, + ), + } +} + +let visibleColumns = [Time_Bucket] + +let tableItemToObjMapper: Dict.t => smartRetryPaymentsProcessedObject = dict => { + open NewPaymentAnalyticsUtils + { + smart_retry_payment_processed_amount_in_usd: dict->getAmountValue( + ~id=Payment_Processed_Amount->getStringFromVariant, + ), + smart_retry_payment_processed_count: dict->getInt( + Payment_Processed_Count->getStringFromVariant, + 0, + ), + total_payment_smart_retry_processed_amount_in_usd: dict->getAmountValue( + ~id=Total_Payment_Processed_Amount->getStringFromVariant, + ), + total_payment_smart_retry_processed_count: dict->getInt( + Total_Payment_Processed_Count->getStringFromVariant, + 0, + ), + time_bucket: dict->getString(Time_Bucket->getStringFromVariant, "NA"), + } +} + +let getObjects: JSON.t => array = json => { + json + ->LogicUtils.getArrayFromJson([]) + ->Array.map(item => { + tableItemToObjMapper(item->getDictFromJsonObject) + }) +} + +let getHeading = colType => { + switch colType { + | Payment_Processed_Amount => + Table.makeHeaderInfo( + ~key=Payment_Processed_Amount->getStringFromVariant, + ~title="Amount", + ~dataType=TextType, + ) + | Payment_Processed_Count => + Table.makeHeaderInfo( + ~key=Payment_Processed_Count->getStringFromVariant, + ~title="Count", + ~dataType=TextType, + ) + | Time_Bucket => + Table.makeHeaderInfo(~key=Time_Bucket->getStringFromVariant, ~title="Date", ~dataType=TextType) + + | Total_Payment_Processed_Amount + | Total_Payment_Processed_Count => + Table.makeHeaderInfo(~key="", ~title="", ~dataType=TextType) + } +} + +let getCell = (obj, colType): Table.cell => { + switch colType { + | Payment_Processed_Amount => + Text(obj.smart_retry_payment_processed_amount_in_usd->valueFormatter(Amount)) + | Payment_Processed_Count => Text(obj.smart_retry_payment_processed_count->Int.toString) + | Time_Bucket => Text(obj.time_bucket->formatDateValue(~includeYear=true)) + | Total_Payment_Processed_Amount + | Total_Payment_Processed_Count => + Text("") + } +} + +open NewAnalyticsTypes +let dropDownOptions = [ + {label: "By Amount", value: Payment_Processed_Amount->getStringFromVariant}, + {label: "By Count", value: Payment_Processed_Count->getStringFromVariant}, +] + +let tabs = [{label: "Daily", value: (#G_ONEDAY: granularity :> string)}] + +let defaultMetric = { + label: "By Amount", + value: Payment_Processed_Amount->getStringFromVariant, +} + +let defaulGranularity = { + label: "Daily", + value: (#G_ONEDAY: granularity :> string), +} + +let modifySmartRetryQueryData = data => { + data->Array.map(item => { + let valueDict = item->getDictFromJsonObject + + let key = Payment_Processed_Count->getStringFromVariant + let paymentProcessedCount = valueDict->getInt(key, 0) + + let key = Payment_Processed_Amount->getStringFromVariant + let paymentProcessedAmount = valueDict->getFloat(key, 0.0) + + let key = + PaymentsProcessedTypes.Payment_Processed_Amount_Without_Smart_Retries->PaymentsProcessedUtils.getStringFromVariant + let paymentProcessedAmountWithoutSmartRetries = valueDict->getFloat(key, 0.0) + + let key = + PaymentsProcessedTypes.Payment_Processed_Count_Without_Smart_Retries->PaymentsProcessedUtils.getStringFromVariant + let paymentProcessedCountWithoutSmartRetries = valueDict->getInt(key, 0) + + let totalPaymentProcessedCount = + paymentProcessedCount - paymentProcessedCountWithoutSmartRetries + + let totalPaymentProcessedAmount = + paymentProcessedAmount -. paymentProcessedAmountWithoutSmartRetries + + valueDict->Dict.set( + Payment_Processed_Count->getStringFromVariant, + totalPaymentProcessedCount->JSON.Encode.int, + ) + valueDict->Dict.set( + Payment_Processed_Amount->getStringFromVariant, + totalPaymentProcessedAmount->JSON.Encode.float, + ) + + valueDict->JSON.Encode.object + }) +} + +let modifySmartRetryMetaData = data => { + data->Array.map(item => { + let valueDict = item->getDictFromJsonObject + + let key = Total_Payment_Processed_Count->getStringFromVariant + let paymentProcessedCount = valueDict->getInt(key, 0) + + let key = Total_Payment_Processed_Amount->getStringFromVariant + let paymentProcessedAmount = valueDict->getFloat(key, 0.0) + + let key = + PaymentsProcessedTypes.Total_Payment_Processed_Amount_Without_Smart_Retries->PaymentsProcessedUtils.getStringFromVariant + let paymentProcessedAmountWithoutSmartRetries = valueDict->getFloat(key, 0.0) + + let key = + PaymentsProcessedTypes.Total_Payment_Processed_Count_Without_Smart_Retriess->PaymentsProcessedUtils.getStringFromVariant + let paymentProcessedCountWithoutSmartRetries = valueDict->getInt(key, 0) + + let totalPaymentProcessedCount = + paymentProcessedCount - paymentProcessedCountWithoutSmartRetries + + let totalPaymentProcessedAmount = + paymentProcessedAmount -. paymentProcessedAmountWithoutSmartRetries + + valueDict->Dict.set( + Total_Payment_Processed_Count->getStringFromVariant, + totalPaymentProcessedCount->JSON.Encode.int, + ) + valueDict->Dict.set( + Total_Payment_Processed_Amount->getStringFromVariant, + totalPaymentProcessedAmount->JSON.Encode.float, + ) + + valueDict->JSON.Encode.object + }) +}