diff --git a/silk-core/src/main/scala/org/silkframework/util/StringUtils.scala b/silk-core/src/main/scala/org/silkframework/util/StringUtils.scala index ff64ad9094..e55d539200 100644 --- a/silk-core/src/main/scala/org/silkframework/util/StringUtils.scala +++ b/silk-core/src/main/scala/org/silkframework/util/StringUtils.scala @@ -16,7 +16,6 @@ package org.silkframework.util import javax.xml.datatype.{DatatypeFactory, XMLGregorianCalendar} import scala.language.implicitConversions -import scala.util.Try import scala.util.matching.Regex object StringUtils { @@ -76,9 +75,15 @@ object StringUtils { object BooleanLiteral { def apply(x: Boolean): String = x.toString - def unapply(x: String): Option[Boolean] = { - Option(x) flatMap { s => - Try(s.toBoolean).toOption + def unapply(s: String): Option[Boolean] = { + if (s != null) { + s.toLowerCase match { + case "true" => Some(true) + case "false" => Some(false) + case _ => None + } + } else { + None } } } diff --git a/silk-plugins/silk-serialization-json/src/main/scala/org/silkframework/serialization/json/JsonHelpers.scala b/silk-plugins/silk-serialization-json/src/main/scala/org/silkframework/serialization/json/JsonHelpers.scala index 0bb15e75e2..3c9a5817b3 100644 --- a/silk-plugins/silk-serialization-json/src/main/scala/org/silkframework/serialization/json/JsonHelpers.scala +++ b/silk-plugins/silk-serialization-json/src/main/scala/org/silkframework/serialization/json/JsonHelpers.scala @@ -87,6 +87,8 @@ object JsonHelpers { optionalValue(json, attributeName) match { case Some(jsBoolean: JsBoolean) => Some(jsBoolean.value) + case Some(jsString: JsString) => + Some(jsString.value.toBoolean) case Some(_) => throw JsonParseException("Value for attribute '" + attributeName + "' is not a boolean!") case None => diff --git a/silk-plugins/silk-serialization-json/src/main/scala/org/silkframework/serialization/json/PluginSerializers.scala b/silk-plugins/silk-serialization-json/src/main/scala/org/silkframework/serialization/json/PluginSerializers.scala index 1edac9773a..0e2d52ab55 100644 --- a/silk-plugins/silk-serialization-json/src/main/scala/org/silkframework/serialization/json/PluginSerializers.scala +++ b/silk-plugins/silk-serialization-json/src/main/scala/org/silkframework/serialization/json/PluginSerializers.scala @@ -3,6 +3,7 @@ package org.silkframework.serialization.json import org.silkframework.runtime.plugin._ import org.silkframework.runtime.serialization.{ReadContext, Serialization, WriteContext} import org.silkframework.serialization.json.JsonSerializers.{PARAMETERS, TEMPLATES, TYPE} +import org.silkframework.util.StringUtils.BooleanLiteral import play.api.libs.json._ import scala.reflect.ClassTag @@ -62,7 +63,12 @@ object PluginSerializers { JsObject( params.values.collect { case (key, ParameterStringValue(strValue)) => - (key, JsString(strValue)) + strValue match { + case BooleanLiteral(booleanValue) => + (key, JsBoolean(booleanValue)) + case _ => + (key, JsString(strValue)) + } case (key, template: ParameterTemplateValue) => (key, JsString(template.evaluate())) case (key, ParameterObjectValue(objValue)) => diff --git a/silk-workbench/silk-workbench-rules/app/controllers/transform/TransformTaskApi.scala b/silk-workbench/silk-workbench-rules/app/controllers/transform/TransformTaskApi.scala index 7a6df89cac..30682a0bfe 100644 --- a/silk-workbench/silk-workbench-rules/app/controllers/transform/TransformTaskApi.scala +++ b/silk-workbench/silk-workbench-rules/app/controllers/transform/TransformTaskApi.scala @@ -425,7 +425,7 @@ class TransformTaskApi @Inject() () extends InjectedController with UserContextA task.synchronized { processRule(task, ruleId) { currentRule => handleValidationExceptions { - implicit val writeContext: WriteContext[JsValue] = WriteContext.fromProject[JsValue](project) + implicit val writeContext: WriteContext[JsValue] = WriteContext.fromProject[JsValue](project).copy(prefixes = Prefixes.empty) implicit val updatedRequest: Request[AnyContent] = updateJsonRequest(request, currentRule) deserializeCompileTime[TransformRule]() { updatedRule => updateRule(currentRule.update(updatedRule)) diff --git a/silk-workbench/silk-workbench-workspace/app/controllers/workspaceApi/search/SearchApiModel.scala b/silk-workbench/silk-workbench-workspace/app/controllers/workspaceApi/search/SearchApiModel.scala index 0f44a5e51d..41a28a6262 100644 --- a/silk-workbench/silk-workbench-workspace/app/controllers/workspaceApi/search/SearchApiModel.scala +++ b/silk-workbench/silk-workbench-workspace/app/controllers/workspaceApi/search/SearchApiModel.scala @@ -3,6 +3,7 @@ package controllers.workspaceApi.search import controllers.util.TextSearchUtils import io.swagger.v3.oas.annotations.media.{ArraySchema, Schema} import org.silkframework.config.{CustomTask, TaskSpec} +import org.silkframework.dataset.DatasetSpec.GenericDatasetSpec import org.silkframework.dataset.{Dataset, DatasetSpec} import org.silkframework.rule.{LinkSpec, TransformSpec} import org.silkframework.runtime.activity.UserContext @@ -35,6 +36,8 @@ object SearchApiModel { final val PLUGIN_LABEL = "pluginLabel" final val TAGS = "tags" final val PARAMETERS = "parameters" + final val READ_ONLY = "readOnly" + final val URI_PROPERTY = "uriProperty" // type values final val PROJECT_TYPE = "project" /* JSON serialization */ @@ -496,6 +499,15 @@ object SearchApiModel { } else { Seq.empty } + val datasetAttributes = { + task.data match { + case ds: GenericDatasetSpec => + Seq(READ_ONLY -> JsBoolean(ds.readOnly)) ++ + ds.uriAttribute.map(uri => URI_PROPERTY -> JsString(uri)) + case _ => + Seq.empty + } + } JsObject( Seq( PROJECT_ID -> JsString(typedTask.project), @@ -510,6 +522,7 @@ object SearchApiModel { ) ++ task.metaData.description.map(d => DESCRIPTION -> JsString(d)) ++ parameters + ++ datasetAttributes ) } } diff --git a/silk-workbench/silk-workbench-workspace/test/controllers/workspace/SearchApiIntegrationTest.scala b/silk-workbench/silk-workbench-workspace/test/controllers/workspace/SearchApiIntegrationTest.scala index 9c69c4942f..4d3312d82b 100644 --- a/silk-workbench/silk-workbench-workspace/test/controllers/workspace/SearchApiIntegrationTest.scala +++ b/silk-workbench/silk-workbench-workspace/test/controllers/workspace/SearchApiIntegrationTest.scala @@ -80,7 +80,7 @@ class SearchApiIntegrationTest extends AnyFlatSpec typeIds mustBe Seq("workflow", "dataset", "transform", "linking", "task") } private def resultAsMap(searchResultObject: JsObject): Map[String, String] = searchResultObject.value - .filter(v => v._1 != "itemLinks" && v._1 != "tags") // Filter out item links and tags, since they are no string + .filter(v => v._2.isInstanceOf[JsString]) // Filter out non-string values .view.mapValues(_.as[String]).toMap it should "return all tasks (pages) for a unrestricted search" in { @@ -335,6 +335,7 @@ class SearchApiIntegrationTest extends AnyFlatSpec val parameters = (resultWithParameters.head \ PARAMETERS).asOpt[JsObject] parameters mustBe defined (parameters.get \ "file").as[String] mustBe "xyz.json" + (parameters.get \ "streaming").as[Boolean] mustBe true } private def resourceNames(defaultResults: IndexedSeq[collection.Map[String, JsValue]]) = { diff --git a/workspace/config/webpack.di.config.js b/workspace/config/webpack.di.config.js index f8fcad4303..4c1a5ab4e7 100644 --- a/workspace/config/webpack.di.config.js +++ b/workspace/config/webpack.di.config.js @@ -4,7 +4,7 @@ const fs = require("fs"); const path = require("path"); const webpack = require("webpack"); const resolve = require("resolve"); -const sass = require('sass'); +const sass = require("sass"); const sassRenderSyncOptions = require("@eccenca/gui-elements/config/sassOptions"); const PnpWebpackPlugin = require("pnp-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); @@ -445,14 +445,16 @@ module.exports = function (webpackEnv, isWatch) { }, { test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/, - use: [{ - loader: 'file-loader', - options: { - name: '[name].[ext]', - outputPath: 'assets/css/fonts/', - publicPath: 'fonts' - } - }] + use: [ + { + loader: "file-loader", + options: { + name: "[name].[ext]", + outputPath: "assets/css/fonts/", + publicPath: "fonts", + }, + }, + ], }, // "file" loader makes sure those assets get served by WebpackDevServer. // When you `import` an asset, you get its (virtual) filename. @@ -583,22 +585,20 @@ module.exports = function (webpackEnv, isWatch) { }), // TypeScript type checking useTypeScript && - new ForkTsCheckerWebpackPlugin( - { - typescript: { - configOverwrite: { - include: [paths.appSrc, ...paths.additionalSourcePaths()] - } - } - } - ), + new ForkTsCheckerWebpackPlugin({ + typescript: { + configOverwrite: { + include: [paths.appSrc, ...paths.additionalSourcePaths()], + }, + }, + }), // isEnvProduction && new BundleAnalyzerPlugin({ // generateStatsFile: true // }), isEnvProduction && new CycloneDxWebpackPlugin({ outputLocation: "./artifacts", - }) + }), ].filter(Boolean), // Some libraries import Node modules but don't use them in the browser. // Tell Webpack to provide empty mocks for them so importing them works. diff --git a/workspace/scripts/i18next-scanner.js b/workspace/scripts/i18next-scanner.js index 05c567c1b6..ee35c27e92 100644 --- a/workspace/scripts/i18next-scanner.js +++ b/workspace/scripts/i18next-scanner.js @@ -111,9 +111,9 @@ function customFlush(done) { const deepMerge = (source, target) => { for (const key of new Set(Object.keys(target).concat(Object.keys(source)))) { if (source[key] !== undefined && target[key] !== undefined) { - if(typeof source[key] === "string" || typeof target[key] === "string") { - console.log(`Found 2 conflicting values for key '${key}'. Selected value: '${source[key]}'`) - target[key] = source[key] + if (typeof source[key] === "string" || typeof target[key] === "string") { + console.log(`Found 2 conflicting values for key '${key}'. Selected value: '${source[key]}'`); + target[key] = source[key]; } else if (typeof source[key] === "object" && typeof target[key] === "object") { Object.assign(target[key], deepMerge(target[key], source[key])); } else { @@ -204,7 +204,7 @@ function validate() { for (const [lang, missingKeys] of languagesWithMissingKeys) { console.warn( `For language '${lang}' ${missingKeys.size} keys do not have a translation value:\n - ` + - [...missingKeys].sort((a, b) => (a < b ? -1 : 1)).join("\n - ") + [...missingKeys].sort((a, b) => (a < b ? -1 : 1)).join("\n - ") ); } process.exit(1); diff --git a/workspace/src/app/store/ducks/common/typings.ts b/workspace/src/app/store/ducks/common/typings.ts index 07d960468f..0c00fd86b6 100644 --- a/workspace/src/app/store/ducks/common/typings.ts +++ b/workspace/src/app/store/ducks/common/typings.ts @@ -121,7 +121,7 @@ export interface IProjectTaskUpdatePayload { taskPluginDetails: IPluginDetails; metaData: IMetadata; currentParameterValues: { - [key: string]: string | object; + [key: string]: string | boolean | object; }; currentTemplateValues: TemplateValueType; dataParameters?: { diff --git a/workspace/src/app/store/ducks/shared/typings.ts b/workspace/src/app/store/ducks/shared/typings.ts index c3b2db80fc..b1b9c67191 100644 --- a/workspace/src/app/store/ducks/shared/typings.ts +++ b/workspace/src/app/store/ducks/shared/typings.ts @@ -53,8 +53,8 @@ export type RuleOperatorType = "AggregationOperator" | "TransformOperator" | "Co export type PluginType = TaskType | RuleOperatorType; export interface IArbitraryPluginParameters { - // If requested with withLabels option, then the values will be reified like this: {label: string, value: string | object} - [key: string]: string | object; + // If requested with withLabels option, then the values will be reified like this: {label: string, value: string | boolean | object} + [key: string]: string | boolean | object; } /** The data of a project task from the generic /tasks endpoint. */ diff --git a/workspace/src/app/utils/transformers.ts b/workspace/src/app/utils/transformers.ts index d7a2ef100f..35c94f0e97 100644 --- a/workspace/src/app/utils/transformers.ts +++ b/workspace/src/app/utils/transformers.ts @@ -13,18 +13,21 @@ export const defaultValueAsJs = (property: IArtefactItemProperty, withLabel: boo return withLabel ? { value, label: optionallyLabelledParameterToLabel(property.value) } : value; }; /** Converts a string value to its typed equivalent based on the given value type. */ -export const stringValueAsJs = (valueType: string, value: OptionallyLabelledParameter | null): any => { +export const stringValueAsJs = ( + valueType: string, + value: OptionallyLabelledParameter | null +): any => { const stringValue = value != null ? optionallyLabelledParameterToValue(value) ?? "" : ""; let v: any = stringValue; - if (valueType === INPUT_TYPES.BOOLEAN) { + if (valueType === INPUT_TYPES.BOOLEAN && typeof v === "string") { // cast to boolean from string - v = stringValue.toLowerCase() === "true"; + v = (stringValue as string).toLowerCase() === "true"; } if (valueType === INPUT_TYPES.INTEGER) { if (v !== "" && stringValue) { - v = parseInt(stringValue); + v = parseInt(stringValue as string); } else { v = null; } diff --git a/workspace/src/app/views/shared/RuleEditor/model/RuleEditorModel.tsx b/workspace/src/app/views/shared/RuleEditor/model/RuleEditorModel.tsx index a534d6b6a3..98b19abb42 100644 --- a/workspace/src/app/views/shared/RuleEditor/model/RuleEditorModel.tsx +++ b/workspace/src/app/views/shared/RuleEditor/model/RuleEditorModel.tsx @@ -1539,7 +1539,14 @@ export const RuleEditorModel = ({ children }: RuleEditorModelProps) => { parameterDiff && parameterDiff.has(parameterId) ? parameterDiff.get(parameterId) : parameterValue; - return [parameterId, typeof value === "string" ? value : value ? value.value : undefined]; + return [ + parameterId, + typeof value === "string" || typeof value === "boolean" + ? value + : value + ? value.value + : undefined, + ]; }) ) : originalNode.parameters, diff --git a/workspace/src/app/views/shared/RuleEditor/model/RuleEditorModel.typings.ts b/workspace/src/app/views/shared/RuleEditor/model/RuleEditorModel.typings.ts index 0b0c5b54d1..e1bfebe1f1 100644 --- a/workspace/src/app/views/shared/RuleEditor/model/RuleEditorModel.typings.ts +++ b/workspace/src/app/views/shared/RuleEditor/model/RuleEditorModel.typings.ts @@ -13,12 +13,14 @@ export interface RuleEditorNode extends Node; } -export type RuleEditorNodeParameterValue = IOperatorNodeParameterValueWithLabel | string | undefined; -export const ruleEditorNodeParameterValue = (value: RuleEditorNodeParameterValue): string | undefined => { - return typeof value === "string" ? value : value?.value; +export type RuleEditorNodeParameterValue = IOperatorNodeParameterValueWithLabel | string | boolean | undefined; +export const ruleEditorNodeParameterValue = (value: RuleEditorNodeParameterValue): string | boolean | undefined => { + return typeof value === "string" || typeof value === "boolean" ? value : value?.value; }; export const ruleEditorNodeParameterLabel = (value: RuleEditorNodeParameterValue): string | undefined => { - return typeof value === "string" ? value : value?.label ?? value?.value; + return typeof value === "string" || typeof value === "boolean" + ? `${value}` + : value?.label ?? (value?.value != null ? `${value.value}` : undefined); }; export type StickyNodePropType = { content?: string; style?: CSSProperties }; diff --git a/workspace/src/app/views/shared/RuleEditor/view/ruleNode/NodeContent.tsx b/workspace/src/app/views/shared/RuleEditor/view/ruleNode/NodeContent.tsx index 5248067a70..11b69fe650 100644 --- a/workspace/src/app/views/shared/RuleEditor/view/ruleNode/NodeContent.tsx +++ b/workspace/src/app/views/shared/RuleEditor/view/ruleNode/NodeContent.tsx @@ -81,9 +81,9 @@ export const NodeContent = ({ const dependentValue = (paramId: string): string | undefined => { const value = operatorContext.currentValue(nodeId, paramId); if ((value as IOperatorNodeParameterValueWithLabel).value != null) { - return (value as IOperatorNodeParameterValueWithLabel).value; + return `${(value as IOperatorNodeParameterValueWithLabel).value}`; } else { - return value as string | undefined; + return value != null ? `${value}` : undefined; } }; return rerender ? null : ( diff --git a/workspace/src/app/views/shared/RuleEditor/view/ruleNode/RuleParameterInput.tsx b/workspace/src/app/views/shared/RuleEditor/view/ruleNode/RuleParameterInput.tsx index 9a7192394c..b0ff5b9ed0 100644 --- a/workspace/src/app/views/shared/RuleEditor/view/ruleNode/RuleParameterInput.tsx +++ b/workspace/src/app/views/shared/RuleEditor/view/ruleNode/RuleParameterInput.tsx @@ -50,14 +50,30 @@ export const RuleParameterInput = ({ const uniqueId = `${nodeId}_${ruleParameter.parameterId}`; const defaultValueWithLabel = ruleParameter.currentValue() ?? ruleParameter.initialValue; const defaultValue = ruleEditorNodeParameterValue(defaultValueWithLabel); - const inputAttributes = { + const _inputAttributes = { id: uniqueId, name: uniqueId, - defaultValue: defaultValue, onChange, intent: hasValidationError ? Intent.DANGER : undefined, readOnly: ruleEditorContext.readOnlyMode || modelContext.readOnly, }; + const stringDefaultValue = + typeof defaultValue === "string" ? defaultValue : defaultValue != null ? `${defaultValue}` : undefined; + const stringValueInputAttributes = { + ..._inputAttributes, + defaultValue: stringDefaultValue, + }; + const booleanDefaultValue = + typeof defaultValue === "boolean" + ? defaultValue + : typeof defaultValue === "string" + ? defaultValue.toLowerCase() === "true" + : undefined; + const booleanValueInputAttributes = { + ..._inputAttributes, + defaultValue: booleanDefaultValue ? "true" : "false", + defaultChecked: booleanDefaultValue, + }; const handleFileSearch = async (input: string) => { try { @@ -77,11 +93,13 @@ export const RuleParameterInput = ({ if (large) { // FIXME: CodeEditor looks buggy in the modal // return ; - return ; + return ( + + ); } else { return ( inputAttributes.onChange(`${value}`)} - defaultChecked={inputAttributes.defaultValue?.toLowerCase() === "true"} - disabled={inputAttributes.readOnly} + {...booleanValueInputAttributes} + onChange={(value: boolean) => booleanValueInputAttributes.onChange(`${value}`)} + disabled={booleanValueInputAttributes.readOnly} /> ); case "code": // FIXME: Add readOnly mode - return ; + return ; case "password": return ( ) => { onChange(e.target.value); @@ -125,11 +142,11 @@ export const RuleParameterInput = ({ itemValueString: resourceNameFn, noResultText: t("common.messages.noResults", "No results."), inputProps: { - readOnly: inputAttributes.readOnly, + readOnly: stringValueInputAttributes.readOnly, }, }} required={ruleParameter.parameterSpecification.required} - {...inputAttributes} + {...stringValueInputAttributes} insideModal={insideModal} /> ); @@ -143,11 +160,11 @@ export const RuleParameterInput = ({ projectId={ruleEditorContext.projectId} paramId={ruleParameter.parameterId} pluginId={pluginId} - onChange={inputAttributes.onChange} + onChange={stringValueInputAttributes.onChange} initialValue={ - defaultValue + stringDefaultValue ? { - value: defaultValue, + value: stringDefaultValue, label: (defaultValueWithLabel as IOperatorNodeParameterValueWithLabel)?.label, } : undefined @@ -157,13 +174,16 @@ export const RuleParameterInput = ({ formParamId={uniqueId} dependentValue={dependentValue} required={ruleParameter.parameterSpecification.required} - readOnly={inputAttributes.readOnly} + readOnly={stringValueInputAttributes.readOnly} hasBackDrop={!insideModal} /> ); } else { return ( - + ); } } diff --git a/workspace/src/app/views/shared/TaskActivityWidget/TaskActivityWidget.tsx b/workspace/src/app/views/shared/TaskActivityWidget/TaskActivityWidget.tsx index 74c6ba5133..bd082ee97e 100644 --- a/workspace/src/app/views/shared/TaskActivityWidget/TaskActivityWidget.tsx +++ b/workspace/src/app/views/shared/TaskActivityWidget/TaskActivityWidget.tsx @@ -37,7 +37,7 @@ interface TaskActivityWidgetProps { */ updateCallback?: (status: IActivityStatus) => void; /** Optional test ID. */ - testId?: string + testId?: string; } /** Task activity widget to show the activity status and start / stop task activities. */ @@ -55,7 +55,7 @@ export const useTaskActivityWidget = ({ activityActionPreAction = {}, updateCallback, isCacheActivity = false, - testId = `activity-control-workflow-editor` + testId = `activity-control-workflow-editor`, }: TaskActivityWidgetProps) => { const [t] = useTranslation(); const { registerError } = useErrorHandler(); diff --git a/workspace/src/app/views/shared/TaskConfig/TaskConfigPreview.tsx b/workspace/src/app/views/shared/TaskConfig/TaskConfigPreview.tsx index cb0e915022..5804c49945 100644 --- a/workspace/src/app/views/shared/TaskConfig/TaskConfigPreview.tsx +++ b/workspace/src/app/views/shared/TaskConfig/TaskConfigPreview.tsx @@ -88,11 +88,15 @@ export function TaskConfigPreview({ taskData, taskDescription }: IProps) { /** Returns the string value if this is an atomic value, else it returns the parameter value object. */ const paramDisplayValue = (parameterValue: any): string | any => { if (typeof parameterValue === "string") { - return t("widget.ConfigWidget.values." + parameterValue, parameterValue); + return parameterValue; + } else if (typeof parameterValue === "boolean") { + return `${parameterValue}`; } else if (typeof parameterValue.label === "string") { - return t("widget.ConfigWidget.values." + parameterValue.label, parameterValue.label); + return parameterValue.label; } else if (typeof parameterValue.value === "string") { - return t("widget.ConfigWidget.values." + parameterValue.value, parameterValue.value); + return parameterValue.value; + } else if (typeof parameterValue.value === "boolean") { + return `${parameterValue.value}`; } else if (parameterValue.value) { // withLabels "object" value return parameterValue.value; diff --git a/workspace/src/app/views/shared/modals/CreateArtefactModal/ArtefactForms/TaskForm.tsx b/workspace/src/app/views/shared/modals/CreateArtefactModal/ArtefactForms/TaskForm.tsx index a00cb22cc1..ccbad53ff7 100644 --- a/workspace/src/app/views/shared/modals/CreateArtefactModal/ArtefactForms/TaskForm.tsx +++ b/workspace/src/app/views/shared/modals/CreateArtefactModal/ArtefactForms/TaskForm.tsx @@ -39,7 +39,7 @@ export interface IProps { export interface UpdateTaskProps { // The existing parameter values parameterValues: { - [key: string]: string | object; + [key: string]: string | boolean | object; }; variableTemplateValues: Record; dataParameters?: { @@ -224,7 +224,9 @@ export function TaskForm({ form, projectId, artefact, updateTask, taskId, detect } if (artefact.taskType === "Dataset") { register({ name: URI_PROPERTY_PARAMETER_ID }); + setValue(URI_PROPERTY_PARAMETER_ID, updateTask?.dataParameters?.uriProperty ?? ""); register({ name: READ_ONLY_PARAMETER }); + setValue(READ_ONLY_PARAMETER, updateTask?.dataParameters?.readOnly ?? false); } registerParameters("", visibleParams, updateTask ? updateTask.parameterValues : {}, requiredRootParameters); setFormValueKeys(returnKeys); diff --git a/workspace/src/app/views/taskViews/linking/LinkingRuleCacheInfo.tsx b/workspace/src/app/views/taskViews/linking/LinkingRuleCacheInfo.tsx index 02109b3080..6ceadefbc2 100644 --- a/workspace/src/app/views/taskViews/linking/LinkingRuleCacheInfo.tsx +++ b/workspace/src/app/views/taskViews/linking/LinkingRuleCacheInfo.tsx @@ -31,7 +31,7 @@ export const LinkingRuleCacheInfo = ({ projectId, taskId }: LinkingRuleCacheInfo taskId: taskId, activityName: "ReferenceEntitiesCache", layoutConfig: { border: true, visualization: "spinner" }, - isCacheActivity: true + isCacheActivity: true, }); const pathCache = useTaskActivityWidget({ diff --git a/workspace/src/app/views/taskViews/linking/LinkingRuleEditor.utils.ts b/workspace/src/app/views/taskViews/linking/LinkingRuleEditor.utils.ts index 2f263108cb..5226256c6a 100644 --- a/workspace/src/app/views/taskViews/linking/LinkingRuleEditor.utils.ts +++ b/workspace/src/app/views/taskViews/linking/LinkingRuleEditor.utils.ts @@ -70,8 +70,11 @@ const extractSimilarityOperatorNode = ( } return t; }; - const reverseParameterValue = () => - operator.parameters[REVERSE_PARAMETER_ID]?.["value"] ?? operator.parameters[REVERSE_PARAMETER_ID]; + const reverseParameterValue = (): string | undefined => { + const value = + operator.parameters[REVERSE_PARAMETER_ID]?.["value"] ?? operator.parameters[REVERSE_PARAMETER_ID]; + return typeof value == "string" ? value : typeof value == "boolean" ? `${value}` : undefined; + }; const inputsCanBeSwitched = isComparison && reverseParameterValue() != null; const switchInputs = inputsCanBeSwitched && reverseParameterValue() === "true"; if (switchInputs) { @@ -185,14 +188,16 @@ const convertRuleOperatorNodeToSimilarityOperator = ( id: ruleOperatorNode.nodeId, indexing: true, // FIXME: Should this be part of the config in the UI? parameters: Object.fromEntries( - Object.entries(ruleOperatorNode.parameters).map(([parameterKey, parameterValue]) => [ - parameterKey, - parameterValue ?? "", - ]) + Object.entries(ruleOperatorNode.parameters) + // Do not send values that were not set, these must have a default value defined. + .filter(([_key, value]) => value != null) + .map(([parameterKey, parameterValue]) => [parameterKey, parameterValue!]) ), type: "Comparison", - threshold: parseFloat(ruleEditorNodeParameterValue(ruleOperatorNode.parameters["threshold"])!!), - weight: parseInt(ruleEditorNodeParameterValue(ruleOperatorNode.parameters["weight"])!!), + threshold: parseFloat( + ruleEditorNodeParameterValue(ruleOperatorNode.parameters["threshold"])!! as string + ), + weight: parseInt(ruleEditorNodeParameterValue(ruleOperatorNode.parameters["weight"])!! as string), }; if (ruleOperatorNode.inputsCanBeSwitched && ruleOperatorNode.inputs[0] != null) { // Set reverse parameter correctly. Either the first input has a target path or the second input has a source path. @@ -232,7 +237,7 @@ const convertRuleOperatorNodeToSimilarityOperator = ( ]) ), type: "Aggregation", - weight: parseInt(ruleEditorNodeParameterValue(ruleOperatorNode.parameters["weight"])!!), + weight: parseInt(ruleEditorNodeParameterValue(ruleOperatorNode.parameters["weight"])!! as string), }; return aggregation; } diff --git a/workspace/src/app/views/taskViews/linking/evaluation/tabView/LinkingEvaluationRow.tsx b/workspace/src/app/views/taskViews/linking/evaluation/tabView/LinkingEvaluationRow.tsx index 7f4517d2ba..de245b59e3 100644 --- a/workspace/src/app/views/taskViews/linking/evaluation/tabView/LinkingEvaluationRow.tsx +++ b/workspace/src/app/views/taskViews/linking/evaluation/tabView/LinkingEvaluationRow.tsx @@ -50,7 +50,7 @@ interface ExpandedEvaluationRowProps { operatorPlugins: Array; evaluationMap?: Map; /** If the row is expanded because of a search match. Only the row values should be shown in that case. */ - expandedBySearch: boolean + expandedBySearch: boolean; } const operatorInputMapping = { @@ -79,7 +79,7 @@ export const LinkingEvaluationRow = React.memo( inputValuesExpandedByDefault, operatorPlugins, evaluationMap, - expandedBySearch + expandedBySearch, }: ExpandedEvaluationRowProps) => { const [treeNodes, setTreeNodes] = React.useState(undefined); const [valueToHighlight, setValueToHighlight] = React.useState(undefined); @@ -113,7 +113,7 @@ export const LinkingEvaluationRow = React.memo( if (inputValuesExpandedByDefault !== inputValueTableExpanded) { setInputValueTableExpanded(inputValuesExpandedByDefault); } - const treeExpanded = operatorTreeExpandedByDefault && (!expandedBySearch || rowIsExpandedByParent) + const treeExpanded = operatorTreeExpandedByDefault && (!expandedBySearch || rowIsExpandedByParent); if (treeExpanded !== operatorTreeExpansion.expanded) { setOperatorTreeExpansion({ ...operatorTreeExpansion, expanded: treeExpanded }); } diff --git a/workspace/src/app/views/taskViews/linking/evaluation/tabView/LinkingEvaluationTabView.tsx b/workspace/src/app/views/taskViews/linking/evaluation/tabView/LinkingEvaluationTabView.tsx index c430f27519..a00eecf1bc 100644 --- a/workspace/src/app/views/taskViews/linking/evaluation/tabView/LinkingEvaluationTabView.tsx +++ b/workspace/src/app/views/taskViews/linking/evaluation/tabView/LinkingEvaluationTabView.tsx @@ -58,7 +58,7 @@ import { AddReferenceLinkModal } from "./modals/AddReferenceLinkModal"; import useErrorHandler from "../../../../../hooks/useErrorHandler"; import { getHistory } from "../../../../../store/configureStore"; import { legacyLinkingEndpoint } from "../../../../../utils/getApiEndpoint"; -import {extractSearchWords, createMultiWordRegex} from "@eccenca/gui-elements/src/components/Typography/Highlighter"; +import { extractSearchWords, createMultiWordRegex } from "@eccenca/gui-elements/src/components/Typography/Highlighter"; interface LinkingEvaluationTabViewProps { projectId: string; @@ -478,8 +478,8 @@ const LinkingEvaluationTabView: React.FC = ({ pro }, []); const handleLinkingTabSwitch = React.useCallback((tabId: number, prevTabId: number) => { - if(prevTabId === tabId) { - return + if (prevTabId === tabId) { + return; } manualLinkChange.current = false; evaluationResults.current = undefined; @@ -758,8 +758,9 @@ const LinkingEvaluationTabView: React.FC = ({ pro {rowData.map((row, rowIdx) => { const result = evaluationResults.current?.links[rowIdx]!; - const resultString = `${result.source} ${result.target}`.toLowerCase() - const expandedBecauseOfStringMatch = !!searchQuery.trim() && !multiWordSearchRegex.test(resultString) + const resultString = `${result.source} ${result.target}`.toLowerCase(); + const expandedBecauseOfStringMatch = + !!searchQuery.trim() && !multiWordSearchRegex.test(resultString); return (