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

Return boolean values for boolean parameters in task and search API #680

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from
Open
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 @@ -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 {
Expand Down Expand Up @@ -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
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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),
Expand All @@ -510,6 +522,7 @@ object SearchApiModel {
)
++ task.metaData.description.map(d => DESCRIPTION -> JsString(d))
++ parameters
++ datasetAttributes
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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]]) = {
Expand Down
38 changes: 19 additions & 19 deletions workspace/config/webpack.di.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions workspace/scripts/i18next-scanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion workspace/src/app/store/ducks/common/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export interface IProjectTaskUpdatePayload {
taskPluginDetails: IPluginDetails;
metaData: IMetadata;
currentParameterValues: {
[key: string]: string | object;
[key: string]: string | boolean | object;
};
currentTemplateValues: TemplateValueType;
dataParameters?: {
Expand Down
4 changes: 2 additions & 2 deletions workspace/src/app/store/ducks/shared/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
11 changes: 7 additions & 4 deletions workspace/src/app/utils/transformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> | null): any => {
export const stringValueAsJs = (
valueType: string,
value: OptionallyLabelledParameter<string | boolean> | 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ export interface RuleEditorNode extends Node<NodeContentPropsWithBusinessData<IR
data: NodeContentPropsWithBusinessData<IRuleNodeData>;
}

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 };

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 : (
Expand Down
Loading