Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/staging' into publish_staging_wi…
Browse files Browse the repository at this point in the history
…thout_passing_tests
  • Loading branch information
mk-software-pl committed Jan 21, 2025
2 parents d6b61eb + d495a91 commit 1d7d2aa
Show file tree
Hide file tree
Showing 24 changed files with 236 additions and 69 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.idea/
.idea/*
!.idea/icon.svg
target/
*.iml
.*orig
Expand Down
1 change: 1 addition & 0 deletions .idea/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ object AssignabilityDeterminer {
def isAssignableStrict(from: TypingResult, to: TypingResult): ValidatedNel[String, Unit] =
isAssignable(from, to, StrictConversionChecker)

def isAssignableWithoutConversion(from: TypingResult, to: TypingResult): ValidatedNel[String, Unit] =
isAssignable(from, to, WithoutConversionChecker)

private def isAssignable(from: TypingResult, to: TypingResult, conversionChecker: ConversionChecker) = {
(from, to) match {
case (_, Unknown) => ().validNel
Expand Down Expand Up @@ -223,6 +226,19 @@ object AssignabilityDeterminer {

}

private object WithoutConversionChecker extends ConversionChecker {

override def isConvertable(
from: SingleTypingResult,
to: TypedClass
): ValidatedNel[String, Unit] = {
val errMsgPrefix =
s"${from.runtimeObjType.display} is not the same as ${to.display}"
condNel(from.withoutValue == to.withoutValue, (), errMsgPrefix)
}

}

private object StrictConversionChecker extends ConversionChecker {

override def isConvertable(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ object typing {
final def canBeStrictlyConvertedTo(typingResult: TypingResult): Boolean =
AssignabilityDeterminer.isAssignableStrict(this, typingResult).isValid

/**
* Checks if the conversion to a given typingResult can be made without any conversion.
*/
final def canBeConvertedWithoutConversionTo(typingResult: TypingResult): Boolean =
AssignabilityDeterminer.isAssignableWithoutConversion(this, typingResult).isValid

def valueOpt: Option[Any]

def withoutValue: TypingResult
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getProcessDefinitionData } from "../../../../reducers/selectors/setting
import { MapVariableProps } from "../MapVariable";
import { NodeCommonDetailsDefinition } from "../NodeCommonDetailsDefinition";
import { FieldsSelect } from "./FieldsSelect";
import { find, head, orderBy } from "lodash";
import { find, head } from "lodash";
import { getDefaultFields } from "./item/utils";
import { FragmentInputParameter } from "./item";

