@finos/legend-application-studio
Version:
Legend Studio application core
336 lines (324 loc) • 9.63 kB
text/typescript
/**
* Copyright (c) 2020-present, Goldman Sachs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
addDays,
assertErrorThrown,
formatDate,
Randomizer,
UnsupportedOperationError,
type PlainObject,
isNonNullable,
filterByType,
} from '@finos/legend-shared';
import type { EditorStore } from '../EditorStore.js';
import {
PrimitiveType,
type Enumeration,
PRIMITIVE_TYPE,
Class,
getAllClassProperties,
DATE_FORMAT,
DATE_TIME_FORMAT,
classHasCycle,
TableAlias,
Table,
Column,
VarChar,
type RelationalDataType,
Char,
VarBinary,
TinyInt,
Float,
Timestamp,
Binary,
Bit,
Other,
Numeric,
Decimal,
Double,
Integer,
Real,
SmallInt,
Date as ColumnDate,
SemiStructured,
Json,
MILESTONING_VERSION_PROPERTY_SUFFIX,
} from '@finos/legend-graph';
import {
CLASS_PROPERTY_TYPE,
getClassPropertyType,
} from './ModelClassifierUtils.js';
import type { MappingElementSource } from '../editor-state/element-editor-state/mapping/MappingEditorState.js';
import { DEFAULT_TAB_SIZE } from '@finos/legend-application';
import { XMLBuilder } from 'fast-xml-parser';
import { stringify as YAML_stringify } from 'yaml';
export const createMockPrimitiveProperty = (
primitiveType: PrimitiveType,
propertyName: string,
): string | number | boolean => {
const randomizer = new Randomizer();
switch (primitiveType.name) {
case PRIMITIVE_TYPE.BOOLEAN:
return randomizer.getRandomItemInCollection([true, false]) ?? true;
case PRIMITIVE_TYPE.FLOAT:
return randomizer.getRandomFloat();
case PRIMITIVE_TYPE.DECIMAL:
return randomizer.getRandomDouble();
case PRIMITIVE_TYPE.NUMBER:
case PRIMITIVE_TYPE.INTEGER:
return randomizer.getRandomWholeNumber(100);
// NOTE that `Date` is the umbrella type that comprises `StrictDate` and `DateTime`, but for simplicity, we will generate `Date` as `StrictDate`
case PRIMITIVE_TYPE.DATE:
case PRIMITIVE_TYPE.STRICTDATE:
return formatDate(
randomizer.getRandomDate(
new Date(Date.now()),
addDays(Date.now(), 100),
),
DATE_FORMAT,
);
case PRIMITIVE_TYPE.DATETIME:
return formatDate(
randomizer.getRandomDate(
new Date(Date.now()),
addDays(Date.now(), 100),
),
DATE_TIME_FORMAT,
);
case PRIMITIVE_TYPE.STRING:
default:
return `${propertyName} ${randomizer.getRandomWholeNumber(100)}`;
}
};
export const createMockEnumerationProperty = (
enumeration: Enumeration,
): string =>
new Randomizer().getRandomItemInCollection(enumeration.values)?.name ?? '';
export const createMockClassInstance = (
_class: Class,
traverseNonRequiredProperties = false,
depth = 0,
): PlainObject => {
const properties = traverseNonRequiredProperties
? getAllClassProperties(_class, true)
: getAllClassProperties(_class, true).filter(
(p) => p.multiplicity.lowerBound,
);
const propertyNames = properties.map((property) => property.name);
const filteredProperties = properties.filter(
(prop) =>
prop.name.endsWith(MILESTONING_VERSION_PROPERTY_SUFFIX.ALL_VERSIONS) ||
(!propertyNames.includes(
prop.name + MILESTONING_VERSION_PROPERTY_SUFFIX.ALL_VERSIONS,
) &&
!prop.name.endsWith(
MILESTONING_VERSION_PROPERTY_SUFFIX.ALL_VERSIONS_IN_RANGE,
)),
);
const mockData: Record<string, object | string | number | boolean> = {};
filteredProperties.forEach((property) => {
const propertyType = property.genericType.value.rawType;
let propertyMockData: PlainObject | string | number | boolean | undefined;
switch (getClassPropertyType(propertyType)) {
case CLASS_PROPERTY_TYPE.PRIMITIVE:
propertyMockData = createMockPrimitiveProperty(
propertyType as PrimitiveType,
property.name,
);
break;
case CLASS_PROPERTY_TYPE.ENUMERATION:
propertyMockData = createMockEnumerationProperty(
propertyType as Enumeration,
);
break;
case CLASS_PROPERTY_TYPE.CLASS:
if (depth > 0) {
propertyMockData = createMockClassInstance(
propertyType as Class,
traverseNonRequiredProperties,
depth - 1,
);
}
break;
default:
break;
}
if (propertyMockData) {
mockData[property.name] =
property.multiplicity.upperBound === undefined ||
property.multiplicity.upperBound >= 2
? [propertyMockData]
: propertyMockData;
}
});
return mockData;
};
export const createMockDataForClass = (
element: Class,
maxDepth = 100,
depthForCycle = 3,
): PlainObject => {
const depth = classHasCycle(element, {
traverseNonRequiredProperties: true,
})
? Math.max(depthForCycle, 0)
: Math.max(maxDepth, 0);
return createMockClassInstance(element, true, depth);
};
export enum CLASS_MOCK_DATA_GENERATION_FORMAT {
JSON = 'JSON',
XML = 'XML',
YAML = 'YAML',
}
export const createMockDataForClassWithFormat = (
element: Class,
format: CLASS_MOCK_DATA_GENERATION_FORMAT,
maxDepth = 100,
depthForCycle = 3,
): string => {
const depth = classHasCycle(element, {
traverseNonRequiredProperties: true,
})
? Math.max(depthForCycle, 0)
: Math.max(maxDepth, 0);
const obj = createMockClassInstance(element, true, depth);
switch (format) {
case CLASS_MOCK_DATA_GENERATION_FORMAT.JSON:
return JSON.stringify(obj, undefined, DEFAULT_TAB_SIZE);
case CLASS_MOCK_DATA_GENERATION_FORMAT.XML: {
return new XMLBuilder({
indentBy: ' '.repeat(DEFAULT_TAB_SIZE),
format: true,
}).build(obj);
}
case CLASS_MOCK_DATA_GENERATION_FORMAT.YAML:
return YAML_stringify(obj, { indent: DEFAULT_TAB_SIZE });
default:
throw new UnsupportedOperationError(
`Can't create mock data for class with format '${format}'`,
);
}
};
export const getPrimitiveTypeFromRelationalType = (
type: RelationalDataType,
): PrimitiveType | undefined => {
if (
type instanceof VarChar ||
type instanceof Char ||
type instanceof VarBinary ||
type instanceof Binary ||
type instanceof Bit ||
type instanceof Other
) {
return PrimitiveType.STRING;
} else if (type instanceof Numeric) {
return PrimitiveType.NUMBER;
} else if (type instanceof Decimal) {
return PrimitiveType.DECIMAL;
} else if (
type instanceof Double ||
type instanceof Integer ||
type instanceof Real ||
type instanceof SmallInt ||
type instanceof TinyInt
) {
return PrimitiveType.INTEGER;
} else if (type instanceof Float) {
return PrimitiveType.FLOAT;
} else if (type instanceof ColumnDate) {
return PrimitiveType.DATE;
} else if (type instanceof Timestamp) {
return PrimitiveType.DATETIME;
}
return undefined;
};
export const createMockDataForColumn = (
col: Column,
isPrimaryKey: boolean,
idx?: number | undefined,
): string | undefined => {
const type = col.type;
if (
(type instanceof Double ||
type instanceof Integer ||
type instanceof Real ||
type instanceof SmallInt ||
type instanceof TinyInt) &&
isPrimaryKey &&
idx
) {
return idx.toString();
}
const primitive = getPrimitiveTypeFromRelationalType(type);
if (primitive) {
return createMockPrimitiveProperty(primitive, col.name).toString();
} else if (type instanceof Json || type instanceof SemiStructured) {
return '{}';
}
return undefined;
};
export const createMockDataForTable = (
table: Table,
ITERATIONS = 1,
): string => {
const targetedCols = table.columns.filter(filterByType(Column));
const colNames = targetedCols.map((e) => e.name).join(',');
const vals = Array.from(Array(ITERATIONS).keys())
.map((idx) =>
targetedCols
.map((col) =>
createMockDataForColumn(col, table.primaryKey.includes(col), idx),
)
.filter(isNonNullable)
.join(','),
)
.join('\n');
return `${colNames}\n${vals}`;
};
export const createMockDataForMappingElementSource = (
srcElement: MappingElementSource,
editorStore: EditorStore,
): string => {
if (srcElement instanceof Class) {
try {
return JSON.stringify(
createMockDataForClass(srcElement),
undefined,
DEFAULT_TAB_SIZE,
);
} catch (error) {
assertErrorThrown(error);
editorStore.applicationStore.notificationService.notifyWarning(
`Can't generate test data for class '${srcElement}'. Error:\n${error.message}`,
);
return '';
}
} else if (
srcElement instanceof TableAlias &&
srcElement.relation instanceof Table
) {
return createMockDataForTable(srcElement.relation);
} else if (srcElement instanceof Table) {
return createMockDataForTable(srcElement);
}
editorStore.applicationStore.notificationService.notifyWarning(
new UnsupportedOperationError(
`Can't generate test data for mapping source`,
srcElement,
),
);
return '';
};