diff --git a/connect_transformations/formula/mixins.py b/connect_transformations/formula/mixins.py index 1ff7f24..65c36b4 100644 --- a/connect_transformations/formula/mixins.py +++ b/connect_transformations/formula/mixins.py @@ -11,7 +11,12 @@ from connect.eaas.core.responses import RowTransformationResponse from connect_transformations.formula.models import Configuration -from connect_transformations.formula.utils import DROP_REGEX, extract_input, validate_formula +from connect_transformations.formula.utils import ( + DROP_REGEX, + clear_formula, + extract_input, + validate_formula, +) from connect_transformations.models import Error, StreamsColumn, ValidationResult from connect_transformations.utils import cast_value_to_type, deep_convert_type @@ -30,7 +35,8 @@ def precompile(self, row: Dict): formula = expression['formula'] if DROP_REGEX.findall(formula): formula = f'def drop_row: "#INSTRUCTION/DELETE_ROW"; {formula}' - self.jq_expressions[expression['to']] = jq.compile(formula) + clean_formula = clear_formula(formula) + self.jq_expressions[expression['to']] = jq.compile(clean_formula) self.column_converters = [] diff --git a/connect_transformations/formula/utils.py b/connect_transformations/formula/utils.py index 628016a..84171a4 100644 --- a/connect_transformations/formula/utils.py +++ b/connect_transformations/formula/utils.py @@ -4,6 +4,7 @@ # All rights reserved. # import re +from collections import defaultdict import jq from fastapi.responses import JSONResponse @@ -18,6 +19,7 @@ JQ_FIELDS_REGEX = re.compile(r'(\.([a-z_][a-z0-9_]*))|(\."(.+?)")|(\.\["(.+?)"\])', re.I) DROP_REGEX = re.compile(r'(? 1 + and col_name_parts[-1] in cols_by_id + and cols_by_id[col_name_parts[-1]]['name'] == ' '.join(col_name_parts[:-1]) + ): + return cols_by_id[col_name_parts[-1]] + + return converter + + +def clear_formula(expression): + return COL_ID_REGEX.sub(r'."\1"', expression) diff --git a/connect_transformations/static/transformations/airtable_lookup.36093b636dae3ccdc875.js b/connect_transformations/static/transformations/airtable_lookup.2852161b91d2511b0373.js similarity index 98% rename from connect_transformations/static/transformations/airtable_lookup.36093b636dae3ccdc875.js rename to connect_transformations/static/transformations/airtable_lookup.2852161b91d2511b0373.js index ecfb44c..9157691 100644 --- a/connect_transformations/static/transformations/airtable_lookup.36093b636dae3ccdc875.js +++ b/connect_transformations/static/transformations/airtable_lookup.2852161b91d2511b0373.js @@ -141,6 +141,12 @@ const getAirtableTables = (key, baseId) => fetch(`/api/airtable_lookup/tables?ap }, }).then((response) => response.json()); +const getColumnLabel = (column) => { + const colIdParts = column.id.split('-'); + const colIdSuffix = colIdParts[colIdParts.length - 1]; + + return `${column.name} (C${colIdSuffix})`; +}; ;// CONCATENATED MODULE: ./ui/src/pages/transformations/airtable_lookup.js /* @@ -170,7 +176,7 @@ const createCopyRow = (parent, index, options, input, output) => { @@ -428,7 +434,6 @@ const airtable = (app) => { .then(airtable); - /***/ }) /******/ }); diff --git a/connect_transformations/static/transformations/airtable_lookup.html b/connect_transformations/static/transformations/airtable_lookup.html index 8c86d9c..ea03110 100644 --- a/connect_transformations/static/transformations/airtable_lookup.html +++ b/connect_transformations/static/transformations/airtable_lookup.html @@ -1 +1 @@ -Transformations/Airtable Lookup
Columns Mapping
\ No newline at end of file +Transformations/Airtable Lookup
Columns Mapping
\ No newline at end of file diff --git a/connect_transformations/static/transformations/attachment_lookup.ce7b0ca6b8388fab423e.js b/connect_transformations/static/transformations/attachment_lookup.073059714271bc4cd2f6.js similarity index 98% rename from connect_transformations/static/transformations/attachment_lookup.ce7b0ca6b8388fab423e.js rename to connect_transformations/static/transformations/attachment_lookup.073059714271bc4cd2f6.js index f8bbd58..6c1fbb9 100644 --- a/connect_transformations/static/transformations/attachment_lookup.ce7b0ca6b8388fab423e.js +++ b/connect_transformations/static/transformations/attachment_lookup.073059714271bc4cd2f6.js @@ -143,6 +143,12 @@ const getAirtableTables = (key, baseId) => fetch(`/api/airtable_lookup/tables?ap }, }).then((response) => response.json()); +const getColumnLabel = (column) => { + const colIdParts = column.id.split('-'); + const colIdSuffix = colIdParts[colIdParts.length - 1]; + + return `${column.name} (C${colIdSuffix})`; +}; ;// CONCATENATED MODULE: ./ui/src/pages/transformations/attachment_lookup.js /* @@ -177,7 +183,7 @@ const fillSelect = (options, id, value) => { options.forEach((item) => { const option = document.createElement('option'); option.value = item.id; - option.text = item.name; + option.text = getColumnLabel(item); if (item.id === value) { option.selected = true; } diff --git a/connect_transformations/static/transformations/attachment_lookup.html b/connect_transformations/static/transformations/attachment_lookup.html index 991d93f..5528030 100644 --- a/connect_transformations/static/transformations/attachment_lookup.html +++ b/connect_transformations/static/transformations/attachment_lookup.html @@ -1 +1 @@ -Transformations/Attachment Lookup
\ No newline at end of file +Transformations/Attachment Lookup
\ No newline at end of file diff --git a/connect_transformations/static/transformations/copy.1ede3336839fbbe21e63.js b/connect_transformations/static/transformations/copy.3f7e33696135d1752653.js similarity index 98% rename from connect_transformations/static/transformations/copy.1ede3336839fbbe21e63.js rename to connect_transformations/static/transformations/copy.3f7e33696135d1752653.js index 2597216..ce36551 100644 --- a/connect_transformations/static/transformations/copy.1ede3336839fbbe21e63.js +++ b/connect_transformations/static/transformations/copy.3f7e33696135d1752653.js @@ -143,6 +143,12 @@ const getAirtableTables = (key, baseId) => fetch(`/api/airtable_lookup/tables?ap }, }).then((response) => response.json()); +const getColumnLabel = (column) => { + const colIdParts = column.id.split('-'); + const colIdSuffix = colIdParts[colIdParts.length - 1]; + + return `${column.name} (C${colIdSuffix})`; +}; ;// CONCATENATED MODULE: ./ui/src/pages/transformations/copy.js /* @@ -166,7 +172,7 @@ const createCopyRow = (parent, index, options, input, output) => { diff --git a/connect_transformations/static/transformations/copy.html b/connect_transformations/static/transformations/copy.html index f3b3d31..31a6461 100755 --- a/connect_transformations/static/transformations/copy.html +++ b/connect_transformations/static/transformations/copy.html @@ -1 +1 @@ -Transformations/Copy
\ No newline at end of file +Transformations/Copy
\ No newline at end of file diff --git a/connect_transformations/static/transformations/currency_conversion.c112386fe452cc74af5b.js b/connect_transformations/static/transformations/currency_conversion.345ae7ad31b241768362.js similarity index 97% rename from connect_transformations/static/transformations/currency_conversion.c112386fe452cc74af5b.js rename to connect_transformations/static/transformations/currency_conversion.345ae7ad31b241768362.js index 11daebb..45b69b5 100644 --- a/connect_transformations/static/transformations/currency_conversion.c112386fe452cc74af5b.js +++ b/connect_transformations/static/transformations/currency_conversion.345ae7ad31b241768362.js @@ -79,6 +79,12 @@ const getAirtableTables = (key, baseId) => fetch(`/api/airtable_lookup/tables?ap }, }).then((response) => response.json()); +const getColumnLabel = (column) => { + const colIdParts = column.id.split('-'); + const colIdSuffix = colIdParts[colIdParts.length - 1]; + + return `${column.name} (C${colIdSuffix})`; +}; ;// CONCATENATED MODULE: ./ui/src/components.js /* @@ -194,8 +200,8 @@ const convert = (app) => { columns.forEach(column => { const isSelected = settings && column.name === settings.from.column; - - const option = isSelected ? `` : ``; + const colLabel = getColumnLabel(column); + const option = isSelected ? `` : ``; inputColumnSelect.innerHTML += option; }); diff --git a/connect_transformations/static/transformations/currency_conversion.html b/connect_transformations/static/transformations/currency_conversion.html index f4edc2b..a93030a 100644 --- a/connect_transformations/static/transformations/currency_conversion.html +++ b/connect_transformations/static/transformations/currency_conversion.html @@ -1 +1 @@ -Transformations/Currency Conversion
\ No newline at end of file +Transformations/Currency Conversion
\ No newline at end of file diff --git a/connect_transformations/static/transformations/filter_row.97891e81a14862909dfb.js b/connect_transformations/static/transformations/filter_row.348ec003c725618baa08.js similarity index 97% rename from connect_transformations/static/transformations/filter_row.97891e81a14862909dfb.js rename to connect_transformations/static/transformations/filter_row.348ec003c725618baa08.js index 2393d00..7feca74 100644 --- a/connect_transformations/static/transformations/filter_row.97891e81a14862909dfb.js +++ b/connect_transformations/static/transformations/filter_row.348ec003c725618baa08.js @@ -143,6 +143,12 @@ const getAirtableTables = (key, baseId) => fetch(`/api/airtable_lookup/tables?ap }, }).then((response) => response.json()); +const getColumnLabel = (column) => { + const colIdParts = column.id.split('-'); + const colIdSuffix = colIdParts[colIdParts.length - 1]; + + return `${column.name} (C${colIdSuffix})`; +}; ;// CONCATENATED MODULE: ./ui/src/pages/transformations/filter_row.js /* @@ -199,7 +205,7 @@ const filterRow = (app) => { availableColumns.forEach((column) => { const option = document.createElement('option'); option.value = column.id; - option.text = column.name; + option.text = getColumnLabel(column); document.getElementById('column').appendChild(option); }); @@ -247,13 +253,12 @@ const filterRow = (app) => { hideComponent('app'); const inputSelector = document.getElementById('column'); - const selectedColumn = inputSelector.options[inputSelector.selectedIndex].text; const inputColumn = columns.find((column) => column.id === inputSelector.value); const matchCondition = document.getElementById('match').checked; data.columns.input.push(inputColumn); data.columns.output.push( { - name: `${selectedColumn}_INSTRUCTIONS`, + name: `${inputColumn.name}_INSTRUCTIONS`, type: 'string', output: false, }, @@ -261,7 +266,7 @@ const filterRow = (app) => { const inputValue = document.getElementById('value'); data.settings = { - from: selectedColumn, + from: inputColumn.name, value: inputValue.value, match_condition: matchCondition, additional_values: [], diff --git a/connect_transformations/static/transformations/filter_row.html b/connect_transformations/static/transformations/filter_row.html index ad35033..2c93583 100644 --- a/connect_transformations/static/transformations/filter_row.html +++ b/connect_transformations/static/transformations/filter_row.html @@ -1 +1 @@ -Transformations/Filter Row