Expand All @@ -26,11 +26,9 @@ export function useFragmentInputDefinitionTypeOptions() {
[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,
typeOptions,
defaultTypeOption,
};
}
Expand All @@ -40,7 +38,7 @@ export default function FragmentInputDefinition(props: Props): JSX.Element {
const { node, setProperty, isEditMode, showValidation } = passProps;

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

const addField = useCallback(() => {
addElement("parameters", getDefaultFields(defaultTypeOption.value));
Expand All @@ -57,7 +55,7 @@ export default function FragmentInputDefinition(props: Props): JSX.Element {
removeField={removeElement}
namespace={"parameters"}
fields={fields}
options={orderedTypeOptions}
options={typeOptions}
showValidation={showValidation}
readOnly={readOnly}
variableTypes={variableTypes}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { PropsWithChildren, useCallback, useMemo } from "react";
import { Button, styled, Typography } from "@mui/material";
import { Button, styled, Tooltip, Typography } from "@mui/material";
import { SearchHighlighter } from "../../creator/SearchHighlighter";
import HttpService from "../../../../http/HttpService";
import { ActionMetadata, ActivityAttachment, ActivityComment, ActivityType } from "../types";
Expand All @@ -20,6 +20,7 @@ import { ActivityItemCommentModify } from "./ActivityItemCommentModify";
import { getLoggedUser } from "../../../../reducers/selectors/settings";
import { getCapabilities } from "../../../../reducers/selectors/other";
import { EventTrackingSelector, getEventTrackingProps } from "../../../../containers/event-tracking";
import CircleIcon from "@mui/icons-material/Circle";

const StyledHeaderIcon = styled(UrlIcon)(({ theme }) => ({
width: "16px",
Expand All @@ -37,15 +38,18 @@ const StyledHeaderActionRoot = styled("div")(({ theme }) => ({
gap: theme.spacing(0.5),
}));

const StyledActivityItemHeader = styled("div")<{ isHighlighted: boolean; isDeploymentActive: boolean; isActiveFound: boolean }>(
({ theme, isHighlighted, isDeploymentActive, isActiveFound }) => ({
display: "flex",
alignItems: "center",
padding: theme.spacing(0.5, 0.5, 0.5, 0.75),
borderRadius: theme.spacing(0.5),
...getHeaderColors(theme, isHighlighted, isDeploymentActive, isActiveFound),
}),
);
const StyledActivityItemHeader = styled("div")<{
isHighlighted: boolean;
isDeploymentActive: boolean;
isActiveFound: boolean;
isVersionSelected: boolean;
}>(({ theme, isHighlighted, isDeploymentActive, isActiveFound, isVersionSelected }) => ({
display: "flex",
alignItems: "center",
padding: theme.spacing(0.5, 0.5, 0.5, 0.75),
borderRadius: theme.spacing(0.5),
...getHeaderColors(theme, isHighlighted, isDeploymentActive, isActiveFound, isVersionSelected),
}));

const HeaderActivity = ({
activityAction,
Expand Down Expand Up @@ -233,30 +237,45 @@ const WithOpenVersion = ({
const ActivityItemHeader = ({ activity, isDeploymentActive, isFound, isActiveFound, searchQuery }: Props) => {
const scenario = useSelector(getScenario);
const { processVersionId } = scenario || {};
const { t } = useTranslation();

const isHighlighted = ["SCENARIO_DEPLOYED", "SCENARIO_CANCELED"].includes(activity.type);
const openVersionEnable =
["SCENARIO_MODIFIED", "SCENARIO_DEPLOYED"].includes(activity.type) && activity.scenarioVersionId !== processVersionId;
const isVersionSelected = ["SCENARIO_MODIFIED"].includes(activity.type) && activity.scenarioVersionId === processVersionId;

const getHeaderTitle = useMemo(() => {
const text = activity.overrideDisplayableName || activity.activities.displayableName;

const activeItemIndicatorText = isDeploymentActive
? t("activityItem.currentlyDeployedVersionText", "Currently deployed version")
: isVersionSelected
? t("activityItem.currentlySelectedVersionText", "Currently selected version")
: undefined;

const headerTitle = (
<Typography
variant={"caption"}
component={SearchHighlighter}
title={text}
highlights={[searchQuery]}
sx={(theme) => ({
color: theme.palette.text.primary,
overflow: "hidden",
textOverflow: "ellipsis",
textWrap: "noWrap",
padding: !openVersionEnable && theme.spacing(0, 1),
})}
>
{text}
</Typography>
<>
<Typography
variant={"caption"}
component={SearchHighlighter}
title={text}
highlights={[searchQuery]}
sx={(theme) => ({
color: theme.palette.text.primary,
overflow: "hidden",
textOverflow: "ellipsis",
textWrap: "noWrap",
padding: !openVersionEnable && theme.spacing(0, 1),
})}
>
{text}
</Typography>
{activeItemIndicatorText && (
<Tooltip title={activeItemIndicatorText}>
<CircleIcon sx={{ fontSize: "10px", mx: openVersionEnable && 1 }} color={"primary"} />
</Tooltip>
)}
</>
);

if (openVersionEnable) {
Expand All @@ -273,13 +292,21 @@ const ActivityItemHeader = ({ activity, isDeploymentActive, isFound, isActiveFou
activity.overrideDisplayableName,
activity.scenarioVersionId,
activity.type,
isDeploymentActive,
isFound,
isVersionSelected,
openVersionEnable,
searchQuery,
t,
]);

return (
<StyledActivityItemHeader isHighlighted={isHighlighted} isDeploymentActive={isDeploymentActive} isActiveFound={isActiveFound}>
<StyledActivityItemHeader
isHighlighted={isHighlighted}
isDeploymentActive={isDeploymentActive}
isActiveFound={isActiveFound}
isVersionSelected={isVersionSelected}
>
<StyledHeaderIcon src={activity.activities.icon} id={activity.uiGeneratedId} />
{getHeaderTitle}
<StyledHeaderActionRoot>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,24 @@ import { getBorderColor } from "../../../../containers/theme/helpers";

const defaultBorder = (theme: Theme) => `0.5px solid ${getBorderColor(theme)}`;
const activeBorder = (theme: Theme) => `0.5px solid ${blend(theme.palette.background.paper, theme.palette.primary.main, 0.4)}`;
const deployedBorder = (theme: Theme) => `0.5px solid ${theme.palette.primary.main}`;
const selectedVersionBorder = (theme: Theme) => `0.5px solid ${theme.palette.primary.main}`;

const runningActiveFoundHeaderBackground = (theme: Theme) => blend(theme.palette.background.paper, theme.palette.primary.main, 0.3);
const highlightedHeaderBackground = (theme: Theme) => blend(theme.palette.background.paper, theme.palette.primary.main, 0.05);
const highlightedActiveFoundHeaderBackground = (theme: Theme) => blend(theme.palette.background.paper, theme.palette.primary.main, 0.2);
const runningHeaderBackground = (theme: Theme) => blend(theme.palette.background.paper, theme.palette.primary.main, 0.2);
const activeFoundItemBackground = (theme: Theme) => blend(theme.palette.background.paper, theme.palette.primary.main, 0.2);
const foundItemBackground = (theme: Theme) => blend(theme.palette.background.paper, theme.palette.primary.main, 0.08);
const selectedVersionHeaderBackground = (theme: Theme) => blend(theme.palette.background.paper, theme.palette.primary.main, 0.2);

export const getHeaderColors = (theme: Theme, isHighlighted: boolean, isDeploymentActive: boolean, isActiveFound: boolean) => {
export const getHeaderColors = (
theme: Theme,
isHighlighted: boolean,
isDeploymentActive: boolean,
isActiveFound: boolean,
isVersionSelected: boolean,
) => {
if (isDeploymentActive && isActiveFound) {
return {
backgroundColor: runningActiveFoundHeaderBackground(theme),
Expand All @@ -30,7 +39,7 @@ export const getHeaderColors = (theme: Theme, isHighlighted: boolean, isDeployme
if (isDeploymentActive) {
return {
backgroundColor: runningHeaderBackground(theme),
border: defaultBorder(theme),
border: deployedBorder(theme),
};
}

Expand All @@ -41,6 +50,13 @@ export const getHeaderColors = (theme: Theme, isHighlighted: boolean, isDeployme
};
}

if (isVersionSelected) {
return {
backgroundColor: selectedVersionHeaderBackground(theme),
border: selectedVersionBorder(theme),
};
}

return {
backgroundColor: undefined,
border: "none",
Expand Down
11 changes: 8 additions & 3 deletions designer/client/src/reducers/selectors/settings.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { createSelector } from "reselect";
import { createSelector, createSelectorCreator, defaultMemoize } from "reselect";
import { MetricsType } from "../../actions/nk";
import { DynamicTabData } from "../../containers/DynamicTab";
import { ComponentGroup, ProcessDefinitionData } from "../../types";
import { RootState } from "../index";
import { AuthenticationSettings, SettingsState } from "../settings";
import { uniqBy } from "lodash";
import { isEqual, uniqBy } from "lodash";

const createDeepEqualSelector = createSelectorCreator(defaultMemoize, isEqual);

export const getSettings = (state: RootState): SettingsState => state.settings;

Expand All @@ -17,7 +19,10 @@ export const getSurveySettings = createSelector(getFeatureSettings, (s) => s?.su
export const getStickyNotesSettings = createSelector(getFeatureSettings, (s) => s?.stickyNotesSettings);
export const getLoggedUser = createSelector(getSettings, (s) => s.loggedUser);
export const getLoggedUserId = createSelector(getLoggedUser, (s) => s.id);
export const getProcessDefinitionData = createSelector(getSettings, (s) => s.processDefinitionData || ({} as ProcessDefinitionData));
export const getProcessDefinitionData = createDeepEqualSelector(
getSettings,
(s) => s.processDefinitionData || ({} as ProcessDefinitionData),
);
export const getComponentGroups = createSelector(getProcessDefinitionData, (p) => p.componentGroups || ({} as ComponentGroup[]));
export const getCategories = createSelector(getLoggedUser, (u) => u.categories || []);
export const getWritableCategories = createSelector(getLoggedUser, getCategories, (user, categories) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import pl.touk.nussknacker.engine.definition.component.{ComponentStaticDefinitio
import pl.touk.nussknacker.engine.util.Implicits.RichScalaMap
import pl.touk.nussknacker.engine.ModelData
import pl.touk.nussknacker.engine.api.TemplateEvaluationResult
import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypingResult}
import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypedClass, TypingResult}
import pl.touk.nussknacker.restmodel.definition._
import pl.touk.nussknacker.ui.definition.DefinitionsService.{
ComponentUiConfigMode,
Expand Down Expand Up @@ -106,7 +106,13 @@ class DefinitionsService(
UIDefinitions(
componentGroups = ComponentGroupsPreparer.prepareComponentGroups(components),
components = components.map(component => component.component.id -> createUIComponentDefinition(component)).toMap,
classes = modelData.modelDefinitionWithClasses.classDefinitions.all.toList.map(_.clazzName),
classes = modelData.modelDefinitionWithClasses.classDefinitions.all.toList
.map(_.clazzName)
.filter {
case t: TypedClass if t.klass.isArray => false
case _ => true
}
.sortBy(_.display.toLowerCase),
scenarioProperties = {
if (forFragment) {
createUIProperties(FragmentPropertiesConfig.properties ++ fragmentPropertiesConfig, fragmentPropertiesDocsUrl)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, OptionValues}
import pl.touk.nussknacker.engine.api.CirceUtil.RichACursor
import pl.touk.nussknacker.engine.api.definition.FixedExpressionValue
import pl.touk.nussknacker.engine.api.parameter.{ParameterName, ValueInputWithFixedValuesProvided}
import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypingResult, Unknown}
import pl.touk.nussknacker.engine.api.{FragmentSpecificData, MetaData}
import pl.touk.nussknacker.engine.canonicalgraph.canonicalnode.FlatNode
import pl.touk.nussknacker.engine.canonicalgraph.{CanonicalProcess, canonicalnode}
Expand Down Expand Up @@ -103,6 +104,20 @@ class DefinitionResourcesSpec
}
}

it("should return definition sorted data for allowed classes - skipping array because list should be uses instead") {
getProcessDefinitionData() ~> check {
status shouldBe StatusCodes.OK

val allowedClasses = responseAs[Json].hcursor.downField("classes").focus.value.asArray.value
val allowedClassesRefClazzNames = allowedClasses.flatMap(_.hcursor.downField("refClazzName").focus.value.asString)
val allowedClassesDisplay = allowedClasses.flatMap(_.hcursor.downField("display").focus.value.asString)

allowedClassesRefClazzNames should contain("java.util.List")
allowedClassesRefClazzNames should not contain (Array().getClass.getName)
allowedClassesDisplay shouldBe allowedClassesDisplay.sortBy(_.toLowerCase)
}
}

it("should return info about editor based on fragment parameter definition") {
val fragmentWithFixedValuesEditor = {
CanonicalProcess(
Expand Down
2 changes: 2 additions & 0 deletions docs/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
* [#7364](https://github.com/TouK/nussknacker/pull/7364) PeriodicDeploymentManger is no longer a separate DM, but instead is an optional functionality and decorator for all DMs
* in order to use it, DM must implement interface `schedulingSupported`, that handles deployments on a specific engine
* implementation provided for Flink DM
* [#7443](https://github.com/TouK/nussknacker/pull/7443) Indexing on record is more similar to indexing on map. The change lets us access record values dynamically. For example now spel expression "{a: 5, b: 10}[#input.field]" compiles and has type "Integer" inferred from types of values of the record. This lets us access record value based on user input, for instance if user passes "{"field": "b"}" to scenario we will get value "10", whereas input {"field": "c"} would result in "null". Expression "{a: 5}["b"]" still does not compile because it is known at compile time that record does not have property "b".
* [#7324](https://github.com/TouK/nussknacker/pull/7324) Fix: Passing Flink Job Global Params

## 1.18

Expand Down
Loading

0 comments on commit 1d7d2aa

Please sign in to comment.