Search functionality for the Fraud Manager

Advanced filters for the Queue creation flow
Deployment to 2 tenants
​
Other Changes
Simplified Azure Maps user management
Added timezone info to local dates on the Item Details page
Performance improvements
Optimized item enrichment procedure
Deployment scripmt improvement
Hotfix enrichment for purchases with rich relations
Improve enrichment logging
This commit is contained in:
Irina Tarnavski 2020-11-18 09:55:19 -08:00
Родитель eb235c67ba
Коммит 504e85ab62
332 изменённых файлов: 12365 добавлений и 5725 удалений

Просмотреть файл

@ -133,14 +133,18 @@ Configuration files are available in the future in AppService.
**WARNING!** Redeployment will override any manual changes in configuration files made at AppService.
# Deploy application
## Initial deployment
## Single tenant deployment
Single tenant deployment creates all objects (Azure AD applications, azure resources) in single tenant.
It also use same tenant Active Directory to authenticate users, accessing the application.
### Initial deployment (single tenant)
When deploying the application for the first time, the mail account password should be provided (see step 2 on
*Prerequisites*). Use the following command to initiate deployment with prompting for mail account password:
```
pwsh deploy.ps1 -config main.parameters.<X>.json -setMailPassword
```
Mail account password is stored in Azure Key Vault.
## Redeployments
### Redeployments (single tenants)
If solution is already deployed, in order to apply changes, use the following command
```
pwsh deploy.ps1 -config main.parameters.<X>.json
@ -152,6 +156,68 @@ Note: it is possible to automate deployment and exclude prompting password by in
from some powershell wrapper script, and provide mail account password as **secureString** type parameter
**-mailSecurePassword** .
## Two tenants deployment
Two tenants deployment separates deployment of objects into two tenants: client and provider.
Client tenant in this case used to create Azure AD application with necessary permissions, to install
DFP application, and to authenticate users, accessing the application.
Provider tenant is used to deploy all Azure resources required to implement the application.
Two tenants deployment splitted into two phases. Phase one should be performed in client tenant, and phase
two in provider tenant
### Initial deployment (two tenants)
#### Phase one (client side)
During this phase Azure AD application and service principal will be created in the client tenant.
Use the following command:
```pwsh ./deploy_ad_app.ps1 -prefix <prefix> -envType <Dev|Prod> -tenantId <tenant_id>```
At this phase one will be prompted for the azure active directory application secret. This secret should
be shared with the provider in a secure manner. Also the output block starting from `Collect shared parameters`
should be shared with the provider.
#### Phase two (provider side)
Before starting deployment on provider side, shared parameters from client (see previous step) should be
added to environment configuration file main.parameters.<X>.json
```json
"clientTenantId": {
"value": ""
},
"clientTenantShortName": {
"value": ""
},
"appClientId": {
"value": ""
},
"appSpId": {
"value": ""
},
"dfpSpId": {
"value": ""
}
```
When the environment configuration file is ready, run the following command to start the deployment:
```
pwsh ./deploy.ps1 -config main.parameters.<X>.json -setMailPassword -SetClientSecret -twoTenants
```
One will be prompted to enter the mail account password and azure application secret, which was shared
with the client on phase one.
### Redeployments (two tenants)
#### Redeployment (two tenants, client side)
Normally there is no reason to re-run deployment on the client side. It can happen when there is a requirement
to update client application secret, application reply urls, or client application permissions.
The redeployment can be run with the command
```pwsh ./deploy_phase1.ps1 -prefix <prefix> -envType <Dev|Prod> -tenantId <tenant_id>```
Be aware, that when client application is updated, it is not applied to service principal automatically. It
might require to re-create service principal for the application.
#### Redeployment (two tenants, provider side)
If solution is already deployed, in order to apply changes, use the following command
```
pwsh deploy.ps1 -config main.parameters.<X>.json -twoTenants
```
## Post-deployment actions
Once you've installed the new environment from scratch you need to configure application for your needs.
Please, find below the tuning description.
@ -159,7 +225,7 @@ Please, find below the tuning description.
### Notification templates
Analytics BE module uses notification templates that are stored in the related CosmosDB database called `AnalyticsDB`.
The application creates default templates automatically if there are no other templates in the DB.
In order to change a desired template, you can find it in the `ConfigurableAppSetting` container and edit it as your wish.
In order to change a desired template, you can find it in the `ConfigurableAppSettings` container and edit it as your wish.
You can use any placeholder that mentioned in
[code](../backend/analytics/src/main/java/com/griddynamics/msd365fp/manualreview/analytics/config/Constants.java)
@ -168,7 +234,7 @@ with the `MAIL_TAG_` prefix
### External tool links
Queue BE module store templates of External Tool Links in the related Cosmos DB database `QueuesDB`.
There are no default templates in the application code, so you need to define them manually.
For that, open the `ConfigurableAppSetting` container and put templates in the following format:
For that, open the `ConfigurableAppSettings` container and put templates in the following format:
```json
{
"type": "review-console-links",

Просмотреть файл

@ -19,7 +19,7 @@ $tenantId = $json.parameters.tenantId.value
$deploymentResourceGroup = $prefix + "-rg"
$logWorkspace = $prefix + "-log-analytics-ws"
$logWorkspaceSecondary = $prefix + "-secondary-log-analytics-ws"
$mapsGroupName = $prefix + "-map-access"
$mapAppName = $prefix + "-map"
# Login to Azure and set context
@ -57,28 +57,22 @@ foreach ($la in @($logWorkspace, $logWorkspaceSecondary)) {
--force true -y
}
# remove AD service principal
if (Get-AzADServicePrincipal -DisplayName $prefix -ErrorAction Ignore) {
Write-Host "Removing Azure AD service principal $prefix"
Remove-AzADServicePrincipal `
-DisplayName $prefix `
-Force
}
foreach ($app in @($prefix, $mapAppName)) {
# remove AD service principal
if (Get-AzADServicePrincipal -DisplayName $app -ErrorAction Ignore) {
Write-Host "Removing Azure AD service principal $app"
Remove-AzADServicePrincipal `
-DisplayName $app `
-Force
}
# remove AD application
if (Get-AzADApplication -DisplayName $prefix -ErrorAction Ignore) {
Write-Host "Removing Azure AD application $prefix"
Remove-AzADApplication `
-DisplayName $prefix `
-Force
}
# remove AD maps group
if (Get-AzADGroup -DisplayName $mapsGroupName -ErrorAction Ignore) {
Write-Host "Removing Azure AD group $mapsGroupName"
Remove-AzADGroup `
-DisplayName $mapsGroupName `
-Force
# remove AD application
if (Get-AzADApplication -DisplayName $app -ErrorAction Ignore) {
Write-Host "Removing Azure AD application $app"
Remove-AzADApplication `
-DisplayName $app `
-Force
}
}
# remove deployment resource group

Просмотреть файл

@ -12,33 +12,13 @@ Param(
[switch] $setMailPassword,
[switch] $setClientSecret,
[switch] $twoTenants,
[string] $location = "westus"
)
$ErrorActionPreference = "Stop"
$frontendContainer = "web"
# Dict with java microservice name and path to base directory
$applications = @{
"queues" = "../backend/queues"
"analytics" = "../backend/analytics"
}
$version = Get-Date -Format FileDateTimeUniversal
# Path to fronend build directory
$frontendBuildPath = "../frontend/scripts"
# DFP roles definition
$dfpRoles = @{}
$dfpRoles["Dev"] = @(
"Sandbox_Risk_API",
"Sandbox_ManualReview_API"
)
$dfpRoles["Prod"] = @(
"Risk_API",
"ManualReview_API"
)
# Read prefix from json
$json = Get-Content $config | ConvertFrom-Json
@ -46,7 +26,17 @@ $prefix = $json.parameters.prefix.value
$subscriptionId = $json.parameters.subscriptionId.value
$tenantId = $json.parameters.tenantId.value
$envType = $json.parameters.envType.value
$customDomain = $json.parameters.customDomain.value
$clientTenantId = $tenantId
if ($twoTenants) {
$clientTenantId = $json.parameters.clientTenantId.value
$clientAdApp = @{
"appClientId" = $json.parameters.appClientId.value
"appSpId" = $json.parameters.appSpId.value
"dfpSpId" = $json.parameters.dfpSpId.value
"clientTenantShortName" = $json.parameters.clientTenantShortName.value
}
}
$deploymentResourceGroup = $prefix + "-rg"
$deploymentStorageAccount = $prefix.ToLower().Replace("-","") + "deploysa"
@ -54,157 +44,11 @@ $deploymentContainer = "deploy"
$sasTokenHours = 24 * 365 # one year
$deploymentIdentity = $prefix + "-deploy-identity"
$keyVaultName = $prefix.ToLower().Replace("-","") + "keyvault"
$mapsGroupName = $prefix + "-map-access"
$mapAppName = $prefix + "-map"
$baseDir = Get-Location
function BuildFrontend {
Set-Location "${baseDir}/$frontendBuildPath"
Copy-Item -Path "config.template.json" -Destination "../public/config.json"
yarn install
yarn build
if ($LastExitCode -ne 0) {
throw "Build failed, check logs"
}
New-Item -Path "${baseDir}/build" -Name "frontend" -ItemType "directory" -ErrorAction Ignore
Copy-Item -Path "../build/*" -Destination "${baseDir}/build/frontend" -Recurse
Set-Location $baseDir
}
function BuildBackend {
foreach ($app in $applications.GetEnumerator()) {
Write-Host "=== Building $($app.Name) app..."
Set-Location "${baseDir}/$($app.Value)"
if (CheckOSWindows) {
./gradlew.bat clean packageDist --console=plain
} else {
./gradlew clean packageDist --console=plain
}
if ($LastExitCode -ne 0) {
throw "Build failed, check logs"
}
New-Item -Path "${baseDir}/build" -Name "$($app.Name)" -ItemType "directory" -ErrorAction Ignore
Copy-Item -Path "build/dist/target.zip" -Destination "${baseDir}/build/$($app.Name)/target.zip" -Recurse
}
}
function GetRandomPassword {
-join ('abcdefghkmnrstuvwxyzABCDEFGHKLMNPRSTUVWXYZ0123456789'.ToCharArray() | Get-Random -Count 32)
}
function ProvideCleanContainer {
param (
$storageContainer,
$storageContext
)
$sc = Get-AzStorageContainer -Name $storageContainer -Context $storageContext -ErrorAction Ignore
if (!$sc) {
Write-Host "= Create container $storageContainer"
New-AzStorageContainer `
-Name $storageContainer `
-Context $storageContext
}
else
{
Write-Host "= Clean container $storageContainer"
Get-AzStorageBlob -Container $storageContainer -Context $storageContext `
| Where-Object { $_.Name -notMatch '^.*target.*zip' } `
| Remove-AzStorageBlob
}
}
function GrantDfpPermissionWin {
param (
[string] $clientAppName,
[string] $dfpRoleName
)
Write-Host "= Assign role $dfpRoleName to app" $clientAppName
$app_name = "Dynamics 365 Fraud Protection"
$sp = Get-AzureADServicePrincipal -Filter "displayName eq '$app_name'"
$c_appRole = $sp.AppRoles | Where-Object { $_.DisplayName -eq $dfpRoleName }
$c_sp = Get-AzureADServicePrincipal -Filter "displayName eq '$clientAppName'"
$roleAssigned = Get-AzureADServiceAppRoleAssignedTo -ObjectId $c_sp.ObjectId `
| Where-Object { $_.Id -eq $c_appRole.Id }
if(!($roleAssigned))
{
Write-host "Assigning DFP role ..."
New-AzureADServiceAppRoleAssignment `
-ObjectId $c_sp.ObjectId `
-PrincipalId $c_sp.ObjectId `
-ResourceId $sp.ObjectId `
-Id $c_appRole.Id
}
else
{
Write-host "DFP role is already assigned"
}
}
function CheckOSWindows
{
$envOS = Get-ChildItem -Path Env:OS -ErrorAction Ignore
return ( $envOS -And $envOS.value.Contains('Windows') )
}
function PutSecret {
param (
[string] $vaultName,
[string] $secretName,
[string] $secretValue,
[SecureString] $secretSecureValue
)
Write-Host "= Put secret $secretName to Key Vault $vaultName"
if ($secretValue) {
$secretSecureValue = ConvertTo-SecureString $secretValue -AsPlainText -Force
}
Set-AzKeyVaultSecret `
-VaultName $vaultName `
-Name $secretName `
-SecretValue $secretSecureValue
}
function GenReplyUrls {
# Define Azure app reply urls
$replyUrls = @(
"https://$prefix.azurefd.net/login",
"https://$prefix-queues.azurewebsites.net/oauth2-redirect.html",
"https://$prefix-queues-secondary.azurewebsites.net/oauth2-redirect.html",
"https://$prefix-analytics.azurewebsites.net/oauth2-redirect.html",
"https://$prefix-analytics-secondary.azurewebsites.net/oauth2-redirect.html"
)
$localSwaggerUri = "http://localhost:8080/oauth2-redirect.html"
$localFrontUri = "http://localhost:3000/login"
if ($envType -eq "Dev") {
$replyUrls += @($localSwaggerUri, $localFrontUri)
}
if ($customDomain -And ($customDomain -ne "UNDEFINED")) {
$replyUrls += "https://${customDomain}/login"
}
return $replyUrls
}
function GetTenantShortName {
param(
[string] $TenantId
)
foreach ($domain in (Get-AzTenant -TenantId $tenantId).domains) {
if ($domain -match '(.*)\.onmicrosoft\.com') {
return $Matches.1
}
}
}
. $baseDir/functions.ps1
################################################################# MAIN PROGRAM
@ -213,6 +57,10 @@ if ($setMailPassword) {
$mailSecurePassword = Read-Host "Enter the mail account password" -AsSecureString
}
# Read client secret when azure app deployed to different tenant
if ($setClientSecret) {
$clientSecureSecret = Read-Host "Enter the Azure application secret" -AsSecureString
}
Write-Host "=== Setting Azure context..."
if (!(Set-AzContext -SubscriptionId $subscriptionId -TenantId $tenantId -ErrorAction Ignore)) {
@ -277,20 +125,40 @@ if (!($vault)) {
-Name $keyVaultName `
-ResourceGroupName $deploymentResourceGroup `
-Location $location `
-EnabledForTemplateDeployment `
-DisableSoftDelete
-EnabledForTemplateDeployment
}
# Store app client secret in Key Vault if it was provided
Write-Host "=== Provide Azure AD App client secret"
if (!(Get-AzKeyVaultSecret -Name "client-secret" -VaultName $keyVaultName))
{
$clientSecret = GetRandomPassword
PutSecret -vaultName $keyVaultName -secretName "client-secret" -secretValue $clientSecret
if (!($clientSecureSecret)) {
if ($twoTenants) {
Write-Host "When Azure app installed to different tenant, deployment should be done with -SetClientSecret flag"
throw "Client secret not provided"
}
$clientSecret = GetRandomPassword
$clientSecureSecret = ConvertTo-SecureString $clientSecret -AsPlainText -Force
}
PutSecret -vaultName $keyVaultName -secretName "client-secret" -secretSecureValue $clientSecureSecret
}
else
{
$clientSecret = (Get-AzKeyVaultSecret -VaultName $keyVaultName -Name "client-secret").SecretValueText
$clientSecureSecret = ConvertTo-SecureString $clientSecret -AsPlainText -Force
}
# Store Map app client secret in Key Vault if it was provided
Write-Host "=== Provide Azure AD Map App client secret"
if (!(Get-AzKeyVaultSecret -Name "map-client-secret" -VaultName $keyVaultName))
{
$mapClientSecret = GetRandomPassword
PutSecret -vaultName $keyVaultName -secretName "map-client-secret" -secretValue $mapClientSecret
}
else
{
$mapClientSecret = (Get-AzKeyVaultSecret -VaultName $keyVaultName -Name "map-client-secret").SecretValueText
}
# Store mail password in Key Vault if it was provided
@ -299,36 +167,22 @@ if ($mailSecurePassword)
PutSecret -vaultName $keyVaultName -secretName "spring-mail-password" -secretSecureValue $mailSecurePassword
}
# Register application in Azure AD
Write-Host "=== Create Azure App registration"
$replyUrls = GenReplyUrls
./deploy_ad_app.ps1 -AD_APP_NAME "$prefix" -CLIENT_SECRET "$clientSecret" -REPLY_URLS $replyUrls
# read application id and service principal id
$appAd = Get-AzADApplication -DisplayName $prefix
$appServicePrincipal = Get-AzADServicePrincipal -ApplicationId $appAd.ApplicationId
Write-Host "Azure AD application id:"$appAd.ApplicationId
Write-Host "Azure AD app service principal:"$appServicePrincipal.Id
# read DFP application service principal
try {
$dfpAppServicePrincipal = Get-AzADServicePrincipal -DisplayName "Dynamics 365 Fraud Protection"
}
catch {
"Please check if you installed Dynamics 365 Fraud Protection to your tenant (see Readme.md)"
}
Write-Host "Dynamics 365 Fraud Protection service principal id:"$dfpAppServicePrincipal.Id
# Create Azure maps group
Write-Host "=== Create Azure Maps Group"
if (!($mapsGroup = Get-AzADGroup -DisplayName $mapsGroupName))
{
$mapsGroup = New-AzADGroup `
-DisplayName $mapsGroupName `
-MailNickname $mapsGroupName
# Register azure app if single tenant deployment
if (!($twoTenants)) {
Write-Host "=== Register azure application"
$clientAdApp = ./deploy_ad_app.ps1 `
-prefix $prefix `
-envType $envType `
-tenantId $tenantId `
-clientSecureSecret $clientSecureSecret `
}
# Register map application in Azure AD
Write-Host "== Create Azure application for maps"
$mapClientSecureSecret = ConvertTo-SecureString $mapClientSecret -AsPlainText -Force
$mapApp = CreateMapApp -mapAppName $mapAppName -MapAppSecurePassword $mapClientSecureSecret
Write-Host "Map application id:"$mapApp.mapAppId
Write-Host "Map service principal id:"$mapApp.mapSpId
# Prepare Storage Account
Write-Host "=== Provide storage account for deployment artifacts"
@ -415,40 +269,24 @@ if (!(Get-AzRoleAssignment -ObjectId $spId.PrincipalId -ResourceGroupName $deplo
}
# Read tenant short name
$tenantShortName = GetTenantShortName -TenantId $tenantId
Write-Host "=== Start deployment"
$deployment = New-AzResourceGroupDeployment `
-ResourceGroupName $deploymentResourceGroup `
-TemplateUri ("https://${deploymentStorageAccount}.blob.core.windows.net/deploy/main.json" + $token) `
-TemplateParameterFile $config `
-appClientId $appAd.ApplicationId `
-appSpId $appServicePrincipal.Id `
-dfpSpId $dfpAppServicePrincipal.Id `
-mapsGroupId $mapsGroup.Id `
-clientTenantId $clientTenantId `
-appClientId $clientAdApp.appClientId `
-appSpId $clientAdApp.appSpId `
-dfpSpId $clientAdApp.dfpSpId `
-mapAppId $mapApp.mapAppId `
-mapSpId $mapApp.mapSpId `
-deploymentIdentity $spId.Id `
-deploymentStorageAccount $deploymentStorageAccount `
-tenantShortName $tenantShortName `
-clientTenantShortName $clientAdApp.clientTenantShortName `
-appQueuesPackageUrl ("https://${deploymentStorageAccount}.blob.core.windows.net/deploy/queues/target-${version}.zip" + $token) `
-appAnalyticsPackageUrl ("https://${deploymentStorageAccount}.blob.core.windows.net/deploy/analytics/target-${version}.zip" + $token) `
-keyVaultName $keyVaultName
Write-Host "=== Set admin consent for Azure AD applicaton"
./deploy_ad_app_admin_consent.ps1 -AD_APP_NAME "$prefix"
# Assign Azure App permissions for DFP
# Works only on Windows due to AzureAD module
# on Mac grant_dfp_permissions.ps1 should be executed manually (see Readme for more details)
if (CheckOSWindows)
{
Write-Host "=== Assing DFP roles to application"
foreach ($dfpRoleName in $dfpRoles[$envType]) {
GrantDfpPermissionWin -clientAppName $prefix -dfpRoleName $dfpRoleName
}
}
Write-Host "Deployment Output"
Write-Host $deployment.OutputsString

Просмотреть файл

@ -2,77 +2,198 @@
# Licensed under the MIT license.
Param(
[string] $AD_APP_NAME,
[Parameter(Mandatory=$true)]
[string] $prefix,
[string] $CLIENT_SECRET,
[Parameter(Mandatory=$true)]
[string] $envType,
[string[]] $REPLY_URLS
[Parameter(Mandatory=$true)]
[string] $tenantId,
[SecureString] $clientSecureSecret
)
$ErrorActionPreference = "Stop"
. ./functions.ps1
# Register Azure AD Application
Write-Host "= Register App"$AD_APP_NAME
$CLIENT_ID = (az ad app list --display-name "${AD_APP_NAME}" --query "[0].appId")
if (!($CLIENT_ID))
function ConvertSecureToPlaintext
{
$output = ( az ad app create --display-name "${AD_APP_NAME}" `
--oauth2-allow-implicit-flow `
--available-to-other-tenants `
param (
[secureString] $secureStr
)
# Use different functions in powershell 5.1 and 7.x+
if (((Get-Host).Version.Major -eq "5") -And ((Get-Host).Version.Minor -eq "1")) {
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureStr)
$plaintextStr = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
} else {
$plaintextStr = ConvertFrom-SecureString -SecureString $secureStr -AsPlainText
}
return $plaintextStr
}
function deployAdApp {
Param(
[string] $AD_APP_NAME,
[string] $CLIENT_SECRET,
[string[]] $REPLY_URLS
)
$ErrorActionPreference = "Stop"
# Register Azure AD application
Write-Host "= Register App"$AD_APP_NAME
$CLIENT_ID = (az ad app list --display-name "${AD_APP_NAME}" --query "[0].appId")
if (!($CLIENT_ID))
{
$output = ( az ad app create --display-name "${AD_APP_NAME}" `
--oauth2-allow-implicit-flow `
--available-to-other-tenants `
--password "${CLIENT_SECRET}" `
--reply-urls $REPLY_URLS `
--app-roles @manifests/ad_app_roles_manifest.json `
--optional-claims @manifests/ad_app_claims_manifest.json `
--required-resource-accesses @manifests/ad_app_permissions_manifest.json )
if ($LastExitCode -ne 0) {
throw "Last command failed, check logs"
}
$CLIENT_ID = (az ad app list --display-name ${AD_APP_NAME} --query "[0].appId")
$CLIENT_ID = $CLIENT_ID.Trim('"')
Write-Host "= Update app identifier uris"
az ad app update --id "${CLIENT_ID}" `
--identifier-uris "api://${CLIENT_ID}"
if ($LastExitCode -ne 0) {
throw "Last command failed, check logs"
}
}
else {
Write-Host "= Azure AD App is already exist"
$CLIENT_ID = $CLIENT_ID.Trim('"')
}
Write-Host "= Update app properties"
az ad app update --id "${CLIENT_ID}" `
--password "${CLIENT_SECRET}" `
--reply-urls $REPLY_URLS `
--app-roles @manifests/ad_app_roles_manifest.json `
--optional-claims @manifests/ad_app_claims_manifest.json `
--required-resource-accesses @manifests/ad_app_permissions_manifest.json )
--required-resource-accesses @manifests/ad_app_permissions_manifest.json
if ($LastExitCode -ne 0) {
throw "Last command failed, check logs"
}
$CLIENT_ID = (az ad app list --display-name ${AD_APP_NAME} --query "[0].appId")
$CLIENT_ID = $CLIENT_ID.Trim('"')
Write-Host "= Check if SP already exist"
$APP_SP_ID = (az ad sp list --filter "displayname eq '${AD_APP_NAME}'" --query "[0].objectId" )
if (!($APP_SP_ID))
{
Write-Host "= Create SP"
$output = ( az ad sp create --id "${CLIENT_ID}" )
if ($LastExitCode -ne 0) {
throw "Last command failed, check logs"
}
az ad sp update --id "${CLIENT_ID}" --add tags "WindowsAzureActiveDirectoryIntegratedApp"
if ($LastExitCode -ne 0) {
throw "Last command failed, check logs"
}
Write-Host "= Update app identifier uris"
az ad app update --id "${CLIENT_ID}" `
--identifier-uris "api://${CLIENT_ID}"
# Set admin consent
Write-Host "= Set admin consent"
for ($i = 0; $i -lt 5; $i++) {
Write-Host "Retry"$i
Start-Sleep -s 10
az ad app permission admin-consent --id "${CLIENT_ID}"
if ($LastExitCode -eq 0) { break }
}
if ($LastExitCode -ne 0) {
throw "Last command failed, check logs"
if ($LastExitCode -ne 0) {
throw "Last command failed, check logs"
}
}
else {
Write-Host "= SP is already exist"
}
}
else {
Write-Host "= Azure AD App is already exist"
$CLIENT_ID = $CLIENT_ID.Trim('"')
################################################################# MAIN PROGRAM
Write-Host "== Verify Azure context..."
$currentAzureContext = Get-AzContext
if (!($currentAzureContext.Tenant.Id -eq $tenantId)) {
Write-Host "= Login to Azure"
Write-Host "Provide credentials for PS in tenant ${tenantId}"
Login-AzAccount
}
Write-Host "= Update app properties"
az ad app update --id "${CLIENT_ID}" `
--password "${CLIENT_SECRET}" `
--reply-urls $REPLY_URLS `
--required-resource-accesses @manifests/ad_app_permissions_manifest.json
if ($LastExitCode -ne 0) {
throw "Last command failed, check logs"
$az_tenant = (az account show --query "tenantId").Trim('"')
if (!($az_tenant -eq $tenantId)) {
Write-Host "= Provide credentials for Az Cli in tenant ${tenantId} and subscription ${subscriptionId}"
az login
}
Write-Host "= Check if SP already exist"
$APP_SP_ID = (az ad sp list --filter "displayname eq '${AD_APP_NAME}'" --query "[0].objectId" )
if (!($APP_SP_ID))
# Init AzureAD in windows environment
if (CheckOSWindows)
{
Write-Host "= Create SP"
$output = ( az ad sp create --id "${CLIENT_ID}" )
if ($LastExitCode -ne 0) {
throw "Last command failed, check logs"
}
az ad sp update --id "${CLIENT_ID}" --add tags "WindowsAzureActiveDirectoryIntegratedApp"
if ($LastExitCode -ne 0) {
throw "Last command failed, check logs"
}
}
else {
Write-Host "= SP is already exist"
# Will skip re-prompting credentials for AzureAD module
Connect-AzureAD -TenantId $currentAzureContext.Tenant.Id -AccountId $currentAzureContext.Account.Id
}
# Read Azure app client secret
if (!($clientSecureSecret)) {
$clientSecureSecret = Read-Host "Enter the Azure application secret" -AsSecureString
}
# Register application in Azure AD
Write-Host "== Create Azure App registration"
$replyUrls = GenReplyUrls
$clientSecret = ConvertSecureToPlaintext -SecureStr $clientSecureSecret
deployAdApp -AD_APP_NAME "$prefix" -CLIENT_SECRET "$clientSecret" -REPLY_URLS $replyUrls
# read application id and service principal id
$appAd = Get-AzADApplication -DisplayName $prefix
$appServicePrincipal = Get-AzADServicePrincipal -ApplicationId $appAd.ApplicationId
# read DFP application service principal
try {
$dfpAppServicePrincipal = Get-AzADServicePrincipal -DisplayName "Dynamics 365 Fraud Protection"
}
catch {
"Please check if you installed Dynamics 365 Fraud Protection to your tenant (see Readme.md)"
}
# Assign Azure App permissions for DFP
# Works only on Windows due to AzureAD module
# on Mac grant_dfp_permissions.ps1 shuld be execured (see Readme for more details)
if (CheckOSWindows)
{
Write-Host "== Assing DFP roles to application"
foreach ($dfpRoleName in $dfpRoles[$envType]) {
GrantDfpPermissionWin -clientAppName $prefix -dfpRoleName $dfpRoleName
}
}
# Read tenant short name
$tenantShortName = GetTenantShortName -TenantId $tenantId
# Outputs
Write-Host "== Collect shared parameters"
Write-Host "Azure AD application name:"$prefix
Write-Host "Azure AD application client id:"$appAd.ApplicationId
Write-Host "Azure AD app service principal:"$appServicePrincipal.Id
Write-Host "Dynamics 365 Fraud Protection service principal id:"$dfpAppServicePrincipal.Id
Write-Host "Tenant id:"$tenantId
Write-Host "Tenant short name:"$tenantShortName
return @{
"appClientId" = $appAd.ApplicationId
"appSpId" = $appServicePrincipal.Id
"dfpSpId" = $dfpAppServicePrincipal.Id
"clientTenantShortName" = $tenantShortName
}

217
arm/functions.ps1 Normal file
Просмотреть файл

@ -0,0 +1,217 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
$ErrorActionPreference = "Stop"
$frontendContainer = "web"
# Dict with java microservice name and path to base directory
$applications = @{
"queues" = "../backend/queues"
"analytics" = "../backend/analytics"
}
$version = Get-Date -Format FileDateTimeUniversal
# Path to fronend build directory
$frontendBuildPath = "../frontend/scripts"
# DFP roles definition
$dfpRoles = @{}
$dfpRoles["Dev"] = @(
"Sandbox_Risk_API",
"Sandbox_ManualReview_API"
)
$dfpRoles["Prod"] = @(
"Risk_API",
"ManualReview_API"
)
function BuildFrontend {
Set-Location "${baseDir}/$frontendBuildPath"
Copy-Item -Path "config.template.json" -Destination "../public/config.json"
yarn install
yarn build
if ($LastExitCode -ne 0) {
throw "Build failed, check logs"
}
New-Item -Path "${baseDir}/build" -Name "frontend" -ItemType "directory" -ErrorAction Ignore
Copy-Item -Path "../build/*" -Destination "${baseDir}/build/frontend" -Recurse
Set-Location $baseDir
}
function BuildBackend {
foreach ($app in $applications.GetEnumerator()) {
Write-Host "=== Building $($app.Name) app..."
Set-Location "${baseDir}/$($app.Value)"
if (CheckOSWindows) {
./gradlew.bat clean packageDist --console=plain
} else {
./gradlew clean packageDist --console=plain
}
if ($LastExitCode -ne 0) {
throw "Build failed, check logs"
}
New-Item -Path "${baseDir}/build" -Name "$($app.Name)" -ItemType "directory" -ErrorAction Ignore
Copy-Item -Path "build/dist/target.zip" -Destination "${baseDir}/build/$($app.Name)/target.zip" -Recurse
}
}
function GetRandomPassword {
$password = -join ('abcdefghkmnrstuvwxyzABCDEFGHKLMNPRSTUVWXYZ0123456789'.ToCharArray() | Get-Random -Count 30)
$password += -join ('!~.'.ToCharArray() | Get-Random -Count 2)
return $password
}
function ProvideCleanContainer {
param (
$storageContainer,
$storageContext
)
$sc = Get-AzStorageContainer -Name $storageContainer -Context $storageContext -ErrorAction Ignore
if (!$sc) {
Write-Host "= Create container $storageContainer"
New-AzStorageContainer `
-Name $storageContainer `
-Context $storageContext
}
else
{
Write-Host "= Clean container $storageContainer"
Get-AzStorageBlob -Container $storageContainer -Context $storageContext `
| Where-Object { $_.Name -notMatch '^.*target.*zip' } `
| Remove-AzStorageBlob
}
}
function GrantDfpPermissionWin {
param (
[string] $clientAppName,
[string] $dfpRoleName
)
Write-Host "= Assign role $dfpRoleName to app" $clientAppName
$app_name = "Dynamics 365 Fraud Protection"
$sp = Get-AzureADServicePrincipal -Filter "displayName eq '$app_name'"
$c_appRole = $sp.AppRoles | Where-Object { $_.DisplayName -eq $dfpRoleName }
for ($i = 0; $i -lt 5; $i++) {
Start-Sleep -s 5
$c_sp = Get-AzureADServicePrincipal -Filter "displayName eq '$clientAppName'"
if ($c_sp) { break }
}
$roleAssigned = Get-AzureADServiceAppRoleAssignedTo -ObjectId $c_sp.ObjectId `
| Where-Object { $_.Id -eq $c_appRole.Id }
if(!($roleAssigned))
{
Write-host "Assigning DFP role ..."
New-AzureADServiceAppRoleAssignment `
-ObjectId $c_sp.ObjectId `
-PrincipalId $c_sp.ObjectId `
-ResourceId $sp.ObjectId `
-Id $c_appRole.Id
}
else
{
Write-host "DFP role is already assigned"
}
}
function CheckOSWindows
{
$envOS = Get-ChildItem -Path Env:OS -ErrorAction Ignore
return ( $envOS -And $envOS.value.Contains('Windows') )
}
function PutSecret {
param (
[string] $vaultName,
[string] $secretName,
[string] $secretValue,
[SecureString] $secretSecureValue
)
Write-Host "= Put secret $secretName to Key Vault $vaultName"
if ($secretValue) {
$secretSecureValue = ConvertTo-SecureString $secretValue -AsPlainText -Force
}
Set-AzKeyVaultSecret `
-VaultName $vaultName `
-Name $secretName `
-SecretValue $secretSecureValue
}
function GenReplyUrls {
# Define Azure app reply urls
$replyUrls = @(
"https://$prefix.azurefd.net/login",
"https://$prefix-queues.azurewebsites.net/oauth2-redirect.html",
"https://$prefix-queues-secondary.azurewebsites.net/oauth2-redirect.html",
"https://$prefix-analytics.azurewebsites.net/oauth2-redirect.html",
"https://$prefix-analytics-secondary.azurewebsites.net/oauth2-redirect.html"
)
$localSwaggerUri = "http://localhost:8080/oauth2-redirect.html"
$localFrontUri = "http://localhost:3000/login"
if ($envType -eq "Dev") {
$replyUrls += @($localSwaggerUri, $localFrontUri)
}
if ($customDomain -And ($customDomain -ne "UNDEFINED")) {
$replyUrls += "https://${customDomain}/login"
}
return $replyUrls
}
function GetTenantShortName {
param(
[string] $TenantId
)
foreach ($domain in (Get-AzTenant -TenantId $tenantId).domains) {
if ($domain -match '(.*)\.onmicrosoft\.com') {
return $Matches.1
}
}
}
function CreateMapApp {
param (
[string] $mapAppName,
[secureString] $MapAppSecurePassword
)
Write-Host "= Create application"
if (!($mapApp = Get-AzADApplication -DisplayName $mapAppName)) {
$mapApp = New-AzADApplication `
-DisplayName $mapAppName `
-Password $mapAppSecurePassword `
-IdentifierUris "http://${mapAppName}"
Write-Host "= Update application identifier uri"
Update-AzADApplication `
-ApplicationId $mapApp.ApplicationId `
-IdentifierUri "api://$($mapApp.ApplicationId)"
} else {
Write-Host "Application is already exist"
}
Write-Host "= Create service principal"
if (!($mapSp = Get-AzADServicePrincipal -DisplayName $mapAppName)) {
$mapSp = New-AzADServicePrincipal `
-DisplayName $mapAppName `
-ApplicationId $mapApp.ApplicationId `
-SkipAssignment
} else {
Write-Host "Service principal is already exist"
}
return @{
"mapAppId" = $mapApp.ApplicationId
"mapSpId" = $mapSp.Id
}
}

Просмотреть файл

@ -31,6 +31,9 @@
"tenantId": {
"type": "string"
},
"clientTenantId": {
"type": "string"
},
"keyVaultName": {
"type": "string"
},
@ -106,7 +109,11 @@
"type": "string",
"defaultValue": ""
},
"mapsGroupId": {
"mapAppId": {
"type": "string",
"defaultValue": ""
},
"mapSpId": {
"type": "string",
"defaultValue": ""
},
@ -126,7 +133,7 @@
"dfpSpId": {
"type": "string"
},
"tenantShortName": {
"clientTenantShortName": {
"type": "string"
},
"alertReceivers": {
@ -343,7 +350,7 @@
"value": "[parameters('mapAccountSku')]"
},
"principalId": {
"value": "[parameters('mapsGroupId')]"
"value": "[parameters('mapSpId')]"
}
}
}
@ -607,6 +614,9 @@
"tenantId": {
"value": "[parameters('tenantId')]"
},
"clientTenantId": {
"value": "[parameters('clientTenantId')]"
},
"appJavaOpts": {
"value": "[variables('appJavaOpts')]"
},
@ -622,8 +632,8 @@
"dfpSpId": {
"value": "[parameters('dfpSpId')]"
},
"tenantShortName": {
"value": "[parameters('tenantShortName')]"
"clientTenantShortName": {
"value": "[parameters('clientTenantShortName')]"
},
"keyVaultEndpoint": {
"value": "[variables('keyVaultEndpoint')]"
@ -636,6 +646,9 @@
},
"logWorkspaceName": {
"value": "[variables('LogWorkspaceName')]"
},
"mapAppId": {
"value": "[parameters('mapAppId')]"
}
}
}
@ -703,6 +716,9 @@
"tenantId": {
"value": "[parameters('tenantId')]"
},
"clientTenantId": {
"value": "[parameters('clientTenantId')]"
},
"appJavaOpts": {
"value": "[variables('appJavaOptsSecondary')]"
},
@ -718,8 +734,8 @@
"dfpSpId": {
"value": "[parameters('dfpSpId')]"
},
"tenantShortName": {
"value": "[parameters('tenantShortName')]"
"clientTenantShortName": {
"value": "[parameters('clientTenantShortName')]"
},
"keyVaultEndpoint": {
"value": "[variables('keyVaultEndpoint')]"
@ -732,6 +748,9 @@
},
"logWorkspaceName": {
"value": "[variables('LogWorkspaceSecondaryName')]"
},
"mapAppId": {
"value": "[parameters('mapAppId')]"
}
}
}
@ -799,6 +818,9 @@
"tenantId": {
"value": "[parameters('tenantId')]"
},
"clientTenantId": {
"value": "[parameters('clientTenantId')]"
},
"appJavaOpts": {
"value": "[variables('appJavaOpts')]"
},
@ -814,8 +836,8 @@
"dfpSpId": {
"value": "[parameters('dfpSpId')]"
},
"tenantShortName": {
"value": "[parameters('tenantShortName')]"
"clientTenantShortName": {
"value": "[parameters('clientTenantShortName')]"
},
"keyVaultEndpoint": {
"value": "[variables('keyVaultEndpoint')]"
@ -828,6 +850,9 @@
},
"logWorkspaceName": {
"value": "[variables('LogWorkspaceName')]"
},
"mapAppId": {
"value": "[parameters('mapAppId')]"
}
}
}
@ -895,6 +920,9 @@
"tenantId": {
"value": "[parameters('tenantId')]"
},
"clientTenantId": {
"value": "[parameters('clientTenantId')]"
},
"appJavaOpts": {
"value": "[variables('appJavaOptsSecondary')]"
},
@ -910,8 +938,8 @@
"dfpSpId": {
"value": "[parameters('dfpSpId')]"
},
"tenantShortName": {
"value": "[parameters('tenantShortName')]"
"clientTenantShortName": {
"value": "[parameters('clientTenantShortName')]"
},
"keyVaultEndpoint": {
"value": "[variables('keyVaultEndpoint')]"
@ -924,6 +952,9 @@
},
"logWorkspaceName": {
"value": "[variables('LogWorkspaceSecondaryName')]"
},
"mapAppId": {
"value": "[parameters('mapAppId')]"
}
}
}
@ -1201,7 +1232,7 @@
"value": "[parameters('appClientId')]"
},
"tenantId":{
"value": "[parameters('tenantId')]"
"value": "[parameters('clientTenantId')]"
},
"mapClientId":{
"value": "[reference('mapAccountTemplate').outputs.mapsAccountClientId.value]"

Просмотреть файл

@ -8,9 +8,24 @@
"subscriptionId": {
"value": "00000000-0000-0000-0000-000000000000"
},
"clientTenantId": {
"value": "00000000-0000-0000-0000-000000000000"
},
"tenantId": {
"value": "00000000-0000-0000-0000-000000000000"
},
"clientTenantShortName": {
"value": "gddfp"
},
"appClientId": {
"value": "00000000-0000-0000-0000-000000000000"
},
"appSpId": {
"value": "00000000-0000-0000-0000-000000000000"
},
"dfpSpId": {
"value": "00000000-0000-0000-0000-000000000000"
},
"envType": {
"value": "Dev"
},

Просмотреть файл

@ -20,7 +20,7 @@
},
"guid": {
"type": "string",
"defaultValue": "[guid(resourceGroup().id)]",
"defaultValue": "[guid(parameters('principalId'))]",
"metadata": {
"description": "Input string for new GUID associated with assigning built in role types"
}
@ -49,7 +49,7 @@
"properties": {
"roleDefinitionId": "[variables('Azure Maps Data Reader')]",
"principalId": "[parameters('principalId')]",
"principalType": "Group"
"principalType": "ServicePrincipal"
}
}
],

Просмотреть файл

@ -548,6 +548,28 @@
"options": {}
}
},
{
"type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers",
"apiVersion": "2020-04-01",
"name": "[concat(parameters('cosmosDbAccountName'), '/QueuesDB/EmailDomains')]",
"dependsOn": [
"[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('cosmosDbAccountName'), 'QueuesDB')]",
"[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDbAccountName'))]"
],
"properties": {
"resource": {
"id": "EmailDomains",
"partitionKey": {
"paths": [
"/id"
],
"kind": "Hash"
},
"defaultTtl": -1
},
"options": {}
}
},
{
"type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers",
"apiVersion": "2020-04-01",

Просмотреть файл

@ -46,39 +46,54 @@
"dfphubPolicyName": "dfpSend"
},
"resources": [
{
"type": "Microsoft.EventHub/namespaces",
"apiVersion": "2018-01-01-preview",
"name": "[parameters('ehubName')]",
"location": "[parameters('location')]",
"sku": {
"name": "[parameters('eventHubSku')]",
"tier": "[parameters('eventHubSku')]",
"capacity": "[parameters('ehubCapacity')]"
},
"properties": {
"zoneRedundant": false,
"isAutoInflateEnabled": true,
"maximumThroughputUnits": "[parameters('ehubMaximumThroughputUnits')]",
"kafkaEnabled": true
}
},
{
"type": "Microsoft.EventHub/namespaces/AuthorizationRules",
"apiVersion": "2017-04-01",
"name": "[concat(parameters('ehubName'), '/RootManageSharedAccessKey')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.EventHub/namespaces', parameters('ehubName'))]"
],
"properties": {
"rights": [
"Listen",
"Manage",
"Send"
]
}
},
{
"type": "Microsoft.EventHub/namespaces",
"apiVersion": "2018-01-01-preview",
"name": "[parameters('ehubName')]",
"location": "[parameters('location')]",
"sku": {
"name": "[parameters('eventHubSku')]",
"tier": "[parameters('eventHubSku')]",
"capacity": "[parameters('ehubCapacity')]"
},
"properties": {
"zoneRedundant": false,
"isAutoInflateEnabled": true,
"maximumThroughputUnits": "[parameters('ehubMaximumThroughputUnits')]",
"kafkaEnabled": true
}
},
{
"type": "Microsoft.EventHub/namespaces/AuthorizationRules",
"apiVersion": "2017-04-01",
"name": "[concat(parameters('ehubName'), '/RootManageSharedAccessKey')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.EventHub/namespaces', parameters('ehubName'))]"
],
"properties": {
"rights": [
"Listen",
"Manage",
"Send"
]
}
},
{
"type": "Microsoft.EventHub/namespaces/networkRuleSets",
"apiVersion": "2018-01-01-preview",
"name": "[concat(parameters('ehubName'), '/default')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.EventHub/namespaces', parameters('ehubName'))]",
"[resourceId('Microsoft.EventHub/namespaces/AuthorizationRules',parameters('ehubName'),'RootManageSharedAccessKey')]"
],
"properties": {
"defaultAction": "Allow",
"virtualNetworkRules": [],
"ipRules": []
}
},
{
"type": "Microsoft.EventHub/namespaces/eventhubs",
"apiVersion": "2017-04-01",
@ -190,20 +205,6 @@
"partitionCount": "[parameters('ehubNumPartitions')]",
"status": "Active"
}
},
{
"type": "Microsoft.EventHub/namespaces/networkRuleSets",
"apiVersion": "2018-01-01-preview",
"name": "[concat(parameters('ehubName'), '/default')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.EventHub/namespaces', parameters('ehubName'))]"
],
"properties": {
"defaultAction": "Allow",
"virtualNetworkRules": [],
"ipRules": []
}
},
{
"type": "Microsoft.EventHub/namespaces/eventhubs/authorizationRules",

Просмотреть файл

@ -75,6 +75,9 @@
"tenantId": {
"type": "string"
},
"clientTenantId": {
"type": "string"
},
"appJavaOpts": {
"type": "string"
},
@ -99,11 +102,14 @@
"appInsightName": {
"type": "String"
},
"tenantShortName": {
"clientTenantShortName": {
"type": "String"
},
"logWorkspaceName": {
"type": "string"
},
"mapAppId": {
"type": "string"
}
},
"variables": {
@ -174,10 +180,18 @@
"name": "APP_SP_ID",
"value": "[parameters('appSpId')]"
},
{
"name": "MAP_CLIENT_ID",
"value": "[parameters('mapAppId')]"
},
{
"name": "TENANT_ID",
"value": "[parameters('tenantId')]"
},
{
"name": "CLIENT_TENANT_ID",
"value": "[parameters('clientTenantId')]"
},
{
"name": "MAIL_USERNAME",
"value": "[parameters('mailUsername')]"
@ -195,8 +209,8 @@
"value": "[parameters('dfpSpId')]"
},
{
"name": "TENANT_SHORT_NAME",
"value": "[parameters('tenantShortName')]"
"name": "CLIENT_TENANT_SHORT_NAME",
"value": "[parameters('clientTenantShortName')]"
},
{
"name": "KEYVAULT_ENDPOINT",

Просмотреть файл

@ -55,17 +55,25 @@ unoptimized code to avoid unexpected costs in Azure resources.
### Configure integration
To get the whole environment installed, please, refer to [deployment](../arm/README.md).
To get environment variables that are used by application to connect to cloud environment, run [script](./getEnv.sh)
To get environment variables that are used by application to connect to cloud environment, run [script](./getEnv.sh)
(works only for single-tenant deployments)
with specified environment name:
```shell script
/getEnv.sh "<name_that_is_prefix_of_deployment>"
```
__Warning!__ You need to be unlogined in "Az Cli" or be logined with appropriate account. In first case the script will
route you to login page.
__Warning!__ In order to get all variables you should have enough [permissions](#prerequisites).
>__Warning!__ You need to be unlogined in "Az Cli" or be logined with appropriate account. In first case the script will
route you to login page.
>__Warning!__ In order to get all variables you should have enough [permissions](#prerequisites) and you should
be authorized to get/list secrets in the KeyVault of the current installation.
>__Warning!__ For some tenants definition of SHORT_TENANT_NAME doesn't work properly. Please, define it manually as substring
of your Tenant's Primary domain before `.onmicrosoft.com`.
The script output under the `-----[ Result ]-----` row will contain the set of variables that you need to have
established in your environment before local launch or comprehensive build.
established in your environment before local launch or comprehensive build. Also, please, be aware that application
which mentioned in the `CLIENT_ID` variable should have permissions to get/list secrets in the KeyVault of
the current installation.
__Warning!__ Values in the script output are unescaped. Be careful if there are special symbols.

Просмотреть файл

@ -114,8 +114,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -128,8 +128,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -251,8 +251,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -265,8 +265,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -378,8 +378,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -392,8 +392,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -509,8 +509,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -523,8 +523,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -630,8 +630,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -644,8 +644,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -755,8 +755,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -769,8 +769,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -892,8 +892,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -906,8 +906,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -1024,8 +1024,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -1038,8 +1038,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -1158,8 +1158,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -1172,8 +1172,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -1290,8 +1290,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -1304,8 +1304,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -1456,8 +1456,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -1470,8 +1470,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -1632,8 +1632,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -1646,8 +1646,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -1812,8 +1812,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -1826,8 +1826,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -1982,8 +1982,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -1996,8 +1996,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -2148,8 +2148,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -2162,8 +2162,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -2314,8 +2314,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -2328,8 +2328,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -2478,8 +2478,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -2492,8 +2492,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -2633,8 +2633,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -2647,8 +2647,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -2797,8 +2797,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -2811,8 +2811,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -2952,8 +2952,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -2966,8 +2966,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -3127,8 +3127,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -3141,8 +3141,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -3247,8 +3247,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -3261,8 +3261,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -3362,8 +3362,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -3376,8 +3376,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -3488,8 +3488,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -3502,8 +3502,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -3650,8 +3650,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -3664,8 +3664,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -3803,8 +3803,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -3817,8 +3817,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -3924,8 +3924,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -3938,8 +3938,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -4048,8 +4048,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -4062,8 +4062,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -4171,8 +4171,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -4185,8 +4185,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -4309,8 +4309,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -4323,8 +4323,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -4447,8 +4447,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -4461,8 +4461,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -4578,8 +4578,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -4592,8 +4592,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -4716,8 +4716,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -4730,8 +4730,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -4839,8 +4839,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -4853,8 +4853,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -4977,8 +4977,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -4991,8 +4991,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -5107,8 +5107,8 @@
}
}
},
"500" : {
"description" : "Internal Server Error",
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
@ -5121,8 +5121,8 @@
}
}
},
"400" : {
"description" : "Bad Request",
"500" : {
"description" : "Internal Server Error",
"content" : {
"application/json" : {
"schema" : {
@ -5793,7 +5793,13 @@
"type" : "string",
"format" : "date-time"
},
"failedStatusMessage" : {
"previousRunSuccessfull" : {
"type" : "boolean"
},
"lastFailedRunMessage" : {
"type" : "string"
},
"instanceId" : {
"type" : "string"
},
"get_etag" : {
@ -5834,13 +5840,13 @@
"type" : "integer",
"format" : "int64"
},
"zero" : {
"type" : "boolean"
},
"nano" : {
"type" : "integer",
"format" : "int32"
},
"zero" : {
"type" : "boolean"
},
"negative" : {
"type" : "boolean"
},

Просмотреть файл

@ -26,7 +26,9 @@ public class Task {
private String id;
private TaskStatus status;
private OffsetDateTime previousRun;
private String failedStatusMessage;
private Boolean previousRunSuccessfull;
private String lastFailedRunMessage;
private String instanceId;
@Version
@SuppressWarnings("java:S116")

Просмотреть файл

@ -96,7 +96,7 @@ public class TaskService {
allTasks.forEach(task -> {
if (!READY.equals(task.getStatus())) {
task.setStatus(READY);
task.setFailedStatusMessage("Restored manually");
task.setLastFailedRunMessage("Restored manually");
taskRepository.save(task);
}
});
@ -174,13 +174,13 @@ public class TaskService {
(long) (timeout.toSeconds() * applicationProperties.getTaskResetTimeoutMultiplier()));
if (timeAfterPreviousRun.compareTo(acceptableDelayBeforeWarning) > 0) {
log.warn("Task [{}] is idle for too long. Last execution was [{}] minutes ago with status message: [{}]",
task.getId(), timeAfterPreviousRun.toMinutes(), task.getFailedStatusMessage());
task.getId(), timeAfterPreviousRun.toMinutes(), task.getLastFailedRunMessage());
}
if (!READY.equals(task.getStatus()) && timeAfterPreviousRun.compareTo(acceptableDelayBeforeReset) > 0) {
try {
log.info("Start [{}] task restore", task.getId());
task.setStatus(READY);
task.setFailedStatusMessage("Restored after long downtime");
task.setLastFailedRunMessage("Restored after long downtime");
taskRepository.save(task);
log.info("Task [{}] has been restored", task.getId());
} catch (CosmosDBAccessException e) {
@ -225,7 +225,7 @@ public class TaskService {
* have to be saved to the database with updated {@link Task#getStatus()}.
* </p>
* In case task execution failed with an exception,
* {@link Task#getFailedStatusMessage()} is set to
* {@link Task#getLastFailedRunMessage()} is set to
* {@link Exception#getMessage()} and new state saved into the database.
*
* @param task which should represent a lock object
@ -246,6 +246,7 @@ public class TaskService {
// acquire a lock
OffsetDateTime startTime = OffsetDateTime.now();
task.setStatus(RUNNING);
task.setInstanceId(applicationProperties.getInstanceId());
if (task.getPreviousRun() == null){
task.setPreviousRun(startTime);
}
@ -279,10 +280,12 @@ public class TaskService {
.whenComplete((result, exception) -> {
runningTask.setStatus(READY);
runningTask.setPreviousRun(startTime);
runningTask.setPreviousRunSuccessfull(true);
if (exception != null) {
log.warn("Task [{}] finished its execution with an exception.",
runningTask.getId(), exception);
runningTask.setFailedStatusMessage(exception.getMessage());
runningTask.setLastFailedRunMessage(exception.getMessage());
runningTask.setPreviousRunSuccessfull(false);
taskRepository.save(runningTask);
} else if (result.isEmpty()) {
log.info("Task [{}] finished its execution with empty result.", runningTask.getId());

Просмотреть файл

@ -33,7 +33,7 @@ azure:
cosmosdb:
default-ttl: P1827D
dfp:
purchase-status-event-url: https://${TENANT_SHORT_NAME}-${TENANT_ID}.api.dfp.dynamics-int.com/v1.0/merchantservices/events/PurchaseStatus
purchase-status-event-url: https://${CLIENT_TENANT_SHORT_NAME}-${CLIENT_TENANT_ID}.api.dfp.dynamics-int.com/v1.0/merchantservices/events/PurchaseStatus
graph-api:
role-assignments-url: https://graph.microsoft.com/v1.0/servicePrincipals/${DFP_SP_ID}/appRoleAssignedTo
user-role-assignments-url-template: https://graph.microsoft.com/v1.0/users/#user_id#/appRoleAssignments?$filter=resourceId eq ${DFP_SP_ID}
@ -41,6 +41,8 @@ azure:
user-url-template: https://graph.microsoft.com/v1.0/users/#user_id#
app-service-principal-url: https://graph.microsoft.com/v1.0/servicePrincipals/${DFP_SP_ID}
user-photo-url-template: https://graph.microsoft.com/beta/users/#user_id#/photo/$value
timeout: PT5S
retries: 2
role-mapping:
ManualReviewFraudManager: ADMIN_MANAGER
ManualReviewSeniorAnalyst: SENIOR_ANALYST
@ -61,7 +63,7 @@ azure:
swagger:
# the https://cors-anywhere.herokuapp.com/ prefix is only for dev environments
token-url: https://cors-anywhere.herokuapp.com/https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token
token-url: https://cors-anywhere.herokuapp.com/https://login.microsoftonline.com/${CLIENT_TENANT_ID}/oauth2/v2.0/token
spring:
security:
@ -74,9 +76,9 @@ spring:
scope: https://api.dfp.microsoft-int.com/.default
provider:
azure-graph-api:
token-uri: https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token
token-uri: https://login.microsoftonline.com/${CLIENT_TENANT_ID}/oauth2/v2.0/token
azure-dfp-api:
token-uri: https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token
token-uri: https://login.microsoftonline.com/${CLIENT_TENANT_ID}/oauth2/v2.0/token
resilience4j.retry:
instances:

Просмотреть файл

@ -27,4 +27,4 @@ azure:
# Use service principle to authenticate locally
client-key: ${CLIENT_SECRET}
client-id: ${CLIENT_ID}
tenant-id: ${TENANT_ID}
tenant-id: ${CLIENT_TENANT_ID}

Просмотреть файл

@ -32,7 +32,7 @@ azure:
cosmosdb:
default-ttl: P1827D
dfp:
purchase-status-event-url: https://${TENANT_SHORT_NAME}-${TENANT_ID}.api.dfp.dynamics.com/v1.0/merchantservices/events/PurchaseStatus
purchase-status-event-url: https://${CLIENT_TENANT_SHORT_NAME}-${CLIENT_TENANT_ID}.api.dfp.dynamics.com/v1.0/merchantservices/events/PurchaseStatus
graph-api:
role-assignments-url: https://graph.microsoft.com/v1.0/servicePrincipals/${DFP_SP_ID}/appRoleAssignedTo
user-role-assignments-url-template: https://graph.microsoft.com/v1.0/users/#user_id#/appRoleAssignments?$filter=resourceId eq ${DFP_SP_ID}
@ -40,6 +40,8 @@ azure:
user-url-template: https://graph.microsoft.com/v1.0/users/#user_id#
app-service-principal-url: https://graph.microsoft.com/v1.0/servicePrincipals/${DFP_SP_ID}
user-photo-url-template: https://graph.microsoft.com/beta/users/#user_id#/photo/$value
timeout: PT5S
retries: 2
role-mapping:
ManualReviewFraudManager: ADMIN_MANAGER
ManualReviewSeniorAnalyst: SENIOR_ANALYST
@ -56,7 +58,7 @@ azure:
health-check-allowed-delay: PT60M
swagger:
token-url: https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token
token-url: https://login.microsoftonline.com/${CLIENT_TENANT_ID}/oauth2/v2.0/token
spring:
security:
@ -69,9 +71,9 @@ spring:
scope: https://api.dfp.microsoft.com/.default
provider:
azure-graph-api:
token-uri: https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token
token-uri: https://login.microsoftonline.com/${CLIENT_TENANT_ID}/oauth2/v2.0/token
azure-dfp-api:
token-uri: https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token
token-uri: https://login.microsoftonline.com/${CLIENT_TENANT_ID}/oauth2/v2.0/token
resilience4j.retry:
instances:

Просмотреть файл

@ -54,7 +54,7 @@ azure:
session-stateless: true
app-id-uri: api://${CLIENT_ID}
dfp:
purchase-status-event-url: https://${TENANT_SHORT_NAME}-${TENANT_ID}.api.dfp.dynamics-int.com/v1.0/merchantservices/events/PurchaseStatus
purchase-status-event-url: https://${CLIENT_TENANT_SHORT_NAME}-${CLIENT_TENANT_ID}.api.dfp.dynamics-int.com/v1.0/merchantservices/events/PurchaseStatus
graph-api:
role-assignments-url: https://graph.microsoft.com/v1.0/servicePrincipals/${DFP_SP_ID}/appRoleAssignedTo
user-role-assignments-url-template: https://graph.microsoft.com/v1.0/users/#user_id#/appRoleAssignments?$filter=resourceId eq ${DFP_SP_ID}
@ -62,6 +62,8 @@ azure:
user-url-template: https://graph.microsoft.com/v1.0/users/#user_id#
app-service-principal-url: https://graph.microsoft.com/v1.0/servicePrincipals/${DFP_SP_ID}
user-photo-url-template: https://graph.microsoft.com/beta/users/#user_id#/photo/$value
timeout: PT5S
retries: 2
role-mapping:
ManualReviewFraudManager: ADMIN_MANAGER
ManualReviewSeniorAnalyst: SENIOR_ANALYST
@ -110,9 +112,9 @@ azure:
group: ${spring.application.name}
swagger:
auth-url: https://login.microsoftonline.com/${TENANT_ID}/oauth2/authorize?resource=${CLIENT_ID}
auth-url: https://login.microsoftonline.com/${CLIENT_TENANT_ID}/oauth2/authorize?resource=${CLIENT_ID}
# the https://cors-anywhere.herokuapp.com/ prefix is only for dev environments
token-url: https://cors-anywhere.herokuapp.com/https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token
token-url: https://cors-anywhere.herokuapp.com/https://login.microsoftonline.com/${CLIENT_TENANT_ID}/oauth2/v2.0/token
token-scope: ${azure.activedirectory.app-id-uri}/.default
spring:
@ -134,9 +136,9 @@ spring:
scope: https://api.dfp.microsoft-int.com/.default
provider:
azure-graph-api:
token-uri: https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token
token-uri: https://login.microsoftonline.com/${CLIENT_TENANT_ID}/oauth2/v2.0/token
azure-dfp-api:
token-uri: https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token
token-uri: https://login.microsoftonline.com/${CLIENT_TENANT_ID}/oauth2/v2.0/token
aop:
proxyTargetClass: true
mail:

Просмотреть файл

@ -4,6 +4,7 @@
package com.griddynamics.msd365fp.manualreview.analytics.service;
import com.griddynamics.msd365fp.manualreview.analytics.config.ModelMapperConfig;
import com.griddynamics.msd365fp.manualreview.analytics.config.properties.ApplicationProperties;
import com.griddynamics.msd365fp.manualreview.analytics.model.persistence.*;
import com.griddynamics.msd365fp.manualreview.analytics.repository.*;
import com.griddynamics.msd365fp.manualreview.model.Label;
@ -52,6 +53,10 @@ class StreamServiceTest {
ItemPlacementActivityRepository itemPlacementActivityRepository;
@Mock
QueueSizeCalculationActivityRepository queueSizeCalculationActivityRepository;
@Mock
HealthCheckRepository healthCheckRepository;
@Mock
ApplicationProperties applicationProperties;
@Captor
private ArgumentCaptor<Iterable<ItemPlacementActivityEntity>> placementCaptor;
@ -73,7 +78,7 @@ class StreamServiceTest {
ModelMapper modelMapper = new ModelMapperConfig().modelMapper();
this.streamService = new StreamService(resolutionRepository, itemLockActivityRepository,
collectedQueueInfoRepository, queueSizeCalculationActivityRepository, performanceRepository,
itemPlacementActivityRepository, modelMapper);
itemPlacementActivityRepository, healthCheckRepository, applicationProperties, modelMapper);
}
/**

Просмотреть файл

@ -33,7 +33,7 @@ spring:
scope: https://graph.microsoft.com/.default
provider:
azure-graph-api:
token-uri: https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token
token-uri: https://login.microsoftonline.com/${CLIENT_TENANT_ID}/oauth2/v2.0/token
```

Просмотреть файл

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Просмотреть файл

@ -131,6 +131,8 @@ public class AnalystClient {
.uri(properties.getUserPhotoUrlTemplate().replace(USER_ID_PLACEHOLDER, id))
.retrieve()
.bodyToMono(byte[].class)
.timeout(properties.getTimeout())
.retry(properties.getRetries() == null ? 0 : properties.getRetries())
.block();
} catch (WebClientResponseException.NotFound e) {
throw new EmptySourceException();
@ -161,6 +163,8 @@ public class AnalystClient {
.uri(properties.getAppServicePrincipalUrl())
.retrieve()
.bodyToMono(ServicePrincipalDTO.class)
.timeout(properties.getTimeout())
.retry(properties.getRetries() == null ? 0 : properties.getRetries())
.block(Duration.of(1, ChronoUnit.MINUTES));
return Stream.ofNullable(res);
@ -173,6 +177,8 @@ public class AnalystClient {
.uri(properties.getUserUrlTemplate().replace(USER_ID_PLACEHOLDER, id))
.retrieve()
.bodyToMono(UserDTO.class)
.timeout(properties.getTimeout())
.retry(properties.getRetries() == null ? 0 : properties.getRetries())
.block();
}
@ -213,6 +219,8 @@ public class AnalystClient {
.uri(url)
.retrieve()
.bodyToMono(cls)
.timeout(properties.getTimeout())
.retry(properties.getRetries() == null ? 0 : properties.getRetries())
.block();
url = null;
if (response != null) {

Просмотреть файл

@ -8,6 +8,7 @@ import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import java.time.Duration;
import java.util.Map;
@Getter
@ -31,5 +32,7 @@ public class AnalystClientProperties {
private final String usersUrl;
private final String userUrlTemplate;
private final String userPhotoUrlTemplate;
private final Duration timeout;
private final Long retries;
}

Просмотреть файл

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Просмотреть файл

@ -55,7 +55,9 @@ public class ExtendedCosmosContainer {
final FeedOptions feedOptions = new FeedOptions();
feedOptions.enableCrossPartitionQuery(true);
feedOptions.maxItemCount(size);
feedOptions.requestContinuation(continuationToken);
if (continuationToken != null) {
feedOptions.requestContinuation(continuationToken);
}
Flux<FeedResponse<CosmosItemProperties>> feedResponseFlux =
container.queryItems(query, feedOptions);
FeedResponse<CosmosItemProperties> res = feedResponseFlux.blockFirst(Duration.ofSeconds(DEFAULT_COSMOS_TIMEOUT_SEC));

Просмотреть файл

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Просмотреть файл

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Просмотреть файл

@ -18,13 +18,13 @@ AD_APP_NAME=${SOLUTION_NAME}
GROUP_NAME=${SOLUTION_NAME}-rg
ANALYTICS_APP_SERVICE_NAME=${SOLUTION_NAME}-analytics
TENANT_ID=$(az account show --query=tenantId)
TENANT_ID="${TENANT_ID%\"}"
export TENANT_ID="${TENANT_ID#\"}"
CLIENT_TENANT_ID=$(az account show --query=tenantId)
CLIENT_TENANT_ID="${CLIENT_TENANT_ID%\"}"
export CLIENT_TENANT_ID="${CLIENT_TENANT_ID#\"}"
TENANT_SHORT_NAME=$(az account show --query=name)
TENANT_SHORT_NAME="${TENANT_SHORT_NAME%.onmicrosoft.com\"}"
export TENANT_SHORT_NAME="${TENANT_SHORT_NAME#\"}"
CLIENT_TENANT_SHORT_NAME=$(az account show --query=name)
CLIENT_TENANT_SHORT_NAME="${CLIENT_TENANT_SHORT_NAME%.onmicrosoft.com\"}"
export CLIENT_TENANT_SHORT_NAME="${CLIENT_TENANT_SHORT_NAME#\"}"
COSMOSDB_ACCOUNT_NAME=${SOLUTION_NAME}-storage
COSMOSDB_ENDPOINT=$(az cosmosdb show \
@ -136,7 +136,10 @@ echo COSMOSDB_ENDPOINT=${COSMOSDB_ENDPOINT}
echo COSMOSDB_KEY=${COSMOSDB_KEY}
echo CLIENT_ID=${CLIENT_ID}
echo CLIENT_SECRET=${CLIENT_SECRET}
echo TENANT_ID=${TENANT_ID}
echo MAP_CLIENT_ID=${CLIENT_ID}
echo MAP_CLIENT_SECRET=${CLIENT_SECRET}
echo TENANT_ID=${CLIENT_TENANT_ID}
echo CLIENT_TENANT_ID=${CLIENT_TENANT_ID}
echo APP_SP_ID=${APP_SP_ID}
echo DFP_SP_ID=${DFP_SP_ID}
echo EVENT_HUB_CONNECTION_STRING=${EVENT_HUB_CONNECTION_STRING}
@ -148,5 +151,5 @@ echo MAIL_SMTP_PORT=${MAIL_SMTP_PORT}
echo MAIL_USERNAME=${MAIL_USERNAME}
echo MAIL_PASSWORD=${MAIL_PASSWORD}
echo KEYVAULT_ENDPOINT=${KEYVAULT_ENDPOINT}
echo TENANT_SHORT_NAME=${TENANT_SHORT_NAME}
echo SPRING_PROFILES_ACTIVE=local
echo CLIENT_TENANT_SHORT_NAME=${CLIENT_TENANT_SHORT_NAME}
echo SPRING_PROFILES_ACTIVE=local

Просмотреть файл

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Просмотреть файл

@ -30,6 +30,8 @@ jar {
dependencies {
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.2'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.2'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2'
implementation 'io.swagger.core.v3:swagger-annotations:2.1.2'
implementation 'org.apache.commons:commons-lang3:3.9'
implementation 'org.springframework:spring-core'

Просмотреть файл

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Просмотреть файл

@ -0,0 +1,10 @@
package com.griddynamics.msd365fp.manualreview.model;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Constants {
public static final String DFP_DATE_TIME_PATTERN = "MM/dd/yyyy HH:mm:ss xxxxx";
public static final String ISO_OFFSET_DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXXXX";
}

Просмотреть файл

@ -0,0 +1,13 @@
package com.griddynamics.msd365fp.manualreview.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
public class DisposabilityCheck {
private Boolean disposable;
private List<DisposabilityCheckServiceResponse> disposabilityResponses;
}

Просмотреть файл

@ -0,0 +1,39 @@
package com.griddynamics.msd365fp.manualreview.model;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.griddynamics.msd365fp.manualreview.model.jackson.EpochSecondsDateTimeSerializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.ISOStringDateTimeSerializer;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.OffsetDateTime;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DisposabilityCheckServiceResponse implements Serializable {
private Boolean disposable;
private String resource;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime checked;
private String rawResponse;
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getCheckedEpochSeconds() {
return checked;
}
}

Просмотреть файл

@ -3,21 +3,20 @@
package com.griddynamics.msd365fp.manualreview.model.dfp;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.*;
import lombok.Data;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class Address implements Serializable {
private String addressId;
private String type;
private String firstName;
@ -31,5 +30,14 @@ public class Address implements Serializable {
private String district;
private String zipCode;
private String country;
private String countryRegion;
private Map<String, String> additionalParams = new HashMap<>();
@JsonAnySetter
public void setAdditionalParam(String name, String value) {
additionalParams.put(name, value);
}
public void setAdditionalParams(Map<String, String> map) {
additionalParams.putAll(map);
}
}

Просмотреть файл

@ -3,19 +3,19 @@
package com.griddynamics.msd365fp.manualreview.model.dfp;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.*;
import lombok.Data;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class AssesmentResult implements Serializable {
@ -27,4 +27,14 @@ public class AssesmentResult implements Serializable {
private String policyApplied;
private List<PurchaseStatus> purchaseStatusList;
private Map<String, String> additionalParams = new HashMap<>();
@JsonAnySetter
public void setAdditionalParam(String name, String value) {
additionalParams.put(name, value);
}
public void setAdditionalParams(Map<String, String> map) {
additionalParams.putAll(map);
}
}

Просмотреть файл

@ -3,24 +3,31 @@
package com.griddynamics.msd365fp.manualreview.model.dfp;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.*;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.griddynamics.msd365fp.manualreview.model.jackson.EpochSecondsDateTimeSerializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.ISOStringDateTimeSerializer;
import lombok.Data;
import java.io.Serializable;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.Map;
@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class BankEvent implements Serializable {
private String bankEventId;
private String type;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime bankEventTimestamp;
private String status;
private String bankResponseCode;
@ -29,4 +36,25 @@ public class BankEvent implements Serializable {
private String mrn;
@JsonProperty("MID")
private String mid;
private Map<String, String> additionalParams = new HashMap<>();
@JsonAnySetter
public void setAdditionalParam(String name, String value) {
additionalParams.put(name, value);
}
public void setAdditionalParams(Map<String, String> map) {
additionalParams.putAll(map);
}
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getBankEventTimestampEpochSeconds() {
return bankEventTimestamp;
}
}

Просмотреть файл

@ -4,7 +4,13 @@
package com.griddynamics.msd365fp.manualreview.model.dfp;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.griddynamics.msd365fp.manualreview.model.DisposabilityCheckServiceResponse;
import com.griddynamics.msd365fp.manualreview.model.jackson.EpochSecondsDateTimeSerializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.ISOStringDateTimeSerializer;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
@ -13,75 +19,56 @@ import java.util.List;
@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CalculatedFields implements Serializable {
// Geo data
private Boolean matchingOfCountriesForShippingAndIP;
private Boolean matchingOfCountriesForBillingAndShipping;
private Boolean matchingOfCountriesForBillingAndIP;
private List<String> billingCountries;
private List<String> billingZipCodes;
private List<String> billingAddresses;
private BigDecimal distanceToPreviousTransactionIP;
private Integer accountAgeInDays;
private Integer activityAgeInDays;
// Account statistic
private Long accountAgeInDays;
private Long activityAgeInDays;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime firstTransactionDateTime;
private Boolean aggregatedEmailConfirmed;
private String aggregatedEmailDomain;
private Boolean disposableEmailDomain;
private List<DisposabilityCheckServiceResponse> disposabilityChecks;
private List<String> authResultCodes;
private List<String> approveResultCodes;
private List<String> declineResultCodes;
// Bank data
private List<String> authBankEventResultCodes;
private List<String> approveBankEventResultCodes;
private List<String> declineBankEventResultCodes;
private Integer lastHourTransactionCount;
private Integer lastDayTransactionCount;
private Integer lastWeekTransactionCount;
// Previous transactions statistic
private Velocity<Long> transactionCount;
private Velocity<BigDecimal> transactionAmount;
private Velocity<Long> rejectedTransactionCount;
private Velocity<BigDecimal> rejectedTransactionAmount;
private Velocity<Long> failedTransactionCount;
private Velocity<BigDecimal> failedTransactionAmount;
private Velocity<Long> successfulTransactionCount;
private Velocity<BigDecimal> successfulTransactionAmount;
private Velocity<Long> currentPaymentInstrumentTransactionCount;
private Velocity<BigDecimal> currentPaymentInstrumentTransactionAmount;
private Velocity<Long> uniquePaymentInstrumentCount;
private Velocity<Long> uniqueIPCountries;
private BigDecimal lastHourTransactionAmount;
private BigDecimal lastDayTransactionAmount;
private BigDecimal lastWeekTransactionAmount;
private Integer lastHourRejectedTransactionCount;
private Integer lastDayRejectedTransactionCount;
private Integer lastWeekRejectedTransactionCount;
private BigDecimal lastHourRejectedTransactionAmount;
private BigDecimal lastDayRejectedTransactionAmount;
private BigDecimal lastWeekRejectedTransactionAmount;
private Integer lastHourFailedTransactionCount;
private Integer lastDayFailedTransactionCount;
private Integer lastWeekFailedTransactionCount;
private BigDecimal lastHourFailedTransactionAmount;
private BigDecimal lastDayFailedTransactionAmount;
private BigDecimal lastWeekFailedTransactionAmount;
private Integer lastHourSuccessfulTransactionCount;
private Integer lastDaySuccessfulTransactionCount;
private Integer lastWeekSuccessfulTransactionCount;
private BigDecimal lastHourSuccessfulTransactionAmount;
private BigDecimal lastDaySuccessfulTransactionAmount;
private BigDecimal lastWeekSuccessfulTransactionAmount;
private Integer lastHourUniquePaymentInstrumentCount;
private Integer lastDayUniquePaymentInstrumentCount;
private Integer lastWeekUniquePaymentInstrumentCount;
private Integer lastHourTransactionCountWithCurrentPaymentInstrument;
private Integer lastDayTransactionCountWithCurrentPaymentInstrument;
private Integer lastWeekTransactionCountWithCurrentPaymentInstrument;
private BigDecimal lastHourTransactionAmountWithCurrentPaymentInstrument;
private BigDecimal lastDayTransactionAmountWithCurrentPaymentInstrument;
private BigDecimal lastWeekTransactionAmountWithCurrentPaymentInstrument;
private Integer lastHourUniqueIPCountries;
private Integer lastDayUniqueIPCountries;
private Integer lastWeekUniqueIPCountries;
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getFirstTransactionDateTimeEpochSeconds() {
return firstTransactionDateTime;
}
}

Просмотреть файл

@ -3,21 +3,26 @@
package com.griddynamics.msd365fp.manualreview.model.dfp;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.*;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.griddynamics.msd365fp.manualreview.model.jackson.EpochSecondsDateTimeSerializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.ISOStringDateTimeSerializer;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.Map;
@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class DeviceContext implements Serializable {
@ -48,5 +53,28 @@ public class DeviceContext implements Serializable {
@JsonProperty("IPState")
private String ipState;
private String purchaseId;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime merchantLocalDate;
private Map<String, String> additionalParams = new HashMap<>();
@JsonAnySetter
public void setAdditionalParam(String name, String value) {
additionalParams.put(name, value);
}
public void setAdditionalParams(Map<String, String> map) {
additionalParams.putAll(map);
}
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getMerchantLocalDateEpochSeconds() {
return merchantLocalDate;
}
}

Просмотреть файл

@ -6,7 +6,9 @@ package com.griddynamics.msd365fp.manualreview.model.dfp;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
import java.util.Map;
@ -14,18 +16,11 @@ import java.util.Map;
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class MainPurchase extends Purchase {
private User user;
private DeviceContext deviceContext;
private List<Address> addressList;
private List<PaymentInstrument> paymentInstrumentList;
private List<Product> productList;
private List<BankEvent> bankEventsList;
private Map<String, String> customData;
private Map<String, Map<String, Object>> additionalInfo;
private List<PreviousPurchase> previousPurchaseList;

Просмотреть файл

@ -3,20 +3,25 @@
package com.griddynamics.msd365fp.manualreview.model.dfp;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.*;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.griddynamics.msd365fp.manualreview.model.jackson.EpochSecondsDateTimeSerializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.ISOStringDateTimeSerializer;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.Map;
@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class PaymentInstrument implements Serializable {
@ -25,7 +30,11 @@ public class PaymentInstrument implements Serializable {
private BigDecimal purchaseAmountInUSD;
private String merchantPaymentInstrumentId;
private String type;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime creationDate;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime updateDate;
private String state;
private String cardType;
@ -42,5 +51,50 @@ public class PaymentInstrument implements Serializable {
@JsonProperty("IMEI")
private String imei;
private String addressId;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime merchantLocalDate;
private Map<String, String> additionalParams = new HashMap<>();
@JsonAnySetter
public void setAdditionalParam(String name, String value) {
additionalParams.put(name, value);
}
public void setAdditionalParams(Map<String, String> map) {
additionalParams.putAll(map);
}
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getCreationDateEpochSeconds() {
return creationDate;
}
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getUpdateDateEpochSeconds() {
return updateDate;
}
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getMerchantLocalDateEpochSeconds() {
return merchantLocalDate;
}
}

Просмотреть файл

@ -5,26 +5,60 @@ package com.griddynamics.msd365fp.manualreview.model.dfp;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.*;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.griddynamics.msd365fp.manualreview.model.jackson.EpochSecondsDateTimeSerializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.ISOStringDateTimeSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
import java.time.OffsetDateTime;
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class PreviousPurchase extends Purchase {
private Integer riskScore;
private String reasonCodes;
private String policyApplied;
private String lastMerchantStatus;
private DeviceContext deviceContext;
private List<Address> addressList;
private List<PaymentInstrument> paymentInstrumentList;
private List<BankEvent> bankEventsList;
private String lastMerchantStatusReason;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime lastMerchantStatusDate;
private String lastBankEventStatus;
private String lastBankEventResponseCode;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime lastBankEventDate;
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getLastMerchantStatusDateEpochSeconds() {
return lastMerchantStatusDate;
}
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getLastBankEventDateEpochSeconds() {
return lastBankEventDate;
}
}

Просмотреть файл

@ -3,19 +3,19 @@
package com.griddynamics.msd365fp.manualreview.model.dfp;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.*;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class Product implements Serializable {
@ -43,4 +43,14 @@ public class Product implements Serializable {
private BigDecimal quantity;
private Boolean isPreorder;
private String shippingMethod;
private Map<String, String> additionalParams = new HashMap<>();
@JsonAnySetter
public void setAdditionalParam(String name, String value) {
additionalParams.put(name, value);
}
public void setAdditionalParams(Map<String, String> map) {
additionalParams.putAll(map);
}
}

Просмотреть файл

@ -3,14 +3,23 @@
package com.griddynamics.msd365fp.manualreview.model.dfp;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.*;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.griddynamics.msd365fp.manualreview.model.jackson.EpochSecondsDateTimeSerializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.ISOStringDateTimeSerializer;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@ -19,7 +28,11 @@ public abstract class Purchase implements Serializable {
private String purchaseId;
private String assessmentType;
private String originalOrderId;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime customerLocalDate;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime merchantLocalDate;
private BigDecimal totalAmount;
private BigDecimal totalAmountInUSD;
@ -30,4 +43,42 @@ public abstract class Purchase implements Serializable {
private String shippingMethod;
private String bankName;
private String hashedEvaluationId;
private DeviceContext deviceContext;
private List<Address> addressList;
private List<PaymentInstrument> paymentInstrumentList;
private List<BankEvent> bankEventsList;
private Map<String, String> additionalParams = new HashMap<>();
@JsonAnySetter
public void setAdditionalParam(String name, String value) {
additionalParams.put(name, value);
}
public void setAdditionalParams(Map<String, String> map) {
additionalParams.putAll(map);
}
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getCustomerLocalDateEpochSeconds() {
return customerLocalDate;
}
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getMerchantLocalDateEpochSeconds() {
return merchantLocalDate;
}
}

Просмотреть файл

@ -3,22 +3,52 @@
package com.griddynamics.msd365fp.manualreview.model.dfp;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.*;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.griddynamics.msd365fp.manualreview.model.jackson.EpochSecondsDateTimeSerializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.ISOStringDateTimeSerializer;
import lombok.Data;
import java.io.Serializable;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.Map;
@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class PurchaseStatus implements Serializable {
private String purchaseId;
private String statusType;
private String statusDate;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime statusDate;
private String reason;
private Map<String, String> additionalParams = new HashMap<>();
@JsonAnySetter
public void setAdditionalParam(String name, String value) {
additionalParams.put(name, value);
}
public void setAdditionalParams(Map<String, String> map) {
additionalParams.putAll(map);
}
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getStatusDateEpochSeconds() {
return statusDate;
}
}

Просмотреть файл

@ -3,24 +3,33 @@
package com.griddynamics.msd365fp.manualreview.model.dfp;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.*;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.griddynamics.msd365fp.manualreview.model.jackson.EpochSecondsDateTimeSerializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.ISOStringDateTimeSerializer;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.Map;
@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class User implements Serializable {
private String userId;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime creationDate;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime updateDate;
private String firstName;
private String lastName;
@ -36,8 +45,12 @@ public class User implements Serializable {
private String authenticationProvider;
private String displayName;
private Boolean isEmailValidated;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime emailValidatedDate;
private Boolean isPhoneNumberValidated;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime phoneNumberValidatedDate;
private BigDecimal totalSpend;
private BigDecimal totalTransactions;
@ -53,6 +66,87 @@ public class User implements Serializable {
private BigDecimal monthlyAverageTransactions;
private BigDecimal monthlyAverageRefundAmount;
private BigDecimal monthlyAverageChargebackAmount;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime measuresIngestionDateTimeUTC;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
@JsonSerialize(using = ISOStringDateTimeSerializer.class)
private OffsetDateTime merchantLocalDate;
private Map<String, String> additionalParams = new HashMap<>();
@JsonAnySetter
public void setAdditionalParam(String name, String value) {
additionalParams.put(name, value);
}
public void setAdditionalParams(Map<String, String> map) {
additionalParams.putAll(map);
}
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getCreationDateEpochSeconds() {
return creationDate;
}
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getUpdateDateEpochSeconds() {
return updateDate;
}
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getMerchantLocalDateEpochSeconds() {
return merchantLocalDate;
}
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getEmailValidatedDateEpochSeconds() {
return emailValidatedDate;
}
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getPhoneNumberValidatedDateEpochSeconds() {
return phoneNumberValidatedDate;
}
/**
* Getter for advanced serialization.
* Json object will contain both representation of DateTime field -
* timestamp (for SQL queries) and string representation.
*/
@SuppressWarnings("unused")
@JsonSerialize(using = EpochSecondsDateTimeSerializer.class)
public OffsetDateTime getMeasuresIngestionDateTimeUTCEpochSeconds() {
return measuresIngestionDateTimeUTC;
}
}

Просмотреть файл

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
package com.griddynamics.msd365fp.manualreview.model.dfp;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Velocity<T extends Serializable> implements Serializable {
private T hour;
private T day;
private T week;
}

Просмотреть файл

@ -3,6 +3,7 @@
package com.griddynamics.msd365fp.manualreview.model.dfp.raw;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;
@ -19,6 +20,8 @@ import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class AddressNodeData extends NodeData {
public static final String NODE_NAME = "Address";
private String addressId;
private String street1;
private String street2;
@ -26,6 +29,7 @@ public class AddressNodeData extends NodeData {
private String city;
private String state;
private String zipCode;
private String countryRegion;
@JsonProperty("CountryRegion")
private String country;
}

Просмотреть файл

@ -5,11 +5,15 @@ package com.griddynamics.msd365fp.manualreview.model.dfp.raw;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.OffsetDateTime;
/**
* Implementation of {@link NodeData}
*
@ -20,9 +24,12 @@ import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class BankEventNodeData extends NodeData {
public static final String NODE_NAME = "BankEvent";
private String bankEventId;
private String type;
private String bankEventTimestamp;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
private OffsetDateTime bankEventTimestamp;
private String status;
private String bankResponseCode;
private String paymentProcessor;

Просмотреть файл

@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
package com.griddynamics.msd365fp.manualreview.model.dfp.raw;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* Implementation of {@link EdgeData}
*
* @see EdgeData
*/
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class DefaultEdgeData extends EdgeData {
}

Просмотреть файл

@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
package com.griddynamics.msd365fp.manualreview.model.dfp.raw;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* Implementation of {@link NodeData}
*
* @see NodeData
*/
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class DefaultNodeData extends NodeData {
}

Просмотреть файл

@ -20,6 +20,8 @@ import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class DeviceContextNodeData extends NodeData {
public static final String NODE_NAME = "DeviceContext";
private String deviceContextId;
private String provider;
private String deviceContextDC;

Просмотреть файл

@ -22,18 +22,29 @@ public class Edge {
private Object edgeIdAttributeList;
private String id;
private String name;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "name", visible = true)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "name",
visible = true,
defaultImpl = DefaultEdgeData.class)
@JsonSubTypes({
@JsonSubTypes.Type(value = PaymentInstrumentAddressEdgeData.class, name = "PaymentInstrumentAddress"),
@JsonSubTypes.Type(value = PurchaseAddressEdgeData.class, name = "PurchaseAddress"),
@JsonSubTypes.Type(value = PurchaseBankEventEdgeData.class, name = "PurchaseBankEvent"),
@JsonSubTypes.Type(value = PurchaseDeviceContextEdgeData.class, name = "PurchaseDeviceContext"),
@JsonSubTypes.Type(value = PurchasePaymentInstrumentEdgeData.class, name = "PurchasePaymentInstrument"),
@JsonSubTypes.Type(value = PurchasePaymentInstrumentEdgeData.class, name = "PaymentInstrumentPurchase"),
@JsonSubTypes.Type(value = PurchaseProductEdgeData.class, name = "PurchaseProduct"),
@JsonSubTypes.Type(value = PurchaseStatusEdgeData.class, name = "PurchaseStatus"),
@JsonSubTypes.Type(value = PurchaseUserEdgeData.class, name = "PurchaseUser"),
@JsonSubTypes.Type(value = PurchaseUserEdgeData.class, name = "UserPurchase")
@JsonSubTypes.Type(value = PaymentInstrumentAddressEdgeData.class, name = PaymentInstrumentAddressEdgeData.EDGE_DIRECT_NAME),
@JsonSubTypes.Type(value = PaymentInstrumentAddressEdgeData.class, name = PaymentInstrumentAddressEdgeData.EDGE_REVERSED_NAME),
@JsonSubTypes.Type(value = PurchaseAddressEdgeData.class, name = PurchaseAddressEdgeData.EDGE_DIRECT_NAME),
@JsonSubTypes.Type(value = PurchaseAddressEdgeData.class, name = PurchaseAddressEdgeData.EDGE_REVERSED_NAME),
@JsonSubTypes.Type(value = PurchaseBankEventEdgeData.class, name = PurchaseBankEventEdgeData.EDGE_DIRECT_NAME),
@JsonSubTypes.Type(value = PurchaseBankEventEdgeData.class, name = PurchaseBankEventEdgeData.EDGE_REVERSED_NAME),
@JsonSubTypes.Type(value = PurchaseDeviceContextEdgeData.class, name = PurchaseDeviceContextEdgeData.EDGE_DIRECT_NAME),
@JsonSubTypes.Type(value = PurchaseDeviceContextEdgeData.class, name = PurchaseDeviceContextEdgeData.EDGE_REVERSED_NAME),
@JsonSubTypes.Type(value = PurchasePaymentInstrumentEdgeData.class, name = PurchasePaymentInstrumentEdgeData.EDGE_DIRECT_NAME),
@JsonSubTypes.Type(value = PurchasePaymentInstrumentEdgeData.class, name = PurchasePaymentInstrumentEdgeData.EDGE_REVERSED_NAME),
@JsonSubTypes.Type(value = PurchaseProductEdgeData.class, name = PurchaseProductEdgeData.EDGE_DIRECT_NAME),
@JsonSubTypes.Type(value = PurchaseProductEdgeData.class, name = PurchaseProductEdgeData.EDGE_REVERSED_NAME),
@JsonSubTypes.Type(value = PurchaseStatusEdgeData.class, name = PurchaseStatusEdgeData.EDGE_DIRECT_NAME),
@JsonSubTypes.Type(value = PurchaseStatusEdgeData.class, name = PurchaseStatusEdgeData.EDGE_REVERSED_NAME),
@JsonSubTypes.Type(value = PurchaseUserEdgeData.class, name = PurchaseUserEdgeData.EDGE_DIRECT_NAME),
@JsonSubTypes.Type(value = PurchaseUserEdgeData.class, name = PurchaseUserEdgeData.EDGE_REVERSED_NAME)
})
private EdgeData data;
}

Просмотреть файл

@ -3,13 +3,12 @@
package com.griddynamics.msd365fp.manualreview.model.dfp.raw;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.lang.NonNull;
import lombok.NoArgsConstructor;
import org.springframework.util.CollectionUtils;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
/**
* The info returned by the {@code azure.dfp.graph-explorer-url} DFP endpoint.
@ -19,18 +18,22 @@ import java.util.Objects;
* @see Edge
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ExplorerEntity {
public static final ExplorerEntity EMPTY = new ExplorerEntity(
null,
null,
List.of(),
List.of());
private String requestAttributeName;
private String requestAttributeValue;
private List<Node> nodes = null;
private List<Edge> edges = null;
public void extend(@NonNull ExplorerEntity entity) {
if (!CollectionUtils.isEmpty(entity.getNodes())) {
this.nodes = Objects.requireNonNullElseGet(this.nodes, LinkedList::new);
this.nodes.addAll(entity.getNodes());
}
if (!CollectionUtils.isEmpty(entity.getEdges())) {
this.edges = Objects.requireNonNullElseGet(this.edges, LinkedList::new);
this.edges.addAll(entity.getEdges());
}
public boolean isEmpty() {
return CollectionUtils.isEmpty(nodes) && CollectionUtils.isEmpty(edges);
}
}

Просмотреть файл

@ -18,15 +18,20 @@ public class Node {
private String nodeIdAttribute;
private String id;
private String name;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "name", visible = true)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "name",
visible = true,
defaultImpl = DefaultNodeData.class)
@JsonSubTypes({
@JsonSubTypes.Type(value = AddressNodeData.class, name = "Address"),
@JsonSubTypes.Type(value = BankEventNodeData.class, name = "BankEvent"),
@JsonSubTypes.Type(value = DeviceContextNodeData.class, name = "DeviceContext"),
@JsonSubTypes.Type(value = PaymentInstrumentNodeData.class, name = "PaymentInstrument"),
@JsonSubTypes.Type(value = ProductNodeData.class, name = "Product"),
@JsonSubTypes.Type(value = UserNodeData.class, name = "User"),
@JsonSubTypes.Type(value = PurchaseNodeData.class, name = "Purchase")
@JsonSubTypes.Type(value = AddressNodeData.class, name = AddressNodeData.NODE_NAME),
@JsonSubTypes.Type(value = BankEventNodeData.class, name = BankEventNodeData.NODE_NAME),
@JsonSubTypes.Type(value = DeviceContextNodeData.class, name = DeviceContextNodeData.NODE_NAME),
@JsonSubTypes.Type(value = PaymentInstrumentNodeData.class, name = PaymentInstrumentNodeData.NODE_NAME),
@JsonSubTypes.Type(value = ProductNodeData.class, name = ProductNodeData.NODE_NAME),
@JsonSubTypes.Type(value = UserNodeData.class, name = UserNodeData.NODE_NAME),
@JsonSubTypes.Type(value = PurchaseNodeData.class, name = PurchaseNodeData.NODE_NAME)
})
private NodeData data;
}

Просмотреть файл

@ -19,6 +19,9 @@ import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class PaymentInstrumentAddressEdgeData extends EdgeData {
public static final String EDGE_DIRECT_NAME = "PaymentInstrumentAddress";
public static final String EDGE_REVERSED_NAME = "AddressPaymentInstrument";
private String paymentInstrumentId;
private String addressId;
private String type;

Просмотреть файл

@ -5,11 +5,15 @@ package com.griddynamics.msd365fp.manualreview.model.dfp.raw;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.OffsetDateTime;
/**
* Implementation of {@link NodeData}
*
@ -20,11 +24,15 @@ import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class PaymentInstrumentNodeData extends NodeData {
public static final String NODE_NAME = "PaymentInstrument";
private String paymentInstrumentId;
private String merchantPaymentInstrumentId;
private String type;
private String creationDate;
private String updateDate;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
private OffsetDateTime creationDate;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
private OffsetDateTime updateDate;
private String state;
private String cardType;
private String holderName;

Просмотреть файл

@ -20,6 +20,8 @@ import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class ProductNodeData extends NodeData {
public static final String NODE_NAME = "Product";
private String productId;
private String productName;
private String type;

Просмотреть файл

@ -19,6 +19,9 @@ import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class PurchaseAddressEdgeData extends EdgeData {
public static final String EDGE_DIRECT_NAME = "PurchaseAddress";
public static final String EDGE_REVERSED_NAME = "AddressPurchase";
private String purchaseId;
private String addressId;
private String type;

Просмотреть файл

@ -19,6 +19,9 @@ import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class PurchaseBankEventEdgeData extends EdgeData {
public static final String EDGE_DIRECT_NAME = "PurchaseBankEvent";
public static final String EDGE_REVERSED_NAME = "BankEventPurchase";
private String purchaseId;
private String bankEventId;
}

Просмотреть файл

@ -5,11 +5,15 @@ package com.griddynamics.msd365fp.manualreview.model.dfp.raw;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.OffsetDateTime;
/**
* Implementation of {@link EdgeData}
*
@ -20,6 +24,9 @@ import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class PurchaseDeviceContextEdgeData extends EdgeData {
public static final String EDGE_DIRECT_NAME = "PurchaseDeviceContext";
public static final String EDGE_REVERSED_NAME = "DeviceContextPurchase";
private String purchaseId;
private String deviceContextId;
@JsonProperty("IPAddress")
@ -36,5 +43,6 @@ public class PurchaseDeviceContextEdgeData extends EdgeData {
private String ipCountry;
@JsonProperty("IPState")
private String ipState;
private String merchantLocalDate;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
private OffsetDateTime merchantLocalDate;
}

Просмотреть файл

@ -5,12 +5,15 @@ package com.griddynamics.msd365fp.manualreview.model.dfp.raw;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.apache.commons.lang3.StringUtils;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.Map;
@ -24,11 +27,15 @@ import java.util.Map;
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class PurchaseNodeData extends NodeData {
public static final String NODE_NAME = "Purchase";
private String purchaseId;
private String assessmentType;
private String originalOrderId;
private String merchantLocalDate;
private String customerLocalDate;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
private OffsetDateTime merchantLocalDate;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
private OffsetDateTime customerLocalDate;
private Double totalAmount;
private Double totalAmountInUSD;
private Double salesTax;

Просмотреть файл

@ -4,11 +4,15 @@
package com.griddynamics.msd365fp.manualreview.model.dfp.raw;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.OffsetDateTime;
/**
* Implementation of {@link EdgeData}
*
@ -19,8 +23,12 @@ import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class PurchasePaymentInstrumentEdgeData extends EdgeData {
public static final String EDGE_DIRECT_NAME = "PurchasePaymentInstrument";
public static final String EDGE_REVERSED_NAME = "PaymentInstrumentPurchase";
private String purchaseId;
private String merchantLocalDate;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
private OffsetDateTime merchantLocalDate;
private String paymentInstrumentId;
private Double purchaseAmount;
private Double purchaseAmountInUSD;

Просмотреть файл

@ -19,6 +19,9 @@ import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class PurchaseProductEdgeData extends EdgeData {
public static final String EDGE_DIRECT_NAME = "PurchaseProduct";
public static final String EDGE_REVERSED_NAME = "ProductPurchase";
private String purchaseId;
private String productId;
private Double purchasePrice;

Просмотреть файл

@ -4,11 +4,15 @@
package com.griddynamics.msd365fp.manualreview.model.dfp.raw;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.OffsetDateTime;
/**
* Implementation of {@link EdgeData}
*
@ -19,8 +23,12 @@ import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class PurchaseStatusEdgeData extends EdgeData {
public static final String EDGE_DIRECT_NAME = "PurchaseStatus";
public static final String EDGE_REVERSED_NAME = "StatusPurchase";
private String purchaseId;
private String statusType;
private String statusDate;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
private OffsetDateTime statusDate;
private String reason;
}

Просмотреть файл

@ -4,11 +4,15 @@
package com.griddynamics.msd365fp.manualreview.model.dfp.raw;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.OffsetDateTime;
/**
* Implementation of {@link EdgeData}
*
@ -19,8 +23,12 @@ import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class PurchaseUserEdgeData extends EdgeData {
public static final String EDGE_DIRECT_NAME = "PurchaseUser";
public static final String EDGE_REVERSED_NAME = "UserPurchase";
private String purchaseId;
private String merchantLocalDate;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
private OffsetDateTime merchantLocalDate;
private String userId;
}

Просмотреть файл

@ -5,11 +5,17 @@ package com.griddynamics.msd365fp.manualreview.model.dfp.raw;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.griddynamics.msd365fp.manualreview.model.jackson.FlexibleDateFormatDeserializer;
import com.griddynamics.msd365fp.manualreview.model.jackson.ISOStringDateTimeSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.OffsetDateTime;
/**
* Implementation of {@link NodeData}
*
@ -20,9 +26,13 @@ import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class UserNodeData extends NodeData {
public static final String NODE_NAME = "User";
private String userId;
private String creationDate;
private String updateDate;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
private OffsetDateTime creationDate;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
private OffsetDateTime updateDate;
private String firstName;
private String lastName;
@JsonProperty("CountryRegion")
@ -37,7 +47,11 @@ public class UserNodeData extends NodeData {
private String displayName;
private String authenticationProvider;
private Boolean isEmailValidated;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
private OffsetDateTime emailValidatedDate;
private Boolean isPhoneNumberValidated;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
private OffsetDateTime phoneNumberValidatedDate;
private Double totalSpend;
private Double totalTransactions;
private Double totalRefundAmount;
@ -52,7 +66,8 @@ public class UserNodeData extends NodeData {
private Double monthlyAverageTransactions;
private Double monthlyAverageRefundAmount;
private Double monthlyAverageChargebackAmount;
private String measuresIngestionDateTimeUTC;
@JsonDeserialize(using = FlexibleDateFormatDeserializer.class)
private OffsetDateTime measuresIngestionDateTimeUTC;
private String membershipId;
}

Просмотреть файл

@ -0,0 +1,22 @@
package com.griddynamics.msd365fp.manualreview.model.jackson;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
import java.time.OffsetDateTime;
public class EpochSecondsDateTimeSerializer extends StdSerializer<OffsetDateTime> {
protected EpochSecondsDateTimeSerializer() {
super(OffsetDateTime.class);
}
@Override
public void serialize(final OffsetDateTime value, final JsonGenerator gen, final SerializerProvider serializers) throws IOException {
if (value != null) {
gen.writeNumber(value.toEpochSecond());
}
}
}

Просмотреть файл

@ -0,0 +1,39 @@
package com.griddynamics.msd365fp.manualreview.model.jackson;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonTokenId;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer;
import java.io.IOException;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import static com.griddynamics.msd365fp.manualreview.model.Constants.DFP_DATE_TIME_PATTERN;
import static com.griddynamics.msd365fp.manualreview.model.Constants.ISO_OFFSET_DATE_TIME_PATTERN;
public class FlexibleDateFormatDeserializer extends InstantDeserializer<OffsetDateTime> {
public FlexibleDateFormatDeserializer() {
super(InstantDeserializer.OFFSET_DATE_TIME, DateTimeFormatter.ofPattern(ISO_OFFSET_DATE_TIME_PATTERN));
}
@Override
public OffsetDateTime deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
if (jp.getCurrentTokenId() == JsonTokenId.ID_STRING) {
try {
return OffsetDateTime.parse(jp.getText());
} catch (DateTimeParseException ignored) {
// ignored
}
try {
return OffsetDateTime.parse(jp.getText(), DateTimeFormatter.ofPattern(DFP_DATE_TIME_PATTERN));
} catch (DateTimeParseException ignored) {
// ignored
}
}
return super.deserialize(jp, ctxt);
}
}

Просмотреть файл

@ -0,0 +1,25 @@
package com.griddynamics.msd365fp.manualreview.model.jackson;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import static com.griddynamics.msd365fp.manualreview.model.Constants.ISO_OFFSET_DATE_TIME_PATTERN;
public class ISOStringDateTimeSerializer extends StdSerializer<OffsetDateTime> {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(ISO_OFFSET_DATE_TIME_PATTERN);
protected ISOStringDateTimeSerializer() {
super(OffsetDateTime.class);
}
@Override
public void serialize(final OffsetDateTime value, final JsonGenerator gen, final SerializerProvider serializers) throws IOException {
gen.writeString(value == null ? null : value.format(formatter));
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -45,6 +45,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'com.nimbusds:nimbus-jose-jwt:7.9'
implementation 'com.google.guava:guava:25.0-jre'
implementation 'org.gavaghan:geodesy:1.1.3'
// Web
implementation 'org.modelmapper:modelmapper:2.3.7'

Просмотреть файл

@ -3,18 +3,33 @@
package com.griddynamics.msd365fp.manualreview.queues.config;
import com.google.common.cache.CacheBuilder;
import com.griddynamics.msd365fp.manualreview.queues.config.properties.ApplicationProperties;
import com.griddynamics.msd365fp.manualreview.queues.config.properties.CacheProperties;
import com.griddynamics.msd365fp.manualreview.queues.config.properties.CachePropertyEntry;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import javax.annotation.Nonnull;
import java.util.Objects;
import static com.griddynamics.msd365fp.manualreview.queues.config.Constants.DEFAULT_CACHE_INVALIDATION_INTERVAL;
import static com.griddynamics.msd365fp.manualreview.queues.config.Constants.DEFAULT_CACHE_SIZE;
@Slf4j
@RequiredArgsConstructor
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ApplicationProperties.class})
@EnableConfigurationProperties({ApplicationProperties.class, CacheProperties.class})
@EnableCaching
public class ApplicationConfig {
private final ApplicationProperties applicationProperties;
@ -30,4 +45,30 @@ public class ApplicationConfig {
threadPoolTaskExecutor.getMaxPoolSize(), threadPoolTaskExecutor.getCorePoolSize());
return threadPoolTaskExecutor;
}
/**
* Cache manager configuration bean.
* This implementation allows to configure
* time-to-live for entries per each cache
*
* @param cacheProperties - a config bean
* @return the new CacheManager bean is based on guava cache
*/
@Bean
public CacheManager cacheManager(final CacheProperties cacheProperties) {
return new ConcurrentMapCacheManager() {
@Override
@Nonnull
protected Cache createConcurrentMapCache(@Nonnull final String name) {
CachePropertyEntry config = Objects.requireNonNull(cacheProperties.get(name));
CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder()
.expireAfterWrite(Objects.requireNonNullElse(
config.getInvalidationInterval(), DEFAULT_CACHE_INVALIDATION_INTERVAL))
.maximumSize(Objects.requireNonNullElse(
config.getMaxSize(), DEFAULT_CACHE_SIZE));
return new ConcurrentMapCache(name,
cacheBuilder.build().asMap(), false);
}
};
}
}

Просмотреть файл

@ -6,6 +6,8 @@ package com.griddynamics.msd365fp.manualreview.queues.config;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.time.Duration;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("java:S2386")
public class Constants {
@ -13,6 +15,10 @@ public class Constants {
public static final int TOP_ELEMENT_IN_CONTAINER_PAGE_SIZE = 1;
public static final String TOP_ELEMENT_IN_CONTAINER_CONTINUATION = null;
public static final Duration DEFAULT_CACHE_INVALIDATION_INTERVAL = Duration.ZERO;
public static final long DEFAULT_CACHE_SIZE = 0;
public static final String DEFAULT_QUEUE_PAGE_SIZE_STR = "20";
public static final int DEFAULT_QUEUE_PAGE_SIZE = 20;
public static final String DEFAULT_QUEUE_VIEW_PARAMETER_STR = "REGULAR";
@ -26,6 +32,7 @@ public class Constants {
public static final String DICTIONARIES_CONTAINER_NAME = "Dictionaries";
public static final String SETTINGS_CONTAINER_NAME = "ConfigurableAppSettings";
public static final String SEARCH_QUERIES_CONTAINER_NAME = "SearchQueries";
public static final String EMAIL_DOMAINS_CONTAINER_NAME = "EmailDomains";
public static final int DEFAULT_CACHE_CONTROL_SECONDS = 1800;
@ -65,8 +72,6 @@ public class Constants {
public static final String PRIM_HEALTH_ANALYSIS_TASK_NAME = "prim-health-analysis-task";
public static final String SEC_HEALTH_ANALYSIS_TASK_NAME = "sec-health-analysis-task";
public static final String DATETIME_PATTERN_DFP = "MM/dd/yyyy HH:mm:ss xxxxx";
public static final String SECURITY_SCHEMA_IMPLICIT = "mr_user_auth";
public static final String CLIENT_REGISTRATION_AZURE_DFP_API = "azure-dfp-api";

Просмотреть файл

@ -3,11 +3,7 @@
package com.griddynamics.msd365fp.manualreview.queues.config;
import com.griddynamics.msd365fp.manualreview.model.Decision;
import com.griddynamics.msd365fp.manualreview.model.dfp.*;
import com.griddynamics.msd365fp.manualreview.model.dfp.raw.*;
import com.griddynamics.msd365fp.manualreview.queues.model.persistence.Item;
import org.apache.commons.collections4.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.modelmapper.Converter;
import org.modelmapper.ModelMapper;
import org.modelmapper.TypeMap;
@ -17,14 +13,12 @@ import org.springframework.context.annotation.Configuration;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import static com.griddynamics.msd365fp.manualreview.queues.config.Constants.DATETIME_PATTERN_DFP;
import static com.griddynamics.msd365fp.manualreview.model.Constants.DFP_DATE_TIME_PATTERN;
@Configuration
@Slf4j
public class DFPModelMapperConfig {
@Bean
@ -35,153 +29,19 @@ public class DFPModelMapperConfig {
.setCollectionsMergeEnabled(false)
.setMatchingStrategy(MatchingStrategies.STRICT);
addStringToDateConverter(modelMapper);
addGeneralModelConvertor(modelMapper);
return modelMapper;
}
@Deprecated
private void addStringToDateConverter(ModelMapper modelMapper) {
TypeMap<String, OffsetDateTime> typeMap = modelMapper.createTypeMap(String.class, OffsetDateTime.class);
Converter<String, OffsetDateTime> dateConverter = ctx ->
ctx.getSource() == null ?
null :
OffsetDateTime.parse(ctx.getSource(), DateTimeFormatter.ofPattern(DATETIME_PATTERN_DFP));
Converter<String, OffsetDateTime> dateConverter = ctx ->{
log.error("Incorrect mapper usage, parsing of datetime in mapper is deprecated in favor of Jackson conversion: {}", ctx.getSource());
return ctx.getSource() == null ?
null :
OffsetDateTime.parse(ctx.getSource(), DateTimeFormatter.ofPattern(DFP_DATE_TIME_PATTERN));
};
typeMap.setConverter(dateConverter);
}
private void addGeneralModelConvertor(ModelMapper modelMapper) {
TypeMap<ExplorerEntity, Item> typeMap = modelMapper.createTypeMap(ExplorerEntity.class, Item.class);
Converter<ExplorerEntity, Item> converter = ctx ->
{
Item item = Objects.requireNonNullElse(ctx.getDestination(), new Item());
ExplorerEntity entity = Objects.requireNonNull(ctx.getSource());
mapExplorerEntityToItem(modelMapper, item, entity);
return item;
};
typeMap.setConverter(converter);
}
private void mapExplorerEntityToItem(final ModelMapper modelMapper, final Item item, final ExplorerEntity entity) {
DeviceContext deviceContext = new DeviceContext();
Map<String, PaymentInstrument> paymentInstrumentMap = new HashMap<>();
Map<String, Product> productMap = new HashMap<>();
Map<String, BankEvent> bankEventMap = new HashMap<>();
Map<String, Address> shippingAddressMap = new HashMap<>();
Map<String, Address> billingAddressMap = new HashMap<>();
Map<String, PreviousPurchase> previousPurchaseMap = new HashMap<>();
AssesmentResult assesmentResult = new AssesmentResult();
Map<String, PurchaseStatus> purchaseStatusMap = new HashMap<>();
User user = new User();
MainPurchase purchase = new MainPurchase();
Map<String, Map<String, Object>> additionalInfo = new HashMap<>();
entity.getEdges().forEach(edge -> {
switch (edge.getName()) {
case "PurchaseAddress":
modelMapper.map(edge.getData(), shippingAddressMap.computeIfAbsent(
((PurchaseAddressEdgeData) edge.getData()).getAddressId(),
key -> new Address()));
break;
case "PaymentInstrumentAddress":
PaymentInstrumentAddressEdgeData edgeData =
((PaymentInstrumentAddressEdgeData) edge.getData());
modelMapper.map(edge.getData(), billingAddressMap.computeIfAbsent(
edgeData.getAddressId(),
key -> new Address()));
paymentInstrumentMap
.computeIfAbsent(
edgeData.getPaymentInstrumentId(),
key -> new PaymentInstrument())
.setAddressId(edgeData.getAddressId());
break;
case "PurchaseBankEvent":
modelMapper.map(edge.getData(), bankEventMap.computeIfAbsent(
((PurchaseBankEventEdgeData) edge.getData()).getBankEventId(),
key -> new BankEvent()));
break;
case "PurchaseDeviceContext":
modelMapper.map(edge.getData(), deviceContext);
break;
case "PaymentInstrumentPurchase":
case "PurchasePaymentInstrument":
modelMapper.map(edge.getData(), paymentInstrumentMap.computeIfAbsent(
((PurchasePaymentInstrumentEdgeData) edge.getData()).getPaymentInstrumentId(),
key -> new PaymentInstrument()));
break;
case "PurchaseProduct":
modelMapper.map(edge.getData(), productMap.computeIfAbsent(
((PurchaseProductEdgeData) edge.getData()).getProductId(),
key -> new Product()));
break;
case "PurchaseStatus":
modelMapper.map(edge.getData(), purchaseStatusMap.computeIfAbsent(
edge.getId(),
key -> new PurchaseStatus()));
modelMapper.map(edge.getData(), assesmentResult);
break;
case "PurchaseUser":
modelMapper.map(edge.getData(), user);
break;
default:
modelMapper.map(edge.getData(), additionalInfo.computeIfAbsent(
edge.getName() + edge.getId(),
key -> new HashMap<>()));
}
});
entity.getNodes().forEach(node -> {
switch (node.getName()) {
case "Address":
if (shippingAddressMap.containsKey(node.getId())) {
modelMapper.map(node.getData(), shippingAddressMap.get(node.getId()));
}
if (billingAddressMap.containsKey(node.getId())) {
modelMapper.map(node.getData(), billingAddressMap.get(node.getId()));
}
break;
case "BankEvent":
modelMapper.map(node.getData(), bankEventMap.computeIfAbsent(node.getId(), key -> new BankEvent()));
break;
case "DeviceContext":
modelMapper.map(node.getData(), deviceContext);
break;
case "PaymentInstrument":
modelMapper.map(node.getData(), paymentInstrumentMap.computeIfAbsent(node.getId(), key -> new PaymentInstrument()));
break;
case "Product":
modelMapper.map(node.getData(), productMap.computeIfAbsent(node.getId(), key -> new Product()));
break;
case "Purchase":
if (node.getId().equals(item.getId())) {
modelMapper.map(node.getData(), purchase);
modelMapper.map(node.getData(), assesmentResult);
} else {
modelMapper.map(node.getData(), previousPurchaseMap.computeIfAbsent(node.getId(), key -> new PreviousPurchase()));
}
break;
case "User":
modelMapper.map(node.getData(), user);
break;
default:
modelMapper.map(node.getData(), additionalInfo.computeIfAbsent(
node.getName() + node.getId(),
key -> new HashMap<>()));
}
});
purchase.setPreviousPurchaseList(new ArrayList<>(previousPurchaseMap.values()));
purchase.setAddressList(new ArrayList<>(CollectionUtils.union(shippingAddressMap.values(), billingAddressMap.values())));
purchase.setBankEventsList(new ArrayList<>(bankEventMap.values()));
purchase.setDeviceContext(deviceContext);
purchase.setPaymentInstrumentList(new ArrayList<>(paymentInstrumentMap.values()));
purchase.setProductList(new ArrayList<>(productMap.values()));
purchase.setUser(user);
assesmentResult.setPurchaseStatusList(new ArrayList<>(purchaseStatusMap.values()));
// Create and save the item
item.setPurchase(purchase);
item.setAssessmentResult(assesmentResult);
item.setDecision(Decision.builder()
.riskScore(assesmentResult.getRiskScore())
.reasonCodes(assesmentResult.getReasonCodes())
.build());
}
}

Просмотреть файл

@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ClientCodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
@ -45,6 +46,7 @@ public class WebClientConfig {
}
@Bean
@Primary
WebClient azureDFPAPIWebClient(OAuth2AuthorizedClientManager authorizedClientManager, ObjectMapper mapper) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
@ -63,6 +65,12 @@ public class WebClientConfig {
.build();
}
@Bean
WebClient nonAuthorizingWebClient() {
return WebClient.builder()
.build();
}
private static ExchangeFilterFunction logRequestFilter() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
log.info("Calling Azure DFP API via Web Client: {} {}", clientRequest.method(), clientRequest.url());

Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
package com.griddynamics.msd365fp.manualreview.queues.config.properties;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import java.util.HashMap;
@ConstructorBinding
@ConfigurationProperties("mr.cache")
@Getter
@AllArgsConstructor
public class CacheProperties extends HashMap<String, CachePropertyEntry> {
}

Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
package com.griddynamics.msd365fp.manualreview.queues.config.properties;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.boot.context.properties.ConstructorBinding;
import java.time.Duration;
@ConstructorBinding
@Getter
@AllArgsConstructor
public class CachePropertyEntry {
private final Duration invalidationInterval;
private final Long maxSize;
}

Просмотреть файл

@ -0,0 +1,38 @@
package com.griddynamics.msd365fp.manualreview.queues.controller;
import com.griddynamics.msd365fp.manualreview.model.exception.IncorrectConfigurationException;
import com.griddynamics.msd365fp.manualreview.queues.model.dto.MapTokenDTO;
import com.griddynamics.msd365fp.manualreview.queues.service.MapService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static com.griddynamics.msd365fp.manualreview.queues.config.Constants.*;
@Tag(name = "maps", description = "The Maps API")
@SecurityRequirement(name = SECURITY_SCHEMA_IMPLICIT)
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/maps")
@Secured({ADMIN_MANAGER_ROLE})
public class MapController {
private final MapService mapService;
@Operation(summary = "Get token for read-only map access")
@GetMapping(value = "/token/read", produces = MediaType.APPLICATION_JSON_VALUE)
@Secured({ADMIN_MANAGER_ROLE, SENIOR_ANALYST_ROLE, ANALYST_ROLE})
public MapTokenDTO getReadToken() throws IncorrectConfigurationException {
return mapService.getReadToken();
}
}

Просмотреть файл

@ -20,18 +20,21 @@ import com.griddynamics.msd365fp.manualreview.cosmos.utilities.PageProcessingUti
import com.griddynamics.msd365fp.manualreview.dfpauth.util.UserPrincipalUtility;
import com.griddynamics.msd365fp.manualreview.ehub.durable.config.properties.EventHubProperties;
import com.griddynamics.msd365fp.manualreview.ehub.durable.model.DurableEventHubProducerClientRegistry;
import com.griddynamics.msd365fp.manualreview.model.DisposabilityCheck;
import com.griddynamics.msd365fp.manualreview.model.ItemLabel;
import com.griddynamics.msd365fp.manualreview.model.ItemLock;
import com.griddynamics.msd365fp.manualreview.model.PageableCollection;
import com.griddynamics.msd365fp.manualreview.model.event.Event;
import com.griddynamics.msd365fp.manualreview.model.exception.BusyException;
import com.griddynamics.msd365fp.manualreview.model.exception.IncorrectConditionException;
import com.griddynamics.msd365fp.manualreview.model.exception.NotFoundException;
import com.griddynamics.msd365fp.manualreview.queues.model.ItemDataField;
import com.griddynamics.msd365fp.manualreview.queues.model.ItemFilterField;
import com.griddynamics.msd365fp.manualreview.queues.model.QueueViewType;
import com.griddynamics.msd365fp.manualreview.queues.model.persistence.Item;
import com.griddynamics.msd365fp.manualreview.queues.model.testing.MicrosoftDynamicsFraudProtectionV1ModelsBankEventActivityBankEvent;
import com.griddynamics.msd365fp.manualreview.queues.model.testing.MicrosoftDynamicsFraudProtectionV1ModelsPurchaseActivityPurchase;
import com.griddynamics.msd365fp.manualreview.queues.repository.ItemRepository;
import com.griddynamics.msd365fp.manualreview.queues.service.ItemEnrichmentService;
import com.griddynamics.msd365fp.manualreview.queues.service.TestingService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -42,6 +45,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Sort;
import org.springframework.http.MediaType;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@ -73,6 +77,7 @@ public class TestingController {
private final ItemRepository itemRepository;
private final TestingService testingService;
private final ItemEnrichmentService itemEnrichmentService;
private final EventHubProperties ehProperties;
@Qualifier("cosmosdbObjectMapper")
private final ObjectMapper mapper;
@ -80,6 +85,7 @@ public class TestingController {
private final CosmosClient cosmosClient;
@Setter(onMethod = @__({@Autowired, @Qualifier("azureDFPAPIWebClient")}))
private WebClient dfpClient;
private final Random rand = new Random();
@Value("${azure.dfp.purchase-event-url}")
private String purchaseEventUrl;
@ -106,10 +112,30 @@ public class TestingController {
return res;
}
@Operation(summary = "Duplicate all items in the queue")
@PostMapping(value = "/items/duplicate/queue/{queueId}")
public void duplicateItems(@PathVariable("queueId") final String queueId) throws NotFoundException, BusyException {
testingService.duplicate(queueId);
@Operation(summary = "Trigger forced item enrichment")
@PostMapping(value = "/items/{id}/enrichment")
public void enrichItemById(@PathVariable final String id) {
itemEnrichmentService.enrichItem(id, true);
}
@Operation(summary = "Randomize scores for items in a queue")
@PostMapping(value = "/queue/{queueId}/score/randomize")
public void randomizeScore(@PathVariable final String queueId) throws BusyException {
PageProcessingUtility.executeForAllPages(
continuationToken -> itemRepository.findActiveItemsByQueueView(
QueueViewType.DIRECT,
queueId,
20,
continuationToken,
new Sort.Order(Sort.Direction.ASC, ItemDataField.IMPORT_DATE.getPath()),
null,
null),
items -> items.getValues().forEach(item -> {
int score = rand.nextInt(999);
item.getAssessmentResult().setRiskScore(score);
item.getDecision().setRiskScore(score);
itemRepository.save(item);
}));
}
@Operation(summary = "Get filter samples")
@ -305,6 +331,12 @@ public class TestingController {
return result;
}
@Operation(summary = "Check disposable email domains for all the items")
@PostMapping(value = "/check-email-domains")
public List<DisposabilityCheck> checkEmailDomains() {
return testingService.checkDisposableEmails();
}
@Data
public static class EventResponse {
private EventResultDetails resultDetails;

Просмотреть файл

@ -41,27 +41,30 @@ public enum ItemDataField implements Serializable {
AUTHENTICATION_PROVIDER("purchase.User.AuthenticationProvider"),
AGGREGATED_EMAIL_DOMAIN("purchase.CalculatedFields.aggregatedEmailDomain"),
AGGREGATED_EMAIL_CONFIRMED("purchase.CalculatedFields.aggregatedEmailConfirmed"),
EMAIL_DOMAIN_DISPOSABLE("purchase.CalculatedFields.disposableEmailDomain"),
// payment
MERCHANT_PAYMENT_INSTRUMENT_ID("purchase.PaymentInstrumentList[].PaymentInstrumentId"),
CUSTOM_CC_HASH("purchase.CustomData.cc_hash"),
PAYMENT_GATEWAY("purchase.BankEventsList[].PaymentProcessor"),
BANK_RESPONSE_CODE("purchase.BankEventsList[].BankResponseCode"),
AUTH_RESULT_CODE("purchase.CalculatedFields.authResultCodes[]"),
APPROVE_RESULT_CODE("purchase.CalculatedFields.approveResultCodes[]"),
DECLINE_RESULT_CODE("purchase.CalculatedFields.declineResultCodes[]"),
BANK_EVENT_RESULT_CODE("purchase.BankEventsList[].BankResponseCode"),
AUTH_BANK_EVENT_RESULT_CODE("purchase.CalculatedFields.authBankEventResultCodes[]"),
APPROVE_BANK_EVENT_RESULT_CODE("purchase.CalculatedFields.approveBankEventResultCodes[]"),
DECLINE_BANK_EVENT_RESULT_CODE("purchase.CalculatedFields.declineBankEventResultCodes[]"),
PI_BIN("purchase.PaymentInstrumentList[].BIN"),
PI_COUNTRY("purchase.CalculatedField.billingCountries[]"),
PI_ZIP("purchase.CalculatedField.billingZipCodes[]"),
PI_ADDRESS("purchase.CalculatedField.billingAddresses[]"),
PI_COUNTRY("purchase.CalculatedFields.billingCountries[]"),
PI_ZIP("purchase.CalculatedFields.billingZipCodes[]"),
PI_ADDRESS("purchase.CalculatedFields.billingAddresses[]"),
BILLING_COUNTRY_MATCHES_SHIPPING_COUNTRY("purchase.CalculatedFields.matchingOfCountriesForBillingAndShipping"),
BILLING_COUNTRY_MATCHES_IP_COUNTRY("purchase.CalculatedFields.matchingOfCountriesForBillingAndIP"),
// device
DEVICE_CONTEXT_ID("purchase.DeviceContext.DeviceContextId"),
EXTERNAL_DEVICE_ID("purchase.DeviceContext.ExternalDeviceId"),
DEVICE_CONTEXT_USER_AGENT("purchase.DeviceContext.UserAgent"),
IP_COUNTRY("purchase.DeviceContext.IPCountry"),
IP_COUNTRY_MATCHES_SHIPPING_COUNTRY("purchase.CalculatedFields.matchingOfCountriesForShippingAndIP"),
DISTANCE_TO_PREVIOUS_TRANSACTION_IP("purchase.CalculatedFields.distanceToPreviousTransactionIP"),
// velocity
CUSTOM_CONNECTION_COUNT_BUCKET("purchase.CustomData.connection_count_bucket"),
@ -69,43 +72,47 @@ public enum ItemDataField implements Serializable {
TOTAL_TRANSACTION_COUNT("purchase.User.TotalTransactions"),
LAST_30DAYS_TRANSACTION_AMOUNT("purchase.User.last30DaysSpend"),
LAST_30DAYS_TRANSACTION_COUNT("purchase.User.last30DaysTransactions"),
LAST_HOUR_TRANSACTION_COUNT("purchase.CalculatedFields.lastHourTransactionCount"),
LAST_DAY_TRANSACTION_COUNT("purchase.CalculatedFields.lastDayTransactionCount"),
LAST_WEEK_TRANSACTION_COUNT("purchase.CalculatedFields.lastWeekTransactionCount"),
LAST_HOUR_SUCCESSFUL_TRANSACTION_COUNT("purchase.CalculatedFields.lastHourSuccessfulTransactionCount"),
LAST_DAY_SUCCESSFUL_TRANSACTION_COUNT("purchase.CalculatedFields.lastDaySuccessfulTransactionCount"),
LAST_WEEK_SUCCESSFUL_TRANSACTION_COUNT("purchase.CalculatedFields.lastWeekSuccessfulTransactionCount"),
LAST_HOUR_REJECTED_TRANSACTION_COUNT("purchase.CalculatedFields.lastHourRejectedTransactionCount"),
LAST_DAY_REJECTED_TRANSACTION_COUNT("purchase.CalculatedFields.lastDayRejectedTransactionCount"),
LAST_WEEK_REJECTED_TRANSACTION_COUNT("purchase.CalculatedFields.lastWeekRejectedTransactionCount"),
LAST_HOUR_FAILED_TRANSACTION_COUNT("purchase.CalculatedFields.lastHourFailedTransactionCount"),
LAST_DAY_FAILED_TRANSACTION_COUNT("purchase.CalculatedFields.lastDayFailedTransactionCount"),
LAST_WEEK_FAILED_TRANSACTION_COUNT("purchase.CalculatedFields.lastWeekFailedTransactionCount"),
LAST_HOUR_TRANSACTION_AMOUNT("purchase.CalculatedFields.lastHourTransactionAmount"),
LAST_DAY_TRANSACTION_AMOUNT("purchase.CalculatedFields.lastDayTransactionAmount"),
LAST_WEEK_TRANSACTION_AMOUNT("purchase.CalculatedFields.lastWeekTransactionAmount"),
LAST_HOUR_SUCCESSFUL_TRANSACTION_AMOUNT("purchase.CalculatedFields.lastHourSuccessfulTransactionAmount"),
LAST_DAY_SUCCESSFUL_TRANSACTION_AMOUNT("purchase.CalculatedFields.lastDaySuccessfulTransactionAmount"),
LAST_WEEK_SUCCESSFUL_TRANSACTION_AMOUNT("purchase.CalculatedFields.lastWeekSuccessfulTransactionAmount"),
LAST_HOUR_REJECTED_TRANSACTION_AMOUNT("purchase.CalculatedFields.lastHourRejectedTransactionAmount"),
LAST_DAY_REJECTED_TRANSACTION_AMOUNT("purchase.CalculatedFields.lastDayRejectedTransactionAmount"),
LAST_WEEK_REJECTED_TRANSACTION_AMOUNT("purchase.CalculatedFields.lastWeekRejectedTransactionAmount"),
LAST_HOUR_FAILED_TRANSACTION_AMOUNT("purchase.CalculatedFields.lastHourFailedTransactionAmount"),
LAST_DAY_FAILED_TRANSACTION_AMOUNT("purchase.CalculatedFields.lastDayFailedTransactionAmount"),
LAST_WEEK_FAILED_TRANSACTION_AMOUNT("purchase.CalculatedFields.lastWeekFailedTransactionAmount"),
LAST_HOUR_TRANSACTION_COUNT("purchase.CalculatedFields.transactionCount.hour"),
LAST_DAY_TRANSACTION_COUNT("purchase.CalculatedFields.transactionCount.day"),
LAST_WEEK_TRANSACTION_COUNT("purchase.CalculatedFields.transactionCount.week"),
LAST_HOUR_SUCCESSFUL_TRANSACTION_COUNT("purchase.CalculatedFields.successfulTransactionCount.hour"),
LAST_DAY_SUCCESSFUL_TRANSACTION_COUNT("purchase.CalculatedFields.successfulTransactionCount.day"),
LAST_WEEK_SUCCESSFUL_TRANSACTION_COUNT("purchase.CalculatedFields.successfulTransactionCount.week"),
LAST_HOUR_REJECTED_TRANSACTION_COUNT("purchase.CalculatedFields.rejectedTransactionCount.hour"),
LAST_DAY_REJECTED_TRANSACTION_COUNT("purchase.CalculatedFields.rejectedTransactionCount.day"),
LAST_WEEK_REJECTED_TRANSACTION_COUNT("purchase.CalculatedFields.rejectedTransactionCount.week"),
LAST_HOUR_FAILED_TRANSACTION_COUNT("purchase.CalculatedFields.failedTransactionCount.hour"),
LAST_DAY_FAILED_TRANSACTION_COUNT("purchase.CalculatedFields.failedTransactionCount.day"),
LAST_WEEK_FAILED_TRANSACTION_COUNT("purchase.CalculatedFields.failedTransactionCount.week"),
LAST_HOUR_TRANSACTION_AMOUNT("purchase.CalculatedFields.transactionAmount.hour"),
LAST_DAY_TRANSACTION_AMOUNT("purchase.CalculatedFields.transactionAmount.day"),
LAST_WEEK_TRANSACTION_AMOUNT("purchase.CalculatedFields.transactionAmount.week"),
LAST_HOUR_SUCCESSFUL_TRANSACTION_AMOUNT("purchase.CalculatedFields.successfulTransactionCount.hour"),
LAST_DAY_SUCCESSFUL_TRANSACTION_AMOUNT("purchase.CalculatedFields.successfulTransactionCount.day"),
LAST_WEEK_SUCCESSFUL_TRANSACTION_AMOUNT("purchase.CalculatedFields.successfulTransactionCount.week"),
LAST_HOUR_REJECTED_TRANSACTION_AMOUNT("purchase.CalculatedFields.rejectedTransactionAmount.hour"),
LAST_DAY_REJECTED_TRANSACTION_AMOUNT("purchase.CalculatedFields.rejectedTransactionAmount.day"),
LAST_WEEK_REJECTED_TRANSACTION_AMOUNT("purchase.CalculatedFields.rejectedTransactionAmount.week"),
LAST_HOUR_FAILED_TRANSACTION_AMOUNT("purchase.CalculatedFields.failedTransactionAmount.hour"),
LAST_DAY_FAILED_TRANSACTION_AMOUNT("purchase.CalculatedFields.failedTransactionAmount.day"),
LAST_WEEK_FAILED_TRANSACTION_AMOUNT("purchase.CalculatedFields.failedTransactionAmount.week"),
LAST_HOUR_UNIQUE_PI("purchase.CalculatedFields.lastHourUniquePaymentInstrumentCount"),
LAST_DAY_UNIQUE_PI("purchase.CalculatedFields.lastDayUniquePaymentInstrumentCount"),
LAST_WEEK_UNIQUE_PI("purchase.CalculatedFields.lastWeekUniquePaymentInstrumentCount"),
LAST_HOUR_TRANSACTION_COUNT_CURRENT_PI("purchase.CalculatedFields.lastHourTransactionCountWithCurrentPaymentInstrument"),
LAST_DAY_TRANSACTION_COUNT_CURRENT_PI("purchase.CalculatedFields.lastDayTransactionCountWithCurrentPaymentInstrument"),
LAST_WEEK_TRANSACTION_COUNT_CURRENT_PI("purchase.CalculatedFields.lastWeekTransactionCountWithCurrentPaymentInstrument"),
LAST_HOUR_TRANSACTION_AMOUNT_CURRENT_PI("purchase.CalculatedFields.lastHourTransactionAmountWithCurrentPaymentInstrument"),
LAST_DAY_TRANSACTION_AMOUNT_CURRENT_PI("purchase.CalculatedFields.lastDayTransactionAmountWithCurrentPaymentInstrument"),
LAST_WEEK_TRANSACTION_AMOUNT_CURRENT_PI("purchase.CalculatedFields.lastWeekTransactionAmountWithCurrentPaymentInstrument"),
LAST_HOUR_UNIQUE_PI("purchase.CalculatedFields.uniquePaymentInstrumentCount.hour"),
LAST_DAY_UNIQUE_PI("purchase.CalculatedFields.uniquePaymentInstrumentCount.day"),
LAST_WEEK_UNIQUE_PI("purchase.CalculatedFields.uniquePaymentInstrumentCount.week"),
LAST_HOUR_TRANSACTION_COUNT_CURRENT_PI("purchase.CalculatedFields.currentPaymentInstrumentTransactionCount.hour"),
LAST_DAY_TRANSACTION_COUNT_CURRENT_PI("purchase.CalculatedFields.currentPaymentInstrumentTransactionCount.day"),
LAST_WEEK_TRANSACTION_COUNT_CURRENT_PI("purchase.CalculatedFields.currentPaymentInstrumentTransactionCount.week"),
LAST_HOUR_TRANSACTION_AMOUNT_CURRENT_PI("purchase.CalculatedFields.currentPaymentInstrumentTransactionAmount.hour"),
LAST_DAY_TRANSACTION_AMOUNT_CURRENT_PI("purchase.CalculatedFields.currentPaymentInstrumentTransactionAmount.day"),
LAST_WEEK_TRANSACTION_AMOUNT_CURRENT_PI("purchase.CalculatedFields.currentPaymentInstrumentTransactionAmount.week"),
CUSTOM_LAST6MONTHS_INVITATIONS_SENT("purchase.CustomData.num_invitations_sent_last_6_months"),
CUSTOM_LAST_DAY_CARD_USAGE_COUNT("purchase.CustomData.purchases_per_card_24_hours");
CUSTOM_LAST_DAY_CARD_USAGE_COUNT("purchase.CustomData.purchases_per_card_24_hours"),
LAST_HOUR_UNIQUE_IP_COUNTRIES("purchase.CalculatedFields.uniqueIPCountries.hour"),
LAST_DAY_UNIQUE_IP_COUNTRIES("purchase.CalculatedFields.uniqueIPCountries.day"),
LAST_WEEK_UNIQUE_IP_COUNTRIES("purchase.CalculatedFields.uniqueIPCountries.week");
@Getter

Просмотреть файл

@ -17,8 +17,13 @@ public enum ItemDataFieldCondition implements Serializable {
BETWEEN,
BETWEEN_ALPH,
BETWEEN_DATE,
NOT_BETWEEN,
NOT_BETWEEN_ALPH,
NOT_BETWEEN_DATE,
EQUAL,
EQUAL_ALPH,
NOT_EQUAL,
NOT_EQUAL_ALPH,
GREATER,
GREATER_ALPH,
GREATER_DATE,

Просмотреть файл

@ -27,12 +27,17 @@ import java.util.List;
@JsonSubTypes.Type(value = ItemFilterBetween.class, name = "BETWEEN"),
@JsonSubTypes.Type(value = ItemFilterBetweenAlph.class, name = "BETWEEN_ALPH"),
@JsonSubTypes.Type(value = ItemFilterBetweenDate.class, name = "BETWEEN_DATE"),
@JsonSubTypes.Type(value = ItemFilterBetween.class, name = "NOT_BETWEEN"),
@JsonSubTypes.Type(value = ItemFilterBetweenAlph.class, name = "NOT_BETWEEN_ALPH"),
@JsonSubTypes.Type(value = ItemFilterBetweenDate.class, name = "NOT_BETWEEN_DATE"),
@JsonSubTypes.Type(value = ItemFilterComparison.class, name = "EQUAL"),
@JsonSubTypes.Type(value = ItemFilterComparison.class, name = "NOT_EQUAL"),
@JsonSubTypes.Type(value = ItemFilterComparison.class, name = "GREATER"),
@JsonSubTypes.Type(value = ItemFilterComparison.class, name = "LESS"),
@JsonSubTypes.Type(value = ItemFilterComparison.class, name = "GREATER_OR_EQUAL"),
@JsonSubTypes.Type(value = ItemFilterComparison.class, name = "LESS_OR_EQUAL"),
@JsonSubTypes.Type(value = ItemFilterComparisonAlph.class, name = "EQUAL_ALPH"),
@JsonSubTypes.Type(value = ItemFilterComparisonAlph.class, name = "NOT_EQUAL_ALPH"),
@JsonSubTypes.Type(value = ItemFilterComparisonAlph.class, name = "GREATER_ALPH"),
@JsonSubTypes.Type(value = ItemFilterComparisonAlph.class, name = "LESS_ALPH"),
@JsonSubTypes.Type(value = ItemFilterComparisonAlph.class, name = "GREATER_OR_EQUAL_ALPH"),

Просмотреть файл

@ -16,117 +16,123 @@ import static com.griddynamics.msd365fp.manualreview.queues.model.ItemDataFieldC
public enum ItemFilterField implements Serializable {
IMPORT_DATE("Other signals", "Import date",
ItemDataField.IMPORT_DATE,
Set.of(CONTAINS, REGEXP, BETWEEN_DATE, LESS_DATE, GREATER_DATE, LESS_OR_EQUAL_DATE, GREATER_OR_EQUAL_DATE),
Set.of(CONTAINS, REGEXP, BETWEEN_DATE, NOT_BETWEEN_DATE, LESS_DATE, GREATER_DATE, LESS_OR_EQUAL_DATE, GREATER_OR_EQUAL_DATE),
null, null,
"The moment of adding item to MR Tool."),
TOTAL_AMOUNT("Transaction", "Total amount",
ItemDataField.TOTAL_AMOUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Total amount charged to the customer. Provided by the Merchant."),
"Total amount charged to the customer. Provided by the merchant."),
USER_COUNTRY("Account", "User country",
ItemDataField.USER_COUNTRY,
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"The user's country or region."),
PRODUCT_SKU("Transaction", "Product SKU",
ItemDataField.PRODUCT_SKU,
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"Product SKU."),
SCORE("Other signals", "Risk score",
ItemDataField.SCORE,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"-1", "999",
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"-1", "1000",
"Risk score calculated by Fraud Protection risk models in the range from -1 to 1000. -1 indicates an error determining a risk score."
),
AGGREGATED_EMAIL_CONFIRMED("Account", "Email confirmed",
ItemDataField.AGGREGATED_EMAIL_CONFIRMED,
Set.of(IS_TRUE),
null, null,
"An indicator whether the user-provided email address has been verified as owned by the user. Value is aggregated from user data and 'email_confirmed' field in custom purchase data."
"An indicator of whether the user-provided email address has been verified as owned by the user. Value is aggregated from user data and 'email_confirmed' field in custom purchase data."
),
EMAIL_DOMAIN_DISPOSABLE("Other signals", "Disposable email domain",
ItemDataField.EMAIL_DOMAIN_DISPOSABLE,
Set.of(IS_TRUE),
null, null,
"An indicator of whether the aggregated email domain is treated as disposable in connected verification systems. Collected near to time of purchase receiving."
),
AGGREGATED_EMAIL_DOMAIN("Account", "Email domain",
ItemDataField.AGGREGATED_EMAIL_DOMAIN,
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"An user's email domain. Value is aggregated from user data and 'email_domain' field in custom purchase data."
"A user's email domain. Value is aggregated from user data and 'email_domain' field in custom purchase data."
),
ACCOUNT_AGE("Account", "Account age",
ItemDataField.ACCOUNT_AGE,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Account age in days on moment of purchase making."
),
CUSTOM_ACCOUNT_AGE_BUCKET("Account", "Account age bucket",
ItemDataField.CUSTOM_ACCOUNT_AGE_BUCKET,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"It is retrieved from 'account_age_in_days_bucket ' field in custom purchase data."
),
AGE_OF_FIRST_KNOWN_TRANSACTION("Account", "Age of first known transaction",
ItemDataField.AGE_OF_FIRST_KNOWN_TRANSACTION,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Age in days of first transaction registered in DFP. Calculated on moment of purchase making."
"Age in days of first transaction registered in DFP. Calculated near to time of purchase receiving."
),
AUTHENTICATION_PROVIDER("Account", "Authentication provider",
ItemDataField.AUTHENTICATION_PROVIDER,
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"The user's authentication provider, if different from the merchant's."
),
PRODUCT_CATEGORY("Transaction", "Product category",
ItemDataField.PRODUCT_CATEGORY,
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"Category of product."
),
PRODUCT_TYPE("Transaction", "Product type",
ItemDataField.PRODUCT_TYPE,
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"Type of product sold."
),
CUSTOM_PRODUCT_FAMILIES("Transaction", "Product families",
ItemDataField.CUSTOM_PRODUCT_FAMILIES,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"Product families sent by merchant as 'product_families' field in custom purchase data."
"Product families sent by the merchant as 'product_families' field in custom purchase data."
),
MERCHANT_PAYMENT_INSTRUMENT_ID("Credit card", "Merchant payment instrument ID",
ItemDataField.MERCHANT_PAYMENT_INSTRUMENT_ID,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"The identifier of the payment instrument. This information is provided by the merchant."
),
CUSTOM_CC_HASH("Credit card", "Card hash",
ItemDataField.PI_BIN,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
ItemDataField.CUSTOM_CC_HASH,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"Cryptographically hashed card information. This attribute is used only for payments of the Credit/Debit Card type. It is retrieved from 'cc_hash' field in custom purchase data."
),
PI_BIN("Credit card", "Card BIN",
ItemDataField.PI_BIN,
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"The term bank identification number. This attribute is used only for payments of the Credit/Debit Card type."
),
PI_COUNTRY("Credit card", "Billing address: Country",
ItemDataField.PI_COUNTRY,
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"Country that used in billing address."
"Country that is used in a billing address."
),
PI_ZIP("Credit card", "Billing address: zip",
ItemDataField.PI_ZIP,
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"Postal code that used in billing address."
"Postal code that is used in a billing address."
),
PI_ADDRESS("Credit card", "Billing address: Address",
ItemDataField.PI_ADDRESS,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"The whole billing address string concatenated from all address details."
),
@ -134,308 +140,339 @@ public enum ItemFilterField implements Serializable {
ItemDataField.BILLING_COUNTRY_MATCHES_SHIPPING_COUNTRY,
Set.of(IS_TRUE),
null, null,
"Matching with shipping address. Calculated on moment of purchase making."
"Matching with shipping address. Calculated near to time of purchase receiving."
),
BILLING_COUNTRY_MATCHES_IP_COUNTRY("Credit card", "Matching with IP address",
ItemDataField.BILLING_COUNTRY_MATCHES_IP_COUNTRY,
Set.of(IS_TRUE),
null, null,
"Matching with IP address. Calculated on moment of purchase making."
"Matching with IP address. Calculated near to time of purchase receiving."
),
PAYMENT_GATEWAY("Transaction", "Payment gateway",
ItemDataField.PAYMENT_GATEWAY,
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"The value sent by banks as a payment processor."
),
BANK_RESPONSE_CODE("CC Authentication", "Bank response code",
ItemDataField.BANK_RESPONSE_CODE,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
BANK_EVENT_RESULT_CODE("CC Authentication", "Bank response code",
ItemDataField.BANK_EVENT_RESULT_CODE,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"The 'BankResponseCode' field through all bank events."
),
AUTH_RESULT_CODE("CC Authentication", "Auth result code",
ItemDataField.AUTH_RESULT_CODE,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
AUTH_BANK_EVENT_RESULT_CODE("CC Authentication", "Auth result code",
ItemDataField.AUTH_BANK_EVENT_RESULT_CODE,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"Result of CVV/AVS/other verifications. Collected as 'BankResponseCode' field in 'Auth' and 'AuthCancel' bank event types."
),
APPROVE_RESULT_CODE("CC Authentication", "Approve result code",
ItemDataField.APPROVE_RESULT_CODE,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
APPROVE_BANK_EVENT_RESULT_CODE("CC Authentication", "Approve result code",
ItemDataField.APPROVE_BANK_EVENT_RESULT_CODE,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"Results of approval bank events. Collected as 'BankResponseCode' field."
),
DECLINE_RESULT_CODE("CC Authentication", "Decline result code",
ItemDataField.DECLINE_RESULT_CODE,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
DECLINE_BANK_EVENT_RESULT_CODE("CC Authentication", "Decline result code",
ItemDataField.DECLINE_BANK_EVENT_RESULT_CODE,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"Results of refusal bank events. Collected as 'BankResponseCode' field."
),
DEVICE_CONTEXT_ID("Device", "Device context ID",
ItemDataField.DEVICE_CONTEXT_ID,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"The customer's fingerprinting session ID, or the event ID if the session is not available."
),
EXTERNAL_DEVICE_ID("Device", "External device ID",
ItemDataField.EXTERNAL_DEVICE_ID,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"The customer's device ID, as provided and mastered by the merchant."
),
DEVICE_CONTEXT_USER_AGENT("Device", "User agent",
ItemDataField.DEVICE_CONTEXT_USER_AGENT,
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"User Agent collected during purchase making."
),
IP_COUNTRY("Device", "IP country",
ItemDataField.IP_COUNTRY,
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(IN, CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"A country that us related to purchse IP address."
"A country that is related to the purchase IP address."
),
IP_COUNTRY_MATCHES_SHIPPING_COUNTRY("Device", "Matching with shipping address",
ItemDataField.IP_COUNTRY_MATCHES_SHIPPING_COUNTRY,
Set.of(IS_TRUE),
null, null,
"Matching with shipping address. Calculated on moment of purchase making."
"Matching with shipping address. Calculated near to time of purchase receiving."
),
DISTANCE_TO_PREVIOUS_TRANSACTION_IP("Device", "Distance to previous transaction IP",
ItemDataField.DISTANCE_TO_PREVIOUS_TRANSACTION_IP,
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Distance between current transaction IP location and previous transaction IP location. Location is taken from DFP data. Calculated near to time of purchase receiving."
),
LAST_HOUR_UNIQUE_IP_COUNTRIES("Velocity/Stats", "Transactions with unique IP Countries (1 hour)",
ItemDataField.LAST_HOUR_UNIQUE_IP_COUNTRIES,
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Count of transactions with unique IP Countries made by the user last hour before current purchase making. Collected near to time of purchase receiving."
),
LAST_DAY_UNIQUE_IP_COUNTRIES("Velocity/Stats", "Transactions with unique IP Countries (1 day)",
ItemDataField.LAST_DAY_UNIQUE_IP_COUNTRIES,
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Count of transactions with unique IP Countries made by the user last day before current purchase making. Collected near to time of purchase receiving."
),
LAST_WEEK_UNIQUE_IP_COUNTRIES("Velocity/Stats", "Transactions with unique IP Countries (1 week)",
ItemDataField.LAST_WEEK_UNIQUE_IP_COUNTRIES,
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Count of transactions with unique IP Countries made by the user last week before current purchase making. Collected near to time of purchase receiving."
),
CUSTOM_CONNECTION_COUNT_BUCKET("Velocity/Stats", "Connection count bucket",
ItemDataField.CUSTOM_CONNECTION_COUNT_BUCKET,
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
Set.of(CONTAINS, REGEXP, BETWEEN_ALPH, NOT_BETWEEN_ALPH, EQUAL_ALPH, NOT_EQUAL_ALPH, LESS_ALPH, GREATER_ALPH, LESS_OR_EQUAL_ALPH, GREATER_OR_EQUAL_ALPH),
null, null,
"The parameter is retrieved from 'connection_count_bucket' field in custom purchase data."
),
TOTAL_TRANSACTION_AMOUNT("Velocity/Stats", "Transaction amount (total)",
ItemDataField.TOTAL_TRANSACTION_AMOUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Total expenses. Registered in DFP on moment of purchase making."
),
TOTAL_TRANSACTION_COUNT("Velocity/Stats", "Transaction count (total)",
ItemDataField.TOTAL_TRANSACTION_COUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Total amount of made transactions. Registered in DFP on moment of purchase making."
),
LAST_30DAYS_TRANSACTION_AMOUNT("Velocity/Stats", "Transaction amount (30 days)",
ItemDataField.LAST_30DAYS_TRANSACTION_AMOUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Expenses for last 30 days before purchase making. Registered in DFP."
"Expenses for the last 30 days before purchase making. Registered in DFP."
),
LAST_30DAYS_TRANSACTION_COUNT("Velocity/Stats", "Transaction count (30 days)",
ItemDataField.LAST_30DAYS_TRANSACTION_COUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Amount of made transactions for last 30 days before purchase making. Registered in DFP."
"Amount of made transactions for the last 30 days before purchase making. Registered in DFP."
),
CUSTOM_LAST6MONTHS_INVITATIONS_SENT("Velocity/Stats", "Invitation sent count (6 months)",
ItemDataField.CUSTOM_LAST6MONTHS_INVITATIONS_SENT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"The parameter is retrieved from 'num_invitations_sent_last_6_months' field in custom purchase data."
),
CUSTOM_LAST_DAY_CARD_USAGE_COUNT("Velocity/Stats", "Transactions through the same card or member (24 hours)",
ItemDataField.CUSTOM_LAST_DAY_CARD_USAGE_COUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"The parameter is retrieved from 'purchases_per_member_24_hours' field in custom purchase data."
),
LAST_HOUR_TRANSACTION_COUNT("Velocity/Stats", "Transaction count (1 hour)",
ItemDataField.LAST_HOUR_TRANSACTION_COUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Amount of transactions for last hour before purchase making. Collected near to time of purchase receiving."
"Amount of transactions for the last hour before purchase making. Collected near to time of purchase receiving."
),
LAST_DAY_TRANSACTION_COUNT("Velocity/Stats", "Transaction count (1 day)",
ItemDataField.LAST_DAY_TRANSACTION_COUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Amount of transactions for last day before purchase making. Collected near to time of purchase receiving."
"Amount of transactions for the last day before purchase making. Collected near to time of purchase receiving."
),
LAST_WEEK_TRANSACTION_COUNT("Velocity/Stats", "Transaction count (1 week)",
ItemDataField.LAST_WEEK_TRANSACTION_COUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Amount of transactions for last hour before purchase making. Collected near to time of purchase receiving."
"Amount of transactions for the last hour before purchase making. Collected near to time of purchase receiving."
),
LAST_HOUR_SUCCESSFUL_TRANSACTION_COUNT("Velocity/Stats", "Successful transaction count (1 hour)",
ItemDataField.LAST_HOUR_SUCCESSFUL_TRANSACTION_COUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Amount of approved transactions for last hour before purchase making. Collected near to time of purchase receiving."
"Amount of approved transactions for the last hour before purchase making. Collected near to time of purchase receiving."
),
LAST_DAY_SUCCESSFUL_TRANSACTION_COUNT("Velocity/Stats", "Successful transaction count (1 day)",
ItemDataField.LAST_DAY_SUCCESSFUL_TRANSACTION_COUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Amount of approved transactions for last day before purchase making. Collected near to time of purchase receiving."
"Amount of approved transactions for the last day before purchase making. Collected near to time of purchase receiving."
),
LAST_WEEK_SUCCESSFUL_TRANSACTION_COUNT("Velocity/Stats", "Successful transaction count (1 week)",
ItemDataField.LAST_WEEK_SUCCESSFUL_TRANSACTION_COUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Amount of approved transactions for last hour before purchase making. Collected near to time of purchase receiving."
"Amount of approved transactions for the last hour before purchase making. Collected near to time of purchase receiving."
),
LAST_HOUR_REJECTED_TRANSACTION_COUNT("Velocity/Stats", "Rejected transaction count (1 hour)",
ItemDataField.LAST_HOUR_REJECTED_TRANSACTION_COUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Amount of rejected transactions for last hour before purchase making. Collected near to time of purchase receiving."
"Amount of rejected transactions for the last hour before purchase making. Collected near to time of purchase receiving."
),
LAST_DAY_REJECTED_TRANSACTION_COUNT("Velocity/Stats", "Rejected transaction count (1 day)",
ItemDataField.LAST_DAY_REJECTED_TRANSACTION_COUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Amount of rejected transactions for last day before purchase making. Collected near to time of purchase receiving."
"Amount of rejected transactions for the last day before purchase making. Collected near to time of purchase receiving."
),
LAST_WEEK_REJECTED_TRANSACTION_COUNT("Velocity/Stats", "Rejected transaction count (1 week)",
ItemDataField.LAST_WEEK_REJECTED_TRANSACTION_COUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Amount of rejected transactions for last hour before purchase making. Collected near to time of purchase receiving."
"Amount of rejected transactions for the last hour before purchase making. Collected near to time of purchase receiving."
),
LAST_HOUR_FAILED_TRANSACTION_COUNT("Velocity/Stats", "Failed transaction count (1 hour)",
ItemDataField.LAST_HOUR_FAILED_TRANSACTION_COUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Amount of failed transactions for last hour before purchase making. Collected near to time of purchase receiving."
"Amount of failed transactions for the last hour before purchase making. Collected near to time of purchase receiving."
),
LAST_DAY_FAILED_TRANSACTION_COUNT("Velocity/Stats", "Failed transaction count (1 day)",
ItemDataField.LAST_DAY_FAILED_TRANSACTION_COUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Amount of failed transactions for last day before purchase making. Collected near to time of purchase receiving."
"Amount of failed transactions for the last day before purchase making. Collected near to time of purchase receiving."
),
LAST_WEEK_FAILED_TRANSACTION_COUNT("Velocity/Stats", "Failed transaction count (1 week)",
ItemDataField.LAST_WEEK_FAILED_TRANSACTION_COUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Amount of failed transactions for last hour before purchase making. Collected near to time of purchase receiving."
"Amount of failed transactions for the last hour before purchase making. Collected near to time of purchase receiving."
),
LAST_HOUR_TRANSACTION_AMOUNT("Velocity/Stats", "Transaction amount (1 hour)",
ItemDataField.LAST_HOUR_TRANSACTION_AMOUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Expenses for last hour before purchase making. Collected near to time of purchase receiving."
"Expenses for the last hour before purchase making. Collected near to time of purchase receiving."
),
LAST_DAY_TRANSACTION_AMOUNT("Velocity/Stats", "Transaction amount (1 day)",
ItemDataField.LAST_DAY_TRANSACTION_AMOUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Expenses for last day before purchase making. Collected near to time of purchase receiving."
"Expenses for the last day before purchase making. Collected near to time of purchase receiving."
),
LAST_WEEK_TRANSACTION_AMOUNT("Velocity/Stats", "Transaction amount (1 week)",
ItemDataField.LAST_WEEK_TRANSACTION_AMOUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Expenses for last week before purchase making. Collected near to time of purchase receiving."
"Expenses for the last week before purchase making. Collected near to time of purchase receiving."
),
LAST_HOUR_SUCCESSFUL_TRANSACTION_AMOUNT("Velocity/Stats", "Successful transaction amount (1 hour)",
ItemDataField.LAST_HOUR_SUCCESSFUL_TRANSACTION_AMOUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Expenses in approved transactions for last hour before purchase making. Collected near to time of purchase receiving."
"Expenses in approved transactions for the last hour before purchase making. Collected near to time of purchase receiving."
),
LAST_DAY_SUCCESSFUL_TRANSACTION_AMOUNT("Velocity/Stats", "Successful transaction amount (1 day)",
ItemDataField.LAST_DAY_SUCCESSFUL_TRANSACTION_AMOUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Expenses in approved transactions for last day before purchase making. Collected near to time of purchase receiving."
"Expenses in approved transactions for the last day before purchase making. Collected near to time of purchase receiving."
),
LAST_WEEK_SUCCESSFUL_TRANSACTION_AMOUNT("Velocity/Stats", "Successful transaction amount (1 week)",
ItemDataField.LAST_WEEK_SUCCESSFUL_TRANSACTION_AMOUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Expenses in approved transactions for last week before purchase making. Collected near to time of purchase receiving."
"Expenses in approved transactions for the last week before purchase making. Collected near to time of purchase receiving."
),
LAST_HOUR_REJECTED_TRANSACTION_AMOUNT("Velocity/Stats", "Rejected transaction amount (1 hour)",
ItemDataField.LAST_HOUR_REJECTED_TRANSACTION_AMOUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Expenses in rejected transactions for last hour before purchase making. Collected near to time of purchase receiving."
"Expenses in rejected transactions for the last hour before purchase making. Collected near to time of purchase receiving."
),
LAST_DAY_REJECTED_TRANSACTION_AMOUNT("Velocity/Stats", "Rejected transaction amount (1 day)",
ItemDataField.LAST_DAY_REJECTED_TRANSACTION_AMOUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Expenses in rejected transactions for last day before purchase making. Collected near to time of purchase receiving."
"Expenses in rejected transactions for the last day before purchase making. Collected near to time of purchase receiving."
),
LAST_WEEK_REJECTED_TRANSACTION_AMOUNT("Velocity/Stats", "Rejected transaction amount (1 week)",
ItemDataField.LAST_WEEK_REJECTED_TRANSACTION_AMOUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Expenses in rejected transactions for last week before purchase making. Collected near to time of purchase receiving."
"Expenses in rejected transactions for the last week before purchase making. Collected near to time of purchase receiving."
),
LAST_HOUR_FAILED_TRANSACTION_AMOUNT("Velocity/Stats", "Failed transaction amount (1 hour)",
ItemDataField.LAST_HOUR_FAILED_TRANSACTION_AMOUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Expenses in failed transactions for last hour before purchase making. Collected near to time of purchase receiving."
"Expenses in failed transactions for the last hour before purchase making. Collected near to time of purchase receiving."
),
LAST_DAY_FAILED_TRANSACTION_AMOUNT("Velocity/Stats", "Failed transaction amount (1 day)",
ItemDataField.LAST_DAY_FAILED_TRANSACTION_AMOUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Expenses in failed transactions for last day before purchase making. Collected near to time of purchase receiving."
"Expenses in failed transactions for the last day before purchase making. Collected near to time of purchase receiving."
),
LAST_WEEK_FAILED_TRANSACTION_AMOUNT("Velocity/Stats", "Failed transaction amount (1 week)",
ItemDataField.LAST_WEEK_FAILED_TRANSACTION_AMOUNT,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Expenses in failed transactions for last week before purchase making. Collected near to time of purchase receiving."
"Expenses in failed transactions for the last week before purchase making. Collected near to time of purchase receiving."
),
LAST_HOUR_UNIQUE_PI("Velocity/Stats", "Unique used payment instruments count (1 hour)",
ItemDataField.LAST_HOUR_UNIQUE_PI,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Count of unique payment instruments used by user last hour before purchase making. Collected near to time of purchase receiving."
"Count of unique payment instruments used by the user last hour before purchase making. Collected near to time of purchase receiving."
),
LAST_DAY_UNIQUE_PI("Velocity/Stats", "Unique used payment instruments count (1 day)",
ItemDataField.LAST_DAY_UNIQUE_PI,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Count of unique payment instruments used by user last day before purchase making. Collected near to time of purchase receiving."
"Count of unique payment instruments used by the user last day before purchase making. Collected near to time of purchase receiving."
),
LAST_WEEK_UNIQUE_PI("Velocity/Stats", "Unique used payment instruments count (1 week)",
ItemDataField.LAST_WEEK_UNIQUE_PI,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Count of unique payment instruments used by user last week before purchase making. Collected near to time of purchase receiving."
"Count of unique payment instruments used by the user last week before purchase making. Collected near to time of purchase receiving."
),
LAST_HOUR_TRANSACTION_COUNT_CURRENT_PI("Velocity/Stats", "Transaction count with current payment instrument (1 hour)",
ItemDataField.LAST_HOUR_TRANSACTION_COUNT_CURRENT_PI,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Count of Transactions made with usage of payment instruments from current transaction during last hour before purchase making. Collected near to time of purchase receiving."
"Count of Transactions made with the usage of payment instruments from the current transaction during the last hour before purchase making. Collected near to time of purchase receiving."
),
LAST_DAY_TRANSACTION_COUNT_CURRENT_PI("Velocity/Stats", "Transaction count with current payment instrument (1 day)",
ItemDataField.LAST_DAY_TRANSACTION_COUNT_CURRENT_PI,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Count of Transactions made with usage of payment instruments from current transaction during last day before purchase making. Collected near to time of purchase receiving."
"Count of Transactions made with the usage of payment instruments from the current transaction during the last day before purchase making. Collected near to time of purchase receiving."
),
LAST_WEEK_TRANSACTION_COUNT_CURRENT_PI("Velocity/Stats", "Transaction count with current payment instrument (1 week)",
ItemDataField.LAST_WEEK_TRANSACTION_COUNT_CURRENT_PI,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Count of Transactions made with usage of payment instruments from current transaction during last week before purchase making. Collected near to time of purchase receiving."
"Count of Transactions made with the usage of payment instruments from the current transaction during the last week before purchase making. Collected near to time of purchase receiving."
),
LAST_HOUR_TRANSACTION_AMOUNT_CURRENT_PI("Velocity/Stats", "Transaction amount with current payment instrument (1 hour)",
ItemDataField.LAST_HOUR_TRANSACTION_AMOUNT_CURRENT_PI,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Expenses processed with usage of payment instruments from current transaction during last hour before purchase making. Collected near to time of purchase receiving."
"Expenses processed with the usage of payment instruments from the current transaction during the last hour before purchase making. Collected near to time of purchase receiving."
),
LAST_DAY_TRANSACTION_AMOUNT_CURRENT_PI("Velocity/Stats", "Transaction amount with current payment instrument (1 day)",
ItemDataField.LAST_DAY_TRANSACTION_AMOUNT_CURRENT_PI,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Expenses processed with usage of payment instruments from current transaction during last day before purchase making. Collected near to time of purchase receiving."
"Expenses processed with the usage of payment instruments from the current transaction during the last day before purchase making. Collected near to time of purchase receiving."
),
LAST_WEEK_TRANSACTION_AMOUNT_CURRENT_PI("Velocity/Stats", "Transaction amount with current payment instrument (1 week)",
ItemDataField.LAST_WEEK_TRANSACTION_AMOUNT_CURRENT_PI,
Set.of(BETWEEN, EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
Set.of(BETWEEN, NOT_BETWEEN, EQUAL, NOT_EQUAL, LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL),
"0", null,
"Expenses processed with usage of payment instruments from current transaction during last week before purchase making. Collected near to time of purchase receiving."
"Expenses processed with the usage of payment instruments from the current transaction during the last week before purchase making. Collected near to time of purchase receiving."
);
@Getter

Просмотреть файл

@ -7,6 +7,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.util.List;
@ -14,6 +16,6 @@ import java.util.List;
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ItemFilterIsTrue extends ItemFilter {
@Size(max = 0, message = "there should not be any arguments for IS_TRUE filter")
private List<String> values;
@Size(min = 1, max = 1, message = "there should be exactly one argument for IS_TRUE filter")
private List<@NotBlank @Pattern(regexp = "true|false", flags = {Pattern.Flag.CASE_INSENSITIVE}) String> values;
}

Просмотреть файл

@ -16,6 +16,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Sort;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.time.OffsetDateTime;
import java.util.*;
@ -38,8 +39,8 @@ public class ItemQuery {
private String alias;
private final List<String> queryParts = new ArrayList<>();
private final Map<String, List<String>> joinParts = new TreeMap<>();
private String orderPart = "";
ItemQueryConstructor alias(String alias) {
this.alias = alias;
@ -138,9 +139,10 @@ public class ItemQuery {
break;
case IS_TRUE:
condition = String.format(
"(IS_DEFINED(%1$s%2$s) AND NOT IS_NULL(%1$s%2$s) AND %1$s%2$s)", //TODO: check
"%3$s (IS_DEFINED(%1$s%2$s) AND NOT IS_NULL(%1$s%2$s) AND %1$s%2$s)", //TODO: check
decomposition.getLocalAlias(),
decomposition.getPath()
decomposition.getPath(),
Boolean.parseBoolean(itemFilter.getValues().get(0)) ? "" : "NOT"
);
break;
case REGEXP:
@ -160,6 +162,15 @@ public class ItemQuery {
itemFilter.getValues().get(1)
);
break;
case NOT_BETWEEN:
condition = String.format(
"(%s%s NOT BETWEEN %s AND %s)",
decomposition.getLocalAlias(),
decomposition.getPath(),
itemFilter.getValues().get(0),
itemFilter.getValues().get(1)
);
break;
case BETWEEN_ALPH:
condition = String.format(
"(%s%s BETWEEN '%s' AND '%s')",
@ -169,6 +180,15 @@ public class ItemQuery {
itemFilter.getValues().get(1)
);
break;
case NOT_BETWEEN_ALPH:
condition = String.format(
"(%s%s NOT BETWEEN '%s' AND '%s')",
decomposition.getLocalAlias(),
decomposition.getPath(),
itemFilter.getValues().get(0),
itemFilter.getValues().get(1)
);
break;
case BETWEEN_DATE:
condition = String.format(
"(%s%s BETWEEN %s AND %s)",
@ -178,7 +198,17 @@ public class ItemQuery {
OffsetDateTime.parse(itemFilter.getValues().get(1)).toEpochSecond()
);
break;
case NOT_BETWEEN_DATE:
condition = String.format(
"(%s%s NOT BETWEEN %s AND %s)",
decomposition.getLocalAlias(),
decomposition.getPath(),
OffsetDateTime.parse(itemFilter.getValues().get(0)).toEpochSecond(),
OffsetDateTime.parse(itemFilter.getValues().get(1)).toEpochSecond()
);
break;
case EQUAL:
case NOT_EQUAL:
case GREATER:
case LESS:
case GREATER_OR_EQUAL:
@ -191,6 +221,7 @@ public class ItemQuery {
itemFilter.getValues().get(0));
break;
case EQUAL_ALPH:
case NOT_EQUAL_ALPH:
case GREATER_ALPH:
case LESS_ALPH:
case GREATER_OR_EQUAL_ALPH:
@ -234,6 +265,9 @@ public class ItemQuery {
case EQUAL:
case EQUAL_ALPH:
return "=";
case NOT_EQUAL:
case NOT_EQUAL_ALPH:
return "!=";
case GREATER:
case GREATER_ALPH:
case GREATER_DATE:
@ -256,12 +290,7 @@ public class ItemQuery {
}
public ItemQueryConstructor order(Sort.Order order) {
queryParts.add(String.format(
"ORDER BY %s.%s %s",
alias,
order.getProperty(),
order.getDirection())
);
orderPart = order.getProperty() + " " + order.getDirection();
return this;
}
@ -420,11 +449,22 @@ public class ItemQuery {
}
public String constructSelect() {
return String.format(
"SELECT %1$s FROM %1$s %2$s WHERE %3$s",
alias,
getJoinClause(),
Joiner.on(" ").join(queryParts));
String joinClause = getJoinClause();
if (StringUtils.isEmpty(joinClause)) {
return String.format(
"SELECT %1$s FROM %1$s WHERE %2$s %3$s",
alias,
Joiner.on(" ").join(queryParts),
getOrderByClause(""));
} else {
return String.format(
"SELECT VALUE root FROM (SELECT DISTINCT %1$s FROM %1$s %2$s WHERE %3$s) AS root %4$s",
alias,
joinClause,
Joiner.on(" ").join(queryParts),
getOrderByClause("root"));
}
}
public String constructCount() {
@ -450,7 +490,7 @@ public class ItemQuery {
return CollectionUtils.isEmpty(joinParts) ? "" :
joinParts.entrySet().stream()
.map(entry -> String.format(
"JOIN (SELECT VALUE %1$s FROM %1$s IN %2$s.%3$s WHERE %4$s) %1$s",
"JOIN (SELECT DISTINCT VALUE %1$s FROM %1$s IN %2$s.%3$s WHERE %4$s) %1$s",
getJoinClauseAliasName(entry.getKey()),
alias,
entry.getKey(),
@ -458,6 +498,15 @@ public class ItemQuery {
.collect(Collectors.joining(" "));
}
private String getOrderByClause(String rootAlias) {
return StringUtils.isEmpty(orderPart) ? "" :
String.format(
"ORDER BY %s%s.%s",
StringUtils.isEmpty(rootAlias) ? "" : rootAlias + ".",
alias,
orderPart);
}
public ItemSelectQueryExecutor constructSelectExecutor(ExtendedCosmosContainer itemsContainer) {
return (size, continuationToken) -> {
String query = constructSelect();

Просмотреть файл

@ -0,0 +1,14 @@
package com.griddynamics.msd365fp.manualreview.queues.model;
import lombok.Data;
@Data
public class NameApiResponse {
private DisposabilityStatus disposable;
public enum DisposabilityStatus {
YES,
NO,
UNKNOWN
}
}

Просмотреть файл

@ -0,0 +1,8 @@
package com.griddynamics.msd365fp.manualreview.queues.model;
import lombok.Data;
@Data
public class OpenKickboxResponse {
private boolean disposable;
}

Просмотреть файл

@ -0,0 +1,27 @@
package com.griddynamics.msd365fp.manualreview.queues.model.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import java.time.Duration;
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class AccessTokenDTO {
@NotNull
@JsonProperty("access_token")
private String token;
@JsonProperty("expires_in")
private Duration expiresIn;
@JsonProperty("token_type")
private String tokenType;
}

Просмотреть файл

@ -8,6 +8,7 @@ import com.griddynamics.msd365fp.manualreview.queues.model.ItemFilter;
import com.griddynamics.msd365fp.manualreview.queues.validation.FieldConditionCombination;
import lombok.Data;
import javax.validation.Valid;
import java.util.Set;
@Data
@ -16,7 +17,7 @@ public class ItemSearchQueryDTO {
private Boolean active;
private Set<String> queueIds;
private boolean residual = false;
private Set<@FieldConditionCombination ItemFilter> itemFilters;
private Set<@FieldConditionCombination @Valid ItemFilter> itemFilters;
private Set<String> lockOwnerIds;
private Set<String> holdOwnerIds;
private Set<Label> labels;

Просмотреть файл

@ -0,0 +1,28 @@
package com.griddynamics.msd365fp.manualreview.queues.model.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import java.time.Duration;
import java.time.OffsetDateTime;
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MapTokenDTO {
@NotNull
private String token;
@NotNull
private Duration expiresIn;
@NotNull
private OffsetDateTime expiresAt;
@NotNull
private String tokenType;
}

Просмотреть файл

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
package com.griddynamics.msd365fp.manualreview.queues.model.persistence;
import com.griddynamics.msd365fp.manualreview.model.DisposabilityCheck;
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.Document;
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.PartitionKey;
import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import java.io.Serializable;
import static com.griddynamics.msd365fp.manualreview.queues.config.Constants.EMAIL_DOMAINS_CONTAINER_NAME;
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder(toBuilder = true)
@EqualsAndHashCode(exclude = "_etag")
@Document(collection = EMAIL_DOMAINS_CONTAINER_NAME)
public class EmailDomain implements Serializable {
@Id
@PartitionKey
private String emailDomainName;
private DisposabilityCheck disposabilityCheck;
@Version
@SuppressWarnings("java:S116")
String _etag;
private long ttl;
}

Просмотреть файл

@ -33,6 +33,9 @@ public class Item implements Serializable {
private String id;
private OffsetDateTime imported;
private OffsetDateTime enriched;
private int enrichmentAttempts;
private boolean enrichmentFailed;
private String enrichmentFailReason;
@JsonProperty(value = "_ts")
private OffsetDateTime updated;

Просмотреть файл

@ -28,7 +28,9 @@ public class Task {
private TaskStatus status;
private Map<String,String> variables;
private OffsetDateTime previousRun;
private String failedStatusMessage;
private Boolean previousRunSuccessfull;
private String lastFailedRunMessage;
private String instanceId;
@Version
@SuppressWarnings("java:S116")

Просмотреть файл

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
package com.griddynamics.msd365fp.manualreview.queues.repository;
import com.griddynamics.msd365fp.manualreview.queues.model.persistence.EmailDomain;
import com.microsoft.azure.spring.data.cosmosdb.repository.CosmosRepository;
public interface EmailDomainRepository extends CosmosRepository<EmailDomain, String> {
}

Просмотреть файл

@ -55,7 +55,7 @@ public interface ItemRepositoryCustomMethods {
final String continuationToken);
PageableCollection<String> findUnenrichedItemIds(
final OffsetDateTime updatedUpperBoundary,
final OffsetDateTime importedUpperBoundary,
final int size,
final String continuationToken);

Просмотреть файл

@ -154,12 +154,18 @@ public class ItemRepositoryCustomMethodsImpl implements ItemRepositoryCustomMeth
@Override
public PageableCollection<String> findUnenrichedItemIds(
final OffsetDateTime updatedUpperBoundary,
final OffsetDateTime importedUpperBoundary,
final int size,
final String continuationToken) {
ExtendedCosmosContainer.Page res = itemsContainer.runCrossPartitionPageableQuery(
String.format("SELECT i.id FROM i WHERE IS_NULL(i.enriched) AND i._ts <= %s",
updatedUpperBoundary.toEpochSecond()),
String.format("SELECT i.id FROM i WHERE IS_NULL(i.enriched) " +
"AND (" +
" NOT IS_DEFINED(i.enrichmentFailed) " +
" OR IS_NULL(i.enrichmentFailed) " +
" OR NOT i.enrichmentFailed " +
") AND i.imported <= %s " +
"ORDER BY i._ts",
importedUpperBoundary.toEpochSecond()),
size,
continuationToken);
List<String> queriedItems = res.getContent()
@ -235,7 +241,6 @@ public class ItemRepositoryCustomMethodsImpl implements ItemRepositoryCustomMeth
.and().notEscalation()
.and().updatedAfter(enrichedSince)
.and().includeLocked(includeLocked)
.order(order)
.constructSelectExecutor(itemsContainer)
.execute(size, continuationToken);
}

Просмотреть файл

@ -12,6 +12,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@ -31,6 +32,7 @@ public class DFPExplorerService {
@Value("${azure.dfp.graph-explorer-url}")
private String dfpExplorerUrl;
@Cacheable(value = "traversal-purchase", unless = "#result.isEmpty()")
public ExplorerEntity explorePurchase(final String id) {
ExplorerEntityRequest request = ExplorerEntityRequest.builder()
.attribute("PurchaseId")
@ -40,6 +42,7 @@ public class DFPExplorerService {
return explore(request);
}
@Cacheable(value = "traversal-pi", unless = "#result.isEmpty()")
public ExplorerEntity explorePaymentInstrument(final String id) {
ExplorerEntityRequest request = ExplorerEntityRequest.builder()
.attribute("PaymentInstrumentId")
@ -49,6 +52,7 @@ public class DFPExplorerService {
return explore(request);
}
@Cacheable(value = "traversal-user", unless = "#result.isEmpty()")
public ExplorerEntity exploreUser(final String id) {
ExplorerEntityRequest request = ExplorerEntityRequest.builder()
.attribute("UserId")
@ -59,12 +63,17 @@ public class DFPExplorerService {
}
private ExplorerEntity explore(final ExplorerEntityRequest request) {
return dfpClient
log.info("Start exploration of [{}] [{}]", request.getAttribute(), request.getValue());
ExplorerEntity result = dfpClient
.post()
.uri(dfpExplorerUrl)
.body(Mono.just(request), ExplorerEntityRequest.class)
.retrieve()
.bodyToMono(ExplorerEntity.class)
.block();
log.info("Exploration of [{}] [{}] has finished successfully", request.getAttribute(), request.getValue());
result.setRequestAttributeName(request.getAttribute());
result.setRequestAttributeValue(request.getValue());
return result;
}
}

Просмотреть файл

@ -159,7 +159,7 @@ public class DataSecurityService {
@NonNull final Queue queue,
@NonNull final String actor) {
return SetUtils.union(
Objects.requireNonNullElse(queue.getReviewers(), Collections.emptySet()),
Objects.requireNonNullElse(queue.getSupervisors(), Collections.emptySet()),
Objects.requireNonNullElse(queue.getReviewers(), Collections.emptySet()))
.contains(actor);
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше