Skip to content

Commit

Permalink
[NU-7164] Add basic parser for fragment input definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
Piotr Rudnicki committed Nov 21, 2024
1 parent 14a9a75 commit 3c31755
Show file tree
Hide file tree
Showing 20 changed files with 149 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { PopoverPosition } from "@mui/material/Popover/Popover";
import i18next from "i18next";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import ValidationLabels from "../../../../../modals/ValidationLabels";
import { useTypeOptions } from "../../../fragment-input-definition/FragmentInputDefinition";
import { EditorProps, ExtendedEditor } from "../Editor";
import "@glideapps/glide-data-grid/dist/index.css";
import { CellMenu, DeleteColumnMenuItem, DeleteRowMenuItem, ResetColumnWidthMenuItem } from "./CellMenu";
Expand All @@ -31,6 +30,10 @@ import { TypesMenu } from "./TypesMenu";
import { customRenderers } from "./customRenderers";
import { isDatePickerCell } from "./customCells";
import type { GetRowThemeCallback } from "@glideapps/glide-data-grid/src/internal/data-grid/render/data-grid-render.cells";
import { useSelector } from "react-redux";
import { getProcessDefinitionData } from "../../../../../../reducers/selectors/settings";
import ProcessUtils from "../../../../../../common/ProcessUtils";
import { find, head, orderBy } from "lodash";

const SUPPORTED_TYPES = [
"java.lang.String",
Expand Down Expand Up @@ -87,6 +90,27 @@ const emptySelection = {
rows: CompactSelection.empty(),
};

export function useTableEditorTypeOptions() {
const definitionData = useSelector(getProcessDefinitionData);

const typeOptions = useMemo(
() =>
definitionData?.classes?.map((type) => ({
value: type.refClazzName as SupportedType,
label: ProcessUtils.humanReadableType(type),
})),
[definitionData?.classes],
);

const orderedTypeOptions = useMemo(() => orderBy(typeOptions, (item) => [item.label, item.value], ["asc"]), [typeOptions]);

const defaultTypeOption = useMemo(() => find(typeOptions, { label: "String" }) || head(typeOptions), [typeOptions]);
return {
orderedTypeOptions,
defaultTypeOption,
};
}

export const Table = ({ expressionObj, onValueChange, className, fieldErrors }: EditorProps) => {
const tableDateContext = useTableState(expressionObj);
const [{ rows, columns }, dispatch, rawExpression] = tableDateContext;
Expand All @@ -97,7 +121,7 @@ export const Table = ({ expressionObj, onValueChange, className, fieldErrors }:
}
}, [expressionObj.expression, onValueChange, rawExpression]);

const { defaultTypeOption, orderedTypeOptions } = useTypeOptions<SupportedType>();
const { defaultTypeOption, orderedTypeOptions } = useTableEditorTypeOptions();
const supportedTypes = useMemo(() => orderedTypeOptions.filter(({ value }) => SUPPORTED_TYPES.includes(value)), [orderedTypeOptions]);

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ interface Props extends Omit<MapVariableProps<FragmentInputParameter>, "readOnly
isEditMode?: boolean;
}

