Add type as modeled method type

This adds support for modeling types. A MaD language can now optionally
define a `type` predicate. This allows internally propagating these
models. The UI will now simply show a label "type" for type models
without any way to edit these.
This commit is contained in:
Koen Vlaswinkel
2023-11-03 11:12:14 +01:00
parent 4673bf56bd
commit 261f8b3b2c
9 changed files with 181 additions and 30 deletions

View File

@@ -5,6 +5,7 @@ import {
SinkModeledMethod,
SourceModeledMethod,
SummaryModeledMethod,
TypeModeledMethod,
} from "../modeled-method";
import { DataTuple } from "../model-extension-file";
import { Mode } from "../shared/mode";
@@ -17,7 +18,7 @@ type ReadModeledMethod = (row: DataTuple[]) => ModeledMethod;
export type ModelsAsDataLanguagePredicate<T> = {
extensiblePredicate: string;
supportedKinds: string[];
supportedKinds?: string[];
generateMethodDefinition: GenerateMethodDefinition<T>;
readModeledMethod: ReadModeledMethod;
};
@@ -43,6 +44,7 @@ export type ModelsAsDataLanguagePredicates = {
sink?: ModelsAsDataLanguagePredicate<SinkModeledMethod>;
summary?: ModelsAsDataLanguagePredicate<SummaryModeledMethod>;
neutral?: ModelsAsDataLanguagePredicate<NeutralModeledMethod>;
type?: ModelsAsDataLanguagePredicate<TypeModeledMethod>;
};
export type ModelsAsDataLanguage = {

View File

@@ -150,6 +150,30 @@ export const ruby: ModelsAsDataLanguage = {
};
},
},
type: {
extensiblePredicate: "typeModel",
// extensible predicate typeModel(string type1, string type2, string path);
generateMethodDefinition: (method) => [
method.relatedTypeName,
method.typeName,
`Method[${method.methodName}].${method.path}`,
],
readModeledMethod: (row) => {
const typeName = row[1] as string;
const { methodName, path } = parseRubyAccessPath(row[2] as string);
return {
type: "type",
relatedTypeName: row[0] as string,
path,
signature: rubyMethodSignature(typeName, methodName),
packageName: "",
typeName,
methodName,
methodParameters: "",
};
},
},
},
modelGeneration: {
queryConstraints: {

View File

@@ -25,6 +25,8 @@ export function createEmptyModeledMethod(
return createEmptySummaryModeledMethod(canonicalMethodSignature);
case "neutral":
return createEmptyNeutralModeledMethod(canonicalMethodSignature);
case "type":
return createEmptyTypeModeledMethod(canonicalMethodSignature);
default:
assertNever(type);
}
@@ -86,3 +88,14 @@ function createEmptyNeutralModeledMethod(
provenance: "manual",
};
}
function createEmptyTypeModeledMethod(
methodSignature: MethodSignature,
): ModeledMethod {
return {
...methodSignature,
type: "type",
relatedTypeName: "",
path: "",
};
}

View File

@@ -5,6 +5,7 @@ export type ModeledMethodType =
| "source"
| "sink"
| "summary"
| "type"
| "neutral";
export type Provenance =
@@ -51,12 +52,19 @@ export interface NeutralModeledMethod extends MethodSignature {
readonly provenance: Provenance;
}
export interface TypeModeledMethod extends MethodSignature {
readonly type: "type";
readonly relatedTypeName: string;
readonly path: string;
}
export type ModeledMethod =
| NoneModeledMethod
| SourceModeledMethod
| SinkModeledMethod
| SummaryModeledMethod
| NeutralModeledMethod;
| NeutralModeledMethod
| TypeModeledMethod;
export type ModeledMethodKind = string;

View File

@@ -70,6 +70,13 @@ function canonicalizeModeledMethod(
kind: modeledMethod.kind,
provenance: "manual",
};
case "type":
return {
...methodSignature,
type: "type",
relatedTypeName: modeledMethod.relatedTypeName,
path: modeledMethod.path,
};
default:
assertNever(modeledMethod);
}

View File

