diff --git a/backend_py/primary/primary/routers/explore/router.py b/backend_py/primary/primary/routers/explore/router.py index 50fd5c929..ee32801a5 100644 --- a/backend_py/primary/primary/routers/explore/router.py +++ b/backend_py/primary/primary/routers/explore/router.py @@ -1,18 +1,21 @@ -from typing import List import asyncio +import logging +from typing import List, Coroutine, Any - -from fastapi import APIRouter, Depends, Path, Query, Body - +from fastapi import APIRouter, Depends, Path, Query, Body, Response from primary.auth.auth_helper import AuthHelper +from primary.middleware.add_browser_cache import no_cache from primary.services.sumo_access.case_inspector import CaseInspector from primary.services.sumo_access.sumo_inspector import SumoInspector +from primary.services.sumo_access.sumo_fingerprinter import get_sumo_fingerprinter_for_user from primary.services.utils.authenticated_user import AuthenticatedUser -from primary.middleware.add_browser_cache import no_cache +from primary.utils.response_perf_metrics import ResponsePerfMetrics from . import schemas +LOGGER = logging.getLogger(__name__) + router = APIRouter() @@ -67,7 +70,6 @@ async def get_cases( @router.get("/cases/{case_uuid}/ensembles/{ensemble_name}") -@no_cache async def get_ensemble_details( authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), case_uuid: str = Path(description="Sumo case uuid"), @@ -80,7 +82,6 @@ async def get_ensemble_details( realizations = await case_inspector.get_realizations_in_ensemble_async(ensemble_name) field_identifiers = await case_inspector.get_field_identifiers_async() stratigraphic_column_identifier = await case_inspector.get_stratigraphic_column_identifier_async() - timestamps = await case_inspector.get_ensemble_timestamps_async(ensemble_name) standard_results = await case_inspector.get_standard_results_in_ensemble_async(ensemble_name) if len(field_identifiers) != 1: @@ -93,41 +94,42 @@ async def get_ensemble_details( realizations=realizations, fieldIdentifier=field_identifiers[0], stratigraphicColumnIdentifier=stratigraphic_column_identifier, - timestamps=schemas.EnsembleTimestamps( - caseUpdatedAtUtcMs=timestamps.case_updated_at_utc_ms, - dataUpdatedAtUtcMs=timestamps.data_updated_at_utc_ms, - ), standardResults=standard_results, ) -@router.post("/ensembles/get_timestamps") -@no_cache -async def post_get_timestamps_for_ensembles( +@router.post("/ensembles/refresh_fingerprints") +async def post_refresh_fingerprints_for_ensembles( + # fmt:off + response: Response, authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), - ensemble_idents: list[schemas.EnsembleIdent] = Body( - description="A list of ensemble idents (aka; case uuid and ensemble name)" - ), -) -> list[schemas.EnsembleTimestamps]: + ensemble_idents: list[schemas.EnsembleIdent] = Body(description="Ensembles to refresh and get fingerprints for, specified as pairs of caseUuid,ensembleName"), + # fmt:on +) -> list[str | None]: """ - Fetches ensemble timestamps for a list of ensembles + Retrieves freshly calculated fingerprints for a list of ensembles """ - return await asyncio.gather( - *[_get_ensemble_timestamps_for_ident_async(authenticated_user, ident) for ident in ensemble_idents] - ) + perf_metrics = ResponsePerfMetrics(response) + # For how long should we cache the calculated fingerprints? + # Given that currently we will have the frontend call this endpoint every 5 minutes, a TTL of 5 minutes seems reasonable + fingerprinter = get_sumo_fingerprinter_for_user(authenticated_user=authenticated_user, cache_ttl_s=5 * 60) -async def _get_ensemble_timestamps_for_ident_async( - authenticated_user: AuthenticatedUser, ensemble_ident: schemas.EnsembleIdent -) -> schemas.EnsembleTimestamps: - case_uuid = ensemble_ident.caseUuid - ensemble_name = ensemble_ident.ensembleName + coros_arr: list[Coroutine[Any, Any, str]] = [] + for ident in ensemble_idents: + coros_arr.append(fingerprinter.calc_and_store_ensemble_fp_async(ident.caseUuid, ident.ensembleName)) - case_inspector = CaseInspector.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid) + raw_results = await asyncio.gather(*coros_arr, return_exceptions=True) + perf_metrics.record_lap("calc-and-write-fingerprints") - timestamps = await case_inspector.get_ensemble_timestamps_async(ensemble_name) + ret_fingerprints: list[str | None] = [] + for res in raw_results: + if isinstance(res, str): + ret_fingerprints.append(res) + else: + LOGGER.warning(f"Unable to calculate fingerprint for ensemble {ident}: {res}") + ret_fingerprints.append(None) - return schemas.EnsembleTimestamps( - caseUpdatedAtUtcMs=timestamps.case_updated_at_utc_ms, - dataUpdatedAtUtcMs=timestamps.data_updated_at_utc_ms, - ) + LOGGER.debug(f"Calculated and refreshed {len(ret_fingerprints)} fingerprints in: {perf_metrics.to_string()}") + + return ret_fingerprints diff --git a/backend_py/primary/primary/routers/explore/schemas.py b/backend_py/primary/primary/routers/explore/schemas.py index 35e71c13c..788730bd2 100644 --- a/backend_py/primary/primary/routers/explore/schemas.py +++ b/backend_py/primary/primary/routers/explore/schemas.py @@ -40,5 +40,4 @@ class EnsembleDetails(BaseModel): caseUuid: str realizations: Sequence[int] stratigraphicColumnIdentifier: str - timestamps: EnsembleTimestamps standardResults: Sequence[str] diff --git a/backend_py/primary/primary/routers/surface/task_helpers.py b/backend_py/primary/primary/routers/surface/task_helpers.py index 8bb001a6c..b31bf1193 100644 --- a/backend_py/primary/primary/routers/surface/task_helpers.py +++ b/backend_py/primary/primary/routers/surface/task_helpers.py @@ -17,13 +17,12 @@ async def determine_surf_task_fingerprint_async( authenticated_user: AuthenticatedUser, addr: StatisticalSurfaceAddress ) -> str: - # For how long should we cache the ensemble fingerprint? - # The TTL for fingerprints should be aligned with the polling interval we use for busting the client side browser cache. - # Actually, the backend code that provides data for the the client side browser caching should probably go via the very same SumoFingerprinter cache. - fingerprinter = get_sumo_fingerprinter_for_user(authenticated_user=authenticated_user, cache_ttl_s=30) + # For how long should we cache the ensemble fingerprint here? + # Note that the explore endpoint that calculates/refreshes fingerprints sets a TTL of 5 minutes. + # Be a bit defensive here and set a TTL of 2 minutes. + fingerprinter = get_sumo_fingerprinter_for_user(authenticated_user=authenticated_user, cache_ttl_s=2 * 60) - # Note that we limit the ensemble portion of the fingerprint to surfaces - ensemble_fp = await fingerprinter.get_or_calc_ensemble_fp_async(addr.case_uuid, addr.ensemble_name, "surface") + ensemble_fp = await fingerprinter.get_or_calc_ensemble_fp_async(addr.case_uuid, addr.ensemble_name) # Note that we include the ensemble fingerprint in the task hash/fingerprint task_fp = sha256((addr.to_addr_str() + ensemble_fp).encode()).hexdigest() diff --git a/backend_py/primary/primary/services/sumo_access/case_inspector.py b/backend_py/primary/primary/services/sumo_access/case_inspector.py index 6c5ce8664..ff8d6cc62 100644 --- a/backend_py/primary/primary/services/sumo_access/case_inspector.py +++ b/backend_py/primary/primary/services/sumo_access/case_inspector.py @@ -5,7 +5,6 @@ from fmu.sumo.explorer.objects import Case, SearchContext from webviz_pkg.core_utils.perf_metrics import PerfMetrics -from webviz_pkg.core_utils.timestamp_utils import iso_str_to_timestamp_utc_ms from primary.services.service_exceptions import ( Service, NoDataError, @@ -19,15 +18,9 @@ LOGGER = logging.getLogger(__name__) -class EnsembleTimestamps(BaseModel): - case_updated_at_utc_ms: int - data_updated_at_utc_ms: int - - class EnsembleInfo(BaseModel): name: str realization_count: int - timestamps: EnsembleTimestamps class CaseInspector: @@ -49,33 +42,6 @@ async def _get_or_create_case_context_async(self) -> Case: return self._cached_case_context - async def _get_case_updated_timestamp_async(self) -> int: - case = await self._get_or_create_case_context_async() - timestamp_str = case.metadata["_sumo"]["timestamp"] # Returns a datetime string. - return iso_str_to_timestamp_utc_ms(timestamp_str) - - async def _get_ensemble_data_update_timestamp_async(self, ensemble_name: str) -> int: - timer = PerfMetrics() - case_context = await self._get_or_create_case_context_async() - - search_context = SearchContext(self._sumo_client).filter( - uuid=case_context.uuid, ensemble=ensemble_name, realization=True - ) - - data_timestamp_int = await search_context.metrics.max_async("_sumo.timestamp") - - timer.record_lap("aggregate_data_timestamps") - LOGGER.debug(f"get_last_data_change_timestamp_async {timer.to_string()}") - - return data_timestamp_int or -1 - - async def get_ensemble_timestamps_async(self, ensemble_name: str) -> EnsembleTimestamps: - case_updated_at = await self._get_case_updated_timestamp_async() - # Data is occasionally None. This is likely due to data errors, so we just default to 0 (since an ensemble with bad data probably won't be used) - data_updated_at = await self._get_ensemble_data_update_timestamp_async(ensemble_name) or 0 - - return EnsembleTimestamps(case_updated_at_utc_ms=case_updated_at, data_updated_at_utc_ms=data_updated_at) - async def get_case_name_async(self) -> str: """Get name of the case""" case = await self._get_or_create_case_context_async() @@ -86,9 +52,7 @@ async def _get_ensemble_info_async(self, ensemble_uuid: str) -> EnsembleInfo: ensemble_obj = await search_context.get_ensemble_by_uuid_async(ensemble_uuid) realization_count = len(await ensemble_obj.realizations_async) - ensemble_timestamps = await self.get_ensemble_timestamps_async(ensemble_obj.name) - - return EnsembleInfo(name=ensemble_obj.name, realization_count=realization_count, timestamps=ensemble_timestamps) + return EnsembleInfo(name=ensemble_obj.name, realization_count=realization_count) async def get_ensembles_async(self) -> list[EnsembleInfo]: """Get list of ensembles for a case""" diff --git a/backend_py/primary/primary/services/sumo_access/sumo_fingerprinter.py b/backend_py/primary/primary/services/sumo_access/sumo_fingerprinter.py index 596873aa9..0cb91e763 100644 --- a/backend_py/primary/primary/services/sumo_access/sumo_fingerprinter.py +++ b/backend_py/primary/primary/services/sumo_access/sumo_fingerprinter.py @@ -57,35 +57,52 @@ def __init__(self, authenticated_user: AuthenticatedUser, redis_client: redis.Re self._redis_client = redis_client self._cache_ttl_s = cache_ttl_s - async def get_or_calc_ensemble_fp_async(self, case_uuid: str, ensemble_name: str, class_name: str | None) -> str: + async def get_or_calc_ensemble_fp_async(self, case_uuid: str, ensemble_name: str) -> str: """ Get from cache or calculate the fingerprint string for contents of an ensemble. See calc_ensemble_fp_async() for details. """ perf_metrics = PerfMetrics() - redis_key = self._make_full_redis_key(case_uuid=case_uuid, ensemble_name=ensemble_name, class_name=class_name) + redis_key = self._make_full_redis_key(case_uuid=case_uuid, ensemble_name=ensemble_name) cached_fp = await self._redis_client.get(redis_key) perf_metrics.record_lap("redis-get") if cached_fp is not None: - # LOGGER.debug(f"get_or_calc_ensemble_fp_async() - from cache in: {perf_metrics.to_string()} [{cached_fp=}]") + LOGGER.debug(f"get_or_calc_ensemble_fp_async() - from cache in: {perf_metrics.to_string()} [{cached_fp=}]") return cached_fp - new_fp = await calc_ensemble_fp_async(self._sumo_client, case_uuid, ensemble_name, class_name) + new_fp = await calc_ensemble_fp_async(self._sumo_client, case_uuid, ensemble_name, None) perf_metrics.record_lap("calc-fp") # Schedule the Redis set call, but don't await it asyncio.create_task(self._redis_client.set(name=redis_key, value=new_fp, ex=self._cache_ttl_s)) perf_metrics.record_lap("schedule-redis-set") - # LOGGER.debug(f"get_or_calc_ensemble_fp_async() - calculated in: {perf_metrics.to_string()} [{new_fp=}]") + LOGGER.debug(f"get_or_calc_ensemble_fp_async() - calculated in: {perf_metrics.to_string()} [{new_fp=}]") return new_fp - def _make_full_redis_key(self, case_uuid: str, ensemble_name: str, class_name: str | None) -> str: - return ( - f"{_REDIS_KEY_PREFIX}:user:{self._user_id}:case:{case_uuid}:ens:{ensemble_name}:class:{class_name or 'ALL'}" - ) + async def calc_and_store_ensemble_fp_async(self, case_uuid: str, ensemble_name: str) -> str: + """ + Calculate and unconditionally store fingerprint string for contents of an ensemble, also returning the result. + This method does not check the cache first, it will always calculate a new fingerprint and write it to the cache. + See calc_ensemble_fp_async() for details. + """ + perf_metrics = PerfMetrics() + + redis_key = self._make_full_redis_key(case_uuid=case_uuid, ensemble_name=ensemble_name) + + new_fp = await calc_ensemble_fp_async(self._sumo_client, case_uuid, ensemble_name, None) + perf_metrics.record_lap("calc-fp") + + await self._redis_client.set(name=redis_key, value=new_fp, ex=self._cache_ttl_s) + perf_metrics.record_lap("redis-set") + + LOGGER.debug(f"calc_and_store_ensemble_fp_async() - calculated in: {perf_metrics.to_string()} [{new_fp=}]") + return new_fp + + def _make_full_redis_key(self, case_uuid: str, ensemble_name: str) -> str: + return f"{_REDIS_KEY_PREFIX}:user:{self._user_id}:case:{case_uuid}:ens:{ensemble_name}" def get_sumo_fingerprinter_for_user(authenticated_user: AuthenticatedUser, cache_ttl_s: int) -> SumoFingerprinter: diff --git a/frontend/open-api/cachebusting-plugin/config.ts b/frontend/open-api/cachebusting-plugin/config.ts index 00ea40e13..2ad69d3b7 100644 --- a/frontend/open-api/cachebusting-plugin/config.ts +++ b/frontend/open-api/cachebusting-plugin/config.ts @@ -6,7 +6,7 @@ import type { Config } from "./types"; export const defaultConfig: Plugin.Config = { name: "cache-busting", output: "types", - cacheKey: "t", + cacheKey: "zCacheBust", // No need to define this _handlerLegacy: () => {}, _handler: handler, diff --git a/frontend/open-api/cachebusting-plugin/plugin.ts b/frontend/open-api/cachebusting-plugin/plugin.ts index 73713bd50..b9dce0e76 100644 --- a/frontend/open-api/cachebusting-plugin/plugin.ts +++ b/frontend/open-api/cachebusting-plugin/plugin.ts @@ -17,7 +17,7 @@ export const handler: Plugin.Handler = ({ context, plugin }) => { location: "query", explode: false, name: cacheKey, - schema: { type: "number" }, + schema: { type: "string" }, style: "form", }; } diff --git a/frontend/open-api/cachebusting-plugin/types.d.ts b/frontend/open-api/cachebusting-plugin/types.d.ts index 2bf0418f1..a2fc840c6 100644 --- a/frontend/open-api/cachebusting-plugin/types.d.ts +++ b/frontend/open-api/cachebusting-plugin/types.d.ts @@ -12,7 +12,7 @@ export interface Config { /** * The query parameter to use for caching - * @default "t" + * @default "f" */ cacheKey?: string | IR.ParameterObject; } diff --git a/frontend/src/api/autogen/@tanstack/react-query.gen.ts b/frontend/src/api/autogen/@tanstack/react-query.gen.ts index 60b363d37..3506025eb 100644 --- a/frontend/src/api/autogen/@tanstack/react-query.gen.ts +++ b/frontend/src/api/autogen/@tanstack/react-query.gen.ts @@ -8,7 +8,7 @@ import { getFields, getCases, getEnsembleDetails, - postGetTimestampsForEnsembles, + postRefreshFingerprintsForEnsembles, getVectorList, getDeltaEnsembleVectorList, getRealizationsVectorData, @@ -84,9 +84,9 @@ import type { GetFieldsData_api, GetCasesData_api, GetEnsembleDetailsData_api, - PostGetTimestampsForEnsemblesData_api, - PostGetTimestampsForEnsemblesError_api, - PostGetTimestampsForEnsemblesResponse_api, + PostRefreshFingerprintsForEnsemblesData_api, + PostRefreshFingerprintsForEnsemblesError_api, + PostRefreshFingerprintsForEnsemblesResponse_api, GetVectorListData_api, GetDeltaEnsembleVectorListData_api, GetRealizationsVectorDataData_api, @@ -258,14 +258,16 @@ export const getEnsembleDetailsOptions = (options: Options) => [ - createQueryKey("postGetTimestampsForEnsembles", options), -]; +export const postRefreshFingerprintsForEnsemblesQueryKey = ( + options: Options, +) => [createQueryKey("postRefreshFingerprintsForEnsembles", options)]; -export const postGetTimestampsForEnsemblesOptions = (options: Options) => { +export const postRefreshFingerprintsForEnsemblesOptions = ( + options: Options, +) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { - const { data } = await postGetTimestampsForEnsembles({ + const { data } = await postRefreshFingerprintsForEnsembles({ ...options, ...queryKey[0], signal, @@ -273,20 +275,20 @@ export const postGetTimestampsForEnsemblesOptions = (options: Options>, +export const postRefreshFingerprintsForEnsemblesMutation = ( + options?: Partial>, ) => { const mutationOptions: UseMutationOptions< - PostGetTimestampsForEnsemblesResponse_api, - AxiosError, - Options + PostRefreshFingerprintsForEnsemblesResponse_api, + AxiosError, + Options > = { mutationFn: async (localOptions) => { - const { data } = await postGetTimestampsForEnsembles({ + const { data } = await postRefreshFingerprintsForEnsembles({ ...options, ...localOptions, throwOnError: true, diff --git a/frontend/src/api/autogen/sdk.gen.ts b/frontend/src/api/autogen/sdk.gen.ts index d07a9d1b4..325d4b055 100644 --- a/frontend/src/api/autogen/sdk.gen.ts +++ b/frontend/src/api/autogen/sdk.gen.ts @@ -11,9 +11,9 @@ import type { GetEnsembleDetailsData_api, GetEnsembleDetailsResponse_api, GetEnsembleDetailsError_api, - PostGetTimestampsForEnsemblesData_api, - PostGetTimestampsForEnsemblesResponse_api, - PostGetTimestampsForEnsemblesError_api, + PostRefreshFingerprintsForEnsemblesData_api, + PostRefreshFingerprintsForEnsemblesResponse_api, + PostRefreshFingerprintsForEnsemblesError_api, GetVectorListData_api, GetVectorListResponse_api, GetVectorListError_api, @@ -254,15 +254,15 @@ export const getEnsembleDetails = ( }; /** - * Post Get Timestamps For Ensembles - * Fetches ensemble timestamps for a list of ensembles + * Post Refresh Fingerprints For Ensembles + * Retrieves freshly calculated fingerprints for a list of ensembles */ -export const postGetTimestampsForEnsembles = ( - options: Options, +export const postRefreshFingerprintsForEnsembles = ( + options: Options, ) => { return (options?.client ?? client).post< - PostGetTimestampsForEnsemblesResponse_api, - PostGetTimestampsForEnsemblesError_api, + PostRefreshFingerprintsForEnsemblesResponse_api, + PostRefreshFingerprintsForEnsemblesError_api, ThrowOnError >({ ...options, @@ -270,7 +270,7 @@ export const postGetTimestampsForEnsembles = ; stratigraphicColumnIdentifier: string; - timestamps: EnsembleTimestamps_api; standardResults: Array; }; @@ -184,11 +183,6 @@ export type EnsembleSensitivityCase_api = { realizations: Array; }; -export type EnsembleTimestamps_api = { - caseUpdatedAtUtcMs: number; - dataUpdatedAtUtcMs: number; -}; - export type FenceMeshSection_api = { vertices_uz_b64arr: B64FloatArray_api; poly_indices_b64arr: B64UintArray_api; @@ -1196,7 +1190,7 @@ export type GetFieldsData_api = { body?: never; path?: never; query?: { - t?: number; + zCacheBust?: string; }; url: "/fields"; }; @@ -1218,7 +1212,7 @@ export type GetCasesData_api = { * Field identifier */ field_identifier: string; - t?: number; + zCacheBust?: string; }; url: "/cases"; }; @@ -1254,7 +1248,7 @@ export type GetEnsembleDetailsData_api = { ensemble_name: string; }; query?: { - t?: number; + zCacheBust?: string; }; url: "/cases/{case_uuid}/ensembles/{ensemble_name}"; }; @@ -1277,37 +1271,37 @@ export type GetEnsembleDetailsResponses_api = { export type GetEnsembleDetailsResponse_api = GetEnsembleDetailsResponses_api[keyof GetEnsembleDetailsResponses_api]; -export type PostGetTimestampsForEnsemblesData_api = { +export type PostRefreshFingerprintsForEnsemblesData_api = { /** - * A list of ensemble idents (aka; case uuid and ensemble name) + * Ensembles to refresh and get fingerprints for, specified as pairs of caseUuid,ensembleName */ body: Array; path?: never; query?: { - t?: number; + zCacheBust?: string; }; - url: "/ensembles/get_timestamps"; + url: "/ensembles/refresh_fingerprints"; }; -export type PostGetTimestampsForEnsemblesErrors_api = { +export type PostRefreshFingerprintsForEnsemblesErrors_api = { /** * Validation Error */ 422: HttpValidationError_api; }; -export type PostGetTimestampsForEnsemblesError_api = - PostGetTimestampsForEnsemblesErrors_api[keyof PostGetTimestampsForEnsemblesErrors_api]; +export type PostRefreshFingerprintsForEnsemblesError_api = + PostRefreshFingerprintsForEnsemblesErrors_api[keyof PostRefreshFingerprintsForEnsemblesErrors_api]; -export type PostGetTimestampsForEnsemblesResponses_api = { +export type PostRefreshFingerprintsForEnsemblesResponses_api = { /** * Successful Response */ - 200: Array; + 200: Array; }; -export type PostGetTimestampsForEnsemblesResponse_api = - PostGetTimestampsForEnsemblesResponses_api[keyof PostGetTimestampsForEnsemblesResponses_api]; +export type PostRefreshFingerprintsForEnsemblesResponse_api = + PostRefreshFingerprintsForEnsemblesResponses_api[keyof PostRefreshFingerprintsForEnsemblesResponses_api]; export type GetVectorListData_api = { body?: never; @@ -1325,7 +1319,7 @@ export type GetVectorListData_api = { * Include derived vectors */ include_derived_vectors?: boolean | null; - t?: number; + zCacheBust?: string; }; url: "/timeseries/vector_list/"; }; @@ -1372,7 +1366,7 @@ export type GetDeltaEnsembleVectorListData_api = { * Include derived vectors */ include_derived_vectors?: boolean | null; - t?: number; + zCacheBust?: string; }; url: "/timeseries/delta_ensemble_vector_list/"; }; @@ -1420,7 +1414,7 @@ export type GetRealizationsVectorDataData_api = { * Optional list of realizations encoded as string to include. If not specified, all realizations will be included. */ realizations_encoded_as_uint_list_str?: string | null; - t?: number; + zCacheBust?: string; }; url: "/timeseries/realizations_vector_data/"; }; @@ -1476,7 +1470,7 @@ export type GetDeltaEnsembleRealizationsVectorDataData_api = { * Optional list of realizations encoded as string to include. If not specified, all realizations will be included. */ realizations_encoded_as_uint_list_str?: string | null; - t?: number; + zCacheBust?: string; }; url: "/timeseries/delta_ensemble_realizations_vector_data/"; }; @@ -1517,7 +1511,7 @@ export type GetTimestampsListData_api = { * Resampling frequency */ resampling_frequency?: Frequency_api | null; - t?: number; + zCacheBust?: string; }; url: "/timeseries/timestamps_list/"; }; @@ -1560,7 +1554,7 @@ export type GetHistoricalVectorDataData_api = { * Resampling frequency */ resampling_frequency?: Frequency_api | null; - t?: number; + zCacheBust?: string; }; url: "/timeseries/historical_vector_data/"; }; @@ -1611,7 +1605,7 @@ export type GetStatisticalVectorDataData_api = { * Optional list of realizations encoded as string to include. If not specified, all realizations will be included. */ realizations_encoded_as_uint_list_str?: string | null; - t?: number; + zCacheBust?: string; }; url: "/timeseries/statistical_vector_data/"; }; @@ -1671,7 +1665,7 @@ export type GetDeltaEnsembleStatisticalVectorDataData_api = { * Optional list of realizations encoded as string to include. If not specified, all realizations will be included. */ realizations_encoded_as_uint_list_str?: string | null; - t?: number; + zCacheBust?: string; }; url: "/timeseries/delta_ensemble_statistical_vector_data/"; }; @@ -1724,7 +1718,7 @@ export type GetStatisticalVectorDataPerSensitivityData_api = { * Optional list of realizations to include. If not specified, all realizations will be included. */ realizations_encoded_as_uint_list_str?: string | null; - t?: number; + zCacheBust?: string; }; url: "/timeseries/statistical_vector_data_per_sensitivity/"; }; @@ -1769,7 +1763,7 @@ export type GetRealizationVectorAtTimestampData_api = { * Timestamp in ms UTC to query vectors at */ timestamp_utc_ms: number; - t?: number; + zCacheBust?: string; }; url: "/timeseries/realization_vector_at_timestamp/"; }; @@ -1806,7 +1800,7 @@ export type GetTableDefinitionsData_api = { * Ensemble name */ ensemble_name: string; - t?: number; + zCacheBust?: string; }; url: "/inplace_volumes/table_definitions/"; }; @@ -1857,7 +1851,7 @@ export type PostGetAggregatedPerRealizationTableDataData_api = { * Optional list of realizations encoded as string to include. If not specified, all realizations will be included. */ realizations_encoded_as_uint_list_str?: string | null; - t?: number; + zCacheBust?: string; }; url: "/inplace_volumes/get_aggregated_per_realization_table_data/"; }; @@ -1910,7 +1904,7 @@ export type PostGetAggregatedStatisticalTableDataData_api = { * Optional list of realizations encoded as string to include. If not specified, all realizations will be included. */ realizations_encoded_as_uint_list_str?: string | null; - t?: number; + zCacheBust?: string; }; url: "/inplace_volumes/get_aggregated_statistical_table_data/"; }; @@ -1947,7 +1941,7 @@ export type GetRealizationSurfacesMetadataData_api = { * Ensemble name */ ensemble_name: string; - t?: number; + zCacheBust?: string; }; url: "/surface/realization_surfaces_metadata/"; }; @@ -1980,7 +1974,7 @@ export type GetObservedSurfacesMetadataData_api = { * Sumo case uuid */ case_uuid: string; - t?: number; + zCacheBust?: string; }; url: "/surface/observed_surfaces_metadata/"; }; @@ -2021,7 +2015,7 @@ export type GetSurfaceDataData_api = { * Definition of the surface onto which the data should be resampled. *SurfaceDef_api* object properties encoded as a `KeyValStr` string. */ resample_to_def_str?: string | null; - t?: number; + zCacheBust?: string; }; url: "/surface/surface_data"; }; @@ -2060,7 +2054,7 @@ export type GetStatisticalSurfaceDataHybridData_api = { * Definition of the surface onto which the data should be resampled. *SurfaceDef_api* object properties encoded as a `KeyValStr` string. */ resample_to_def_str?: string | null; - t?: number; + zCacheBust?: string; }; url: "/surface/statistical_surface_data/hybrid"; }; @@ -2113,7 +2107,7 @@ export type PostGetSurfaceIntersectionData_api = { * Time point or time interval string */ time_or_interval_str?: string | null; - t?: number; + zCacheBust?: string; }; url: "/surface/get_surface_intersection"; }; @@ -2161,7 +2155,7 @@ export type PostGetSampleSurfaceInPointsData_api = { * Realization numbers */ realization_nums: Array; - t?: number; + zCacheBust?: string; }; url: "/surface/get_sample_surface_in_points"; }; @@ -2206,7 +2200,7 @@ export type GetDeltaSurfaceDataData_api = { * Definition of the surface onto which the data should be resampled. *SurfaceDef_api* object properties encoded as a `KeyValStr` string. */ resample_to_def_str?: string | null; - t?: number; + zCacheBust?: string; }; url: "/surface/delta_surface_data"; }; @@ -2257,7 +2251,7 @@ export type GetMisfitSurfaceDataData_api = { * Definition of the surface onto which the data should be resampled. *SurfaceDef_api* object properties encoded as a `KeyValStr` string. */ resample_to_def_str?: string | null; - t?: number; + zCacheBust?: string; }; url: "/surface/misfit_surface_data"; }; @@ -2288,7 +2282,7 @@ export type DeprecatedGetStratigraphicUnitsData_api = { * Sumo case uuid */ case_uuid: string; - t?: number; + zCacheBust?: string; }; url: "/surface/deprecated_stratigraphic_units"; }; @@ -2321,7 +2315,7 @@ export type GetStratigraphicUnitsForStratColumnData_api = { * SMDA stratigraphic column identifier */ strat_column: string; - t?: number; + zCacheBust?: string; }; url: "/surface/stratigraphic_units_for_strat_column"; }; @@ -2366,7 +2360,7 @@ export type GetParameterNamesAndDescriptionData_api = { * Sort order */ sort_order?: "alphabetically" | "standard_deviation"; - t?: number; + zCacheBust?: string; }; url: "/parameters/parameter_names_and_description/"; }; @@ -2407,7 +2401,7 @@ export type GetParameterData_api = { * Parameter name */ parameter_name: string; - t?: number; + zCacheBust?: string; }; url: "/parameters/parameter/"; }; @@ -2442,7 +2436,7 @@ export type GetParametersData_api = { * Ensemble name */ ensemble_name: string; - t?: number; + zCacheBust?: string; }; url: "/parameters/parameters/"; }; @@ -2477,7 +2471,7 @@ export type GetIsSensitivityRunData_api = { * Ensemble name */ ensemble_name: string; - t?: number; + zCacheBust?: string; }; url: "/parameters/is_sensitivity_run/"; }; @@ -2512,7 +2506,7 @@ export type GetSensitivitiesData_api = { * Ensemble name */ ensemble_name: string; - t?: number; + zCacheBust?: string; }; url: "/parameters/sensitivities/"; }; @@ -2551,7 +2545,7 @@ export type GetGridModelsInfoData_api = { * Realization */ realization_num: number; - t?: number; + zCacheBust?: string; }; url: "/grid3d/grid_models_info/"; }; @@ -2618,7 +2612,7 @@ export type GetGridSurfaceData_api = { * Max k index */ k_max?: number; - t?: number; + zCacheBust?: string; }; url: "/grid3d/grid_surface"; }; @@ -2693,7 +2687,7 @@ export type GetGridParameterData_api = { * Max k index */ k_max?: number; - t?: number; + zCacheBust?: string; }; url: "/grid3d/grid_parameter"; }; @@ -2744,7 +2738,7 @@ export type PostGetPolylineIntersectionData_api = { * Time point or time interval string */ parameter_time_or_interval_str?: string | null; - t?: number; + zCacheBust?: string; }; url: "/grid3d/get_polyline_intersection"; }; @@ -2793,7 +2787,7 @@ export type GetRealizationFlowNetworkData_api = { * Node types */ node_type_set: Array; - t?: number; + zCacheBust?: string; }; url: "/flow_network/realization_flow_network/"; }; @@ -2833,7 +2827,7 @@ export type GetTableDataData_api = { * Realization number */ realization: number; - t?: number; + zCacheBust?: string; }; url: "/pvt/table_data/"; }; @@ -2872,7 +2866,7 @@ export type GetWellCompletionsDataData_api = { * Optional realizations to include, list encoded as string. If not specified, all realizations will be returned. */ realizations_encoded_as_uint_list_str?: number | string | null; - t?: number; + zCacheBust?: string; }; url: "/well_completions/well_completions_data/"; }; @@ -2903,7 +2897,7 @@ export type GetDrilledWellboreHeadersData_api = { * Official field identifier */ field_identifier: string; - t?: number; + zCacheBust?: string; }; url: "/well/drilled_wellbore_headers/"; }; @@ -2939,7 +2933,7 @@ export type GetWellTrajectoriesData_api = { * Optional subset of wellbore uuids */ wellbore_uuids?: Array | null; - t?: number; + zCacheBust?: string; }; url: "/well/well_trajectories/"; }; @@ -2970,7 +2964,7 @@ export type GetWellborePickIdentifiersData_api = { * Stratigraphic column identifier */ strat_column_identifier: string; - t?: number; + zCacheBust?: string; }; url: "/well/wellbore_pick_identifiers/"; }; @@ -3006,7 +3000,7 @@ export type GetWellborePicksForPickIdentifierData_api = { * Pick identifier */ pick_identifier: string; - t?: number; + zCacheBust?: string; }; url: "/well/wellbore_picks_for_pick_identifier/"; }; @@ -3039,7 +3033,7 @@ export type DeprecatedGetWellborePicksForWellboreData_api = { * Wellbore uuid */ wellbore_uuid: string; - t?: number; + zCacheBust?: string; }; url: "/well/deprecated_wellbore_picks_for_wellbore/"; }; @@ -3076,7 +3070,7 @@ export type GetWellborePicksInStratColumnData_api = { * Filter by stratigraphic column */ strat_column_identifier: string; - t?: number; + zCacheBust?: string; }; url: "/well/wellbore_picks_in_strat_column"; }; @@ -3109,7 +3103,7 @@ export type GetWellboreStratigraphicColumnsData_api = { * Wellbore uuid */ wellbore_uuid: string; - t?: number; + zCacheBust?: string; }; url: "/well/wellbore_stratigraphic_columns/"; }; @@ -3142,7 +3136,7 @@ export type GetWellboreCompletionsData_api = { * Wellbore uuid */ wellbore_uuid: string; - t?: number; + zCacheBust?: string; }; url: "/well/wellbore_completions/"; }; @@ -3173,7 +3167,7 @@ export type GetWellboreCasingsData_api = { * Wellbore uuid */ wellbore_uuid: string; - t?: number; + zCacheBust?: string; }; url: "/well/wellbore_casings/"; }; @@ -3204,7 +3198,7 @@ export type GetWellborePerforationsData_api = { * Wellbore uuid */ wellbore_uuid: string; - t?: number; + zCacheBust?: string; }; url: "/well/wellbore_perforations/"; }; @@ -3239,7 +3233,7 @@ export type GetWellboreLogCurveHeadersData_api = { * Sources to fetch well-logs from. */ sources?: Array; - t?: number; + zCacheBust?: string; }; url: "/well/wellbore_log_curve_headers/"; }; @@ -3283,7 +3277,7 @@ export type GetLogCurveDataData_api = { * Source to fetch well-logs from. */ source?: WellLogCurveSourceEnum_api; - t?: number; + zCacheBust?: string; }; url: "/well/log_curve_data/"; }; @@ -3318,7 +3312,7 @@ export type GetSeismicCubeMetaListData_api = { * Ensemble name */ ensemble_name: string; - t?: number; + zCacheBust?: string; }; url: "/seismic/seismic_cube_meta_list/"; }; @@ -3373,7 +3367,7 @@ export type GetInlineSliceData_api = { * Inline number */ inline_number: number; - t?: number; + zCacheBust?: string; }; url: "/seismic/get_inline_slice/"; }; @@ -3428,7 +3422,7 @@ export type GetCrosslineSliceData_api = { * Crossline number */ crossline_num: number; - t?: number; + zCacheBust?: string; }; url: "/seismic/get_crossline_slice/"; }; @@ -3483,7 +3477,7 @@ export type GetDepthSliceData_api = { * Depth slice number */ depth_slice_num: number; - t?: number; + zCacheBust?: string; }; url: "/seismic/get_depth_slice/"; }; @@ -3546,7 +3540,7 @@ export type GetSeismicSlicesData_api = { * Depth slice number */ depth_slice_number: number; - t?: number; + zCacheBust?: string; }; url: "/seismic/get_seismic_slices/"; }; @@ -3597,7 +3591,7 @@ export type PostGetSeismicFenceData_api = { * Observed or simulated */ observed: boolean; - t?: number; + zCacheBust?: string; }; url: "/seismic/get_seismic_fence/"; }; @@ -3632,7 +3626,7 @@ export type GetPolygonsDirectoryData_api = { * Ensemble name */ ensemble_name: string; - t?: number; + zCacheBust?: string; }; url: "/polygons/polygons_directory/"; }; @@ -3679,7 +3673,7 @@ export type GetPolygonsDataData_api = { * Surface attribute */ attribute: string; - t?: number; + zCacheBust?: string; }; url: "/polygons/polygons_data/"; }; @@ -3711,7 +3705,7 @@ export type GetUserInfoData_api = { user_id_or_email: string; }; query?: { - t?: number; + zCacheBust?: string; }; url: "/graph/user_info/{user_id_or_email}"; }; @@ -3742,7 +3736,7 @@ export type GetUserPhotoData_api = { * User email, graph-id, or 'me' for the authenticated user */ user_id_or_email: string; - t?: number; + zCacheBust?: string; }; url: "/graph/user_photo/"; }; @@ -3773,7 +3767,7 @@ export type GetObservationsData_api = { * Sumo case uuid */ case_uuid: string; - t?: number; + zCacheBust?: string; }; url: "/observations/observations/"; }; @@ -3808,7 +3802,7 @@ export type GetTableDefinitionData_api = { * Ensemble name */ ensemble_name: string; - t?: number; + zCacheBust?: string; }; url: "/rft/table_definition"; }; @@ -3859,7 +3853,7 @@ export type GetRealizationDataData_api = { * Optional list of realizations encoded as string to include. If not specified, all realizations will be included. */ realizations_encoded_as_uint_list_str?: string | null; - t?: number; + zCacheBust?: string; }; url: "/rft/realization_data"; }; @@ -3898,7 +3892,7 @@ export type GetVfpTableNamesData_api = { * Realization */ realization: number; - t?: number; + zCacheBust?: string; }; url: "/vfp/vfp_table_names/"; }; @@ -3941,7 +3935,7 @@ export type GetVfpTableData_api = { * VFP table name */ vfp_table_name: string; - t?: number; + zCacheBust?: string; }; url: "/vfp/vfp_table/"; }; @@ -3969,7 +3963,7 @@ export type LoginRouteData_api = { path?: never; query?: { redirect_url_after_login?: string | null; - t?: number; + zCacheBust?: string; }; url: "/login"; }; @@ -3994,7 +3988,7 @@ export type AuthorizedCallbackRouteData_api = { body?: never; path?: never; query?: { - t?: number; + zCacheBust?: string; }; url: "/auth-callback"; }; @@ -4010,7 +4004,7 @@ export type GetAliveData_api = { body?: never; path?: never; query?: { - t?: number; + zCacheBust?: string; }; url: "/alive"; }; @@ -4028,7 +4022,7 @@ export type GetAliveProtectedData_api = { body?: never; path?: never; query?: { - t?: number; + zCacheBust?: string; }; url: "/alive_protected"; }; @@ -4046,7 +4040,7 @@ export type PostLogoutData_api = { body?: never; path?: never; query?: { - t?: number; + zCacheBust?: string; }; url: "/logout"; }; @@ -4068,7 +4062,7 @@ export type GetLoggedInUserData_api = { * Set to true to include user avatar and display name from Microsoft Graph API */ includeGraphApiInfo?: boolean; - t?: number; + zCacheBust?: string; }; url: "/logged_in_user"; }; @@ -4095,7 +4089,7 @@ export type RootData_api = { body?: never; path?: never; query?: { - t?: number; + zCacheBust?: string; }; url: "/"; }; diff --git a/frontend/src/framework/EnsembleFingerprintStore.ts b/frontend/src/framework/EnsembleFingerprintStore.ts new file mode 100644 index 000000000..44ee95941 --- /dev/null +++ b/frontend/src/framework/EnsembleFingerprintStore.ts @@ -0,0 +1,23 @@ +class EnsembleFingerprintStoreImpl { + private _fingerprints: Map = new Map(); + + setAll(fingerprints: Map) { + this._fingerprints = fingerprints; + } + + update(fingerprints: Map) { + for (const [key, value] of fingerprints) { + this._fingerprints.set(key, value); + } + } + + getFingerprintFromEnsembleIdentString(ensembleIdentString: string): string | null { + return this._fingerprints.get(ensembleIdentString) ?? null; + } + + clear() { + this._fingerprints.clear(); + } +} + +export const EnsembleFingerprintStore = new EnsembleFingerprintStoreImpl(); diff --git a/frontend/src/framework/EnsembleTimestampsStore.ts b/frontend/src/framework/EnsembleTimestampsStore.ts deleted file mode 100644 index bbff5eff6..000000000 --- a/frontend/src/framework/EnsembleTimestampsStore.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { RegularEnsembleIdent } from "./RegularEnsembleIdent"; - -export type EnsembleTimestamps = { - caseUpdatedAtUtcMs: number; - dataUpdatedAtUtcMs: number; -}; - -class EnsembleTimestampsStoreImpl { - private _timestamps: Map = new Map(); - - setAll(timestamps: Map) { - this._timestamps = timestamps; - } - - update(timestamps: Map) { - for (const [key, value] of timestamps) { - this._timestamps.set(key, value); - } - } - - getLatestTimestamps(...idents: RegularEnsembleIdent[]): EnsembleTimestamps { - let dataUpdatedAt = 0; - let caseUpdatedAt = 0; - - for (const ident of idents) { - const ts = this._timestamps.get(ident.toString()); - if (!ts) continue; - - if (ts.dataUpdatedAtUtcMs && ts.dataUpdatedAtUtcMs > dataUpdatedAt) { - dataUpdatedAt = ts.dataUpdatedAtUtcMs; - } - if (ts.caseUpdatedAtUtcMs && ts.caseUpdatedAtUtcMs > caseUpdatedAt) { - caseUpdatedAt = ts.caseUpdatedAtUtcMs; - } - } - - return { dataUpdatedAtUtcMs: dataUpdatedAt, caseUpdatedAtUtcMs: caseUpdatedAt }; - } - - clear() { - this._timestamps.clear(); - } -} - -export const EnsembleTimestampsStore = new EnsembleTimestampsStoreImpl(); diff --git a/frontend/src/framework/internal/EnsembleSetLoader.ts b/frontend/src/framework/internal/EnsembleSetLoader.ts index 3b5c288f6..cb8939e34 100644 --- a/frontend/src/framework/internal/EnsembleSetLoader.ts +++ b/frontend/src/framework/internal/EnsembleSetLoader.ts @@ -1,9 +1,10 @@ import type { QueryClient } from "@tanstack/react-query"; -import type { EnsembleDetails_api, EnsembleParameter_api, EnsembleSensitivity_api, EnsembleTimestamps_api } from "@api"; +import type { EnsembleDetails_api, EnsembleParameter_api, EnsembleSensitivity_api, } from "@api"; import { SensitivityType_api, getEnsembleDetailsOptions, getParametersOptions, getSensitivitiesOptions } from "@api"; import { DeltaEnsemble } from "@framework/DeltaEnsemble"; -import { EnsembleTimestampsStore, type EnsembleTimestamps } from "@framework/EnsembleTimestampsStore"; +import { EnsembleFingerprintStore } from "@framework/EnsembleFingerprintStore"; +import { calcFnv1aHash } from "@lib/utils/hashUtils"; import type { ContinuousParameter, DiscreteParameter, Parameter } from "../EnsembleParameters"; import { ParameterType } from "../EnsembleParameters"; @@ -14,7 +15,7 @@ import { RegularEnsemble } from "../RegularEnsemble"; import type { RegularEnsembleIdent } from "../RegularEnsembleIdent"; import { tanstackDebugTimeOverride } from "./utils/debug"; -import { fetchLatestEnsembleTimestamps } from "./utils/fetchEnsembleTimestamps"; +import { fetchLatestEnsembleFingerprints } from "./utils/fetchEnsembleFingerprints"; type EnsembleApiData = { ensembleDetails: EnsembleDetails_api; @@ -29,7 +30,6 @@ export type UserEnsembleSetting = { ensembleIdent: RegularEnsembleIdent; customName: string | null; color: string; - timestamps?: EnsembleTimestamps_api; }; export type UserDeltaEnsembleSetting = { @@ -45,34 +45,47 @@ export async function loadMetadataFromBackendAndCreateEnsembleSet( userDeltaEnsembleSettings: UserDeltaEnsembleSetting[], ): Promise { // Get ensemble idents to load - const ensembleTimestampMap = new Map(); + const ensembleFingerprintsMap = new Map(); const ensembleIdentsToLoad: RegularEnsembleIdent[] = []; + const uniqueIdentSet = new Set(); for (const ensembleSetting of userEnsembleSettings) { ensembleIdentsToLoad.push(ensembleSetting.ensembleIdent); + uniqueIdentSet.add(ensembleSetting.ensembleIdent.toString()); } for (const deltaEnsembleSetting of userDeltaEnsembleSettings) { - if (!ensembleIdentsToLoad.includes(deltaEnsembleSetting.comparisonEnsembleIdent)) { + const comparisonIdentString = deltaEnsembleSetting.comparisonEnsembleIdent.toString(); + if (!uniqueIdentSet.has(comparisonIdentString)) { ensembleIdentsToLoad.push(deltaEnsembleSetting.comparisonEnsembleIdent); + uniqueIdentSet.add(comparisonIdentString); } - if (!ensembleIdentsToLoad.includes(deltaEnsembleSetting.referenceEnsembleIdent)) { + const referenceIdentString = deltaEnsembleSetting.referenceEnsembleIdent.toString(); + if (!uniqueIdentSet.has(referenceIdentString)) { ensembleIdentsToLoad.push(deltaEnsembleSetting.referenceEnsembleIdent); + uniqueIdentSet.add(referenceIdentString); } } - // Loading timestamps here in order to make use of caching in the browser - const timestamps = await fetchLatestEnsembleTimestamps(queryClient, ensembleIdentsToLoad); - for (const item of timestamps) { - ensembleTimestampMap.set(item.ensembleIdent.toString(), item.timestamps); + // Loading fingerprints here in order to make use of caching in the browser + const fingerprints = await fetchLatestEnsembleFingerprints(queryClient, ensembleIdentsToLoad); + for (const item of fingerprints) { + if (!item.fingerprint) { + console.warn( + "No fingerprint found for ensemble, will not use cache-busting:", + item.ensembleIdent.toString(), + ); + continue; + } + ensembleFingerprintsMap.set(item.ensembleIdent.toString(), item.fingerprint); } - EnsembleTimestampsStore.setAll(ensembleTimestampMap); + EnsembleFingerprintStore.setAll(ensembleFingerprintsMap); // Fetch from back-end const ensembleApiDataMap = await loadEnsembleApiDataMapFromBackend( queryClient, ensembleIdentsToLoad, - ensembleTimestampMap, + ensembleFingerprintsMap, ); // Create regular ensembles @@ -185,7 +198,7 @@ export async function loadMetadataFromBackendAndCreateEnsembleSet( async function loadEnsembleApiDataMapFromBackend( queryClient: QueryClient, ensembleIdents: RegularEnsembleIdent[], - ensembleTimestampMap: Map, + ensembleFingerprintsMap: Map, ): Promise { console.debug("loadEnsembleIdentStringToApiDataMapFromBackend", ensembleIdents); const STALE_TIME = tanstackDebugTimeOverride(5 * 60 * 1000); @@ -198,12 +211,13 @@ async function loadEnsembleApiDataMapFromBackend( for (const ensembleIdent of ensembleIdents) { const caseUuid = ensembleIdent.getCaseUuid(); const ensembleName = ensembleIdent.getEnsembleName(); - const timestamps = ensembleTimestampMap.get(ensembleIdent.toString()); + const fingerprint = ensembleFingerprintsMap.get(ensembleIdent.toString()); + + const fingerprintHash = fingerprint ? calcFnv1aHash(fingerprint) : undefined; const ensembleDetailsPromise = queryClient.fetchQuery({ ...getEnsembleDetailsOptions({ - // ! We've assumed that these data are only affected by the case timestamp - query: { t: timestamps?.caseUpdatedAtUtcMs ?? Date.now() }, + query: { zCacheBust: fingerprintHash }, path: { case_uuid: caseUuid, ensemble_name: ensembleName, @@ -217,10 +231,9 @@ async function loadEnsembleApiDataMapFromBackend( const parametersPromise = queryClient.fetchQuery({ ...getParametersOptions({ query: { - // ? These are only affected by the "data" timestamp, right? - t: timestamps?.dataUpdatedAtUtcMs ?? Date.now(), case_uuid: caseUuid, ensemble_name: ensembleName, + zCacheBust: fingerprintHash, }, }), gcTime: CACHE_TIME, @@ -231,10 +244,9 @@ async function loadEnsembleApiDataMapFromBackend( const sensitivitiesPromise = queryClient.fetchQuery({ ...getSensitivitiesOptions({ query: { - // ! We've assumed that these data are only affected by the case timestamp - t: timestamps?.dataUpdatedAtUtcMs ?? Date.now(), case_uuid: caseUuid, ensemble_name: ensembleName, + zCacheBust: fingerprintHash, }, }), gcTime: CACHE_TIME, diff --git a/frontend/src/framework/internal/EnsembleUpdateMonitor.ts b/frontend/src/framework/internal/EnsembleUpdateMonitor.ts index 7280598cb..a38ea8a6e 100644 --- a/frontend/src/framework/internal/EnsembleUpdateMonitor.ts +++ b/frontend/src/framework/internal/EnsembleUpdateMonitor.ts @@ -1,15 +1,16 @@ import type { QueryClient } from "@tanstack/query-core"; -import { EnsembleTimestampsStore, type EnsembleTimestamps } from "@framework/EnsembleTimestampsStore"; +import { EnsembleFingerprintStore } from "@framework/EnsembleFingerprintStore"; import { globalLog } from "@framework/Log"; import { RegularEnsembleIdent } from "@framework/RegularEnsembleIdent"; import type { Workbench } from "@framework/Workbench"; -import { fetchLatestEnsembleTimestamps } from "./utils/fetchEnsembleTimestamps"; +import { fetchLatestEnsembleFingerprints } from "./utils/fetchEnsembleFingerprints"; const logger = globalLog.registerLogger("EnsembleUpdateMonitor"); -const ENSEMBLE_POLLING_INTERVAL_MS = 60000; // 60 seconds +// Polling interval for ensemble fingerprints is 5 minutes +const ENSEMBLE_POLLING_INTERVAL_MS = 5 * 60 * 1000; export class EnsembleUpdateMonitor { private _queryClient: QueryClient; @@ -124,29 +125,32 @@ export class EnsembleUpdateMonitor { return; } - // Fetch the latest timestamps for all ensembles - const latestTimestamps = await fetchLatestEnsembleTimestamps( + // Fetch the latest fingerprints for all ensembles + const latestFingerprints = await fetchLatestEnsembleFingerprints( this._queryClient, Array.from(allRegularEnsembleIdents).map((id) => RegularEnsembleIdent.fromString(id)), ); - if (latestTimestamps.length !== allRegularEnsembleIdents.size) { + if (latestFingerprints.length !== allRegularEnsembleIdents.size) { console.warn( - `Expected ${allRegularEnsembleIdents.size} timestamps, received ${latestTimestamps.length}.`, + `Expected ${allRegularEnsembleIdents.size} fingerprints, received ${latestFingerprints.length}.`, ); } - const latestTimestampsMap = new Map(); + const latestFingerprintsMap = new Map(); - // Update the ensemble timestamps map - for (const item of latestTimestamps) { - latestTimestampsMap.set(item.ensembleIdent.toString(), item.timestamps); + // Update the ensemble fingerprints map + for (const item of latestFingerprints) { + if (item.fingerprint === null) { + continue; + } + latestFingerprintsMap.set(item.ensembleIdent.toString(), item.fingerprint); } - // Update the EnsembleTimestampsStore with the latest timestamps - EnsembleTimestampsStore.setAll(latestTimestampsMap); + // Update the EnsembleFingerprintsStore with the latest fingerprints + EnsembleFingerprintStore.setAll(latestFingerprintsMap); - logger.console?.log(`checkForEnsembleUpdate - fetched and updated timestamps for ensembles.`); + logger.console?.log(`checkForEnsembleUpdate - fetched and updated fingerprints for ensembles.`); } catch (error) { console.error(`Error during ensemble polling:`, error); } finally { diff --git a/frontend/src/framework/internal/WorkbenchSession/PrivateWorkbenchSession.ts b/frontend/src/framework/internal/WorkbenchSession/PrivateWorkbenchSession.ts index 5dfc26a31..cef7d4923 100644 --- a/frontend/src/framework/internal/WorkbenchSession/PrivateWorkbenchSession.ts +++ b/frontend/src/framework/internal/WorkbenchSession/PrivateWorkbenchSession.ts @@ -1,8 +1,8 @@ import type { QueryClient } from "@tanstack/query-core"; import type { AtomStoreMaster } from "@framework/AtomStoreMaster"; +import { EnsembleFingerprintStore } from "@framework/EnsembleFingerprintStore"; import { EnsembleSet } from "@framework/EnsembleSet"; -import { EnsembleTimestampsStore } from "@framework/EnsembleTimestampsStore"; import { EnsembleSetAtom, RealizationFilterSetAtom } from "@framework/GlobalAtoms"; import { Dashboard, type SerializedDashboard } from "@framework/internal/Dashboard"; import { RealizationFilterSet } from "@framework/RealizationFilterSet"; @@ -240,7 +240,7 @@ export class PrivateWorkbenchSession implements WorkbenchSession { this._dashboards = []; this._activeDashboardId = null; this._ensembleSet = new EnsembleSet([]); - EnsembleTimestampsStore.clear(); + EnsembleFingerprintStore.clear(); } beforeDestroy(): void { diff --git a/frontend/src/framework/internal/components/SelectEnsemblesDialog/private-components/CaseExplorer/CaseExplorer.tsx b/frontend/src/framework/internal/components/SelectEnsemblesDialog/private-components/CaseExplorer/CaseExplorer.tsx index 9aa405119..ac5a39004 100644 --- a/frontend/src/framework/internal/components/SelectEnsemblesDialog/private-components/CaseExplorer/CaseExplorer.tsx +++ b/frontend/src/framework/internal/components/SelectEnsemblesDialog/private-components/CaseExplorer/CaseExplorer.tsx @@ -19,6 +19,7 @@ import { Tooltip } from "@lib/components/Tooltip"; import { useValidArrayState } from "@lib/hooks/useValidArrayState"; import { useValidState } from "@lib/hooks/useValidState"; + import { makeCaseRowData, makeCaseTableColumns, diff --git a/frontend/src/framework/internal/utils/fetchEnsembleTimestamps.ts b/frontend/src/framework/internal/utils/fetchEnsembleFingerprints.ts similarity index 54% rename from frontend/src/framework/internal/utils/fetchEnsembleTimestamps.ts rename to frontend/src/framework/internal/utils/fetchEnsembleFingerprints.ts index a9bb95052..f4db75e3c 100644 --- a/frontend/src/framework/internal/utils/fetchEnsembleTimestamps.ts +++ b/frontend/src/framework/internal/utils/fetchEnsembleFingerprints.ts @@ -1,35 +1,36 @@ import type { QueryClient } from "@tanstack/react-query"; -import { postGetTimestampsForEnsemblesOptions, type EnsembleIdent_api, type EnsembleTimestamps_api } from "@api"; +import { type EnsembleIdent_api } from "@api"; +import { postRefreshFingerprintsForEnsemblesOptions } from "@api"; import type { RegularEnsembleIdent } from "@framework/RegularEnsembleIdent"; -export type EnsembleTimestampsItem = { +export type EnsembleFingerprintItem = { ensembleIdent: RegularEnsembleIdent; - timestamps: EnsembleTimestamps_api; + fingerprint: string | null; }; -export async function fetchLatestEnsembleTimestamps( +export async function fetchLatestEnsembleFingerprints( queryClient: QueryClient, ensembleIdents: RegularEnsembleIdent[], -): Promise { +): Promise { const idents = ensembleIdents.map((ens) => ({ caseUuid: ens.getCaseUuid(), ensembleName: ens.getEnsembleName(), })); try { - const timestamps = await queryClient.fetchQuery({ - ...postGetTimestampsForEnsemblesOptions({ body: idents }), + const fingerprints = await queryClient.fetchQuery({ + ...postRefreshFingerprintsForEnsemblesOptions({ body: idents }), staleTime: 0, gcTime: 0, }); return ensembleIdents.map((ident, i) => ({ ensembleIdent: ident, - timestamps: timestamps[i], + fingerprint: fingerprints[i], })); } catch (error) { - console.error("Error fetching ensemble timestamps:", error); + console.error("Error fetching latest ensemble fingerprints:", error); return []; } } diff --git a/frontend/src/framework/utils/queryUtils.ts b/frontend/src/framework/utils/queryUtils.ts index 845cba658..d4326f989 100644 --- a/frontend/src/framework/utils/queryUtils.ts +++ b/frontend/src/framework/utils/queryUtils.ts @@ -1,15 +1,34 @@ -import { EnsembleTimestampsStore } from "@framework/EnsembleTimestampsStore"; +import { EnsembleFingerprintStore } from "@framework/EnsembleFingerprintStore"; import type { RegularEnsembleIdent } from "@framework/RegularEnsembleIdent"; +import { calcFnv1aHash } from "@lib/utils/hashUtils"; -export function makeTimestampQueryParam(...ensembleIdents: RegularEnsembleIdent[]): { t?: number } { +export function makeCacheBustingQueryParam(...ensembleIdents: (RegularEnsembleIdent | null)[]): { + zCacheBust?: string; +} { // If no ensembles are provided, return an empty object if (ensembleIdents.length === 0) { - return {}; + throw new Error("makeCacheBustingQueryParam requires at least one ensemble ident"); } - // Get the ensemble timestamps from the EnsembleTimestampsStore - const ensembleTimestamps = EnsembleTimestampsStore.getLatestTimestamps(...ensembleIdents); + // Get the ensemble fingerprints from the EnsembleFingerprintsStore + const ensembleFingerprints: string[] = []; + for (const ident of ensembleIdents) { + if (!ident) { + return { + zCacheBust: "INVALID_CACHE_BUSTING_PARAM_NO_ENSEMBLE", + }; + } + const fingerprint = EnsembleFingerprintStore.getFingerprintFromEnsembleIdentString(ident.toString()); + if (!fingerprint) { + throw new Error(`Missing fingerprint for ensemble ident: ${ident}`); + } + ensembleFingerprints.push(fingerprint); + } + + // Make concatenated 64bit hash of all fingerprints + ensembleFingerprints.sort(); + const hash = calcFnv1aHash(ensembleFingerprints.join("")); - // Return the maximum timestamp as a query parameter - return { t: Math.max(ensembleTimestamps.dataUpdatedAtUtcMs, ensembleTimestamps.caseUpdatedAtUtcMs) }; + // Return the fingerprint query param + return { zCacheBust: hash }; } diff --git a/frontend/src/lib/utils/hashUtils.ts b/frontend/src/lib/utils/hashUtils.ts new file mode 100644 index 000000000..13a99a80b --- /dev/null +++ b/frontend/src/lib/utils/hashUtils.ts @@ -0,0 +1,27 @@ +const FNV64_OFFSET_BASIS = 0xcbf29ce484222325n; // 14695981039346656037 +const FNV64_PRIME_MULTIPLIER = 0x00000100000001b3n; // 1099511628211 +const U64_MASK = 0xffffffffffffffffn; + +/** + * Hash a string to a 64-bit hex using a 64-bit FNV-1a hash. + * @param str The string to hash. + * @returns The 64-bit hexadecimal hash of the string. + */ +export function calcFnv1aHash(str: string): string { + // Encode string as UTF-8 + const bytes = new TextEncoder().encode(str); + + let hash = FNV64_OFFSET_BASIS; + + // Loop over bytes + for (let i = 0; i < bytes.length; i++) { + // XOR the hash with the current byte + hash ^= BigInt(bytes[i]); + // Multiply the hash by the FNV prime and wrap to 64 bits + hash = (hash * FNV64_PRIME_MULTIPLIER) & U64_MASK; + } + + // Format as zero-padded 16-hex-digit string + const hex = hash.toString(16).padStart(16, "0"); + return hex; +} diff --git a/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/ObservedSurfaceProvider.ts b/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/ObservedSurfaceProvider.ts index 3a8e4fbe3..91724aefc 100644 --- a/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/ObservedSurfaceProvider.ts +++ b/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/ObservedSurfaceProvider.ts @@ -2,6 +2,7 @@ import { isEqual } from "lodash"; import type { SurfaceDataPng_api } from "@api"; import { SurfaceTimeType_api, getObservedSurfacesMetadataOptions, getSurfaceDataOptions } from "@api"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import type { CustomDataProviderImplementation, DataProviderInformationAccessors, @@ -94,6 +95,7 @@ export class ObservedSurfaceProvider ...getObservedSurfacesMetadataOptions({ query: { case_uuid: ensembleIdent.getCaseUuid(), + ...makeCacheBustingQueryParam(ensembleIdent), }, signal: abortSignal, }), diff --git a/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationGridProvider.ts b/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationGridProvider.ts index f7ba37764..5e7199120 100644 --- a/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationGridProvider.ts +++ b/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationGridProvider.ts @@ -1,6 +1,7 @@ import { isEqual } from "lodash"; import { getGridModelsInfoOptions, getGridParameterOptions, getGridSurfaceOptions } from "@api"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import type { AreSettingsValidArgs, CustomDataProviderImplementation, @@ -112,6 +113,7 @@ export class RealizationGridProvider j_max: jMax - 1, k_min: kMin, k_max: kMax, + ...makeCacheBustingQueryParam(ensembleIdent ?? null), }, }); @@ -127,6 +129,7 @@ export class RealizationGridProvider j_max: jMax - 1, k_min: kMin, k_max: kMax, + ...makeCacheBustingQueryParam(ensembleIdent ?? null), }, }); @@ -196,6 +199,7 @@ export class RealizationGridProvider case_uuid: ensembleIdent.getCaseUuid(), ensemble_name: ensembleIdent.getEnsembleName(), realization_num: realization, + ...makeCacheBustingQueryParam(ensembleIdent), }, signal: abortSignal, }), diff --git a/frontend/src/modules/3DViewer/DataProviderFramework/customDataProviderImplementations/RealizationGridProvider.ts b/frontend/src/modules/3DViewer/DataProviderFramework/customDataProviderImplementations/RealizationGridProvider.ts index e9a8ea328..747608e4e 100644 --- a/frontend/src/modules/3DViewer/DataProviderFramework/customDataProviderImplementations/RealizationGridProvider.ts +++ b/frontend/src/modules/3DViewer/DataProviderFramework/customDataProviderImplementations/RealizationGridProvider.ts @@ -1,4 +1,5 @@ import { getGridModelsInfoOptions, getGridParameterOptions, getGridSurfaceOptions } from "@api"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { NO_UPDATE } from "@modules/_shared/DataProviderFramework/delegates/_utils/Dependency"; import type { AreSettingsValidArgs, @@ -110,6 +111,7 @@ export class RealizationGridProvider j_max: range[1][1], k_min: range[2][0], k_max: range[2][1], + ...makeCacheBustingQueryParam(ensembleIdent ?? null), }, }); @@ -125,6 +127,7 @@ export class RealizationGridProvider j_max: range[1][1], k_min: range[2][0], k_max: range[2][1], + ...makeCacheBustingQueryParam(ensembleIdent ?? null), }, }); @@ -193,6 +196,7 @@ export class RealizationGridProvider case_uuid: ensembleIdent.getCaseUuid(), ensemble_name: ensembleIdent.getEnsembleName(), realization_num: realization, + ...makeCacheBustingQueryParam(ensembleIdent), }, signal: abortSignal, }), diff --git a/frontend/src/modules/3DViewer/DataProviderFramework/customDataProviderImplementations/RealizationSeismicSlicesProvider.ts b/frontend/src/modules/3DViewer/DataProviderFramework/customDataProviderImplementations/RealizationSeismicSlicesProvider.ts index 2b9443e17..c97eefab4 100644 --- a/frontend/src/modules/3DViewer/DataProviderFramework/customDataProviderImplementations/RealizationSeismicSlicesProvider.ts +++ b/frontend/src/modules/3DViewer/DataProviderFramework/customDataProviderImplementations/RealizationSeismicSlicesProvider.ts @@ -2,6 +2,7 @@ import { isEqual } from "lodash"; import { type SeismicCubeMeta_api, getSeismicCubeMetaListOptions, getSeismicSlicesOptions } from "@api"; import { defaultContinuousDivergingColorPalettes } from "@framework/utils/colorPalettes"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { ColorScale, ColorScaleGradientType, ColorScaleType } from "@lib/utils/ColorScale"; import { NO_UPDATE } from "@modules/_shared/DataProviderFramework/delegates/_utils/Dependency"; import type { @@ -155,6 +156,7 @@ export class RealizationSeismicSlicesProvider inline_number: slices?.[0] ?? 0, crossline_number: slices?.[1] ?? 0, depth_slice_number: slices?.[2] ?? 0, + ...makeCacheBustingQueryParam(ensembleIdent ?? null), }, }); @@ -210,6 +212,7 @@ export class RealizationSeismicSlicesProvider query: { case_uuid: ensembleIdent.getCaseUuid(), ensemble_name: ensembleIdent.getEnsembleName(), + ...makeCacheBustingQueryParam(ensembleIdent), }, signal: abortSignal, }), diff --git a/frontend/src/modules/FlowNetwork/settings/atoms/queryAtoms.ts b/frontend/src/modules/FlowNetwork/settings/atoms/queryAtoms.ts index 4fa5e0ae2..2b8af10cb 100644 --- a/frontend/src/modules/FlowNetwork/settings/atoms/queryAtoms.ts +++ b/frontend/src/modules/FlowNetwork/settings/atoms/queryAtoms.ts @@ -1,7 +1,7 @@ import { atomWithQuery } from "jotai-tanstack-query"; import { getRealizationFlowNetworkOptions } from "@api"; - +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { selectedNodeTypesAtom, selectedResamplingFrequencyAtom } from "./baseAtoms"; import { selectedEnsembleIdentAtom, selectedRealizationNumberAtom } from "./derivedAtoms"; @@ -20,6 +20,7 @@ export const realizationFlowNetworkQueryAtom = atomWithQuery((get) => { realization: selectedRealizationNumber ?? 0, resampling_frequency: selectedResamplingFrequency, node_type_set: Array.from(selectedNodeTypes), + ...makeCacheBustingQueryParam(selectedEnsembleIdent), }, }), enabled: Boolean( diff --git a/frontend/src/modules/InplaceVolumesPlot/settings/atoms/queryAtoms.ts b/frontend/src/modules/InplaceVolumesPlot/settings/atoms/queryAtoms.ts index 613fd018b..f2a95daa3 100644 --- a/frontend/src/modules/InplaceVolumesPlot/settings/atoms/queryAtoms.ts +++ b/frontend/src/modules/InplaceVolumesPlot/settings/atoms/queryAtoms.ts @@ -4,6 +4,7 @@ import type { InplaceVolumesTableDefinition_api } from "@api"; import { getTableDefinitionsOptions } from "@api"; import type { RegularEnsembleIdent } from "@framework/RegularEnsembleIdent"; import { atomWithQueries } from "@framework/utils/atomUtils"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { selectedEnsembleIdentsAtom } from "./derivedAtoms"; @@ -24,6 +25,7 @@ export const tableDefinitionsQueryAtom = atomWithQueries((get) => { query: { case_uuid: ensembleIdent.getCaseUuid(), ensemble_name: ensembleIdent.getEnsembleName(), + ...makeCacheBustingQueryParam(ensembleIdent), }, }), }); diff --git a/frontend/src/modules/InplaceVolumesTable/settings/atoms/queryAtoms.ts b/frontend/src/modules/InplaceVolumesTable/settings/atoms/queryAtoms.ts index 613fd018b..f2a95daa3 100644 --- a/frontend/src/modules/InplaceVolumesTable/settings/atoms/queryAtoms.ts +++ b/frontend/src/modules/InplaceVolumesTable/settings/atoms/queryAtoms.ts @@ -4,6 +4,7 @@ import type { InplaceVolumesTableDefinition_api } from "@api"; import { getTableDefinitionsOptions } from "@api"; import type { RegularEnsembleIdent } from "@framework/RegularEnsembleIdent"; import { atomWithQueries } from "@framework/utils/atomUtils"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { selectedEnsembleIdentsAtom } from "./derivedAtoms"; @@ -24,6 +25,7 @@ export const tableDefinitionsQueryAtom = atomWithQueries((get) => { query: { case_uuid: ensembleIdent.getCaseUuid(), ensemble_name: ensembleIdent.getEnsembleName(), + ...makeCacheBustingQueryParam(ensembleIdent), }, }), }); diff --git a/frontend/src/modules/Intersection/DataProviderFramework/customDataProviderImplementations/RealizationSurfacesProvider.ts b/frontend/src/modules/Intersection/DataProviderFramework/customDataProviderImplementations/RealizationSurfacesProvider.ts index 8fe3908aa..01ea4efbc 100644 --- a/frontend/src/modules/Intersection/DataProviderFramework/customDataProviderImplementations/RealizationSurfacesProvider.ts +++ b/frontend/src/modules/Intersection/DataProviderFramework/customDataProviderImplementations/RealizationSurfacesProvider.ts @@ -7,6 +7,7 @@ import { postGetSurfaceIntersectionOptions, } from "@api"; import { IntersectionType } from "@framework/types/intersection"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { assertNonNull } from "@lib/utils/assertNonNull"; import { createIntersectionPolylineWithSectionLengthsForField, @@ -163,6 +164,7 @@ export class RealizationSurfacesProvider query: { case_uuid: ensembleIdent.getCaseUuid(), ensemble_name: ensembleIdent.getEnsembleName(), + ...makeCacheBustingQueryParam(ensembleIdent), }, signal: abortSignal, }), @@ -280,6 +282,7 @@ export class RealizationSurfacesProvider realization_num: realization, name: surfaceName, attribute: attribute, + ...makeCacheBustingQueryParam(ensembleIdent), }, body: { cumulative_length_polyline: { diff --git a/frontend/src/modules/Intersection/DataProviderFramework/customDataProviderImplementations/SurfacesPerRealizationValuesProvider.ts b/frontend/src/modules/Intersection/DataProviderFramework/customDataProviderImplementations/SurfacesPerRealizationValuesProvider.ts index e549057a6..232b3751c 100644 --- a/frontend/src/modules/Intersection/DataProviderFramework/customDataProviderImplementations/SurfacesPerRealizationValuesProvider.ts +++ b/frontend/src/modules/Intersection/DataProviderFramework/customDataProviderImplementations/SurfacesPerRealizationValuesProvider.ts @@ -7,6 +7,7 @@ import { postGetSampleSurfaceInPointsOptions, } from "@api"; import { IntersectionType } from "@framework/types/intersection"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { assertNonNull } from "@lib/utils/assertNonNull"; import { createIntersectionPolylineWithSectionLengthsForField, @@ -167,6 +168,7 @@ export class SurfacesPerRealizationValuesProvider query: { case_uuid: ensembleIdent.getCaseUuid(), ensemble_name: ensembleIdent.getEnsembleName(), + ...makeCacheBustingQueryParam(ensembleIdent), }, signal: abortSignal, }), @@ -289,6 +291,7 @@ export class SurfacesPerRealizationValuesProvider surface_name: surfaceName, surface_attribute: attribute, realization_nums: realizations ?? [], + ...makeCacheBustingQueryParam(ensembleIdent), }, body: { sample_points: { diff --git a/frontend/src/modules/Pvt/settings/atoms/queryAtoms.ts b/frontend/src/modules/Pvt/settings/atoms/queryAtoms.ts index c7748bc5b..f1e3ca202 100644 --- a/frontend/src/modules/Pvt/settings/atoms/queryAtoms.ts +++ b/frontend/src/modules/Pvt/settings/atoms/queryAtoms.ts @@ -3,12 +3,12 @@ import type { UseQueryResult } from "@tanstack/react-query"; import { type PvtData_api, getTableDataOptions } from "@api"; import type { RegularEnsembleIdent } from "@framework/RegularEnsembleIdent"; import { atomWithQueries } from "@framework/utils/atomUtils"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import type { CombinedPvtDataResult } from "../../typesAndEnums"; import { selectedEnsembleIdentsAtom, selectedRealizationsAtom } from "./derivedAtoms"; - export const pvtDataQueriesAtom = atomWithQueries((get) => { const selectedEnsembleIdents = get(selectedEnsembleIdentsAtom); const selectedRealizations = get(selectedRealizationsAtom); @@ -28,6 +28,7 @@ export const pvtDataQueriesAtom = atomWithQueries((get) => { case_uuid: el.ensembleIdent.getCaseUuid(), ensemble_name: el.ensembleIdent.getEnsembleName(), realization: el.realization, + ...makeCacheBustingQueryParam(el.ensembleIdent), }, }), enabled: Boolean(el.ensembleIdent && el.realization !== null), diff --git a/frontend/src/modules/Rft/settings/atoms/queryAtoms.ts b/frontend/src/modules/Rft/settings/atoms/queryAtoms.ts index 7139d5b1c..d672f394a 100644 --- a/frontend/src/modules/Rft/settings/atoms/queryAtoms.ts +++ b/frontend/src/modules/Rft/settings/atoms/queryAtoms.ts @@ -1,7 +1,7 @@ import { atomWithQuery } from "jotai-tanstack-query"; import { getRealizationDataOptions, getTableDefinitionOptions } from "@api"; - +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { selectedEnsembleIdentAtom, @@ -18,6 +18,7 @@ export const rftTableDefinitionAtom = atomWithQuery((get) => { query: { case_uuid: selectedEnsembleIdent?.getCaseUuid() ?? "", ensemble_name: selectedEnsembleIdent?.getEnsembleName() ?? "", + ...makeCacheBustingQueryParam(selectedEnsembleIdent), }, }), enabled: Boolean(selectedEnsembleIdent?.getCaseUuid() && selectedEnsembleIdent?.getEnsembleName()), @@ -39,6 +40,7 @@ export const rftRealizationDataQueryAtom = atomWithQuery((get) => { well_name: selectedWellName ?? "", response_name: selectedResponseName ?? "", timestamps_utc_ms: selectedRftTimestampsUtcMs ? [selectedRftTimestampsUtcMs] : null, + ...makeCacheBustingQueryParam(selectedEnsembleIdent), }, }), enabled: Boolean( diff --git a/frontend/src/modules/SimulationTimeSeries/settings/atoms/queryAtoms.ts b/frontend/src/modules/SimulationTimeSeries/settings/atoms/queryAtoms.ts index 62245deed..3d59fbd97 100644 --- a/frontend/src/modules/SimulationTimeSeries/settings/atoms/queryAtoms.ts +++ b/frontend/src/modules/SimulationTimeSeries/settings/atoms/queryAtoms.ts @@ -4,7 +4,7 @@ import { EnsembleSetAtom } from "@framework/GlobalAtoms"; import { RegularEnsembleIdent } from "@framework/RegularEnsembleIdent"; import { atomWithQueries } from "@framework/utils/atomUtils"; import { isEnsembleIdentOfType } from "@framework/utils/ensembleIdentUtils"; -import { makeTimestampQueryParam } from "@framework/utils/queryUtils"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { selectedEnsembleIdentsAtom } from "./derivedAtoms"; @@ -20,10 +20,10 @@ export const vectorListQueriesAtom = atomWithQueries((get) => { queryFn: async () => { const { data } = await getVectorList({ query: { - ...makeTimestampQueryParam(ensembleIdent), case_uuid: ensembleIdent.getCaseUuid(), ensemble_name: ensembleIdent.getEnsembleName(), include_derived_vectors: true, + ...makeCacheBustingQueryParam(ensembleIdent), }, throwOnError: true, }); @@ -55,6 +55,10 @@ export const vectorListQueriesAtom = atomWithQueries((get) => { reference_case_uuid: referenceEnsembleIdent.getCaseUuid(), reference_ensemble_name: referenceEnsembleIdent.getEnsembleName(), include_derived_vectors: true, + ...makeCacheBustingQueryParam( + ensembleIdent.getComparisonEnsembleIdent(), + ensembleIdent.getReferenceEnsembleIdent(), + ), }, throwOnError: true, }); diff --git a/frontend/src/modules/SimulationTimeSeries/view/atoms/queryAtoms.ts b/frontend/src/modules/SimulationTimeSeries/view/atoms/queryAtoms.ts index 66936a1b9..d3b7dd695 100644 --- a/frontend/src/modules/SimulationTimeSeries/view/atoms/queryAtoms.ts +++ b/frontend/src/modules/SimulationTimeSeries/view/atoms/queryAtoms.ts @@ -15,6 +15,7 @@ import { ValidEnsembleRealizationsFunctionAtom } from "@framework/GlobalAtoms"; import { RegularEnsembleIdent } from "@framework/RegularEnsembleIdent"; import { atomWithQueries } from "@framework/utils/atomUtils"; import { isEnsembleIdentOfType } from "@framework/utils/ensembleIdentUtils"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { encodeAsUintListStr } from "@lib/utils/queryStringUtils"; import { showHistoricalAtom } from "@modules/SimulationTimeSeries/settings/atoms/baseAtoms"; import type { @@ -67,6 +68,7 @@ export const vectorDataQueriesAtom = atomWithQueries((get) => { vector_name: vectorSpecification.vectorName, resampling_frequency: resampleFrequency, realizations_encoded_as_uint_list_str: realizationsEncodedAsUintListStr, + ...makeCacheBustingQueryParam(vectorSpecification.ensembleIdent), }, throwOnError: true, }); @@ -116,6 +118,10 @@ export const vectorDataQueriesAtom = atomWithQueries((get) => { vector_name: vectorSpecification.vectorName, resampling_frequency: resampleFrequency ?? Frequency_api.YEARLY, realizations_encoded_as_uint_list_str: realizationsEncodedAsUintListStr, + ...makeCacheBustingQueryParam( + vectorSpecification.ensembleIdent.getComparisonEnsembleIdent(), + vectorSpecification.ensembleIdent.getReferenceEnsembleIdent(), + ), }, throwOnError: true, }); @@ -179,6 +185,7 @@ export const vectorStatisticsQueriesAtom = atomWithQueries((get) => { vector_name: vectorSpecification.vectorName, resampling_frequency: resampleFrequency ?? Frequency_api.MONTHLY, realizations_encoded_as_uint_list_str: realizationsEncodedAsUintListStr, + ...makeCacheBustingQueryParam(vectorSpecification.ensembleIdent), }, throwOnError: true, }); @@ -228,6 +235,10 @@ export const vectorStatisticsQueriesAtom = atomWithQueries((get) => { vector_name: vectorSpecification.vectorName, resampling_frequency: resampleFrequency ?? Frequency_api.MONTHLY, realizations_encoded_as_uint_list_str: realizationsEncodedAsUintListStr, + ...makeCacheBustingQueryParam( + vectorSpecification.ensembleIdent.getComparisonEnsembleIdent(), + vectorSpecification.ensembleIdent.getReferenceEnsembleIdent(), + ), }, throwOnError: true, }); @@ -290,6 +301,7 @@ export const regularEnsembleHistoricalVectorDataQueriesAtom = atomWithQueries((g ensemble_name: vectorSpecification.ensembleIdent.getEnsembleName(), non_historical_vector_name: vectorSpecification.vectorName, resampling_frequency: resampleFrequency ?? Frequency_api.MONTHLY, + ...makeCacheBustingQueryParam(vectorSpecification.ensembleIdent), }, throwOnError: true, }); diff --git a/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/atoms/queryAtoms.ts b/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/atoms/queryAtoms.ts index 377f5e1e3..833a6d617 100644 --- a/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/atoms/queryAtoms.ts +++ b/frontend/src/modules/SimulationTimeSeriesSensitivity/settings/atoms/queryAtoms.ts @@ -1,7 +1,7 @@ import { atomWithQuery } from "jotai-tanstack-query"; import { getVectorListOptions } from "@api"; - +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { selectedRegularEnsembleIdentAtom } from "./derivedAtoms"; @@ -13,6 +13,7 @@ export const vectorListQueryAtom = atomWithQuery((get) => { query: { case_uuid: selectedEnsembleIdent?.getCaseUuid() ?? "", ensemble_name: selectedEnsembleIdent?.getEnsembleName() ?? "", + ...makeCacheBustingQueryParam(selectedEnsembleIdent), }, }), enabled: !!(selectedEnsembleIdent?.getCaseUuid() && selectedEnsembleIdent?.getEnsembleName()), diff --git a/frontend/src/modules/SimulationTimeSeriesSensitivity/view/atoms/queryAtoms.ts b/frontend/src/modules/SimulationTimeSeriesSensitivity/view/atoms/queryAtoms.ts index cd2b349e1..9b6cb97bd 100644 --- a/frontend/src/modules/SimulationTimeSeriesSensitivity/view/atoms/queryAtoms.ts +++ b/frontend/src/modules/SimulationTimeSeriesSensitivity/view/atoms/queryAtoms.ts @@ -7,6 +7,7 @@ import { getStatisticalVectorDataPerSensitivityOptions, } from "@api"; import { ValidEnsembleRealizationsFunctionAtom } from "@framework/GlobalAtoms"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { encodeAsUintListStr } from "@lib/utils/queryStringUtils"; import { resamplingFrequencyAtom, @@ -15,7 +16,6 @@ import { vectorSpecificationAtom, } from "@modules/SimulationTimeSeriesSensitivity/view/atoms/baseAtoms"; - export const vectorDataQueryAtom = atomWithQuery((get) => { const vectorSpecification = get(vectorSpecificationAtom); const resampleFrequency = get(resamplingFrequencyAtom); @@ -33,6 +33,7 @@ export const vectorDataQueryAtom = atomWithQuery((get) => { vector_name: vectorSpecification?.vectorName ?? "", resampling_frequency: resampleFrequency, realizations_encoded_as_uint_list_str: realizationsEncodedAsUintListStr, + ...makeCacheBustingQueryParam(vectorSpecification?.ensembleIdent ?? null), }, }); return { ...queryOptions, enabled: !!vectorSpecification }; @@ -59,6 +60,7 @@ export const statisticalVectorSensitivityDataQueryAtom = atomWithQuery((get) => resampling_frequency: fallbackStatisticsResampleFrequency, statistic_functions: undefined, realizations_encoded_as_uint_list_str: realizationsEncodedAsUintListStr, + ...makeCacheBustingQueryParam(vectorSpecification?.ensembleIdent ?? null), }, }); return { ...queryOptions, enabled: !!(showStatistics && vectorSpecification) }; @@ -75,6 +77,7 @@ export const historicalVectorDataQueryAtom = atomWithQuery((get) => { ensemble_name: vectorSpecification?.ensembleIdent.getEnsembleName() ?? "", non_historical_vector_name: vectorSpecification?.vectorName ?? "", resampling_frequency: resampleFrequency, + ...makeCacheBustingQueryParam(vectorSpecification?.ensembleIdent ?? null), }, }); diff --git a/frontend/src/modules/SubsurfaceMap/settings/settings.tsx b/frontend/src/modules/SubsurfaceMap/settings/settings.tsx index 2db8905ae..6c72ff3e6 100644 --- a/frontend/src/modules/SubsurfaceMap/settings/settings.tsx +++ b/frontend/src/modules/SubsurfaceMap/settings/settings.tsx @@ -210,10 +210,7 @@ export function Settings({ settingsContext, workbenchSession, workbenchServices } // Polygon - const polygonsDirectoryQuery = usePolygonsDirectoryQuery( - computedEnsembleIdent?.getCaseUuid(), - computedEnsembleIdent?.getEnsembleName(), - ); + const polygonsDirectoryQuery = usePolygonsDirectoryQuery(computedEnsembleIdent); const polygonsDirectory = new PolygonsDirectory( polygonsDirectoryQuery.data diff --git a/frontend/src/modules/Vfp/settings/atoms/queryAtoms.ts b/frontend/src/modules/Vfp/settings/atoms/queryAtoms.ts index c5c688894..6b1b18224 100644 --- a/frontend/src/modules/Vfp/settings/atoms/queryAtoms.ts +++ b/frontend/src/modules/Vfp/settings/atoms/queryAtoms.ts @@ -1,7 +1,7 @@ import { atomWithQuery } from "jotai-tanstack-query"; import { getVfpTableNamesOptions, getVfpTableOptions } from "@api"; - +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { selectedEnsembleIdentAtom, selectedRealizationNumberAtom, selectedVfpTableNameAtom } from "./derivedAtoms"; @@ -17,6 +17,7 @@ export const vfpTableQueryAtom = atomWithQuery((get) => { ensemble_name: selectedEnsembleIdent?.getEnsembleName() ?? "", realization: selectedRealizationNumber ?? 0, vfp_table_name: selectedVfpTableName ?? "", + ...makeCacheBustingQueryParam(selectedEnsembleIdent), }, }), enabled: Boolean( @@ -39,6 +40,7 @@ export const vfpTableNamesQueryAtom = atomWithQuery((get) => { case_uuid: selectedEnsembleIdent?.getCaseUuid() ?? "", ensemble_name: selectedEnsembleIdent?.getEnsembleName() ?? "", realization: selectedRealizationNumber ?? 0, + ...makeCacheBustingQueryParam(selectedEnsembleIdent), }, }), enabled: Boolean( diff --git a/frontend/src/modules/WellCompletions/settings/atoms/queryAtoms.ts b/frontend/src/modules/WellCompletions/settings/atoms/queryAtoms.ts index 35f98cf53..407c57b59 100644 --- a/frontend/src/modules/WellCompletions/settings/atoms/queryAtoms.ts +++ b/frontend/src/modules/WellCompletions/settings/atoms/queryAtoms.ts @@ -1,6 +1,7 @@ import { atomWithQuery } from "jotai-tanstack-query"; import { getWellCompletionsDataOptions } from "@api"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { encodeAsUintListStr } from "@lib/utils/queryStringUtils"; import { RealizationSelection } from "@modules/WellCompletions/typesAndEnums"; @@ -35,6 +36,7 @@ export const wellCompletionsQueryAtom = atomWithQuery((get) => { case_uuid: caseUuid ?? "", ensemble_name: ensembleName ?? "", realizations_encoded_as_uint_list_str: realizationsEncodedAsUintListStr, + ...makeCacheBustingQueryParam(selectedEnsembleIdent), }, }), enabled: Boolean(caseUuid && ensembleName && hasValidRealizations), diff --git a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/IntersectionRealizationGridProvider.ts b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/IntersectionRealizationGridProvider.ts index be4f71a2b..b6935f8eb 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/IntersectionRealizationGridProvider.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/IntersectionRealizationGridProvider.ts @@ -2,6 +2,7 @@ import { isEqual } from "lodash"; import { getGridModelsInfoOptions, postGetPolylineIntersectionOptions } from "@api"; import { IntersectionType } from "@framework/types/intersection"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { assertNonNull } from "@lib/utils/assertNonNull"; import type { MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; @@ -186,6 +187,7 @@ export class IntersectionRealizationGridProvider case_uuid: ensembleIdent.getCaseUuid(), ensemble_name: ensembleIdent.getEnsembleName(), realization_num: realization, + ...makeCacheBustingQueryParam(ensembleIdent), }, signal: abortSignal, }), @@ -332,6 +334,7 @@ export class IntersectionRealizationGridProvider parameter_name: parameterName, parameter_time_or_interval_str: timeOrInterval, realization_num: realizationNum, + ...makeCacheBustingQueryParam(ensembleIdent), }, body: { polyline_utm_xy: polylineWithSectionLengths.polylineUtmXy }, }); diff --git a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/IntersectionRealizationSeismicProvider.ts b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/IntersectionRealizationSeismicProvider.ts index 808890bcd..c9920f4f2 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/IntersectionRealizationSeismicProvider.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/IntersectionRealizationSeismicProvider.ts @@ -3,6 +3,7 @@ import { isEqual } from "lodash"; import { getSeismicCubeMetaListOptions, postGetSeismicFenceOptions } from "@api"; import { IntersectionType } from "@framework/types/intersection"; import { defaultContinuousDivergingColorPalettes } from "@framework/utils/colorPalettes"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { assertNonNull } from "@lib/utils/assertNonNull"; import { ColorScale, ColorScaleGradientType, ColorScaleType } from "@lib/utils/ColorScale"; import type { PolylineWithSectionLengths } from "@modules/_shared/Intersection/intersectionPolylineTypes"; @@ -201,6 +202,7 @@ export class IntersectionRealizationSeismicProvider query: { case_uuid: ensembleIdent.getCaseUuid() ?? "", ensemble_name: ensembleIdent.getEnsembleName() ?? "", + ...makeCacheBustingQueryParam(ensembleIdent ?? null), }, signal: abortSignal, @@ -352,6 +354,7 @@ export class IntersectionRealizationSeismicProvider seismic_attribute: attribute, time_or_interval_str: timeOrInterval ?? "", observed: this._dataSource === SeismicDataSource.OBSERVED, + ...makeCacheBustingQueryParam(ensembleIdent), }, body: { polyline: apiSeismicFencePolyline, diff --git a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/RealizationPolygonsProvider.ts b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/RealizationPolygonsProvider.ts index 499c5fb01..f43ffbe79 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/RealizationPolygonsProvider.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/RealizationPolygonsProvider.ts @@ -2,6 +2,7 @@ import { isEqual } from "lodash"; import type { PolygonData_api } from "@api"; import { getPolygonsDataOptions, getPolygonsDirectoryOptions } from "@api"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import type { CustomDataProviderImplementation, FetchDataParams, @@ -74,6 +75,7 @@ export class RealizationPolygonsProvider query: { case_uuid: ensembleIdent.getCaseUuid(), ensemble_name: ensembleIdent.getEnsembleName(), + ...makeCacheBustingQueryParam(ensembleIdent), }, signal: abortSignal, }), @@ -130,6 +132,7 @@ export class RealizationPolygonsProvider realization_num: realizationNum ?? 0, name: polygonsName ?? "", attribute: polygonsAttribute ?? "", + ...makeCacheBustingQueryParam(ensembleIdent ?? null), }, }); diff --git a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/RealizationSurfaceProvider.ts b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/RealizationSurfaceProvider.ts index 5cb465b68..578e18bd8 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/RealizationSurfaceProvider.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/RealizationSurfaceProvider.ts @@ -7,6 +7,7 @@ import { getRealizationSurfacesMetadataOptions, getSurfaceDataOptions, } from "@api"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import type { CustomDataProviderImplementation, DataProviderInformationAccessors, @@ -144,6 +145,7 @@ export class RealizationSurfaceProvider query: { case_uuid: ensembleIdent.getCaseUuid(), ensemble_name: ensembleIdent.getEnsembleName(), + ...makeCacheBustingQueryParam(ensembleIdent), }, signal: abortSignal, }), diff --git a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/StatisticalSurfaceProvider.ts b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/StatisticalSurfaceProvider.ts index c8b33edcc..943d8561e 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/StatisticalSurfaceProvider.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/StatisticalSurfaceProvider.ts @@ -12,6 +12,7 @@ import { } from "@api"; import { lroProgressBus } from "@framework/LroProgressBus"; import { wrapLongRunningQuery } from "@framework/utils/lro/longRunningApiCalls"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import type { CustomDataProviderImplementation, DataProviderInformationAccessors, @@ -153,6 +154,7 @@ export class StatisticalSurfaceProvider query: { case_uuid: ensembleIdent.getCaseUuid(), ensemble_name: ensembleIdent.getEnsembleName(), + ...makeCacheBustingQueryParam(ensembleIdent), }, signal: abortSignal, }), diff --git a/frontend/src/modules/_shared/InplaceVolumes/queryHooks.ts b/frontend/src/modules/_shared/InplaceVolumes/queryHooks.ts index 27fc5e514..254a11deb 100644 --- a/frontend/src/modules/_shared/InplaceVolumes/queryHooks.ts +++ b/frontend/src/modules/_shared/InplaceVolumes/queryHooks.ts @@ -7,6 +7,7 @@ import type { } from "@api"; import { postGetAggregatedPerRealizationTableDataOptions, postGetAggregatedStatisticalTableDataOptions } from "@api"; import type { RegularEnsembleIdent } from "@framework/RegularEnsembleIdent"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { encodeAsUintListStr } from "@lib/utils/queryStringUtils"; import type { InplaceVolumesStatisticalTableData, @@ -65,6 +66,7 @@ export function useGetAggregatedStatisticalTableDataQueries( result_names: resultNames, group_by_indices: validGroupByIndices, realizations_encoded_as_uint_list_str: validRealizationsEncodedAsUintListStr, + ...makeCacheBustingQueryParam(source.ensembleIdent), }, body: { indices_with_values: indicesWithValues, @@ -146,6 +148,7 @@ export function useGetAggregatedPerRealizationTableDataQueries( result_names: resultNames, group_by_indices: validGroupByIndices, realizations_encoded_as_uint_list_str: validRealizationsEncodedAsUintListStr, + ...makeCacheBustingQueryParam(source.ensembleIdent), }, body: { indices_with_values: indicesWithValues, diff --git a/frontend/src/modules/_shared/Polygons/queryHooks.ts b/frontend/src/modules/_shared/Polygons/queryHooks.ts index c67dab074..6ba20b4c1 100644 --- a/frontend/src/modules/_shared/Polygons/queryHooks.ts +++ b/frontend/src/modules/_shared/Polygons/queryHooks.ts @@ -1,70 +1,46 @@ -import type { QueryFunction, QueryKey, UseQueryResult } from "@tanstack/react-query"; +import type { UseQueryResult } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query"; import type { PolygonData_api, PolygonsMeta_api } from "@api"; -import { getPolygonsData, getPolygonsDataQueryKey, getPolygonsDirectoryOptions } from "@api"; +import { getPolygonsDataOptions, getPolygonsDirectoryOptions } from "@api"; +import { RegularEnsembleIdent } from "@framework/RegularEnsembleIdent"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import type { PolygonsAddress } from "./polygonsAddress"; export function usePolygonsDirectoryQuery( - caseUuid: string | undefined, - ensembleName: string | undefined, + ensembleIdent: RegularEnsembleIdent | null, ): UseQueryResult { return useQuery({ ...getPolygonsDirectoryOptions({ query: { - case_uuid: caseUuid ?? "", - ensemble_name: ensembleName ?? "", + case_uuid: ensembleIdent?.getCaseUuid() ?? "", + ensemble_name: ensembleIdent?.getEnsembleName() ?? "", + ...makeCacheBustingQueryParam(ensembleIdent ?? null), }, }), - enabled: Boolean(caseUuid && ensembleName), + enabled: Boolean(ensembleIdent), }); } export function usePolygonsDataQueryByAddress( polygonsAddress: PolygonsAddress | null, ): UseQueryResult { - function dummyApiCall(): Promise { - return new Promise((_resolve, reject) => { - reject(null); - }); - } - - let queryKey: QueryKey | null = null; - let queryFn: QueryFunction | null = null; - - if (!polygonsAddress) { - queryKey = ["getPolygonsData_DUMMY_ALWAYS_DISABLED"]; - queryFn = dummyApiCall; - } else { - queryKey = getPolygonsDataQueryKey({ - query: { - case_uuid: polygonsAddress.caseUuid, - ensemble_name: polygonsAddress.ensemble, - realization_num: polygonsAddress.realizationNum, - name: polygonsAddress.name, - attribute: polygonsAddress.attribute, - }, - }); - queryFn = async () => { - const { data } = await getPolygonsData({ - query: { - case_uuid: polygonsAddress.caseUuid, - ensemble_name: polygonsAddress.ensemble, - realization_num: polygonsAddress.realizationNum, - name: polygonsAddress.name, - attribute: polygonsAddress.attribute, - }, - throwOnError: true, - }); - - return data; - }; - } + const queryOptions = getPolygonsDataOptions({ + query: { + case_uuid: polygonsAddress?.caseUuid ?? "", + ensemble_name: polygonsAddress?.ensemble ?? "", + realization_num: polygonsAddress?.realizationNum ?? 0, + name: polygonsAddress?.name ?? "", + attribute: polygonsAddress?.attribute ?? "", + ...makeCacheBustingQueryParam( + polygonsAddress ? new RegularEnsembleIdent(polygonsAddress.caseUuid, polygonsAddress.ensemble) : null, + ), + }, + }); return useQuery({ - queryKey: queryKey, - queryFn: queryFn, + ...queryOptions, enabled: Boolean(polygonsAddress), }); } diff --git a/frontend/src/modules/_shared/Surface/queryHooks.ts b/frontend/src/modules/_shared/Surface/queryHooks.ts index 219545e26..275cccde8 100644 --- a/frontend/src/modules/_shared/Surface/queryHooks.ts +++ b/frontend/src/modules/_shared/Surface/queryHooks.ts @@ -3,6 +3,8 @@ import { useQuery } from "@tanstack/react-query"; import type { SurfaceDataPng_api, SurfaceDef_api, SurfaceMetaSet_api } from "@api"; import { getObservedSurfacesMetadataOptions, getRealizationSurfacesMetadataOptions, getSurfaceDataOptions } from "@api"; +import { RegularEnsembleIdent } from "@framework/RegularEnsembleIdent"; +import { makeCacheBustingQueryParam } from "@framework/utils/queryUtils"; import { encodePropertiesAsKeyValStr } from "@lib/utils/queryStringUtils"; import type { SurfaceDataFloat_trans } from "./queryDataTransforms"; @@ -18,6 +20,9 @@ export function useRealizationSurfacesMetadataQuery( query: { case_uuid: caseUuid ?? "", ensemble_name: ensembleName ?? "", + ...makeCacheBustingQueryParam( + caseUuid && ensembleName ? new RegularEnsembleIdent(caseUuid, ensembleName) : null, + ), }, }), enabled: Boolean(caseUuid && ensembleName),