export function useTypeOptions<Value = string>() {
export function useFragmentInputDefinitionTypeOptions() {
const definitionData = useSelector(getProcessDefinitionData);

const typeOptions = useMemo(
() =>
definitionData?.classes?.map((type) => ({
// TODO: Instead of using type assertion type, set refClazzName as a union of available clazzNames
value: type.refClazzName as Value,
value: type.display as string,
label: ProcessUtils.humanReadableType(type),
})),
[definitionData?.classes],
Expand All @@ -40,7 +40,7 @@ export default function FragmentInputDefinition(props: Props): JSX.Element {
const { node, setProperty, isEditMode, showValidation } = passProps;

const readOnly = !isEditMode;
const { orderedTypeOptions, defaultTypeOption } = useTypeOptions();
const { orderedTypeOptions, defaultTypeOption } = useFragmentInputDefinitionTypeOptions();

const addField = useCallback(() => {
addElement("parameters", getDefaultFields(defaultTypeOption.value));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ object AlignedComponentsDefinitionProvider {
new BuiltInComponentsDefinitionsPreparer(designerModelData.modelData.componentsUiConfig),
new FragmentComponentDefinitionExtractor(
designerModelData.modelData.modelClassLoader.classLoader,
designerModelData.modelData.modelDefinitionWithClasses.classDefinitions.all,
designerModelData.modelData.componentsUiConfig.groupName,
designerModelData.modelData.determineDesignerWideId
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ class DefinitionsServiceSpec extends AnyFunSuite with Matchers with PatientScala
new BuiltInComponentsDefinitionsPreparer(ComponentsUiConfigParser.parse(model.modelConfig)),
new FragmentComponentDefinitionExtractor(
getClass.getClassLoader,
model.modelDefinitionWithClasses.classDefinitions.all,
Some(_),
DesignerWideComponentId.default(processingType.stringify, _)
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class EdgeTypesPreparerTest extends AnyFunSuite with Matchers with ValidatedValu
test("return edge types for fragment, filters, switches and components with multiple inputs") {
val sampleFragmentDef = new FragmentComponentDefinitionExtractor(
getClass.getClassLoader,
Set.empty,
Some(_),
DesignerWideComponentId.default(Streaming.stringify, _)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ class ComponentGroupsPreparerSpec
new BuiltInComponentsDefinitionsPreparer(new ComponentsUiConfig(Map.empty, groupNameMapping)),
new FragmentComponentDefinitionExtractor(
getClass.getClassLoader,
Set.empty,
Some(_),
DesignerWideComponentId.default("Streaming", _)
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class ComponentsUsageHelperTest extends AnyFunSuite with Matchers with TableDriv

val alignedComponentsDefinitionProvider = new AlignedComponentsDefinitionProvider(
new BuiltInComponentsDefinitionsPreparer(new ComponentsUiConfig(Map.empty, Map.empty)),
new FragmentComponentDefinitionExtractor(getClass.getClassLoader, Some(_), determineDesignerWideId),
new FragmentComponentDefinitionExtractor(getClass.getClassLoader, Set.empty, Some(_), determineDesignerWideId),
modelDefinition,
ProcessingMode.UnboundedStream
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import pl.touk.nussknacker.engine.api.process.{ComponentUseCase, ProcessConfigCr
import pl.touk.nussknacker.engine.api.{JobData, MetaData, ProcessListener, ProcessVersion}
import pl.touk.nussknacker.engine.compile._
import pl.touk.nussknacker.engine.compile.nodecompilation.LazyParameterCreationStrategy
import pl.touk.nussknacker.engine.definition.clazz.ClassDefinitionSet
import pl.touk.nussknacker.engine.definition.clazz.{ClassDefinition, ClassDefinitionSet}
import pl.touk.nussknacker.engine.definition.globalvariables.ExpressionConfigDefinition
import pl.touk.nussknacker.engine.definition.model.{ModelDefinition, ModelDefinitionWithClasses}
import pl.touk.nussknacker.engine.dict.DictServicesFactoryLoader
Expand Down Expand Up @@ -137,13 +137,18 @@ class FlinkProcessCompilerDataFactory(
modelDefinitionWithTypes.modelDefinition.expressionConfig,
modelDefinitionWithTypes.classDefinitions
)
val adjustedDefinitions = adjustDefinitions(modelDefinitionWithTypes.modelDefinition, definitionContext)
val adjustedDefinitions = adjustDefinitions(
modelDefinitionWithTypes.modelDefinition,
definitionContext,
modelDefinitionWithTypes.classDefinitions.all
)
(ModelDefinitionWithClasses(adjustedDefinitions), dictRegistry)
}

protected def adjustDefinitions(
originalModelDefinition: ModelDefinition,
definitionContext: ComponentDefinitionContext
definitionContext: ComponentDefinitionContext,
classDefinitions: Set[ClassDefinition]
): ModelDefinition = originalModelDefinition

private def loadDictRegistry(userCodeClassLoader: ClassLoader) = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import pl.touk.nussknacker.engine.api.process.{ComponentUseCase, ProcessConfigCr
import pl.touk.nussknacker.engine.api.typed.ReturningType
import pl.touk.nussknacker.engine.api.typed.typing.{TypingResult, Unknown}
import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess
import pl.touk.nussknacker.engine.definition.clazz.ClassDefinition
import pl.touk.nussknacker.engine.definition.component.dynamic.DynamicComponentDefinitionWithImplementation
import pl.touk.nussknacker.engine.definition.component.methodbased.MethodBasedComponentDefinitionWithImplementation
import pl.touk.nussknacker.engine.definition.component.{
Expand Down Expand Up @@ -42,7 +43,8 @@ abstract class StubbedFlinkProcessCompilerDataFactory(

override protected def adjustDefinitions(
originalModelDefinition: ModelDefinition,
definitionContext: ComponentDefinitionContext
definitionContext: ComponentDefinitionContext,
classDefinitions: Set[ClassDefinition]
): ModelDefinition = {
val usedSourceIds = process.allStartNodes
.map(_.head.data)
Expand All @@ -61,7 +63,8 @@ abstract class StubbedFlinkProcessCompilerDataFactory(
}

val fragmentParametersDefinitionExtractor = new FragmentParametersDefinitionExtractor(
definitionContext.userCodeClassLoader
definitionContext.userCodeClassLoader,
classDefinitions
)
val fragmentSourceDefinitionPreparer = new StubbedFragmentSourceDefinitionPreparer(
fragmentParametersDefinitionExtractor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ object ProcessValidator {

val nodeCompiler = new NodeCompiler(
modelDefinition,
new FragmentParametersDefinitionExtractor(classLoader),
new FragmentParametersDefinitionExtractor(classLoader, definitionWithTypes.classDefinitions.all),
expressionCompiler,
classLoader,
Seq.empty,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ object ProcessCompilerData {
// for testing environment it's important to take classloader from user jar
val nodeCompiler = new NodeCompiler(
definitionWithTypes.modelDefinition,
new FragmentParametersDefinitionExtractor(userCodeClassLoader),
new FragmentParametersDefinitionExtractor(userCodeClassLoader, definitionWithTypes.classDefinitions.all),
expressionCompiler,
userCodeClassLoader,
listeners,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pl.touk.nussknacker.engine.compile.nodecompilation
import cats.data.Validated.{Valid, invalid, invalidNel, valid}
import cats.data.{NonEmptyList, Validated, ValidatedNel}
import cats.implicits.toTraverseOps
import org.apache.commons.lang3.ClassUtils
import pl.touk.nussknacker.engine.api.NodeId
import pl.touk.nussknacker.engine.api.context.ProcessCompilationError._
import pl.touk.nussknacker.engine.api.context.{PartSubGraphCompilationError, ProcessCompilationError, ValidationContext}
Expand All @@ -14,9 +15,12 @@ import pl.touk.nussknacker.engine.api.parameter.{
ValueInputWithDictEditor,
ValueInputWithFixedValuesProvided
}
import pl.touk.nussknacker.engine.api.typed.typing.{Typed, Unknown}
import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypingResult, Unknown}
import pl.touk.nussknacker.engine.api.validation.Validations.validateVariableName
import pl.touk.nussknacker.engine.compile.ExpressionCompiler
import pl.touk.nussknacker.engine.compile.nodecompilation.FragmentParameterValidator.permittedTypesForEditors
import pl.touk.nussknacker.engine.definition.clazz.ClassDefinition
import pl.touk.nussknacker.engine.definition.fragment.FragmentParameterTypingParser
import pl.touk.nussknacker.engine.graph.expression.Expression
import pl.touk.nussknacker.engine.graph.expression.Expression.Language
import pl.touk.nussknacker.engine.graph.node.FragmentInputDefinition.{FragmentClazzRef, FragmentParameter}
Expand All @@ -29,6 +33,8 @@ import pl.touk.nussknacker.engine.graph.node.{
}
import pl.touk.nussknacker.engine.language.dictWithLabel.DictKeyWithLabelExpressionParser

import scala.util.Try

object FragmentParameterValidator {

val permittedTypesForEditors: List[FragmentClazzRef] = List(
Expand All @@ -37,6 +43,10 @@ object FragmentParameterValidator {
FragmentClazzRef[java.lang.Long]
)

}

case class FragmentParameterValidator(classDefinitions: Set[ClassDefinition] = Set.empty) {

// This method doesn't fully validate valueEditor (see ValueEditorValidator.validateAndGetEditor comments)
def validateAgainstClazzRefAndGetEditor(
valueEditor: ParameterValueInput,
Expand Down Expand Up @@ -147,9 +157,11 @@ object FragmentParameterValidator {
validateNonEmptyDictId(dictId, fragmentParameter.name).andThen(_ =>
dictionaries.get(dictId) match {
case Some(dictDefinition) =>
val fragmentParameterTypingResult = fragmentParameter.typ
.toRuntimeClass(classLoader)
.map(Typed(_))
val fragmentParameterTypingParser = new FragmentParameterTypingParser(classLoader, classDefinitions)
val fragmentParameterTypingResult = fragmentParameterTypingParser
.parseClassNameToTypingResult(
fragmentParameter.typ.refClazzName
)
.getOrElse(Unknown)

val dictValueType = dictDefinition.valueType(dictId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import pl.touk.nussknacker.engine.api.parameter.ParameterName
import pl.touk.nussknacker.engine.api.process.{ComponentUseCase, Source}
import pl.touk.nussknacker.engine.api.typed.ReturningType
import pl.touk.nussknacker.engine.api.typed.typing.{TypingResult, Unknown}
import pl.touk.nussknacker.engine.compile.nodecompilation.FragmentParameterValidator.validateParameterNames
import pl.touk.nussknacker.engine.compile.nodecompilation.NodeCompiler.NodeCompilationResult
import pl.touk.nussknacker.engine.compile.{
ComponentExecutorFactory,
Expand Down Expand Up @@ -100,6 +99,8 @@ class NodeCompiler(
new DynamicNodeValidator(expressionCompiler, globalVariablesPreparer, parametersEvaluator)
private val builtInNodeCompiler = new BuiltInNodeCompiler(expressionCompiler)

private val fragmentParameterValidator = FragmentParameterValidator(fragmentDefinitionExtractor.classDefinitions)

def compileSource(
nodeData: SourceNodeData
)(implicit jobData: JobData, nodeId: NodeId): NodeCompilationResult[Source] = nodeData match {
Expand Down Expand Up @@ -165,7 +166,7 @@ class NodeCompiler(
)
}

val parameterNameValidation = validateParameterNames(parameterDefinitions.value)
val parameterNameValidation = fragmentParameterValidator.validateParameterNames(parameterDefinitions.value)

// by relying on name for the field names used on FE, we display the same errors under all fields with the
// duplicated name
Expand Down Expand Up @@ -195,7 +196,7 @@ class NodeCompiler(

val fixedValuesErrors = fragmentInputDefinition.parameters
.map { param =>
FragmentParameterValidator.validateFixedExpressionValues(
fragmentParameterValidator.validateFixedExpressionValues(
param,
validationContext,
expressionCompiler
Expand All @@ -206,7 +207,7 @@ class NodeCompiler(

val dictValueEditorErrors = fragmentInputDefinition.parameters
.map { param =>
FragmentParameterValidator.validateValueInputWithDictEditor(param, expressionConfig.dictionaries, classLoader)
fragmentParameterValidator.validateValueInputWithDictEditor(param, expressionConfig.dictionaries, classLoader)
}
.sequence
.map(_ => ())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ class NodeDataValidator(modelData: ModelData) {

private val compiler = new NodeCompiler(
modelData.modelDefinition,
new FragmentParametersDefinitionExtractor(modelData.modelClassLoader.classLoader),
new FragmentParametersDefinitionExtractor(
modelData.modelClassLoader.classLoader,
modelData.modelDefinitionWithClasses.classDefinitions.all
),
expressionCompiler,
modelData.modelClassLoader.classLoader,
Seq.empty,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import pl.touk.nussknacker.engine.api.component.{
}
import pl.touk.nussknacker.engine.api.{FragmentSpecificData, NodeId}
import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess
import pl.touk.nussknacker.engine.definition.clazz.ClassDefinition
import pl.touk.nussknacker.engine.definition.component.{
ComponentDefinitionWithImplementation,
ComponentImplementationInvoker
Expand All @@ -18,11 +19,12 @@ import pl.touk.nussknacker.engine.util.MetaDataExtractor

class FragmentComponentDefinitionExtractor(
classLoader: ClassLoader,
classDefinitions: Set[ClassDefinition],
translateGroupName: ComponentGroupName => Option[ComponentGroupName],
determineDesignerWideId: ComponentId => DesignerWideComponentId
) {

val parametersExtractor = new FragmentParametersDefinitionExtractor(classLoader)
val parametersExtractor = new FragmentParametersDefinitionExtractor(classLoader, classDefinitions)

def extractFragmentComponentDefinition(
fragment: CanonicalProcess,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package pl.touk.nussknacker.engine.definition.fragment

import org.apache.commons.lang3.ClassUtils
import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypingResult}
import pl.touk.nussknacker.engine.definition.clazz.ClassDefinition

import scala.util.Try

class FragmentParameterTypingParser(classLoader: ClassLoader, classDefinitions: Set[ClassDefinition]) {

def parseClassNameToTypingResult(className: String): Try[TypingResult] = {
/*
TODO: Write this parser in a way that handles arbitrary depth expressions
One should not use regexes for doing so and rather build AST
*/
def resolveInnerClass(simpleClassName: String): TypingResult =
classDefinitions
.find(classDefinition => classDefinition.clazzName.display == simpleClassName)
.fold(
// This is fallback - it may be removed and `ClassNotFound` exception may be thrown here after cleaning up the mess with `FragmentClazzRef` class
Typed(ClassUtils.getClass(classLoader, simpleClassName))
) { classDefinition =>
classDefinition.clazzName
}

val mapPattern = "Map\\[(.+),(.+)\\]".r
val listPattern = "List\\[(.+)\\]".r
val setPattern = "Set\\[(.+)\\]".r

Try(className match {
case mapPattern(x, y) =>
val resolvedFirstTypeParam = resolveInnerClass(x)
val resolvedSecondTypeParam = resolveInnerClass(y)
Typed.genericTypeClass[java.util.Map[_, _]](List(resolvedFirstTypeParam, resolvedSecondTypeParam))
case listPattern(x) =>
val resolvedTypeParam = resolveInnerClass(x)
Typed.genericTypeClass[java.util.List[_]](List(resolvedTypeParam))
case setPattern(x) =>
val resolvedTypeParam = resolveInnerClass(x)
Typed.genericTypeClass[java.util.Set[_]](List(resolvedTypeParam))
case simpleClassName => resolveInnerClass(simpleClassName)
})
}

}
Loading

0 comments on commit 3c31755

Please sign in to comment.