From 11e791b0d1d3bf2ec0402b358c4d86d6216d0bd2 Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Wed, 21 Oct 2020 17:02:12 -0700 Subject: [PATCH 1/2] feat: support schema as a launch input type --- .../Launch/LaunchForm/LaunchFormInputs.tsx | 1 - .../Launch/LaunchForm/SimpleInput.tsx | 1 + .../Launch/LaunchForm/__mocks__/utils.ts | 31 ++ src/components/Launch/LaunchForm/constants.ts | 2 +- .../LaunchForm/inputHelpers/constants.ts | 1 + .../inputHelpers/getHelperForInput.ts | 3 +- .../Launch/LaunchForm/inputHelpers/schema.ts | 30 ++ .../inputHelpers/test/inputHelpers.test.ts | 150 +++++---- .../LaunchForm/inputHelpers/test/testCases.ts | 292 ++++++++++++------ .../inputHelpers/test/utils.test.ts | 66 ++-- .../Launch/LaunchForm/inputHelpers/utils.ts | 2 +- src/components/Launch/LaunchForm/types.ts | 2 + src/components/Launch/LaunchForm/utils.ts | 48 ++- 13 files changed, 413 insertions(+), 216 deletions(-) create mode 100644 src/components/Launch/LaunchForm/inputHelpers/schema.ts diff --git a/src/components/Launch/LaunchForm/LaunchFormInputs.tsx b/src/components/Launch/LaunchForm/LaunchFormInputs.tsx index 01ae9adb5..2dbf5cab8 100644 --- a/src/components/Launch/LaunchForm/LaunchFormInputs.tsx +++ b/src/components/Launch/LaunchForm/LaunchFormInputs.tsx @@ -23,7 +23,6 @@ function getComponentForInput(input: InputProps, showErrors: boolean) { case InputType.Collection: return ; case InputType.Map: - case InputType.Schema: case InputType.Unknown: case InputType.None: return ; diff --git a/src/components/Launch/LaunchForm/SimpleInput.tsx b/src/components/Launch/LaunchForm/SimpleInput.tsx index 5f4aa8f5f..058f9fbf5 100644 --- a/src/components/Launch/LaunchForm/SimpleInput.tsx +++ b/src/components/Launch/LaunchForm/SimpleInput.tsx @@ -55,6 +55,7 @@ export const SimpleInput: React.FC = props => { ); case InputType.Datetime: return ; + case InputType.Schema: case InputType.String: case InputType.Integer: case InputType.Float: diff --git a/src/components/Launch/LaunchForm/__mocks__/utils.ts b/src/components/Launch/LaunchForm/__mocks__/utils.ts index 4b41bf796..2d5efcad8 100644 --- a/src/components/Launch/LaunchForm/__mocks__/utils.ts +++ b/src/components/Launch/LaunchForm/__mocks__/utils.ts @@ -1,5 +1,6 @@ import { Core } from 'flyteidl'; import { BlobDimensionality } from 'models'; +import { InputType, InputTypeDefinition } from '../types'; export function primitiveLiteral(primitive: Core.IPrimitive): Core.ILiteral { return { scalar: { primitive } }; @@ -20,3 +21,33 @@ export function blobLiteral({ } }; } + +export function collectionInputTypeDefinition( + typeDefinition: InputTypeDefinition +): InputTypeDefinition { + return { + literalType: { + collectionType: typeDefinition.literalType + }, + type: InputType.Collection, + subtype: typeDefinition + }; +} + +export function nestedCollectionInputTypeDefinition( + typeDefinition: InputTypeDefinition +): InputTypeDefinition { + return { + literalType: { + collectionType: { + collectionType: typeDefinition.literalType + } + }, + type: InputType.Collection, + subtype: { + literalType: { collectionType: typeDefinition.literalType }, + type: InputType.Collection, + subtype: typeDefinition + } + }; +} diff --git a/src/components/Launch/LaunchForm/constants.ts b/src/components/Launch/LaunchForm/constants.ts index 31f18c1e4..398a79761 100644 --- a/src/components/Launch/LaunchForm/constants.ts +++ b/src/components/Launch/LaunchForm/constants.ts @@ -36,7 +36,7 @@ export const typeLabels: { [k in InputType]: string } = { [InputType.Integer]: 'integer', [InputType.Map]: '', [InputType.None]: 'none', - [InputType.Schema]: 'schema', + [InputType.Schema]: 'schema - uri', [InputType.String]: 'string', [InputType.Struct]: 'struct', [InputType.Unknown]: 'unknown' diff --git a/src/components/Launch/LaunchForm/inputHelpers/constants.ts b/src/components/Launch/LaunchForm/inputHelpers/constants.ts index 15aad09e3..0951a3a89 100644 --- a/src/components/Launch/LaunchForm/inputHelpers/constants.ts +++ b/src/components/Launch/LaunchForm/inputHelpers/constants.ts @@ -8,6 +8,7 @@ export function literalNone(): Core.ILiteral { export const allowedDateFormats = [ISO_8601, RFC_2822]; const primitivePath = 'scalar.primitive'; +export const schemaUriPath = 'scalar.schema.uri'; /** Strings constants which can be used to perform a deep `get` on a scalar * literal type using a primitive value. diff --git a/src/components/Launch/LaunchForm/inputHelpers/getHelperForInput.ts b/src/components/Launch/LaunchForm/inputHelpers/getHelperForInput.ts index ef6c69ec8..d58d8b191 100644 --- a/src/components/Launch/LaunchForm/inputHelpers/getHelperForInput.ts +++ b/src/components/Launch/LaunchForm/inputHelpers/getHelperForInput.ts @@ -7,6 +7,7 @@ import { durationHelper } from './duration'; import { floatHelper } from './float'; import { integerHelper } from './integer'; import { noneHelper } from './none'; +import { schemaHelper } from './schema'; import { stringHelper } from './string'; import { InputHelper } from './types'; @@ -25,7 +26,7 @@ const inputHelpers: Record = { [InputType.Integer]: integerHelper, [InputType.Map]: unsupportedHelper, [InputType.None]: noneHelper, - [InputType.Schema]: unsupportedHelper, + [InputType.Schema]: schemaHelper, [InputType.String]: stringHelper, [InputType.Struct]: unsupportedHelper, [InputType.Unknown]: unsupportedHelper diff --git a/src/components/Launch/LaunchForm/inputHelpers/schema.ts b/src/components/Launch/LaunchForm/inputHelpers/schema.ts new file mode 100644 index 000000000..5533144bb --- /dev/null +++ b/src/components/Launch/LaunchForm/inputHelpers/schema.ts @@ -0,0 +1,30 @@ +import { Core } from 'flyteidl'; +import { InputValue } from '../types'; +import { schemaUriPath } from './constants'; +import { ConverterInput, InputHelper, InputValidatorParams } from './types'; +import { extractLiteralWithCheck } from './utils'; + +function fromLiteral(literal: Core.ILiteral): InputValue { + return extractLiteralWithCheck(literal, schemaUriPath); +} + +function toLiteral({ typeDefinition, value }: ConverterInput): Core.ILiteral { + const uri = typeof value === 'string' ? value : value.toString(); + // Note: schema type may be undefined if user is working with a generic schema. + const { + literalType: { schema: type } + } = typeDefinition; + return { scalar: { schema: { type, uri } } }; +} + +function validate({ value }: InputValidatorParams) { + if (typeof value !== 'string') { + throw new Error('Value is not a string'); + } +} + +export const schemaHelper: InputHelper = { + fromLiteral, + toLiteral, + validate +}; diff --git a/src/components/Launch/LaunchForm/inputHelpers/test/inputHelpers.test.ts b/src/components/Launch/LaunchForm/inputHelpers/test/inputHelpers.test.ts index b62c6211a..f845e4fd1 100644 --- a/src/components/Launch/LaunchForm/inputHelpers/test/inputHelpers.test.ts +++ b/src/components/Launch/LaunchForm/inputHelpers/test/inputHelpers.test.ts @@ -1,8 +1,12 @@ import { Core } from 'flyteidl'; import * as Long from 'long'; import { BlobDimensionality } from 'models'; -import { primitiveLiteral } from '../../__mocks__/utils'; -import { InputProps, InputType } from '../../types'; +import { + collectionInputTypeDefinition, + nestedCollectionInputTypeDefinition, + primitiveLiteral +} from '../../__mocks__/utils'; +import { InputProps, InputType, InputTypeDefinition } from '../../types'; import { literalNone } from '../constants'; import { getHelperForInput } from '../getHelperForInput'; import { @@ -12,6 +16,7 @@ import { } from '../inputHelpers'; import { collectionChildToString } from '../utils'; import { + inputTypes, literalTestCases, literalToInputTestCases, supportedPrimitives, @@ -25,69 +30,80 @@ const baseInputProps: InputProps = { name: '', onChange: () => {}, required: false, - typeDefinition: { type: InputType.Unknown } + typeDefinition: inputTypes.unknown }; -function makeSimpleInput(type: InputType, value: any): InputProps { - return { ...baseInputProps, value, typeDefinition: { type } }; +function makeSimpleInput( + typeDefinition: InputTypeDefinition, + value: any +): InputProps { + return { ...baseInputProps, value, typeDefinition }; } -function makeCollectionInput(type: InputType, value: string): InputProps { +function makeCollectionInput( + typeDefinition: InputTypeDefinition, + value: string +): InputProps { return { ...baseInputProps, value, - typeDefinition: { type: InputType.Collection, subtype: { type } } + typeDefinition: collectionInputTypeDefinition(typeDefinition) }; } -function makeNestedCollectionInput(type: InputType, value: string): InputProps { +function makeNestedCollectionInput( + typeDefinition: InputTypeDefinition, + value: string +): InputProps { return { ...baseInputProps, value, - typeDefinition: { - type: InputType.Collection, - subtype: { type: InputType.Collection, subtype: { type } } - } + typeDefinition: nestedCollectionInputTypeDefinition(typeDefinition) }; } describe('literalToInputValue', () => { describe('Primitives', () => { - literalToInputTestCases.map(([type, input, output]) => - it(`should correctly convert ${type}: ${JSON.stringify( - input - )}`, () => - expect(literalToInputValue({ type }, input)).toEqual(output)) + literalToInputTestCases.map(([typeDefinition, input, output]) => + it(`should correctly convert ${ + typeDefinition.type + }: ${JSON.stringify(input)}`, () => + expect(literalToInputValue(typeDefinition, input)).toEqual( + output + )) ); - supportedPrimitives.map(type => - it(`should convert None value for ${type} to undefined`, () => + supportedPrimitives.map(typeDefinition => + it(`should convert None value for ${typeDefinition.type} to undefined`, () => expect( - literalToInputValue({ type }, literalNone()) + literalToInputValue(typeDefinition, literalNone()) ).toBeUndefined()) ); it('should correctly convert noneType to undefined', () => - expect( - literalToInputValue({ type: InputType.None }, literalNone()) - ).toEqual(undefined)); + expect(literalToInputValue(inputTypes.none, literalNone())).toEqual( + undefined + )); }); describe('Collections', () => { - literalToInputTestCases.map(([type, input, output]) => { - it(`should correctly convert collection of ${type}: ${JSON.stringify( - input - )}`, () => { + literalToInputTestCases.map(([typeDefinition, input, output]) => { + it(`should correctly convert collection of ${ + typeDefinition.type + }: ${JSON.stringify(input)}`, () => { const collection: Core.ILiteral = { collection: { // Duplicate it to test comma separation literals: [input, input] } }; - const stringifiedValue = collectionChildToString(type, output); + const stringifiedValue = collectionChildToString( + typeDefinition.type, + output + ); const expectedString = `[${stringifiedValue},${stringifiedValue}]`; const result = literalToInputValue( - { type: InputType.Collection, subtype: { type } }, + collectionInputTypeDefinition(typeDefinition), collection ); expect(result).toEqual(expectedString); @@ -102,12 +118,14 @@ describe('literalToInputValue', () => { } }; + const typeDefinition: InputTypeDefinition = { + literalType: { simple: Core.SimpleType.NONE }, + type: InputType.None + }; + expect( literalToInputValue( - { - type: InputType.Collection, - subtype: { type: InputType.None } - }, + collectionInputTypeDefinition(typeDefinition), collection ) ).toEqual('[]'); @@ -118,7 +136,7 @@ describe('literalToInputValue', () => { const { defaultValue } = getHelperForInput(InputType.Boolean); expect( literalToInputValue( - { type: InputType.Boolean }, + inputTypes.boolean, // Invalid boolean input value because it uses the string field { scalar: { primitive: { stringValue: 'whoops' } } } ) @@ -128,18 +146,18 @@ describe('literalToInputValue', () => { describe('inputToLiteral', () => { describe('Scalars', () => { - literalTestCases.map(([type, input, output]) => { - it(`should correctly convert ${type}: ${JSON.stringify( - input - )} (${typeof input})`, () => - expect(inputToLiteral(makeSimpleInput(type, input))).toEqual( - output - )); + literalTestCases.map(([typeDefinition, input, output]) => { + it(`should correctly convert ${ + typeDefinition.type + }: ${JSON.stringify(input)} (${typeof input})`, () => + expect( + inputToLiteral(makeSimpleInput(typeDefinition, input)) + ).toEqual(output)); }); }); describe('Collections', () => { - literalTestCases.map(([type, input, output]) => { + literalTestCases.map(([typeDefinition, input, output]) => { let value: any; if (['boolean', 'number'].includes(typeof input)) { value = input; @@ -153,20 +171,20 @@ describe('inputToLiteral', () => { value = JSON.stringify(input); } - it(`should correctly convert collection of type ${type}: [${JSON.stringify( - value - )}] (${typeof input})`, () => { + it(`should correctly convert collection of type ${ + typeDefinition.type + }: [${JSON.stringify(value)}] (${typeof input})`, () => { const result = inputToLiteral( - makeCollectionInput(type, `[${value}]`) + makeCollectionInput(typeDefinition, `[${value}]`) ); expect(result.collection!.literals![0]).toEqual(output); }); - it(`should correctly convert nested collection of type ${type}: [[${JSON.stringify( - value - )}]] (${typeof input})`, () => { + it(`should correctly convert nested collection of type ${ + typeDefinition.type + }: [[${JSON.stringify(value)}]] (${typeof input})`, () => { const result = inputToLiteral( - makeNestedCollectionInput(type, `[[${value}]]`) + makeNestedCollectionInput(typeDefinition, `[[${value}]]`) ); expect( result.collection!.literals![0].collection!.literals![0] @@ -176,10 +194,10 @@ describe('inputToLiteral', () => { }); describe('Unsupported Types', () => { - unsupportedTypes.map(type => - it(`should return empty value for type: ${type}`, () => { + unsupportedTypes.map(typeDefinition => + it(`should return empty value for type: ${typeDefinition.type}`, () => { expect( - inputToLiteral(makeSimpleInput(type, '')).scalar + inputToLiteral(makeSimpleInput(typeDefinition, '')).scalar ).toEqual({ noneType: {} }); }) ); @@ -187,7 +205,7 @@ describe('inputToLiteral', () => { it('Should return initial value for inputs with no value', () => { const simpleInput = makeSimpleInput( - InputType.String, + inputTypes.string, primitiveLiteral({ stringValue: '' }) ); const initialValue = primitiveLiteral({ stringValue: 'abcdefg' }); @@ -199,14 +217,14 @@ describe('inputToLiteral', () => { }); function generateValidityTests( - type: InputType, + typeDefinition: InputTypeDefinition, { valid, invalid }: { valid: any[]; invalid: any[] } ) { valid.map(value => it(`should treat ${JSON.stringify( value )} (${typeof value}) as valid`, () => { - const input = makeSimpleInput(type, value); + const input = makeSimpleInput(typeDefinition, value); expect(() => validateInput(input)).not.toThrowError(); }) ); @@ -214,38 +232,38 @@ function generateValidityTests( it(`should treat ${JSON.stringify( value )} (${typeof value}) as invalid`, () => { - const input = makeSimpleInput(type, value); + const input = makeSimpleInput(typeDefinition, value); expect(() => validateInput(input)).toThrowError(); }) ); } describe('validateInput', () => { describe('boolean', () => { - generateValidityTests(InputType.Boolean, validityTestCases.boolean); + generateValidityTests(inputTypes.boolean, validityTestCases.boolean); }); describe('blob', () => { - generateValidityTests(InputType.Blob, validityTestCases.blob); + generateValidityTests(inputTypes.blobSingle, validityTestCases.blob); }); describe('datetime', () => { - generateValidityTests(InputType.Datetime, validityTestCases.datetime); + generateValidityTests(inputTypes.datetime, validityTestCases.datetime); }); describe('duration', () => { - generateValidityTests(InputType.Duration, validityTestCases.duration); + generateValidityTests(inputTypes.duration, validityTestCases.duration); }); describe('float', () => { - generateValidityTests(InputType.Float, validityTestCases.float); + generateValidityTests(inputTypes.float, validityTestCases.float); }); describe('integer', () => { - generateValidityTests(InputType.Integer, validityTestCases.integer); + generateValidityTests(inputTypes.integer, validityTestCases.integer); }); describe('string', () => { - generateValidityTests(InputType.String, validityTestCases.string); + generateValidityTests(inputTypes.string, validityTestCases.string); }); it('should throw errors for missing required simple values', () => { @@ -258,7 +276,7 @@ describe('validateInput', () => { it('should throw errors for missing required Blob values', () => { // URI is the only required, user-provided value with no default - const simpleInput = makeSimpleInput(InputType.Blob, { + const simpleInput = makeSimpleInput(inputTypes.blobSingle, { format: 'csv', dimensionality: BlobDimensionality.SINGLE }); @@ -268,7 +286,7 @@ describe('validateInput', () => { it('should not throw an error for a required input with an initial value and no value', () => { const simpleInput = makeSimpleInput( - InputType.String, + inputTypes.string, primitiveLiteral({ stringValue: '' }) ); simpleInput.required = true; diff --git a/src/components/Launch/LaunchForm/inputHelpers/test/testCases.ts b/src/components/Launch/LaunchForm/inputHelpers/test/testCases.ts index 99c69a215..e79b8b1b7 100644 --- a/src/components/Launch/LaunchForm/inputHelpers/test/testCases.ts +++ b/src/components/Launch/LaunchForm/inputHelpers/test/testCases.ts @@ -1,32 +1,118 @@ import { dateToTimestamp, millisecondsToDuration } from 'common/utils'; import { Core } from 'flyteidl'; import * as Long from 'long'; -import { BlobDimensionality } from 'models'; +import { BlobDimensionality, SchemaColumnType } from 'models'; import { blobLiteral, primitiveLiteral } from '../../__mocks__/utils'; -import { InputType, InputValue } from '../../types'; +import { InputType, InputTypeDefinition, InputValue } from '../../types'; import { literalNone } from '../constants'; // Defines type of value, input, and expected value of a `Core.ILiteral` -type LiteralTestParams = [InputType, any, Core.ILiteral]; +type LiteralTestParams = [InputTypeDefinition, any, Core.ILiteral]; + +type InputTypeKey = + | 'binary' + | 'boolean' + | 'blobSingle' + | 'blobMulti' + | 'datetime' + | 'duration' + | 'error' + | 'integer' + | 'float' + | 'map' + | 'none' + | 'schema' + | 'string' + | 'struct' + | 'unknown'; +export const inputTypes: Record = { + binary: { + literalType: { simple: Core.SimpleType.BINARY }, + type: InputType.Binary + }, + boolean: { + literalType: { simple: Core.SimpleType.BOOLEAN }, + type: InputType.Boolean + }, + blobSingle: { + literalType: { blob: { dimensionality: BlobDimensionality.SINGLE } }, + type: InputType.Blob + }, + blobMulti: { + literalType: { blob: { dimensionality: BlobDimensionality.MULTIPART } }, + type: InputType.Blob + }, + datetime: { + literalType: { simple: Core.SimpleType.DATETIME }, + type: InputType.Datetime + }, + duration: { + literalType: { simple: Core.SimpleType.DURATION }, + type: InputType.Duration + }, + error: { + literalType: { simple: Core.SimpleType.ERROR }, + type: InputType.Error + }, + integer: { + literalType: { simple: Core.SimpleType.INTEGER }, + type: InputType.Integer + }, + float: { + literalType: { simple: Core.SimpleType.FLOAT }, + type: InputType.Float + }, + map: { + literalType: { + mapValueType: { simple: Core.SimpleType.STRING } + }, + type: InputType.Map + }, + none: { + literalType: { simple: Core.SimpleType.NONE }, + type: InputType.None + }, + schema: { + literalType: { + schema: { + columns: [{ name: 'column1', type: SchemaColumnType.STRING }] + } + }, + type: InputType.Schema + }, + string: { + literalType: { simple: Core.SimpleType.STRING }, + type: InputType.String + }, + struct: { + literalType: { simple: Core.SimpleType.STRUCT }, + type: InputType.Struct + }, + unknown: { + literalType: { simple: Core.SimpleType.NONE }, + type: InputType.Unknown + } +}; const validDateString = '2019-01-10T00:00:00.000Z'; // Dec 1, 2019 -export const supportedPrimitives = [ - InputType.Boolean, - InputType.Blob, - InputType.Datetime, - InputType.Duration, - InputType.Float, - InputType.Integer +export const supportedPrimitives: InputTypeDefinition[] = [ + inputTypes.boolean, + inputTypes.blobSingle, + inputTypes.blobMulti, + inputTypes.datetime, + inputTypes.duration, + inputTypes.float, + inputTypes.integer, + inputTypes.schema ]; -export const unsupportedTypes = [ - InputType.Binary, - InputType.Error, - InputType.Map, - InputType.None, - InputType.Schema, - InputType.Struct +export const unsupportedTypes: InputTypeDefinition[] = [ + inputTypes.binary, + inputTypes.error, + inputTypes.map, + inputTypes.none, + inputTypes.struct ]; export const validityTestCases = { @@ -111,98 +197,114 @@ export const validityTestCases = { }; export const literalTestCases: LiteralTestParams[] = [ - [InputType.Boolean, true, primitiveLiteral({ boolean: true })], - [InputType.Boolean, 'true', primitiveLiteral({ boolean: true })], - [InputType.Boolean, 't', primitiveLiteral({ boolean: true })], - [InputType.Boolean, '1', primitiveLiteral({ boolean: true })], - [InputType.Boolean, 1, primitiveLiteral({ boolean: true })], - [InputType.Boolean, false, primitiveLiteral({ boolean: false })], - [InputType.Boolean, 'false', primitiveLiteral({ boolean: false })], - [InputType.Boolean, 'f', primitiveLiteral({ boolean: false })], - [InputType.Boolean, '0', primitiveLiteral({ boolean: false })], - [InputType.Boolean, 0, primitiveLiteral({ boolean: false })], - [ - InputType.Datetime, + [inputTypes.boolean, true, primitiveLiteral({ boolean: true })], + [inputTypes.boolean, 'true', primitiveLiteral({ boolean: true })], + [inputTypes.boolean, 't', primitiveLiteral({ boolean: true })], + [inputTypes.boolean, '1', primitiveLiteral({ boolean: true })], + [inputTypes.boolean, 1, primitiveLiteral({ boolean: true })], + [inputTypes.boolean, false, primitiveLiteral({ boolean: false })], + [inputTypes.boolean, 'false', primitiveLiteral({ boolean: false })], + [inputTypes.boolean, 'f', primitiveLiteral({ boolean: false })], + [inputTypes.boolean, '0', primitiveLiteral({ boolean: false })], + [inputTypes.boolean, 0, primitiveLiteral({ boolean: false })], + [ + inputTypes.datetime, new Date(validDateString), primitiveLiteral({ datetime: dateToTimestamp(new Date(validDateString)) }) ], [ - InputType.Datetime, + inputTypes.datetime, validDateString, primitiveLiteral({ datetime: dateToTimestamp(new Date(validDateString)) }) ], [ - InputType.Duration, + inputTypes.duration, 0, primitiveLiteral({ duration: millisecondsToDuration(0) }) ], [ - InputType.Duration, + inputTypes.duration, 10000, primitiveLiteral({ duration: millisecondsToDuration(10000) }) ], - [InputType.Float, 0, primitiveLiteral({ floatValue: 0 })], - [InputType.Float, '0', primitiveLiteral({ floatValue: 0 })], - [InputType.Float, -1.5, primitiveLiteral({ floatValue: -1.5 })], - [InputType.Float, '-1.5', primitiveLiteral({ floatValue: -1.5 })], - [InputType.Float, 1.5, primitiveLiteral({ floatValue: 1.5 })], - [InputType.Float, '1.5', primitiveLiteral({ floatValue: 1.5 })], - [InputType.Float, 1.25e10, primitiveLiteral({ floatValue: 1.25e10 })], - [InputType.Float, '1.25e10', primitiveLiteral({ floatValue: 1.25e10 })], - [InputType.Integer, 0, primitiveLiteral({ integer: Long.fromNumber(0) })], - [ - InputType.Integer, + [inputTypes.float, 0, primitiveLiteral({ floatValue: 0 })], + [inputTypes.float, '0', primitiveLiteral({ floatValue: 0 })], + [inputTypes.float, -1.5, primitiveLiteral({ floatValue: -1.5 })], + [inputTypes.float, '-1.5', primitiveLiteral({ floatValue: -1.5 })], + [inputTypes.float, 1.5, primitiveLiteral({ floatValue: 1.5 })], + [inputTypes.float, '1.5', primitiveLiteral({ floatValue: 1.5 })], + [inputTypes.float, 1.25e10, primitiveLiteral({ floatValue: 1.25e10 })], + [inputTypes.float, '1.25e10', primitiveLiteral({ floatValue: 1.25e10 })], + [inputTypes.integer, 0, primitiveLiteral({ integer: Long.fromNumber(0) })], + [ + inputTypes.integer, Long.fromNumber(0), primitiveLiteral({ integer: Long.fromNumber(0) }) ], - [InputType.Integer, '0', primitiveLiteral({ integer: Long.fromNumber(0) })], - [InputType.Integer, 1, primitiveLiteral({ integer: Long.fromNumber(1) })], [ - InputType.Integer, + inputTypes.integer, + '0', + primitiveLiteral({ integer: Long.fromNumber(0) }) + ], + [inputTypes.integer, 1, primitiveLiteral({ integer: Long.fromNumber(1) })], + [ + inputTypes.integer, Long.fromNumber(1), primitiveLiteral({ integer: Long.fromNumber(1) }) ], - [InputType.Integer, '1', primitiveLiteral({ integer: Long.fromNumber(1) })], - [InputType.Integer, -1, primitiveLiteral({ integer: Long.fromNumber(-1) })], [ - InputType.Integer, + inputTypes.integer, + '1', + primitiveLiteral({ integer: Long.fromNumber(1) }) + ], + [ + inputTypes.integer, + -1, + primitiveLiteral({ integer: Long.fromNumber(-1) }) + ], + [ + inputTypes.integer, Long.fromNumber(-1), primitiveLiteral({ integer: Long.fromNumber(-1) }) ], [ - InputType.Integer, + inputTypes.integer, '-1', primitiveLiteral({ integer: Long.fromNumber(-1) }) ], [ - InputType.Integer, + inputTypes.integer, Long.MAX_VALUE.toString(), primitiveLiteral({ integer: Long.MAX_VALUE }) ], [ - InputType.Integer, + inputTypes.integer, Long.MAX_VALUE, primitiveLiteral({ integer: Long.MAX_VALUE }) ], [ - InputType.Integer, + inputTypes.integer, Long.MIN_VALUE.toString(), primitiveLiteral({ integer: Long.MIN_VALUE }) ], [ - InputType.Integer, + inputTypes.integer, Long.MIN_VALUE, primitiveLiteral({ integer: Long.MIN_VALUE }) ], - [InputType.String, '', primitiveLiteral({ stringValue: '' })], - [InputType.String, 'abcdefg', primitiveLiteral({ stringValue: 'abcdefg' })], + [inputTypes.string, '', primitiveLiteral({ stringValue: '' })], + [ + inputTypes.string, + 'abcdefg', + primitiveLiteral({ stringValue: 'abcdefg' }) + ], // Standard Blob [ - InputType.Blob, + inputTypes.blobSingle, { uri: 's3://somePath', format: 'csv', @@ -216,7 +318,7 @@ export const literalTestCases: LiteralTestParams[] = [ ], // Multi-part blob [ - InputType.Blob, + inputTypes.blobSingle, { dimensionality: BlobDimensionality.MULTIPART, format: 'csv', @@ -230,7 +332,7 @@ export const literalTestCases: LiteralTestParams[] = [ ], // Blob with missing format [ - InputType.Blob, + inputTypes.blobSingle, { dimensionality: BlobDimensionality.SINGLE, uri: 's3://somePath' @@ -242,7 +344,7 @@ export const literalTestCases: LiteralTestParams[] = [ ], // Blob with empty format string [ - InputType.Blob, + inputTypes.blobSingle, { dimensionality: BlobDimensionality.SINGLE, format: '', @@ -255,7 +357,7 @@ export const literalTestCases: LiteralTestParams[] = [ ], // Blobs using lowercase string for dimensionality [ - InputType.Blob, + inputTypes.blobSingle, { dimensionality: 'single', uri: 's3://somePath' @@ -266,7 +368,7 @@ export const literalTestCases: LiteralTestParams[] = [ }) ], [ - InputType.Blob, + inputTypes.blobMulti, { dimensionality: 'multipart', uri: 's3://somePath' @@ -278,7 +380,7 @@ export const literalTestCases: LiteralTestParams[] = [ ], // Blobs using uppercase string for dimensionality [ - InputType.Blob, + inputTypes.blobMulti, { dimensionality: 'SINGLE', uri: 's3://somePath' @@ -289,7 +391,7 @@ export const literalTestCases: LiteralTestParams[] = [ }) ], [ - InputType.Blob, + inputTypes.blobMulti, { dimensionality: 'MULTIPART', uri: 's3://somePath' @@ -301,7 +403,7 @@ export const literalTestCases: LiteralTestParams[] = [ ], // Blob missing URI (results in None) [ - InputType.Blob, + inputTypes.blobMulti, { format: 'csv', dimensionality: 'MULTIPART' @@ -309,66 +411,78 @@ export const literalTestCases: LiteralTestParams[] = [ literalNone() ], // Blob which is not an object (results in None) - [InputType.Blob, undefined, literalNone()] + [inputTypes.blobMulti, undefined, literalNone()] ]; type InputToLiteralTestParams = [ - InputType, + InputTypeDefinition, Core.ILiteral, InputValue | undefined ]; export const literalToInputTestCases: InputToLiteralTestParams[] = [ - [InputType.Boolean, primitiveLiteral({ boolean: true }), true], - [InputType.Boolean, primitiveLiteral({ boolean: false }), false], + [inputTypes.boolean, primitiveLiteral({ boolean: true }), true], + [inputTypes.boolean, primitiveLiteral({ boolean: false }), false], [ - InputType.Datetime, + inputTypes.datetime, primitiveLiteral({ datetime: dateToTimestamp(new Date(validDateString)) }), validDateString ], [ - InputType.Duration, + inputTypes.duration, primitiveLiteral({ duration: millisecondsToDuration(0) }), 0 ], [ - InputType.Duration, + inputTypes.duration, primitiveLiteral({ duration: millisecondsToDuration(10000) }), 10000 ], [ - InputType.Duration, + inputTypes.duration, primitiveLiteral({ duration: millisecondsToDuration(1.5) }), 1.5 ], - [InputType.Float, primitiveLiteral({ floatValue: 0 }), 0], - [InputType.Float, primitiveLiteral({ floatValue: -1.5 }), -1.5], - [InputType.Float, primitiveLiteral({ floatValue: 1.5 }), 1.5], - [InputType.Float, primitiveLiteral({ floatValue: 1.25e10 }), 1.25e10], + [inputTypes.float, primitiveLiteral({ floatValue: 0 }), 0], + [inputTypes.float, primitiveLiteral({ floatValue: -1.5 }), -1.5], + [inputTypes.float, primitiveLiteral({ floatValue: 1.5 }), 1.5], + [inputTypes.float, primitiveLiteral({ floatValue: 1.25e10 }), 1.25e10], // Integers will be returned as strings because they may overflow numbers - [InputType.Integer, primitiveLiteral({ integer: Long.fromNumber(0) }), '0'], - [InputType.Integer, primitiveLiteral({ integer: Long.fromNumber(1) }), '1'], [ - InputType.Integer, + inputTypes.integer, + primitiveLiteral({ integer: Long.fromNumber(0) }), + '0' + ], + [ + inputTypes.integer, + primitiveLiteral({ integer: Long.fromNumber(1) }), + '1' + ], + [ + inputTypes.integer, primitiveLiteral({ integer: Long.fromNumber(-1) }), '-1' ], [ - InputType.Integer, + inputTypes.integer, primitiveLiteral({ integer: Long.MAX_VALUE }), Long.MAX_VALUE.toString() ], [ - InputType.Integer, + inputTypes.integer, primitiveLiteral({ integer: Long.MIN_VALUE }), Long.MIN_VALUE.toString() ], - [InputType.String, primitiveLiteral({ stringValue: '' }), ''], - [InputType.String, primitiveLiteral({ stringValue: 'abcdefg' }), 'abcdefg'], + [inputTypes.string, primitiveLiteral({ stringValue: '' }), ''], + [ + inputTypes.string, + primitiveLiteral({ stringValue: 'abcdefg' }), + 'abcdefg' + ], // Standard Blob case [ - InputType.Blob, + inputTypes.blobSingle, blobLiteral({ dimensionality: BlobDimensionality.SINGLE, format: 'csv', @@ -382,7 +496,7 @@ export const literalToInputTestCases: InputToLiteralTestParams[] = [ ], // Multipart blob [ - InputType.Blob, + inputTypes.blobMulti, blobLiteral({ dimensionality: BlobDimensionality.MULTIPART, format: 'csv', @@ -396,7 +510,7 @@ export const literalToInputTestCases: InputToLiteralTestParams[] = [ ], // Empty uri [ - InputType.Blob, + inputTypes.blobSingle, blobLiteral({ dimensionality: BlobDimensionality.SINGLE, format: 'csv' @@ -409,7 +523,7 @@ export const literalToInputTestCases: InputToLiteralTestParams[] = [ ], // Empty format string [ - InputType.Blob, + inputTypes.blobSingle, blobLiteral({ dimensionality: BlobDimensionality.SINGLE, format: '', @@ -422,7 +536,7 @@ export const literalToInputTestCases: InputToLiteralTestParams[] = [ ], // Missing dimensionality [ - InputType.Blob, + inputTypes.blobSingle, blobLiteral({ format: 'csv', uri: 's3://somePath' diff --git a/src/components/Launch/LaunchForm/inputHelpers/test/utils.test.ts b/src/components/Launch/LaunchForm/inputHelpers/test/utils.test.ts index 6671d9e00..f26904117 100644 --- a/src/components/Launch/LaunchForm/inputHelpers/test/utils.test.ts +++ b/src/components/Launch/LaunchForm/inputHelpers/test/utils.test.ts @@ -1,4 +1,8 @@ -import { InputType, InputTypeDefinition } from '../../types'; +import { + collectionInputTypeDefinition, + nestedCollectionInputTypeDefinition +} from '../../__mocks__/utils'; +import { InputTypeDefinition } from '../../types'; import { typeIsSupported } from '../utils'; import { supportedPrimitives, unsupportedTypes } from './testCases'; @@ -6,40 +10,40 @@ type TypeIsSupportedTestCase = [string, InputTypeDefinition, boolean]; describe('Launch/inputHelpers/utils', () => { describe('typeIsSupported', () => { const cases: TypeIsSupportedTestCase[] = [ - ...supportedPrimitives.map(type => [ - `supports type ${type}`, - { type }, - true - ]), - ...supportedPrimitives.map(type => [ - `supports 1-dimension collection of type ${type}`, - { type: InputType.Collection, subtype: { type } }, - true - ]), - ...supportedPrimitives.map(type => [ - `supports 2-dimension collection of type: ${type}`, - { - type: InputType.Collection, - subtype: { type: InputType.Collection, subtype: { type } } - }, - true - ]), - ...unsupportedTypes.map(type => [ - `does NOT support type ${type}`, - { type }, + ...supportedPrimitives.map( + typeDefinition => [ + `supports type ${typeDefinition.type}`, + typeDefinition, + true + ] + ), + ...supportedPrimitives.map( + typeDefinition => [ + `supports 1-dimension collection of type ${typeDefinition.type}`, + collectionInputTypeDefinition(typeDefinition), + true + ] + ), + ...supportedPrimitives.map( + typeDefinition => [ + `supports 2-dimension collection of type: ${typeDefinition.type}`, + nestedCollectionInputTypeDefinition(typeDefinition), + true + ] + ), + ...unsupportedTypes.map(typeDefinition => [ + `does NOT support type ${typeDefinition.type}`, + typeDefinition, false ]), - ...unsupportedTypes.map(type => [ - `does NOT support 1-dimension collection of type ${type}`, - { type: InputType.Collection, subtype: { type } }, + ...unsupportedTypes.map(typeDefinition => [ + `does NOT support 1-dimension collection of type ${typeDefinition.type}`, + collectionInputTypeDefinition(typeDefinition), false ]), - ...unsupportedTypes.map(type => [ - `does NOT support 2-dimension collection of type: ${type}`, - { - type: InputType.Collection, - subtype: { type: InputType.Collection, subtype: { type } } - }, + ...unsupportedTypes.map(typeDefinition => [ + `does NOT support 2-dimension collection of type: ${typeDefinition.type}`, + nestedCollectionInputTypeDefinition(typeDefinition), false ]) ]; diff --git a/src/components/Launch/LaunchForm/inputHelpers/utils.ts b/src/components/Launch/LaunchForm/inputHelpers/utils.ts index f520127da..4018e0b4e 100644 --- a/src/components/Launch/LaunchForm/inputHelpers/utils.ts +++ b/src/components/Launch/LaunchForm/inputHelpers/utils.ts @@ -38,7 +38,6 @@ export function typeIsSupported(typeDefinition: InputTypeDefinition): boolean { case InputType.Error: case InputType.Map: case InputType.None: - case InputType.Schema: case InputType.Struct: case InputType.Unknown: return false; @@ -48,6 +47,7 @@ export function typeIsSupported(typeDefinition: InputTypeDefinition): boolean { case InputType.Duration: case InputType.Float: case InputType.Integer: + case InputType.Schema: case InputType.String: return true; case InputType.Collection: { diff --git a/src/components/Launch/LaunchForm/types.ts b/src/components/Launch/LaunchForm/types.ts index eb3c9f679..35f8f2f23 100644 --- a/src/components/Launch/LaunchForm/types.ts +++ b/src/components/Launch/LaunchForm/types.ts @@ -3,6 +3,7 @@ import { BlobDimensionality, Identifier, LaunchPlan, + LiteralType, NamedEntityIdentifier, Task, Workflow, @@ -152,6 +153,7 @@ export enum InputType { } export interface InputTypeDefinition { + literalType: LiteralType; type: InputType; subtype?: InputTypeDefinition; } diff --git a/src/components/Launch/LaunchForm/utils.ts b/src/components/Launch/LaunchForm/utils.ts index ba1111dbd..6f24fd675 100644 --- a/src/components/Launch/LaunchForm/utils.ts +++ b/src/components/Launch/LaunchForm/utils.ts @@ -144,38 +144,34 @@ export function convertFormInputsToLiterals( } /** Converts a `LiteralType` to an `InputTypeDefintion` to assist with rendering - * a type annotation. + * a type annotation and converting input values. */ export function getInputDefintionForLiteralType( literalType: LiteralType ): InputTypeDefinition { - if (literalType.blob) { - return { type: InputType.Blob }; - } - - if (literalType.collectionType) { - return { - type: InputType.Collection, - subtype: getInputDefintionForLiteralType(literalType.collectionType) - }; - } - - if (literalType.mapValueType) { - return { - type: InputType.Map, - subtype: getInputDefintionForLiteralType(literalType.mapValueType) - }; - } - - if (literalType.schema) { - return { type: InputType.Schema }; - } + const result: InputTypeDefinition = { + literalType, + type: InputType.Unknown + }; - if (literalType.simple) { - return { type: simpleTypeToInputType[literalType.simple] }; + if (literalType.blob) { + result.type = InputType.Blob; + } else if (literalType.collectionType) { + result.type = InputType.Collection; + result.subtype = getInputDefintionForLiteralType( + literalType.collectionType + ); + } else if (literalType.mapValueType) { + result.type = InputType.Map; + result.subtype = getInputDefintionForLiteralType( + literalType.mapValueType + ); + } else if (literalType.schema) { + result.type = InputType.Schema; + } else if (literalType.simple) { + result.type = simpleTypeToInputType[literalType.simple]; } - - return { type: InputType.Unknown }; + return result; } export function getLaunchInputId(name: string): string { From 568b984962bf64e13bf02abec4d029380a962aed Mon Sep 17 00:00:00 2001 From: Randy Schott <1815175+schottra@users.noreply.github.com> Date: Wed, 21 Oct 2020 17:13:03 -0700 Subject: [PATCH 2/2] test: add value test cases for schema --- .../inputHelpers/test/inputHelpers.test.ts | 4 +++ .../LaunchForm/inputHelpers/test/testCases.ts | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/components/Launch/LaunchForm/inputHelpers/test/inputHelpers.test.ts b/src/components/Launch/LaunchForm/inputHelpers/test/inputHelpers.test.ts index f845e4fd1..7f0ab1cde 100644 --- a/src/components/Launch/LaunchForm/inputHelpers/test/inputHelpers.test.ts +++ b/src/components/Launch/LaunchForm/inputHelpers/test/inputHelpers.test.ts @@ -262,6 +262,10 @@ describe('validateInput', () => { generateValidityTests(inputTypes.integer, validityTestCases.integer); }); + describe('schema', () => { + generateValidityTests(inputTypes.schema, validityTestCases.schema); + }); + describe('string', () => { generateValidityTests(inputTypes.string, validityTestCases.string); }); diff --git a/src/components/Launch/LaunchForm/inputHelpers/test/testCases.ts b/src/components/Launch/LaunchForm/inputHelpers/test/testCases.ts index e79b8b1b7..7a14b9b90 100644 --- a/src/components/Launch/LaunchForm/inputHelpers/test/testCases.ts +++ b/src/components/Launch/LaunchForm/inputHelpers/test/testCases.ts @@ -193,6 +193,8 @@ export const validityTestCases = { Long.MIN_VALUE ] }, + // schema is just a specialized string input, so it has the same validity cases as string + schema: { invalid: [123, true, new Date(), {}], valid: ['', 'abcdefg'] }, string: { invalid: [123, true, new Date(), {}], valid: ['', 'abcdefg'] } }; @@ -296,6 +298,27 @@ export const literalTestCases: LiteralTestParams[] = [ Long.MIN_VALUE, primitiveLiteral({ integer: Long.MIN_VALUE }) ], + [ + inputTypes.schema, + '', + { + scalar: { + schema: { type: inputTypes.schema.literalType.schema, uri: '' } + } + } + ], + [ + inputTypes.schema, + 's3://someUri', + { + scalar: { + schema: { + type: inputTypes.schema.literalType.schema, + uri: 's3://someUri' + } + } + } + ], [inputTypes.string, '', primitiveLiteral({ stringValue: '' })], [ inputTypes.string, @@ -474,6 +497,12 @@ export const literalToInputTestCases: InputToLiteralTestParams[] = [ primitiveLiteral({ integer: Long.MIN_VALUE }), Long.MIN_VALUE.toString() ], + [inputTypes.schema, { scalar: { schema: { uri: '' } } }, ''], + [ + inputTypes.schema, + { scalar: { schema: { uri: 's3://someUri' } } }, + 's3://someUri' + ], [inputTypes.string, primitiveLiteral({ stringValue: '' }), ''], [ inputTypes.string,