From ce88e5732a34bac887fadc883f569ad90542398f Mon Sep 17 00:00:00 2001 From: JulianWielga Date: Mon, 18 Mar 2024 09:12:35 +0100 Subject: [PATCH 1/4] Minor table editor fix (#5737) * fixed minor table editor error tooltip error * hide selection highlight on blur * Updated snapshots (#5738) Co-authored-by: JulianWielga --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: JulianWielga --- ...or should display rich table editor #1.png | Bin 8916 -> 8923 bytes .../editors/expression/Table/TableEditor.tsx | 162 ++++++++++-------- .../expression/Table/errorHighlights.tsx | 11 +- 3 files changed, 92 insertions(+), 81 deletions(-) diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Table editor should display rich table editor #1.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Table editor should display rich table editor #1.png index 6af95639c9a9cf05219d3520da1616500da550e2..afee3ef83b91926eac238c8622245b5badd8a18f 100644 GIT binary patch literal 8923 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 E0r`a7p8x;= literal 8916 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 RC%%TFen?B@ void }) => { ); }; +const emptySelection = { + columns: CompactSelection.empty(), + rows: CompactSelection.empty(), +}; + export const Table = ({ expressionObj, onValueChange, className, fieldErrors }: EditorProps) => { const tableDateContext = useTableState(expressionObj); const [{ rows, columns }, dispatch, rawExpression] = tableDateContext; @@ -155,10 +160,7 @@ export const Table = ({ expressionObj, onValueChange, className, fieldErrors }: [dispatch], ); - const [selection, setSelection] = useState({ - columns: CompactSelection.empty(), - rows: CompactSelection.empty(), - }); + const [selection, setSelection] = useState(emptySelection); const pasteWithExpand: DataEditorProps["onPaste"] = useCallback<(target: Item, values: readonly (readonly string[])[]) => boolean>( ([column, row], input) => { @@ -352,6 +354,7 @@ export const Table = ({ expressionObj, onValueChange, className, fieldErrors }: const rightElement = useMemo(() => , [onColumnAppend]); + const [hasFocus, setHasFocus] = useState(false); return ( <> setHasFocus(true)} + onBlur={(e) => { + if (e.currentTarget.contains(e.relatedTarget)) { + return; + } + setHasFocus(false); + }} > ({ @@ -387,7 +397,7 @@ export const Table = ({ expressionObj, onValueChange, className, fieldErrors }: theme={tableTheme} width="100%" trailingRowOptions={trailingRowOptions} - gridSelection={selection} + gridSelection={hasFocus ? selection : emptySelection} onGridSelectionChange={(selection) => { setSelection(selection); toggleTooltip(selection); @@ -402,67 +412,67 @@ export const Table = ({ expressionObj, onValueChange, className, fieldErrors }: onItemHovered={toggleTooltip} drawCell={drawCell} /> + + + {cellMenuData?.column >= 0 && cellMenuData?.row < 0 ? ( + 0 ? selection.columns.toArray() : [cellMenuData?.column]} + onClick={(indexes) => { + dispatch({ + type: ActionTypes.resetColumnsSize, + columns: indexes, + }); + closeCellMenu(); + }} + /> + ) : null} + {cellMenuData?.column >= 0 ? ( + 0 + ? selection.columns.toArray() + : selection.current?.range + ? Array.from({ length: selection.current.range.width }, (_, i) => selection.current.range.x + i) + : [cellMenuData?.column] + } + onClick={(indexes) => { + dispatch({ + type: ActionTypes.deleteColumns, + columns: indexes, + }); + clearSelection(); + closeCellMenu(); + }} + /> + ) : null} + {cellMenuData?.row >= 0 ? ( + 0 + ? selection.rows.toArray() + : selection.current?.range + ? Array.from({ length: selection.current.range.height }, (_, i) => selection.current.range.y + i) + : [cellMenuData?.row] + } + onClick={(indexes) => { + dispatch({ + type: ActionTypes.deleteRows, + rows: indexes, + }); + clearSelection(); + closeCellMenu(); + }} + /> + ) : null} + {tooltipElement} - - - {cellMenuData?.column >= 0 && cellMenuData?.row < 0 ? ( - 0 ? selection.columns.toArray() : [cellMenuData?.column]} - onClick={(indexes) => { - dispatch({ - type: ActionTypes.resetColumnsSize, - columns: indexes, - }); - closeCellMenu(); - }} - /> - ) : null} - {cellMenuData?.column >= 0 ? ( - 0 - ? selection.columns.toArray() - : selection.current?.range - ? Array.from({ length: selection.current.range.width }, (_, i) => selection.current.range.x + i) - : [cellMenuData?.column] - } - onClick={(indexes) => { - dispatch({ - type: ActionTypes.deleteColumns, - columns: indexes, - }); - clearSelection(); - closeCellMenu(); - }} - /> - ) : null} - {cellMenuData?.row >= 0 ? ( - 0 - ? selection.rows.toArray() - : selection.current?.range - ? Array.from({ length: selection.current.range.height }, (_, i) => selection.current.range.y + i) - : [cellMenuData?.row] - } - onClick={(indexes) => { - dispatch({ - type: ActionTypes.deleteRows, - rows: indexes, - }); - clearSelection(); - closeCellMenu(); - }} - /> - ) : null} - ); }; diff --git a/designer/client/src/components/graph/node-modal/editors/expression/Table/errorHighlights.tsx b/designer/client/src/components/graph/node-modal/editors/expression/Table/errorHighlights.tsx index 473314832fa..952e646e1ff 100644 --- a/designer/client/src/components/graph/node-modal/editors/expression/Table/errorHighlights.tsx +++ b/designer/client/src/components/graph/node-modal/editors/expression/Table/errorHighlights.tsx @@ -69,12 +69,13 @@ export function useErrorHighlights(fieldErrors: FieldError[], columns: DataColum ); const toggleTooltip = useCallback( - (selection: GridMouseEventArgs | GridSelection) => { - const { cell, rect } = getSelectedCell(selection); - const error = getErrorForCell(cell); + (selection?: GridMouseEventArgs | GridSelection) => { + const selected = getSelectedCell(selection); + const error = getErrorForCell(selected?.cell); + setTooltipOpen(() => { - if (rect && error) { - positionRef.current = rect; + if (selected?.rect && error) { + positionRef.current = selected.rect; setTooltipMessage(error.errorMessage); return true; } else { From 0cdaf504a08a5d0fbb6455ea9f81d4510d25dfd3 Mon Sep 17 00:00:00 2001 From: Arek Burdach Date: Mon, 18 Mar 2024 13:26:16 +0100 Subject: [PATCH 2/4] [NU-1071] Processing mode related changes in the documentation (#5747) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --------- Co-authored-by: Mateusz Słabek --- docs/developers_guide/Basics.md | 2 +- docs/installation/Installation.md | 4 +- .../Common.md | 38 ++++++----- .../DesignerConfiguration.md | 4 +- ....md => ScenarioDeploymentConfiguration.md} | 60 ++++++++++-------- .../img/configuration_areas.png | Bin 57953 -> 27763 bytes .../model/ModelConfiguration.md | 2 +- docs/integration/OpenAPI.md | 2 +- docs/operations_guide/Lite.md | 2 +- 9 files changed, 61 insertions(+), 53 deletions(-) rename docs/installation_configuration_guide/{DeploymentManagerConfiguration.md => ScenarioDeploymentConfiguration.md} (77%) diff --git a/docs/developers_guide/Basics.md b/docs/developers_guide/Basics.md index fc8e8eb8b4e..c6869490f8b 100644 --- a/docs/developers_guide/Basics.md +++ b/docs/developers_guide/Basics.md @@ -34,7 +34,7 @@ To read more see [ComponentProvider API](./Components.md) The Designer uses [DeploymentManager](https://github.com/TouK/nussknacker/blob/staging/designer/deployment-manager-api/src/main/scala/pl/touk/nussknacker/engine/api/deployment/DeploymentManager.scala) interface to perform actions on scenarios (deploy / cancel / etc.). All providers that are available in distribution deployment are located in `managers` directory and are added to the Designer classpath. If you want to implement own `DeploymentManager`, you should implement this interface, package it, add to classpath and configure scenario type to use it. More info you can find on -[DeploymentManagerConfiguration page](../installation_configuration_guide/DeploymentManagerConfiguration.md) +[Scenario Deployment Configuration page](../installation_configuration_guide/ScenarioDeploymentConfiguration.md) ## Other SPIs for Nussknacker customization (documentation will follow soon...) diff --git a/docs/installation/Installation.md b/docs/installation/Installation.md index 4ff4f505c0a..ed20c62b812 100644 --- a/docs/installation/Installation.md +++ b/docs/installation/Installation.md @@ -31,7 +31,7 @@ If you want to see Nussknacker in action without Kafka, using embedded Request-R ```bash docker run -it -p 8080:8080 -p 8181:8181 touk/nussknacker:latest ``` -After it started go to http://localhost:8080 and login using credentials: admin/admin. +After it started go to [http://localhost:8080](http://localhost:8080) and login using credentials: admin/admin. REST endpoints of deployed scenarios will be exposed at `http://localhost:8181/scenario/`. Slug is defined in Properties, and by default it is scenario name. More information you can find at [Docker Hub](https://hub.docker.com/r/touk/nussknacker/) @@ -258,7 +258,7 @@ If you want to install them from the scratch or use already installed at your or - Telegraf's configuration - some metric tags and names need to be cleaned - Importing scenario dashboard to Grafana configuration - Flink savepoint configuration. To be able to use scenario verification - (see `shouldVerifyBeforeDeploy` property in [Deployment Manager documentation](../installation_configuration_guide/DeploymentManagerConfiguration.md)) + (see `shouldVerifyBeforeDeploy` property in [scenario deployment configuration](../installation_configuration_guide/ScenarioDeploymentConfiguration.md)) you have to make sure that savepoint location is available from Nussknacker designer (e.g. via NFS like in quickstart setup) diff --git a/docs/installation_configuration_guide/Common.md b/docs/installation_configuration_guide/Common.md index ab64897a8c1..1caf605d6de 100644 --- a/docs/installation_configuration_guide/Common.md +++ b/docs/installation_configuration_guide/Common.md @@ -19,11 +19,15 @@ Nussknacker configuration is divided into several configuration areas, each area * [Designer](/about/GLOSSARY#nussknacker-designer) configuration (web application ports, security, various UI settings, database), * Scenario Types configuration, comprising of: - * [Deployment Manager](/about/GLOSSARY#deployment-manager) configuration, * [Model](/about/GLOSSARY#model) configuration. - * [Category](/installation_configuration_guide/DesignerConfiguration/#scenario-type-categories) configuration + * [Scenario Deployment](./ScenarioDeploymentConfiguration.md) configuration, + * [Category](./DesignerConfiguration.md/#scenario-type-categories) configuration -The Scenario Type is a convenient umbrella term for a particular Deployment Manager configuration and the associated model configuration. Diagram below presents main relationships between configuration areas. +[Model](/about/GLOSSARY#model) configuration defines which components and which [Processing Mode](/about/ProcessingModes) will be available for the user. +[Scenario Deployment](./ScenarioDeploymentConfiguration.md) configuration defines how scenario using these components will be deployed on the [Engine](/about/engine). +[Category](./DesignerConfiguration.md/#scenario-type-categories) defines who has access to the given combination of [Model](/about/GLOSSARY#model) and [Scenario Deployment](./ScenarioDeploymentConfiguration.md). + +The Scenario Type is a convenient umbrella term that groups all these things. Diagram below presents main relationships between configuration areas. ![Configuration areas](img/configuration_areas.png "configuration areas") @@ -44,28 +48,28 @@ environment: "local"
# Each scenario type is configured here
scenarioTypes {"{"}
{" "} "scenario-type-1": {"{"}
-{" "} # Configuration of DeploymentManager (Flink used as example here)
+{" "} # Configuration of scenario deployment (Flink used as example here)
{" "} deploymentConfig: {"{"}
-{" "} type: "flinkStreaming"
-{" "} restUrl: "http://localhost:8081"
-{" "} }
+{" "} type: "flinkStreaming"
+{" "} restUrl: "http://localhost:8081"
+{" "} }
{" "} # Configuration of model
{" "} modelConfig: {"{"}
-{" "} classPath: ["model/defaultModel.jar", "model/flinkExecutor.jar", "components/flink"]
-{" "} restartStrategy.default.strategy: disable
-{" "} components {"{"}
+{" "} classPath: ["model/defaultModel.jar", "model/flinkExecutor.jar", "components/flink"]
+{" "} restartStrategy.default.strategy: disable
+{" "} components {"{"}
{" "} ...
-{" "} }
-{" "} }
-{" "} category: "Default"
-{" "} }
+{" "} }
+{" "} }
+{" "} category: "Default"
+{" "} }
}
-It is worth noting that one Nussknacker Designer instance may be used to work with multiple Scenario Types which: +It is worth noting that one Nussknacker Designer may be used to work with multiple Scenario Types and allow user: -* can be deployed with various Deployment Managers to e.g. different Flink clusters -* use different components and Model configurations +* To use different set of components depending on the category +* To deploy scenarios on different [Engines](/about/engine) See [development configuration](https://github.com/TouK/nussknacker/blob/staging/nussknacker-dist/src/universal/conf/dev-application.conf#L33) (used to test various Nussknacker features) for an example of configuration with more than one Scenario Type. diff --git a/docs/installation_configuration_guide/DesignerConfiguration.md b/docs/installation_configuration_guide/DesignerConfiguration.md index e7a104be65f..2fe4147ae71 100644 --- a/docs/installation_configuration_guide/DesignerConfiguration.md +++ b/docs/installation_configuration_guide/DesignerConfiguration.md @@ -104,7 +104,7 @@ with the settings presented below: Nussknacker Designer can be configured to replace certain values in comments to links that can point e.g. to external issue tracker like GitHub issues or Jira. For example, `MARKETING-555` will change to link `https://jira.organization.com/jira/browse/MARKETING-555`. -See [development configuration](https://github.com/TouK/nussknacker/blob/staging/nussknacker-dist/src/universal/conf/dev-application.conf#L104) for example configuration. +See [development configuration](https://github.com/TouK/nussknacker/blob/staging/nussknacker-dist/src/universal/conf/dev-application.conf#L329) for example configuration. | Parameter name | Importance | Type | Default value | Description | @@ -770,7 +770,7 @@ scenarioTypes { ``` Scenario type configuration consists of parts: -- `deploymentConfig` - [deployment manager configuration](./DeploymentManagerConfiguration.md) +- `deploymentConfig` - [scenario deployment configuration](./ScenarioDeploymentConfiguration.md) - `modelConfig` - [model configuration](./model/ModelConfiguration.md) - `category` - category handled by given scenario type diff --git a/docs/installation_configuration_guide/DeploymentManagerConfiguration.md b/docs/installation_configuration_guide/ScenarioDeploymentConfiguration.md similarity index 77% rename from docs/installation_configuration_guide/DeploymentManagerConfiguration.md rename to docs/installation_configuration_guide/ScenarioDeploymentConfiguration.md index 0d202e2f53f..62a4b9b933f 100644 --- a/docs/installation_configuration_guide/DeploymentManagerConfiguration.md +++ b/docs/installation_configuration_guide/ScenarioDeploymentConfiguration.md @@ -3,26 +3,32 @@ title: Deployment sidebar_position: 3 --- -# Deployment Manager configuration +# Scenario Deployment configuration -Deployment Manager deploys scenarios from the Designer to the engine on which scenarios are processed. -Check [configuration areas](./Common.md#configuration-areas) to understand where Deployment Manager configuration should be -placed in Nussknacker configuration. +In order to deploy scenario on the given [Engine](/about/engine), you need to configure the deployment. -Below you can find a snippet of Deployment Manager configuration. +Deployment of a scenario is managed by Designer's extension called [Deployment Manager](/about/GLOSSARY#deployment-manager). +To enable given [Deployment Manager](/about/GLOSSARY#deployment-manager) you need to place its jar package in the Designer's classpath. +Nussknacker is distributed with three default [Deployment Managers](/about/GLOSSARY#deployment-manager) (`flinkStreaming`, `lite-k8s`, `lite-embedded`). Their jars are located in the `managers` +directory. Depending on which [Deployment Manager](/about/GLOSSARY#deployment-manager) you've selected, you should provide parameters values for it specifically - see sections below to find out available parameters. + +Section with `deploymentConfig` needs to be placed in the correct place in the Designer's configuration. Check [configuration areas](./Common.md#configuration-areas) to understand the structure of the configuration. + +Below you can find a snippet of scenario deployment configuration. ``` deploymentConfig { type: "flinkStreaming" - restUrl: "http://localhost:8081" engineSetupName: "My Flink Cluster" - # additional configuration goes here + # Deployment Manager's specific parameters + restUrl: "http://localhost:8081" } ``` -`type` parameter determines engine to which the scenario is deployed. It is set in the [minimal configuration file](./Common.md#minimal-configuration-file) (docker image, binary distribution) and in the Helm chart - you will not need to set it on your own. -`engineSetupName` parameter is optional. It specifies how the engine will be displayed in the GUI. If not specified, default name will be used instead (e.g. `Flink` for `flinkStreaming` Deployment Manager). +Parameters: +- `type` parameter determines the type of the [Deployment Manager](/about/GLOSSARY#deployment-manager). Possible options are: `flinkStreaming`, `lite-k8s`, `lite-embedded` +- `engineSetupName` parameter is optional. It specifies how the engine will be displayed in the GUI. If not specified, default name will be used instead (e.g. `Flink` for `flinkStreaming` Deployment Manager). ## Kubernetes native Lite engine configuration @@ -38,25 +44,23 @@ The table below contains configuration options for the Lite engine. If you insta   If you install Designer outside the K8s cluster then the required changes should be applied under the `deploymentConfig` key as any other Nussknacker non K8s configuration. - - -| Parameter | Type | Default value | Description | -|-------------------------------|-----------------------------------------------------|-----------------------------------|------------------------------------------------------------------------------------------| -| mode | string | | Processing mode: either streaming or request-response | -| dockerImageName | string | touk/nussknacker-lite-runtime-app | Runtime image (please note that it's **not** touk/nussknacker - which is designer image) | -| dockerImageTag | string | current nussknacker version | | -| scalingConfig *(Streaming processing mode)*| {tasksPerReplica: int} | { tasksPerReplica: 4 } | see [below](#configuring-replicas-count) | -| scalingConfig *(Request - Response processing mode)*| {fixedReplicasCount: int} | { fixedReplicasCount: 2 } | see [below](#configuring-replicas-count) | -| configExecutionOverrides | config | {} | see [below](#overriding-configuration-passed-to-runtime) | -| k8sDeploymentConfig | config | {} | see [below](#customizing-k8s-deployment-resource-definition) | -| nussknackerInstanceName | string | {?NUSSKNACKER_INSTANCE_NAME} | see [below](#nussknacker-instance-name) | -| logbackConfigPath | string | {} | see [below](#configuring-runtime-logging) | -| commonConfigMapForLogback | string | {} | see [below](#configuring-runtime-logging) | -| ingress | config | {enabled: false} | (Request-Response only) see [below](#configuring-runtime-ingress) | -| servicePort | int | 80 | (Request-Response only) Port of service exposed | -| scenarioStateCaching.enabled | boolean | true | Enables scenario state caching in scenario list view | -| scenarioStateCaching.cacheTTL | duration | 10 seconds | TimeToLeave for scenario state cache entries | -| scenarioStateIdleTimeout | duration | 3 seconds | Idle timeout for fetching scenario state from K8s | +| Parameter | Type | Default value | Description | +|------------------------------------------------------|---------------------------|-----------------------------------|------------------------------------------------------------------------------------------| +| mode | string | | Processing mode: either streaming or request-response | +| dockerImageName | string | touk/nussknacker-lite-runtime-app | Runtime image (please note that it's **not** touk/nussknacker - which is designer image) | +| dockerImageTag | string | current nussknacker version | | +| scalingConfig *(Streaming processing mode)* | {tasksPerReplica: int} | { tasksPerReplica: 4 } | see [below](#configuring-replicas-count) | +| scalingConfig *(Request - Response processing mode)* | {fixedReplicasCount: int} | { fixedReplicasCount: 2 } | see [below](#configuring-replicas-count) | +| configExecutionOverrides | config | {} | see [below](#overriding-configuration-passed-to-runtime) | +| k8sDeploymentConfig | config | {} | see [below](#customizing-k8s-deployment-resource-definition) | +| nussknackerInstanceName | string | {?NUSSKNACKER_INSTANCE_NAME} | see [below](#nussknacker-instance-name) | +| logbackConfigPath | string | {} | see [below](#configuring-runtime-logging) | +| commonConfigMapForLogback | string | {} | see [below](#configuring-runtime-logging) | +| ingress | config | {enabled: false} | (Request-Response only) see [below](#configuring-runtime-ingress) | +| servicePort | int | 80 | (Request-Response only) Port of service exposed | +| scenarioStateCaching.enabled | boolean | true | Enables scenario state caching in scenario list view | +| scenarioStateCaching.cacheTTL | duration | 10 seconds | TimeToLeave for scenario state cache entries | +| scenarioStateIdleTimeout | duration | 3 seconds | Idle timeout for fetching scenario state from K8s | ### Customizing K8s deployment resource definition diff --git a/docs/installation_configuration_guide/img/configuration_areas.png b/docs/installation_configuration_guide/img/configuration_areas.png index 0ff5c3b73a7ed71d561443b285f1a3a902f6a401..baa90d3518f3784f281b7848230ea1a3fd40e48e 100644 GIT binary patch literal 27763 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 literal 57953 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 diff --git a/docs/installation_configuration_guide/model/ModelConfiguration.md b/docs/installation_configuration_guide/model/ModelConfiguration.md index c076a293128..210f73f177a 100644 --- a/docs/installation_configuration_guide/model/ModelConfiguration.md +++ b/docs/installation_configuration_guide/model/ModelConfiguration.md @@ -5,7 +5,7 @@ sidebar_position: 1 # Model configuration Model definition is part of a scenario type definition. There can be multiple scenario types in one Nussknacker installation, consequently there will also be multiple model definitions in such a case. -Check [configuration areas](docs/installation_configuration_guide/Common.md#configuration-areas) to understand where Model configuration should be placed in the Nussknacker configuration. If you deploy to K8s using Nussknacker Helm chart, check [here](docs/installation_configuration_guide/DeploymentManagerConfiguration.md#overriding-configuration-passed-to-runtime) how to supply additional model configuration. +Check [configuration areas](docs/installation_configuration_guide/Common.md#configuration-areas) to understand where Model configuration should be placed in the Nussknacker configuration. If you deploy to K8s using Nussknacker Helm chart, check [here](docs/installation_configuration_guide/ScenarioDeploymentConfiguration.md#overriding-configuration-passed-to-runtime) how to supply additional model configuration. Model defines how to configure [components](/about/GLOSSARY#component) and certain runtime behavior (e.g. error handling) for a given scenario type. Model configuration is processed not only at the Designer but also passed to the execution engine (e.g. Flink), that’s why it’s parsed and processed a bit differently: diff --git a/docs/integration/OpenAPI.md b/docs/integration/OpenAPI.md index be3e688ff89..fa94fda2b40 100644 --- a/docs/integration/OpenAPI.md +++ b/docs/integration/OpenAPI.md @@ -89,4 +89,4 @@ Enricher level logging can be enabled: - in Flink TaskManager [configuration](https://github.com/TouK/nussknacker-quickstart/blob/main/docker/streaming/flink/log4j-console.properties) - in Lite - runtime [configuration](../installation_configuration_guide/DeploymentManagerConfiguration.md#configuring-runtime-logging) + runtime [configuration](../installation_configuration_guide/ScenarioDeploymentConfiguration.md#configuring-runtime-logging) diff --git a/docs/operations_guide/Lite.md b/docs/operations_guide/Lite.md index 60896e55036..3d71216cdfc 100644 --- a/docs/operations_guide/Lite.md +++ b/docs/operations_guide/Lite.md @@ -100,7 +100,7 @@ If you need more fine-grained control over logging in specific scenario deployme file which is being used by it, by overriding config map linked to your runtime container under `logback.xml` key. Please be aware, that modifications made to this config map are transient - every (re)deploy of scenario, creates config map from scratch with default (or configured in DeploymentManager - -see [docs](../installation_configuration_guide/DeploymentManagerConfiguration.md#configuring-runtime-logging) ) content. +see [docs](../installation_configuration_guide/ScenarioDeploymentConfiguration.md#configuring-runtime-logging) ) content. ### Managing lifecycle of scenario From 3d08af134f1fbb4855004729cbe8620969943d65 Mon Sep 17 00:00:00 2001 From: Arek Burdach Date: Wed, 20 Mar 2024 10:04:06 +0100 Subject: [PATCH 3/4] [NU-1542] Duplicated category fix (#5764) --- .../ui/services/UserApiHttpService.scala | 2 +- .../basicauth-users.conf | 0 .../multiple-category-designer.conf} | 2 +- .../basicauth-users.conf | 0 ...category-used-more-than-once-designer.conf | 36 ++++++++ .../simple-streaming-use-case-designer.conf | 2 +- ...ControlCheckingConfigScenarioHelper.scala} | 8 +- .../test/config/ConfigWithScalaVersion.scala | 4 +- ...AccessControlCheckingDesignerConfig.scala} | 15 ++-- ...sinessCaseRestAssuredUsersExtensions.scala | 22 +++++ ...tegoryUsedMoreThanOnceDesignerConfig.scala | 84 +++++++++++++++++++ .../config/WithSimplifiedDesignerConfig.scala | 19 +---- .../api/AppApiHttpServiceBusinessSpec.scala | 4 +- .../api/AppApiHttpServiceSecuritySpec.scala | 16 ++-- .../ComponentApiHttpServiceBusinessSpec.scala | 4 +- .../ComponentApiHttpServiceSecuritySpec.scala | 17 ++-- ...ManagementApiHttpServiceBusinessSpec.scala | 4 +- .../api/NodesApiHttpServiceBusinessSpec.scala | 4 +- .../api/NodesApiHttpServiceSecuritySpec.scala | 21 +++-- ...tificationApiHttpServiceBusinessSpec.scala | 4 +- ...tificationApiHttpServiceSecuritySpec.scala | 16 ++-- .../ui/api/ProcessesResourcesSpec.scala | 15 ++-- ...ioActivityApiHttpServiceBusinessSpec.scala | 4 +- ...ioActivityApiHttpServiceSecuritySpec.scala | 16 ++-- ...ParametersApiHttpServiceBusinessSpec.scala | 4 +- ...ParametersApiHttpServiceSecuritySpec.scala | 9 +- .../api/UserApiHttpServiceBusinessSpec.scala | 4 +- ...ceCategoryUsedMoreThanOnceConfigSpec.scala | 44 ++++++++++ .../api/UserApiHttpServiceSecuritySpec.scala | 9 +- .../ui/process/DBProcessServiceSpec.scala | 4 +- 30 files changed, 289 insertions(+), 104 deletions(-) rename designer/server/src/test/resources/config/{rich => access-control-checking}/basicauth-users.conf (100%) rename designer/server/src/test/resources/config/{rich/rich-streaming-use-case-designer.conf => access-control-checking/multiple-category-designer.conf} (96%) rename designer/server/src/test/resources/config/{simple => business-cases}/basicauth-users.conf (100%) create mode 100644 designer/server/src/test/resources/config/business-cases/category-used-more-than-once-designer.conf rename designer/server/src/test/resources/config/{simple => business-cases}/simple-streaming-use-case-designer.conf (96%) rename designer/server/src/test/scala/pl/touk/nussknacker/test/base/it/{WithRichConfigScenarioHelper.scala => WithAccessControlCheckingConfigScenarioHelper.scala} (87%) rename designer/server/src/test/scala/pl/touk/nussknacker/test/config/{WithRichDesignerConfig.scala => WithAccessControlCheckingDesignerConfig.scala} (83%) create mode 100644 designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithBusinessCaseRestAssuredUsersExtensions.scala create mode 100644 designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithCategoryUsedMoreThanOnceDesignerConfig.scala create mode 100644 designer/server/src/test/scala/pl/touk/nussknacker/ui/api/UserApiHttpServiceCategoryUsedMoreThanOnceConfigSpec.scala diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/services/UserApiHttpService.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/services/UserApiHttpService.scala index 3b57f6c2863..c990b2f41bd 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/services/UserApiHttpService.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/services/UserApiHttpService.scala @@ -24,7 +24,7 @@ class UserApiHttpService( .serverLogic { user: LoggedUser => _ => Future( success( - DisplayableUser(user, categories.all(user).values) + DisplayableUser(user, categories.all(user).values.toList.distinct.sorted) ) ) } diff --git a/designer/server/src/test/resources/config/rich/basicauth-users.conf b/designer/server/src/test/resources/config/access-control-checking/basicauth-users.conf similarity index 100% rename from designer/server/src/test/resources/config/rich/basicauth-users.conf rename to designer/server/src/test/resources/config/access-control-checking/basicauth-users.conf diff --git a/designer/server/src/test/resources/config/rich/rich-streaming-use-case-designer.conf b/designer/server/src/test/resources/config/access-control-checking/multiple-category-designer.conf similarity index 96% rename from designer/server/src/test/resources/config/rich/rich-streaming-use-case-designer.conf rename to designer/server/src/test/resources/config/access-control-checking/multiple-category-designer.conf index 5cbc524cb4e..85b897ec9e3 100644 --- a/designer/server/src/test/resources/config/rich/rich-streaming-use-case-designer.conf +++ b/designer/server/src/test/resources/config/access-control-checking/multiple-category-designer.conf @@ -2,7 +2,7 @@ include "../common-designer.conf" authentication: { method: "BasicAuth" - usersFile: "designer/server/src/test/resources/config/rich/basicauth-users.conf" + usersFile: "designer/server/src/test/resources/config/access-control-checking/basicauth-users.conf" anonymousUserRole: "Demo" } diff --git a/designer/server/src/test/resources/config/simple/basicauth-users.conf b/designer/server/src/test/resources/config/business-cases/basicauth-users.conf similarity index 100% rename from designer/server/src/test/resources/config/simple/basicauth-users.conf rename to designer/server/src/test/resources/config/business-cases/basicauth-users.conf diff --git a/designer/server/src/test/resources/config/business-cases/category-used-more-than-once-designer.conf b/designer/server/src/test/resources/config/business-cases/category-used-more-than-once-designer.conf new file mode 100644 index 00000000000..dccd5cf8d91 --- /dev/null +++ b/designer/server/src/test/resources/config/business-cases/category-used-more-than-once-designer.conf @@ -0,0 +1,36 @@ +include "../common-designer.conf" + +authentication: { + method: "BasicAuth" + usersFile: "designer/server/src/test/resources/config/business-cases/basicauth-users.conf" +} + +baseModelConfig { + classPath: [ + "engine/flink/management/dev-model/target/scala-"${scala.major.version}"/devModel.jar", + "engine/flink/executor/target/scala-"${scala.major.version}"/flinkExecutor.jar" + ] +} + +scenarioTypes { + streaming1 { + deploymentConfig { + restUrl: "http://localhost:8081" + jobManagerTimeout: 1m + type: "flinkStreaming" + engineSetupName: "Flink 1" + } + modelConfig: ${baseModelConfig} + category: "Category1" + } + streaming2 { + deploymentConfig { + restUrl: "http://localhost:8082" + jobManagerTimeout: 1m + type: "flinkStreaming" + engineSetupName: "Flink 2" + } + modelConfig: ${baseModelConfig} + category: "Category1" + } +} diff --git a/designer/server/src/test/resources/config/simple/simple-streaming-use-case-designer.conf b/designer/server/src/test/resources/config/business-cases/simple-streaming-use-case-designer.conf similarity index 96% rename from designer/server/src/test/resources/config/simple/simple-streaming-use-case-designer.conf rename to designer/server/src/test/resources/config/business-cases/simple-streaming-use-case-designer.conf index 5933c69c2b9..087a1d85774 100644 --- a/designer/server/src/test/resources/config/simple/simple-streaming-use-case-designer.conf +++ b/designer/server/src/test/resources/config/business-cases/simple-streaming-use-case-designer.conf @@ -2,7 +2,7 @@ include "../common-designer.conf" authentication: { method: "BasicAuth" - usersFile: "designer/server/src/test/resources/config/simple/basicauth-users.conf" + usersFile: "designer/server/src/test/resources/config/business-cases/basicauth-users.conf" } baseModelConfig { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/test/base/it/WithRichConfigScenarioHelper.scala b/designer/server/src/test/scala/pl/touk/nussknacker/test/base/it/WithAccessControlCheckingConfigScenarioHelper.scala similarity index 87% rename from designer/server/src/test/scala/pl/touk/nussknacker/test/base/it/WithRichConfigScenarioHelper.scala rename to designer/server/src/test/scala/pl/touk/nussknacker/test/base/it/WithAccessControlCheckingConfigScenarioHelper.scala index c2721176bae..49552bffe70 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/test/base/it/WithRichConfigScenarioHelper.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/test/base/it/WithAccessControlCheckingConfigScenarioHelper.scala @@ -3,14 +3,14 @@ package pl.touk.nussknacker.test.base.it import pl.touk.nussknacker.engine.api.process.{ProcessId, ProcessName} import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess import pl.touk.nussknacker.test.base.db.WithTestDb -import pl.touk.nussknacker.test.config.WithRichDesignerConfig -import pl.touk.nussknacker.test.config.WithRichDesignerConfig.TestCategory +import pl.touk.nussknacker.test.config.WithAccessControlCheckingDesignerConfig +import pl.touk.nussknacker.test.config.WithAccessControlCheckingDesignerConfig.TestCategory import pl.touk.nussknacker.test.utils.domain.ScenarioHelper import scala.concurrent.ExecutionContext.Implicits.global -trait WithRichConfigScenarioHelper { - this: WithTestDb with WithRichDesignerConfig => +trait WithAccessControlCheckingConfigScenarioHelper { + this: WithTestDb with WithAccessControlCheckingDesignerConfig => private val rawScenarioHelper = new ScenarioHelper(testDbRef, designerConfig) diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/test/config/ConfigWithScalaVersion.scala b/designer/server/src/test/scala/pl/touk/nussknacker/test/config/ConfigWithScalaVersion.scala index b6d2beed8fa..192a64c9ec6 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/test/config/ConfigWithScalaVersion.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/test/config/ConfigWithScalaVersion.scala @@ -9,13 +9,13 @@ import pl.touk.nussknacker.test.config.WithSimplifiedDesignerConfig.TestProcessi object ConfigWithScalaVersion { val TestsConfig: Config = ScalaMajorVersionConfig.configWithScalaMajorVersion( - ConfigFactory.parseResources("config/simple/simple-streaming-use-case-designer.conf") + ConfigFactory.parseResources("config/business-cases/simple-streaming-use-case-designer.conf") ) // TODO: we should switch to lite-embedded in most places in tests, because it has lower performance overhead val TestsConfigWithEmbeddedEngine: Config = ScalaMajorVersionConfig.configWithScalaMajorVersion( ConfigFactory - .parseResources("config/simple/simple-streaming-use-case-designer.conf") + .parseResources("config/business-cases/simple-streaming-use-case-designer.conf") .withValue(s"scenarioTypes.${Streaming.stringify}.deploymentConfig.type", fromAnyRef("lite-embedded")) .withValue(s"scenarioTypes.${Streaming.stringify}.deploymentConfig.mode", fromAnyRef("streaming")) ) diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithRichDesignerConfig.scala b/designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithAccessControlCheckingDesignerConfig.scala similarity index 83% rename from designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithRichDesignerConfig.scala rename to designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithAccessControlCheckingDesignerConfig.scala index 7c3a4e85a49..533ebfb036e 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithRichDesignerConfig.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithAccessControlCheckingDesignerConfig.scala @@ -6,16 +6,17 @@ import io.restassured.specification.RequestSpecification import org.scalatest.Suite import pl.touk.nussknacker.engine.util.config.ScalaMajorVersionConfig import pl.touk.nussknacker.test.NuRestAssureExtensions -import pl.touk.nussknacker.test.config.WithRichDesignerConfig.TestCategory +import pl.touk.nussknacker.test.config.WithAccessControlCheckingDesignerConfig.TestCategory import pl.touk.nussknacker.test.utils.DesignerTestConfigValidator -trait WithRichDesignerConfig extends WithDesignerConfig { +// This trait shows setups with multiple categories allowing to verify cases such as access to some category but without access to another one +trait WithAccessControlCheckingDesignerConfig extends WithDesignerConfig { this: Suite => validateConsistency() override def designerConfig: Config = ScalaMajorVersionConfig.configWithScalaMajorVersion( - ConfigFactory.parseResources("config/rich/rich-streaming-use-case-designer.conf") + ConfigFactory.parseResources("config/access-control-checking/multiple-category-designer.conf") ) private def validateConsistency(): Unit = { @@ -27,7 +28,7 @@ trait WithRichDesignerConfig extends WithDesignerConfig { } -object WithRichDesignerConfig { +object WithAccessControlCheckingDesignerConfig { sealed trait TestProcessingType extends EnumEntry object TestProcessingType extends Enum[TestProcessingType] { @@ -77,7 +78,7 @@ object WithRichDesignerConfig { .apply(category) } - private[WithRichDesignerConfig] lazy val categoryByProcessingType = + private[WithAccessControlCheckingDesignerConfig] lazy val categoryByProcessingType = TestProcessingType.values.map { processingType => (processingType, TestProcessingType.categoryBy(processingType)) }.toMap @@ -86,8 +87,8 @@ object WithRichDesignerConfig { } -trait WithRichConfigRestAssuredUsersExtensions extends NuRestAssureExtensions { - this: WithRichDesignerConfig => +trait WithAccessControlCheckingConfigRestAssuredUsersExtensions extends NuRestAssureExtensions { + this: WithAccessControlCheckingDesignerConfig => implicit class UsersBasicAuth[T <: RequestSpecification](requestSpecification: T) { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithBusinessCaseRestAssuredUsersExtensions.scala b/designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithBusinessCaseRestAssuredUsersExtensions.scala new file mode 100644 index 00000000000..b916c1ef974 --- /dev/null +++ b/designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithBusinessCaseRestAssuredUsersExtensions.scala @@ -0,0 +1,22 @@ +package pl.touk.nussknacker.test.config + +import io.restassured.specification.RequestSpecification +import pl.touk.nussknacker.test.NuRestAssureExtensions + +// It enriches rest assure directives with user specified in /config/business-cases/basicauth-users.conf +// which is used among designer configuration inside the same directory +trait WithBusinessCaseRestAssuredUsersExtensions extends NuRestAssureExtensions { + + implicit class UsersBasicAuth[T <: RequestSpecification](requestSpecification: T) { + + def basicAuthAdmin(): RequestSpecification = + requestSpecification.preemptiveBasicAuth("admin", "admin") + + def basicAuthAllPermUser(): RequestSpecification = + requestSpecification.preemptiveBasicAuth("allpermuser", "allpermuser") + + def basicAuthUnknownUser(): RequestSpecification = + requestSpecification.preemptiveBasicAuth("unknownuser", "wrongcredentials") + } + +} diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithCategoryUsedMoreThanOnceDesignerConfig.scala b/designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithCategoryUsedMoreThanOnceDesignerConfig.scala new file mode 100644 index 00000000000..e174de6cefa --- /dev/null +++ b/designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithCategoryUsedMoreThanOnceDesignerConfig.scala @@ -0,0 +1,84 @@ +package pl.touk.nussknacker.test.config + +import com.typesafe.config.{Config, ConfigFactory} +import enumeratum.{Enum, EnumEntry} +import org.scalatest.Suite +import pl.touk.nussknacker.engine.util.config.ScalaMajorVersionConfig +import pl.touk.nussknacker.test.config.WithCategoryUsedMoreThanOnceDesignerConfig.TestCategory +import pl.touk.nussknacker.test.utils.DesignerTestConfigValidator + +trait WithCategoryUsedMoreThanOnceDesignerConfig extends WithDesignerConfig { this: Suite => + + validateConsistency() + + override def designerConfig: Config = ScalaMajorVersionConfig.configWithScalaMajorVersion( + ConfigFactory.parseResources("config/business-cases/category-used-more-than-once-designer.conf") + ) + + private def validateConsistency(): Unit = { + val configValidator = new DesignerTestConfigValidator(designerConfig) + val processingTypeWithCategories = + TestCategory.categoryByProcessingType.map { case (k, v) => (k.stringify, v.stringify) } + configValidator.validateTestDataWithDesignerConfFile(processingTypeWithCategories) + } + +} + +object WithCategoryUsedMoreThanOnceDesignerConfig { + + sealed trait TestProcessingType extends EnumEntry + + object TestProcessingType extends Enum[TestProcessingType] { + case object Streaming1 extends TestProcessingType + + case object Streaming2 extends TestProcessingType + + override val values = findValues + + implicit class ProcessingTypeStringify(processingType: TestProcessingType) { + + def stringify: String = processingType match { + case TestProcessingType.Streaming1 => "streaming1" + case TestProcessingType.Streaming2 => "streaming2" + } + + } + + def categoryBy(processingType: TestProcessingType): TestCategory = { + processingType match { + case TestProcessingType.Streaming1 => TestCategory.Category1 + case TestProcessingType.Streaming2 => TestCategory.Category1 + } + } + + } + + sealed trait TestCategory extends EnumEntry + + object TestCategory extends Enum[TestCategory] { + case object Category1 extends TestCategory + + override val values = findValues + + implicit class CategoryStringify(category: TestCategory) { + + def stringify: String = category match { + case Category1 => "Category1" + } + + } + + def processingTypeBy(category: TestCategory): TestProcessingType = { + categoryByProcessingType + .map(_.swap) + .apply(category) + } + + private[WithCategoryUsedMoreThanOnceDesignerConfig] lazy val categoryByProcessingType = + TestProcessingType.values.map { processingType => + (processingType, TestProcessingType.categoryBy(processingType)) + }.toMap + + } + +} diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithSimplifiedDesignerConfig.scala b/designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithSimplifiedDesignerConfig.scala index dcda256dd32..51c4d1c47c4 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithSimplifiedDesignerConfig.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/test/config/WithSimplifiedDesignerConfig.scala @@ -15,7 +15,7 @@ trait WithSimplifiedDesignerConfig extends WithDesignerConfig { validateConsistency() override def designerConfig: Config = ScalaMajorVersionConfig.configWithScalaMajorVersion( - ConfigFactory.parseResources("config/simple/simple-streaming-use-case-designer.conf") + ConfigFactory.parseResources("config/business-cases/simple-streaming-use-case-designer.conf") ) private def validateConsistency(): Unit = { @@ -80,20 +80,3 @@ object WithSimplifiedDesignerConfig { } } - -trait WithSimplifiedConfigRestAssuredUsersExtensions extends NuRestAssureExtensions { - this: WithSimplifiedDesignerConfig => - - implicit class UsersBasicAuth[T <: RequestSpecification](requestSpecification: T) { - - def basicAuthAdmin(): RequestSpecification = - requestSpecification.preemptiveBasicAuth("admin", "admin") - - def basicAuthAllPermUser(): RequestSpecification = - requestSpecification.preemptiveBasicAuth("allpermuser", "allpermuser") - - def basicAuthUnknownUser(): RequestSpecification = - requestSpecification.preemptiveBasicAuth("unknownuser", "wrongcredentials") - } - -} diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/AppApiHttpServiceBusinessSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/AppApiHttpServiceBusinessSpec.scala index 23e068e2c9c..646a9881ca4 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/AppApiHttpServiceBusinessSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/AppApiHttpServiceBusinessSpec.scala @@ -13,8 +13,8 @@ import pl.touk.nussknacker.engine.api.process.{ProcessName, VersionId} import pl.touk.nussknacker.test.{NuRestAssureMatchers, PatientScalaFutures, RestAssuredVerboseLogging} import pl.touk.nussknacker.test.base.it.{NuItTest, WithSimplifiedConfigScenarioHelper} import pl.touk.nussknacker.test.config.{ + WithBusinessCaseRestAssuredUsersExtensions, WithMockableDeploymentManager, - WithSimplifiedConfigRestAssuredUsersExtensions, WithSimplifiedDesignerConfig } @@ -24,7 +24,7 @@ class AppApiHttpServiceBusinessSpec with WithSimplifiedDesignerConfig with WithSimplifiedConfigScenarioHelper with WithMockableDeploymentManager - with WithSimplifiedConfigRestAssuredUsersExtensions + with WithBusinessCaseRestAssuredUsersExtensions with NuRestAssureMatchers with RestAssuredVerboseLogging with PatientScalaFutures { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/AppApiHttpServiceSecuritySpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/AppApiHttpServiceSecuritySpec.scala index 2cc24cf8707..2a9dd455221 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/AppApiHttpServiceSecuritySpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/AppApiHttpServiceSecuritySpec.scala @@ -11,21 +11,21 @@ import pl.touk.nussknacker.engine.api.deployment.simple.SimpleStateStatus import pl.touk.nussknacker.engine.api.deployment.simple.SimpleStateStatus.ProblemStateStatus import pl.touk.nussknacker.engine.api.process.{ProcessName, VersionId} import pl.touk.nussknacker.test.{NuRestAssureMatchers, PatientScalaFutures, RestAssuredVerboseLogging} -import pl.touk.nussknacker.test.base.it.{NuItTest, WithRichConfigScenarioHelper} -import pl.touk.nussknacker.test.config.WithRichDesignerConfig.TestCategory.{Category1, Category2} +import pl.touk.nussknacker.test.base.it.{NuItTest, WithAccessControlCheckingConfigScenarioHelper} +import pl.touk.nussknacker.test.config.WithAccessControlCheckingDesignerConfig.TestCategory.{Category1, Category2} import pl.touk.nussknacker.test.config.{ - WithMockableDeploymentManager, - WithRichConfigRestAssuredUsersExtensions, - WithRichDesignerConfig + WithAccessControlCheckingConfigRestAssuredUsersExtensions, + WithAccessControlCheckingDesignerConfig, + WithMockableDeploymentManager } class AppApiHttpServiceSecuritySpec extends AnyFreeSpecLike with NuItTest - with WithRichDesignerConfig - with WithRichConfigScenarioHelper + with WithAccessControlCheckingDesignerConfig + with WithAccessControlCheckingConfigScenarioHelper with WithMockableDeploymentManager - with WithRichConfigRestAssuredUsersExtensions + with WithAccessControlCheckingConfigRestAssuredUsersExtensions with NuRestAssureMatchers with RestAssuredVerboseLogging with PatientScalaFutures { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ComponentApiHttpServiceBusinessSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ComponentApiHttpServiceBusinessSpec.scala index a290ff54a1a..d5943ff8ac0 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ComponentApiHttpServiceBusinessSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ComponentApiHttpServiceBusinessSpec.scala @@ -19,8 +19,8 @@ import pl.touk.nussknacker.test.base.it.{NuItTest, WithSimplifiedConfigScenarioH import pl.touk.nussknacker.test.config.WithSimplifiedDesignerConfig.TestCategory.Category1 import pl.touk.nussknacker.test.config.WithSimplifiedDesignerConfig.TestProcessingType.Streaming import pl.touk.nussknacker.test.config.{ + WithBusinessCaseRestAssuredUsersExtensions, WithMockableDeploymentManager, - WithSimplifiedConfigRestAssuredUsersExtensions, WithSimplifiedDesignerConfig } @@ -30,7 +30,7 @@ class ComponentApiHttpServiceBusinessSpec with WithSimplifiedDesignerConfig with WithSimplifiedConfigScenarioHelper with WithMockableDeploymentManager - with WithSimplifiedConfigRestAssuredUsersExtensions + with WithBusinessCaseRestAssuredUsersExtensions with NuRestAssureExtensions with NuRestAssureMatchers with RestAssuredVerboseLogging diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ComponentApiHttpServiceSecuritySpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ComponentApiHttpServiceSecuritySpec.scala index de10c74c2ea..a8e9bdca4a9 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ComponentApiHttpServiceSecuritySpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ComponentApiHttpServiceSecuritySpec.scala @@ -9,10 +9,13 @@ import org.scalatest.matchers.must.Matchers.contain import pl.touk.nussknacker.engine.api.component.{ComponentId, ComponentType, DesignerWideComponentId} import pl.touk.nussknacker.engine.build.ScenarioBuilder import pl.touk.nussknacker.test.ProcessUtils.convertToAnyShouldWrapper -import pl.touk.nussknacker.test.base.it.{NuItTest, WithRichConfigScenarioHelper} -import pl.touk.nussknacker.test.config.WithRichDesignerConfig.TestCategory -import pl.touk.nussknacker.test.config.WithRichDesignerConfig.TestCategory.{Category1, Category2} -import pl.touk.nussknacker.test.config.{WithRichConfigRestAssuredUsersExtensions, WithRichDesignerConfig} +import pl.touk.nussknacker.test.base.it.{NuItTest, WithAccessControlCheckingConfigScenarioHelper} +import pl.touk.nussknacker.test.config.WithAccessControlCheckingDesignerConfig.TestCategory +import pl.touk.nussknacker.test.config.WithAccessControlCheckingDesignerConfig.TestCategory.{Category1, Category2} +import pl.touk.nussknacker.test.config.{ + WithAccessControlCheckingConfigRestAssuredUsersExtensions, + WithAccessControlCheckingDesignerConfig +} import pl.touk.nussknacker.test.{ NuRestAssureExtensions, NuRestAssureMatchers, @@ -23,9 +26,9 @@ import pl.touk.nussknacker.test.{ class ComponentApiHttpServiceSecuritySpec extends AnyFreeSpecLike with NuItTest - with WithRichDesignerConfig - with WithRichConfigScenarioHelper - with WithRichConfigRestAssuredUsersExtensions + with WithAccessControlCheckingDesignerConfig + with WithAccessControlCheckingConfigScenarioHelper + with WithAccessControlCheckingConfigRestAssuredUsersExtensions with NuRestAssureExtensions with NuRestAssureMatchers with RestAssuredVerboseLogging diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ManagementApiHttpServiceBusinessSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ManagementApiHttpServiceBusinessSpec.scala index dc8b544ee86..3134eab78f0 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ManagementApiHttpServiceBusinessSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ManagementApiHttpServiceBusinessSpec.scala @@ -9,8 +9,8 @@ import pl.touk.nussknacker.engine.build.ScenarioBuilder import pl.touk.nussknacker.engine.testmode.TestProcess.TestResults import pl.touk.nussknacker.test.base.it.{NuItTest, WithSimplifiedConfigScenarioHelper} import pl.touk.nussknacker.test.config.{ + WithBusinessCaseRestAssuredUsersExtensions, WithMockableDeploymentManager, - WithSimplifiedConfigRestAssuredUsersExtensions, WithSimplifiedDesignerConfig } import pl.touk.nussknacker.test.{NuRestAssureExtensions, NuRestAssureMatchers, RestAssuredVerboseLogging} @@ -22,7 +22,7 @@ class ManagementApiHttpServiceBusinessSpec with WithSimplifiedDesignerConfig with WithSimplifiedConfigScenarioHelper with WithMockableDeploymentManager - with WithSimplifiedConfigRestAssuredUsersExtensions + with WithBusinessCaseRestAssuredUsersExtensions with NuRestAssureExtensions with NuRestAssureMatchers with RestAssuredVerboseLogging { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NodesApiHttpServiceBusinessSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NodesApiHttpServiceBusinessSpec.scala index 994876dcb1d..4861789b7f2 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NodesApiHttpServiceBusinessSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NodesApiHttpServiceBusinessSpec.scala @@ -8,8 +8,8 @@ import pl.touk.nussknacker.engine.build.ScenarioBuilder import pl.touk.nussknacker.test.{NuRestAssureMatchers, PatientScalaFutures, RestAssuredVerboseLogging} import pl.touk.nussknacker.test.base.it.{NuItTest, WithSimplifiedConfigScenarioHelper} import pl.touk.nussknacker.test.config.{ + WithBusinessCaseRestAssuredUsersExtensions, WithMockableDeploymentManager, - WithSimplifiedConfigRestAssuredUsersExtensions, WithSimplifiedDesignerConfig } @@ -19,7 +19,7 @@ class NodesApiHttpServiceBusinessSpec with WithSimplifiedDesignerConfig with WithSimplifiedConfigScenarioHelper with WithMockableDeploymentManager - with WithSimplifiedConfigRestAssuredUsersExtensions + with WithBusinessCaseRestAssuredUsersExtensions with NuRestAssureMatchers with RestAssuredVerboseLogging with PatientScalaFutures { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NodesApiHttpServiceSecuritySpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NodesApiHttpServiceSecuritySpec.scala index 34f925a2a6f..ef3531eac88 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NodesApiHttpServiceSecuritySpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NodesApiHttpServiceSecuritySpec.scala @@ -6,22 +6,25 @@ import org.hamcrest.Matchers.equalTo import org.scalatest.freespec.AnyFreeSpecLike import pl.touk.nussknacker.engine.build.ScenarioBuilder import pl.touk.nussknacker.test.{NuRestAssureMatchers, PatientScalaFutures, RestAssuredVerboseLogging} -import pl.touk.nussknacker.test.base.it.{NuItTest, WithRichConfigScenarioHelper} -import pl.touk.nussknacker.test.config.WithRichDesignerConfig.TestCategory.{Category1, Category2} -import pl.touk.nussknacker.test.config.WithRichDesignerConfig.TestProcessingType.{Streaming1, Streaming2} +import pl.touk.nussknacker.test.base.it.{NuItTest, WithAccessControlCheckingConfigScenarioHelper} +import pl.touk.nussknacker.test.config.WithAccessControlCheckingDesignerConfig.TestCategory.{Category1, Category2} +import pl.touk.nussknacker.test.config.WithAccessControlCheckingDesignerConfig.TestProcessingType.{ + Streaming1, + Streaming2 +} import pl.touk.nussknacker.test.config.{ - WithMockableDeploymentManager, - WithRichConfigRestAssuredUsersExtensions, - WithRichDesignerConfig + WithAccessControlCheckingConfigRestAssuredUsersExtensions, + WithAccessControlCheckingDesignerConfig, + WithMockableDeploymentManager } class NodesApiHttpServiceSecuritySpec extends AnyFreeSpecLike with NuItTest - with WithRichDesignerConfig - with WithRichConfigScenarioHelper + with WithAccessControlCheckingDesignerConfig + with WithAccessControlCheckingConfigScenarioHelper with WithMockableDeploymentManager - with WithRichConfigRestAssuredUsersExtensions + with WithAccessControlCheckingConfigRestAssuredUsersExtensions with NuRestAssureMatchers with RestAssuredVerboseLogging with PatientScalaFutures { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NotificationApiHttpServiceBusinessSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NotificationApiHttpServiceBusinessSpec.scala index dd746ae35d6..adba8dfb59f 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NotificationApiHttpServiceBusinessSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NotificationApiHttpServiceBusinessSpec.scala @@ -7,8 +7,8 @@ import org.scalatest.freespec.AnyFreeSpecLike import pl.touk.nussknacker.engine.api.process.ProcessName import pl.touk.nussknacker.test.base.it.{NuItTest, WithSimplifiedConfigScenarioHelper} import pl.touk.nussknacker.test.config.{ + WithBusinessCaseRestAssuredUsersExtensions, WithMockableDeploymentManager, - WithSimplifiedConfigRestAssuredUsersExtensions, WithSimplifiedDesignerConfig } import pl.touk.nussknacker.test.{NuRestAssureMatchers, PatientScalaFutures, RestAssuredVerboseLogging} @@ -19,7 +19,7 @@ class NotificationApiHttpServiceBusinessSpec with WithSimplifiedDesignerConfig with WithSimplifiedConfigScenarioHelper with WithMockableDeploymentManager - with WithSimplifiedConfigRestAssuredUsersExtensions + with WithBusinessCaseRestAssuredUsersExtensions with NuRestAssureMatchers with RestAssuredVerboseLogging with PatientScalaFutures { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NotificationApiHttpServiceSecuritySpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NotificationApiHttpServiceSecuritySpec.scala index 29010de8001..b776366d32e 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NotificationApiHttpServiceSecuritySpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NotificationApiHttpServiceSecuritySpec.scala @@ -5,22 +5,22 @@ import io.restassured.module.scala.RestAssuredSupport.AddThenToResponse import org.hamcrest.Matchers.equalTo import org.scalatest.freespec.AnyFreeSpecLike import pl.touk.nussknacker.engine.api.process.ProcessName -import pl.touk.nussknacker.test.base.it.{NuItTest, WithRichConfigScenarioHelper} -import pl.touk.nussknacker.test.config.WithRichDesignerConfig.TestCategory.{Category1, Category2} +import pl.touk.nussknacker.test.base.it.{NuItTest, WithAccessControlCheckingConfigScenarioHelper} +import pl.touk.nussknacker.test.config.WithAccessControlCheckingDesignerConfig.TestCategory.{Category1, Category2} import pl.touk.nussknacker.test.config.{ - WithMockableDeploymentManager, - WithRichConfigRestAssuredUsersExtensions, - WithRichDesignerConfig + WithAccessControlCheckingConfigRestAssuredUsersExtensions, + WithAccessControlCheckingDesignerConfig, + WithMockableDeploymentManager } import pl.touk.nussknacker.test.{NuRestAssureMatchers, PatientScalaFutures, RestAssuredVerboseLogging} class NotificationApiHttpServiceSecuritySpec extends AnyFreeSpecLike with NuItTest - with WithRichDesignerConfig - with WithRichConfigScenarioHelper + with WithAccessControlCheckingDesignerConfig + with WithAccessControlCheckingConfigScenarioHelper with WithMockableDeploymentManager - with WithRichConfigRestAssuredUsersExtensions + with WithAccessControlCheckingConfigRestAssuredUsersExtensions with NuRestAssureMatchers with RestAssuredVerboseLogging with PatientScalaFutures { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ProcessesResourcesSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ProcessesResourcesSpec.scala index a5993412ce7..f71283729f5 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ProcessesResourcesSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ProcessesResourcesSpec.scala @@ -24,10 +24,13 @@ import pl.touk.nussknacker.restmodel.scenariodetails.ScenarioWithDetails import pl.touk.nussknacker.restmodel.validation.ValidationResults.ValidationResult import pl.touk.nussknacker.test.PatientScalaFutures import pl.touk.nussknacker.test.base.it._ -import pl.touk.nussknacker.test.config.WithRichDesignerConfig.TestCategory.{Category1, Category2} -import pl.touk.nussknacker.test.config.WithRichDesignerConfig.TestProcessingType.{Streaming1, Streaming2} -import pl.touk.nussknacker.test.config.WithRichDesignerConfig.{TestCategory, TestProcessingType} -import pl.touk.nussknacker.test.config.{WithMockableDeploymentManager, WithRichDesignerConfig} +import pl.touk.nussknacker.test.config.WithAccessControlCheckingDesignerConfig.TestCategory.{Category1, Category2} +import pl.touk.nussknacker.test.config.WithAccessControlCheckingDesignerConfig.TestProcessingType.{ + Streaming1, + Streaming2 +} +import pl.touk.nussknacker.test.config.WithAccessControlCheckingDesignerConfig.{TestCategory, TestProcessingType} +import pl.touk.nussknacker.test.config.{WithAccessControlCheckingDesignerConfig, WithMockableDeploymentManager} import pl.touk.nussknacker.test.utils.scalas.AkkaHttpExtensions.toRequestEntity import pl.touk.nussknacker.ui.config.scenariotoolbar.CategoriesScenarioToolbarsConfigParser import pl.touk.nussknacker.ui.config.scenariotoolbar.ToolbarButtonConfigType.{CustomLink, ProcessDeploy, ProcessSave} @@ -54,8 +57,8 @@ import scala.concurrent.Future class ProcessesResourcesSpec extends AnyFunSuite with NuItTest - with WithRichDesignerConfig - with WithRichConfigScenarioHelper + with WithAccessControlCheckingDesignerConfig + with WithAccessControlCheckingConfigScenarioHelper with WithMockableDeploymentManager with ScalatestRouteTest with Matchers diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioActivityApiHttpServiceBusinessSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioActivityApiHttpServiceBusinessSpec.scala index 8b4133d13c3..f2dd34d6dd5 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioActivityApiHttpServiceBusinessSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioActivityApiHttpServiceBusinessSpec.scala @@ -8,8 +8,8 @@ import pl.touk.nussknacker.engine.build.ScenarioBuilder import pl.touk.nussknacker.test.{NuRestAssureExtensions, NuRestAssureMatchers, RestAssuredVerboseLogging} import pl.touk.nussknacker.test.base.it.{NuItTest, WithSimplifiedConfigScenarioHelper} import pl.touk.nussknacker.test.config.{ + WithBusinessCaseRestAssuredUsersExtensions, WithMockableDeploymentManager, - WithSimplifiedConfigRestAssuredUsersExtensions, WithSimplifiedDesignerConfig } @@ -21,7 +21,7 @@ class ScenarioActivityApiHttpServiceBusinessSpec with WithSimplifiedDesignerConfig with WithSimplifiedConfigScenarioHelper with WithMockableDeploymentManager - with WithSimplifiedConfigRestAssuredUsersExtensions + with WithBusinessCaseRestAssuredUsersExtensions with NuRestAssureExtensions with NuRestAssureMatchers with RestAssuredVerboseLogging { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioActivityApiHttpServiceSecuritySpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioActivityApiHttpServiceSecuritySpec.scala index d83008ada9e..c45b57a1088 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioActivityApiHttpServiceSecuritySpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioActivityApiHttpServiceSecuritySpec.scala @@ -5,21 +5,21 @@ import io.restassured.module.scala.RestAssuredSupport.AddThenToResponse import org.scalatest.freespec.AnyFreeSpecLike import pl.touk.nussknacker.engine.build.ScenarioBuilder import pl.touk.nussknacker.test.{NuRestAssureMatchers, RestAssuredVerboseLogging} -import pl.touk.nussknacker.test.base.it.{NuItTest, WithRichConfigScenarioHelper} -import pl.touk.nussknacker.test.config.WithRichDesignerConfig.TestCategory.{Category1, Category2} +import pl.touk.nussknacker.test.base.it.{NuItTest, WithAccessControlCheckingConfigScenarioHelper} +import pl.touk.nussknacker.test.config.WithAccessControlCheckingDesignerConfig.TestCategory.{Category1, Category2} import pl.touk.nussknacker.test.config.{ - WithMockableDeploymentManager, - WithRichConfigRestAssuredUsersExtensions, - WithRichDesignerConfig + WithAccessControlCheckingConfigRestAssuredUsersExtensions, + WithAccessControlCheckingDesignerConfig, + WithMockableDeploymentManager } class ScenarioActivityApiHttpServiceSecuritySpec extends AnyFreeSpecLike with NuItTest - with WithRichDesignerConfig - with WithRichConfigScenarioHelper + with WithAccessControlCheckingDesignerConfig + with WithAccessControlCheckingConfigScenarioHelper with WithMockableDeploymentManager - with WithRichConfigRestAssuredUsersExtensions + with WithAccessControlCheckingConfigRestAssuredUsersExtensions with NuRestAssureMatchers with RestAssuredVerboseLogging { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioParametersApiHttpServiceBusinessSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioParametersApiHttpServiceBusinessSpec.scala index fedd1cfeeee..1fe9757d178 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioParametersApiHttpServiceBusinessSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioParametersApiHttpServiceBusinessSpec.scala @@ -4,14 +4,14 @@ import io.restassured.RestAssured.`given` import io.restassured.module.scala.RestAssuredSupport.AddThenToResponse import org.scalatest.freespec.AnyFreeSpecLike import pl.touk.nussknacker.test.base.it.NuItTest -import pl.touk.nussknacker.test.config.{WithSimplifiedConfigRestAssuredUsersExtensions, WithSimplifiedDesignerConfig} +import pl.touk.nussknacker.test.config.{WithBusinessCaseRestAssuredUsersExtensions, WithSimplifiedDesignerConfig} import pl.touk.nussknacker.test.{NuRestAssureMatchers, PatientScalaFutures, RestAssuredVerboseLogging} class ScenarioParametersApiHttpServiceBusinessSpec extends AnyFreeSpecLike with NuItTest with WithSimplifiedDesignerConfig - with WithSimplifiedConfigRestAssuredUsersExtensions + with WithBusinessCaseRestAssuredUsersExtensions with NuRestAssureMatchers with RestAssuredVerboseLogging with PatientScalaFutures { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioParametersApiHttpServiceSecuritySpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioParametersApiHttpServiceSecuritySpec.scala index 81820931cd5..132bcd41f65 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioParametersApiHttpServiceSecuritySpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioParametersApiHttpServiceSecuritySpec.scala @@ -4,14 +4,17 @@ import io.restassured.RestAssured.`given` import io.restassured.module.scala.RestAssuredSupport.AddThenToResponse import org.scalatest.freespec.AnyFreeSpecLike import pl.touk.nussknacker.test.base.it.NuItTest -import pl.touk.nussknacker.test.config.{WithRichConfigRestAssuredUsersExtensions, WithRichDesignerConfig} +import pl.touk.nussknacker.test.config.{ + WithAccessControlCheckingConfigRestAssuredUsersExtensions, + WithAccessControlCheckingDesignerConfig +} import pl.touk.nussknacker.test.{NuRestAssureMatchers, PatientScalaFutures, RestAssuredVerboseLogging} class ScenarioParametersApiHttpServiceSecuritySpec extends AnyFreeSpecLike with NuItTest - with WithRichDesignerConfig - with WithRichConfigRestAssuredUsersExtensions + with WithAccessControlCheckingDesignerConfig + with WithAccessControlCheckingConfigRestAssuredUsersExtensions with NuRestAssureMatchers with RestAssuredVerboseLogging with PatientScalaFutures { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/UserApiHttpServiceBusinessSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/UserApiHttpServiceBusinessSpec.scala index 1f35e8d6941..cdf3e1fca4c 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/UserApiHttpServiceBusinessSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/UserApiHttpServiceBusinessSpec.scala @@ -6,13 +6,13 @@ import org.hamcrest.Matchers.equalTo import org.scalatest.freespec.AnyFreeSpecLike import pl.touk.nussknacker.test.{NuRestAssureMatchers, PatientScalaFutures, RestAssuredVerboseLogging} import pl.touk.nussknacker.test.base.it.NuItTest -import pl.touk.nussknacker.test.config.{WithSimplifiedConfigRestAssuredUsersExtensions, WithSimplifiedDesignerConfig} +import pl.touk.nussknacker.test.config.{WithBusinessCaseRestAssuredUsersExtensions, WithSimplifiedDesignerConfig} class UserApiHttpServiceBusinessSpec extends AnyFreeSpecLike with NuItTest with WithSimplifiedDesignerConfig - with WithSimplifiedConfigRestAssuredUsersExtensions + with WithBusinessCaseRestAssuredUsersExtensions with NuRestAssureMatchers with RestAssuredVerboseLogging with PatientScalaFutures { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/UserApiHttpServiceCategoryUsedMoreThanOnceConfigSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/UserApiHttpServiceCategoryUsedMoreThanOnceConfigSpec.scala new file mode 100644 index 00000000000..cd4337469a7 --- /dev/null +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/UserApiHttpServiceCategoryUsedMoreThanOnceConfigSpec.scala @@ -0,0 +1,44 @@ +package pl.touk.nussknacker.ui.api + +import io.restassured.RestAssured.given +import io.restassured.module.scala.RestAssuredSupport.AddThenToResponse +import org.scalatest.freespec.AnyFreeSpecLike +import pl.touk.nussknacker.test.base.it.NuItTest +import pl.touk.nussknacker.test.config.WithCategoryUsedMoreThanOnceDesignerConfig.TestCategory +import pl.touk.nussknacker.test.config.{ + WithBusinessCaseRestAssuredUsersExtensions, + WithCategoryUsedMoreThanOnceDesignerConfig +} +import pl.touk.nussknacker.test.{NuRestAssureMatchers, PatientScalaFutures, RestAssuredVerboseLogging} + +class UserApiHttpServiceCategoryUsedMoreThanOnceConfigSpec + extends AnyFreeSpecLike + with NuItTest + with WithCategoryUsedMoreThanOnceDesignerConfig + with WithBusinessCaseRestAssuredUsersExtensions + with NuRestAssureMatchers + with RestAssuredVerboseLogging + with PatientScalaFutures { + + "In designer configured with multiple processing types using the same category" - { + "The endpoint for getting user info should" - { + "return not duplicated categories" in { + given() + .when() + .basicAuthAdmin() + .get(s"$nuDesignerHttpAddress/api/user") + .Then() + .statusCode(200) + .equalsJsonBody(s"""{ + | "id": "admin", + | "username": "admin", + | "isAdmin": true, + | "categories": ["${TestCategory.Category1}"], + | "categoryPermissions": {}, + | "globalPermissions": [] + |}""".stripMargin) + } + } + } + +} diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/UserApiHttpServiceSecuritySpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/UserApiHttpServiceSecuritySpec.scala index 7c26ada9047..23ecd9abed8 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/UserApiHttpServiceSecuritySpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/UserApiHttpServiceSecuritySpec.scala @@ -5,14 +5,17 @@ import io.restassured.module.scala.RestAssuredSupport.AddThenToResponse import org.hamcrest.Matchers.equalTo import org.scalatest.freespec.AnyFreeSpecLike import pl.touk.nussknacker.test.base.it.NuItTest -import pl.touk.nussknacker.test.config.{WithRichConfigRestAssuredUsersExtensions, WithRichDesignerConfig} +import pl.touk.nussknacker.test.config.{ + WithAccessControlCheckingConfigRestAssuredUsersExtensions, + WithAccessControlCheckingDesignerConfig +} import pl.touk.nussknacker.test.{NuRestAssureMatchers, PatientScalaFutures, RestAssuredVerboseLogging} class UserApiHttpServiceSecuritySpec extends AnyFreeSpecLike with NuItTest - with WithRichDesignerConfig - with WithRichConfigRestAssuredUsersExtensions + with WithAccessControlCheckingDesignerConfig + with WithAccessControlCheckingConfigRestAssuredUsersExtensions with NuRestAssureMatchers with RestAssuredVerboseLogging with PatientScalaFutures { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/DBProcessServiceSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/DBProcessServiceSpec.scala index 13a656ef533..541d4311b3b 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/DBProcessServiceSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/DBProcessServiceSpec.scala @@ -14,8 +14,8 @@ import pl.touk.nussknacker.security.Permission import pl.touk.nussknacker.test.PatientScalaFutures import pl.touk.nussknacker.test.utils.domain.TestProcessUtil.{createFragmentEntity, createScenarioEntity} import pl.touk.nussknacker.test.config.ConfigWithScalaVersion -import pl.touk.nussknacker.test.config.WithRichDesignerConfig.TestCategory -import pl.touk.nussknacker.test.config.WithRichDesignerConfig.TestCategory.{Category1, Category2} +import pl.touk.nussknacker.test.config.WithAccessControlCheckingDesignerConfig.TestCategory +import pl.touk.nussknacker.test.config.WithAccessControlCheckingDesignerConfig.TestCategory.{Category1, Category2} import pl.touk.nussknacker.test.mock.MockFetchingProcessRepository import pl.touk.nussknacker.test.utils.domain.{ProcessTestData, TestFactory} import pl.touk.nussknacker.ui.NuDesignerError From 1725f69f11008e06d7eb13add59f078b8f20fc02 Mon Sep 17 00:00:00 2001 From: Dawid Poliszak Date: Wed, 20 Mar 2024 12:05:05 +0100 Subject: [PATCH 4/4] [NU-1544] change of scenario name to the same value force user to save scenario (#5763) * NU-1554 change of scenario name to the same value force user to save scenario * NU-1554 fix types --- .../client/src/actions/nk/calculateProcessAfterChange.ts | 5 ++++- designer/client/src/common/ProcessUtils.ts | 2 +- designer/client/src/components/graph/NodeUtils.ts | 4 ++-- .../src/components/graph/node-modal/DescriptionField.tsx | 4 ++-- .../client/src/components/graph/node-modal/IdField.tsx | 4 ++-- .../client/src/components/graph/node-modal/NodeField.tsx | 4 ++-- .../src/components/graph/node-modal/ScenarioProperty.tsx | 6 +++++- designer/client/src/types/node.ts | 9 +++++---- 8 files changed, 23 insertions(+), 15 deletions(-) diff --git a/designer/client/src/actions/nk/calculateProcessAfterChange.ts b/designer/client/src/actions/nk/calculateProcessAfterChange.ts index 54a39e88523..b7bc80c9b43 100644 --- a/designer/client/src/actions/nk/calculateProcessAfterChange.ts +++ b/designer/client/src/actions/nk/calculateProcessAfterChange.ts @@ -30,9 +30,12 @@ export function calculateProcessAfterChange( if (after.id !== before.id) { dispatch({ type: "PROCESS_RENAME", name: after.id }); } + + const { id, ...properties } = after; + return { processName: after.id, - scenarioGraph: { ...processWithNewFragmentSchema, properties: after }, + scenarioGraph: { ...processWithNewFragmentSchema, properties }, }; } diff --git a/designer/client/src/common/ProcessUtils.ts b/designer/client/src/common/ProcessUtils.ts index b3f8915145d..4854cc75fd1 100644 --- a/designer/client/src/common/ProcessUtils.ts +++ b/designer/client/src/common/ProcessUtils.ts @@ -1,5 +1,5 @@ /* eslint-disable i18next/no-literal-string */ -import { flatten, isEmpty, isEqual, map, omit, pickBy, transform } from "lodash"; +import { flatten, isEmpty, isEqual, omit, pickBy, transform } from "lodash"; import { ComponentDefinition, NodeId, diff --git a/designer/client/src/components/graph/NodeUtils.ts b/designer/client/src/components/graph/NodeUtils.ts index 7f4e5fc3089..7f3c178b37c 100644 --- a/designer/client/src/components/graph/NodeUtils.ts +++ b/designer/client/src/components/graph/NodeUtils.ts @@ -22,7 +22,7 @@ class NodeUtils { return !isEmpty(obj) && has(obj, "id") && has(obj, "type"); }; - nodeType = (node: NodeType) => { + nodeType = (node: UINodeType) => { return node.type ? node.type : "Properties"; }; @@ -31,7 +31,7 @@ class NodeUtils { return type === "Properties"; }; - nodeIsFragment = (node): node is FragmentNodeType => { + nodeIsFragment = (node: UINodeType): node is FragmentNodeType => { return this.nodeType(node) === "FragmentInput"; }; diff --git a/designer/client/src/components/graph/node-modal/DescriptionField.tsx b/designer/client/src/components/graph/node-modal/DescriptionField.tsx index 0a862dcfbeb..d11ef998f60 100644 --- a/designer/client/src/components/graph/node-modal/DescriptionField.tsx +++ b/designer/client/src/components/graph/node-modal/DescriptionField.tsx @@ -2,13 +2,13 @@ import { NodeField } from "./NodeField"; import { FieldType } from "./editors/field/Field"; import React from "react"; -import { NodeType, NodeValidationError } from "../../../types"; +import { NodeType, NodeValidationError, UINodeType } from "../../../types"; interface DescriptionFieldProps { autoFocus?: boolean; defaultValue?: string; isEditMode?: boolean; - node: NodeType; + node: UINodeType; readonly?: boolean; renderFieldLabel: (paramName: string) => JSX.Element; setProperty: (property: K, newValue: NodeType[K], defaultValue?: NodeType[K]) => void; diff --git a/designer/client/src/components/graph/node-modal/IdField.tsx b/designer/client/src/components/graph/node-modal/IdField.tsx index ec9841ecd0e..793cb540e89 100644 --- a/designer/client/src/components/graph/node-modal/IdField.tsx +++ b/designer/client/src/components/graph/node-modal/IdField.tsx @@ -2,7 +2,7 @@ import { extendErrors, getValidationErrorsForField, uniqueScenarioValueValidator import Field, { FieldType } from "./editors/field/Field"; import React, { useMemo } from "react"; import { useDiffMark } from "./PathsToMark"; -import { NodeType, NodeValidationError } from "../../../types"; +import { NodeType, NodeValidationError, UINodeType } from "../../../types"; import { useSelector } from "react-redux"; import { getProcessNodesIds } from "../../../reducers/selectors/graph"; import NodeUtils from "../NodeUtils"; @@ -10,7 +10,7 @@ import { isEmpty } from "lodash"; interface IdFieldProps { isEditMode?: boolean; - node: NodeType; + node: UINodeType; renderFieldLabel: (paramName: string) => JSX.Element; setProperty?: (property: K, newValue: NodeType[K], defaultValue?: NodeType[K]) => void; showValidation?: boolean; diff --git a/designer/client/src/components/graph/node-modal/NodeField.tsx b/designer/client/src/components/graph/node-modal/NodeField.tsx index 022f605f92e..665907e04a4 100644 --- a/designer/client/src/components/graph/node-modal/NodeField.tsx +++ b/designer/client/src/components/graph/node-modal/NodeField.tsx @@ -3,7 +3,7 @@ import { getValidationErrorsForField } from "./editors/Validators"; import { get, isEmpty } from "lodash"; import React from "react"; import { useDiffMark } from "./PathsToMark"; -import { NodeType, NodeValidationError } from "../../../types"; +import { NodeType, NodeValidationError, UINodeType } from "../../../types"; type NodeFieldProps = { autoFocus?: boolean; @@ -12,7 +12,7 @@ type NodeFieldProps = { fieldName: N; fieldType: FieldType; isEditMode?: boolean; - node: NodeType; + node: UINodeType; readonly?: boolean; renderFieldLabel: (paramName: string) => JSX.Element; setProperty: (property: K, newValue: NodeType[K], defaultValue?: NodeType[K]) => void; diff --git a/designer/client/src/components/graph/node-modal/ScenarioProperty.tsx b/designer/client/src/components/graph/node-modal/ScenarioProperty.tsx index a0c54319008..39d0da273cb 100644 --- a/designer/client/src/components/graph/node-modal/ScenarioProperty.tsx +++ b/designer/client/src/components/graph/node-modal/ScenarioProperty.tsx @@ -17,7 +17,11 @@ interface Props { propertyName: string; propertyConfig: ScenarioPropertyConfig; editedNode: PropertiesType; - onChange: (property: K, newValue: PropertiesType[K], defaultValue?: PropertiesType[K]) => void; + onChange: ( + property: K, + newValue: PropertiesType["additionalFields"]["properties"][K], + defaultValue?: PropertiesType["additionalFields"]["properties"][K], + ) => void; renderFieldLabel: (paramName: string) => JSX.Element; readOnly: boolean; errors: NodeValidationError[]; diff --git a/designer/client/src/types/node.ts b/designer/client/src/types/node.ts index a2d2dced57d..1953506ab78 100644 --- a/designer/client/src/types/node.ts +++ b/designer/client/src/types/node.ts @@ -38,7 +38,7 @@ export type NodeType = { id: string; parameters?: $TodoType[]; }; - nodeType: string; + nodeType?: string; [key: string]: any; }; @@ -59,12 +59,13 @@ export interface Expression { expression: string; } -//TODO: Add other process properties... -export type PropertiesType = NodeType & { +export type PropertiesType = { + // FE applies fake id as name, but it's not send by/to BE + id?: string; type: "Properties"; additionalFields: ProcessAdditionalFields; }; export type NodeId = NodeType["id"]; -export type UINodeType = NodeType; +export type UINodeType = NodeType | PropertiesType;