How to filter rows by condition:

\ No newline at end of file +Transformations/Filter Row

How to filter rows by condition:

\ No newline at end of file diff --git a/connect_transformations/static/transformations/formula.7e06ea00eb29f0f8219a.js b/connect_transformations/static/transformations/formula.9c406fdcdf9b7c4c43cc.js similarity index 98% rename from connect_transformations/static/transformations/formula.7e06ea00eb29f0f8219a.js rename to connect_transformations/static/transformations/formula.9c406fdcdf9b7c4c43cc.js index 4d6b405..05bdc8e 100644 --- a/connect_transformations/static/transformations/formula.7e06ea00eb29f0f8219a.js +++ b/connect_transformations/static/transformations/formula.9c406fdcdf9b7c4c43cc.js @@ -440,6 +440,12 @@ const getAirtableTables = (key, baseId) => fetch(`/api/airtable_lookup/tables?ap }, }).then((response) => response.json()); +const getColumnLabel = (column) => { + const colIdParts = column.id.split('-'); + const colIdSuffix = colIdParts[colIdParts.length - 1]; + + return `${column.name} (C${colIdSuffix})`; +}; ;// CONCATENATED MODULE: ./ui/src/components.js /* @@ -635,11 +641,11 @@ const formula = (app) => { /* eslint-disable-next-line */ const pattern = /[ .,|*:;{}[\]+\/%]/; suggestor = { '.': availableColumns.map(col => { - const needsQuotes = pattern.test(col.name); + const colLabel = getColumnLabel(col); return { - title: col.name, - value: needsQuotes ? `."${col.name}"` : `.${col.name}`, + title: colLabel, + value: `."${colLabel}"`, }; }) }; diff --git a/connect_transformations/static/transformations/formula.html b/connect_transformations/static/transformations/formula.html index d2990c8..d7321b5 100644 --- a/connect_transformations/static/transformations/formula.html +++ b/connect_transformations/static/transformations/formula.html @@ -1 +1 @@ -Transformations/Formula
\ No newline at end of file +Transformations/Formula
\ No newline at end of file diff --git a/connect_transformations/static/transformations/lookup_product_item.ea28feb331922b987dae.js b/connect_transformations/static/transformations/lookup_product_item.aec320dd162749d8e3a1.js similarity index 98% rename from connect_transformations/static/transformations/lookup_product_item.ea28feb331922b987dae.js rename to connect_transformations/static/transformations/lookup_product_item.aec320dd162749d8e3a1.js index 9f38bba..29689e0 100644 --- a/connect_transformations/static/transformations/lookup_product_item.ea28feb331922b987dae.js +++ b/connect_transformations/static/transformations/lookup_product_item.aec320dd162749d8e3a1.js @@ -81,6 +81,12 @@ const getAirtableTables = (key, baseId) => fetch(`/api/airtable_lookup/tables?ap }, }).then((response) => response.json()); +const getColumnLabel = (column) => { + const colIdParts = column.id.split('-'); + const colIdSuffix = colIdParts[colIdParts.length - 1]; + + return `${column.name} (C${colIdSuffix})`; +}; ;// CONCATENATED MODULE: ./ui/src/components.js /* @@ -209,7 +215,7 @@ const lookupProductItem = (app) => { availableColumns.forEach((column) => { const option = document.createElement('option'); option.value = column.id; - option.text = column.name; + option.text = getColumnLabel(column); document.getElementById('column').appendChild(option); const anotherOption = document.createElement('option'); diff --git a/connect_transformations/static/transformations/lookup_product_item.html b/connect_transformations/static/transformations/lookup_product_item.html index 4d2b896..a2fa0c2 100644 --- a/connect_transformations/static/transformations/lookup_product_item.html +++ b/connect_transformations/static/transformations/lookup_product_item.html @@ -1 +1 @@ -Transformations/Lookup Product Item
Supported search criteria includes CloudBlue Item MPN and CloudBlue Item ID.
Warning: No product associated with this data stream. Please either enter a Product ID, which will be applied to all product item lookups, or select a Product column containing the row-specific product IDs.

