From 87f8e1a52845cd1821d7a76c2469717e8392303d Mon Sep 17 00:00:00 2001 From: ewezy Date: Fri, 9 Sep 2022 16:40:53 +0800 Subject: [PATCH 01/16] Update type ensembler options to allow std ensemblers for custom exp engines --- .../form/components/ensembler_config/typeOptions.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ui/src/router/components/form/components/ensembler_config/typeOptions.js b/ui/src/router/components/form/components/ensembler_config/typeOptions.js index 9de1b1e0b..9b52a5998 100644 --- a/ui/src/router/components/form/components/ensembler_config/typeOptions.js +++ b/ui/src/router/components/form/components/ensembler_config/typeOptions.js @@ -63,10 +63,7 @@ export const ensemblerTypeOptions = (engineProps) => { } // Ensembler must be selected when there is an experiment engine const ensemblerOptions = typeOptions.filter((o) => o.value !== "nop"); - if ( - !engineProps?.standard_experiment_manager_config - ?.experiment_selection_enabled - ) { + if (engineProps?.standard_experiment_manager_config?.experiment_selection_enabled === false) { // Standard Ensembler is not available when experiment selection is disabled return ensemblerOptions.filter((o) => o.value !== "standard"); } From cdaceecdb95f9e22b8309f64b0e2b5c55d99e60e Mon Sep 17 00:00:00 2001 From: ewezy Date: Mon, 12 Sep 2022 18:45:46 +0800 Subject: [PATCH 02/16] Make create and edit router consume standard ensembler component from exp engine ui --- .../StandardEnsemblerLoaderComponent.js | 57 ++++++++++++ .../StandardEnsemblerFormGroup.js | 91 ++++++++++++++++--- .../components/form/steps/EnsemblerStep.js | 3 +- 3 files changed, 138 insertions(+), 13 deletions(-) create mode 100644 ui/src/components/ensembler/StandardEnsemblerLoaderComponent.js diff --git a/ui/src/components/ensembler/StandardEnsemblerLoaderComponent.js b/ui/src/components/ensembler/StandardEnsemblerLoaderComponent.js new file mode 100644 index 000000000..ea9765742 --- /dev/null +++ b/ui/src/components/ensembler/StandardEnsemblerLoaderComponent.js @@ -0,0 +1,57 @@ +import React, { useEffect, useState } from "react"; + +import useDynamicScript from "../../hooks/useDynamicScript"; + +// Renderless component wrapper +const LoadDynamicScript = ({ url, setReady, setFailed }) => { + const { ready, failed } = useDynamicScript({ + url: url, + }); + + useEffect(() => { + setReady(ready); + setFailed(failed); + }, [setReady, setFailed, ready, failed]); + + return null; +}; + +// Dynamic Script Loading component wrapper +export const StandardEnsemblerLoaderComponent = ({ + FallbackView, + experimentEngine, + children, +}) => { + const [urlReady, setUrlReady] = useState(false); + const [urlFailed, setUrlFailed] = useState(false); + const [configReady, setConfigReady] = useState(false); + const [configFailed, setConfigFailed] = useState(false); + + return urlFailed ? ( + + ) : configFailed ? ( + + ) : !urlReady || !configReady ? ( + <> + {!!experimentEngine.url && !urlReady && ( + + )} + {!!experimentEngine.config && !configReady && ( + + )} + + + ) : ( + children + ); +}; + +export default StandardEnsemblerLoaderComponent; diff --git a/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js b/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js index 4118c84c4..1e9300e9c 100644 --- a/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js +++ b/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js @@ -1,5 +1,5 @@ -import React, { useEffect } from "react"; -import { EuiFlexItem, EuiText } from "@elastic/eui"; +import React, {useContext, useEffect} from "react"; +import {EuiFlexItem, EuiSpacer, EuiText} from "@elastic/eui"; import { get } from "../../../../../../components/form/utils"; import { StandardEnsembler } from "../../../../../../services/ensembler"; @@ -7,9 +7,51 @@ import { StandardEnsemblerPanel } from "./StandardEnsemblerPanel"; import { RouteSelectionPanel } from "../RouteSelectionPanel"; import { FormLabelWithToolTip } from "../../../../../../components/form/label_with_tooltip/FormLabelWithToolTip"; import { useOnChangeHandler } from "../../../../../../components/form/hooks/useOnChangeHandler"; +import ExperimentEngineContext from "../../../../../../providers/experiments/context"; +import { Panel } from "../../Panel"; +import { RemoteComponent } from "../../../../../../components/remote_component/RemoteComponent"; +import StandardEnsemblerLoaderComponent from "../../../../../../components/ensembler/StandardEnsemblerLoaderComponent"; + +const FallbackView = ({ text }) => ( + + + + {text} + + +); + +const StandardEnsemblerWithCustomExperimentEnginePanel = ({ + remoteUi, + projectId, + config, + onChangeHandler, + errors, +}) => { + // Load component from remote host + return ( + }> + + } + projectId={projectId} + config={config} + onChangeHandler={onChangeHandler} + errors={errors} + /> + + + ); +}; export const StandardEnsemblerFormGroup = ({ - experimentConfig = {}, + projectId, + experimentEngine = {}, routes, rules, default_traffic_rule, @@ -19,10 +61,14 @@ export const StandardEnsemblerFormGroup = ({ }) => { const { onChange } = useOnChangeHandler(onChangeHandler); + const { getEngineProperties, isLoaded } = useContext(ExperimentEngineContext); + useEffect(() => { !standardConfig && onChangeHandler(StandardEnsembler.newConfig()); }, [standardConfig, onChangeHandler]); + const engineProps = getEngineProperties(experimentEngine.type); + const routeOptions = [ { value: "nop", @@ -47,15 +93,36 @@ export const StandardEnsemblerFormGroup = ({ return ( !!standardConfig && ( <> - - - + {engineProps?.type === "standard" ? ( + + + + ) : isLoaded ? ( + + + + ) : ( + + + + )} + { {ensembler.type === "standard" && ( Date: Tue, 13 Sep 2022 18:32:30 +0800 Subject: [PATCH 03/16] Add route name path to standard ensembler config struct --- ui/src/services/ensembler/StandardEnsembler.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/src/services/ensembler/StandardEnsembler.js b/ui/src/services/ensembler/StandardEnsembler.js index 866e7387c..45a494fff 100644 --- a/ui/src/services/ensembler/StandardEnsembler.js +++ b/ui/src/services/ensembler/StandardEnsembler.js @@ -21,6 +21,7 @@ export class StandardEnsembler extends Ensembler { return { experiment_mappings: [], fallback_response_route_id: "", + route_name_path: "", }; } } From 65a184c80ed71208720e5135ee5a4d7b069183c3 Mon Sep 17 00:00:00 2001 From: ewezy Date: Wed, 14 Sep 2022 12:26:10 +0800 Subject: [PATCH 04/16] Clean up props to be passed to remote ensembler component --- .../StandardEnsemblerFormGroup.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js b/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js index 1e9300e9c..7382a14a7 100644 --- a/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js +++ b/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js @@ -1,5 +1,5 @@ -import React, {useContext, useEffect} from "react"; -import {EuiFlexItem, EuiSpacer, EuiText} from "@elastic/eui"; +import React, { useContext, useEffect } from "react"; +import { EuiFlexItem, EuiSpacer, EuiText } from "@elastic/eui"; import { get } from "../../../../../../components/form/utils"; import { StandardEnsembler } from "../../../../../../services/ensembler"; @@ -24,8 +24,8 @@ const FallbackView = ({ text }) => ( const StandardEnsemblerWithCustomExperimentEnginePanel = ({ remoteUi, projectId, - config, - onChangeHandler, + routeNamePath, + onChange, errors, }) => { // Load component from remote host @@ -40,8 +40,8 @@ const StandardEnsemblerWithCustomExperimentEnginePanel = ({ name="./EditStandardEnsemblerConfig" fallback={} projectId={projectId} - config={config} - onChangeHandler={onChangeHandler} + routeNamePath={routeNamePath} + onChange={onChange} errors={errors} /> @@ -112,9 +112,9 @@ export const StandardEnsemblerFormGroup = ({ url: "http://localhost:3002/xp/remoteEntry.js" }} projectId={projectId} - config={experimentEngine.config} - onChangeHandler={onChangeHandler} - errors={errors} + routeNamePath={standardConfig.route_name_path} + onChange={onChange("route_name_path")} + errors={get(errors, "route_name_path")} /> ) : ( From f42ba35876ec6309d78af82377c7d7e71c85d3c5 Mon Sep 17 00:00:00 2001 From: ewezy Date: Wed, 14 Sep 2022 18:45:57 +0800 Subject: [PATCH 05/16] Add routes prop to pass to the remote standard ensembler --- .../standard_ensembler/StandardEnsemblerFormGroup.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js b/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js index 7382a14a7..664d23b34 100644 --- a/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js +++ b/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js @@ -24,6 +24,7 @@ const FallbackView = ({ text }) => ( const StandardEnsemblerWithCustomExperimentEnginePanel = ({ remoteUi, projectId, + routes, routeNamePath, onChange, errors, @@ -40,6 +41,7 @@ const StandardEnsemblerWithCustomExperimentEnginePanel = ({ name="./EditStandardEnsemblerConfig" fallback={} projectId={projectId} + routes={routes} routeNamePath={routeNamePath} onChange={onChange} errors={errors} @@ -112,6 +114,7 @@ export const StandardEnsemblerFormGroup = ({ url: "http://localhost:3002/xp/remoteEntry.js" }} projectId={projectId} + routes={routes} routeNamePath={standardConfig.route_name_path} onChange={onChange("route_name_path")} errors={get(errors, "route_name_path")} From 6b1416eb991e8f5a12fa33fe849f3606f14e69a2 Mon Sep 17 00:00:00 2001 From: ewezy Date: Tue, 20 Sep 2022 12:19:16 +0800 Subject: [PATCH 06/16] Update typeOptions to return correct variant of standard ensembler --- .../components/ensembler_config/typeOptions.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/ui/src/router/components/form/components/ensembler_config/typeOptions.js b/ui/src/router/components/form/components/ensembler_config/typeOptions.js index 9b52a5998..a6a628631 100644 --- a/ui/src/router/components/form/components/ensembler_config/typeOptions.js +++ b/ui/src/router/components/form/components/ensembler_config/typeOptions.js @@ -14,6 +14,7 @@ const typeOptions = [ { value: "standard", inputDisplay: "Standard", + expenginetype: "standard", description: ( Turing will select the response from one of the routes, based on the @@ -21,6 +22,18 @@ const typeOptions = [ ), }, + { + value: "standard", + inputDisplay: "Standard", + expenginetype: "custom", + description: ( + + Turing will select the route response corresponding to the route name specified + in the treatment configuration. The route name path will be used to locate the + name of the route within the treatment configuration. + + ), + }, { value: "docker", inputDisplay: "Docker", @@ -62,7 +75,9 @@ export const ensemblerTypeOptions = (engineProps) => { return typeOptions.filter((o) => o.value !== "standard"); } // Ensembler must be selected when there is an experiment engine - const ensemblerOptions = typeOptions.filter((o) => o.value !== "nop"); + const ensemblerOptions = typeOptions.filter( + (o) => o.value !== "nop" && (o.expenginetype === engineProps.type || o.expenginetype === undefined) + ); if (engineProps?.standard_experiment_manager_config?.experiment_selection_enabled === false) { // Standard Ensembler is not available when experiment selection is disabled return ensemblerOptions.filter((o) => o.value !== "standard"); From d151b109fa48ef92ca7e6b82722af88c6143fa82 Mon Sep 17 00:00:00 2001 From: ewezy Date: Tue, 20 Sep 2022 19:37:21 +0800 Subject: [PATCH 07/16] Add standard ensembler config view --- .../components/EnsemblerConfigSection.js | 28 ++---- .../StandardConfigViewGroup.js | 96 +++++++++++++++++++ .../StandardEnsemblerFormGroup.js | 10 +- 3 files changed, 108 insertions(+), 26 deletions(-) create mode 100644 ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js diff --git a/ui/src/router/components/configuration/components/EnsemblerConfigSection.js b/ui/src/router/components/configuration/components/EnsemblerConfigSection.js index 9d165bdf1..4e17e7581 100644 --- a/ui/src/router/components/configuration/components/EnsemblerConfigSection.js +++ b/ui/src/router/components/configuration/components/EnsemblerConfigSection.js @@ -1,16 +1,15 @@ import React, { Fragment } from "react"; import { DockerConfigViewGroup } from "./docker_config_section/DockerConfigViewGroup"; -import { FallbackRouteConfigSection } from "./standard_config_section/FallbackRouteConfigSection"; -import { TreatmentMappingConfigSection } from "./standard_config_section/TreatmentMappingConfigSection"; import { ExperimentEngineContextProvider } from "../../../../providers/experiments/ExperimentEngineContextProvider"; import { NopConfigViewGroup } from "./nop_config_section/NopConfigViewGroup"; import { PyFuncConfigViewGroup } from "./pyfunc_config_section/PyFuncConfigViewGroup"; +import { StandardConfigViewGroup } from "./standard_config_section/StandardConfigViewGroup" import { EnsemblersContextContextProvider } from "../../../../providers/ensemblers/context"; -import { EuiFlexGroup, EuiFlexItem } from "@elastic/eui"; export const EnsemblerConfigSection = ({ projectId, config: { + routes, ensembler, experiment_engine: { type, config: experimentConfig }, }, @@ -38,22 +37,13 @@ export const EnsemblerConfigSection = ({ )} {ensembler.type === "standard" && ( - - - - - - - - + )} diff --git a/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js b/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js new file mode 100644 index 000000000..dbdfddc71 --- /dev/null +++ b/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js @@ -0,0 +1,96 @@ +import React, { useContext } from "react"; + +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from "@elastic/eui"; +import { TreatmentMappingConfigSection } from "./TreatmentMappingConfigSection"; +import { FallbackRouteConfigSection } from "./FallbackRouteConfigSection"; +import ExperimentEngineContext from "../../../../../providers/experiments/context"; +import { Panel} from "../../../form/components/Panel"; +import StandardEnsemblerLoaderComponent from "../../../../../components/ensembler/StandardEnsemblerLoaderComponent"; +import { RemoteComponent } from "../../../../../components/remote_component/RemoteComponent"; + +const FallbackView = ({ text }) => ( + + + + {text} + + +); + +const StandardEnsemblerWithCustomExperimentEngineConfigView = ({ + remoteUi, + projectId, + routes, + routeNamePath +}) => { + // Load component from remote host + return ( + }> + + } + projectId={projectId} + routes={routes} + routeNamePath={routeNamePath} + /> + + + ); +}; + + +export const StandardConfigViewGroup = ({ + projectId, + routes, + standardConfig, + experimentConfig, + type +}) => { + const { getEngineProperties, isLoaded } = useContext(ExperimentEngineContext); + + const engineProps = getEngineProperties(type); + + return ( + !!standardConfig && ( + + <> + {engineProps?.type === "standard" ? ( + + + + ) : isLoaded ? ( + + + + ) : ( + + + + )} + + + + + + + ) + ); +}; \ No newline at end of file diff --git a/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js b/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js index 664d23b34..237173d1c 100644 --- a/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js +++ b/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js @@ -14,7 +14,7 @@ import StandardEnsemblerLoaderComponent from "../../../../../../components/ensem const FallbackView = ({ text }) => ( - + {text} @@ -39,7 +39,7 @@ const StandardEnsemblerWithCustomExperimentEnginePanel = ({ } + fallback={} projectId={projectId} routes={routes} routeNamePath={routeNamePath} @@ -108,11 +108,7 @@ export const StandardEnsemblerFormGroup = ({ ) : isLoaded ? ( Date: Wed, 21 Sep 2022 11:24:48 +0800 Subject: [PATCH 08/16] Rename prop for StandardEnsemblerLoaderComponent --- .../ensembler/StandardEnsemblerLoaderComponent.js | 10 +++++----- .../standard_config_section/StandardConfigViewGroup.js | 5 ++--- .../standard_ensembler/StandardEnsemblerFormGroup.js | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/ui/src/components/ensembler/StandardEnsemblerLoaderComponent.js b/ui/src/components/ensembler/StandardEnsemblerLoaderComponent.js index ea9765742..1fff6e1fa 100644 --- a/ui/src/components/ensembler/StandardEnsemblerLoaderComponent.js +++ b/ui/src/components/ensembler/StandardEnsemblerLoaderComponent.js @@ -19,7 +19,7 @@ const LoadDynamicScript = ({ url, setReady, setFailed }) => { // Dynamic Script Loading component wrapper export const StandardEnsemblerLoaderComponent = ({ FallbackView, - experimentEngine, + remoteUi, children, }) => { const [urlReady, setUrlReady] = useState(false); @@ -33,18 +33,18 @@ export const StandardEnsemblerLoaderComponent = ({ ) : !urlReady || !configReady ? ( <> - {!!experimentEngine.url && !urlReady && ( + {!!remoteUi.url && !urlReady && ( )} - {!!experimentEngine.config && !configReady && ( + {!!remoteUi.config && !configReady && ( )} diff --git a/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js b/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js index dbdfddc71..9039ca805 100644 --- a/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js +++ b/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js @@ -4,7 +4,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from "@elastic/eui"; import { TreatmentMappingConfigSection } from "./TreatmentMappingConfigSection"; import { FallbackRouteConfigSection } from "./FallbackRouteConfigSection"; import ExperimentEngineContext from "../../../../../providers/experiments/context"; -import { Panel} from "../../../form/components/Panel"; +import { Panel } from "../../../form/components/Panel"; import StandardEnsemblerLoaderComponent from "../../../../../components/ensembler/StandardEnsemblerLoaderComponent"; import { RemoteComponent } from "../../../../../components/remote_component/RemoteComponent"; @@ -29,7 +29,7 @@ const StandardEnsemblerWithCustomExperimentEngineConfigView = ({ fallback={}> + remoteUi={remoteUi}> }> + remoteUi={remoteUi}> Date: Thu, 22 Sep 2022 11:12:42 +0800 Subject: [PATCH 09/16] Make status colours use default EUI palette as much as possible --- ui/src/services/job/JobStatus.js | 14 +++++++------- ui/src/services/status/Status.js | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ui/src/services/job/JobStatus.js b/ui/src/services/job/JobStatus.js index 300ec4c4b..36afcad68 100644 --- a/ui/src/services/job/JobStatus.js +++ b/ui/src/services/job/JobStatus.js @@ -3,22 +3,22 @@ import { Enum, EnumValue } from "../enum/Enum"; export const JobStatus = Enum({ PENDING: EnumValue("pending", { label: "Pending", - color: "#FEC514", + color: "warning", iconType: "clock", }), BUILDING: EnumValue("building", { label: "Pending", - color: "#FEC514", + color: "warning", iconType: "clock", }), RUNNING: EnumValue("running", { label: "Running", - color: "#00BFB3", + color: "success", iconType: "check", }), TERMINATING: EnumValue("terminating", { label: "Terminating", - color: "#BD271E", + color: "danger", iconType: "clock", }), TERMINATED: EnumValue("terminated", { @@ -32,17 +32,17 @@ export const JobStatus = Enum({ }), FAILED: EnumValue("failed", { label: "Failed", - color: "#BD271E", + color: "danger", iconType: "cross", }), FAILED_SUBMISSION: EnumValue("failed_submission", { label: "Failed", - color: "#BD271E", + color: "danger", iconType: "cross", }), FAILED_BUILDING: EnumValue("failed_building", { label: "Failed", - color: "#BD271E", + color: "danger", iconType: "cross", }), }); diff --git a/ui/src/services/status/Status.js b/ui/src/services/status/Status.js index 66c38faf8..10a01e26b 100644 --- a/ui/src/services/status/Status.js +++ b/ui/src/services/status/Status.js @@ -3,17 +3,17 @@ import { EnumValue, Enum } from "../enum/Enum"; export const Status = Enum({ DEPLOYED: EnumValue("deployed", { label: "Deployed", - color: "#00BFB3", + color: "success", iconType: "check", }), FAILED: EnumValue("failed", { label: "Failed", - color: "#BD271E", + color: "danger", iconType: "cross", }), PENDING: EnumValue("pending", { label: "Updating", - color: "#FEC514", + color: "warning", iconType: "clock", }), UNDEPLOYED: EnumValue("undeployed", { From 5a3a938971e3ad28c5b5cfa6fa0d96436c962347 Mon Sep 17 00:00:00 2001 From: ewezy Date: Fri, 23 Sep 2022 11:44:39 +0800 Subject: [PATCH 10/16] Remove redundant empty tags --- .../StandardConfigViewGroup.js | 56 +++++++++---------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js b/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js index 9039ca805..d632d484c 100644 --- a/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js +++ b/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js @@ -57,38 +57,36 @@ export const StandardConfigViewGroup = ({ return ( !!standardConfig && ( - <> - {engineProps?.type === "standard" ? ( - - - - ) : isLoaded ? ( - - - - ) : ( - - - - )} - + {engineProps?.type === "standard" ? ( + + + + ) : isLoaded ? ( - - + ) : ( + + + + )} + + + + ) ); From 620adc136da8e3cf6934e35b974eea6e0fcadc1c Mon Sep 17 00:00:00 2001 From: ewezy Date: Fri, 23 Sep 2022 12:00:09 +0800 Subject: [PATCH 11/16] Refactor components to use common RemoteLoaderComponent --- .../ExperimentEngineLoaderComponent.js | 57 ------------------- .../RemoteLoaderComponent.js} | 11 ++-- ui/src/experiment/ExperimentsRouter.js | 10 ++-- .../components/ExperimentConfigSection.js | 10 ++-- .../StandardConfigViewGroup.js | 10 ++-- .../StandardEnsemblerFormGroup.js | 10 ++-- .../ExperimentConfigPanel.js | 10 ++-- 7 files changed, 36 insertions(+), 82 deletions(-) delete mode 100644 ui/src/components/experiments/ExperimentEngineLoaderComponent.js rename ui/src/components/{ensembler/StandardEnsemblerLoaderComponent.js => remote_component/RemoteLoaderComponent.js} (74%) diff --git a/ui/src/components/experiments/ExperimentEngineLoaderComponent.js b/ui/src/components/experiments/ExperimentEngineLoaderComponent.js deleted file mode 100644 index 8e042845e..000000000 --- a/ui/src/components/experiments/ExperimentEngineLoaderComponent.js +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useEffect, useState } from "react"; - -import useDynamicScript from "../../hooks/useDynamicScript"; - -// Renderless component wrapper -const LoadDynamicScript = ({ url, setReady, setFailed }) => { - const { ready, failed } = useDynamicScript({ - url: url, - }); - - useEffect(() => { - setReady(ready); - setFailed(failed); - }, [setReady, setFailed, ready, failed]); - - return null; -}; - -// Dynamic Script Loading component wrapper -export const ExperimentEngineLoaderComponent = ({ - FallbackView, - experimentEngine, - children, -}) => { - const [urlReady, setUrlReady] = useState(false); - const [urlFailed, setUrlFailed] = useState(false); - const [configReady, setConfigReady] = useState(false); - const [configFailed, setConfigFailed] = useState(false); - - return urlFailed ? ( - - ) : configFailed ? ( - - ) : !urlReady || !configReady ? ( - <> - {!!experimentEngine.url && !urlReady && ( - - )} - {!!experimentEngine.config && !configReady && ( - - )} - - - ) : ( - children - ); -}; - -export default ExperimentEngineLoaderComponent; diff --git a/ui/src/components/ensembler/StandardEnsemblerLoaderComponent.js b/ui/src/components/remote_component/RemoteLoaderComponent.js similarity index 74% rename from ui/src/components/ensembler/StandardEnsemblerLoaderComponent.js rename to ui/src/components/remote_component/RemoteLoaderComponent.js index 1fff6e1fa..6d468b739 100644 --- a/ui/src/components/ensembler/StandardEnsemblerLoaderComponent.js +++ b/ui/src/components/remote_component/RemoteLoaderComponent.js @@ -17,9 +17,10 @@ const LoadDynamicScript = ({ url, setReady, setFailed }) => { }; // Dynamic Script Loading component wrapper -export const StandardEnsemblerLoaderComponent = ({ +export const RemoteLoaderComponent = ({ FallbackView, remoteUi, + componentName, children, }) => { const [urlReady, setUrlReady] = useState(false); @@ -28,9 +29,9 @@ export const StandardEnsemblerLoaderComponent = ({ const [configFailed, setConfigFailed] = useState(false); return urlFailed ? ( - + ) : configFailed ? ( - + ) : !urlReady || !configReady ? ( <> {!!remoteUi.url && !urlReady && ( @@ -47,11 +48,11 @@ export const StandardEnsemblerLoaderComponent = ({ url={remoteUi.config} /> )} - + ) : ( children ); }; -export default StandardEnsemblerLoaderComponent; +export default RemoteLoaderComponent; diff --git a/ui/src/experiment/ExperimentsRouter.js b/ui/src/experiment/ExperimentsRouter.js index bc0a9034d..5044464ec 100644 --- a/ui/src/experiment/ExperimentsRouter.js +++ b/ui/src/experiment/ExperimentsRouter.js @@ -3,7 +3,7 @@ import { EuiPageTemplate, } from "@elastic/eui"; import { RemoteComponent } from "../components/remote_component/RemoteComponent"; -import { ExperimentEngineLoaderComponent } from "../components/experiments/ExperimentEngineLoaderComponent"; +import RemoteLoaderComponent from "../components/remote_component/RemoteLoaderComponent"; import { useConfig } from "../config"; @@ -32,16 +32,18 @@ const RemoteRouter = ({ projectId }) => { return ( }> - + remoteUi={defaultExperimentEngine} + componentName="Experiment Engine" + > } projectId={projectId} /> - + ); }; diff --git a/ui/src/router/components/configuration/components/ExperimentConfigSection.js b/ui/src/router/components/configuration/components/ExperimentConfigSection.js index de7ee7464..a8e13146a 100644 --- a/ui/src/router/components/configuration/components/ExperimentConfigSection.js +++ b/ui/src/router/components/configuration/components/ExperimentConfigSection.js @@ -3,7 +3,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel } from "@elastic/eui"; import { ConfigSectionPanel } from "../../../../components/config_section"; import { RemoteComponent } from "../../../../components/remote_component/RemoteComponent"; -import { ExperimentEngineLoaderComponent } from "../../../../components/experiments/ExperimentEngineLoaderComponent"; +import RemoteLoaderComponent from "../../../../components/remote_component/RemoteLoaderComponent"; import ExperimentEngineContext from "../../../../providers/experiments/context"; import { StandardExperimentConfigGroup } from "./experiment_config_section/StandardExperimentConfigGroup"; @@ -29,9 +29,11 @@ const CustomExperimentConfigView = ({ projectId, remoteUi, config }) => { return ( }> - + remoteUi={remoteUi} + componentName="Experiment Engine" + > { projectId={projectId} config={config} /> - + ); }; diff --git a/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js b/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js index d632d484c..685f5409c 100644 --- a/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js +++ b/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js @@ -5,7 +5,7 @@ import { TreatmentMappingConfigSection } from "./TreatmentMappingConfigSection"; import { FallbackRouteConfigSection } from "./FallbackRouteConfigSection"; import ExperimentEngineContext from "../../../../../providers/experiments/context"; import { Panel } from "../../../form/components/Panel"; -import StandardEnsemblerLoaderComponent from "../../../../../components/ensembler/StandardEnsemblerLoaderComponent"; +import RemoteLoaderComponent from "../../../../../components/remote_component/RemoteLoaderComponent"; import { RemoteComponent } from "../../../../../components/remote_component/RemoteComponent"; const FallbackView = ({ text }) => ( @@ -27,9 +27,11 @@ const StandardEnsemblerWithCustomExperimentEngineConfigView = ({ return ( }> - + remoteUi={remoteUi} + componentName="Standard Ensembler" + > - + ); }; diff --git a/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js b/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js index 50a51bd44..6100edc8a 100644 --- a/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js +++ b/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js @@ -10,7 +10,7 @@ import { useOnChangeHandler } from "../../../../../../components/form/hooks/useO import ExperimentEngineContext from "../../../../../../providers/experiments/context"; import { Panel } from "../../Panel"; import { RemoteComponent } from "../../../../../../components/remote_component/RemoteComponent"; -import StandardEnsemblerLoaderComponent from "../../../../../../components/ensembler/StandardEnsemblerLoaderComponent"; +import RemoteLoaderComponent from "../../../../../../components/remote_component/RemoteLoaderComponent"; const FallbackView = ({ text }) => ( @@ -33,9 +33,11 @@ const StandardEnsemblerWithCustomExperimentEnginePanel = ({ return ( }> - + remoteUi={remoteUi} + componentName="Standard Ensembler" + > - + ); }; diff --git a/ui/src/router/components/form/components/experiment_config/ExperimentConfigPanel.js b/ui/src/router/components/form/components/experiment_config/ExperimentConfigPanel.js index d44025fdb..0d94be56b 100644 --- a/ui/src/router/components/form/components/experiment_config/ExperimentConfigPanel.js +++ b/ui/src/router/components/form/components/experiment_config/ExperimentConfigPanel.js @@ -3,7 +3,7 @@ import { EuiFlexItem, EuiSpacer } from "@elastic/eui"; import { RemoteComponent } from "../../../../../components/remote_component/RemoteComponent"; import ExperimentEngineContext from "../../../../../providers/experiments/context"; -import { ExperimentEngineLoaderComponent } from "../../../../../components/experiments/ExperimentEngineLoaderComponent"; +import RemoteLoaderComponent from "../../../../../components/remote_component/RemoteLoaderComponent"; import { Panel } from "../Panel"; import { StandardExperimentConfigGroup } from "./StandardExperimentConfigGroup"; @@ -28,9 +28,11 @@ const CustomExperimentEngineConfigGroup = ({ return ( }> - + remoteUi={remoteUi} + componentName="Experiment Engine" + > - + ); }; From 95faa7418334aeb127fd2586310819c9b93e8b4c Mon Sep 17 00:00:00 2001 From: ewezy Date: Fri, 23 Sep 2022 12:21:37 +0800 Subject: [PATCH 12/16] Replace colour hexes corresponding to subdued to default --- ui/src/services/job/JobStatus.js | 4 ++-- ui/src/services/status/Status.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/services/job/JobStatus.js b/ui/src/services/job/JobStatus.js index 36afcad68..8dac192af 100644 --- a/ui/src/services/job/JobStatus.js +++ b/ui/src/services/job/JobStatus.js @@ -23,11 +23,11 @@ export const JobStatus = Enum({ }), TERMINATED: EnumValue("terminated", { label: "Terminated", - color: "#6A717D", + color: "default", }), COMPLETED: EnumValue("completed", { label: "Completed", - color: "#6A717D", + color: "default", iconType: "check", }), FAILED: EnumValue("failed", { diff --git a/ui/src/services/status/Status.js b/ui/src/services/status/Status.js index 10a01e26b..928b09498 100644 --- a/ui/src/services/status/Status.js +++ b/ui/src/services/status/Status.js @@ -18,6 +18,6 @@ export const Status = Enum({ }), UNDEPLOYED: EnumValue("undeployed", { label: "Not Deployed", - color: "#6A717D", + color: "default", }), }); From 5f1cf8471ad0732215e73cb3e8cc8a2546938d07 Mon Sep 17 00:00:00 2001 From: ewezy Date: Mon, 26 Sep 2022 12:57:55 +0800 Subject: [PATCH 13/16] Add docs for standard ensembler --- .../create-a-router/configure-ensembler.md | 19 ++++++++++++++- engines/experiment/docs/developer_guide.md | 24 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/docs/how-to/create-a-router/configure-ensembler.md b/docs/how-to/create-a-router/configure-ensembler.md index 794ce9b2a..5659e0223 100644 --- a/docs/how-to/create-a-router/configure-ensembler.md +++ b/docs/how-to/create-a-router/configure-ensembler.md @@ -15,7 +15,11 @@ It is not possible to select as the final response a route that has traffic rule ## Standard Ensembler -For experiment engines configured to work with standard ensemblers (i.e., Standard Experiment Engines with experiment selection enabled), the router will return a response from one of the routes based on the configured mapping between routes and experiment treatments. At run time, the treatment returned by the engine will be used to select the corresponding route’s response. +There are two types of Standard Ensemblers available, one that is available with Standard Experiment Engines that +have experiment selection enabled, and the other with Custom Experiment Engines. + +### Standard Experiment Engines and Experiment Selection Enabled +For routers configured with Standard Experiment Engines that have experiment selection enabled, the router will return a response from one of the routes based on the configured mapping between routes and experiment treatments. At run time, the treatment returned by the engine will be used to select the corresponding route’s response. In addition, a fallback route may be configured whose results will be used at runtime when the call to the experiment engine fails or if a route mapping for the treatment generated by the experiment engine does not exist. @@ -25,6 +29,19 @@ In addition, a fallback route may be configured whose results will be used at ru It is not possible to select as the fallback response a route that has traffic rules associated to it. {% endhint %} +### Custom Experiment Engines +For routers configured with Custom Experiment Engines, the router will return a response from one of the routes +corresponding to the route name that is found within the treatment configuration returned by the experiment +engine. At run time, the router will attempt to access the route name within the treatment configuration received via a +user-configured path. + +A fallback route also has to be configured in order to capture cases whereby the route name found in the treatment +configuration does not correspond to any of the routes configured, or when the user-configured path is invalid with +respect to the treatment configuration received. + +The UI for Standard Ensemblers with Custom Experiment Engines is consumed directly as a remote component that is +implemented as part of the individual Custom Experiment Engines. + ## Docker Turing will deploy the specified image as a post-processor and will send in the request payload the following, for diff --git a/engines/experiment/docs/developer_guide.md b/engines/experiment/docs/developer_guide.md index 5fb314179..c62b9ab44 100644 --- a/engines/experiment/docs/developer_guide.md +++ b/engines/experiment/docs/developer_guide.md @@ -79,6 +79,30 @@ type IExperimentConfig = { [key: string]: IJsonValue }; }) => React.ReactElement ``` +In addition, custom experiment engines should also provide the following UI components to support the edit form and view +configuration components for Standard Ensemblers (see +[this](../../../docs/how-to/create-a-router/configure-ensembler.md) for more details). The `routeNamePath` field +should be configured to allow the Turing Router to access the route name from a treatment configuration received +from the experiment engine. + +```javascript +// EditStandardEnsemblerConfig component signature +({ + projectId: int, + routes: Array, + routeNamePath: string, + onChangeHandler: (React.ChangeEvent) => void, + errors: yup.ValidationError, +}) => React.ReactElement + +// StandardEnsemblerConfigDetails component signature. +({ + projectId: int, + routes: Array, + routeNamePath: string +}) => React.ReactElement +``` + ### Experiment Runner Experiment runners are required to implement the methods in the `ExperimentRunner` interface. This interface contains a single method to retrieve the treatment for a given request. From 4478b71ebfe0c51187482de31617784499a0fd25 Mon Sep 17 00:00:00 2001 From: ewezy Date: Mon, 26 Sep 2022 15:56:54 +0800 Subject: [PATCH 14/16] Remove redundant description for std ensembler and custom exp engines --- .../components/ensembler_config/typeOptions.js | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/ui/src/router/components/form/components/ensembler_config/typeOptions.js b/ui/src/router/components/form/components/ensembler_config/typeOptions.js index a6a628631..9b52a5998 100644 --- a/ui/src/router/components/form/components/ensembler_config/typeOptions.js +++ b/ui/src/router/components/form/components/ensembler_config/typeOptions.js @@ -14,7 +14,6 @@ const typeOptions = [ { value: "standard", inputDisplay: "Standard", - expenginetype: "standard", description: ( Turing will select the response from one of the routes, based on the @@ -22,18 +21,6 @@ const typeOptions = [ ), }, - { - value: "standard", - inputDisplay: "Standard", - expenginetype: "custom", - description: ( - - Turing will select the route response corresponding to the route name specified - in the treatment configuration. The route name path will be used to locate the - name of the route within the treatment configuration. - - ), - }, { value: "docker", inputDisplay: "Docker", @@ -75,9 +62,7 @@ export const ensemblerTypeOptions = (engineProps) => { return typeOptions.filter((o) => o.value !== "standard"); } // Ensembler must be selected when there is an experiment engine - const ensemblerOptions = typeOptions.filter( - (o) => o.value !== "nop" && (o.expenginetype === engineProps.type || o.expenginetype === undefined) - ); + const ensemblerOptions = typeOptions.filter((o) => o.value !== "nop"); if (engineProps?.standard_experiment_manager_config?.experiment_selection_enabled === false) { // Standard Ensembler is not available when experiment selection is disabled return ensemblerOptions.filter((o) => o.value !== "standard"); From 572fe8d20e4054cb849f207dc4078418ccbf5448 Mon Sep 17 00:00:00 2001 From: ewezy Date: Mon, 26 Sep 2022 16:04:51 +0800 Subject: [PATCH 15/16] Rename generic component loader as ExperimentEngineComponentLoader --- ...oaderComponent.js => ExperimentEngineComponentLoader.js} | 4 ++-- ui/src/experiment/ExperimentsRouter.js | 6 +++--- .../configuration/components/ExperimentConfigSection.js | 6 +++--- .../standard_config_section/StandardConfigViewGroup.js | 6 +++--- .../standard_ensembler/StandardEnsemblerFormGroup.js | 6 +++--- .../components/experiment_config/ExperimentConfigPanel.js | 6 +++--- 6 files changed, 17 insertions(+), 17 deletions(-) rename ui/src/components/remote_component/{RemoteLoaderComponent.js => ExperimentEngineComponentLoader.js} (93%) diff --git a/ui/src/components/remote_component/RemoteLoaderComponent.js b/ui/src/components/remote_component/ExperimentEngineComponentLoader.js similarity index 93% rename from ui/src/components/remote_component/RemoteLoaderComponent.js rename to ui/src/components/remote_component/ExperimentEngineComponentLoader.js index 6d468b739..bf98690a7 100644 --- a/ui/src/components/remote_component/RemoteLoaderComponent.js +++ b/ui/src/components/remote_component/ExperimentEngineComponentLoader.js @@ -17,7 +17,7 @@ const LoadDynamicScript = ({ url, setReady, setFailed }) => { }; // Dynamic Script Loading component wrapper -export const RemoteLoaderComponent = ({ +export const ExperimentEngineComponentLoader = ({ FallbackView, remoteUi, componentName, @@ -55,4 +55,4 @@ export const RemoteLoaderComponent = ({ ); }; -export default RemoteLoaderComponent; +export default ExperimentEngineComponentLoader; diff --git a/ui/src/experiment/ExperimentsRouter.js b/ui/src/experiment/ExperimentsRouter.js index 5044464ec..1d3187d84 100644 --- a/ui/src/experiment/ExperimentsRouter.js +++ b/ui/src/experiment/ExperimentsRouter.js @@ -3,7 +3,7 @@ import { EuiPageTemplate, } from "@elastic/eui"; import { RemoteComponent } from "../components/remote_component/RemoteComponent"; -import RemoteLoaderComponent from "../components/remote_component/RemoteLoaderComponent"; +import ExperimentEngineComponentLoader from "../components/remote_component/ExperimentEngineComponentLoader"; import { useConfig } from "../config"; @@ -32,7 +32,7 @@ const RemoteRouter = ({ projectId }) => { return ( }> - { fallback={} projectId={projectId} /> - + ); }; diff --git a/ui/src/router/components/configuration/components/ExperimentConfigSection.js b/ui/src/router/components/configuration/components/ExperimentConfigSection.js index a8e13146a..1858ba870 100644 --- a/ui/src/router/components/configuration/components/ExperimentConfigSection.js +++ b/ui/src/router/components/configuration/components/ExperimentConfigSection.js @@ -3,7 +3,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel } from "@elastic/eui"; import { ConfigSectionPanel } from "../../../../components/config_section"; import { RemoteComponent } from "../../../../components/remote_component/RemoteComponent"; -import RemoteLoaderComponent from "../../../../components/remote_component/RemoteLoaderComponent"; +import ExperimentEngineComponentLoader from "../../../../components/remote_component/ExperimentEngineComponentLoader"; import ExperimentEngineContext from "../../../../providers/experiments/context"; import { StandardExperimentConfigGroup } from "./experiment_config_section/StandardExperimentConfigGroup"; @@ -29,7 +29,7 @@ const CustomExperimentConfigView = ({ projectId, remoteUi, config }) => { return ( }> - { projectId={projectId} config={config} /> - + ); }; diff --git a/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js b/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js index 685f5409c..3aa23cd75 100644 --- a/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js +++ b/ui/src/router/components/configuration/components/standard_config_section/StandardConfigViewGroup.js @@ -5,7 +5,7 @@ import { TreatmentMappingConfigSection } from "./TreatmentMappingConfigSection"; import { FallbackRouteConfigSection } from "./FallbackRouteConfigSection"; import ExperimentEngineContext from "../../../../../providers/experiments/context"; import { Panel } from "../../../form/components/Panel"; -import RemoteLoaderComponent from "../../../../../components/remote_component/RemoteLoaderComponent"; +import ExperimentEngineComponentLoader from "../../../../../components/remote_component/ExperimentEngineComponentLoader"; import { RemoteComponent } from "../../../../../components/remote_component/RemoteComponent"; const FallbackView = ({ text }) => ( @@ -27,7 +27,7 @@ const StandardEnsemblerWithCustomExperimentEngineConfigView = ({ return ( }> - - + ); }; diff --git a/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js b/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js index 6100edc8a..3be98dd4c 100644 --- a/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js +++ b/ui/src/router/components/form/components/ensembler_config/standard_ensembler/StandardEnsemblerFormGroup.js @@ -10,7 +10,7 @@ import { useOnChangeHandler } from "../../../../../../components/form/hooks/useO import ExperimentEngineContext from "../../../../../../providers/experiments/context"; import { Panel } from "../../Panel"; import { RemoteComponent } from "../../../../../../components/remote_component/RemoteComponent"; -import RemoteLoaderComponent from "../../../../../../components/remote_component/RemoteLoaderComponent"; +import ExperimentEngineComponentLoader from "../../../../../../components/remote_component/ExperimentEngineComponentLoader"; const FallbackView = ({ text }) => ( @@ -33,7 +33,7 @@ const StandardEnsemblerWithCustomExperimentEnginePanel = ({ return ( }> - - + ); }; diff --git a/ui/src/router/components/form/components/experiment_config/ExperimentConfigPanel.js b/ui/src/router/components/form/components/experiment_config/ExperimentConfigPanel.js index 0d94be56b..af7ff7083 100644 --- a/ui/src/router/components/form/components/experiment_config/ExperimentConfigPanel.js +++ b/ui/src/router/components/form/components/experiment_config/ExperimentConfigPanel.js @@ -3,7 +3,7 @@ import { EuiFlexItem, EuiSpacer } from "@elastic/eui"; import { RemoteComponent } from "../../../../../components/remote_component/RemoteComponent"; import ExperimentEngineContext from "../../../../../providers/experiments/context"; -import RemoteLoaderComponent from "../../../../../components/remote_component/RemoteLoaderComponent"; +import ExperimentEngineComponentLoader from "../../../../../components/remote_component/ExperimentEngineComponentLoader"; import { Panel } from "../Panel"; import { StandardExperimentConfigGroup } from "./StandardExperimentConfigGroup"; @@ -28,7 +28,7 @@ const CustomExperimentEngineConfigGroup = ({ return ( }> - - + ); }; From d5ad99bf699a761a67cabf6dc4c38aac0af880bf Mon Sep 17 00:00:00 2001 From: ewezy Date: Mon, 26 Sep 2022 18:17:22 +0800 Subject: [PATCH 16/16] Remove redundant line from docs --- docs/how-to/create-a-router/configure-ensembler.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/how-to/create-a-router/configure-ensembler.md b/docs/how-to/create-a-router/configure-ensembler.md index 5659e0223..2d366caf7 100644 --- a/docs/how-to/create-a-router/configure-ensembler.md +++ b/docs/how-to/create-a-router/configure-ensembler.md @@ -18,7 +18,7 @@ It is not possible to select as the final response a route that has traffic rule There are two types of Standard Ensemblers available, one that is available with Standard Experiment Engines that have experiment selection enabled, and the other with Custom Experiment Engines. -### Standard Experiment Engines and Experiment Selection Enabled +### Standard Experiment Engines For routers configured with Standard Experiment Engines that have experiment selection enabled, the router will return a response from one of the routes based on the configured mapping between routes and experiment treatments. At run time, the treatment returned by the engine will be used to select the corresponding route’s response. In addition, a fallback route may be configured whose results will be used at runtime when the call to the experiment engine fails or if a route mapping for the treatment generated by the experiment engine does not exist. @@ -39,9 +39,6 @@ A fallback route also has to be configured in order to capture cases whereby the configuration does not correspond to any of the routes configured, or when the user-configured path is invalid with respect to the treatment configuration received. -The UI for Standard Ensemblers with Custom Experiment Engines is consumed directly as a remote component that is -implemented as part of the individual Custom Experiment Engines. - ## Docker Turing will deploy the specified image as a post-processor and will send in the request payload the following, for