diff --git a/ui/craco.config.js b/ui/craco.config.js index b620af1c..3cf41d3a 100644 --- a/ui/craco.config.js +++ b/ui/craco.config.js @@ -37,6 +37,8 @@ module.exports = ({ }) => ({ "./EditExperimentEngineConfig": "./src/turing/components/form/EditExperimentEngineConfig", "./ExperimentEngineConfigDetails": "./src/turing/components/configuration/ExperimentEngineConfigDetails", "./ExperimentsLandingPage": "./src/experiments/ExperimentsLandingPage", + "./EditStandardEnsemblerConfig": "./src/turing/components/form/EditStandardEnsemblerConfig", + "./StandardEnsemblerConfigDetails": "./src/turing/components/configuration/StandardEnsemblerConfigDetails" }, filename: "remoteEntry.js", shared: { diff --git a/ui/src/config.js b/ui/src/config.js index cd1de73a..3d7ce133 100644 --- a/ui/src/config.js +++ b/ui/src/config.js @@ -55,6 +55,7 @@ export const appConfig = { // Padding of page template paddingSize: "none", }, + routeNamePathPrefix: "treatment.configuration." }; const sentryConfig = { diff --git a/ui/src/experiments/components/configuration/SegmentConfigSection.js b/ui/src/experiments/components/configuration/SegmentConfigSection.js index 87f5c8b0..6439f302 100644 --- a/ui/src/experiments/components/configuration/SegmentConfigSection.js +++ b/ui/src/experiments/components/configuration/SegmentConfigSection.js @@ -12,7 +12,7 @@ import { useDimension, useToggle } from "@gojek/mlp-ui"; import { ConfigPanel } from "components/config_section/ConfigPanel"; import { ConfigSectionFlyout } from "components/config_section/ConfigSectionFlyout"; import { ExpandableTableColumn } from "components/table/ExpandableTableColumn"; -import SegmenterContext from "providers/segmenters/context"; +import SegmenterContext from "providers/segmenter/context"; import { stringifySegmenterValue } from "services/experiment/Segment"; export const SegmentConfigSection = ({ experiment }) => { @@ -148,5 +148,5 @@ const ExperimentSegmentTable = ({ items, projectSegmenters, buttonAction }) => { }, ]; - return ; + return ; }; diff --git a/ui/src/experiments/components/form/CreateExperimentForm.js b/ui/src/experiments/components/form/CreateExperimentForm.js index ed522781..cdf6a040 100644 --- a/ui/src/experiments/components/form/CreateExperimentForm.js +++ b/ui/src/experiments/components/form/CreateExperimentForm.js @@ -4,7 +4,7 @@ import { EuiLoadingChart, EuiTextAlign } from "@elastic/eui"; import { FormContext, StepsWizardHorizontal, addToast } from "@gojek/mlp-ui"; import { useXpApi } from "hooks/useXpApi"; -import SegmenterContext from "providers/segmenters/context"; +import SegmenterContext from "providers/segmenter/context"; import SettingsContext from "providers/settings/context"; import { GeneralStep } from "./steps/GeneralStep"; diff --git a/ui/src/experiments/components/form/EditExperimentForm.js b/ui/src/experiments/components/form/EditExperimentForm.js index efb73c08..adaed2b9 100644 --- a/ui/src/experiments/components/form/EditExperimentForm.js +++ b/ui/src/experiments/components/form/EditExperimentForm.js @@ -5,7 +5,7 @@ import { AccordionForm, FormContext, addToast } from "@gojek/mlp-ui"; import { ConfigSectionTitle } from "components/config_section/ConfigSectionTitle"; import { useXpApi } from "hooks/useXpApi"; -import SegmenterContext from "providers/segmenters/context"; +import SegmenterContext from "providers/segmenter/context"; import SettingsContext from "providers/settings/context"; import { GeneralStep } from "./steps/GeneralStep"; diff --git a/ui/src/experiments/components/form/components/segment_config/SegmentConfigPanel.js b/ui/src/experiments/components/form/components/segment_config/SegmentConfigPanel.js index 8901af80..ffbdc5ca 100644 --- a/ui/src/experiments/components/form/components/segment_config/SegmentConfigPanel.js +++ b/ui/src/experiments/components/form/components/segment_config/SegmentConfigPanel.js @@ -11,7 +11,7 @@ import { import { OverlayMask, get } from "@gojek/mlp-ui"; import { useXpApi } from "hooks/useXpApi"; -import SegmenterContext from "providers/segmenters/context"; +import SegmenterContext from "providers/segmenter/context"; import { SegmenterConfigRow } from "./SegmenterConfigRow"; diff --git a/ui/src/experiments/components/form/components/segment_config/SegmentSelectionPanel.js b/ui/src/experiments/components/form/components/segment_config/SegmentSelectionPanel.js index c71af588..2ca4a71e 100644 --- a/ui/src/experiments/components/form/components/segment_config/SegmentSelectionPanel.js +++ b/ui/src/experiments/components/form/components/segment_config/SegmentSelectionPanel.js @@ -8,7 +8,7 @@ import { } from "@elastic/eui"; import { Panel } from "components/panel/Panel"; -import SegmentsContext from "providers/segment/context"; +import SegmentContext from "providers/segment/context"; import { SegmentConfigPanel } from "./SegmentConfigPanel"; @@ -19,7 +19,7 @@ export const SegmentSelectionPanel = ({ onChange, errors = [], }) => { - const { isLoaded, segments } = useContext(SegmentsContext); + const { isLoaded, segments } = useContext(SegmentContext); const segmentSelectionOptions = segments.map((segment) => { return { diff --git a/ui/src/experiments/components/form/components/treatments_config/TreatmentCard.js b/ui/src/experiments/components/form/components/treatments_config/TreatmentCard.js index df758528..9561a41f 100644 --- a/ui/src/experiments/components/form/components/treatments_config/TreatmentCard.js +++ b/ui/src/experiments/components/form/components/treatments_config/TreatmentCard.js @@ -14,7 +14,7 @@ import { } from "@elastic/eui"; import { useXpApi } from "hooks/useXpApi"; -import TreatmentsContext from "providers/treatment/context"; +import TreatmentContext from "providers/treatment/context"; import { TreatmentPanel } from "./TreatmentPanel"; @@ -25,7 +25,7 @@ export const TreatmentCard = ({ projectId, errors = {}, }) => { - const { isLoaded, treatments } = useContext(TreatmentsContext); + const { isLoaded, treatments } = useContext(TreatmentContext); const [treatmentId, setTreatmentId] = useState(); const [hasNewResponse, setHasNewResponse] = useState(false); diff --git a/ui/src/experiments/create/CreateExperimentView.js b/ui/src/experiments/create/CreateExperimentView.js index c4643c0d..706eae06 100644 --- a/ui/src/experiments/create/CreateExperimentView.js +++ b/ui/src/experiments/create/CreateExperimentView.js @@ -8,10 +8,10 @@ import { FormContextProvider, replaceBreadcrumbs } from "@gojek/mlp-ui"; import { PageTitle } from "components/page/PageTitle"; import { CreateExperimentForm } from "experiments/components/form/CreateExperimentForm"; -import { SegmentsContextProvider } from "providers/segment/context"; -import { SegmenterContextProvider } from "providers/segmenters/context"; +import { SegmentContextProvider } from "providers/segment/context"; +import { SegmenterContextProvider } from "providers/segmenter/context"; import { SettingsContextProvider } from "providers/settings/context"; -import { TreatmentsContextProvider } from "providers/treatment/context"; +import { TreatmentContextProvider } from "providers/treatment/context"; import { Experiment } from "services/experiment/Experiment"; import { useConfig } from "config"; @@ -39,11 +39,11 @@ const CreateExperimentView = ({ projectId, ...props }) => { - + - + window.history.back()} @@ -51,11 +51,11 @@ const CreateExperimentView = ({ projectId, ...props }) => { props.navigate(`../${experimentId}`) } /> - + - + diff --git a/ui/src/experiments/details/config/ExperimentConfigView.js b/ui/src/experiments/details/config/ExperimentConfigView.js index 8f23b22d..96cc1558 100644 --- a/ui/src/experiments/details/config/ExperimentConfigView.js +++ b/ui/src/experiments/details/config/ExperimentConfigView.js @@ -8,7 +8,7 @@ import { ConfigSection } from "components/config_section/ConfigSection"; import { GeneralInfoConfigSection } from "experiments/components/configuration/GeneralInfoConfigSection"; import { SegmentConfigSection } from "experiments/components/configuration/SegmentConfigSection"; import { TreatmentConfigSection } from "experiments/components/configuration/TreatmentConfigSection"; -import { SegmenterContextProvider } from "providers/segmenters/context"; +import { SegmenterContextProvider } from "providers/segmenter/context"; export const ExperimentConfigView = ({ experiment }) => { const generalInfo = { diff --git a/ui/src/experiments/edit/EditExperimentView.js b/ui/src/experiments/edit/EditExperimentView.js index 8837c379..3f89a992 100644 --- a/ui/src/experiments/edit/EditExperimentView.js +++ b/ui/src/experiments/edit/EditExperimentView.js @@ -4,10 +4,10 @@ import { EuiPageTemplate, EuiSpacer } from "@elastic/eui"; import { FormContextProvider, replaceBreadcrumbs } from "@gojek/mlp-ui"; import { EditExperimentForm } from "experiments/components/form/EditExperimentForm"; -import { SegmentsContextProvider } from "providers/segment/context"; -import { SegmenterContextProvider } from "providers/segmenters/context"; +import { SegmentContextProvider } from "providers/segment/context"; +import { SegmenterContextProvider } from "providers/segmenter/context"; import { SettingsContextProvider } from "providers/settings/context"; -import { TreatmentsContextProvider } from "providers/treatment/context"; +import { TreatmentContextProvider } from "providers/treatment/context"; import { Experiment } from "services/experiment/Experiment"; import { PageTitle } from "components/page/PageTitle"; @@ -28,11 +28,11 @@ const EditExperimentView = ({ projectId, experimentSpec, ...props }) => { /> - + - + window.history.back()} @@ -40,11 +40,11 @@ const EditExperimentView = ({ projectId, experimentSpec, ...props }) => { props.navigate("../", { state: { refresh: true } }) } /> - + - + ); diff --git a/ui/src/experiments/history/details/ExperimentHistoryDetailsView.js b/ui/src/experiments/history/details/ExperimentHistoryDetailsView.js index beae47ab..f7b1d047 100644 --- a/ui/src/experiments/history/details/ExperimentHistoryDetailsView.js +++ b/ui/src/experiments/history/details/ExperimentHistoryDetailsView.js @@ -18,7 +18,7 @@ import { GeneralInfoConfigSection } from "experiments/components/configuration/G import { SegmentConfigSection } from "experiments/components/configuration/SegmentConfigSection"; import { TreatmentConfigSection } from "experiments/components/configuration/TreatmentConfigSection"; import { useXpApi } from "hooks/useXpApi"; -import { SegmenterContextProvider } from "providers/segmenters/context"; +import { SegmenterContextProvider } from "providers/segmenter/context"; import { useConfig } from "config"; import { VersionBadge } from "components/version_badge/VersionBadge"; diff --git a/ui/src/experiments/list/search/SearchExperimentsFilters.js b/ui/src/experiments/list/search/SearchExperimentsFilters.js index 8f8595b9..b990370d 100644 --- a/ui/src/experiments/list/search/SearchExperimentsFilters.js +++ b/ui/src/experiments/list/search/SearchExperimentsFilters.js @@ -18,7 +18,7 @@ import { experimentTypes, } from "experiments/components/typeOptions"; -import SegmenterContext from "providers/segmenters/context"; +import SegmenterContext from "providers/segmenter/context"; import { extractErrors } from "utils/helpers"; import ExperimentDateFilter from "./components/ExperimentDateFilter"; diff --git a/ui/src/experiments/list/search/SearchExperimentsPanel.js b/ui/src/experiments/list/search/SearchExperimentsPanel.js index ec96e572..1e8f33f3 100644 --- a/ui/src/experiments/list/search/SearchExperimentsPanel.js +++ b/ui/src/experiments/list/search/SearchExperimentsPanel.js @@ -2,7 +2,7 @@ import React from "react"; import { EuiButton, EuiFlyout, EuiFlyoutFooter } from "@elastic/eui"; -import { SegmenterContextProvider } from "providers/segmenters/context"; +import { SegmenterContextProvider } from "providers/segmenter/context"; import SearchExperimentsFilters from "./SearchExperimentsFilters"; diff --git a/ui/src/providers/experiment/context.js b/ui/src/providers/experiment/context.js new file mode 100644 index 00000000..39843a76 --- /dev/null +++ b/ui/src/providers/experiment/context.js @@ -0,0 +1,64 @@ +import React, {useEffect, useMemo, useState} from "react"; + +import { useXpApi } from "hooks/useXpApi"; +import moment from "moment"; +import { useConfig } from "config"; + +const ExperimentContext = React.createContext({}); + +export const ExperimentContextProvider = ({ projectId, children }) => { + const { appConfig } = useConfig(); + + const [isAllExperimentsLoaded, setIsAllExperimentsLoaded] = useState(false); + const [pageIndex, setPageIndex] = useState(0); + const [allExperiments, setAllExperiments] = useState([]); + + const { start_time, end_time } = useMemo( + () => { + let current_time = moment.utc(); + let start_time = current_time.format(appConfig.datetime.format); + let end_time = current_time.add(1000, "y").format(appConfig.datetime.format); + return { start_time, end_time }; + }, + [appConfig] + ); + + const [{ data: { data: experiments, paging }, isLoaded }] = useXpApi( + `/projects/${projectId}/experiments`, + { + query: { + start_time: start_time, + end_time: end_time, + page: pageIndex + 1, + page_size: appConfig.pagination.defaultPageSize, + status: "active" + }, + }, + { data: [], paging: { total: 0 } } + ); + + useEffect(() => { + if (isLoaded) { + if (!!experiments && !isAllExperimentsLoaded) { + setAllExperiments((curExperiments) => [...curExperiments, ...experiments]); + } + if (paging.pages > paging.page) { + setPageIndex(paging.page); + } else { + setIsAllExperimentsLoaded(true); + } + } + }, [isLoaded, experiments, paging, isAllExperimentsLoaded]); + + return ( + + {children} + + ); +}; + +export default ExperimentContext; diff --git a/ui/src/providers/segment/context.js b/ui/src/providers/segment/context.js index 687adad0..7181fa07 100644 --- a/ui/src/providers/segment/context.js +++ b/ui/src/providers/segment/context.js @@ -2,9 +2,9 @@ import React from "react"; import { useXpApi } from "hooks/useXpApi"; -const SegmentsContext = React.createContext({}); +const SegmentContext = React.createContext({}); -export const SegmentsContextProvider = ({ projectId, children }) => { +export const SegmentContextProvider = ({ projectId, children }) => { const [ { data: { data: segments }, @@ -21,14 +21,14 @@ export const SegmentsContextProvider = ({ projectId, children }) => { ); return ( - {children} - + ); }; -export default SegmentsContext; +export default SegmentContext; diff --git a/ui/src/providers/segmenters/context.js b/ui/src/providers/segmenter/context.js similarity index 100% rename from ui/src/providers/segmenters/context.js rename to ui/src/providers/segmenter/context.js diff --git a/ui/src/providers/treatment/context.js b/ui/src/providers/treatment/context.js index 7628bd56..fd70c0e0 100644 --- a/ui/src/providers/treatment/context.js +++ b/ui/src/providers/treatment/context.js @@ -2,9 +2,9 @@ import React from "react"; import { useXpApi } from "hooks/useXpApi"; -const TreatmentsContext = React.createContext({}); +const TreatmentContext = React.createContext({}); -export const TreatmentsContextProvider = ({ projectId, children }) => { +export const TreatmentContextProvider = ({ projectId, children }) => { const [ { data: { data: treatments }, @@ -21,14 +21,14 @@ export const TreatmentsContextProvider = ({ projectId, children }) => { ); return ( - {children} - + ); }; -export default TreatmentsContext; +export default TreatmentContext; diff --git a/ui/src/segments/components/form/CreateSegmentForm.js b/ui/src/segments/components/form/CreateSegmentForm.js index 53fa13ed..4f72b0f7 100644 --- a/ui/src/segments/components/form/CreateSegmentForm.js +++ b/ui/src/segments/components/form/CreateSegmentForm.js @@ -4,7 +4,7 @@ import { AccordionForm, FormContext, addToast } from "@gojek/mlp-ui"; import { ConfigSectionTitle } from "components/config_section/ConfigSectionTitle"; import { useXpApi } from "hooks/useXpApi"; -import SegmenterContext from "providers/segmenters/context"; +import SegmenterContext from "providers/segmenter/context"; import { ConfigurationStep } from "./steps/ConfigurationStep"; import schema from "./validation/schema"; diff --git a/ui/src/segments/components/form/EditSegmentForm.js b/ui/src/segments/components/form/EditSegmentForm.js index 84e5685c..8772ff24 100644 --- a/ui/src/segments/components/form/EditSegmentForm.js +++ b/ui/src/segments/components/form/EditSegmentForm.js @@ -4,7 +4,7 @@ import { AccordionForm, FormContext, addToast } from "@gojek/mlp-ui"; import { ConfigSectionTitle } from "components/config_section/ConfigSectionTitle"; import { useXpApi } from "hooks/useXpApi"; -import SegmenterContext from "providers/segmenters/context"; +import SegmenterContext from "providers/segmenter/context"; import { ConfigurationStep } from "./steps/ConfigurationStep"; import schema from "./validation/schema"; diff --git a/ui/src/segments/create/CreateSegmentView.js b/ui/src/segments/create/CreateSegmentView.js index 07fb930a..763f431c 100644 --- a/ui/src/segments/create/CreateSegmentView.js +++ b/ui/src/segments/create/CreateSegmentView.js @@ -7,8 +7,8 @@ import { import { FormContextProvider, replaceBreadcrumbs } from "@gojek/mlp-ui"; import { PageTitle } from "components/page/PageTitle"; -import { SegmentsContextProvider } from "providers/segment/context"; -import { SegmenterContextProvider } from "providers/segmenters/context"; +import { SegmentContextProvider } from "providers/segment/context"; +import { SegmenterContextProvider } from "providers/segmenter/context"; import { CreateSegmentForm } from "segments/components/form/CreateSegmentForm"; import { CustomSegment } from "services/segment/CustomSegment"; import { useConfig } from "config"; @@ -39,13 +39,13 @@ const CreateSegmentView = ({ projectId, ...props }) => { - + window.history.back()} onSuccess={(segmentId) => props.navigate(`../${segmentId}`)} /> - + diff --git a/ui/src/segments/details/config/SegmentConfigView.js b/ui/src/segments/details/config/SegmentConfigView.js index ff26d63d..a9b6c8e4 100644 --- a/ui/src/segments/details/config/SegmentConfigView.js +++ b/ui/src/segments/details/config/SegmentConfigView.js @@ -6,7 +6,7 @@ import { replaceBreadcrumbs } from "@gojek/mlp-ui"; import { ActivityConfigSection } from "components/config_section/ActivityConfigSection"; import { ConfigSection } from "components/config_section/ConfigSection"; import { SegmentConfigSection } from "experiments/components/configuration/SegmentConfigSection"; -import { SegmenterContextProvider } from "providers/segmenters/context"; +import { SegmenterContextProvider } from "providers/segmenter/context"; export const SegmentConfigView = ({ segment }) => { const activity = { diff --git a/ui/src/segments/edit/EditSegmentView.js b/ui/src/segments/edit/EditSegmentView.js index 49dc21c6..64680f2c 100644 --- a/ui/src/segments/edit/EditSegmentView.js +++ b/ui/src/segments/edit/EditSegmentView.js @@ -7,8 +7,8 @@ import { import { FormContextProvider, replaceBreadcrumbs } from "@gojek/mlp-ui"; import { PageTitle } from "components/page/PageTitle"; -import { SegmentsContextProvider } from "providers/segment/context"; -import { SegmenterContextProvider } from "providers/segmenters/context"; +import { SegmentContextProvider } from "providers/segment/context"; +import { SegmenterContextProvider } from "providers/segmenter/context"; import { EditSegmentForm } from "segments/components/form/EditSegmentForm"; import { CustomSegment } from "services/segment/CustomSegment"; @@ -32,7 +32,7 @@ const EditSegmentView = ({ projectId, segmentSpec, ...props }) => { - + window.history.back()} @@ -40,7 +40,7 @@ const EditSegmentView = ({ projectId, segmentSpec, ...props }) => { props.navigate("../", { state: { refresh: true } }); }} /> - + diff --git a/ui/src/segments/history/details/SegmentHistoryDetailsView.js b/ui/src/segments/history/details/SegmentHistoryDetailsView.js index a942dbf5..867ec9f6 100644 --- a/ui/src/segments/history/details/SegmentHistoryDetailsView.js +++ b/ui/src/segments/history/details/SegmentHistoryDetailsView.js @@ -16,7 +16,7 @@ import { ConfigSection } from "components/config_section/ConfigSection"; import { PageTitle } from "components/page/PageTitle"; import { SegmentConfigSection } from "experiments/components/configuration/SegmentConfigSection"; import { useXpApi } from "hooks/useXpApi"; -import { SegmenterContextProvider } from "providers/segmenters/context"; +import { SegmenterContextProvider } from "providers/segmenter/context"; import { useConfig } from "config"; const SegmentHistoryDetailsView = ({ projectId, segmentId, version }) => { diff --git a/ui/src/services/experiment/ExperimentStatus.js b/ui/src/services/experiment/ExperimentStatus.js index 658acd17..2294297d 100644 --- a/ui/src/services/experiment/ExperimentStatus.js +++ b/ui/src/services/experiment/ExperimentStatus.js @@ -13,7 +13,7 @@ export const getExperimentStatus = (experiment) => { const statusMapping = { Deactivated: { label: "Deactivated", - color: "subdued", + color: "default", iconType: "cross", }, Completed: { diff --git a/ui/src/settings/components/form/CreateSettingsForm.js b/ui/src/settings/components/form/CreateSettingsForm.js index f7236a7d..25df53ae 100644 --- a/ui/src/settings/components/form/CreateSettingsForm.js +++ b/ui/src/settings/components/form/CreateSettingsForm.js @@ -4,7 +4,7 @@ import { AccordionForm, FormContext, addToast } from "@gojek/mlp-ui"; import { ConfigSectionTitle } from "components/config_section/ConfigSectionTitle"; import { useXpApi } from "hooks/useXpApi"; -import SegmenterContext from "providers/segmenters/context"; +import SegmenterContext from "providers/segmenter/context"; import { RandomizationStep } from "./steps/RandomizationStep"; import { SegmentersStep } from "./steps/SegmentersStep"; diff --git a/ui/src/settings/components/form/EditSettingsForm.js b/ui/src/settings/components/form/EditSettingsForm.js index b20dc81e..407f1fa0 100644 --- a/ui/src/settings/components/form/EditSettingsForm.js +++ b/ui/src/settings/components/form/EditSettingsForm.js @@ -4,7 +4,7 @@ import { AccordionForm, FormContext, addToast } from "@gojek/mlp-ui"; import { ConfigSectionTitle } from "components/config_section/ConfigSectionTitle"; import { useXpApi } from "hooks/useXpApi"; -import SegmenterContext from "providers/segmenters/context"; +import SegmenterContext from "providers/segmenter/context"; import { RandomizationStep } from "./steps/RandomizationStep"; import { SegmentersStep } from "./steps/SegmentersStep"; diff --git a/ui/src/settings/components/form/components/segmenter_section/SegmenterPanel.js b/ui/src/settings/components/form/components/segmenter_section/SegmenterPanel.js index dcce1bc9..fbe4721c 100644 --- a/ui/src/settings/components/form/components/segmenter_section/SegmenterPanel.js +++ b/ui/src/settings/components/form/components/segmenter_section/SegmenterPanel.js @@ -19,7 +19,7 @@ import { FormLabelWithToolTip } from "@gojek/mlp-ui"; import isEqual from "lodash/isEqual"; import { Panel } from "components/panel/Panel"; -import SegmenterContext from "providers/segmenters/context"; +import SegmenterContext from "providers/segmenter/context"; import { makeId } from "utils/helpers"; import { SegmenterCard } from "./SegmenterCard"; diff --git a/ui/src/settings/create/CreateSettingsView.js b/ui/src/settings/create/CreateSettingsView.js index 448b6469..592b2a9b 100644 --- a/ui/src/settings/create/CreateSettingsView.js +++ b/ui/src/settings/create/CreateSettingsView.js @@ -7,7 +7,7 @@ import { import { FormContextProvider, replaceBreadcrumbs } from "@gojek/mlp-ui"; import { PageTitle } from "components/page/PageTitle"; -import { SegmenterContextProvider } from "providers/segmenters/context"; +import { SegmenterContextProvider } from "providers/segmenter/context"; import { Settings } from "services/settings/Settings"; import { CreateSettingsForm } from "settings/components/form/CreateSettingsForm"; import { useConfig } from "config"; diff --git a/ui/src/settings/edit/EditSettingsView.js b/ui/src/settings/edit/EditSettingsView.js index ae4aefb1..f26d0372 100644 --- a/ui/src/settings/edit/EditSettingsView.js +++ b/ui/src/settings/edit/EditSettingsView.js @@ -3,7 +3,7 @@ import React, { Fragment, useEffect } from "react"; import { EuiPageTemplate, EuiSpacer } from "@elastic/eui"; import { FormContextProvider, replaceBreadcrumbs } from "@gojek/mlp-ui"; -import { SegmenterContextProvider } from "providers/segmenters/context"; +import { SegmenterContextProvider } from "providers/segmenter/context"; import { Settings } from "services/settings/Settings"; import { EditSettingsForm } from "settings/components/form/EditSettingsForm"; import { PageTitle } from "components/page/PageTitle"; diff --git a/ui/src/treatments/components/form/components/config_section/TreatmentSelectionPanel.js b/ui/src/treatments/components/form/components/config_section/TreatmentSelectionPanel.js index 1b561b30..4112ab22 100644 --- a/ui/src/treatments/components/form/components/config_section/TreatmentSelectionPanel.js +++ b/ui/src/treatments/components/form/components/config_section/TreatmentSelectionPanel.js @@ -8,7 +8,7 @@ import { } from "@elastic/eui"; import { Panel } from "components/panel/Panel"; -import TreatmentsContext from "providers/treatment/context"; +import TreatmentContext from "providers/treatment/context"; import { TreatmentConfigPanel } from "./TreatmentConfigPanel"; @@ -19,7 +19,7 @@ export const TreatmentSelectionPanel = ({ onChange, errors = [], }) => { - const { isLoaded, treatments } = useContext(TreatmentsContext); + const { isLoaded, treatments } = useContext(TreatmentContext); const treatmentSelectionOptions = treatments.map((treatment) => { return { diff --git a/ui/src/treatments/create/CreateTreatmentView.js b/ui/src/treatments/create/CreateTreatmentView.js index 3bcfdea8..765fb64d 100644 --- a/ui/src/treatments/create/CreateTreatmentView.js +++ b/ui/src/treatments/create/CreateTreatmentView.js @@ -7,7 +7,7 @@ import { import { FormContextProvider, replaceBreadcrumbs } from "@gojek/mlp-ui"; import { PageTitle } from "components/page/PageTitle"; -import { TreatmentsContextProvider } from "providers/treatment/context"; +import { TreatmentContextProvider } from "providers/treatment/context"; import { Treatment } from "services/treatment/Treatment"; import { CreateTreatmentForm } from "treatments/components/form/CreateTreatmentForm"; import { useConfig } from "config"; @@ -37,13 +37,13 @@ const CreateTreatmentView = ({ projectId, ...props }) => { - + window.history.back()} onSuccess={(treatmentId) => props.navigate(`../${treatmentId}`)} /> - + diff --git a/ui/src/treatments/edit/EditTreatmentView.js b/ui/src/treatments/edit/EditTreatmentView.js index 76d6b834..5146d0ea 100644 --- a/ui/src/treatments/edit/EditTreatmentView.js +++ b/ui/src/treatments/edit/EditTreatmentView.js @@ -7,7 +7,7 @@ import { import { FormContextProvider, replaceBreadcrumbs } from "@gojek/mlp-ui"; import { PageTitle } from "components/page/PageTitle"; -import { TreatmentsContextProvider } from "providers/treatment/context"; +import { TreatmentContextProvider } from "providers/treatment/context"; import { Treatment } from "services/treatment/Treatment"; import { EditTreatmentForm } from "treatments/components/form/EditTreatmentForm"; @@ -30,7 +30,7 @@ const EditTreatmentView = ({ projectId, treatmentSpec, ...props }) => { - + window.history.back()} @@ -38,7 +38,7 @@ const EditTreatmentView = ({ projectId, treatmentSpec, ...props }) => { props.navigate("../", { state: { refresh: true } }); }} /> - + diff --git a/ui/src/turing/components/configuration/StandardEnsemblerConfigDetails.js b/ui/src/turing/components/configuration/StandardEnsemblerConfigDetails.js new file mode 100644 index 00000000..07f4657b --- /dev/null +++ b/ui/src/turing/components/configuration/StandardEnsemblerConfigDetails.js @@ -0,0 +1,48 @@ +import React from "react"; +import { ConfigProvider, useConfig } from "config"; +import { EuiFlexGroup, EuiFlexItem } from "@elastic/eui"; +import { ConfigSectionPanel } from "components/config_section/ConfigSectionPanel"; +import { LinkedRoutesTable } from "turing/components/form/standard_ensembler/LinkedRoutesTable"; +import { RouteNamePathConfigGroup } from "./standard_ensembler_config/RouteNamePathConfigGroup"; +import { ProjectContextProvider } from "providers/project/context"; +import { ExperimentContextProvider } from "providers/experiment/context"; + +const StandardEnsemblerConfigDetailsComponent = ({ projectId, routes, routeNamePath }) => { + const { appConfig: { routeNamePathPrefix } } = useConfig(); + + return ( + + + + + + + + + + + + + + + + + + ); +}; + +const StandardEnsemblerConfigDetails = (props) => { + return ( + + + + + + ) +}; + +export default StandardEnsemblerConfigDetails; diff --git a/ui/src/turing/components/configuration/standard_ensembler_config/RouteNamePathConfigGroup.js b/ui/src/turing/components/configuration/standard_ensembler_config/RouteNamePathConfigGroup.js new file mode 100644 index 00000000..5c40fdfa --- /dev/null +++ b/ui/src/turing/components/configuration/standard_ensembler_config/RouteNamePathConfigGroup.js @@ -0,0 +1,26 @@ +import React from "react"; + +import { + EuiDescriptionList, + EuiTitle, +} from "@elastic/eui"; + +export const RouteNamePathConfigGroup = ({ routeNamePath }) => { + return ( + + + + ); +}; diff --git a/ui/src/turing/components/form/EditStandardEnsemblerConfig.js b/ui/src/turing/components/form/EditStandardEnsemblerConfig.js new file mode 100644 index 00000000..721eb9be --- /dev/null +++ b/ui/src/turing/components/form/EditStandardEnsemblerConfig.js @@ -0,0 +1,80 @@ +import React, { useContext, useRef } from "react"; + +import { EuiCallOut, EuiFlexItem, EuiLoadingChart, EuiHorizontalRule } from "@elastic/eui"; +import { OverlayMask } from "@gojek/mlp-ui"; + +import { Panel } from "components/panel/Panel"; +import { ConfigProvider, useConfig } from "config"; +import ProjectContext, { ProjectContextProvider } from "providers/project/context"; +import { ExperimentContextProvider } from "providers/experiment/context"; +import { LinkedRoutesTable } from "./standard_ensembler/LinkedRoutesTable"; +import { RouteNamePathRow } from "./standard_ensembler/RouteNamePathRow"; + +const EditStandardEnsemblerConfigComponent = ({ + projectId, + routes, + routeNamePath = "", + onChange, + errors, +}) => { + const { appConfig: { routeNamePathPrefix } } = useConfig(); + + const { isProjectOnboarded, isLoaded } = useContext(ProjectContext); + const overlayRef = useRef(); + + return ( + + {isLoaded ? ( + isProjectOnboarded(projectId) ? ( + + + + + + + + + + ) : ( + + +

+ {"Please complete onboarding to Turing experiments to configure the standard ensembler."} +

+
+
+ ) + ) : ( +
+ + + +
+ )} +
+ ); +}; + +const EditStandardEnsemblerConfig = (props) => { + return ( + + + + + + ) +}; + +export default EditStandardEnsemblerConfig; diff --git a/ui/src/turing/components/form/standard_ensembler/LinkedExperimentsContextMenu.js b/ui/src/turing/components/form/standard_ensembler/LinkedExperimentsContextMenu.js new file mode 100644 index 00000000..130b6a16 --- /dev/null +++ b/ui/src/turing/components/form/standard_ensembler/LinkedExperimentsContextMenu.js @@ -0,0 +1,68 @@ +import React from "react"; + +import { + EuiContextMenu, + EuiPopover, + EuiButtonEmpty, + EuiLink, + EuiIcon +} from "@elastic/eui"; +import { useToggle } from "@gojek/mlp-ui"; + +export const LinkedExperimentsContextMenu = ({ + projectId, + linkedExperiments, + experimentStatus +}) => { + const [isPopoverOpen, togglePopover] = useToggle(); + + let numExperiments = Object.keys(linkedExperiments[experimentStatus]).length; + + const button = ( + + {numExperiments} + + ); + + return ( + + ( + { + name: ( + + {e.name} + + ), + icon: , + size: "s", + toolTipContent: "Open experiment details page", + toolTipPosition: "right", + } + )) + } + ] + } + /> + + ); +}; \ No newline at end of file diff --git a/ui/src/turing/components/form/standard_ensembler/LinkedRoutesTable.js b/ui/src/turing/components/form/standard_ensembler/LinkedRoutesTable.js new file mode 100644 index 00000000..d81b4ce3 --- /dev/null +++ b/ui/src/turing/components/form/standard_ensembler/LinkedRoutesTable.js @@ -0,0 +1,116 @@ +import React, { useContext, useEffect, useState } from "react"; + +import { + EuiFlexItem, + EuiLoadingChart, + EuiTextAlign, + EuiInMemoryTable, + EuiIcon, + EuiTextColor, +} from "@elastic/eui"; + +import { getExperimentStatus } from "services/experiment/ExperimentStatus"; +import { LinkedExperimentsContextMenu } from "./LinkedExperimentsContextMenu"; +import ExperimentContext from "providers/experiment/context"; + +export const LinkedRoutesTable = ({ + projectId, + routes, + treatmentConfigRouteNamePath, +}) => { + const { allExperiments, isAllExperimentsLoaded } = useContext(ExperimentContext) + + const [routeToExperimentMappings, setRouteToExperimentMappings] = useState(routes.reduce((m, r) => {m[r.id] = {running: {}, scheduled: {}}; return m}, {})); + + const getRouteName = (config, path) => path.split('.').reduce((obj, key) => obj && obj[key], config); + + // this stringified value of routes below allows the React effect below to mimic a deep comparison when changes to the + // array routes are made + const stringifiedRoutes = routes.map(e => e.id).join(); + + // reset loaded routeToExperimentMappings if treatmentConfigRouteNamePath or routes changes + useEffect(() => { + if (isAllExperimentsLoaded) { + let newRouteToExperimentMappings = routes.reduce((m, r) => {m[r.id] = {running: {}, scheduled: {}}; return m}, {}); + for (let experiment of allExperiments) { + for (let treatment of experiment.treatments) { + let configRouteName = getRouteName(treatment.configuration, treatmentConfigRouteNamePath); + if (typeof configRouteName === 'string' && configRouteName in newRouteToExperimentMappings) { + newRouteToExperimentMappings[configRouteName][getExperimentStatus(experiment).label.toLowerCase()][experiment.id] = experiment; + } + } + } + setRouteToExperimentMappings(newRouteToExperimentMappings); + } + }, [treatmentConfigRouteNamePath, stringifiedRoutes, routes, isAllExperimentsLoaded, allExperiments]); + + const columns = [ + { + field: "id", + width: "5px", + render: (id) => { + const isAssigned = routeToExperimentMappings[id] ? + Object.keys(routeToExperimentMappings[id].running).length + + Object.keys(routeToExperimentMappings[id].scheduled).length > 0 : false; + return ( + + ); + }, + }, + { + field: "id", + width: "20%", + name: "Route Name", + render: (id) => { + const isAssigned = routeToExperimentMappings[id] ? + Object.keys(routeToExperimentMappings[id].running).length + + Object.keys(routeToExperimentMappings[id].scheduled).length > 0 : false; + return ({id}); + }, + }, + { + field: "id", + width: "35%", + name: "Running Experiments", + render: (id) => routeToExperimentMappings[id] && ( + + ), + }, + { + field: "id", + width: "35%", + name: "Scheduled Experiments", + render: (id) => routeToExperimentMappings[id] && ( + + ) + }, + ]; + + return isAllExperimentsLoaded ? ( + + r.id !== "")} + columns={columns} + itemId={"id"} + isSelectable={false} + /> + + ) : ( + + + + ); +}; diff --git a/ui/src/turing/components/form/standard_ensembler/RouteNamePathFlyout.js b/ui/src/turing/components/form/standard_ensembler/RouteNamePathFlyout.js new file mode 100644 index 00000000..340ebabd --- /dev/null +++ b/ui/src/turing/components/form/standard_ensembler/RouteNamePathFlyout.js @@ -0,0 +1,86 @@ +import React from "react"; + +import { + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiText, + EuiTitle, + EuiCode, + EuiCodeBlock +} from "@elastic/eui"; +import {useConfig} from "config"; + +const RouteNamePathFlyout = ({ onClose }) => { + const { appConfig: { routeNamePathPrefix } } = useConfig(); + + return ( + + + + +

Route Name Path Prefix

+
+
+
+ + + + +

+ The prefix in the grayed-out area specifies the path prefix that gets appended to a user-defined + treatment configuration. +

+ +

+ This path prefix reflects the nesting of the treatment configuration within + the final response payload that the Turing Router finally receives. +

+ +

+ In Turing Experiments' case, if the user-defined treatment configuration is: +

+ + {`{ + "route_name": "control", + ... +}` + } + + +

+ the client response that gets sent back to the Turing Router is actually as follows: +

+ + {`{ + "treatment": { + "configuration": { + "route_name": "control" + ... + } + .... + } + ... +}` + } + + +

+ Hence, the path prefix is automatically specified as + {routeNamePathPrefix} (including the final period). +

+
+
+
+
+
+ ); +}; + +export default RouteNamePathFlyout; diff --git a/ui/src/turing/components/form/standard_ensembler/RouteNamePathRow.js b/ui/src/turing/components/form/standard_ensembler/RouteNamePathRow.js new file mode 100644 index 00000000..145412b5 --- /dev/null +++ b/ui/src/turing/components/form/standard_ensembler/RouteNamePathRow.js @@ -0,0 +1,52 @@ +import React, { Fragment } from "react"; + +import { EuiFlexItem, EuiText, EuiFormRow, EuiFieldText, EuiButtonIcon } from "@elastic/eui"; +import { FormLabelWithToolTip, useToggle } from "@gojek/mlp-ui"; +import RouteNamePathFlyout from "./RouteNamePathFlyout"; + +export const RouteNamePathRow = ({ + routeNamePath, + routeNamePathPrefix, + onChange, + errors, +}) => { + const [isFlyoutVisible, toggleFlyout] = useToggle(); + + return ( + + + + } + isInvalid={!!errors} + error={errors} + display="row"> + onChange(routeNamePathPrefix + e.target.value)} + isInvalid={!!errors} + name="route-name-path" + prepend={[ + , + {routeNamePathPrefix} + ]} + /> + + + + {isFlyoutVisible && ( + + )} + + ); +};