Skip to content

Commit f9c5df6

Browse files
authored
Add types to existing common values formatter (#680)
* add types to existing common values formatter * fix unit test for event type * fix types
1 parent 465c40e commit f9c5df6

15 files changed

+175
-77
lines changed

src/utils/data-formatters/__tests__/format-duration-to-seconds.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe('formatDurationToSeconds', () => {
1010
});
1111

1212
test('should return seconds as number for valid input', () => {
13-
const duration = { seconds: 60 };
13+
const duration = { seconds: '60' };
1414
expect(formatDurationToSeconds(duration)).toBe(60);
1515
});
1616

@@ -20,7 +20,7 @@ describe('formatDurationToSeconds', () => {
2020
});
2121

2222
test('should handle zero seconds', () => {
23-
const duration = { seconds: 0 };
23+
const duration = { seconds: '0' };
2424
expect(formatDurationToSeconds(duration)).toBe(0);
2525
});
2626

src/utils/data-formatters/__tests__/format-failure-details.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import formatFailureDetails from '../format-failure-details';
33
describe('formatFailureDetails', () => {
44
test('should return null if failure details are not provided', () => {
55
const failure = {};
6+
// @ts-expect-error Testing with wrong type `{}`
67
expect(formatFailureDetails(failure)).toBeNull();
78
});
89

910
test('should return null if failure details are null', () => {
1011
const failure = { details: null };
12+
// @ts-expect-error Testing with wrong type `{ details: null}`
1113
expect(formatFailureDetails(failure)).toBeNull();
1214
});
1315

src/utils/data-formatters/__tests__/format-payload-map.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe('formatPayloadMap', () => {
2020

2121
test('should return null if fieldKey is not present in map', () => {
2222
const map = { otherKey: { subkey: { value: 'test' } } };
23+
// @ts-expect-error Testing without passing `someKey` property
2324
expect(formatPayloadMap(map, 'someKey')).toBeNull();
2425
});
2526

src/utils/data-formatters/__tests__/format-prev-auto-reset-points.test.ts

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { type ResetPoints } from '@/__generated__/proto-ts/uber/cadence/api/v1/ResetPoints';
2+
13
import formatPrevAutoResetPoints from '../format-prev-auto-reset-points';
24
import formatTimestampToDatetime from '../format-timestamp-to-datetime';
35

@@ -13,6 +15,7 @@ describe('formatPrevAutoResetPoints', () => {
1315
});
1416

1517
test('should return null if prevAutoResetPoints is not provided', () => {
18+
// @ts-expect-error Testing with `undefined`
1619
expect(formatPrevAutoResetPoints(undefined)).toBeNull();
1720
});
1821

@@ -28,12 +31,15 @@ describe('formatPrevAutoResetPoints', () => {
2831
});
2932

3033
test('should format prevAutoResetPoints correctly for valid input', () => {
31-
const prevAutoResetPoints = {
34+
const prevAutoResetPoints: ResetPoints = {
3235
points: [
3336
{
34-
createdTime: { seconds: 1623153200, nanos: 0 },
35-
expiringTime: { seconds: 1623239600, nanos: 0 },
36-
otherProperty: 'some value',
37+
createdTime: { seconds: '1623153200', nanos: 0 },
38+
expiringTime: { seconds: '1623239600', nanos: 0 },
39+
binaryChecksum: '122434',
40+
firstDecisionCompletedId: '123',
41+
resettable: true,
42+
runId: '2348yjk',
3743
},
3844
],
3945
};
@@ -43,7 +49,10 @@ describe('formatPrevAutoResetPoints', () => {
4349
const expectedFormattedPoints = {
4450
points: [
4551
{
46-
otherProperty: 'some value',
52+
binaryChecksum: '122434',
53+
firstDecisionCompletedId: '123',
54+
resettable: true,
55+
runId: '2348yjk',
4756
createdTimeNano: formattedCreatedTime1,
4857
expiringTimeNano: formattedCreatedTime2,
4958
},
@@ -57,23 +66,39 @@ describe('formatPrevAutoResetPoints', () => {
5766
expectedFormattedPoints
5867
);
5968
expect(formatTimestampToDatetime).toHaveBeenCalledWith({
60-
seconds: 1623153200,
69+
seconds: '1623153200',
6170
nanos: 0,
6271
});
6372
expect(formatTimestampToDatetime).toHaveBeenCalledWith({
64-
seconds: 1623239600,
73+
seconds: '1623239600',
6574
nanos: 0,
6675
});
6776
});
6877

6978
test('should format prevAutoResetPoints correctly when createdTime or expiringTime is missing', () => {
70-
const prevAutoResetPoints = {
71-
points: [{ createdTime: { seconds: 1623153200, nanos: 0 } }],
79+
const prevAutoResetPoints: ResetPoints = {
80+
points: [
81+
{
82+
createdTime: { seconds: '1623153200', nanos: 0 },
83+
expiringTime: { seconds: '1623239600', nanos: 0 },
84+
binaryChecksum: '122434',
85+
firstDecisionCompletedId: '123',
86+
resettable: true,
87+
runId: '2348yjk',
88+
},
89+
],
7290
};
7391
const formattedCreatedTime1 = new Date(1623153200000);
7492
const expectedFormattedPoints = {
7593
points: [
76-
{ createdTimeNano: formattedCreatedTime1, expiringTimeNano: null },
94+
{
95+
createdTimeNano: formattedCreatedTime1,
96+
expiringTimeNano: null,
97+
binaryChecksum: '122434',
98+
firstDecisionCompletedId: '123',
99+
resettable: true,
100+
runId: '2348yjk',
101+
},
77102
],
78103
};
79104
mockedFormatTimestampToDatetime
@@ -84,7 +109,7 @@ describe('formatPrevAutoResetPoints', () => {
84109
expectedFormattedPoints
85110
);
86111
expect(formatTimestampToDatetime).toHaveBeenCalledWith({
87-
seconds: 1623153200,
112+
seconds: '1623153200',
88113
nanos: 0,
89114
});
90115
});

src/utils/data-formatters/__tests__/format-retry-policy.test.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { type RetryPolicy } from '@/__generated__/proto-ts/uber/cadence/api/v1/RetryPolicy';
2+
13
import formatDurationToSeconds from '../format-duration-to-seconds';
24
import formatRetryPolicy from '../format-retry-policy';
35

@@ -21,10 +23,13 @@ describe('formatRetryPolicy', () => {
2123
});
2224

2325
test('should return formatted retry policy with valid input', () => {
24-
const retryPolicy = {
25-
expirationInterval: { seconds: 60 },
26-
initialInterval: { seconds: '30' },
27-
maximumInterval: { seconds: 300 },
26+
const retryPolicy: RetryPolicy = {
27+
expirationInterval: { seconds: '60', nanos: 0 },
28+
initialInterval: { seconds: '30', nanos: 0 },
29+
maximumInterval: { seconds: '300', nanos: 0 },
30+
backoffCoefficient: 10,
31+
maximumAttempts: 10,
32+
nonRetryableErrorReasons: [],
2833
};
2934
const formattedExpirationInterval = 60;
3035
const formattedInitialInterval = 30;
@@ -39,6 +44,9 @@ describe('formatRetryPolicy', () => {
3944
expirationIntervalInSeconds: formattedExpirationInterval,
4045
initialIntervalInSeconds: formattedInitialInterval,
4146
maximumIntervalInSeconds: formattedMaximumInterval,
47+
backoffCoefficient: 10,
48+
maximumAttempts: 10,
49+
nonRetryableErrorReasons: [],
4250
};
4351

4452
expect(formatRetryPolicy(retryPolicy)).toEqual(

src/utils/data-formatters/__tests__/format-workflow-history-event-type.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,20 @@ describe('formatWorkflowHistoryEventType', () => {
1010
it('should handle strings without "EventAttributes" correctly', () => {
1111
const input = 'workflowExecutionStarted';
1212
const expectedOutput = 'WorkflowExecutionStarted';
13+
// @ts-expect-error Testing with wrong attribute `workflowExecutionStarted`
1314
expect(formatWorkflowHistoryEventType(input)).toEqual(expectedOutput);
1415
});
1516

1617
it('should handle empty strings correctly', () => {
1718
const input = '';
1819
const expectedOutput = '';
20+
// @ts-expect-error Testing with wrong attribute ``
1921
expect(formatWorkflowHistoryEventType(input)).toEqual(expectedOutput);
2022
});
2123

2224
it('should handle null input correctly', () => {
2325
const input = null;
2426
const expectedOutput = null;
25-
// @ts-expect-error Testing null
2627
expect(formatWorkflowHistoryEventType(input)).toEqual(expectedOutput);
2728
});
2829

@@ -36,18 +37,20 @@ describe('formatWorkflowHistoryEventType', () => {
3637
it('should handle single-character input correctly', () => {
3738
const input = 'a';
3839
const expectedOutput = 'A';
40+
// @ts-expect-error Testing with wrong attribute `a`
3941
expect(formatWorkflowHistoryEventType(input)).toEqual(expectedOutput);
4042
});
4143

4244
it('should handle strings that start with a capital letter correctly', () => {
43-
const input = 'WorkflowExecutionStartedEventAttributes';
45+
const input = 'workflowExecutionStartedEventAttributes';
4446
const expectedOutput = 'WorkflowExecutionStarted';
4547
expect(formatWorkflowHistoryEventType(input)).toEqual(expectedOutput);
4648
});
4749

4850
it('should handle strings that are already in the correct format', () => {
4951
const input = 'WorkflowExecutionStarted';
5052
const expectedOutput = 'WorkflowExecutionStarted';
53+
// @ts-expect-error Testing with wrong attribute `WorkflowExecutionStarted`
5154
expect(formatWorkflowHistoryEventType(input)).toEqual(expectedOutput);
5255
});
5356
});

src/utils/data-formatters/__tests__/format-workflow-history.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ describe('formatWorkflowHistory', () => {
3030
const expectedTimestamp = new Date('2023-06-18T12:34:56.Z');
3131
mockedFormatTimestampToDatetime.mockReturnValue(expectedTimestamp);
3232
mockedFormatWorkflowHistoryEvent.mockReturnValue({ formattedEvent: true });
33-
mockedFormatWorkflowHistoryEventType.mockReturnValue('FormattedEventType');
33+
mockedFormatWorkflowHistoryEventType.mockReturnValue(
34+
'ActivityTaskCanceled'
35+
);
3436

3537
const input = {
3638
archived: true,
@@ -54,7 +56,7 @@ describe('formatWorkflowHistory', () => {
5456
{
5557
eventId: 1,
5658
timestamp: expectedTimestamp,
57-
eventType: 'FormattedEventType',
59+
eventType: 'ActivityTaskCanceled',
5860
attributes: 'workflowExecutionStartedEventAttributes',
5961
formattedEvent: true,
6062
},

src/utils/data-formatters/format-duration-to-seconds.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2020
// THE SOFTWARE.
2121

22+
import { type Duration } from '@/__generated__/proto-ts/google/protobuf/Duration';
23+
2224
const formatDurationToSeconds = (
23-
duration?: { seconds: number | string } | null
25+
duration?: Pick<Duration, 'seconds'> | null
2426
) => (duration ? parseInt(String(duration.seconds)) : null);
2527

2628
export default formatDurationToSeconds;

src/utils/data-formatters/format-enum.ts

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,33 +24,81 @@ import lowerCase from 'lodash/lowerCase';
2424
import snakeCase from 'lodash/snakeCase';
2525
import startCase from 'lodash/startCase';
2626

27-
const convertToUpper = (value: string) => value.toUpperCase();
27+
type Formatter = (value: string) => string;
2828

29-
const upperSnakeCase = flowRight([snakeCase, convertToUpper]);
29+
const convertToUpper: Formatter = (value: string) => value.toUpperCase();
3030

31-
const removeWhiteSpace = (value: string) => value.replace(/\s/g, '');
31+
const upperSnakeCase: Formatter = flowRight([snakeCase, convertToUpper]);
3232

33-
const pascalCase = flowRight([lowerCase, startCase, removeWhiteSpace]);
33+
const removeWhiteSpace: Formatter = (value: string) => value.replace(/\s/g, '');
3434

35+
const pascalCase: Formatter = flowRight([
36+
lowerCase,
37+
startCase,
38+
removeWhiteSpace,
39+
]);
40+
41+
// Case formatter map with strict typing
3542
const caseFormatterMap = {
3643
snake: upperSnakeCase,
3744
pascal: pascalCase,
3845
} as const;
3946

40-
const formatEnum = (
41-
value: string,
42-
prefix?: string,
43-
caseFormat: keyof typeof caseFormatterMap = 'snake'
44-
) => {
47+
// Case format types
48+
type CaseFormat = keyof typeof caseFormatterMap;
49+
50+
// Utility type to remove a given prefix from a string
51+
type RemovePrefix<
52+
T extends string,
53+
Prefix extends string,
54+
> = T extends `${Prefix}_${infer Rest}` ? Rest : T;
55+
56+
// Template literal types for string transformation
57+
type ToUpperSnakeCase<T extends string> = T extends `${infer Head}${infer Tail}`
58+
? `${Uppercase<Head>}${ToUpperSnakeCase<Tail>}`
59+
: T;
60+
61+
type ToPascalCase<T extends string> = T extends `${infer First}_${infer Rest}`
62+
? `${Capitalize<Lowercase<First>>}${ToPascalCase<Capitalize<Rest>>}`
63+
: Capitalize<Lowercase<T>>;
64+
65+
// Utility type to check if a string contains "INVALID" and exclude it from the type
66+
type ExcludeInvalid<T extends string> = T extends `${infer _}INVALID${infer _}`
67+
? never
68+
: T;
69+
70+
type FormattedEnumValue<
71+
T extends string,
72+
P extends string,
73+
F extends CaseFormat,
74+
> =
75+
ExcludeInvalid<T> extends never
76+
? null
77+
: F extends 'snake'
78+
? ToUpperSnakeCase<RemovePrefix<ExcludeInvalid<T>, P>> | null
79+
: ToPascalCase<RemovePrefix<ExcludeInvalid<T>, P>> | null;
80+
81+
const formatEnum = <
82+
T extends string,
83+
P extends string = '',
84+
F extends CaseFormat = 'snake',
85+
>(
86+
value: T | null | undefined,
87+
prefix?: P,
88+
caseFormat: F = 'snake' as F
89+
): FormattedEnumValue<T, P, F> => {
4590
if (!value || value.includes('INVALID')) {
4691
return null;
4792
}
93+
94+
// Remove prefix from the value if provided
4895
const valueRemovedPrefix = prefix
4996
? value.replace(new RegExp(`^${prefix}_`), '')
5097
: value;
98+
5199
const caseFormatter = caseFormatterMap[caseFormat];
52100

53-
return caseFormatter(valueRemovedPrefix);
101+
return caseFormatter(valueRemovedPrefix) as FormattedEnumValue<T, P, F>;
54102
};
55103

56104
export default formatEnum;

src/utils/data-formatters/format-failure-details.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2020
// THE SOFTWARE.
2121

22-
const formatFailureDetails = (failure: { details?: string | null }) => {
22+
import { type Failure } from '@/__generated__/proto-ts/uber/cadence/api/v1/Failure';
23+
24+
const formatFailureDetails = (failure: Pick<Failure, 'details'> | null) => {
2325
if (!failure?.details) {
2426
return null;
2527
}

0 commit comments

Comments
 (0)