Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(app): Add corresponding string op to FE to parse number with thousands and decimal separators #2959

Merged
merged 3 commits into from
Nov 15, 2024
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
72 changes: 72 additions & 0 deletions weave-js/src/core/ops/primitives/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from '../../model';
import {testClient} from '../../testUtil';
import {
opParseNumberWithSeparator,
opStringAdd,
opStringAppend,
opStringContains,
Expand Down Expand Up @@ -944,3 +945,74 @@ describe('opStringLevenshtein', () => {
expect(distances).toEqual(EXPECTED_DISTANCES);
});
});

async function parseNumberWithSeparatorQuery(
str: string,
thousandsSeparator: string | null,
decimalSeparator: string | null
) {
return (await testClient()).query(
opParseNumberWithSeparator({
str: constString(str),
thousands_separator: thousandsSeparator
? constString(thousandsSeparator)
: constNone(),
decimal_separator: decimalSeparator
? constString(decimalSeparator)
: constNone(),
})
);
}

describe('opParseNumberWithSeparator', () => {
it('parses the specified thousands separator', async () => {
await expect(
parseNumberWithSeparatorQuery('123,456,008', ',', null)
).resolves.toEqual(123456008.0);
await expect(
parseNumberWithSeparatorQuery('123,456,008', '.', null)
).resolves.toEqual(123);
await expect(
parseNumberWithSeparatorQuery('123,456.008', ',', null)
).resolves.toEqual(123456.008);
await expect(
parseNumberWithSeparatorQuery('123_456_008', '_', null)
).resolves.toEqual(123456008.0);
await expect(
parseNumberWithSeparatorQuery('123 456 008', ' ', null)
).resolves.toEqual(123456008.0);
await expect(
parseNumberWithSeparatorQuery('123.456.008', '.', null)
).resolves.toEqual(123456008.0);
});

it('parses the specified decimal separator', async () => {
await expect(
parseNumberWithSeparatorQuery('123456.008', null, '.')
).resolves.toEqual(123456.008);
await expect(
parseNumberWithSeparatorQuery('123456,008', null, ',')
).resolves.toEqual(123456.008);
await expect(
parseNumberWithSeparatorQuery('123456008', null, '.')
).resolves.toEqual(123456008.0);
await expect(
parseNumberWithSeparatorQuery('123,456,008', null, '.')
).resolves.toEqual(123);
});

it('parses both the specified thousands separator and decimal separator', async () => {
await expect(
parseNumberWithSeparatorQuery('123,456.008', ',', '.')
).resolves.toEqual(123456.008);
await expect(
parseNumberWithSeparatorQuery('123.456,008', '.', ',')
).resolves.toEqual(123456.008);
await expect(
parseNumberWithSeparatorQuery('123 456,008', ' ', ',')
).resolves.toEqual(123456.008);
await expect(
parseNumberWithSeparatorQuery('123 456,008', ',', '.')
).resolves.toEqual(123);
});
});
40 changes: 39 additions & 1 deletion weave-js/src/core/ops/primitives/string.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import levenshtein from 'js-levenshtein';

import {nullableSkipTaggable} from '../../model';
import {maybe, nullableSkipTaggable} from '../../model';
import {docType} from '../../util/docs';
import {
makeBinaryStandardOp,
Expand Down Expand Up @@ -401,6 +401,44 @@ export const opStringRightStrip = makeStringOp({
resolver: ({str}) => str?.trimEnd() ?? '',
});

export const opParseNumberWithSeparator = makeStringOp({
name: 'string-parseNumberWithSeparator',
argTypes: {
str: {type: 'union', members: ['none', 'string']},
thousands_separator: {type: 'union', members: ['none', 'string']},
decimal_separator: {type: 'union', members: ['none', 'string']},
},
description: `Parse a string to a number`,
argDescriptions: {
str: `The ${docType('string')} to parse.`,
thousands_separator:
'The delimiter used to partition the string into thousands groupings.',
decimal_separator:
'The symbol used to separate the integer part of the number from the fractional part.',
},
returnValueDescription: `A floating point number, if the ${docType(
'string'
)} is a valid numeral, and null otherwise.`,
returnType: inputTypes => maybe('string'),
resolver: ({str, thousands_separator, decimal_separator}) => {
if (!str) {
return null;
}

let maybeNumber = str;

if (thousands_separator) {
maybeNumber = maybeNumber.replaceAll(thousands_separator, '');
}

if (decimal_separator) {
maybeNumber = maybeNumber.replace(decimal_separator, '.');
}

return parseFloat(maybeNumber) || null;
},
});

// Levenshtein distance is the minimum number of single-character
// edits it would take to go from one string to another
export const opStringLevenshtein = makeStringOp({
Expand Down
Loading