@@ -7,6 +7,7 @@ import {
SinkModeledMethod,
SourceModeledMethod,
SummaryModeledMethod,
TypeModeledMethod,
} from "./modeled-method";
import {
getModelsAsDataLanguage,
@@ -68,6 +69,7 @@ export function createDataExtensionYaml(
sink: [] as SinkModeledMethod[],
summary: [] as SummaryModeledMethod[],
neutral: [] as NeutralModeledMethod[],
type: [] as TypeModeledMethod[],
} satisfies Record<keyof ModelsAsDataLanguagePredicates, ModeledMethod[]>;
for (const modeledMethod of modeledMethods) {
@@ -88,6 +90,9 @@ export function createDataExtensionYaml(
case "neutral":
methodsByType.neutral.push(modeledMethod);
break;
case "type":
methodsByType.type.push(modeledMethod);
break;
default:
assertNever(modeledMethod);
}
@@ -122,6 +127,12 @@ export function createDataExtensionYaml(
methodsByType.neutral,
modelsAsDataLanguage.predicates.neutral,
);
case "type":
return createExtensions(
language,
methodsByType.type,
modelsAsDataLanguage.predicates.type,
);
default:
assertNever(type);
}

View File

@@ -73,6 +73,14 @@ export const ModelTypeDropdown = ({
[onChange, method, modeledMethod, argumentsList],
);
const value = modeledMethod?.type ?? "none";
const isShownOption = options.some((option) => option.value === value);
if (!isShownOption) {
return <>{value}</>;
}
return (
<Dropdown
value={modeledMethod?.type ?? "none"}

View File

@@ -75,33 +75,71 @@ describe("parseGenerateModelResults", () => {
ruby,
createMockLogger(),
);
expect(result.sort()).toEqual(
[
{
input: "Argument[self]",
kind: "value",
methodName: "create_function",
methodParameters: "",
output: "ReturnValue",
packageName: "",
provenance: "manual",
signature: "SQLite3::Database#create_function",
type: "summary",
typeName: "SQLite3::Database",
},
{
input: "Argument[1]",
kind: "value",
methodName: "new",
methodParameters: "",
output: "ReturnValue",
packageName: "",
provenance: "manual",
signature: "SQLite3::Value!#new",
type: "summary",
typeName: "SQLite3::Value!",
},
].sort(),
);
expect(result).toEqual([
{
methodName: "types",
methodParameters: "",
packageName: "",
path: "ReturnValue",
relatedTypeName: "Array",
signature: "SQLite3::ResultSet#types",
type: "type",
typeName: "SQLite3::ResultSet",
},
{
methodName: "columns",
methodParameters: "",
packageName: "",
path: "ReturnValue",
relatedTypeName: "Array",
signature: "SQLite3::ResultSet#columns",
type: "type",
typeName: "SQLite3::ResultSet",
},
{
methodName: "types",
methodParameters: "",
packageName: "",
path: "ReturnValue",
relatedTypeName: "Array",
signature: "SQLite3::Statement#types",
type: "type",
typeName: "SQLite3::Statement",
},
{
methodName: "columns",
methodParameters: "",
packageName: "",
path: "ReturnValue",
relatedTypeName: "Array",
signature: "SQLite3::Statement#columns",
type: "type",
typeName: "SQLite3::Statement",
},
{
input: "Argument[self]",
kind: "value",
methodName: "create_function",
methodParameters: "",
output: "ReturnValue",
packageName: "",
provenance: "manual",
signature: "SQLite3::Database#create_function",
type: "summary",
typeName: "SQLite3::Database",
},
{
input: "Argument[1]",
kind: "value",
methodName: "new",
methodParameters: "",
output: "ReturnValue",
packageName: "",
provenance: "manual",
signature: "SQLite3::Value!#new",
type: "summary",
typeName: "SQLite3::Value!",
},
]);
});
});

View File

@@ -138,6 +138,46 @@ describe("runGenerateQueries", () => {
...options,
});
expect(onResults).toHaveBeenCalledWith([
{
methodName: "types",
methodParameters: "",
packageName: "",
path: "ReturnValue",
relatedTypeName: "Array",
signature: "SQLite3::ResultSet#types",
type: "type",
typeName: "SQLite3::ResultSet",
},
{
methodName: "columns",
methodParameters: "",
packageName: "",
path: "ReturnValue",
relatedTypeName: "Array",
signature: "SQLite3::ResultSet#columns",
type: "type",
typeName: "SQLite3::ResultSet",
},
{
methodName: "types",
methodParameters: "",
packageName: "",
path: "ReturnValue",
relatedTypeName: "Array",
signature: "SQLite3::Statement#types",
type: "type",
typeName: "SQLite3::Statement",
},
{
methodName: "columns",
methodParameters: "",
packageName: "",
path: "ReturnValue",
relatedTypeName: "Array",
signature: "SQLite3::Statement#columns",
type: "type",
typeName: "SQLite3::Statement",
},
{
input: "Argument[self]",
kind: "value",