diff --git a/packages/zapp/console/package.json b/packages/zapp/console/package.json index fa1706344..aea34695a 100644 --- a/packages/zapp/console/package.json +++ b/packages/zapp/console/package.json @@ -48,6 +48,7 @@ "react-chartjs-2": "^4.0.0", "react-flow-renderer": "10.1.1", "react-ga4": "^1.4.1", + "react-json-view": "^1.21.3", "react-transition-group": "^2.3.1", "serve-static": "^1.12.3", "tslib": "^1.9.0" diff --git a/packages/zapp/console/src/components/Literals/DeprecatedLiteralMapViewer.tsx b/packages/zapp/console/src/components/Literals/DeprecatedLiteralMapViewer.tsx new file mode 100644 index 000000000..f8ec614bb --- /dev/null +++ b/packages/zapp/console/src/components/Literals/DeprecatedLiteralMapViewer.tsx @@ -0,0 +1,50 @@ +import classnames from 'classnames'; +import { sortedObjectEntries } from 'common/utils'; +import { useCommonStyles } from 'components/common/styles'; +import { Literal, LiteralMap } from 'models/Common/types'; +import * as React from 'react'; +import { htmlEntities } from './constants'; +import { LiteralValue } from './LiteralValue'; +import { NoneTypeValue } from './Scalar/NoneTypeValue'; + +export const NoDataIsAvailable = () => { + return ( +

+ No data is available. +

+ ); +}; + +/** Renders a LiteralMap as a formatted object */ +export const DeprecatedLiteralMapViewer: React.FC<{ + className?: string; + map: LiteralMap | null; + showBrackets?: boolean; +}> = ({ className, map, showBrackets = false }) => { + if (!map) { + return ; + } + + const commonStyles = useCommonStyles(); + const { literals } = map; + const mapContent = Object.keys(literals).length ? ( + + ) : ( +
+ +
+ ); + return ( + <> + {showBrackets && {htmlEntities.leftCurlyBrace}} + {mapContent} + {showBrackets && {htmlEntities.rightCurlyBrace}} + + ); +}; diff --git a/packages/zapp/console/src/components/Literals/LiteralMapViewer.tsx b/packages/zapp/console/src/components/Literals/LiteralMapViewer.tsx index 5e594c635..77be18da2 100644 --- a/packages/zapp/console/src/components/Literals/LiteralMapViewer.tsx +++ b/packages/zapp/console/src/components/Literals/LiteralMapViewer.tsx @@ -1,10 +1,7 @@ -import classnames from 'classnames'; -import { sortedObjectEntries } from 'common/utils'; -import { useCommonStyles } from 'components/common/styles'; -import { Literal, LiteralMap } from 'models/Common/types'; +import { ReactJsonViewWrapper } from 'components/common/ReactJsonView'; +import { LiteralMap } from 'models/Common/types'; import * as React from 'react'; -import { htmlEntities } from './constants'; -import { LiteralValue } from './LiteralValue'; +import { transformLiterals } from './helpers'; import { NoneTypeValue } from './Scalar/NoneTypeValue'; export const NoDataIsAvailable = () => { @@ -20,31 +17,18 @@ export const LiteralMapViewer: React.FC<{ className?: string; map: LiteralMap | null; showBrackets?: boolean; -}> = ({ className, map, showBrackets = false }) => { +}> = ({ map }) => { if (!map) { return ; } - const commonStyles = useCommonStyles(); const { literals } = map; - const mapContent = Object.keys(literals).length ? ( - - ) : ( -
- -
- ); - return ( - <> - {showBrackets && {htmlEntities.leftCurlyBrace}} - {mapContent} - {showBrackets && {htmlEntities.rightCurlyBrace}} - - ); + + if (!Object.keys(literals).length) { + return ; + } + + const transformedLiterals = transformLiterals(literals); + + return ; }; diff --git a/packages/zapp/console/src/components/Literals/LiteralValue.tsx b/packages/zapp/console/src/components/Literals/LiteralValue.tsx index 302db910e..e4e4756b9 100644 --- a/packages/zapp/console/src/components/Literals/LiteralValue.tsx +++ b/packages/zapp/console/src/components/Literals/LiteralValue.tsx @@ -1,7 +1,7 @@ import { Literal, LiteralCollection, LiteralMap, Scalar } from 'models/Common/types'; import * as React from 'react'; import { LiteralCollectionViewer } from './LiteralCollectionViewer'; -import { LiteralMapViewer } from './LiteralMapViewer'; +import { DeprecatedLiteralMapViewer } from './DeprecatedLiteralMapViewer'; import { ScalarValue } from './Scalar/ScalarValue'; import { useLiteralStyles } from './styles'; import { UnsupportedType } from './UnsupportedType'; @@ -27,7 +27,7 @@ export const LiteralValue: React.FC<{ return ( <> - ; + const map = { literals: { [label]: { collection, value: 'collection' } } }; + return ( + <> +
+
+ OLD + + + + + +
+
+ NEW + + + + + +
+
+ + ); } stories.add('Binary', () => diff --git a/packages/zapp/console/src/components/Literals/__stories__/Map.stories.tsx b/packages/zapp/console/src/components/Literals/__stories__/Map.stories.tsx index 4a4b49cdb..6336fa8e0 100644 --- a/packages/zapp/console/src/components/Literals/__stories__/Map.stories.tsx +++ b/packages/zapp/console/src/components/Literals/__stories__/Map.stories.tsx @@ -1,8 +1,7 @@ import { storiesOf } from '@storybook/react'; import { LiteralMap } from 'models/Common/types'; import * as React from 'react'; -import { LiteralValue } from '../LiteralValue'; -import { CardDecorator } from './CardDecorator'; +import { Card, CardContent } from '@material-ui/core'; import { binaryLiterals, blobLiterals, @@ -11,12 +10,35 @@ import { primitiveLiterals, schemaLiterals, } from './literalValues'; +import { LiteralMapViewer } from '../LiteralMapViewer'; +import { DeprecatedLiteralMapViewer } from '../DeprecatedLiteralMapViewer'; const stories = storiesOf('Literals/Map', module); -stories.addDecorator(CardDecorator); function renderMap(label: string, map: LiteralMap) { - return ; + const fullMap = { literals: { [label]: { map, value: 'map' } } }; + return ( + <> +
+
+ OLD + + + + + +
+
+ NEW + + + + + +
+
+ + ); } stories.add('Binary', () => diff --git a/packages/zapp/console/src/components/Literals/__stories__/ProtobufStruct.stories.tsx b/packages/zapp/console/src/components/Literals/__stories__/ProtobufStruct.stories.tsx index 8781df62d..cc84b8386 100644 --- a/packages/zapp/console/src/components/Literals/__stories__/ProtobufStruct.stories.tsx +++ b/packages/zapp/console/src/components/Literals/__stories__/ProtobufStruct.stories.tsx @@ -1,23 +1,41 @@ import { storiesOf } from '@storybook/react'; import { ProtobufListValue, ProtobufStruct } from 'models/Common/types'; import * as React from 'react'; -import { LiteralValue } from '../LiteralValue'; -import { CardDecorator } from './CardDecorator'; +import { Card, CardContent } from '@material-ui/core'; import { protobufValues } from './protobufValues'; +import { LiteralMapViewer } from '../LiteralMapViewer'; + +import { DeprecatedLiteralMapViewer } from '../DeprecatedLiteralMapViewer'; const stories = storiesOf('Literals/ProtobufStruct', module); -stories.addDecorator(CardDecorator); function renderStruct(label: string, struct: ProtobufStruct) { + const map = { + literals: { + [label]: { scalar: { value: 'generic', generic: struct }, value: 'scalar' }, + }, + }; return ( - + <> +
+
+ OLD + + + + + +
+
+ NEW + + + + + +
+
+ ); } @@ -30,7 +48,6 @@ stories.add('list', () => kind: 'listValue', listValue: { values: [ - ...Object.values(protobufValues), { kind: 'structValue', structValue: { fields: protobufValues }, diff --git a/packages/zapp/console/src/components/Literals/__stories__/Scalar.stories.tsx b/packages/zapp/console/src/components/Literals/__stories__/Scalar.stories.tsx index f79996aea..051658e55 100644 --- a/packages/zapp/console/src/components/Literals/__stories__/Scalar.stories.tsx +++ b/packages/zapp/console/src/components/Literals/__stories__/Scalar.stories.tsx @@ -1,8 +1,8 @@ import { storiesOf } from '@storybook/react'; import { Scalar } from 'models/Common/types'; import * as React from 'react'; -import { ScalarValue } from '../Scalar/ScalarValue'; -import { CardDecorator } from './CardDecorator'; +import { Card, CardContent } from '@material-ui/core'; +import { LiteralMapViewer } from '../LiteralMapViewer'; import { binaryScalars, blobScalars, @@ -11,22 +11,45 @@ import { primitiveScalars, schemaScalars, } from './scalarValues'; +import { DeprecatedLiteralMapViewer } from '../DeprecatedLiteralMapViewer'; const stories = storiesOf('Literals/Scalar', module); -stories.addDecorator(CardDecorator); -const renderScalars = (scalars: Dictionary) => +var renderScalars = (scalars: Dictionary) => { + const literals = {}; + Object.entries(scalars).map(([label, value]) => { - return ; + literals[label] = { scalar: value, value: 'scalar' }; }); + const map = { literals }; + return ( + <> +
+
+ OLD + + + + + +
+
+ NEW + + + + + +
+
+ + ); +}; + stories.add('Binary', () => <>{renderScalars(binaryScalars)}); stories.add('Blob', () => <>{renderScalars(blobScalars)}); stories.add('Error', () => <>{renderScalars(errorScalars)}); -stories.add('NoneType', () => ( - <> - - -)); +stories.add('NoneType', () => <>{renderScalars([noneTypeScalar])}); stories.add('Primitive', () => <>{renderScalars(primitiveScalars)}); stories.add('Schema', () => <>{renderScalars(schemaScalars)}); diff --git a/packages/zapp/console/src/components/Literals/__stories__/StructuredDataSet.stories.tsx b/packages/zapp/console/src/components/Literals/__stories__/StructuredDataSet.stories.tsx new file mode 100644 index 000000000..7f4d6458f --- /dev/null +++ b/packages/zapp/console/src/components/Literals/__stories__/StructuredDataSet.stories.tsx @@ -0,0 +1,176 @@ +import { storiesOf } from '@storybook/react'; +import { Scalar } from 'models/Common/types'; +import { Card, CardContent } from '@material-ui/core'; +import * as React from 'react'; +import { Core } from 'flyteidl'; +import { LiteralMapViewer } from '../LiteralMapViewer'; +import { blobScalars } from './scalarValues'; +import { extractSimpleTypes, extractSchemaTypes, extractBlobTypes } from './helpers/typeGenerators'; + +const stories = storiesOf('Literals/StructuredDataSet', module); + +function generateStructuredDataset(label: string, literalType: Core.ILiteralType) { + return { + literals: { + [label]: { + scalar: { + structuredDataset: { + uri: 's3://random-val', + metadata: { + structuredDatasetType: { + columns: [ + { + name: 'age', + literalType, + }, + ], + format: 'parquet', + }, + }, + }, + value: 'structuredDataset', + }, + value: 'scalar', + }, + }, + }; +} + +function generateSimpleTypes() { + const simpleTypes = extractSimpleTypes().map((simpleType) => { + return Object.keys(simpleType).map((key) => { + return generateStructuredDataset(`simple_${key}`, simpleType[key]); + }); + }); + return simpleTypes.flat(); +} + +function generateSchemaTypes() { + const schemaTypes = extractSchemaTypes(); + const retObj = schemaTypes + .map((schemaType) => { + return Object.keys(schemaType).map((key) => { + const v = schemaType[key]; + return generateStructuredDataset(`schema${key}`, v); + }); + }) + .flat(); + + return retObj; +} + +function generateBlobTypes() { + return Object.keys(blobScalars).map((key) => { + const value = blobScalars[key]?.['blob']?.metadata?.type; + return generateStructuredDataset(key, { + type: 'blob', + blob: value, + }); + }); +} + +function generateCollectionTypes() { + const collectionTypes = []; + + const simpleTypes = extractSimpleTypes() + .map((simpleType) => { + return Object.keys(simpleType).map((key) => { + return generateStructuredDataset(`simple_${key}`, { + type: 'collectionType', + collectionType: simpleType[key], + }); + }); + }) + .flat(); + collectionTypes.push(...simpleTypes); + + // blobTypes + const blobTypes = extractBlobTypes(); + collectionTypes.push( + ...blobTypes + .map((value) => { + return Object.keys(value).map((key) => { + return generateStructuredDataset(key, { + type: 'collectionType', + collectionType: value[key], + }); + }); + }) + .flat(), + ); + + return collectionTypes; +} + +function generateMapValueypes() { + const mapValueTypes = []; + + const simpleTypes = extractSimpleTypes() + .map((simpleType) => { + return Object.keys(simpleType).map((key) => { + return generateStructuredDataset(`simple_${key}`, { + type: 'mapValueType', + mapValueType: simpleType[key], + }); + }); + }) + .flat(); + mapValueTypes.push(...simpleTypes); + + // blobTypes + const blobTypes = extractBlobTypes(); + mapValueTypes.push( + ...blobTypes + .map((value) => { + return Object.keys(value).map((key) => { + return generateStructuredDataset(key, { + type: 'mapValueType', + mapValueType: value[key], + }); + }); + }) + .flat(), + ); + + return mapValueTypes; +} + +function generateEnumTypes() { + const mapValueTypes = [ + generateStructuredDataset(`With_values`, { + type: 'enumType', + enumType: { values: ['1', '2', '3'] }, + }), + generateStructuredDataset(`With_no_values`, { + type: 'enumType', + enumType: { values: [] }, + }), + generateStructuredDataset(`With_null_values`, { + type: 'enumType', + enumType: { values: null }, + }), + ]; + + return mapValueTypes; +} + +const renderScalars = (scalars: Dictionary) => { + const items = Object.entries(scalars).map(([_label, value]) => { + return ( + + + + + + ); + }); + + return
{items}
; +}; + +stories.add('Simple types', () => <>{renderScalars(generateSimpleTypes())}); +stories.add('Blob Type', () => <>{renderScalars(generateBlobTypes())}); +stories.add('Schema types', () => <>{renderScalars(generateSchemaTypes())}); +stories.add('Collection Type', () => <>{renderScalars(generateCollectionTypes())}); +stories.add('mapValue Type', () => <>{renderScalars(generateMapValueypes())}); +stories.add('Enum Type', () => <>{renderScalars(generateEnumTypes())}); diff --git a/packages/zapp/console/src/components/Literals/__stories__/helpers/typeGenerators.ts b/packages/zapp/console/src/components/Literals/__stories__/helpers/typeGenerators.ts new file mode 100644 index 000000000..edf237870 --- /dev/null +++ b/packages/zapp/console/src/components/Literals/__stories__/helpers/typeGenerators.ts @@ -0,0 +1,45 @@ +import { Core } from 'flyteidl'; +import { + blobScalars, + schemaScalars, +} from '../scalarValues'; + +// SIMPLE +type GeneratedSimpleType = { + [key in Core.SimpleType]?: Core.LiteralType; +}; +export function extractSimpleTypes() { + const simpleTypes: GeneratedSimpleType[] = Object.keys(Core.SimpleType).map((key) => ({ + [key]: { + type: 'simple', + simple: Core.SimpleType[key], + }, + })); + return simpleTypes; +} + +// SCHEMA +export function extractSchemaTypes() { + return Object.keys(schemaScalars).map((key) => { + const value = schemaScalars[key]; + return { + [key]: { + type: 'schema', + schema: value?.schema?.type, + }, + }; + }); +} + +// COLLECTION TYPE +export function extractBlobTypes() { + return Object.keys(blobScalars).map((key) => { + const value = blobScalars[key]?.['blob']?.metadata?.type; + return { + [key]: { + type: 'blob', + blob: value, + }, + }; + }); +} diff --git a/packages/zapp/console/src/components/Literals/helpers.ts b/packages/zapp/console/src/components/Literals/helpers.ts new file mode 100644 index 000000000..f04f6574a --- /dev/null +++ b/packages/zapp/console/src/components/Literals/helpers.ts @@ -0,0 +1,390 @@ +import { formatDateUTC, protobufDurationToHMS } from 'common/formatters'; +import { timestampToDate } from 'common/utils'; +import { Core, Protobuf } from 'flyteidl'; +import * as Long from 'long'; +import { BlobDimensionality, SchemaColumnType } from 'models/Common/types'; + +const DEFAULT_UNSUPPORTED = 'This type is not yet supported'; + +// PRIMITIVE +function processPrimitive(primitive?: (Core.IPrimitive & Pick) | null) { + if (!primitive) { + return 'invalid primitive'; + } + + const type = primitive.value; + + switch (type) { + case 'datetime': + return formatDateUTC(timestampToDate(primitive.datetime as Protobuf.Timestamp)); + case 'duration': + return protobufDurationToHMS(primitive.duration as Protobuf.Duration); + case 'integer': { + return Long.fromValue(primitive.integer as Long).toNumber(); + } + case 'boolean': + return primitive.boolean; + case 'floatValue': + return primitive.floatValue; + case 'stringValue': + return `${primitive.stringValue}`; + default: + return 'unknown'; + } +} + +// BLOB +const dimensionalityStrings: Record = { + [BlobDimensionality.SINGLE]: 'single', + [BlobDimensionality.MULTIPART]: 'multi-part', +}; + +function processBlobType(blobType?: Core.IBlobType | null) { + if (!blobType) { + return 'invalid blob type'; + } + + const formatString = blobType.format ? ` (${blobType.format})` : ''; + const dimensionality = blobType.dimensionality; + const dimensionalityString = + dimensionality !== null && dimensionality !== undefined + ? dimensionalityStrings[dimensionality] + : ''; + const typeString = `${dimensionalityString}${formatString}`; + + return `${typeString} blob`; +} + +function processBlob(blob?: Core.IBlob | null) { + if (!blob) { + return 'invalid blob'; + } + + const type = blob.metadata?.type; + + return { + type: processBlobType(type), + uri: blob.uri, + }; +} + +// BINARY +function processBinary(binary?: Core.IBinary | null) { + const tag = binary?.tag; + + if (!tag) { + return 'invalid binary'; + } + + return { + tag: `${tag} (binary data not shown)`, + }; +} + +// SCHEMA +export function columnTypeToString(type?: SchemaColumnType | null) { + switch (type) { + case SchemaColumnType.BOOLEAN: + return 'boolean'; + case SchemaColumnType.DATETIME: + return 'datetime'; + case SchemaColumnType.DURATION: + return 'duration'; + case SchemaColumnType.FLOAT: + return 'float'; + case SchemaColumnType.INTEGER: + return 'integer'; + case SchemaColumnType.STRING: + return 'string'; + default: + return 'unknown'; + } +} + +function processSchemaType(schemaType?: Core.ISchemaType | null, shortString = false) { + const columns = + schemaType?.columns?.length && + schemaType.columns.map((column) => { + return shortString + ? `${columnTypeToString(column.type)}` + : `${column.name} (${columnTypeToString(column.type)})`; + }); + + return columns; +} + +function processSchema(schema?: Core.ISchema | null) { + if (!schema) { + return 'invalid schema'; + } + + const uri = schema.uri; + const columns = processSchemaType(schema.type); + + return { + ...(uri && { uri }), + ...(columns && { columns }), + }; +} + +// NONE +/* eslint-disable @typescript-eslint/no-unused-vars */ +function processNone(none?: Core.IVoid | null) { + return '(empty)'; +} + +// TODO: FC#450 ass support for union types +function processUnionType(union?: Core.IUnionType | null, shortString = false) { + return DEFAULT_UNSUPPORTED; +} + +function processUnion(union: Core.IUnion) { + return DEFAULT_UNSUPPORTED; +} +/* eslint-enable @typescript-eslint/no-unused-vars */ + +// ERROR +function processError(error?: Core.IError | null) { + return { + error: error?.message, + nodeId: error?.failedNodeId, + }; +} + +function processProtobufStructValue(struct?: Protobuf.IStruct | null) { + if (!struct) { + return 'invalid generic struct value'; + } + + const fields = struct?.fields; + const res = + fields && + Object.keys(fields) + .map((v) => { + return { [v]: processGenericValue(fields[v]) }; + }) + .reduce((acc, v) => ({ ...acc, ...v }), {}); + + return res; +} + +function processGenericValue(value: Protobuf.IValue & Pick) { + const kind = value.kind; + + switch (kind) { + case 'nullValue': + return '(empty)'; + case 'listValue': { + const list = value.listValue; + return list?.values?.map((x) => processGenericValue(x)); + } + case 'structValue': + return processProtobufStructValue(value?.structValue); + case 'numberValue': + case 'stringValue': + case 'boolValue': + return value[kind]; + default: + return 'unknown'; + } +} + +function processGeneric(struct?: Protobuf.IStruct | null) { + if (!struct || !struct?.fields) { + return null; + } + + const { fields } = struct; + const mapContent = Object.keys(fields) + .map((key) => { + const value = fields[key]; + return { [key]: processGenericValue(value) }; + }) + .reduce((acc, v) => { + return { ...acc, ...v }; + }, {}); + + return mapContent; +} + +// SIMPLE +export function processSimpleType(simpleType?: Core.SimpleType | null) { + switch (simpleType) { + case Core.SimpleType.NONE: + return 'none'; + case Core.SimpleType.INTEGER: + return 'integer'; + case Core.SimpleType.FLOAT: + return 'float'; + case Core.SimpleType.STRING: + return 'string'; + case Core.SimpleType.BOOLEAN: + return 'booleam'; + case Core.SimpleType.DATETIME: + return 'datetime'; + case Core.SimpleType.DURATION: + return 'duration'; + case Core.SimpleType.BINARY: + return 'binary'; + case Core.SimpleType.ERROR: + return 'error'; + case Core.SimpleType.STRUCT: + return 'struct'; + default: + return 'unknown'; + } +} + +function processEnumType(enumType?: Core.IEnumType | null) { + return enumType?.values || []; +} + +function processLiteralType( + literalType?: (Core.ILiteralType & Pick) | null, +) { + const type = literalType?.type; + + switch (type) { + case 'simple': + return processSimpleType(literalType?.simple); + case 'schema': + return `schema (${processSchemaType(literalType?.schema, true)})`; + case 'collectionType': + return `collection of ${processLiteralType(literalType?.collectionType)}`; + case 'mapValueType': + return `map value of ${processLiteralType(literalType?.mapValueType)}`; + case 'blob': + return processBlobType(literalType?.blob); + case 'enumType': + return `enum (${processEnumType(literalType?.enumType)})`; + case 'structuredDatasetType': + return processStructuredDatasetType(literalType?.structuredDatasetType); + case 'unionType': + return processUnionType(literalType?.unionType, true); + default: + return DEFAULT_UNSUPPORTED; + } +} + +function processStructuredDatasetType(structuredDatasetType?: Core.IStructuredDatasetType | null) { + if (!structuredDatasetType) { + return {}; + } + + const { columns, format } = structuredDatasetType; + const processedColumns = + columns?.length && + columns + .map(({ name, literalType }) => [name, processLiteralType(literalType)]) + .reduce((acc, v) => { + acc[v[0]] = v[1]; + return acc; + }, []); + + return { + ...(format && { format }), + ...(processedColumns && { columns: processedColumns }), + }; +} + +function processStructuredDataset(structuredDataSet?: Core.IStructuredDataset | null) { + if (!structuredDataSet) { + return DEFAULT_UNSUPPORTED; + } + + const retJson = {} as any; + const { uri, metadata } = structuredDataSet; + + if (uri) { + retJson.uri = uri; + } + + const structuredDatasetType = processStructuredDatasetType(metadata?.structuredDatasetType); + + return { + ...(uri && { uri }), + ...structuredDatasetType, + }; +} + +function processScalar(scalar?: (Core.IScalar & Pick) | null) { + const type = scalar?.value; + + switch (type) { + case 'primitive': + return processPrimitive(scalar?.primitive); + case 'blob': + return processBlob(scalar?.blob); + case 'binary': + return processBinary(scalar?.binary); + case 'schema': + return processSchema(scalar?.schema); + case 'noneType': + return processNone(scalar?.noneType); + case 'error': + return processError(scalar?.error); + case 'generic': + return processGeneric(scalar?.generic); + case 'structuredDataset': + return processStructuredDataset(scalar?.structuredDataset); + case 'union': + return processUnion(scalar?.union as Core.IUnion); + default: + return DEFAULT_UNSUPPORTED; + } +} + +function processCollection(collection?: Core.ILiteralCollection | null) { + const literals = collection?.literals; + + if (!literals) { + return 'invalid collection'; + } + + return literals?.map((literal) => processLiteral(literal)); +} + +function processMap(map?: Core.ILiteralMap | null) { + const literals = map?.literals; + + if (!literals) { + return 'invalid map'; + } + + return transformLiterals(literals); +} + +function processLiteral(literal?: Core.ILiteral & Pick) { + const type = literal?.value; + + if (!literal) { + return 'invalid literal'; + } + + switch (type) { + case 'scalar': + return processScalar(literal.scalar); + case 'collection': + return processCollection(literal.collection); + case 'map': + return processMap(literal.map); + default: + return DEFAULT_UNSUPPORTED; + } +} + +export function transformLiterals(json: { [k: string]: Core.ILiteral }) { + const obj = Object.entries(json) + .map(([key, literal]) => ({ + [key]: processLiteral(literal), + })) + .reduce( + (acc, cur) => ({ + ...acc, + ...cur, + }), + {}, + ); + + return obj; +} diff --git a/packages/zapp/console/src/components/Literals/test/helpers/genCollectionTestcase.mock.ts b/packages/zapp/console/src/components/Literals/test/helpers/genCollectionTestcase.mock.ts new file mode 100644 index 000000000..026d64826 --- /dev/null +++ b/packages/zapp/console/src/components/Literals/test/helpers/genCollectionTestcase.mock.ts @@ -0,0 +1,23 @@ +import { Core } from 'flyteidl'; +import { TestCaseList } from '../types'; +import { generateBlobType, getPrimitive, getScalarLiteral } from './literalHelpers'; + +const collection: TestCaseList = { + COL_WITH_SCALARTYPE_PRIMITIVE: { + value: { + ...getScalarLiteral(getPrimitive('floatValue', 2.1), 'primitive'), + }, + expected: { result_var: [2.1] }, + }, + COL_WITH_SCALARTYPE_BLOB: { + value: { + ...getScalarLiteral( + generateBlobType('csv', Core.BlobType.BlobDimensionality.SINGLE, '1'), + 'blob', + ), + }, + expected: { result_var: [{ type: 'single (csv) blob', uri: '1' }] }, + }, +}; + +export default collection; diff --git a/packages/zapp/console/src/components/Literals/test/helpers/genMapTestCase.mock.ts b/packages/zapp/console/src/components/Literals/test/helpers/genMapTestCase.mock.ts new file mode 100644 index 000000000..6eef945ee --- /dev/null +++ b/packages/zapp/console/src/components/Literals/test/helpers/genMapTestCase.mock.ts @@ -0,0 +1,23 @@ +import { Core } from 'flyteidl'; +import { TestCaseList } from '../types'; +import { generateBlobType, getPrimitive, getScalarLiteral } from './literalHelpers'; + +const collection: TestCaseList = { + COL_WITH_SCALARTYPE_PRIMITIVE: { + value: { + ...getScalarLiteral(getPrimitive('floatValue', 2.1), 'primitive'), + }, + expected: { result_var: { value: 2.1 } }, + }, + COL_WITH_SCALARTYPE_BLOB: { + value: { + ...getScalarLiteral( + generateBlobType('csv', Core.BlobType.BlobDimensionality.SINGLE, '1'), + 'blob', + ), + }, + expected: { result_var: { value: { type: 'single (csv) blob', uri: '1' } } }, + }, +}; + +export default collection; diff --git a/packages/zapp/console/src/components/Literals/test/helpers/genScalarBinaryTestCase.mock.ts b/packages/zapp/console/src/components/Literals/test/helpers/genScalarBinaryTestCase.mock.ts new file mode 100644 index 000000000..9804a0f11 --- /dev/null +++ b/packages/zapp/console/src/components/Literals/test/helpers/genScalarBinaryTestCase.mock.ts @@ -0,0 +1,15 @@ +import { Core } from 'flyteidl'; +import { TestCaseList } from '../types'; + +const scalarBinaryTestCases: TestCaseList = { + WITH_VAL: { + value: { value: new Uint8Array(), tag: 'tag1' }, + expected: { result_var: { tag: 'tag1 (binary data not shown)' } }, + }, + INT_WITH_SMALL_LOW: { + value: { tag: 'tag2' }, + expected: { result_var: { tag: 'tag2 (binary data not shown)' } }, + }, +}; + +export default scalarBinaryTestCases; diff --git a/packages/zapp/console/src/components/Literals/test/helpers/genScalarBlobCases.mock.ts b/packages/zapp/console/src/components/Literals/test/helpers/genScalarBlobCases.mock.ts new file mode 100644 index 000000000..28073461f --- /dev/null +++ b/packages/zapp/console/src/components/Literals/test/helpers/genScalarBlobCases.mock.ts @@ -0,0 +1,44 @@ +import { Core } from 'flyteidl'; +import { generateBlobType } from './literalHelpers'; +import { TestCaseList } from '../types'; + +const blobTestcases: TestCaseList = { + single_CSV_BLOB: { + value: generateBlobType('csv', Core.BlobType.BlobDimensionality.SINGLE, '1'), + expected: { + result_var: { + type: 'single (csv) blob', + uri: '1', + }, + }, + }, + multi_part_CSV_BLOB: { + value: generateBlobType('csv', Core.BlobType.BlobDimensionality.MULTIPART, '2'), + expected: { + result_var: { + type: 'multi-part (csv) blob', + uri: '2', + }, + }, + }, + single_blob_BLOB: { + value: generateBlobType(undefined, Core.BlobType.BlobDimensionality.SINGLE, '3'), + expected: { + result_var: { + type: 'single blob', + uri: '3', + }, + }, + }, + single_multi_part_BLOB: { + value: generateBlobType(undefined, Core.BlobType.BlobDimensionality.MULTIPART, '4'), + expected: { + result_var: { + type: 'multi-part blob', + uri: '4', + }, + }, + }, +}; + +export default blobTestcases; diff --git a/packages/zapp/console/src/components/Literals/test/helpers/genScalarErrorCase.mock.ts b/packages/zapp/console/src/components/Literals/test/helpers/genScalarErrorCase.mock.ts new file mode 100644 index 000000000..e05e18f65 --- /dev/null +++ b/packages/zapp/console/src/components/Literals/test/helpers/genScalarErrorCase.mock.ts @@ -0,0 +1,38 @@ +import { Core } from 'flyteidl'; +import { TestCaseList } from '../types'; + +const scalarErrorTestCases: TestCaseList = { + ERROR_FULL: { + value: { + failedNodeId: '1', + message: 'message 1', + }, + expected: { + result_var: { error: 'message 1', nodeId: '1' }, + }, + }, + ERROR_NO_NODE: { + value: { + message: 'message 2', + }, + expected: { + result_var: { error: 'message 2', nodeId: undefined }, + }, + }, + ERROR_NO_MESSAGE: { + value: { + failedNodeId: '3', + }, + expected: { + result_var: { error: undefined, nodeId: '3' }, + }, + }, + ERROR_EMPTY: { + value: {}, + expected: { + result_var: { error: undefined, nodeId: undefined }, + }, + }, +}; + +export default scalarErrorTestCases; diff --git a/packages/zapp/console/src/components/Literals/test/helpers/genScalarGenericCase.mock.ts b/packages/zapp/console/src/components/Literals/test/helpers/genScalarGenericCase.mock.ts new file mode 100644 index 000000000..3cf04eedc --- /dev/null +++ b/packages/zapp/console/src/components/Literals/test/helpers/genScalarGenericCase.mock.ts @@ -0,0 +1,143 @@ +import { Protobuf } from 'flyteidl'; +import { TestCaseList } from '../types'; +import { getIValue } from './literalHelpers'; + +const nullValueTestcases: TestCaseList = { + WITH_NULL_VAL: { + value: { + fields: { + test_field_name1: getIValue('nullValue', Protobuf.NullValue.NULL_VALUE), + }, + }, + expected: { + result_var: { test_field_name1: '(empty)' }, + }, + }, +}; +const numberValueTestCases: TestCaseList = { + WITH_NUMBER_VAL: { + value: { + fields: { + test_field_name2: getIValue('numberValue', 1), + }, + }, + expected: { + result_var: { test_field_name2: 1 }, + }, + }, + WITH_NUMBER_VAL_NULL: { + value: { + fields: { + test_field_name3: getIValue('numberValue', null), + }, + }, + expected: { + result_var: { test_field_name3: null }, + }, + }, +}; +const stringValueTestCases: TestCaseList = { + WITH_STRING_VAL: { + value: { + fields: { + test_field_name4: getIValue('stringValue', 'test val'), + }, + }, + expected: { + result_var: { test_field_name4: 'test val' }, + }, + }, + WITH_STRING_VAL_NULL: { + value: { + fields: { + test_field_name: getIValue('stringValue', null), + }, + }, + expected: { + result_var: { test_field_name: null }, + }, + }, +}; + +const boolValueTestCases: TestCaseList = { + WITH_BOOL_VAL: { + value: { + fields: { + test_field_name: getIValue('boolValue', true), + }, + }, + expected: { + result_var: { test_field_name: true }, + }, + }, + WITH_BOOL_VAL_FALSE: { + value: { + fields: { + test_field_name: getIValue('boolValue', false), + }, + }, + expected: { + result_var: { test_field_name: false }, + }, + }, + WITH_BOOL_VAL_NULL: { + value: { + fields: { + test_field_name: getIValue('boolValue', null), + }, + }, + expected: { + result_var: { test_field_name: null }, + }, + }, +}; + +const structValueTestCases: TestCaseList = { + WITH_STRUCT_VALUE: { + value: { + fields: { + test_struct_name: getIValue('structValue', { + fields: { + struct_string_val_copy: stringValueTestCases.WITH_STRING_VAL.value?.fields + ?.test_field_name4 as Protobuf.IValue, + struct_bool_val_copy: boolValueTestCases.WITH_BOOL_VAL.value?.fields + ?.test_field_name as Protobuf.IValue, + }, + }), + }, + }, + expected: { + result_var: { + test_struct_name: { struct_string_val_copy: 'test val', struct_bool_val_copy: true }, + }, + }, + }, +}; + +const listValueTestCases: TestCaseList = { + WITH_LIST_VALUE: { + value: { + fields: { + test_list_name: getIValue('listValue', { + values: [ + structValueTestCases.WITH_STRUCT_VALUE.value?.fields + ?.test_struct_name as Protobuf.IValue, + ], + }), + }, + }, + expected: { + result_var: { + test_list_name: [{ struct_bool_val_copy: true, struct_string_val_copy: 'test val' }], + }, + }, + }, +}; +export default { + ...nullValueTestcases, + ...numberValueTestCases, + ...stringValueTestCases, + ...boolValueTestCases, + ...structValueTestCases, + ...listValueTestCases, +}; diff --git a/packages/zapp/console/src/components/Literals/test/helpers/genScalarNoneCase.mock.ts b/packages/zapp/console/src/components/Literals/test/helpers/genScalarNoneCase.mock.ts new file mode 100644 index 000000000..140af9bed --- /dev/null +++ b/packages/zapp/console/src/components/Literals/test/helpers/genScalarNoneCase.mock.ts @@ -0,0 +1,13 @@ +import { Core } from 'flyteidl'; +import { TestCaseList } from '../types'; + +const scalarNoneTestCase: TestCaseList = { + VOID_TYPE: { + value: {}, + expected: { + result_var: '(empty)', + }, + }, +}; + +export default scalarNoneTestCase; diff --git a/packages/zapp/console/src/components/Literals/test/helpers/genScalarPrimitiveCases.mock.ts b/packages/zapp/console/src/components/Literals/test/helpers/genScalarPrimitiveCases.mock.ts new file mode 100644 index 000000000..5bff19ef4 --- /dev/null +++ b/packages/zapp/console/src/components/Literals/test/helpers/genScalarPrimitiveCases.mock.ts @@ -0,0 +1,90 @@ +import { Core } from 'flyteidl'; +import * as Long from 'long'; +import { long } from 'test/utils'; +import { getPrimitive } from './literalHelpers'; +import { TestCaseList } from '../types'; + +const scalarPrimitiveTestCases: TestCaseList = { + INT_WITH_LARGE_LOW: { + value: getPrimitive('integer', { low: 1642627611, high: 0, unsigned: false } as Long), + expected: { result_var: 1642627611 }, + }, + INT_WITH_SMALL_LOW: { + value: getPrimitive('integer', { low: 55, high: 0, unsigned: false } as Long), + expected: { result_var: 55 }, + }, + INT_WITH_ZERO: { + value: getPrimitive('integer', { low: 0, high: 0, unsigned: false } as Long), + expected: { result_var: 0 }, + }, + FLOAT_ZERO: { + value: getPrimitive('floatValue', 0), + expected: { result_var: 0 }, + }, + FLOAT_POSITIVE: { + value: getPrimitive('floatValue', 1.5), + expected: { result_var: 1.5 }, + }, + FLOAT_NEGATIVE: { + value: getPrimitive('floatValue', -1.5), + expected: { result_var: -1.5 }, + }, + FLOAT_LARGE: { + value: getPrimitive('floatValue', 1.25e10), + expected: { result_var: 1.25e10 }, + }, + FLOAT_UNDEF: { + value: getPrimitive('floatValue', undefined), + expected: { result_var: undefined }, + }, + STRING_NULL: { + value: getPrimitive('stringValue', null), + expected: { result_var: 'null' }, + }, + STRING_VALID: { + value: getPrimitive('stringValue', 'some val'), + expected: { result_var: 'some val' }, + }, + STRING_UNDEF: { + value: getPrimitive('stringValue', undefined), + expected: { result_var: 'undefined' }, + }, + STRING_NEG: { + value: getPrimitive('stringValue', '-1'), + expected: { result_var: '-1' }, + }, + BOOL_TRUE: { + value: getPrimitive('boolean', true), + expected: { result_var: true }, + }, + BOOL_FALSE: { + value: getPrimitive('boolean', false), + expected: { result_var: false }, + }, + BOOL_NULL: { + value: getPrimitive('boolean', null), + expected: { result_var: null }, + }, + BOOL_UNDEF: { + value: getPrimitive('boolean', undefined), + expected: { result_var: undefined }, + }, + DT_VALID: { + value: getPrimitive('datetime', { seconds: long(3600), nanos: 0 }), + expected: { result_var: '1/1/1970 1:00:00 AM UTC' }, + }, + DURATION_ZERO: { + value: getPrimitive('duration', { seconds: long(0), nanos: 0 }), + expected: { result_var: '0s' }, + }, + DURATION_1H: { + value: getPrimitive('duration', { seconds: long(3600), nanos: 0 }), + expected: { result_var: '1h' }, + }, + DURATION_LARGE: { + value: getPrimitive('duration', { seconds: long(10000), nanos: 0 }), + expected: { result_var: '2h 46m 40s' }, + }, +}; + +export default scalarPrimitiveTestCases; diff --git a/packages/zapp/console/src/components/Literals/test/helpers/genScalarSchemaCase.mock.ts b/packages/zapp/console/src/components/Literals/test/helpers/genScalarSchemaCase.mock.ts new file mode 100644 index 000000000..a51574f9f --- /dev/null +++ b/packages/zapp/console/src/components/Literals/test/helpers/genScalarSchemaCase.mock.ts @@ -0,0 +1,46 @@ +import { Core } from 'flyteidl'; +import { TestCaseList } from '../types'; + +const schemaColumnTypes: TestCaseList = Object.keys( + Core.SchemaType.SchemaColumn.SchemaColumnType, +) + .map((key, index) => ({ + [`SCHEMA_WITH_${key}`]: { + value: { + uri: `s3/${index}`, + type: { + columns: [ + { name: 'name' + index, type: Core.SchemaType.SchemaColumn.SchemaColumnType[key] }, + ], + }, + }, + expected: { + result_var: { uri: `s3/${index}`, columns: [`name${index} (${key.toLocaleLowerCase()})`] }, + }, + }, + })) + .reduce((acc, v) => { + return { + ...acc, + ...v, + }; + }, {}); + +const schemaTestCases: TestCaseList = { + SCHEMA_WITHOUT_TYPE: { + value: { + uri: 'test7', + type: { + columns: [{ name: 'test7' }], + }, + }, + expected: { + result_var: { uri: 'test7', columns: [`test7 (unknown)`] }, + }, + }, +}; + +export default { + ...schemaColumnTypes, + ...schemaTestCases, +}; diff --git a/packages/zapp/console/src/components/Literals/test/helpers/genScalarStructuredDsCase.mock.ts b/packages/zapp/console/src/components/Literals/test/helpers/genScalarStructuredDsCase.mock.ts new file mode 100644 index 000000000..ae6660bf1 --- /dev/null +++ b/packages/zapp/console/src/components/Literals/test/helpers/genScalarStructuredDsCase.mock.ts @@ -0,0 +1,131 @@ +import { Core } from 'flyteidl'; +import { TestCaseList } from '../types'; +import { processSimpleType, columnTypeToString } from '../../helpers'; + +import { simple } from './mock_simpleTypes'; + +const generateColumnEntry = (columnName: string, literalType) => { + return { + name: columnName, + literalType, + }; +}; + +const generateStructuredDataset = (columnName: string, uri: string, literalType) => { + return { + uri, + metadata: { + structuredDatasetType: { + format: 'parquet', + columns: [generateColumnEntry(columnName, literalType)], + }, + }, + }; +}; + +const sasWithMapValueTypeColumns: TestCaseList = Object.keys(simple) + .map((simpleTypeKey, index) => { + const simpleType = simple[simpleTypeKey]; + + const literalType = { + mapValueType: { + ...simpleType, + }, + type: 'mapValueType', + }; + + const name = `column_name_${index}`; + const columns = []; + columns[name] = `map value of ${processSimpleType(simpleType[simpleType.type])}`; + + return { + [`STRUCT_SIMPLE_${simpleTypeKey}`]: { + value: generateStructuredDataset(name, index.toString(), literalType), + expected: { + result_var: { columns, format: 'parquet', uri: index.toString() }, + }, + }, + }; + }) + .reduce((acc, v) => ({ ...acc, ...v }), {}); + +const sasWithCollectionTypeColumns: TestCaseList = Object.keys(simple) + .map((simpleTypeKey, index) => { + const simpleType = simple[simpleTypeKey]; + + const literalType = { + collectionType: { + ...simpleType, + }, + type: 'collectionType', + }; + + const name = `column_name_${index}`; + const columns = []; + columns[name] = `collection of ${processSimpleType(simpleType[simpleType.type])}`; + + return { + [`STRUCT_SIMPLE_${simpleTypeKey}`]: { + value: generateStructuredDataset(name, index.toString(), literalType), + expected: { + result_var: { columns, format: 'parquet', uri: index.toString() }, + }, + }, + }; + }) + .slice(0, 1) + .reduce((acc, v) => ({ ...acc, ...v }), {}); + +const sdsWithSimpleTypeColumns: TestCaseList = Object.keys(simple) + .map((simpleTypeKey, index) => { + const literalType = simple[simpleTypeKey]; + + const name = `column_name_${index}`; + const columns = []; + columns[name] = processSimpleType(literalType[literalType.type]); + + return { + [`STRUCT_SIMPLE_${simpleTypeKey}`]: { + value: generateStructuredDataset(name, index.toString(), literalType), + expected: { + result_var: { columns, format: 'parquet', uri: index.toString() }, + }, + }, + }; + }) + .reduce((acc, v) => ({ ...acc, ...v }), {}); + +const sasWithSchemaColumns: TestCaseList = Object.keys( + Core.SchemaType.SchemaColumn.SchemaColumnType, +) + .map((simpleTypeKey, index) => { + const literalType = { + schema: { + columns: [{ type: Core.SchemaType.SchemaColumn.SchemaColumnType[simpleTypeKey] }], + }, + type: 'schema', + }; + + const name = `schema_column_name_${index}`; + const columns = []; + columns[name] = `schema (${columnTypeToString( + Core.SchemaType.SchemaColumn.SchemaColumnType[simpleTypeKey], + )})`; + + return { + [`STRUCT_SCHEMA_WITH_COLUMNS_${simpleTypeKey}`]: { + value: generateStructuredDataset(name, index.toString(), literalType), + expected: { + result_var: { columns, format: 'parquet', uri: index.toString() }, + }, + }, + }; + }) + .reduce((acc, v) => ({ ...acc, ...v }), {}); + +export default { + ...sdsWithSimpleTypeColumns, + ...sasWithSchemaColumns, + ...sasWithCollectionTypeColumns, + ...sasWithMapValueTypeColumns, +}; diff --git a/packages/zapp/console/src/components/Literals/test/helpers/index.ts b/packages/zapp/console/src/components/Literals/test/helpers/index.ts new file mode 100644 index 000000000..e1e2debc6 --- /dev/null +++ b/packages/zapp/console/src/components/Literals/test/helpers/index.ts @@ -0,0 +1,23 @@ +import primitive from './genScalarPrimitiveCases.mock'; +import blob from './genScalarBlobCases.mock'; +import binary from './genScalarBinaryTestCase.mock'; +import schema from './genScalarSchemaCase.mock'; +import noneType from './genScalarNoneCase.mock'; +import errorType from './genScalarErrorCase.mock'; +import generic from './genScalarGenericCase.mock'; +import structuredDataset from './genScalarStructuredDsCase.mock'; +import collection from './genCollectionTestcase.mock'; +import map from './genMapTestCase.mock'; + +export { + primitive, + blob, + binary, + schema, + noneType, + errorType, + generic, + structuredDataset, + collection, + map, +}; diff --git a/packages/zapp/console/src/components/Literals/test/helpers/literalHelpers.ts b/packages/zapp/console/src/components/Literals/test/helpers/literalHelpers.ts new file mode 100644 index 000000000..67e4da36f --- /dev/null +++ b/packages/zapp/console/src/components/Literals/test/helpers/literalHelpers.ts @@ -0,0 +1,123 @@ +import { Core, Protobuf } from 'flyteidl'; + +export function getIValue( + kind: 'nullValue' | 'numberValue' | 'stringValue' | 'boolValue' | 'structValue' | 'listValue', + value?: + | Protobuf.NullValue + | number + | string + | boolean + | Protobuf.IStruct + | Protobuf.IListValue + | null, +): Protobuf.IValue & Pick { + return { + kind, + [kind]: value, + }; +} + +export function getPrimitive( + key: 'integer' | 'floatValue' | 'stringValue' | 'boolean' | 'datetime' | 'duration', + value?: Long | number | string | boolean | Protobuf.ITimestamp | Protobuf.IDuration | null, +): Core.IPrimitive & Pick { + return { + [key]: value, + value: key, + }; +} + +export function generateBlobType( + format?: string, + dimensionality?: Core.BlobType.BlobDimensionality, + uri?: string, +): Core.IBlob { + return { + metadata: { + type: { + format, + dimensionality, + }, + }, + uri, + }; +} + +const getScalar = ( + value: + | Core.IPrimitive + | Core.IBlob + | Core.IBinary + | Core.ISchema + | Core.IVoid + | Core.IError + | Protobuf.IStruct + | Core.IStructuredDataset + | Core.IUnion, + scalarType: + | 'primitive' + | 'blob' + | 'binary' + | 'schema' + | 'noneType' + | 'error' + | 'generic' + | 'structuredDataset' + | 'union', +): Core.IScalar & Pick => { + return { + [scalarType]: value, + value: scalarType, + }; +}; + +// TOP LEVEL SCHEMA GENERATORS: +export const getScalarLiteral = ( + value: + | Core.IPrimitive + | Core.IBlob + | Core.IBinary + | Core.ISchema + | Core.IVoid + | Core.IError + | Protobuf.IStruct + | Core.IStructuredDataset + | Core.IUnion, + scalarType: + | 'primitive' + | 'blob' + | 'binary' + | 'schema' + | 'noneType' + | 'error' + | 'generic' + | 'structuredDataset' + | 'union', +): Core.ILiteral & Pick => { + return { + scalar: getScalar(value, scalarType), + value: 'scalar', + }; +}; + +export const getCollection = ( + literals: Core.ILiteral[], +): Core.ILiteral & Pick => { + return { + collection: { + literals, + }, + value: 'collection', + }; +}; + +export const getMap = ( + literals: { [k: string]: Core.ILiteral } | null, +): Core.ILiteral & Pick => { + return { + map: { + literals, + }, + value: 'map', + }; +}; diff --git a/packages/zapp/console/src/components/Literals/test/helpers/mock_simpleTypes.ts b/packages/zapp/console/src/components/Literals/test/helpers/mock_simpleTypes.ts new file mode 100644 index 000000000..0bd9d543f --- /dev/null +++ b/packages/zapp/console/src/components/Literals/test/helpers/mock_simpleTypes.ts @@ -0,0 +1,17 @@ +import { Core } from 'flyteidl'; + +export function extractSimpleTypes() { + const simpleTypes= Object.keys(Core.SimpleType).map((key) => ({ + [key]: { + type: 'simple', + simple: Core.SimpleType[key], + }, + })).reduce((acc, v) => ({...acc, ...v}), {}); + return simpleTypes; +} + +const simple: Core.SimpleType[] = extractSimpleTypes() as any; + +export { + simple +}; diff --git a/packages/zapp/console/src/components/Literals/test/literal.helpers.test.ts b/packages/zapp/console/src/components/Literals/test/literal.helpers.test.ts new file mode 100644 index 000000000..8ebf97c6d --- /dev/null +++ b/packages/zapp/console/src/components/Literals/test/literal.helpers.test.ts @@ -0,0 +1,83 @@ +import { Core } from 'flyteidl'; +import { transformLiterals } from '../helpers'; + +import { + primitive, + blob, + binary, + schema, + noneType, + errorType, + generic, + structuredDataset, + collection, + map, +} from './helpers/index'; +import { getCollection, getMap, getScalarLiteral } from './helpers/literalHelpers'; + +const literalTestCases = { + scalar: { + primitive, + blob, + binary, + schema, + noneType, + error: errorType, + generic, + structuredDataset, + union: {} as Core.IPrimitive, // TODO: FC#450 ass support for union types + }, + collection, + map, +}; + +describe('scalar literal', () => { + const scalarTestCases = literalTestCases.scalar; + const scalarType = Object.keys(scalarTestCases); + + scalarType.map((scalarTestType) => { + describe(scalarTestType, () => { + const cases = scalarTestCases[scalarTestType]; + + Object.keys(cases || {}).map((testKey) => { + const { value, expected } = cases[testKey]; + + it(`${testKey}: should return ${expected} for ${value}`, () => { + const scalar = { result_var: { ...getScalarLiteral(value, scalarTestType as any) } }; + const result = transformLiterals(scalar as any); + expect(result).toEqual(expected); + }); + }); + }); + }); +}); + +describe('collection literal', () => { + const cases = literalTestCases.collection; + + Object.keys(cases).map((testKey) => { + const { value, expected } = cases[testKey]; + + it(`${testKey}: should return ${expected} for ${value}`, () => { + const collection = { result_var: { ...getCollection([value]) } }; + + const result = transformLiterals(collection); + expect(result).toEqual(expected); + }); + }); +}); + +describe('map literal', () => { + const cases = literalTestCases.map; + + Object.keys(cases).map((testKey) => { + const { value, expected } = cases[testKey]; + + it(`${testKey}: should return ${expected} for ${value}`, () => { + const collection = { result_var: { ...getMap({ value }) } }; + + const result = transformLiterals(collection); + expect(result).toEqual(expected); + }); + }); +}); diff --git a/packages/zapp/console/src/components/Literals/test/types.d.ts b/packages/zapp/console/src/components/Literals/test/types.d.ts new file mode 100644 index 000000000..04572a2b5 --- /dev/null +++ b/packages/zapp/console/src/components/Literals/test/types.d.ts @@ -0,0 +1,8 @@ +export type TestCase = { + value: K; + expected: any; +}; + +export type TestCaseList = { + [key: string]: TestCase; +}; diff --git a/packages/zapp/console/src/components/common/DumpJSON.tsx b/packages/zapp/console/src/components/common/DumpJSON.tsx index e20db28c4..57349eefb 100644 --- a/packages/zapp/console/src/components/common/DumpJSON.tsx +++ b/packages/zapp/console/src/components/common/DumpJSON.tsx @@ -1,8 +1,8 @@ -import { stringifyValue } from 'common/utils'; -import { useCommonStyles } from 'components/common/styles'; import * as React from 'react'; +import { ReactJsonViewWrapper } from 'components/common/ReactJsonView'; export const DumpJSON: React.FC<{ value: any }> = ({ value }) => { - const commonStyles = useCommonStyles(); - return
{stringifyValue(value)}
; + return ( + + ); }; diff --git a/packages/zapp/console/src/components/common/PanelSection/index.tsx b/packages/zapp/console/src/components/common/PanelSection/index.tsx index 23b229699..ace1ab503 100644 --- a/packages/zapp/console/src/components/common/PanelSection/index.tsx +++ b/packages/zapp/console/src/components/common/PanelSection/index.tsx @@ -4,6 +4,7 @@ import { makeStyles } from '@material-ui/core/styles'; const useStyle = makeStyles((theme) => ({ detailsPanelCard: { borderBottom: `1px solid ${theme.palette.divider}`, + paddingBottom: '150px', // TODO @FC 454 temporary fix for panel height issue }, detailsPanelCardContent: { padding: `${theme.spacing(2)}px ${theme.spacing(3)}px`, diff --git a/packages/zapp/console/src/components/common/ReactJsonView.tsx b/packages/zapp/console/src/components/common/ReactJsonView.tsx new file mode 100644 index 000000000..ea7e9d1a1 --- /dev/null +++ b/packages/zapp/console/src/components/common/ReactJsonView.tsx @@ -0,0 +1,99 @@ +import * as React from 'react'; +import { makeStyles, Theme } from '@material-ui/core/styles'; +import ReactJsonView, { ReactJsonViewProps } from 'react-json-view'; +import * as copyToClipboard from 'copy-to-clipboard'; +import { primaryTextColor } from 'components/Theme/constants'; + +const useStyles = makeStyles((theme: Theme) => ({ + jsonViewer: { + marginLeft: '-10px', + width: '100%', + '& span': { + fontWeight: 'normal !important', + }, + '& .object-container': { + overflowWrap: 'anywhere !important', + }, + '& .copy-to-clipboard-container': { + position: 'absolute', + }, + '& .copy-icon svg': { + color: `${theme.palette.primary.main} !important`, + }, + '& .variable-value': { + paddingLeft: '5px', + }, + '& .variable-value >*': { + color: `${primaryTextColor} !important`, + }, + '& .object-key-val': { + padding: '0 0 0 5px !important', + }, + '& .object-key': { + color: `${theme.palette.grey[500]} !important`, + }, + }, +})); + +/** + * + * Replacer functionality to pass to the JSON.stringify function that + * does proper serialization of arrays that contain non-numeric indexes + * @param _ parent element key + * @param value the element being serialized + * @returns Transformed version of input + */ +const replacer = (_, value) => { + // Check if associative array + if (value instanceof Array && Object.keys(value).some((v) => isNaN(+v))) { + // Serialize associative array + return Object.keys(value).reduce((acc, arrKey) => { + // if: + // string key is encountered insert {[key]: value} into transformed array + // else: + // insert original value + acc.push(isNaN(+arrKey) ? { [arrKey]: value[arrKey] } : value[arrKey]); + + return acc; + }, [] as any[]); + } + + // Non-associative array. return original value to allow default JSON.stringify behavior + return value; +}; + +/** + * Custom implementation for JSON.stringify to allow + * proper serialization of arrays that contain non-numeric indexes + * + * @param json Object to serialize + * @returns A string version of the input json + */ +const customStringify = (json) => { + return JSON.stringify(json, replacer); +}; + +export const ReactJsonViewWrapper: React.FC = (props) => { + const styles = useStyles(); + + return ( +
+ { + const objToCopy = options.src; + const text = typeof objToCopy === 'object' ? customStringify(objToCopy) : objToCopy; + copyToClipboard(text); + }} + displayDataTypes={false} + quotesOnKeys={false} + iconStyle="triangle" + displayObjectSize={true} + name={null} + indentWidth={4} + collapseStringsAfterLength={80} + sortKeys={true} + {...props} + /> +
+ ); +}; diff --git a/yarn.lock b/yarn.lock index e04f21af6..0e47e4ccc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5380,7 +5380,7 @@ arrify@^2.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== -asap@^2.0.0: +asap@^2.0.0, asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -5739,6 +5739,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base16@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70" + integrity sha1-4pf2DX7BAUp6lxo568ipjAtoHnA= + base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -7398,6 +7403,13 @@ cronstrue@^1.31.0: resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-1.105.0.tgz#57f89468c261d7fa56c69057f42c84eba3a37837" integrity sha512-Bv8GHi5uJvxtq/9T7lgBwum7UVKMfR+LSPHZXiezP0E5gnODPVRQBAkCwijCIaWEepqmRcxTAxrUFB0UQK2wdw== +cross-fetch@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + dependencies: + node-fetch "2.6.7" + cross-spawn@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -9451,6 +9463,31 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fbemitter@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-3.0.0.tgz#00b2a1af5411254aab416cd75f9e6289bee4bff3" + integrity sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw== + dependencies: + fbjs "^3.0.0" + +fbjs-css-vars@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" + integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== + +fbjs@^3.0.0, fbjs@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.4.tgz#e1871c6bd3083bac71ff2da868ad5067d37716c6" + integrity sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ== + dependencies: + cross-fetch "^3.1.5" + fbjs-css-vars "^1.0.0" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.30" + figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" @@ -9638,6 +9675,14 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" +flux@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/flux/-/flux-4.0.3.tgz#573b504a24982c4768fdfb59d8d2ea5637d72ee7" + integrity sha512-yKAbrp7JhZhj6uiT1FTuVMlIAT1J4jqEyBpFApi1kxpGZCvacMVc/t1pMQyotqHhAgvoE3bNvAykhCo2CLjnYw== + dependencies: + fbemitter "^3.0.0" + fbjs "^3.0.1" + follow-redirects@^1.0.0, follow-redirects@^1.14.0: version "1.14.8" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc" @@ -12966,6 +13011,11 @@ lodash.clonedeep@^4.5.0, lodash.clonedeep@~4.5.0: resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= +lodash.curry@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170" + integrity sha1-JI42By7ekGUB11lmIAqG2riyMXA= + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -12981,6 +13031,11 @@ lodash.escaperegexp@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= +lodash.flow@^3.3.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a" + integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o= + lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" @@ -13943,7 +13998,7 @@ node-fetch-npm@^2.0.2: json-parse-better-errors "^1.0.0" safe-buffer "^5.1.1" -node-fetch@^2.6.1: +node-fetch@2.6.7, node-fetch@^2.6.1: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -15504,6 +15559,13 @@ promise.prototype.finally@^3.1.0: define-properties "^1.1.3" es-abstract "^1.19.1" +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== + dependencies: + asap "~2.0.3" + prompts@^2.0.1: version "2.4.0" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7" @@ -15666,6 +15728,11 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +pure-color@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" + integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4= + q@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -15807,6 +15874,16 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-base16-styling@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c" + integrity sha1-7yFW1mz0E5aVyKFniGy2nqZgeSw= + dependencies: + base16 "^1.0.0" + lodash.curry "^4.0.1" + lodash.flow "^3.3.0" + pure-color "^1.2.0" + react-chartjs-2@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-4.0.0.tgz#a79919c9efe5381b8cb5abfd0ac7a56c9736cdb8" @@ -15956,6 +16033,16 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== +react-json-view@^1.21.3: + version "1.21.3" + resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.21.3.tgz#f184209ee8f1bf374fb0c41b0813cff54549c475" + integrity sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw== + dependencies: + flux "^4.0.1" + react-base16-styling "^0.6.0" + react-lifecycles-compat "^3.0.4" + react-textarea-autosize "^8.3.2" + react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" @@ -16070,7 +16157,7 @@ react-syntax-highlighter@^13.5.3: prismjs "^1.21.0" refractor "^3.1.0" -react-textarea-autosize@^8.3.0: +react-textarea-autosize@^8.3.0, react-textarea-autosize@^8.3.2: version "8.3.3" resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8" integrity sha512-2XlHXK2TDxS6vbQaoPbMOfQ8GK7+irc2fVK6QFIcC8GOnH3zI/v481n+j1L0WaPVvKxwesnY93fEfH++sus2rQ== @@ -17161,7 +17248,7 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4: +setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -18618,6 +18705,11 @@ typescript@^4.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4" integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg== +ua-parser-js@^0.7.30: + version "0.7.31" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6" + integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ== + uc.micro@^1.0.1: version "1.0.6" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"