Skip to content
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

feat: webhook list and details introduced #2224

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions config/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ dev_recovery_v2_product=false
dev_vault_v2_product=false
dev_modularity_v2=false
maintainence_alert=""
dev_webhooks=false
[default.merchant_config]
[default.merchant_config.new_analytics]
org_ids=[]
Expand Down
20 changes: 20 additions & 0 deletions src/APIUtils/APIUtils.res
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,26 @@ let useGetURL = () => {
/* EVENT LOGS */
| SDK_EVENT_LOGS => `analytics/v1/profile/sdk_event_logs`

| WEBHOOK_EVENTS =>
switch queryParamerters {
| Some(param) => `events/${merchantId}?${param}`
| None => `events/${merchantId}`
}
| WEBHOOK_EVENTS_DETAILS =>
switch queryParamerters {
| Some(param) => `events/${merchantId}/${param}`
| None => `events/${merchantId}`
}
| WEBHOOK_EVENTS_ATTEMPTS =>
switch id {
| Some(id) => `events/${merchantId}/${id}/attempts`
| None => `events/${merchantId}/attempts`
}
| WEBHOOKS_EVENTS_RETRY =>
switch id {
| Some(id) => `events/${merchantId}/${id}/retry`
| None => `events/${merchantId}/retry`
}
| WEBHOOKS_EVENT_LOGS =>
switch methodType {
| Get =>
Expand Down
4 changes: 4 additions & 0 deletions src/APIUtils/APIUtilsTypes.res
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ type entityName =
| DISPUTES_AGGREGATE
| DEFAULT_FALLBACK
| SDK_EVENT_LOGS
| WEBHOOK_EVENTS
| WEBHOOK_EVENTS_DETAILS
| WEBHOOK_EVENTS_ATTEMPTS
| WEBHOOKS_EVENTS_RETRY
| WEBHOOKS_EVENT_LOGS
| CONNECTOR_EVENT_LOGS
| GENERATE_SAMPLE_DATA
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@ let getTableCell = (~connectorType: ConnectorTypes.connector=Processor) => {
"",
)
| ConnectorLabel => Text(connector.connector_label)


| Actions => Table.CustomCell(<div />, "")
| PaymentMethods =>
Table.CustomCell(
Expand Down
2 changes: 2 additions & 0 deletions src/components/LoadedTable.res
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ let make = (
~loadedTableParentClass="",
~remoteSortEnabled=false,
~showAutoScroll=false,
~highlightSelectedRow=false,
) => {
open LogicUtils
let showPopUp = PopUpState.useShowPopUp()
Expand Down Expand Up @@ -832,6 +833,7 @@ let make = (
nonFrozenTableParentClass
showAutoScroll
showPagination
highlightSelectedRow
/>
switch tableLocalFilter {
| true =>
Expand Down
1 change: 1 addition & 0 deletions src/components/LoadedTable.resi
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,5 @@ let make: (
~loadedTableParentClass: string=?,
~remoteSortEnabled: bool=?,
~showAutoScroll: bool=?,
~highlightSelectedRow: bool=?,
) => React.element
19 changes: 17 additions & 2 deletions src/components/Table.res
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ module TableRow = {
~fixLastCol=false,
~alignCellContent="",
~customCellColor="",
~highlightSelectedRow=false,
~selectedIndex,
~setSelectedIndex,
) => {
open Window
let (isCurrentRowExpanded, setIsCurrentRowExpanded) = React.useState(_ => false)
Expand All @@ -90,7 +93,10 @@ module TableRow = {
let onClick = React.useCallback(_ => {
let isRangeSelected = getSelection().\"type" == "Range"
switch (onRowClick, isRangeSelected) {
| (Some(fn), false) => fn(actualIndex)
| (Some(fn), false) => {
setSelectedIndex(_ => actualIndex)
fn(actualIndex)
}
| _ => ()
}
}, (onRowClick, actualIndex))
Expand Down Expand Up @@ -128,7 +134,11 @@ module TableRow = {
}
})
->Option.isSome
let bgColor = coloredRow ? selectedRowColor : "bg-white dark:bg-jp-gray-lightgray_background"
let bgColor = coloredRow
? selectedRowColor
: highlightSelectedRow && selectedIndex == actualIndex
? "bg-nd_gray-150"
: "bg-white dark:bg-jp-gray-lightgray_background"
let fontSize = "text-fs-14"
let fontWeight = "font-medium"
let textColor = "text-nd_gray-600 dark:text-jp-gray-text_darktheme dark:text-opacity-75"
Expand Down Expand Up @@ -659,11 +669,13 @@ let make = (
~showAutoScroll=false,
~showVerticalScroll=false,
~showPagination=true,
~highlightSelectedRow=false,
) => {
let isMobileView = MatchMedia.useMobileChecker()
let rowInfo: array<array<cell>> = rows
let actualData: option<array<Nullable.t<'t>>> = actualData
let numberOfCols = heading->Array.length
let (selectedIndex, setSelectedIndex) = React.useState(_ => -1)
open Webapi
let totalTableWidth =
Dom.document
Expand Down Expand Up @@ -739,6 +751,9 @@ let make = (
fixLastCol
?alignCellContent
?customCellColor
selectedIndex
setSelectedIndex
highlightSelectedRow
/>
})
->React.array
Expand Down
4 changes: 4 additions & 0 deletions src/components/Table.resi
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ module TableRow: {
~fixLastCol: bool=?,
~alignCellContent: string=?,
~customCellColor: string=?,
~highlightSelectedRow: bool=?,
~selectedIndex: int,
~setSelectedIndex: ('a => int) => unit,
) => React.element
}
module SortAction: {
Expand Down Expand Up @@ -308,4 +311,5 @@ let make: (
~showAutoScroll: bool=?,
~showVerticalScroll: bool=?,
~showPagination: bool=?,
~highlightSelectedRow: bool=?,
) => React.element
13 changes: 13 additions & 0 deletions src/container/ConnectorContainer.res
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,19 @@ let make = () => {
renderList={() => <PaymentSettingsList />}
renderShow={(_, _) => <PaymentSettings webhookOnly=false showFormOnly=false />}
/>
| list{"webhooks", ...remainingPath} =>
<AccessControl isEnabled={featureFlagDetails.devWebhooks} authorization=Access>
<FilterContext key="webhooks" index="webhooks">
<EntityScaffold
entityName="Webhooks"
remainingPath
access=Access
renderList={() => <Webhooks />}
renderShow={(id, _) => <WebhooksDetails id />}
/>
</FilterContext>
</AccessControl>

| list{"unauthorized"} => <UnauthorizedPage />
| _ => <NotFoundPage />
}}
Expand Down
2 changes: 2 additions & 0 deletions src/entryPoints/FeatureFlagUtils.res
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type featureFlag = {
forceCookies: bool,
authenticationAnalytics: bool,
devModularityV2: bool,
devWebhooks: bool,
}

let featureFlagType = (featureFlags: JSON.t) => {
Expand Down Expand Up @@ -100,6 +101,7 @@ let featureFlagType = (featureFlags: JSON.t) => {
forceCookies: dict->getBool("force_cookies", false),
authenticationAnalytics: dict->getBool("authentication_analytics", false),
devModularityV2: dict->getBool("dev_modularity_v2", false),
devWebhooks: dict->getBool("dev_webhooks", false),
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/entryPoints/HyperSwitchApp.res
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ let make = () => {
| list{"configure-pmts", ..._}
| list{"routing", ..._}
| list{"payoutrouting", ..._}
| list{"payment-settings", ..._} =>
| list{"payment-settings", ..._}
| list{"webhooks", ..._} =>
<ConnectorContainer />
| list{"business-details", ..._}
| list{"business-profiles", ..._} =>
Expand Down
23 changes: 21 additions & 2 deletions src/entryPoints/SidebarValues.res
Original file line number Diff line number Diff line change
Expand Up @@ -516,12 +516,30 @@ let paymentSettings = userHasResourceAccess => {
})
}

let developers = (isDevelopersEnabled, ~userHasResourceAccess, ~checkUserEntity) => {
let webhooks = userHasResourceAccess => {
SubLevelLink({
name: "Webhooks",
link: `/webhooks`,
access: userHasResourceAccess(~resourceAccess=Account),
searchOptions: [("Webhooks", ""), ("Retry webhooks", "")],
})
}

let developers = (
isDevelopersEnabled,
~isWebhooksEnabled,
~userHasResourceAccess,
~checkUserEntity,
) => {
let isProfileUser = checkUserEntity([#Profile])
let apiKeys = apiKeys(userHasResourceAccess)
let paymentSettings = paymentSettings(userHasResourceAccess)
let webhooks = webhooks(userHasResourceAccess)

let defaultDevelopersOptions = [paymentSettings]
if isWebhooksEnabled {
defaultDevelopersOptions->Array.push(webhooks)
}

if !isProfileUser {
defaultDevelopersOptions->Array.push(apiKeys)
Expand Down Expand Up @@ -654,6 +672,7 @@ let useGetHsSidebarValues = (~isReconEnabled: bool) => {
taxProcessor,
newAnalytics,
authenticationAnalytics,
devWebhooks,
} = featureFlagDetails
let {
useIsFeatureEnabledForMerchant,
Expand Down Expand Up @@ -687,7 +706,7 @@ let useGetHsSidebarValues = (~isReconEnabled: bool) => {
~userEntity,
),
recon->reconAndSettlement(isReconEnabled, checkUserEntity, userHasResourceAccess),
default->developers(~userHasResourceAccess, ~checkUserEntity),
default->developers(~isWebhooksEnabled=devWebhooks, ~userHasResourceAccess, ~checkUserEntity),
settings(~isConfigurePmtsEnabled=configurePmts, ~userHasResourceAccess, ~complianceCertificate),
]

Expand Down
136 changes: 136 additions & 0 deletions src/screens/Developer/Webhooks/Webhooks.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
@react.component
let make = () => {
open APIUtils
open WebhooksUtils
let getURL = useGetURL()
let fetchDetails = useGetMethod()
let {userHasAccess} = GroupACLHooks.useUserGroupACLHook()
let (webhooksData, setWebhooksData) = React.useState(_ => [])
let defaultValue: LoadedTable.pageDetails = {offset: 0, resultsPerPage: 20}
let pageDetailDict = Recoil.useRecoilValueFromAtom(LoadedTable.table_pageDetails)
let pageDetail = pageDetailDict->Dict.get("Webhooks")->Option.getOr(defaultValue)
let (totalCount, setTotalCount) = React.useState(_ => 100) //TODO: to be extracted from API
let (offset, setOffset) = React.useState(_ => pageDetail.offset)
let (screenState, setScreenState) = React.useState(_ => PageLoaderWrapper.Loading)
let {updateExistingKeys, filterValueJson, reset, filterValue} =
FilterContext.filterContext->React.useContext
let businessProfileValues = HyperswitchAtom.businessProfilesAtom->Recoil.useRecoilValueFromAtom
let (searchText, setSearchText) = React.useState(_ => "")

let webhookURL = switch businessProfileValues->Array.get(0) {
| Some(val) => val.webhook_details.webhook_url->Option.getOr("")
| None => ""
}

React.useEffect(() => {
if filterValueJson->Dict.keysToArray->Array.length === 0 {
setOffset(_ => 0)
}
None
}, [])

React.useEffect(() => {
if filterValueJson->Dict.keysToArray->Array.length != 0 {
setOffset(_ => 0)
}
None
}, [filterValue])

let initialDisplayFilters =
[]->Array.filter((item: EntityType.initialFilters<'t>) => item.localFilter->Option.isSome)

let setInitialFilters = HSwitchRemoteFilter.useSetInitialFilters(
~updateExistingKeys,
~startTimeFilterKey,
~endTimeFilterKey,
~compareToStartTimeKey="",
~compareToEndTimeKey="",
~comparisonKey="",
~range=30,
~origin="orders",
(),
)

React.useEffect(() => {
fetchWebhooks(
~getURL,
~fetchDetails,
~filterValueJson,
~offset,
~setOffset,
~searchText,
~setScreenState,
~setWebhooksData,
~setTotalCount,
)->ignore
if filterValueJson->Dict.keysToArray->Array.length < 1 {
setInitialFilters()
}
None
}, (filterValueJson, offset, searchText))

let filtersUI = React.useMemo(() => {
<Filter
key="0"
title="Webhooks"
defaultFilters={""->JSON.Encode.string}
fixedFilters={initialFixedFilter()}
requiredSearchFieldsList=[]
localFilters={initialDisplayFilters}
localOptions=[]
remoteOptions=[]
remoteFilters=[]
autoApply=false
submitInputOnEnter=true
defaultFilterKeys=[startTimeFilterKey, endTimeFilterKey]
updateUrlWith={updateExistingKeys}
customLeftView={<HSwitchRemoteFilter.SearchBarFilter
placeholder="Search for object ID" setSearchVal=setSearchText searchVal=searchText
/>}
clearFilters={() => reset()}
/>
}, [])

let refreshPage = () => {
reset()
Window.Location.reload()
}

let isWebhookUrlConfigured = webhookURL !== ""

let message = isWebhookUrlConfigured
? "No data found, try searching with different filters or try refreshing using the button below"
: "Webhook UI is not configured please do it from payment settings"

let customUI =
<NoDataFound message renderType=Painting>
<RenderIf condition={isWebhookUrlConfigured}>
<div className="m-2">
<Button text="Refresh" buttonType=Primary onClick={_ => refreshPage()} />
</div>
</RenderIf>
</NoDataFound>

<>
<PageUtils.PageHeading title="Webhooks" subTitle="" />
{filtersUI}
<PageLoaderWrapper screenState customUI>
<LoadedTable
title=" "
actualData={webhooksData->Array.map(Nullable.make)}
totalResults=totalCount
resultsPerPage=20
entity={WebhooksTableEntity.webhooksEntity(
`webhooks`,
~authorization=userHasAccess(~groupAccess=AccountManage),
)}
hideTitle=true
offset
setOffset
currrentFetchCount={webhooksData->Array.map(Nullable.make)->Array.length}
collapseTableRow=false
showSerialNumber=true
/>
</PageLoaderWrapper>
</>
}
Loading