Please specify a way to find product:


What To Do If Product Item Not Found:

\ No newline at end of file +Transformations/Lookup Product Item
Supported search criteria includes CloudBlue Item MPN and CloudBlue Item ID.
Warning: No product associated with this data stream. Please either enter a Product ID, which will be applied to all product item lookups, or select a Product column containing the row-specific product IDs.

Please specify a way to find product:


What To Do If Product Item Not Found:

\ No newline at end of file diff --git a/connect_transformations/static/transformations/lookup_subscription.680e46a724ad0732665c.js b/connect_transformations/static/transformations/lookup_subscription.d97c942578580bd45e85.js similarity index 98% rename from connect_transformations/static/transformations/lookup_subscription.680e46a724ad0732665c.js rename to connect_transformations/static/transformations/lookup_subscription.d97c942578580bd45e85.js index e6ba39f..45c844d 100644 --- a/connect_transformations/static/transformations/lookup_subscription.680e46a724ad0732665c.js +++ b/connect_transformations/static/transformations/lookup_subscription.d97c942578580bd45e85.js @@ -81,6 +81,12 @@ const getAirtableTables = (key, baseId) => fetch(`/api/airtable_lookup/tables?ap }, }).then((response) => response.json()); +const getColumnLabel = (column) => { + const colIdParts = column.id.split('-'); + const colIdSuffix = colIdParts[colIdParts.length - 1]; + + return `${column.name} (C${colIdSuffix})`; +}; ;// CONCATENATED MODULE: ./ui/src/components.js /* @@ -199,7 +205,7 @@ const lookupSubscription = (app) => { availableColumns.forEach((column) => { const option = document.createElement('option'); option.value = column.id; - option.text = column.name; + option.text = getColumnLabel(column); document.getElementById('column').appendChild(option); }); diff --git a/connect_transformations/static/transformations/lookup_subscription.html b/connect_transformations/static/transformations/lookup_subscription.html index 6688756..e5bc16a 100755 --- a/connect_transformations/static/transformations/lookup_subscription.html +++ b/connect_transformations/static/transformations/lookup_subscription.html @@ -1 +1 @@ -Transformations/Lookup Subscription
Supported search criteria includes Subscription External ID, ID and Parameter values.

What To Do If Subscription Not Found:

\ No newline at end of file +Transformations/Lookup Subscription
Supported search criteria includes Subscription External ID, ID and Parameter values.

What To Do If Subscription Not Found:

\ No newline at end of file diff --git a/connect_transformations/static/transformations/split_column.9f7e57c0dbd5023c171e.js b/connect_transformations/static/transformations/split_column.377e90dbad3e37167c11.js similarity index 98% rename from connect_transformations/static/transformations/split_column.9f7e57c0dbd5023c171e.js rename to connect_transformations/static/transformations/split_column.377e90dbad3e37167c11.js index afb6b10..2701614 100644 --- a/connect_transformations/static/transformations/split_column.9f7e57c0dbd5023c171e.js +++ b/connect_transformations/static/transformations/split_column.377e90dbad3e37167c11.js @@ -81,6 +81,12 @@ const getAirtableTables = (key, baseId) => fetch(`/api/airtable_lookup/tables?ap }, }).then((response) => response.json()); +const getColumnLabel = (column) => { + const colIdParts = column.id.split('-'); + const colIdSuffix = colIdParts[colIdParts.length - 1]; + + return `${column.name} (C${colIdSuffix})`; +}; ;// CONCATENATED MODULE: ./ui/src/components.js /* @@ -297,7 +303,7 @@ const splitColumn = (app) => { availableColumns.forEach((column) => { const option = document.createElement('option'); option.value = column.id; - option.text = column.name; + option.text = getColumnLabel(column); document.getElementById('column').appendChild(option); }); @@ -328,7 +334,6 @@ const splitColumn = (app) => { hideComponent('app'); const inputSelector = document.getElementById('column'); - const selectedColumn = inputSelector.options[inputSelector.selectedIndex].text; const inputColumn = columns.find((column) => column.id === inputSelector.value); data.columns.input.push(inputColumn); @@ -351,7 +356,7 @@ const splitColumn = (app) => { } data.settings = { - from: selectedColumn, + from: inputColumn.name, regex: { pattern: document.getElementById('pattern').value, groups: getCurrentGroups(document.getElementById('output')), diff --git a/connect_transformations/static/transformations/split_column.html b/connect_transformations/static/transformations/split_column.html index 32a633a..711ed1e 100644 --- a/connect_transformations/static/transformations/split_column.html +++ b/connect_transformations/static/transformations/split_column.html @@ -1 +1 @@ -Transformations/Split Column
\ No newline at end of file +Transformations/Split Column
\ No newline at end of file diff --git a/connect_transformations/static/transformations/vat_rate.c50ddb25b0ef46860a4d.js b/connect_transformations/static/transformations/vat_rate.c752c401f029e90036be.js similarity index 95% rename from connect_transformations/static/transformations/vat_rate.c50ddb25b0ef46860a4d.js rename to connect_transformations/static/transformations/vat_rate.c752c401f029e90036be.js index 3026413..6f5550d 100644 --- a/connect_transformations/static/transformations/vat_rate.c50ddb25b0ef46860a4d.js +++ b/connect_transformations/static/transformations/vat_rate.c752c401f029e90036be.js @@ -79,6 +79,12 @@ const getAirtableTables = (key, baseId) => fetch(`/api/airtable_lookup/tables?ap }, }).then((response) => response.json()); +const getColumnLabel = (column) => { + const colIdParts = column.id.split('-'); + const colIdSuffix = colIdParts[colIdParts.length - 1]; + + return `${column.name} (C${colIdSuffix})`; +}; ;// CONCATENATED MODULE: ./ui/src/components.js /* @@ -175,8 +181,11 @@ const vatRate = (app) => { const inputColumnSelect = document.getElementById('input-column'); const outputColumnInput = document.getElementById('output-column'); columns.forEach(column => { - const isSelected = settings && column.name === settings.from; - const option = isSelected ? `` : ``; + const isSelected = settings && column.id === settings.from; + const colLabel = getColumnLabel(column); + const option = isSelected + ? `` + : ``; inputColumnSelect.innerHTML += option; }); @@ -196,7 +205,7 @@ const vatRate = (app) => { app.listen('save', async () => { const inputColumnValue = document.getElementById('input-column').value; - const inputColumn = columns.find(column => column.name === inputColumnValue); + const inputColumn = columns.find(column => column.id === inputColumnValue); const outputColumnValue = document.getElementById('output-column').value; const actionIfNotFound = document.getElementById('leave_empty').checked ? 'leave_empty' : 'fail'; @@ -207,7 +216,7 @@ const vatRate = (app) => { } else { const data = { settings: { - from: inputColumnValue, + from: inputColumn.name, to: outputColumnValue, action_if_not_found: actionIfNotFound, }, diff --git a/connect_transformations/static/transformations/vat_rate.html b/connect_transformations/static/transformations/vat_rate.html index e615d4c..677debe 100644 --- a/connect_transformations/static/transformations/vat_rate.html +++ b/connect_transformations/static/transformations/vat_rate.html @@ -1 +1 @@ -Transformations/VAT Rate

What To Do For Non-EU Country Codes

\ No newline at end of file +Transformations/VAT Rate

What To Do For Non-EU Country Codes

\ No newline at end of file diff --git a/connect_transformations/vat_rate/utils.py b/connect_transformations/vat_rate/utils.py index bfff60c..556516f 100644 --- a/connect_transformations/vat_rate/utils.py +++ b/connect_transformations/vat_rate/utils.py @@ -16,8 +16,6 @@ def validate_vat_rate(data): if has_invalid_basic_structure(data): return build_error_response('Invalid input data') - input_columns = data['columns']['input'] - available_input_columns = [c['name'] for c in input_columns] if does_not_contain_required_keys( data['settings'], ['from', 'to', 'action_if_not_found'], @@ -27,7 +25,7 @@ def validate_vat_rate(data): 'fields', ) - if data['settings']['from'] not in available_input_columns: + if all(c['name'] != data['settings']['from'] for c in data['columns']['input']): return build_error_response( 'The settings contains an invalid `from` column name' f' "{data["settings"]["from"]}" that does not exist on ' diff --git a/tests/webapp/test_formula.py b/tests/webapp/test_formula.py index 2285feb..6cabab6 100644 --- a/tests/webapp/test_formula.py +++ b/tests/webapp/test_formula.py @@ -59,6 +59,40 @@ def test_validate_formula( } +def test_validate_formula_the_same_name( + test_client_factory, +): + data = { + 'settings': { + 'expressions': [ + { + 'to': 'Price', + 'formula': '."Price (C001)" + .Tax', + 'ignore_errors': False, + 'type': 'integer', + }, + ], + }, + 'columns': { + 'input': [ + {'id': 'COL-111-001', 'name': 'Price', 'nullable': False}, + {'id': 'COL-111-002', 'name': 'Tax', 'nullable': False}, + ], + 'output': [], + }, + } + client = test_client_factory(TransformationsWebApplication) + response = client.post('/api/formula/validate', json=data) + + assert response.status_code == 200, response.content + data = response.json() + assert data == { + 'overview': ( + 'Price = ."Price (C001)" + .Tax\n' + ), + } + + def test_validate_formula_invalid_input( test_client_factory, ): @@ -156,38 +190,6 @@ def test_validate_formula_invalid_expression( } -def test_validate_formula_unique_error( - test_client_factory, -): - data = { - 'settings': { - 'expressions': [ - { - 'to': 'Price', - 'formula': '.(Price) + .(Tax)', - 'ignore_errors': False, - 'type': 'integer', - }, - ], - }, - 'columns': { - 'input': [ - {'id': 'COL-1', 'name': 'Price', 'nullable': False}, - {'id': 'COL-2', 'name': 'Tax', 'nullable': False}, - ], - 'output': [], - }, - } - client = test_client_factory(TransformationsWebApplication) - response = client.post('/api/formula/validate', json=data) - - assert response.status_code == 400 - data = response.json() - assert data == { - 'error': 'Column `Price` already exists.', - } - - def test_validate_formula_duplicated_input_error( test_client_factory, ): @@ -295,7 +297,7 @@ def test_validate_formula_non_existing_column_parenthesis( assert data == { 'error': ( 'Settings contains formula `.Price + .Tax` ' - 'with column that does not exist on columns.input.' + 'with column `Price` that does not exist on columns.input.' ), } @@ -331,7 +333,7 @@ def test_validate_formula_non_existing_column( assert data == { 'error': ( 'Settings contains formula `.Price + .(Tax)` ' - 'with column that does not exist on columns.input.' + 'with column `Price` that does not exist on columns.input.' ), } @@ -367,7 +369,7 @@ def test_validate_formula_non_existing_column_double_quote( assert data == { 'error': ( 'Settings contains formula `."Price without Tax" + ."Tax federal"` ' - 'with column that does not exist on columns.input.' + 'with column `Tax federal` that does not exist on columns.input.' ), } @@ -410,17 +412,23 @@ def test_extract_formula_input( 'expressions': [ { 'to': 'Price with Tax', - 'formula': '.["Price without Tax"] + .Tax + ."Additional fee"', + 'formula': '.["Price without Tax"] + .Tax + ."Fee"', + 'type': 'decimal', + 'precision': '2', + }, + { + 'to': 'MSRP', + 'formula': '.["Price without Tax (C001)"] * 1.05 + ."Tax (C002)" + ."Fee"', 'type': 'decimal', 'precision': '2', }, ], 'columns': [ - {'name': 'Price without Tax', 'nullable': False}, - {'name': 'Tax', 'nullable': False}, - {'name': 'Additional fee', 'nullable': False}, - {'name': 'Created', 'nullable': False}, - {'name': 'Purchase', 'nullable': False}, + {'id': 'C-1-001', 'name': 'Price without Tax', 'nullable': False}, + {'id': 'C-1-002', 'name': 'Tax', 'nullable': False}, + {'id': 'C-1-003', 'name': 'Fee', 'nullable': False}, + {'id': 'C-1-004', 'name': 'Created', 'nullable': False}, + {'id': 'C-1-005', 'name': 'Purchase', 'nullable': False}, ], } client = test_client_factory(TransformationsWebApplication) @@ -429,9 +437,61 @@ def test_extract_formula_input( assert response.status_code == 200 data = response.json() assert len(data) == 3 - assert StreamsColumn(**{'name': 'Price without Tax', 'nullable': False}).dict() in data - assert StreamsColumn(**{'name': 'Tax', 'nullable': False}).dict() in data - assert StreamsColumn(**{'name': 'Additional fee', 'nullable': False}).dict() in data + assert StreamsColumn(**{'id': 'C-1-001', 'name': 'Price without Tax', 'nullable': False}).dict() + assert StreamsColumn(**{'id': 'C-1-002', 'name': 'Tax', 'nullable': False}).dict() in data + assert StreamsColumn(**{'id': 'C-1-003', 'name': 'Fee', 'nullable': False}).dict() in data + + +def test_extract_formula_input_dup_name_wo_suffix( + test_client_factory, +): + data = { + 'expressions': [ + { + 'to': 'Price with Tax', + 'formula': '.["Price"]', + 'type': 'decimal', + 'precision': '2', + }, + ], + 'columns': [ + {'id': 'COL-111-001', 'name': 'Price', 'nullable': False}, + {'id': 'COL-111-002', 'name': 'Price', 'nullable': False}, + ], + } + client = test_client_factory(TransformationsWebApplication) + response = client.post('/api/formula/extract_input', json=data) + + assert response.status_code == 200 + data = response.json() + assert len(data) == 1 + assert StreamsColumn(**{'id': 'COL-111-002', 'name': 'Price', 'nullable': False}).dict() in data + + +def test_extract_formula_input_dup_name_w_suffix( + test_client_factory, +): + data = { + 'expressions': [ + { + 'to': 'Price with Tax', + 'formula': '."Price (C001)"', + 'type': 'decimal', + 'precision': '2', + }, + ], + 'columns': [ + {'id': 'COL-111-001', 'name': 'Price', 'nullable': False}, + {'id': 'COL-111-002', 'name': 'Price', 'nullable': False}, + ], + } + client = test_client_factory(TransformationsWebApplication) + response = client.post('/api/formula/extract_input', json=data) + + assert response.status_code == 200 + data = response.json() + assert len(data) == 1 + assert StreamsColumn(**{'id': 'COL-111-001', 'name': 'Price', 'nullable': False}).dict() in data def test_extract_formula_input_invalid_expressions( diff --git a/ui/src/pages/transformations/airtable_lookup.js b/ui/src/pages/transformations/airtable_lookup.js index 4f029c8..dcd4f73 100644 --- a/ui/src/pages/transformations/airtable_lookup.js +++ b/ui/src/pages/transformations/airtable_lookup.js @@ -16,6 +16,7 @@ import { import { getAirtableBases, getAirtableTables, + getColumnLabel, validate, } from '../../utils'; @@ -34,7 +35,7 @@ const createCopyRow = (parent, index, options, input, output) => { @@ -290,4 +291,3 @@ const airtable = (app) => { createApp({ }) .then(airtable); - diff --git a/ui/src/pages/transformations/attachment_lookup.js b/ui/src/pages/transformations/attachment_lookup.js index e53eada..293ff7c 100644 --- a/ui/src/pages/transformations/attachment_lookup.js +++ b/ui/src/pages/transformations/attachment_lookup.js @@ -18,6 +18,7 @@ import { import { getAttachments, + getColumnLabel, validate, } from '../../utils'; @@ -40,7 +41,7 @@ export const fillSelect = (options, id, value) => { options.forEach((item) => { const option = document.createElement('option'); option.value = item.id; - option.text = item.name; + option.text = getColumnLabel(item); if (item.id === value) { option.selected = true; } diff --git a/ui/src/pages/transformations/copy.js b/ui/src/pages/transformations/copy.js index cb0f99d..c91893b 100644 --- a/ui/src/pages/transformations/copy.js +++ b/ui/src/pages/transformations/copy.js @@ -12,6 +12,7 @@ import { showError, } from '../../components'; import { + getColumnLabel, validate, } from '../../utils'; @@ -25,7 +26,7 @@ export const createCopyRow = (parent, index, options, input, output) => { diff --git a/ui/src/pages/transformations/currency_conversion.js b/ui/src/pages/transformations/currency_conversion.js index cbbc2d9..f890bce 100644 --- a/ui/src/pages/transformations/currency_conversion.js +++ b/ui/src/pages/transformations/currency_conversion.js @@ -7,6 +7,7 @@ import '@fontsource/roboto/500.css'; import '../../../styles/index.css'; import '../../../styles/app.styl'; import { + getColumnLabel, getCurrencies, validate, } from '../../utils'; @@ -56,8 +57,8 @@ const convert = (app) => { columns.forEach(column => { const isSelected = settings && column.name === settings.from.column; - - const option = isSelected ? `` : ``; + const colLabel = getColumnLabel(column); + const option = isSelected ? `` : ``; inputColumnSelect.innerHTML += option; }); diff --git a/ui/src/pages/transformations/filter_row.js b/ui/src/pages/transformations/filter_row.js index b1b82b3..d4ea87f 100644 --- a/ui/src/pages/transformations/filter_row.js +++ b/ui/src/pages/transformations/filter_row.js @@ -13,6 +13,7 @@ import { showError, } from '../../components'; import { + getColumnLabel, validate, } from '../../utils'; @@ -58,7 +59,7 @@ export const filterRow = (app) => { availableColumns.forEach((column) => { const option = document.createElement('option'); option.value = column.id; - option.text = column.name; + option.text = getColumnLabel(column); document.getElementById('column').appendChild(option); }); @@ -106,13 +107,12 @@ export const filterRow = (app) => { hideComponent('app'); const inputSelector = document.getElementById('column'); - const selectedColumn = inputSelector.options[inputSelector.selectedIndex].text; const inputColumn = columns.find((column) => column.id === inputSelector.value); const matchCondition = document.getElementById('match').checked; data.columns.input.push(inputColumn); data.columns.output.push( { - name: `${selectedColumn}_INSTRUCTIONS`, + name: `${inputColumn.name}_INSTRUCTIONS`, type: 'string', output: false, }, @@ -120,7 +120,7 @@ export const filterRow = (app) => { const inputValue = document.getElementById('value'); data.settings = { - from: selectedColumn, + from: inputColumn.name, value: inputValue.value, match_condition: matchCondition, additional_values: [], diff --git a/ui/src/pages/transformations/formula.js b/ui/src/pages/transformations/formula.js index 85122fc..d7c9831 100644 --- a/ui/src/pages/transformations/formula.js +++ b/ui/src/pages/transformations/formula.js @@ -9,6 +9,7 @@ import '../../../styles/index.css'; import '../../../styles/app.styl'; import '../../../styles/formula.css'; import { + getColumnLabel, getJQInput, validate, } from '../../utils'; @@ -136,11 +137,11 @@ export const formula = (app) => { /* eslint-disable-next-line */ const pattern = /[ .,|*:;{}[\]+\/%]/; suggestor = { '.': availableColumns.map(col => { - const needsQuotes = pattern.test(col.name); + const colLabel = getColumnLabel(col); return { - title: col.name, - value: needsQuotes ? `."${col.name}"` : `.${col.name}`, + title: colLabel, + value: `."${colLabel}"`, }; }) }; diff --git a/ui/src/pages/transformations/lookup_product_item.js b/ui/src/pages/transformations/lookup_product_item.js index 596f2a2..5141bfc 100644 --- a/ui/src/pages/transformations/lookup_product_item.js +++ b/ui/src/pages/transformations/lookup_product_item.js @@ -8,6 +8,7 @@ import '../../../styles/index.css'; import '../../../styles/lookup.css'; import '../../../styles/app.styl'; import { + getColumnLabel, validate, } from '../../utils'; import { @@ -68,7 +69,7 @@ export const lookupProductItem = (app) => { availableColumns.forEach((column) => { const option = document.createElement('option'); option.value = column.id; - option.text = column.name; + option.text = getColumnLabel(column); document.getElementById('column').appendChild(option); const anotherOption = document.createElement('option'); diff --git a/ui/src/pages/transformations/lookup_subscription.js b/ui/src/pages/transformations/lookup_subscription.js index 754fcb4..0299a42 100644 --- a/ui/src/pages/transformations/lookup_subscription.js +++ b/ui/src/pages/transformations/lookup_subscription.js @@ -8,6 +8,7 @@ import '../../../styles/index.css'; import '../../../styles/lookup.css'; import '../../../styles/app.styl'; import { + getColumnLabel, getLookupSubscriptionParameters, validate, } from '../../utils'; @@ -59,7 +60,7 @@ export const lookupSubscription = (app) => { availableColumns.forEach((column) => { const option = document.createElement('option'); option.value = column.id; - option.text = column.name; + option.text = getColumnLabel(column); document.getElementById('column').appendChild(option); }); diff --git a/ui/src/pages/transformations/split_column.js b/ui/src/pages/transformations/split_column.js index 9e058a3..f9ebd62 100644 --- a/ui/src/pages/transformations/split_column.js +++ b/ui/src/pages/transformations/split_column.js @@ -8,6 +8,7 @@ import '../../../styles/index.css'; import '../../../styles/split_column.css'; import '../../../styles/app.styl'; import { + getColumnLabel, getGroups, validate, } from '../../utils'; @@ -157,7 +158,7 @@ export const splitColumn = (app) => { availableColumns.forEach((column) => { const option = document.createElement('option'); option.value = column.id; - option.text = column.name; + option.text = getColumnLabel(column); document.getElementById('column').appendChild(option); }); @@ -188,7 +189,6 @@ export const splitColumn = (app) => { hideComponent('app'); const inputSelector = document.getElementById('column'); - const selectedColumn = inputSelector.options[inputSelector.selectedIndex].text; const inputColumn = columns.find((column) => column.id === inputSelector.value); data.columns.input.push(inputColumn); @@ -211,7 +211,7 @@ export const splitColumn = (app) => { } data.settings = { - from: selectedColumn, + from: inputColumn.name, regex: { pattern: document.getElementById('pattern').value, groups: getCurrentGroups(document.getElementById('output')), diff --git a/ui/src/pages/transformations/vat_rate.js b/ui/src/pages/transformations/vat_rate.js index 21ed6c0..7d34184 100644 --- a/ui/src/pages/transformations/vat_rate.js +++ b/ui/src/pages/transformations/vat_rate.js @@ -8,6 +8,7 @@ import '../../../styles/index.css'; import '../../../styles/app.styl'; import '../../../styles/vat_rate.css'; import { + getColumnLabel, validate, } from '../../utils'; @@ -36,8 +37,11 @@ const vatRate = (app) => { const inputColumnSelect = document.getElementById('input-column'); const outputColumnInput = document.getElementById('output-column'); columns.forEach(column => { - const isSelected = settings && column.name === settings.from; - const option = isSelected ? `` : ``; + const isSelected = settings && column.id === settings.from; + const colLabel = getColumnLabel(column); + const option = isSelected + ? `` + : ``; inputColumnSelect.innerHTML += option; }); @@ -57,7 +61,7 @@ const vatRate = (app) => { app.listen('save', async () => { const inputColumnValue = document.getElementById('input-column').value; - const inputColumn = columns.find(column => column.name === inputColumnValue); + const inputColumn = columns.find(column => column.id === inputColumnValue); const outputColumnValue = document.getElementById('output-column').value; const actionIfNotFound = document.getElementById('leave_empty').checked ? 'leave_empty' : 'fail'; @@ -68,7 +72,7 @@ const vatRate = (app) => { } else { const data = { settings: { - from: inputColumnValue, + from: inputColumn.name, to: outputColumnValue, action_if_not_found: actionIfNotFound, }, diff --git a/ui/src/utils.js b/ui/src/utils.js index c2df53b..6c1c6e6 100644 --- a/ui/src/utils.js +++ b/ui/src/utils.js @@ -68,3 +68,9 @@ export const getAirtableTables = (key, baseId) => fetch(`/api/airtable_lookup/ta }, }).then((response) => response.json()); +export const getColumnLabel = (column) => { + const colIdParts = column.id.split('-'); + const colIdSuffix = colIdParts[colIdParts.length - 1]; + + return `${column.name} (C${colIdSuffix})`; +};