Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 37 additions & 17 deletions connect_transformations/lookup_ff_request/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,21 @@
#
from typing import Dict

from connect.client import AsyncConnectClient, ClientError
from connect.client import AsyncConnectClient
from connect.eaas.core.decorators import router, transformation
from connect.eaas.core.inject.asynchronous import get_installation_client
from connect.eaas.core.responses import RowTransformationResponse
from fastapi import Depends

from connect_transformations.constants import MAX_API_CALL_CONNECTION_ERROR_RETRIES, SEPARATOR
from connect_transformations.constants import SEPARATOR
from connect_transformations.lookup_ff_request.exceptions import FFRequestLookupError
from connect_transformations.lookup_ff_request.models import Configuration, SubscriptionParameter
from connect_transformations.lookup_ff_request.utils import validate_lookup_ff_request
from connect_transformations.lookup_ff_request.utils import (
FF_REQ_COMMON_FILTERS,
FF_REQ_SELECT,
filter_requests_with_changes,
validate_lookup_ff_request,
)
from connect_transformations.models import Error, ValidationResult
from connect_transformations.utils import deep_itemgetter, is_input_column_nullable

Expand Down Expand Up @@ -47,8 +52,8 @@ async def lookup_ff_request(
if self.settings.get('asset_type'):
lookup[f'asset.{self.settings["asset_type"]}'] = row[self.settings['asset_column']]

lookup['params.name'] = self.settings['parameter']['name']
lookup['params.value'] = row[self.settings['parameter_column']]
lookup['asset.params.name'] = self.settings['parameter']['name']
lookup['asset.params.value'] = row[self.settings['parameter_column']]

try:
request = await self.get_request(lookup)
Expand All @@ -71,8 +76,9 @@ def extract_row_from_request(self, request, output_columns, item_id):
value = None
attr = col_config['attribute']
if attr == 'asset.parameter.value':
param_name = col_config['parameter_name']
for param_data in request['asset']['params']:
if param_data['name'] == self.settings['parameter']['name']:
if param_data['name'] == param_name:
value = param_data['value']
break
elif attr.startswith('asset.items.'):
Expand All @@ -96,7 +102,12 @@ def extract_item_attrs_from_request(self, item_attrs, row, request, item_id_valu
continue

for col_name, item_attr in item_attrs:
item_value = str(item.get(item_attr, ''))
if item_attr == 'quantity_delta':
item_value = int(item['quantity']) - int(item['old_quantity'])
else:
item_value = item.get(item_attr, '')

item_value = str(item_value)
row[col_name] += f'{SEPARATOR}{item_value}' if row[col_name] else item_value

async def get_request(self, lookup):
Expand All @@ -108,24 +119,33 @@ async def get_request(self, lookup):
except KeyError:
pass

for attempts_left in range(MAX_API_CALL_CONNECTION_ERROR_RETRIES, -1, -1):
try:
result = await self.retrieve_ff_requests(lookup)
break
except ClientError:
if not attempts_left:
raise
continue
result = await self.retrieve_ff_requests(lookup)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please clarify why you decided to remove retries?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This timeout was needed because the Connect client wasn't retrying in case of connection errors.

It was fixed cloudblue/connect-python-openapi-client#74

What I forgot to do is bump the requirements to apply this patch.

This retry should be also deleted in subscriptions lookup. I will do it a bit later.


await self.acache_put(k, result)
return result

async def retrieve_ff_requests(self, lookup):
requests = self.installation_client.requests.filter(**lookup).order_by('-created')
batch_context = self.transformation_request['batch']['context']
period_end = batch_context.get('period', {}).get('end')
additional_filters = {}
if period_end:
additional_filters['updated__lt'] = period_end

requests = self.installation_client.requests.filter(
**FF_REQ_COMMON_FILTERS,
**lookup,
**additional_filters,
).select(
*FF_REQ_SELECT,
).order_by('-updated')
requests = [r async for r in requests]

if len(requests) > 1:
requests = filter_requests_with_changes(requests)

result = None

async for item in requests:
for item in requests:
if result is None:
result = item
elif self.settings.get('action_if_multiple') == 'leave_empty':
Expand Down
25 changes: 25 additions & 0 deletions connect_transformations/lookup_ff_request/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
NOT_FOUND_CHOICES = ['leave_empty', 'fail']
MULTIPLE_CHOICES = ['leave_empty', 'fail', 'use_most_actual']

FF_REQ_COMMON_FILTERS = {
'status': 'approved',
}
FF_REQ_SELECT = ['-activation_key', '-template']


def validate_lookup_ff_request(data): # noqa: CCR001
data = data.dict(by_alias=True)
Expand Down Expand Up @@ -119,3 +124,23 @@ def validate_lookup_ff_request(data): # noqa: CCR001
return {
'overview': overview,
}


def filter_requests_with_changes(requests):
""" FF requests without any changes can be produced during synchronization.
Exclude them.
"""
filtered_requests = []
for request in requests:
changed = False
for item_data in request['asset']['items']:
if item_data['quantity'] != item_data['old_quantity']:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that this will work for 100% of scenarios. Can it be 2 request found and both are SYNC? If yes, we will get an empty result.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is theoretically possible. Thanks!

changed = True
break
if changed:
filtered_requests.append(request)

if not filtered_requests:
return requests[0]

return filtered_requests
4 changes: 2 additions & 2 deletions connect_transformations/lookup_subscription/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,12 @@ def extract_row_from_subscription(self, subscription, output_columns):
row[col_name] = value

if item_attrs:
self.extract_item_attrs_from_sutbscription(item_attrs, row, subscription)
self.extract_item_attrs_from_subscription(item_attrs, row, subscription)

return row

@staticmethod
def extract_item_attrs_from_sutbscription(item_attrs, row, subscription):
def extract_item_attrs_from_subscription(item_attrs, row, subscription):
for item in subscription['items']:
if (
item.get('item_type') == 'reservation'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,15 @@ const AVAILABLE_FF_REQUEST_ATTRS = [
},
{
value: 'asset.items.quantity',
label: 'Product item quantities',
label: 'Product item quantity',
},
{
value: 'asset.items.old_quantity',
label: 'Product item previous quantity',
},
{
value: 'asset.items.quantity_delta',
label: 'Product item quantity delta',
},
];

Expand Down Expand Up @@ -531,7 +539,7 @@ const createOutputRow = (parent, index, column, parameters, columnConfigs) => {
});

const handleSourceSelectChange = () => {
if (sourceSelect.value === 'parameter.value') {
if (sourceSelect.value === 'asset.parameter.value') {
paramNameSelect.style.display = 'block';
paramNameSelect.style.maxWidth = 'calc(28% - 10px)';
sourceSelect.style.maxWidth = 'calc(25% - 10px)';
Expand Down Expand Up @@ -573,7 +581,6 @@ const lookupFFRequest = (app) => {
global_id: 'Item Global ID',
mpn: 'Item MPN',
all: 'Include all',
skip: 'Skip items',
};

hideComponent('loader');
Expand Down Expand Up @@ -671,7 +678,7 @@ const lookupFFRequest = (app) => {
});

document.getElementById('item').addEventListener('change', () => {
if (['skip', 'all'].includes(document.getElementById('item').value)) {
if (document.getElementById('item').value === 'all') {
document.getElementById('item_column_group').style.display = 'none';
} else {
document.getElementById('item_column_group').style.display = 'block';
Expand Down Expand Up @@ -728,7 +735,7 @@ const lookupFFRequest = (app) => {
attribute: colSource,
};

if (colSource === 'parameter.value') {
if (colSource === 'asset.parameter.value') {
const paramName = document.getElementById(`source-param-select-${index}`).value;
columnConfig.parameter_name = paramName;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!doctype html><html><head><meta charset="utf-8"/><title>Transformations/Lookup FF request</title><script defer="defer" src="../vendors.b7829ba6a3fa12b6e5cb.js"></script><script defer="defer" src="../transformations/lookup_ff_request.5ad2ce11ef60a735c477.js"></script><link href="../transformations/lookup_ff_request.421df6826da3f2351526.css" rel="stylesheet"></head><body><div id="loader"></div><div id="app"><div class="main-container lookup" id="content"><div class="subtitle">Supported search by parameter and optional search by Subscription ID or Subscription External ID. Result contains only one item information, matching selected criteria.</div><div class="input-group"><p>Choose search by Subscription ID</p><label class="label" for="criteria">Subscription ID type</label> <select class="input" id="criteria"></select></div><div class="input-group" id="subscription_column"><label class="label" for="column">Input Column</label> <select class="input" id="column"></select></div><div class="input-group"><p>Choose search by parameter</p><label class="label" for="parameter">Parameter name</label> <select class="input" id="parameter"></select></div><div><label class="label" for="parameter_column">Input Column</label> <select class="input" id="parameter_column"></select></div><div class="input-group"><p>Choose Item match criteria</p><label class="label" for="item">Item ID type</label> <select class="input" id="item"></select></div><div id="item_column_group"><label class="label" for="item_column">Input Column</label> <select class="input" id="item_column"></select></div><div class="input-group"><p class="radio-buttons label">Choose what to do if match is not found:</p><div class="radio-buttons input"><input type="radio" id="not_found_leave_empty" name="if_not_found" value="leave_empty"/> <label for="not_found_leave_empty">Leave empty</label> <input type="radio" id="not_found_fail" name="if_not_found" value="fail"/> <label for="not_found_fail">Fail</label></div></div><div class="input-group"><p class="radio-buttons label">Choose what to do if multiple matches are found:</p><div class="radio-buttons input"><input type="radio" id="multiple_use_most_actual" name="if_multiple" value="use_most_actual"/> <label for="multiple_use_most_actual">Use the most recent</label> <input type="radio" id="multiple_leave_empty" name="if_multiple" value="leave_empty"/> <label for="multiple_leave_empty">Leave empty</label> <input type="radio" id="multiple_fail" name="if_multiple" value="fail"/> <label for="multiple_fail">Fail</label></div></div><div class="output"><label>Output columns:</label><div id="output-columns"></div><div class="button-container"><button id="add" class="button">ADD</button></div></div></div></div></body></html>
<!doctype html><html><head><meta charset="utf-8"/><title>Transformations/Lookup FF request</title><script defer="defer" src="../vendors.b7829ba6a3fa12b6e5cb.js"></script><script defer="defer" src="../transformations/lookup_ff_request.2d9834915db04892e9cf.js"></script><link href="../transformations/lookup_ff_request.421df6826da3f2351526.css" rel="stylesheet"></head><body><div id="loader"></div><div id="app"><div class="main-container lookup" id="content"><div class="subtitle">Supported search by parameter and optional search by Subscription ID or Subscription External ID. Result contains only one item information, matching selected criteria.</div><div class="input-group"><p>Choose search by Subscription ID</p><label class="label" for="criteria">Subscription ID type</label> <select class="input" id="criteria"></select></div><div class="input-group" id="subscription_column"><label class="label" for="column">Input Column</label> <select class="input" id="column"></select></div><div class="input-group"><p>Choose search by parameter</p><label class="label" for="parameter">Parameter name</label> <select class="input" id="parameter"></select></div><div><label class="label" for="parameter_column">Input Column</label> <select class="input" id="parameter_column"></select></div><div class="input-group"><p>Choose Item match criteria</p><label class="label" for="item">Item ID type</label> <select class="input" id="item"></select></div><div id="item_column_group"><label class="label" for="item_column">Input Column</label> <select class="input" id="item_column"></select></div><div class="input-group"><p class="radio-buttons label">Choose what to do if match is not found:</p><div class="radio-buttons input"><input type="radio" id="not_found_leave_empty" name="if_not_found" value="leave_empty"/> <label for="not_found_leave_empty">Leave empty</label> <input type="radio" id="not_found_fail" name="if_not_found" value="fail"/> <label for="not_found_fail">Fail</label></div></div><div class="input-group"><p class="radio-buttons label">Choose what to do if multiple matches are found:</p><div class="radio-buttons input"><input type="radio" id="multiple_use_most_actual" name="if_multiple" value="use_most_actual"/> <label for="multiple_use_most_actual">Use the most recent</label> <input type="radio" id="multiple_leave_empty" name="if_multiple" value="leave_empty"/> <label for="multiple_leave_empty">Leave empty</label> <input type="radio" id="multiple_fail" name="if_multiple" value="fail"/> <label for="multiple_fail">Fail</label></div></div><div class="output"><label>Output columns:</label><div id="output-columns"></div><div class="button-container"><button id="add" class="button">ADD</button></div></div></div></div></body></html>
Loading