Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NU-7164] Add basic parser for fragment input definitions #7167

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
};
}
DeamonDev marked this conversation as resolved.
Show resolved Hide resolved

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();
DeamonDev marked this conversation as resolved.
Show resolved Hide resolved
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]
DeamonDev marked this conversation as resolved.
Show resolved Hide resolved
): 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]
DeamonDev marked this conversation as resolved.
Show resolved Hide resolved
): 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
)
DeamonDev marked this conversation as resolved.
Show resolved Hide resolved
.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
DeamonDev marked this conversation as resolved.
Show resolved Hide resolved

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
Loading