Merge pull request #3048 from github/koesie10/type-model

Add `type` as modeled method type
This commit is contained in:
Koen Vlaswinkel
2023-11-10 10:21:29 +01:00
committed by GitHub
12 changed files with 234 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 MethodArgumentOptions = {

View File

@@ -151,6 +151,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

@@ -0,0 +1,31 @@
import * as React from "react";
import { useMemo } from "react";
import { Dropdown } from "./Dropdown";
type Props = {
value: string;
className?: string;
"aria-label"?: string;
};
export function ReadonlyDropdown({ value, ...props }: Props) {
const options = useMemo(() => {
return [
{
value,
label: value,
},
];
}, [value]);
return (
<Dropdown
value={value}
disabledPlaceholder={value}
disabled={true}
options={options}
{...props}
/>
);
}

View File

@@ -6,6 +6,7 @@ import {
modeledMethodSupportsInput,
} from "../../model-editor/modeled-method";
import { Method } from "../../model-editor/method";
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
import { QueryLanguage } from "../../common/query-language";
import { getModelsAsDataLanguage } from "../../model-editor/languages";
@@ -59,6 +60,10 @@ export const ModelInputDropdown = ({
? modeledMethod.input
: undefined;
if (modeledMethod?.type === "type") {
return <ReadonlyDropdown value={modeledMethod.path} aria-label="Path" />;
}
return (
<Dropdown
value={value}

View File

@@ -6,6 +6,7 @@ import {
modeledMethodSupportsOutput,
} from "../../model-editor/modeled-method";
import { Method } from "../../model-editor/method";
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
import { getModelsAsDataLanguage } from "../../model-editor/languages";
import { QueryLanguage } from "../../common/query-language";
@@ -60,6 +61,15 @@ export const ModelOutputDropdown = ({
? modeledMethod.output
: undefined;
if (modeledMethod?.type === "type") {
return (
<ReadonlyDropdown
value={modeledMethod.relatedTypeName}
aria-label="Related type name"
/>
);
}
return (
<Dropdown
value={value}

View File

@@ -10,6 +10,7 @@ import {
import { Method } from "../../model-editor/method";
import { createEmptyModeledMethod } from "../../model-editor/modeled-method-empty";
import { Mutable } from "../../common/mutable";
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
import { QueryLanguage } from "../../common/query-language";
import { getModelsAsDataLanguage } from "../../model-editor/languages";
@@ -73,6 +74,20 @@ export const ModelTypeDropdown = ({
[onChange, method, modeledMethod, language],
);
const value = modeledMethod?.type ?? "none";
const isShownOption = options.some((option) => option.value === value);
if (!isShownOption) {
return (
<ReadonlyDropdown
// Try to show this like a normal type with uppercased first letter
value={value.charAt(0).toUpperCase() + value.slice(1)}
aria-label="Model type"
/>
);
}
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",