From 6b9b179892b67fd0e1544e6791d69faafddc769c Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Wed, 18 Dec 2024 16:15:11 +0100 Subject: [PATCH] Refactor to use jotai - Refactor front-end - Minor adjustments back-end for statistics end-point (should use real filter) --- .../primary/routers/timeseries/router.py | 14 +- .../src/api/services/TimeseriesService.ts | 3 + .../dataGenerators.ts | 32 +++ .../interfaces.ts | 22 +- .../loadModule.tsx | 2 +- .../settings/atoms/baseAtoms.ts | 15 +- .../settings/atoms/derivedAtoms.ts | 132 ++++++++++++ .../settings/atoms/queryAtoms.ts | 26 +++ .../settings/hooks/queryHooks.tsx | 19 -- .../settings/settings.tsx | 192 ++++++------------ .../settings/utils/fixupVectorName.ts | 14 ++ .../typesAndEnums.ts | 9 + .../SimulationTimeSeriesSensitivity/view.tsx | 188 ----------------- .../view/atoms/baseAtoms.ts | 13 ++ .../view/atoms/derivedAtoms.ts | 17 ++ .../view/atoms/interfaceEffects.ts | 38 ++++ .../view/atoms/queryAtoms.ts | 116 +++++++++++ .../components/timeSeriesChart.tsx} | 8 +- .../view/hooks/usePublishToDataChannels.ts | 32 +++ ...seTimeSeriesChartTracesDataArrayBuilder.ts | 96 +++++++++ .../utils/createTracesUtils.ts} | 0 .../view/view.tsx | 72 +++++++ 22 files changed, 702 insertions(+), 358 deletions(-) create mode 100644 frontend/src/modules/SimulationTimeSeriesSensitivity/dataGenerators.ts create mode 100644 frontend/src/modules/SimulationTimeSeriesSensitivity/settings/atoms/derivedAtoms.ts create mode 100644 frontend/src/modules/SimulationTimeSeriesSensitivity/settings/atoms/queryAtoms.ts delete mode 100644 frontend/src/modules/SimulationTimeSeriesSensitivity/settings/hooks/queryHooks.tsx create mode 100644 frontend/src/modules/SimulationTimeSeriesSensitivity/settings/utils/fixupVectorName.ts delete mode 100644 frontend/src/modules/SimulationTimeSeriesSensitivity/view.tsx create mode 100644 frontend/src/modules/SimulationTimeSeriesSensitivity/view/atoms/baseAtoms.ts create mode 100644 frontend/src/modules/SimulationTimeSeriesSensitivity/view/atoms/derivedAtoms.ts create mode 100644 frontend/src/modules/SimulationTimeSeriesSensitivity/view/atoms/interfaceEffects.ts create mode 100644 frontend/src/modules/SimulationTimeSeriesSensitivity/view/atoms/queryAtoms.ts rename frontend/src/modules/SimulationTimeSeriesSensitivity/{simulationTimeSeriesChart/chart.tsx => view/components/timeSeriesChart.tsx} (94%) create mode 100644 frontend/src/modules/SimulationTimeSeriesSensitivity/view/hooks/usePublishToDataChannels.ts create mode 100644 frontend/src/modules/SimulationTimeSeriesSensitivity/view/hooks/useTimeSeriesChartTracesDataArrayBuilder.ts rename frontend/src/modules/SimulationTimeSeriesSensitivity/{simulationTimeSeriesChart/traces.ts => view/utils/createTracesUtils.ts} (100%) create mode 100644 frontend/src/modules/SimulationTimeSeriesSensitivity/view/view.tsx diff --git a/backend_py/primary/primary/routers/timeseries/router.py b/backend_py/primary/primary/routers/timeseries/router.py index c1cf5f390..6d3f46323 100644 --- a/backend_py/primary/primary/routers/timeseries/router.py +++ b/backend_py/primary/primary/routers/timeseries/router.py @@ -199,11 +199,16 @@ async def get_statistical_vector_data_per_sensitivity( vector_name: Annotated[str, Query(description="Name of the vector")], resampling_frequency: Annotated[schemas.Frequency, Query(description="Resampling frequency")], statistic_functions: Annotated[list[schemas.StatisticFunction] | None, Query(description="Optional list of statistics to calculate. If not specified, all statistics will be calculated.")] = None, + realizations_encoded_as_uint_list_str: Annotated[str | None, Query(description="Optional list of realizations to include. If not specified, all realizations will be included.")] = None, # relative_to_timestamp: Annotated[datetime.datetime | None, Query(description="Calculate relative to timestamp")] = None, # fmt:on ) -> list[schemas.VectorStatisticSensitivityData]: """Get statistical vector data for an ensemble per sensitivity""" + realizations: list[int] | None = None + if realizations_encoded_as_uint_list_str: + realizations = decode_uint_list_str(realizations_encoded_as_uint_list_str) + summmary_access = SummaryAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) parameter_access = await ParameterAccess.from_case_uuid_async( authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name @@ -218,10 +223,15 @@ async def get_statistical_vector_data_per_sensitivity( ret_data: list[schemas.VectorStatisticSensitivityData] = [] if not sensitivities: return ret_data + + + requested_realizations_mask = pc.is_in(vector_table["REAL"], value_set=pa.array(realizations)) if realizations else None for sensitivity in sensitivities: for case in sensitivity.cases: - mask = pc.is_in(vector_table["REAL"], value_set=pa.array(case.realizations)) - table = vector_table.filter(mask) + sens_case_realization_mask = pc.is_in(vector_table["REAL"], value_set=pa.array(case.realizations)) + if requested_realizations_mask is not None: + sens_case_realization_mask = pc.and_(requested_realizations_mask, sens_case_realization_mask) + table = vector_table.filter(sens_case_realization_mask) statistics = compute_vector_statistics(table, vector_name, service_stat_funcs_to_compute) if not statistics: diff --git a/frontend/src/api/services/TimeseriesService.ts b/frontend/src/api/services/TimeseriesService.ts index a77b0270b..0fe69cc71 100644 --- a/frontend/src/api/services/TimeseriesService.ts +++ b/frontend/src/api/services/TimeseriesService.ts @@ -176,6 +176,7 @@ export class TimeseriesService { * @param vectorName Name of the vector * @param resamplingFrequency Resampling frequency * @param statisticFunctions Optional list of statistics to calculate. If not specified, all statistics will be calculated. + * @param realizationsEncodedAsUintListStr Optional list of realizations to include. If not specified, all realizations will be included. * @returns VectorStatisticSensitivityData Successful Response * @throws ApiError */ @@ -185,6 +186,7 @@ export class TimeseriesService { vectorName: string, resamplingFrequency: Frequency, statisticFunctions?: (Array | null), + realizationsEncodedAsUintListStr?: (string | null), ): CancelablePromise> { return this.httpRequest.request({ method: 'GET', @@ -195,6 +197,7 @@ export class TimeseriesService { 'vector_name': vectorName, 'resampling_frequency': resamplingFrequency, 'statistic_functions': statisticFunctions, + 'realizations_encoded_as_uint_list_str': realizationsEncodedAsUintListStr, }, errors: { 422: `Validation Error`, diff --git a/frontend/src/modules/SimulationTimeSeriesSensitivity/dataGenerators.ts b/frontend/src/modules/SimulationTimeSeriesSensitivity/dataGenerators.ts new file mode 100644 index 000000000..febddb34d --- /dev/null +++ b/frontend/src/modules/SimulationTimeSeriesSensitivity/dataGenerators.ts @@ -0,0 +1,32 @@ +import { VectorRealizationData_api } from "@api"; +import { DataGenerator } from "@framework/DataChannelTypes"; +import { Ensemble } from "@framework/Ensemble"; + +import { indexOf } from "lodash"; + +export function makeVectorDataGenerator( + ensemble: Ensemble | null, + vectorRealizationData: VectorRealizationData_api[] | null, + activeTimestampUtcMs: number | null +): DataGenerator { + return () => { + const data: { key: number; value: number }[] = []; + + if (ensemble && vectorRealizationData) { + vectorRealizationData.forEach((vec) => { + const indexOfTimestamp = indexOf(vec.timestamps_utc_ms, activeTimestampUtcMs); + data.push({ + key: vec.realization, + value: indexOfTimestamp === -1 ? 0 : vec.values[indexOfTimestamp], + }); + }); + } + return { + data, + metaData: { + ensembleIdentString: ensemble?.getIdent().toString() ?? "", + unit: "unit", + }, + }; + }; +} diff --git a/frontend/src/modules/SimulationTimeSeriesSensitivity/interfaces.ts b/frontend/src/modules/SimulationTimeSeriesSensitivity/interfaces.ts index 2cfd6b91d..dd7c6a7f2 100644 --- a/frontend/src/modules/SimulationTimeSeriesSensitivity/interfaces.ts +++ b/frontend/src/modules/SimulationTimeSeriesSensitivity/interfaces.ts @@ -2,23 +2,20 @@ import { Frequency_api } from "@api"; import { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface"; import { - realizationsToIncludeAtom, resamplingFrequencyAtom, - selectedSensitivitiesAtom, showHistoricalAtom, showRealizationsAtom, showStatisticsAtom, - vectorSpecAtom, } from "./settings/atoms/baseAtoms"; +import { selectedSensitivityNamesAtom, vectorSpecificationAtom } from "./settings/atoms/derivedAtoms"; import { VectorSpec } from "./typesAndEnums"; -type SettingsToViewInterface = { - vectorSpec: VectorSpec | null; +export type SettingsToViewInterface = { + vectorSpecification: VectorSpec | null; resamplingFrequency: Frequency_api | null; - selectedSensitivities: string[] | null; + selectedSensitivityNames: string[] | null; showStatistics: boolean; showRealizations: boolean; - realizationsToInclude: number[] | null; showHistorical: boolean; }; @@ -27,14 +24,14 @@ export type Interfaces = { }; export const settingsToViewInterfaceInitialization: InterfaceInitialization = { - vectorSpec: (get) => { - return get(vectorSpecAtom); + vectorSpecification: (get) => { + return get(vectorSpecificationAtom); }, resamplingFrequency: (get) => { return get(resamplingFrequencyAtom); }, - selectedSensitivities: (get) => { - return get(selectedSensitivitiesAtom); + selectedSensitivityNames: (get) => { + return get(selectedSensitivityNamesAtom); }, showStatistics: (get) => { return get(showStatisticsAtom); @@ -42,9 +39,6 @@ export const settingsToViewInterfaceInitialization: InterfaceInitialization { return get(showRealizationsAtom); }, - realizationsToInclude: (get) => { - return get(realizationsToIncludeAtom); - }, showHistorical: (get) => { return get(showHistoricalAtom); }, diff --git a/frontend/src/modules/SimulationTimeSeriesSensitivity/loadModule.tsx b/frontend/src/modules/SimulationTimeSeriesSensitivity/loadModule.tsx index 12a67850d..a50fd2347 100644 --- a/frontend/src/modules/SimulationTimeSeriesSensitivity/loadModule.tsx +++ b/frontend/src/modules/SimulationTimeSeriesSensitivity/loadModule.tsx @@ -2,7 +2,7 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; import { Interfaces, settingsToViewInterfaceInitialization } from "./interfaces"; import { Settings } from "./settings/settings"; -import { View } from "./view"; +import { View } from "./view/view"; const module = ModuleRegistry.initModule("SimulationTimeSeriesSensitivity", { settingsToViewInterfaceInitialization, diff --git a/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/atoms/baseAtoms.ts b/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/atoms/baseAtoms.ts index a2240c796..b07af259d 100644 --- a/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/atoms/baseAtoms.ts +++ b/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/atoms/baseAtoms.ts @@ -1,12 +1,19 @@ import { Frequency_api } from "@api"; -import { VectorSpec } from "@modules/SimulationTimeSeriesSensitivity/typesAndEnums"; +import { EnsembleIdent } from "@framework/EnsembleIdent"; +import { atomWithCompare } from "@framework/utils/atomUtils"; import { atom } from "jotai"; +import { isEqual } from "lodash"; + +export const syncedEnsembleIdentsAtom = atom(null); +export const syncedVectorNameAtom = atom(null); -export const vectorSpecAtom = atom(null); export const resamplingFrequencyAtom = atom(Frequency_api.MONTHLY); -export const selectedSensitivitiesAtom = atom(null); export const showStatisticsAtom = atom(true); export const showRealizationsAtom = atom(false); -export const realizationsToIncludeAtom = atom(null); export const showHistoricalAtom = atom(true); + +export const userSelectedEnsembleIdentAtom = atom(null); +export const userSelectedVectorNameAtom = atom(null); +export const userSelectedVectorTagAtom = atom(null); +export const userSelectedSensitivityNamesAtom = atomWithCompare(null, isEqual); diff --git a/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/atoms/derivedAtoms.ts b/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/atoms/derivedAtoms.ts new file mode 100644 index 000000000..2c66e46bb --- /dev/null +++ b/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/atoms/derivedAtoms.ts @@ -0,0 +1,132 @@ +import { EnsembleIdent } from "@framework/EnsembleIdent"; +import { EnsembleSetAtom } from "@framework/GlobalAtoms"; +import { fixupEnsembleIdent, maybeAssignFirstSyncedEnsemble } from "@framework/utils/ensembleUiHelpers"; +import { VectorSpec } from "@modules/SimulationTimeSeriesSensitivity/typesAndEnums"; +import { createVectorSelectorDataFromVectors } from "@modules/_shared/components/VectorSelector"; + +import { atom } from "jotai"; + +import { + syncedEnsembleIdentsAtom, + syncedVectorNameAtom, + userSelectedEnsembleIdentAtom, + userSelectedSensitivityNamesAtom, + userSelectedVectorNameAtom, + userSelectedVectorTagAtom, +} from "./baseAtoms"; +import { vectorListQueryAtom } from "./queryAtoms"; + +import { fixupVectorName } from "../utils/fixupVectorName"; + +export const selectedEnsembleIdentAtom = atom((get) => { + const syncedEnsembleIdents = get(syncedEnsembleIdentsAtom); + const userSelectedEnsembleIdent = get(userSelectedEnsembleIdentAtom); + const ensembleSet = get(EnsembleSetAtom); + + const candidateEnsembleIdent = maybeAssignFirstSyncedEnsemble(userSelectedEnsembleIdent, syncedEnsembleIdents); + const fixedUpEnsembleIdent = fixupEnsembleIdent(candidateEnsembleIdent, ensembleSet); + return fixedUpEnsembleIdent; +}); + +export const availableSensitivityNamesAtom = atom((get) => { + const ensembleSet = get(EnsembleSetAtom); + const selectedEnsembleIdent = get(selectedEnsembleIdentAtom); + + const ensemble = selectedEnsembleIdent ? ensembleSet.findEnsemble(selectedEnsembleIdent) : null; + const ensembleSensitivityNames = ensemble?.getSensitivities()?.getSensitivityNames() ?? []; + + return ensembleSensitivityNames; +}); + +export const selectedSensitivityNamesAtom = atom((get) => { + const userSelectedSensitivityNames = get(userSelectedSensitivityNamesAtom); + const availableSensitivityNames = get(availableSensitivityNamesAtom); + + // If userSelectedSensitivityNames is empty, do not override it + if (!userSelectedSensitivityNames || userSelectedSensitivityNames.length === 0) { + return []; + } + + // Fixup invalid sensitivity names + // - If no valid sensitivity names are selected, the change can be due to new available sensitivity names + const fixedUpSensitivityNames = + userSelectedSensitivityNames?.filter((sens) => availableSensitivityNames.includes(sens)) ?? []; + if (fixedUpSensitivityNames.length === 0) { + return availableSensitivityNames; + } + + return fixedUpSensitivityNames; +}); + +export const availableVectorNamesAtom = atom((get) => { + const vectorListQuery = get(vectorListQueryAtom); + return vectorListQuery.data?.map((vec) => vec.name) ?? []; +}); + +export const vectorSelectorDataAtom = atom((get) => { + const isFetching = get(vectorListQueryAtom).isFetching; + const availableVectorNames = get(availableVectorNamesAtom); + + if (isFetching) { + return []; + } + + return createVectorSelectorDataFromVectors(availableVectorNames); +}); + +/** + * Atom that handles vector name and tag in synch with fixup + */ +const fixedUpVectorNameAndTagAtom = atom<{ name: string | null; tag: string | null }>((get) => { + const syncedVectorName = get(syncedVectorNameAtom); + const userSelectedVectorName = get(userSelectedVectorNameAtom); + const userSelectedVectorTag = get(userSelectedVectorTagAtom); + const availableVectorNames = get(availableVectorNamesAtom); + + // Override with synced vector name if available + if (syncedVectorName) { + return { name: syncedVectorName, tag: syncedVectorName }; + } + + // If vector name is fixed up, adjust tag as well + const fixedUpVectorName = fixupVectorName(userSelectedVectorName, availableVectorNames); + if (fixedUpVectorName !== userSelectedVectorName) { + return { name: fixedUpVectorName, tag: fixedUpVectorName }; + } + + return { name: userSelectedVectorName, tag: userSelectedVectorTag }; +}); + +export const selectedVectorNameAtom = atom((get) => { + const fixedUpVectorName = get(fixedUpVectorNameAndTagAtom).name; + return fixedUpVectorName; +}); + +export const selectedVectorTagAtom = atom((get) => { + const fixedUpVectorTag = get(fixedUpVectorNameAndTagAtom).tag; + return fixedUpVectorTag; +}); + +export const selectedVectorNameHasHistoricalAtom = atom((get) => { + const selectedVectorName = get(selectedVectorNameAtom); + const vectorListQuery = get(vectorListQueryAtom); + + const selectedVector = vectorListQuery.data?.find((vec) => vec.name === selectedVectorName); + return !!selectedVector?.has_historical; +}); + +export const vectorSpecificationAtom = atom((get) => { + const selectedEnsembleIdent = get(selectedEnsembleIdentAtom); + const selectedVectorName = get(selectedVectorNameAtom); + const selectedVectorNameHasHistorical = get(selectedVectorNameHasHistoricalAtom); + + if (!selectedEnsembleIdent || !selectedVectorName) { + return null; + } + + return { + ensembleIdent: selectedEnsembleIdent, + vectorName: selectedVectorName, + hasHistorical: selectedVectorNameHasHistorical, + }; +}); diff --git a/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/atoms/queryAtoms.ts b/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/atoms/queryAtoms.ts new file mode 100644 index 000000000..4133056f7 --- /dev/null +++ b/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/atoms/queryAtoms.ts @@ -0,0 +1,26 @@ +import { apiService } from "@framework/ApiService"; + +import { atomWithQuery } from "jotai-tanstack-query"; + +import { selectedEnsembleIdentAtom } from "./derivedAtoms"; + +const STALE_TIME = 60 * 1000; +const CACHE_TIME = 60 * 1000; + +export const vectorListQueryAtom = atomWithQuery((get) => { + const selectedEnsembleIdent = get(selectedEnsembleIdentAtom); + + const query = { + queryKey: ["getVectorList", selectedEnsembleIdent?.getCaseUuid(), selectedEnsembleIdent?.getEnsembleName()], + queryFn: () => + apiService.timeseries.getVectorList( + selectedEnsembleIdent?.getCaseUuid() ?? "", + selectedEnsembleIdent?.getEnsembleName() ?? "" + ), + staleTime: STALE_TIME, + gcTime: CACHE_TIME, + enabled: !!(selectedEnsembleIdent?.getCaseUuid() && selectedEnsembleIdent?.getEnsembleName()), + }; + + return query; +}); diff --git a/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/hooks/queryHooks.tsx b/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/hooks/queryHooks.tsx deleted file mode 100644 index 2d33e1326..000000000 --- a/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/hooks/queryHooks.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { VectorDescription_api } from "@api"; -import { apiService } from "@framework/ApiService"; -import { UseQueryResult, useQuery } from "@tanstack/react-query"; - -const STALE_TIME = 60 * 1000; -const CACHE_TIME = 60 * 1000; - -export function useVectorListQuery( - caseUuid: string | undefined, - ensembleName: string | undefined -): UseQueryResult> { - return useQuery({ - queryKey: ["getVectorList", caseUuid, ensembleName], - queryFn: () => apiService.timeseries.getVectorList(caseUuid ?? "", ensembleName ?? ""), - staleTime: STALE_TIME, - gcTime: CACHE_TIME, - enabled: !!(caseUuid && ensembleName), - }); -} diff --git a/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/settings.tsx b/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/settings.tsx index 4085e7519..0a80d1583 100644 --- a/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/settings.tsx +++ b/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/settings.tsx @@ -6,134 +6,88 @@ import { ModuleSettingsProps } from "@framework/Module"; import { SyncSettingKey, SyncSettingsHelper } from "@framework/SyncSettings"; import { useEnsembleSet } from "@framework/WorkbenchSession"; import { EnsembleDropdown } from "@framework/components/EnsembleDropdown"; -import { fixupEnsembleIdent, maybeAssignFirstSyncedEnsemble } from "@framework/utils/ensembleUiHelpers"; import { Checkbox } from "@lib/components/Checkbox"; import { CircularProgress } from "@lib/components/CircularProgress"; import { CollapsibleGroup } from "@lib/components/CollapsibleGroup"; -import { Dropdown, DropdownOption } from "@lib/components/Dropdown"; +import { Dropdown } from "@lib/components/Dropdown"; import { Label } from "@lib/components/Label"; import { QueryStateWrapper } from "@lib/components/QueryStateWrapper"; -import { Select, SelectOption } from "@lib/components/Select"; -import { SmartNodeSelectorSelection, TreeDataNode } from "@lib/components/SmartNodeSelector"; -import { VectorSelector, createVectorSelectorDataFromVectors } from "@modules/_shared/components/VectorSelector"; +import { Select } from "@lib/components/Select"; +import { SmartNodeSelectorSelection } from "@lib/components/SmartNodeSelector"; +import { VectorSelector } from "@modules/_shared/components/VectorSelector"; -import { useAtom, useSetAtom } from "jotai"; +import { useAtom, useAtomValue, useSetAtom } from "jotai"; import { isEqual } from "lodash"; import { resamplingFrequencyAtom, - selectedSensitivitiesAtom, showHistoricalAtom, showRealizationsAtom, showStatisticsAtom, - vectorSpecAtom, + syncedEnsembleIdentsAtom, + syncedVectorNameAtom, + userSelectedEnsembleIdentAtom, + userSelectedSensitivityNamesAtom, + userSelectedVectorNameAtom, + userSelectedVectorTagAtom, } from "./atoms/baseAtoms"; -import { useVectorListQuery } from "./hooks/queryHooks"; +import { + availableSensitivityNamesAtom, + selectedEnsembleIdentAtom, + selectedSensitivityNamesAtom, + selectedVectorTagAtom, + vectorSelectorDataAtom, +} from "./atoms/derivedAtoms"; +import { vectorListQueryAtom } from "./atoms/queryAtoms"; import { Interfaces } from "../interfaces"; +import { FrequencyEnumToStringMapping } from "../typesAndEnums"; //----------------------------------------------------------------------------------------------------------- export function Settings({ settingsContext, workbenchSession, workbenchServices }: ModuleSettingsProps) { const ensembleSet = useEnsembleSet(workbenchSession); - const [selectedEnsembleIdent, setSelectedEnsembleIdent] = React.useState(null); - const [selectedVectorName, setSelectedVectorName] = React.useState(null); - const [selectedVectorTag, setSelectedVectorTag] = React.useState(null); - const [vectorSelectorData, setVectorSelectorData] = React.useState([]); - const [selectInitialVector, setSelectInitialVector] = React.useState(true); + const setSyncedEnsembleIdents = useSetAtom(syncedEnsembleIdentsAtom); + const setSyncedVectorName = useSetAtom(syncedVectorNameAtom); + const setUserSelectedEnsembleIdent = useSetAtom(userSelectedEnsembleIdentAtom); + const setUserSelectedVectorName = useSetAtom(userSelectedVectorNameAtom); + const setUserSelectedVectorTag = useSetAtom(userSelectedVectorTagAtom); + const setUserSelectedSensitivityNamesAtom = useSetAtom(userSelectedSensitivityNamesAtom); + const selectedEnsembleIdent = useAtomValue(selectedEnsembleIdentAtom); + const vectorsListQuery = useAtomValue(vectorListQueryAtom); + const availableSensitivityNames = useAtomValue(availableSensitivityNamesAtom); + const selectedSensitivityNames = useAtomValue(selectedSensitivityNamesAtom); + const vectorSelectorData = useAtomValue(vectorSelectorDataAtom); + const selectedVectorTag = useAtomValue(selectedVectorTagAtom); - const [selectedSensitivities, setSelectedSensitivities] = useAtom(selectedSensitivitiesAtom); const [resampleFrequency, setResamplingFrequency] = useAtom(resamplingFrequencyAtom); const [showStatistics, setShowStatistics] = useAtom(showStatisticsAtom); const [showRealizations, setShowRealizations] = useAtom(showRealizationsAtom); const [showHistorical, setShowHistorical] = useAtom(showHistoricalAtom); - const setVectorSpec = useSetAtom(vectorSpecAtom); + const syncedSettingKeys = settingsContext.useSyncedSettingKeys(); const syncHelper = new SyncSettingsHelper(syncedSettingKeys, workbenchServices); const syncedValueEnsembles = syncHelper.useValue(SyncSettingKey.ENSEMBLE, "global.syncValue.ensembles"); const syncedValueSummaryVector = syncHelper.useValue(SyncSettingKey.TIME_SERIES, "global.syncValue.timeSeries"); + const [prevSyncedEnsembleIdents, setPrevSyncedEnsembleIdents] = React.useState(null); + const [prevSyncedSummaryVector, setPrevSyncedSummaryVector] = React.useState<{ vectorName: string } | null>(null); - const candidateEnsembleIdent = maybeAssignFirstSyncedEnsemble(selectedEnsembleIdent, syncedValueEnsembles); - const computedEnsembleIdent = fixupEnsembleIdent(candidateEnsembleIdent, ensembleSet); - - const vectorsListQuery = useVectorListQuery( - computedEnsembleIdent?.getCaseUuid(), - computedEnsembleIdent?.getEnsembleName() - ); - - const hasQueryData = vectorsListQuery.data !== undefined; - const vectorNames = vectorsListQuery.data?.map((vec) => vec.name) ?? []; - - // Get vector selector data - let candidateVectorSelectorData = vectorSelectorData; - let candidateVectorName = selectedVectorName; - let candidateVectorTag = selectedVectorTag; - if (hasQueryData) { - candidateVectorSelectorData = createVectorSelectorDataFromVectors(vectorNames); - if (!isEqual(vectorSelectorData, candidateVectorSelectorData)) { - setVectorSelectorData(candidateVectorSelectorData); - - if (selectInitialVector) { - setSelectInitialVector(false); - const fixedUpVectorName = fixupVectorName(selectedVectorName, vectorNames); - if (fixedUpVectorName !== selectedVectorName) { - setSelectedVectorName(fixedUpVectorName); - setSelectedVectorTag(fixedUpVectorName); - candidateVectorName = fixedUpVectorName; - candidateVectorTag = fixedUpVectorName; - } - } + if (!isEqual(syncedValueEnsembles, prevSyncedEnsembleIdents)) { + setPrevSyncedEnsembleIdents(syncedValueEnsembles); + if (syncedValueEnsembles) { + setSyncedEnsembleIdents(syncedValueEnsembles); } } - // Override candidates if synced - if (syncedValueSummaryVector?.vectorName) { - candidateVectorName = syncedValueSummaryVector.vectorName; - candidateVectorTag = syncedValueSummaryVector.vectorName; + if (!isEqual(syncedValueSummaryVector, prevSyncedSummaryVector)) { + setPrevSyncedSummaryVector(syncedValueSummaryVector); + if (syncedValueSummaryVector) { + setSyncedVectorName(syncedValueSummaryVector.vectorName); + } } - const computedVectorSelectorData = candidateVectorSelectorData; - const computedVectorName = candidateVectorName; - const computedVectorTag = candidateVectorTag; - - const computedEnsemble = computedEnsembleIdent ? ensembleSet.findEnsemble(computedEnsembleIdent) : null; - const sensitivityNames = React.useMemo( - () => computedEnsemble?.getSensitivities()?.getSensitivityNames() ?? [], - [computedEnsemble] - ); - - React.useEffect( - function setSensitivitiesOnEnsembleChange() { - if (!isEqual(selectedSensitivities, sensitivityNames)) { - setSelectedSensitivities(sensitivityNames); - } - }, - [computedEnsemble, sensitivityNames, selectedSensitivities, setSelectedSensitivities] - ); - const sensitivityOptions: SelectOption[] = sensitivityNames.map((name) => ({ - value: name, - label: name, - })); - - const hasComputedVectorName = vectorsListQuery.data?.some((vec) => vec.name === computedVectorName) ?? false; - const hasHistoricalVector = - vectorsListQuery.data?.some((vec) => vec.name === computedVectorName && vec.has_historical) ?? false; - React.useEffect( - function propagateVectorSpecToView() { - if (hasComputedVectorName && computedEnsembleIdent && computedVectorName) { - setVectorSpec({ - ensembleIdent: computedEnsembleIdent, - vectorName: computedVectorName, - hasHistorical: hasHistoricalVector, - }); - } else { - setVectorSpec(null); - } - }, - [computedEnsembleIdent, computedVectorName, hasComputedVectorName, hasHistoricalVector, setVectorSpec] - ); function handleEnsembleSelectionChange(newEnsembleIdent: EnsembleIdent | null) { - setSelectedEnsembleIdent(newEnsembleIdent); + setUserSelectedEnsembleIdent(newEnsembleIdent); if (newEnsembleIdent) { syncHelper.publishValue(SyncSettingKey.ENSEMBLE, "global.syncValue.ensembles", [newEnsembleIdent]); } @@ -147,19 +101,23 @@ export function Settings({ settingsContext, workbenchSession, workbenchServices setResamplingFrequency(newFreq); } function handleVectorSelectChange(selection: SmartNodeSelectorSelection) { - setSelectedVectorName(selection.selectedNodes[0] ?? null); - setSelectedVectorTag(selection.selectedTags[0]?.text ?? null); + setUserSelectedVectorName(selection.selectedNodes[0] ?? null); + setUserSelectedVectorTag(selection.selectedTags[0]?.text ?? null); } function handleShowHistorical(event: React.ChangeEvent) { setShowHistorical(event.target.checked); } + function handleSensitivityNamesSelectionChange(newSensitivities: string[]) { + setUserSelectedSensitivityNamesAtom(newSensitivities); + } + return ( <> @@ -171,8 +129,8 @@ export function Settings({ settingsContext, workbenchSession, workbenchServices