diff --git a/.github/workflows/webviz.yml b/.github/workflows/webviz.yml index fdb3e3159..c6ffdbb21 100644 --- a/.github/workflows/webviz.yml +++ b/.github/workflows/webviz.yml @@ -70,9 +70,9 @@ jobs: - name: 🕵️ Check auto-generated frontend code is in sync with backend run: | - docker build -f backend.Dockerfile -t backend:latest . - CONTAINER_ID=$(docker run --detach -p 5000:5000 --env UVICORN_PORT=5000 --env UVICORN_ENTRYPOINT=src.backend.primary.main:app --env WEBVIZ_CLIENT_SECRET=0 --env WEBVIZ_SMDA_SUBSCRIPTION_KEY=0 --env WEBVIZ_SMDA_RESOURCE_SCOPE=0 --env WEBVIZ_VDS_HOST_ADDRESS=0 backend:latest) - sleep 5 # Ensure the backend server is up and running exposing /openapi.json + docker build -f ./backend_py/primary/Dockerfile -t backend:latest . + CONTAINER_ID=$(docker run --detach -p 5000:5000 --env UVICORN_PORT=5000 --env WEBVIZ_CLIENT_SECRET=0 --env WEBVIZ_SMDA_SUBSCRIPTION_KEY=0 --env WEBVIZ_SMDA_RESOURCE_SCOPE=0 --env WEBVIZ_VDS_HOST_ADDRESS=0 backend:latest) + sleep 10 # Ensure the backend server is up and running exposing /openapi.json npm run generate-api --prefix ./frontend docker stop $CONTAINER_ID git diff --exit-code ./frontend/src/api || exit 1 @@ -91,7 +91,7 @@ jobs: cache: pip - name: 📦 Install poetry and dependencies - working-directory: ./backend + working-directory: ./backend_py/primary run: | pip install --upgrade pip pip install poetry @@ -100,15 +100,16 @@ jobs: poetry install --with dev - name: 🕵️ Check code style & linting - working-directory: ./backend + working-directory: ./backend_py/primary run: | - black --check src/ tests/ - pylint src/ tests/ - bandit --recursive src/ - mypy src/ tests/ + set -x + black --check primary/ tests/ + pylint primary/ tests/ + bandit --recursive primary/ + mypy primary/ tests/ - name: 🤖 Run tests - working-directory: ./backend + working-directory: ./backend_py/primary env: WEBVIZ_CLIENT_SECRET: 0 WEBVIZ_SMDA_SUBSCRIPTION_KEY: 0 @@ -148,4 +149,4 @@ jobs: - name: 🐳 Verify Docker images build run: | docker build -f frontend-prod.Dockerfile . - docker build -f backend.Dockerfile . + docker build -f ./backend_py/primary/Dockerfile . diff --git a/.gitignore b/.gitignore index f7f274172..4d9dd997f 100644 --- a/.gitignore +++ b/.gitignore @@ -108,9 +108,10 @@ dist .tern-port # .vscode -.vscode/ +**/.vscode/* !.vscode/tasks.json !.vscode/launch.json +!backend_py/primary/.vscode/launch.json # playwright results playwright-report/ @@ -122,3 +123,6 @@ playwright-report/ # Ignore Jupyter Notebook checkpoints (hidden directories) .ipynb_checkpoints/ + +# Python virtual environments +.venv* diff --git a/.vscode/launch.json b/.vscode/launch.json index 116c58dcb..d5196d4dc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,8 +7,8 @@ "connect": { "host": "localhost", "port": 5678 }, "pathMappings": [ { - "localRoot": "${workspaceFolder}/backend", - "remoteRoot": "/home/appuser/backend" + "localRoot": "${workspaceFolder}/backend_py/primary", + "remoteRoot": "/home/appuser/backend_py/primary" } ] }, diff --git a/README.md b/README.md index c5479873c..dbeddafe4 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,8 @@ as stated above). We have two applications in Radix built from this repository: * [Main application](https://webviz.app.radix.equinor.com/) built from the `main` branch. -* [Review application](https://frontend-webviz-review.radix.equinor.com/) built from the `review` branch +* [Review application](https://frontend-webviz-review.radix.equinor.com/) built from the `review` branch. +* [Dev application](https://frontend-webviz-dev.radix.equinor.com/) built from the `dev` branch. The applications are automatically built and redeployed when pushing commits to the respective branch. diff --git a/backend.Dockerfile b/backend.Dockerfile deleted file mode 100644 index ca192388a..000000000 --- a/backend.Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM python:3.11-slim@sha256:ad2c4e5884418404c5289acad4a471dde8500e24ba57ad574cdcae46523e507a - -RUN useradd --create-home --uid 1234 appuser # Changing to non-root user early - -USER 1234 - -COPY --chown=appuser ./backend /home/appuser/backend -WORKDIR /home/appuser/backend - -ENV PATH="${PATH}:/home/appuser/.local/bin" -RUN pip install poetry \ - && poetry export -f requirements.txt -o requirements.txt \ - && pip install -r requirements.txt - -CMD exec uvicorn --proxy-headers --host=0.0.0.0 $UVICORN_ENTRYPOINT diff --git a/backend/src/backend/primary/main.py b/backend/src/backend/primary/main.py deleted file mode 100644 index ab73685c6..000000000 --- a/backend/src/backend/primary/main.py +++ /dev/null @@ -1,104 +0,0 @@ -import datetime -import logging -import os - -from fastapi import FastAPI -from fastapi.routing import APIRoute -from fastapi.responses import ORJSONResponse -from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware - -from src.backend.utils.add_process_time_to_server_timing_middleware import AddProcessTimeToServerTimingMiddleware -from src.backend.utils.azure_monitor_setup import setup_azure_monitor_telemetry -from src.backend.utils.exception_handlers import override_default_fastapi_exception_handlers -from src.backend.utils.exception_handlers import configure_service_level_exception_handlers -from src.backend.utils.logging_setup import ensure_console_log_handler_is_configured, setup_normal_log_levels -from src.backend.shared_middleware import add_shared_middlewares -from src.backend.auth.auth_helper import AuthHelper -from .routers.explore import router as explore_router -from .routers.general import router as general_router -from .routers.inplace_volumetrics.router import router as inplace_volumetrics_router -from .routers.surface.router import router as surface_router -from .routers.timeseries.router import router as timeseries_router -from .routers.parameters.router import router as parameters_router -from .routers.correlations.router import router as correlations_router -from .routers.grid.router import router as grid_router -from .routers.pvt.router import router as pvt_router -from .routers.well_completions.router import router as well_completions_router -from .routers.well.router import router as well_router -from .routers.seismic.router import router as seismic_router -from .routers.polygons.router import router as polygons_router -from .routers.graph.router import router as graph_router -from .routers.observations.router import router as observations_router -from .routers.rft.router import router as rft_router - - -ensure_console_log_handler_is_configured() -setup_normal_log_levels() - -# temporarily set some loggers to DEBUG -# logging.getLogger().setLevel(logging.DEBUG) -logging.getLogger("src.services.sumo_access").setLevel(logging.DEBUG) - -LOGGER = logging.getLogger(__name__) - - -def custom_generate_unique_id(route: APIRoute) -> str: - return f"{route.name}" - - -app = FastAPI( - generate_unique_id_function=custom_generate_unique_id, - root_path="/api", - default_response_class=ORJSONResponse, -) - -if os.environ.get("APPLICATIONINSIGHTS_CONNECTION_STRING"): - LOGGER.info("Configuring Azure Monitor telemetry for primary backend") - setup_azure_monitor_telemetry(app) -else: - LOGGER.warning("Skipping telemetry configuration, APPLICATIONINSIGHTS_CONNECTION_STRING env variable not set.") - - -# The tags we add here will determine the name of the frontend api service for our endpoints as well as -# providing some grouping when viewing the openapi documentation. -app.include_router(explore_router, tags=["explore"]) -app.include_router(timeseries_router, prefix="/timeseries", tags=["timeseries"]) -app.include_router( - inplace_volumetrics_router, - prefix="/inplace_volumetrics", - tags=["inplace_volumetrics"], -) -app.include_router(surface_router, prefix="/surface", tags=["surface"]) -app.include_router(parameters_router, prefix="/parameters", tags=["parameters"]) -app.include_router(correlations_router, prefix="/correlations", tags=["correlations"]) -app.include_router(grid_router, prefix="/grid", tags=["grid"]) -app.include_router(pvt_router, prefix="/pvt", tags=["pvt"]) -app.include_router(well_completions_router, prefix="/well_completions", tags=["well_completions"]) -app.include_router(well_router, prefix="/well", tags=["well"]) -app.include_router(seismic_router, prefix="/seismic", tags=["seismic"]) -app.include_router(polygons_router, prefix="/polygons", tags=["polygons"]) -app.include_router(graph_router, prefix="/graph", tags=["graph"]) -app.include_router(observations_router, prefix="/observations", tags=["observations"]) -app.include_router(rft_router, prefix="/rft", tags=["rft"]) - -authHelper = AuthHelper() -app.include_router(authHelper.router) -app.include_router(general_router) - -configure_service_level_exception_handlers(app) -override_default_fastapi_exception_handlers(app) - -# This middleware instance approximately measures execution time of the route handler itself -app.add_middleware(AddProcessTimeToServerTimingMiddleware, metric_name="total-exec-route") - -add_shared_middlewares(app) - -app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*") - -# This middleware instance measures execution time of the endpoints, including the cost of other middleware -app.add_middleware(AddProcessTimeToServerTimingMiddleware, metric_name="total") - - -@app.get("/") -async def root() -> str: - return f"Backend is alive at this time: {datetime.datetime.now()}" diff --git a/backend/src/backend/primary/user_session_proxy.py b/backend/src/backend/primary/user_session_proxy.py deleted file mode 100644 index e7b8d5786..000000000 --- a/backend/src/backend/primary/user_session_proxy.py +++ /dev/null @@ -1,149 +0,0 @@ -import os -import asyncio -from typing import Any, Optional - -import httpx -import redis -from starlette.requests import Request -from starlette.responses import StreamingResponse -from starlette.background import BackgroundTask - -from src import config -from src.services.utils.authenticated_user import AuthenticatedUser - -LOCALHOST_DEVELOPMENT = os.environ.get("UVICORN_RELOAD") == "true" - - -class _RedisUserJobs: - def __init__(self) -> None: - # redis.Redis does not yet have namespace support - https://github.com/redis/redis-py/issues/12 - need to prefix manually. - self._redis_client = redis.Redis.from_url(config.REDIS_USER_SESSION_URL, decode_responses=True) - - def get_job_name(self, user_id: str) -> Optional[str]: - return self._redis_client.get("user-job-name:" + user_id) - - def set_job_name(self, user_id: str, job_name: str) -> None: - self._redis_client.set("user-job-name:" + user_id, job_name) - - -class RadixJobScheduler: - """Utility class to help with spawning Radix jobs on demand, - and provide correct URL to communicate with running Radix jobs""" - - def __init__(self, name: str, port: int) -> None: - self._name = name - self._port = port - self._redis_user_jobs = _RedisUserJobs() - - def _get_job_name(self, user_id: str) -> Optional[str]: - return self._redis_user_jobs.get_job_name(user_id) - - def _set_job_name(self, user_id: str, job_name: str) -> None: - self._redis_user_jobs.set_job_name(user_id, job_name) - - async def _active_running_job(self, user_id: str) -> bool: - """Returns true if there already is a running job for logged in user.""" - - existing_job_name = self._get_job_name(user_id) - if not existing_job_name: - return False - if LOCALHOST_DEVELOPMENT: - return True - - async with httpx.AsyncClient() as client: - res = await client.get(f"http://{self._name}:{self._port}/api/v1/jobs/{existing_job_name}") - - job = res.json() - - if job.get("status") != "Running" or not job.get("started"): - return False - - try: - httpx.get(f"http://{existing_job_name}:{self._port}/") - except (ConnectionRefusedError, httpx.ConnectError, httpx.ConnectTimeout): - print(f"User session container for user {user_id} not yet up.") - return False - - return True - - async def _create_new_job(self, user_id: str) -> None: - """Create a new Radix job by sending request to Radix job scheduler. - If localhost development, simply return already running container with - same name.""" - - if LOCALHOST_DEVELOPMENT: - self._set_job_name(user_id, self._name) - else: - print(f"Requesting new user session container for user {user_id}.") - async with httpx.AsyncClient() as client: - res = await client.post( - f"http://{self._name}:{self._port}/api/v1/jobs", - # Maximum limits in "resources" for a Radix job is as of May 2023 - # the specs of a single Standard_E16as_v4 node, i.e.: - # * vCPU: 16 - # * memory: 128 GiB - # * temp storage (SSD): 256 GiB - # - # As of now our CPU/memory requests are hardcoded below, but in the future maybe - # these could be dynamic based on e.g. the selected ensemble sizess by the user. - json={ - "resources": { - "limits": {"memory": "32GiB", "cpu": "2"}, - "requests": {"memory": "32GiB", "cpu": "1"}, - } - }, - ) - self._set_job_name(user_id, res.json()["name"]) - - while not await self._active_running_job(user_id): - # It takes a couple of seconds before Radix job uvicorn process has - # started and begins to listen at the end point. - await asyncio.sleep(1) - - async def get_base_url(self, user_id: str) -> str: - """Input is ID of logged in user. Returned value is base URL towards the correct - Radix job""" - if not await self._active_running_job(user_id): - await self._create_new_job(user_id) - - job_name = self._get_job_name(user_id) - - return f"http://{job_name}:{self._port}" - - -# For now we only have one type of job: -RADIX_JOB_SCHEDULER_INSTANCE = RadixJobScheduler("backend-user-session", 8000) - - -async def proxy_to_user_session(request: Request, authenticated_user: AuthenticatedUser) -> Any: - # Ideally this function should probably be a starlette/FastAPI middleware, but it appears that - # it is not yet possible to put middleware on single routes through decorator like in express.js. - - base_url = await RADIX_JOB_SCHEDULER_INSTANCE.get_base_url( - authenticated_user._user_id # pylint: disable=protected-access - ) - - # See https://github.com/tiangolo/fastapi/discussions/7382: - - client = httpx.AsyncClient(base_url=base_url) - - url = httpx.URL( - path=request.url.path.removeprefix("/api").rstrip("/"), - query=request.url.query.encode("utf-8"), - ) - - job_req = client.build_request( - request.method, - url, - headers=request.headers.raw, - content=request.stream(), - timeout=600, - ) - job_resp = await client.send(job_req, stream=True) - - return StreamingResponse( - job_resp.aiter_raw(), - status_code=job_resp.status_code, - headers=job_resp.headers, - background=BackgroundTask(job_resp.aclose), - ) diff --git a/backend/src/backend/shared_middleware.py b/backend/src/backend/shared_middleware.py deleted file mode 100644 index 60c48c1b6..000000000 --- a/backend/src/backend/shared_middleware.py +++ /dev/null @@ -1,21 +0,0 @@ -from fastapi import FastAPI -from starsessions import SessionMiddleware -from starsessions.stores.redis import RedisStore - -from src import config -from src.backend.auth.enforce_logged_in_middleware import EnforceLoggedInMiddleware - - -def add_shared_middlewares(app: FastAPI) -> None: - # Add out custom middleware to enforce that user is logged in - # Also redirects to /login endpoint for some select paths - unprotected_paths = ["/logged_in_user", "/alive", "/openapi.json"] - paths_redirected_to_login = ["/", "/alive_protected"] - app.add_middleware( - EnforceLoggedInMiddleware, - unprotected_paths=unprotected_paths, - paths_redirected_to_login=paths_redirected_to_login, - ) - - session_store = RedisStore(config.REDIS_USER_SESSION_URL, prefix="user-auth:") - app.add_middleware(SessionMiddleware, store=session_store) diff --git a/backend/src/backend/user_session/main.py b/backend/src/backend/user_session/main.py deleted file mode 100644 index 223ff6596..000000000 --- a/backend/src/backend/user_session/main.py +++ /dev/null @@ -1,39 +0,0 @@ -import logging -import os - -from fastapi import FastAPI -from fastapi.responses import ORJSONResponse - -from src.backend.utils.azure_monitor_setup import setup_azure_monitor_telemetry -from src.backend.utils.logging_setup import ensure_console_log_handler_is_configured, setup_normal_log_levels -from src.backend.shared_middleware import add_shared_middlewares -from .inactivity_shutdown import InactivityShutdown -from .routers.general import router as general_router - -# mypy: disable-error-code="attr-defined" -from .routers.grid.router import router as grid_router - - -ensure_console_log_handler_is_configured() -setup_normal_log_levels() - -LOGGER = logging.getLogger(__name__) - - -app = FastAPI(default_response_class=ORJSONResponse) - -if os.environ.get("APPLICATIONINSIGHTS_CONNECTION_STRING"): - LOGGER.info("Configuring Azure Monitor telemetry for user session server") - setup_azure_monitor_telemetry(app) -else: - LOGGER.warning("Skipping telemetry configuration, APPLICATIONINSIGHTS_CONNECTION_STRING env variable not set.") - -app.include_router(general_router) -app.include_router(grid_router, prefix="/grid") -add_shared_middlewares(app) - -# We shut down the user session container after some -# minutes without receiving any new requests: -InactivityShutdown(app, inactivity_limit_minutes=30) - -LOGGER.info("Successfully completed user session server initialization.") diff --git a/backend/src/backend/user_session/routers/general.py b/backend/src/backend/user_session/routers/general.py deleted file mode 100644 index 3d1fd4566..000000000 --- a/backend/src/backend/user_session/routers/general.py +++ /dev/null @@ -1,37 +0,0 @@ -import datetime -from typing import Dict, Union, NamedTuple - -import psutil -from fastapi import APIRouter, Depends -from src.backend.auth.auth_helper import AuthHelper, AuthenticatedUser - -router = APIRouter() - -START_TIME_CONTAINER = datetime.datetime.now() - - -def _convert_psutil_object_to_dict(psutil_object: NamedTuple) -> Dict[str, Union[str, Dict[str, str]]]: - return {key: getattr(psutil_object, key) for key in psutil_object._fields} - - -@router.get("/user_session_container") -async def user_session_container( - authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), -) -> dict: - """Get information about user session container, like when it was started - together with memory and disk usage. NB! Note that a session container is started - if one is not already running when accessing this endpoint. - - For explanation of the different memory metrics, see e.g. psutil documentation like - * https://psutil.readthedocs.io/en/latest/index.html?highlight=Process()#psutil.virtual_memory - * https://psutil.readthedocs.io/en/latest/index.html?highlight=Process()#psutil.Process - """ - - return { - "username": authenticated_user.get_username(), - "startTimeContainer": START_TIME_CONTAINER, - "rootDiskSystem": _convert_psutil_object_to_dict(psutil.disk_usage("/")), - "memorySystem": _convert_psutil_object_to_dict(psutil.virtual_memory()), - "memoryPythonProcess": _convert_psutil_object_to_dict(psutil.Process().memory_info()), - "cpuPercent": psutil.cpu_percent(), - } diff --git a/backend/src/backend/user_session/routers/grid/router.py b/backend/src/backend/user_session/routers/grid/router.py deleted file mode 100644 index fd55d354d..000000000 --- a/backend/src/backend/user_session/routers/grid/router.py +++ /dev/null @@ -1,520 +0,0 @@ -# type: ignore -# for now -from functools import cache -from typing import List, Tuple -import logging -import os -from concurrent.futures import ThreadPoolExecutor - -import psutil -import numpy as np -import orjson -import xtgeo -from vtkmodules.util.numpy_support import vtk_to_numpy -from fastapi import APIRouter, Depends, Request -from fastapi.responses import ORJSONResponse - -from src.backend.auth.auth_helper import AuthenticatedUser, AuthHelper -from src.backend.primary.routers.grid.schemas import ( - GridSurface, - GridIntersection, -) -from src.services.sumo_access.grid_access import GridAccess -from src.services.utils.b64 import b64_encode_float_array_as_float32, b64_encode_uint_array_as_smallest_size -from src.services.utils.vtk_utils import ( - VtkGridSurface, - get_scalar_values, - get_surface, - cut_along_polyline, - flatten_sliced_grid, - xtgeo_grid_to_vtk_explicit_structured_grid, - create_polyline, - grid_to_numpy, - get_triangles, -) -from src.services.utils.mpl_utils import visualize_triangles_with_scalars -from src.services.utils.perf_timer import PerfTimer - -router = APIRouter() -LOGGER = logging.getLogger(__name__) - - -@router.get("/grid_surface", response_model=GridSurface) -async def grid_surface( - request: Request, - authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), -): - case_uuid = request.query_params.get("case_uuid") - ensemble_name = request.query_params.get("ensemble_name") - grid_name = request.query_params.get("grid_name") - realization = request.query_params.get("realization") - - # Get Xtgeo grid - xtgeo_grid = await get_grid_geometry( - authenticated_user=authenticated_user, - case_uuid=case_uuid, - ensemble_name=ensemble_name, - grid_name=grid_name, - realization=realization, - ) - - # Get grid information from xtgeo (xmin, ymin, etc...) - grid_geometrics = xtgeo_grid.get_geometrics(allcells=True, return_dict=True) - - # Get grid surface (visible cells) - grid_surface_instance = get_grid_surface(grid_geometry=xtgeo_grid) - - # Extract points and polygons from surface - points_np = vtk_to_numpy(grid_surface_instance.polydata.GetPoints().GetData()).ravel().astype(np.float32) - polys_np = vtk_to_numpy(grid_surface_instance.polydata.GetPolys().GetData()).astype(np.int64) - - # Reduce precision of points to 2 decimals - points_np = np.around(points_np, decimals=2) - - grid_surface_payload = GridSurface( - points_b64arr=b64_encode_float_array_as_float32(points_np), - polys_b64arr=b64_encode_uint_array_as_smallest_size(polys_np), - **grid_geometrics, - ) - return ORJSONResponse(grid_surface_payload.dict()) - - -@router.get( - "/grid_parameter", response_model=List[float] -) # stating response_model here instead of return type apparently disables pydantic validation of the response (https://stackoverflow.com/a/65715205) -# type: ignore -async def grid_parameter( - request: Request, - authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), -): - case_uuid = request.query_params.get("case_uuid") - ensemble_name = request.query_params.get("ensemble_name") - grid_name = request.query_params.get("grid_name") - parameter_name = request.query_params.get("parameter_name") - realization = request.query_params.get("realization") - - # Get Xtgeo grid - xtgeo_grid = await get_grid_geometry( - authenticated_user=authenticated_user, - case_uuid=case_uuid, - ensemble_name=ensemble_name, - grid_name=grid_name, - realization=realization, - ) - # Get grid surface (visible cells) - grid_polydata = get_grid_surface(grid_geometry=xtgeo_grid) - - # Get Xtgeo parameter - xtgeo_parameter = await get_grid_parameter( - authenticated_user=authenticated_user, - case_uuid=case_uuid, - ensemble_name=ensemble_name, - grid_name=grid_name, - parameter_name=parameter_name, - realization=realization, - ) - - # Get scalar values from parameter - scalar_values = get_scalar_values(xtgeo_parameter, cell_ids=grid_polydata.original_cell_ids) - - # Handle xtgeo undefined values and truncate - scalar_values[scalar_values == -999.0] = np.nan - scalar_values[scalar_values < np.nanmin(scalar_values)] = np.nanmin(scalar_values) - scalar_values[scalar_values > np.nanmax(scalar_values)] = np.nanmax(scalar_values) - - return ORJSONResponse(scalar_values.tolist()) - - -@router.get( - "/grid_parameter_intersection", response_model=List[float] -) # stating response_model here instead of return type apparently disables pydantic validation of the response (https://stackoverflow.com/a/65715205) -# type: ignore -async def grid_parameter_intersection( # pylint: disable=too-many-locals - request: Request, - authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), -): - case_uuid = request.query_params.get("case_uuid") - ensemble_name = request.query_params.get("ensemble_name") - grid_name = request.query_params.get("grid_name") - parameter_name = request.query_params.get("parameter_name") - realization = request.query_params.get("realization") - - timer = PerfTimer() - # Get Xtgeo grid - xtgeo_grid = await get_grid_geometry( - authenticated_user=authenticated_user, - case_uuid=case_uuid, - ensemble_name=ensemble_name, - grid_name=grid_name, - realization=realization, - ) - # Activate all cells. Should we do this? - xtgeo_grid.activate_all() - print( - f"DOWNLOADED/READ CACHE: grid_geometry for {grid_name}, realization: {realization}: {round(timer.lap_s(),2)}s", - flush=True, - ) - # Get xtgeo parameter - xtgeo_parameter = await get_grid_parameter( - authenticated_user=authenticated_user, - case_uuid=case_uuid, - ensemble_name=ensemble_name, - grid_name=grid_name, - parameter_name=parameter_name, - realization=realization, - ) - print( - f"DOWNLOADED/READ CACHE: grid_parameter for {parameter_name}, realization: {realization}: {round(timer.lap_s(),2)}s", - flush=True, - ) - - # HARDCODED POLYLINE FOR TESTING - xyz_arr = tuple( - tuple(point) - for point in [ - [463156.911, 5929542.294, -49.0], - [463564.402, 5931057.803, -1293.4185], - [463637.925, 5931184.235, -1536.9384], - [463690.658, 5931278.837, -1616.4998], - [463910.452, 5931688.122, -1630.5153], - [464465.876, 5932767.761, -1656.9874], - [464765.876, 5934767.761, -1656.9874], - ] - ) - - # Generate intersection data - coords, triangles, original_cell_indices_np, polyline = generate_grid_intersection(xtgeo_grid, xyz_arr) - print( - f"CALCULATED INTERSECTION: realization: {realization}: {round(timer.lap_s(),2)}s", - flush=True, - ) - - # Get scalar values from parameter and select only the cells that intersect with the polyline - values = get_scalar_values(xtgeo_parameter, cell_ids=original_cell_indices_np) - print( - f"READ SCALAR VALUES: realization: {realization}: {round(timer.lap_s(),2)}s", - flush=True, - ) - - # Handle undefined values and truncate - values[values < np.nanmin(values)] = np.nanmin(values) - values[values > np.nanmax(values)] = np.nanmax(values) - # values[values > 0.4] = 0.4 - # values = values[original_cell_indices_np] - # scalar_values[scalar_values == -999.0] = 0 - - # Get polyline coordinates - polyline_coords = np.array([polyline.GetPoint(i)[:3] for i in range(polyline.GetNumberOfPoints())]) - - # Calculate the cumulative distance along the polyline - polyline_distances = np.zeros(polyline_coords.shape[0]) - for i in range(1, polyline_coords.shape[0]): - polyline_distances[i] = polyline_distances[i - 1] + np.linalg.norm( - polyline_coords[i, :2] - polyline_coords[i - 1, :2] - ) - - polyline_x = polyline_distances - polyline_y = polyline_coords[:, 2] - - # Visualize the intersection using matplotlib as a base64 encoded image - image_data = visualize_triangles_with_scalars(coords, triangles, values, polyline, "55/33-A-4") - print( - f"MATPLOTLIB IMAGE: realization: {realization}: {round(timer.lap_s(),2)}s", - flush=True, - ) - - # Get the bounding box of the intersection - x_min, x_max = np.min(coords[:, 0]), np.max(coords[:, 0]) - y_min, y_max = np.min(coords[:, 1]), np.max(coords[:, 1]) - - # Create the intersection data object - intersection_data = GridIntersection( - image=f"data:image/png;base64,{image_data}", - polyline_x=polyline_x.tolist(), - polyline_y=polyline_y.tolist(), - x_min=float(x_min), - x_max=float(x_max), - y_min=float(y_min), - y_max=float(y_max), - ) - return ORJSONResponse(intersection_data.__dict__) - - -@router.get( - "/statistical_grid_parameter_intersection", response_model=List[float] -) # stating response_model here instead of return type apparently disables pydantic validation of the response (https://stackoverflow.com/a/65715205) -# type: ignore -async def statistical_grid_parameter_intersection( # pylint: disable=too-many-locals - request: Request, - authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), -): - timer = PerfTimer() - print("#" * 80, flush=True) - print("ENTERING STATISTICAL GRID PARAMETER INTERSECTION", flush=True) - print( - f"Memory usage: {psutil.Process(os.getpid()).memory_info().rss / 1024 ** 2} MB", - flush=True, - ) - print("-" * 80, flush=True) - - case_uuid = request.query_params.get("case_uuid") - ensemble_name = request.query_params.get("ensemble_name") - grid_name = request.query_params.get("grid_name") - parameter_name = request.query_params.get("parameter_name") - # convert json string of realizations into list - realizations = orjson.loads(request.query_params.get("realizations")) # pylint: disable=maybe-no-member - - grid_access = await GridAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) - - # Check that all grids have equal nx, ny, nz - # Should raise a http exception instead of a value error - if not await grid_access.grids_have_equal_nxnynz(grid_name=grid_name): - raise ValueError("Grids must have equal nx, ny, nz") - - # Get Xtgeo grid - xtgeo_grid = await get_grid_geometry( - authenticated_user=authenticated_user, - case_uuid=case_uuid, - ensemble_name=ensemble_name, - grid_name=grid_name, - realization=0, - ) - - # Activate all cells. Should we do this? - xtgeo_grid.activate_all() - print( - f"DOWNLOADED/READ CACHE: grid_geometry for {grid_name}, realization: {0}: {round(timer.lap_s(),2)}s", - flush=True, - ) - - print("-" * 80, flush=True) - print("GETTING GRID PARAMETERS", flush=True) - - ### Using ThreadPoolExecutor to parallelize the download of the grid parameters - async def worker(real): - return await get_grid_parameter( - authenticated_user=authenticated_user, - case_uuid=case_uuid, - ensemble_name=ensemble_name, - grid_name=grid_name, - parameter_name=parameter_name, - realization=real, - ) - - with ThreadPoolExecutor() as executor: - xtgeo_parameters = list(executor.map(worker, realizations)) - print( - f"DOWNLOADED/READ CACHE: grid_parameters for {parameter_name}, realizations: {realizations}: {round(timer.lap_s(),2)}s", - flush=True, - ) - - # HARDCODED POLYLINE FOR TESTING - xyz_arr = tuple( - tuple(point) - for point in [ - [463156.911, 5929542.294, -49.0], - [463564.402, 5931057.803, -1293.4185], - [463637.925, 5931184.235, -1536.9384], - [463690.658, 5931278.837, -1616.4998], - [463910.452, 5931688.122, -1630.5153], - [464465.876, 5932767.761, -1656.9874], - [464765.876, 5934767.761, -1656.9874], - ] - ) - print("-" * 80, flush=True) - print("GENERATING GRID INTERSECTION", flush=True) - - # Generate intersection data - coords, triangles, original_cell_indices_np, polyline = generate_grid_intersection(xtgeo_grid, xyz_arr) - print( - f"CALCULATED INTERSECTION: realization: {0}: {round(timer.lap_s(),2)}s", - flush=True, - ) - print("-" * 80, flush=True) - - # Get scalar values for each realization - all_scalar_values = [ - get_scalar_values(xtgeo_parameter, cell_ids=original_cell_indices_np) for xtgeo_parameter in xtgeo_parameters - ] - - # Calculate the mean scalar value for each cell - values = np.nanmean(all_scalar_values, axis=0) - - # Handle xtgeo undefined values and truncate - values[values < np.nanmin(values)] = np.nanmin(values) - values[values > np.nanmax(values)] = np.nanmax(values) - values[values == -999.0] = np.nan - print( - f"DOWNLOADED/READ CACHE: scalar_values for {parameter_name}, realizations: {realizations}: {round(timer.lap_s(),2)}s", - flush=True, - ) - - # Get polyline coordinates - polyline_coords = np.array([polyline.GetPoint(i)[:3] for i in range(polyline.GetNumberOfPoints())]) - - # Calculate the cumulative distance along the polyline - polyline_distances = np.zeros(polyline_coords.shape[0]) - for i in range(1, polyline_coords.shape[0]): - polyline_distances[i] = polyline_distances[i - 1] + np.linalg.norm( - polyline_coords[i, :2] - polyline_coords[i - 1, :2] - ) - - polyline_x = polyline_distances - polyline_y = polyline_coords[:, 2] - print("-" * 80, flush=True) - - print("GENERATE MATPLOTLIB IMAGE", flush=True) - - # Visualize the intersection using matplotlib as a base64 encoded image - image_data = visualize_triangles_with_scalars(coords, triangles, values, polyline, "55/33-A-4") - print( - f"GENERATED MATPLOTLIB IMAGE: {parameter_name}, realization: {0}: {round(timer.lap_s(),2)}s", - flush=True, - ) - - # Get the bounding box of the intersection - x_min, x_max = np.min(coords[:, 0]), np.max(coords[:, 0]) - y_min, y_max = np.min(coords[:, 1]), np.max(coords[:, 1]) - - # Create the intersection data object - intersection_data = GridIntersection( - image=f"data:image/png;base64,{image_data}", - polyline_x=polyline_x.tolist(), - polyline_y=polyline_y.tolist(), - x_min=float(x_min), - x_max=float(x_max), - y_min=float(y_min), - y_max=float(y_max), - ) - - print("-" * 80, flush=True) - print("EXITING STATISTICAL GRID PARAMETER INTERSECTION", flush=True) - print( - f"Memory usage: {psutil.Process(os.getpid()).memory_info().rss / 1024 ** 2} MB", - flush=True, - ) - print("#" * 80, flush=True) - - return ORJSONResponse(intersection_data.__dict__) - - -@router.get( - "/statistical_grid_parameter", response_model=List[float] -) # stating response_model here instead of return type apparently disables pydantic validation of the response (https://stackoverflow.com/a/65715205) -# type: ignore -async def statistical_grid_parameter( - request: Request, - authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), -): - case_uuid = request.query_params.get("case_uuid") - ensemble_name = request.query_params.get("ensemble_name") - grid_name = request.query_params.get("grid_name") - parameter_name = request.query_params.get("parameter_name") - # convert json string of realizations into list - realizations = orjson.loads(request.query_params.get("realizations")) # pylint: disable=maybe-no-member - - grid_access = await GridAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) - - # Check that all grids have equal nx, ny, nz - # Should riase a http exception instead of a value error - if not grid_access.grids_have_equal_nxnynz(grid_name=grid_name): - raise ValueError("Grids must have equal nx, ny, nz") - - xtgeo_grid = await get_grid_geometry( - authenticated_user=authenticated_user, - case_uuid=case_uuid, - ensemble_name=ensemble_name, - grid_name=grid_name, - realization=realizations[0], - ) - - # Get grid surface (visible cells) - grid_polydata = get_grid_surface(grid_geometry=xtgeo_grid) - - # Get the xtgeo grid parameters for each realization - xtgeo_parameters = [ - await get_grid_parameter( - authenticated_user=authenticated_user, - case_uuid=case_uuid, - ensemble_name=ensemble_name, - grid_name=grid_name, - parameter_name=parameter_name, - realization=real, - ) - for real in realizations - ] - - # Get the scalar values for each parameter - all_scalar_values = [ - get_scalar_values(xtgeo_parameter, cell_ids=grid_polydata.original_cell_ids) - for xtgeo_parameter in xtgeo_parameters - ] - - # Calculate the mean scalar values for each cell - mean_scalar_values = np.nanmean(all_scalar_values, axis=0) - - # Handle xtgeo undefined values and truncate - mean_scalar_values[mean_scalar_values == -999.0] = np.nan - mean_scalar_values[mean_scalar_values < np.nanmin(mean_scalar_values)] = np.nanmin(mean_scalar_values) - mean_scalar_values[mean_scalar_values > np.nanmax(mean_scalar_values)] = np.nanmax(mean_scalar_values) - - return ORJSONResponse(mean_scalar_values.tolist()) - - -# @cache -async def get_grid_geometry( - authenticated_user: AuthenticatedUser, - case_uuid: str, - ensemble_name: str, - grid_name: str, - realization: int, -) -> xtgeo.Grid: - """Get the xtgeo grid geometry for a given realization""" - token = authenticated_user.get_sumo_access_token() - grid_access = await GridAccess.from_case_uuid(token, case_uuid, ensemble_name) - grid_geometry = await grid_access.get_grid_geometry(grid_name, int(realization)) - - return grid_geometry - - -@cache -def get_grid_surface(grid_geometry: xtgeo.Grid) -> VtkGridSurface: - return get_surface(grid_geometry) - - -# @cache -async def get_grid_parameter( - authenticated_user: AuthenticatedUser, - case_uuid: str, - ensemble_name: str, - grid_name: str, - parameter_name: str, - realization: int, -) -> xtgeo.GridProperty: - token = authenticated_user.get_sumo_access_token() - grid_access = await GridAccess.from_case_uuid(token, case_uuid, ensemble_name) - - return await grid_access.get_grid_parameter(grid_name, parameter_name, int(realization)) - - -@cache -def generate_grid_intersection(grid_geometry: xtgeo.Grid, xyz_arr: Tuple[List[float]]): - polyline = create_polyline(xyz_arr) - poly_xy = [] - for xy in xyz_arr: - poly_xy.extend([xy[0], xy[1]]) - - esgrid = xtgeo_grid_to_vtk_explicit_structured_grid(grid_geometry) - sliced_grid = cut_along_polyline(esgrid, poly_xy) - original_cell_indices_np = [ - int(c) for c in vtk_to_numpy(sliced_grid.GetCellData().GetAbstractArray("vtkOriginalCellIds")) - ] - - flattened_grid = flatten_sliced_grid(sliced_grid, polyline, original_cell_ids=original_cell_indices_np) - coords = grid_to_numpy(flattened_grid) - triangles = get_triangles(flattened_grid) - original_cell_indices_np = [ - int(c) for c in vtk_to_numpy(flattened_grid.GetCellData().GetAbstractArray("vtkOriginalCellIds")) - ] - - return (coords, triangles, original_cell_indices_np, polyline) diff --git a/backend/src/services/sumo_access/queries/__init__.py b/backend/src/services/sumo_access/queries/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/src/services/utils/__init__.py b/backend/src/services/utils/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/src/services/utils/mpl_utils.py b/backend/src/services/utils/mpl_utils.py deleted file mode 100644 index 855365b70..000000000 --- a/backend/src/services/utils/mpl_utils.py +++ /dev/null @@ -1,111 +0,0 @@ -# pylint: skip-file -import base64 -from io import BytesIO - -import numpy as np -import matplotlib.pyplot as plt -import matplotlib.tri as tri -import matplotlib.cm as cm -from mpl_toolkits.axes_grid1 import make_axes_locatable -from vtkmodules.vtkCommonDataModel import vtkPolyData - - -def visualize_triangles_with_scalars( - coords: np.ndarray, - triangles: list, - scalars: np.ma.core.MaskedArray, - polyline: vtkPolyData, - well_name: str = "Well", - y_scale_factor: int = 20, -) -> str: - x = coords[:, 0] - y = coords[:, 1] * y_scale_factor - - # Create triangulation - triang = tri.Triangulation(x, y, triangles) - - # Create the figure - fig, ax = plt.subplots(figsize=(8, 6), dpi=600) - - # Plot the triangulation with scalar values - tpc = ax.tripcolor(triang, scalars, shading="flat", cmap=plt.cm.viridis) - # Get the polyline coordinates - polyline_coords = np.array([polyline.GetPoint(i)[:3] for i in range(polyline.GetNumberOfPoints())]) - - # Calculate the cumulative distance along the polyline - polyline_distances = np.zeros(polyline_coords.shape[0]) - for i in range(1, polyline_coords.shape[0]): - polyline_distances[i] = polyline_distances[i - 1] + np.linalg.norm( - polyline_coords[i, :2] - polyline_coords[i - 1, :2] - ) - - polyline_x = polyline_distances - polyline_y = polyline_coords[:, 2] * y_scale_factor - - # Plot the polyline as a black line - ax.plot(polyline_x, polyline_y, color="black", linewidth=3) - # # Add text near the polyline - # mid_index = len(polyline_x) // 2 - # ax.text(polyline_x[mid_index], polyline_y[mid_index], well_name, color="black") - - # Set aspect ratio - ax.set_aspect("equal") - - # Remove axis - ax.axis("off") - - # Set axis limits - ax.set_xlim(x.min(), x.max()) - ax.set_ylim(y.min(), y.max()) - - # Adjust the margins - plt.tight_layout() - - # Save the plot to a file - buf = BytesIO() - plt.savefig(buf, bbox_inches="tight", pad_inches=0) - buf.seek(0) - im_base64 = base64.b64encode(buf.read()).decode("utf-8") - plt.close(fig) - # Show the plot - return im_base64 - - -# def plot_cells_with_scalar(triangulation: tri.Triangulation, scalar, polyline): -# fig, ax = plt.subplots(figsize=(8, 6), dpi=600) -# divider = make_axes_locatable(ax) - -# cmap = cm.get_cmap("viridis") -# tpc = ax.tripcolor(triangulation, facecolors=scalar, cmap=cmap) - -# # Adjust the margins -# plt.tight_layout() - -# # Remove axis -# ax.axis("off") - -# # Plot the polyline directly using x-axis as length and z-values from the polyline -# x_poly = [0] -# z_poly = [polyline[0][2]] - -# for i in range(1, len(polyline)): -# x0, y0, z0 = polyline[i - 1] -# x1, y1, z1 = polyline[i] -# segment_length = np.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2) -# x_poly.append(x_poly[-1] + segment_length) -# z_poly.append(-z1) - -# ax.plot(x_poly, z_poly, c="k", linewidth=2) - -# # Set the y-axis (z-axis in our case) limits -# min_z = np.min(triangulation.y) - 100 -# max_z = np.max(triangulation.y) + 100 -# ax.set_ylim(min_z, max_z) - -# buf = BytesIO() -# plt.savefig(buf, bbox_inches="tight", pad_inches=0) -# buf.seek(0) -# im_base64 = base64.b64encode(buf.read()).decode("utf-8") -# plt.close(fig) - -# return im_base64 diff --git a/backend/src/services/utils/vtk_utils.py b/backend/src/services/utils/vtk_utils.py deleted file mode 100644 index 340f6cf28..000000000 --- a/backend/src/services/utils/vtk_utils.py +++ /dev/null @@ -1,298 +0,0 @@ -# pylint: skip-file -# type: ignore -# for now -from typing import Optional, List -from dataclasses import dataclass -import numpy as np -import xtgeo - -# pylint: disable=no-name-in-module, -from vtkmodules.util.numpy_support import ( - numpy_to_vtk, - numpy_to_vtkIdTypeArray, - vtk_to_numpy, -) - -# pylint: disable=no-name-in-module, -from vtkmodules.vtkCommonCore import vtkPoints - -# pylint: disable=no-name-in-module, -from vtkmodules.vtkCommonDataModel import ( - vtkCellArray, - vtkDataSetAttributes, - vtkExplicitStructuredGrid, - vtkUnstructuredGrid, - vtkPolyData, - vtkPlane, -) - -# pylint: disable=no-name-in-module, -from vtkmodules.vtkFiltersCore import ( - vtkAppendPolyData, - vtkClipPolyData, - vtkExplicitStructuredGridCrop, - vtkExplicitStructuredGridToUnstructuredGrid, - vtkExtractCellsAlongPolyLine, - vtkPlaneCutter, - vtkUnstructuredGridToExplicitStructuredGrid, -) - -# pylint: disable=no-name-in-module, -from vtkmodules.vtkFiltersGeometry import vtkExplicitStructuredGridSurfaceFilter - -# pylint: disable=no-name-in-module, -from vtkmodules.vtkFiltersSources import vtkPolyLineSource - - -@dataclass -class VtkGridSurface: - polydata: vtkPolyData - original_cell_ids: np.ndarray - - -def _create_vtk_esgrid_from_verts_and_conn( - point_dims: np.ndarray, vertex_arr_np: np.ndarray, conn_arr_np: np.ndarray -) -> vtkExplicitStructuredGrid: - vertex_arr_np = vertex_arr_np.reshape(-1, 3) - points_vtkarr = numpy_to_vtk(vertex_arr_np, deep=1) - vtk_points = vtkPoints() - vtk_points.SetData(points_vtkarr) - - conn_idarr = numpy_to_vtkIdTypeArray(conn_arr_np, deep=1) - vtk_cell_array = vtkCellArray() - vtk_cell_array.SetData(8, conn_idarr) - - vtk_esgrid = vtkExplicitStructuredGrid() - vtk_esgrid.SetDimensions(point_dims) - vtk_esgrid.SetPoints(vtk_points) - vtk_esgrid.SetCells(vtk_cell_array) - - vtk_esgrid.ComputeFacesConnectivityFlagsArray() - - return vtk_esgrid - - -def xtgeo_grid_to_vtk_explicit_structured_grid( - xtg_grid: xtgeo.Grid, -) -> vtkExplicitStructuredGrid: - # Create geometry data suitable for use with VTK's explicit structured grid - # based on the specified xtgeo 3d grid - pt_dims, vertex_arr, conn_arr, inactive_arr = xtg_grid.get_vtk_esg_geometry_data() - vertex_arr[:, 2] *= -1 - - vtk_esgrid = _create_vtk_esgrid_from_verts_and_conn(pt_dims, vertex_arr, conn_arr) - - # Make sure we hide the inactive cells. - # First we let VTK allocate cell ghost array, then we obtain a numpy view - # on the array and write to that (we're actually modifying the native VTK array) - ghost_arr_vtk = vtk_esgrid.AllocateCellGhostArray() - ghost_arr_np = vtk_to_numpy(ghost_arr_vtk) - ghost_arr_np[inactive_arr] = vtkDataSetAttributes.HIDDENCELL - - return vtk_esgrid - - -def _calc_grid_surface(esgrid: vtkExplicitStructuredGrid) -> vtkPolyData: - surf_filter = vtkExplicitStructuredGridSurfaceFilter() - surf_filter.SetInputData(esgrid) - surf_filter.PassThroughCellIdsOn() - surf_filter.Update() - - polydata: vtkPolyData = surf_filter.GetOutput() - return polydata - - -def get_surface( - xtgeo_grid: xtgeo.Grid, -) -> VtkGridSurface: - es_grid = xtgeo_grid_to_vtk_explicit_structured_grid(xtgeo_grid) - polydata = _calc_grid_surface(es_grid) - - original_cell_indices_np = vtk_to_numpy(polydata.GetCellData().GetAbstractArray("vtkOriginalCellIds")) - return VtkGridSurface(polydata=polydata, original_cell_ids=original_cell_indices_np) - - -def get_scalar_values(xtgeo_grid_property: xtgeo.GridProperty, cell_ids: Optional[np.ndarray] = None) -> np.ndarray: - fill_value = 0.0 if not xtgeo_grid_property.isdiscrete else -1 - raw_scalar_np = xtgeo_grid_property.values.ravel(order="F") - raw_scalar_np.filled(fill_value) - - if cell_ids is not None: - return raw_scalar_np[cell_ids].astype(np.float32) - return raw_scalar_np.astype(np.float32) - - -def _vtk_esg_to_ug(vtk_esgrid: vtkExplicitStructuredGrid) -> vtkUnstructuredGrid: - convert_filter = vtkExplicitStructuredGridToUnstructuredGrid() - convert_filter.SetInputData(vtk_esgrid) - convert_filter.Update() - vtk_ugrid = convert_filter.GetOutput() - - return vtk_ugrid - - -def cut_along_polyline( - esgrid: vtkExplicitStructuredGrid, - polyline_xy: List[float], -) -> vtkPolyData: - num_points_in_polyline = int(len(polyline_xy) / 2) - - ugrid = _vtk_esg_to_ug(esgrid) - - # !!!!!!!!!!!!!! - # Requires VTK 9.2-ish - # ugrid = _extract_intersected_ugrid(ugrid, polyline_xy, 10.0) - - cutter_alg = vtkPlaneCutter() - cutter_alg.SetInputDataObject(ugrid) - - # cell_locator = vtkStaticCellLocator() - # cell_locator.SetDataSet(esgrid) - # cell_locator.BuildLocator() - - # box_clip_alg = vtkBoxClipDataSet() - # box_clip_alg.SetInputDataObject(ugrid) - - append_alg = vtkAppendPolyData() - - et_cut_s = 0.0 - et_clip_s = 0.0 - - for i in range(0, num_points_in_polyline - 1): - x_0 = polyline_xy[2 * i] - y_0 = polyline_xy[2 * i + 1] - x_1 = polyline_xy[2 * (i + 1)] - y_1 = polyline_xy[2 * (i + 1) + 1] - fwd_vec = np.array([x_1 - x_0, y_1 - y_0, 0.0]) - fwd_vec /= np.linalg.norm(fwd_vec) - right_vec = np.array([fwd_vec[1], -fwd_vec[0], 0]) - - # box_clip_alg.SetBoxClip(x_0, x_1, y_0, y_1, min_z, max_z) - # box_clip_alg.Update() - # clipped_ugrid = box_clip_alg.GetOutputDataObject(0) - - # polyline_bounds = _calc_polyline_bounds([x_0, y_0, x_1, y_1]) - # polyline_bounds.extend([min_z, max_z]) - # cell_ids = vtkIdList() - # cell_locator.FindCellsWithinBounds(polyline_bounds, cell_ids) - # print(f"{cell_ids.GetNumberOfIds()} {polyline_bounds=}") - - plane = vtkPlane() - plane.SetOrigin([x_0, y_0, 0]) - plane.SetNormal(right_vec) - - plane_0 = vtkPlane() - plane_0.SetOrigin([x_0, y_0, 0]) - plane_0.SetNormal(fwd_vec) - - plane_1 = vtkPlane() - plane_1.SetOrigin([x_1, y_1, 0]) - plane_1.SetNormal(-fwd_vec) - - cutter_alg.SetPlane(plane) - cutter_alg.Update() - - cut_surface_polydata = cutter_alg.GetOutput() - # print(f"{type(cut_surface_polydata)=}") - - # Used vtkPolyDataPlaneClipper earlier, but it seems that it doesn't - # maintain the original cell IDs that we need for the result mapping. - # May want to check up on any performance degradation! - clipper_0 = vtkClipPolyData() - clipper_0.SetInputDataObject(cut_surface_polydata) - clipper_0.SetClipFunction(plane_0) - clipper_0.Update() - clipped_polydata = clipper_0.GetOutput() - - clipper_1 = vtkClipPolyData() - clipper_1.SetInputDataObject(clipped_polydata) - clipper_1.SetClipFunction(plane_1) - clipper_1.Update() - clipped_polydata = clipper_1.GetOutput() - - append_alg.AddInputData(clipped_polydata) - - append_alg.Update() - comb_polydata = append_alg.GetOutput() - return comb_polydata - - -def flatten_sliced_grid(sliced_grid: vtkPolyData, polyline, original_cell_ids) -> vtkPolyData: - """Flatten the sliced grid to a 2D grid.""" - points = sliced_grid.GetPoints() - num_points = points.GetNumberOfPoints() - flattened_points = vtkPoints() - - for i in range(num_points): - point = np.array(points.GetPoint(i)) - min_dist = float("inf") - closest_point = np.array(polyline.GetPoint(0))[:2] - for j in range(polyline.GetNumberOfPoints() - 1): - p1 = np.array(polyline.GetPoint(j))[:2] - p2 = np.array(polyline.GetPoint(j + 1))[:2] - segment = p2 - p1 - segment_length = np.linalg.norm(segment) - segment_normalized = segment / segment_length - projection = np.dot(point[:2] - p1, segment_normalized) - if 0 <= projection <= segment_length: - projected_point = p1 + projection * segment_normalized - dist = np.linalg.norm(point[:2] - projected_point) - if dist < min_dist: - min_dist = dist - closest_point = projected_point - - flattened_points.InsertNextPoint( - ( - np.linalg.norm(closest_point - np.array(polyline.GetPoint(0))[:2]), - point[2], - 0, - ) - ) - - flattened_grid = vtkPolyData() - flattened_grid.SetPoints(flattened_points) - flattened_grid.SetPolys(sliced_grid.GetPolys()) - - # Transfer original cell IDs to the flattened grid - original_cell_ids_array = numpy_to_vtk(np.array(original_cell_ids)) - flattened_grid.GetCellData().AddArray(original_cell_ids_array) - flattened_grid.GetCellData().GetAbstractArray(0).SetName("vtkOriginalCellIds") - - return flattened_grid - - -def get_triangles(poly_data) -> List[List[int]]: - triangles = [] - num_cells = poly_data.GetNumberOfCells() - for i in range(num_cells): - cell = poly_data.GetCell(i) - if cell.GetNumberOfPoints() == 3: - triangles.append([cell.GetPointId(0), cell.GetPointId(1), cell.GetPointId(2)]) - return triangles - - -def create_polyline(polyline: List[List[float]]): - points = vtkPoints() - for point in polyline: - points.InsertNextPoint(point) - points.InsertNextPoint(point) - points.InsertNextPoint(point) - points.InsertNextPoint(point) - points.InsertNextPoint(point) - - polyline = vtkPolyLineSource() - polyline.SetPoints(points) - - polyline.Update() - return polyline.GetOutput() - - -def grid_to_numpy(flattened_grid): - points = flattened_grid.GetPoints() - num_points = points.GetNumberOfPoints() - coords = np.zeros((num_points, 2)) - - for i in range(num_points): - coords[i, 0], coords[i, 1], _ = points.GetPoint(i) - - return coords diff --git a/backend/src/services/vds_access/__init__.py b/backend/src/services/vds_access/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/tests/unit/__init__.py b/backend/tests/unit/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/tests/unit/services/__init__.py b/backend/tests/unit/services/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend_py/libs/core_utils/poetry.lock b/backend_py/libs/core_utils/poetry.lock new file mode 100644 index 000000000..f03c465b8 --- /dev/null +++ b/backend_py/libs/core_utils/poetry.lock @@ -0,0 +1,183 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + +[[package]] +name = "numpy" +version = "1.26.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, +] + +[[package]] +name = "pydantic" +version = "2.6.3" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.6.3-py3-none-any.whl", hash = "sha256:72c6034df47f46ccdf81869fddb81aade68056003900a8724a4f160700016a2a"}, + {file = "pydantic-2.6.3.tar.gz", hash = "sha256:e07805c4c7f5c6826e33a1d4c9d47950d7eaf34868e2690f8594d2e30241f11f"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.16.3" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.16.3" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, + {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, + {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, + {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, + {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, + {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, + {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, + {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, + {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, + {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, + {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, + {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, + {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, + {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "typing-extensions" +version = "4.10.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "94e824aa93b25684a6ae3f9bbff99c166a956ec1cbc59a6214133d9212d42d40" diff --git a/backend_py/libs/core_utils/pyproject.toml b/backend_py/libs/core_utils/pyproject.toml new file mode 100644 index 000000000..6045696ff --- /dev/null +++ b/backend_py/libs/core_utils/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "core-utils" +version = "0.0.1" +description = "Package with general Webviz utilities" +authors = ["R&T Equinor", "Ceetron Solutions AS"] +packages = [ { include = "webviz_pkg" } ] + +[tool.poetry.dependencies] +python = "^3.11" +numpy = "^1.24.1" +pydantic = "^2.3.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" \ No newline at end of file diff --git a/backend/src/services/utils/b64.py b/backend_py/libs/core_utils/webviz_pkg/core_utils/b64.py similarity index 100% rename from backend/src/services/utils/b64.py rename to backend_py/libs/core_utils/webviz_pkg/core_utils/b64.py diff --git a/backend/src/services/utils/perf_timer.py b/backend_py/libs/core_utils/webviz_pkg/core_utils/perf_timer.py similarity index 100% rename from backend/src/services/utils/perf_timer.py rename to backend_py/libs/core_utils/webviz_pkg/core_utils/perf_timer.py diff --git a/backend/.vscode/launch.json b/backend_py/primary/.vscode/launch.json similarity index 82% rename from backend/.vscode/launch.json rename to backend_py/primary/.vscode/launch.json index 6f7bb14f7..695c19216 100644 --- a/backend/.vscode/launch.json +++ b/backend_py/primary/.vscode/launch.json @@ -8,7 +8,7 @@ "pathMappings": [ { "localRoot": "${workspaceFolder}", - "remoteRoot": "/home/appuser/backend" + "remoteRoot": "/home/appuser/backend_py/primary" } ] } diff --git a/backend_py/primary/Dockerfile b/backend_py/primary/Dockerfile new file mode 100644 index 000000000..c41b46a93 --- /dev/null +++ b/backend_py/primary/Dockerfile @@ -0,0 +1,27 @@ +FROM python:3.11-slim@sha256:ad2c4e5884418404c5289acad4a471dde8500e24ba57ad574cdcae46523e507a + +RUN useradd --create-home --uid 1234 appuser # Changing to non-root user early + +USER 1234 + +ENV PATH="${PATH}:/home/appuser/.local/bin" + +RUN python3 -m pip install --user pipx +RUN python3 -m pipx ensurepath +RUN pipx install poetry==1.8.2 + +ENV VIRTUAL_ENV=/home/appuser/venv +RUN python3 -m venv $VIRTUAL_ENV +ENV PATH="$VIRTUAL_ENV/bin:$PATH" + +WORKDIR /home/appuser/backend_py/primary + +COPY --chown=appuser ./backend_py/primary/pyproject.toml /home/appuser/backend_py/primary/ +COPY --chown=appuser ./backend_py/primary/poetry.lock /home/appuser/backend_py/primary/ +RUN poetry install --only main --no-root --no-directory + +COPY --chown=appuser ./backend_py/libs /home/appuser/backend_py/libs +COPY --chown=appuser ./backend_py/primary /home/appuser/backend_py/primary +RUN poetry install --only main + +CMD exec uvicorn --proxy-headers --host=0.0.0.0 primary.main:app diff --git a/backend/README.md b/backend_py/primary/README.md similarity index 100% rename from backend/README.md rename to backend_py/primary/README.md diff --git a/backend/poetry.lock b/backend_py/primary/poetry.lock similarity index 95% rename from backend/poetry.lock rename to backend_py/primary/poetry.lock index 62b40f8bd..1f65263f7 100644 --- a/backend/poetry.lock +++ b/backend_py/primary/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "annotated-types" @@ -594,6 +594,23 @@ mypy = ["contourpy[bokeh]", "docutils-stubs", "mypy (==0.991)", "types-Pillow"] test = ["Pillow", "matplotlib", "pytest"] test-no-images = ["pytest"] +[[package]] +name = "core-utils" +version = "0.0.1" +description = "Package with general Webviz utilities" +optional = false +python-versions = "^3.11" +files = [] +develop = true + +[package.dependencies] +numpy = "^1.24.1" +pydantic = "^2.3.0" + +[package.source] +type = "directory" +url = "../libs/core_utils" + [[package]] name = "cryptography" version = "39.0.0" @@ -1322,6 +1339,98 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "mmh3" +version = "4.1.0" +description = "Python extension for MurmurHash (MurmurHash3), a set of fast and robust hash functions." +optional = false +python-versions = "*" +files = [ + {file = "mmh3-4.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be5ac76a8b0cd8095784e51e4c1c9c318c19edcd1709a06eb14979c8d850c31a"}, + {file = "mmh3-4.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98a49121afdfab67cd80e912b36404139d7deceb6773a83620137aaa0da5714c"}, + {file = "mmh3-4.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5259ac0535874366e7d1a5423ef746e0d36a9e3c14509ce6511614bdc5a7ef5b"}, + {file = "mmh3-4.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5950827ca0453a2be357696da509ab39646044e3fa15cad364eb65d78797437"}, + {file = "mmh3-4.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1dd0f652ae99585b9dd26de458e5f08571522f0402155809fd1dc8852a613a39"}, + {file = "mmh3-4.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99d25548070942fab1e4a6f04d1626d67e66d0b81ed6571ecfca511f3edf07e6"}, + {file = "mmh3-4.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53db8d9bad3cb66c8f35cbc894f336273f63489ce4ac416634932e3cbe79eb5b"}, + {file = "mmh3-4.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75da0f615eb55295a437264cc0b736753f830b09d102aa4c2a7d719bc445ec05"}, + {file = "mmh3-4.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b926b07fd678ea84b3a2afc1fa22ce50aeb627839c44382f3d0291e945621e1a"}, + {file = "mmh3-4.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c5b053334f9b0af8559d6da9dc72cef0a65b325ebb3e630c680012323c950bb6"}, + {file = "mmh3-4.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5bf33dc43cd6de2cb86e0aa73a1cc6530f557854bbbe5d59f41ef6de2e353d7b"}, + {file = "mmh3-4.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fa7eacd2b830727ba3dd65a365bed8a5c992ecd0c8348cf39a05cc77d22f4970"}, + {file = "mmh3-4.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:42dfd6742b9e3eec599f85270617debfa0bbb913c545bb980c8a4fa7b2d047da"}, + {file = "mmh3-4.1.0-cp310-cp310-win32.whl", hash = "sha256:2974ad343f0d39dcc88e93ee6afa96cedc35a9883bc067febd7ff736e207fa47"}, + {file = "mmh3-4.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:74699a8984ded645c1a24d6078351a056f5a5f1fe5838870412a68ac5e28d865"}, + {file = "mmh3-4.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f0dc874cedc23d46fc488a987faa6ad08ffa79e44fb08e3cd4d4cf2877c00a00"}, + {file = "mmh3-4.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3280a463855b0eae64b681cd5b9ddd9464b73f81151e87bb7c91a811d25619e6"}, + {file = "mmh3-4.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:97ac57c6c3301769e757d444fa7c973ceb002cb66534b39cbab5e38de61cd896"}, + {file = "mmh3-4.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7b6502cdb4dbd880244818ab363c8770a48cdccecf6d729ade0241b736b5ec0"}, + {file = "mmh3-4.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52ba2da04671a9621580ddabf72f06f0e72c1c9c3b7b608849b58b11080d8f14"}, + {file = "mmh3-4.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a5fef4c4ecc782e6e43fbeab09cff1bac82c998a1773d3a5ee6a3605cde343e"}, + {file = "mmh3-4.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5135358a7e00991f73b88cdc8eda5203bf9de22120d10a834c5761dbeb07dd13"}, + {file = "mmh3-4.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cff9ae76a54f7c6fe0167c9c4028c12c1f6de52d68a31d11b6790bb2ae685560"}, + {file = "mmh3-4.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f02576a4d106d7830ca90278868bf0983554dd69183b7bbe09f2fcd51cf54f"}, + {file = "mmh3-4.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:073d57425a23721730d3ff5485e2da489dd3c90b04e86243dd7211f889898106"}, + {file = "mmh3-4.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:71e32ddec7f573a1a0feb8d2cf2af474c50ec21e7a8263026e8d3b4b629805db"}, + {file = "mmh3-4.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7cbb20b29d57e76a58b40fd8b13a9130db495a12d678d651b459bf61c0714cea"}, + {file = "mmh3-4.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:a42ad267e131d7847076bb7e31050f6c4378cd38e8f1bf7a0edd32f30224d5c9"}, + {file = "mmh3-4.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a013979fc9390abadc445ea2527426a0e7a4495c19b74589204f9b71bcaafeb"}, + {file = "mmh3-4.1.0-cp311-cp311-win32.whl", hash = "sha256:1d3b1cdad7c71b7b88966301789a478af142bddcb3a2bee563f7a7d40519a00f"}, + {file = "mmh3-4.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0dc6dc32eb03727467da8e17deffe004fbb65e8b5ee2b502d36250d7a3f4e2ec"}, + {file = "mmh3-4.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9ae3a5c1b32dda121c7dc26f9597ef7b01b4c56a98319a7fe86c35b8bc459ae6"}, + {file = "mmh3-4.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0033d60c7939168ef65ddc396611077a7268bde024f2c23bdc283a19123f9e9c"}, + {file = "mmh3-4.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d6af3e2287644b2b08b5924ed3a88c97b87b44ad08e79ca9f93d3470a54a41c5"}, + {file = "mmh3-4.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d82eb4defa245e02bb0b0dc4f1e7ee284f8d212633389c91f7fba99ba993f0a2"}, + {file = "mmh3-4.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba245e94b8d54765e14c2d7b6214e832557e7856d5183bc522e17884cab2f45d"}, + {file = "mmh3-4.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb04e2feeabaad6231e89cd43b3d01a4403579aa792c9ab6fdeef45cc58d4ec0"}, + {file = "mmh3-4.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e3b1a27def545ce11e36158ba5d5390cdbc300cfe456a942cc89d649cf7e3b2"}, + {file = "mmh3-4.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce0ab79ff736d7044e5e9b3bfe73958a55f79a4ae672e6213e92492ad5e734d5"}, + {file = "mmh3-4.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b02268be6e0a8eeb8a924d7db85f28e47344f35c438c1e149878bb1c47b1cd3"}, + {file = "mmh3-4.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:deb887f5fcdaf57cf646b1e062d56b06ef2f23421c80885fce18b37143cba828"}, + {file = "mmh3-4.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99dd564e9e2b512eb117bd0cbf0f79a50c45d961c2a02402787d581cec5448d5"}, + {file = "mmh3-4.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:08373082dfaa38fe97aa78753d1efd21a1969e51079056ff552e687764eafdfe"}, + {file = "mmh3-4.1.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:54b9c6a2ea571b714e4fe28d3e4e2db37abfd03c787a58074ea21ee9a8fd1740"}, + {file = "mmh3-4.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a7b1edf24c69e3513f879722b97ca85e52f9032f24a52284746877f6a7304086"}, + {file = "mmh3-4.1.0-cp312-cp312-win32.whl", hash = "sha256:411da64b951f635e1e2284b71d81a5a83580cea24994b328f8910d40bed67276"}, + {file = "mmh3-4.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bebc3ecb6ba18292e3d40c8712482b4477abd6981c2ebf0e60869bd90f8ac3a9"}, + {file = "mmh3-4.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:168473dd608ade6a8d2ba069600b35199a9af837d96177d3088ca91f2b3798e3"}, + {file = "mmh3-4.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:372f4b7e1dcde175507640679a2a8790185bb71f3640fc28a4690f73da986a3b"}, + {file = "mmh3-4.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:438584b97f6fe13e944faf590c90fc127682b57ae969f73334040d9fa1c7ffa5"}, + {file = "mmh3-4.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6e27931b232fc676675fac8641c6ec6b596daa64d82170e8597f5a5b8bdcd3b6"}, + {file = "mmh3-4.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:571a92bad859d7b0330e47cfd1850b76c39b615a8d8e7aa5853c1f971fd0c4b1"}, + {file = "mmh3-4.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a69d6afe3190fa08f9e3a58e5145549f71f1f3fff27bd0800313426929c7068"}, + {file = "mmh3-4.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afb127be0be946b7630220908dbea0cee0d9d3c583fa9114a07156f98566dc28"}, + {file = "mmh3-4.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:940d86522f36348ef1a494cbf7248ab3f4a1638b84b59e6c9e90408bd11ad729"}, + {file = "mmh3-4.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3dcccc4935686619a8e3d1f7b6e97e3bd89a4a796247930ee97d35ea1a39341"}, + {file = "mmh3-4.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01bb9b90d61854dfc2407c5e5192bfb47222d74f29d140cb2dd2a69f2353f7cc"}, + {file = "mmh3-4.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bcb1b8b951a2c0b0fb8a5426c62a22557e2ffc52539e0a7cc46eb667b5d606a9"}, + {file = "mmh3-4.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6477a05d5e5ab3168e82e8b106e316210ac954134f46ec529356607900aea82a"}, + {file = "mmh3-4.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:da5892287e5bea6977364b15712a2573c16d134bc5fdcdd4cf460006cf849278"}, + {file = "mmh3-4.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:99180d7fd2327a6fffbaff270f760576839dc6ee66d045fa3a450f3490fda7f5"}, + {file = "mmh3-4.1.0-cp38-cp38-win32.whl", hash = "sha256:9b0d4f3949913a9f9a8fb1bb4cc6ecd52879730aab5ff8c5a3d8f5b593594b73"}, + {file = "mmh3-4.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:598c352da1d945108aee0c3c3cfdd0e9b3edef74108f53b49d481d3990402169"}, + {file = "mmh3-4.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:475d6d1445dd080f18f0f766277e1237fa2914e5fe3307a3b2a3044f30892103"}, + {file = "mmh3-4.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5ca07c41e6a2880991431ac717c2a049056fff497651a76e26fc22224e8b5732"}, + {file = "mmh3-4.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ebe052fef4bbe30c0548d12ee46d09f1b69035ca5208a7075e55adfe091be44"}, + {file = "mmh3-4.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaefd42e85afb70f2b855a011f7b4d8a3c7e19c3f2681fa13118e4d8627378c5"}, + {file = "mmh3-4.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0ae43caae5a47afe1b63a1ae3f0986dde54b5fb2d6c29786adbfb8edc9edfb"}, + {file = "mmh3-4.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6218666f74c8c013c221e7f5f8a693ac9cf68e5ac9a03f2373b32d77c48904de"}, + {file = "mmh3-4.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac59294a536ba447b5037f62d8367d7d93b696f80671c2c45645fa9f1109413c"}, + {file = "mmh3-4.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:086844830fcd1e5c84fec7017ea1ee8491487cfc877847d96f86f68881569d2e"}, + {file = "mmh3-4.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e42b38fad664f56f77f6fbca22d08450f2464baa68acdbf24841bf900eb98e87"}, + {file = "mmh3-4.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d08b790a63a9a1cde3b5d7d733ed97d4eb884bfbc92f075a091652d6bfd7709a"}, + {file = "mmh3-4.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:73ea4cc55e8aea28c86799ecacebca09e5f86500414870a8abaedfcbaf74d288"}, + {file = "mmh3-4.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f90938ff137130e47bcec8dc1f4ceb02f10178c766e2ef58a9f657ff1f62d124"}, + {file = "mmh3-4.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:aa1f13e94b8631c8cd53259250556edcf1de71738936b60febba95750d9632bd"}, + {file = "mmh3-4.1.0-cp39-cp39-win32.whl", hash = "sha256:a3b680b471c181490cf82da2142029edb4298e1bdfcb67c76922dedef789868d"}, + {file = "mmh3-4.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:fefef92e9c544a8dbc08f77a8d1b6d48006a750c4375bbcd5ff8199d761e263b"}, + {file = "mmh3-4.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:8e2c1f6a2b41723a4f82bd5a762a777836d29d664fc0095f17910bea0adfd4a6"}, + {file = "mmh3-4.1.0.tar.gz", hash = "sha256:a1cf25348b9acd229dda464a094d6170f47d2850a1fcb762a3b6172d2ce6ca4a"}, +] + +[package.extras] +test = ["mypy (>=1.0)", "pytest (>=7.0.0)"] + [[package]] name = "msal" version = "1.20.0" @@ -2236,30 +2345,20 @@ redis = ["redis"] tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)"] [[package]] -name = "psutil" -version = "5.9.5" -description = "Cross-platform lib for process and system monitoring in Python." +name = "pottery" +version = "3.0.0" +description = "Redis for Humans." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.7, <4" files = [ - {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"}, - {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"}, - {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"}, - {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"}, - {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"}, - {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"}, - {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"}, - {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"}, - {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"}, - {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"}, - {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"}, - {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"}, - {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"}, - {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"}, + {file = "pottery-3.0.0-py3-none-any.whl", hash = "sha256:0190323bbb1289d40c5cd683feb04c4b8cff76a6c723f3ded9137c8bcc9fb5f8"}, + {file = "pottery-3.0.0.tar.gz", hash = "sha256:adda303e9357442bcac1d4c7f86aa7deec855e0190c101d09448afbcf5676a74"}, ] -[package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +[package.dependencies] +mmh3 = "*" +redis = ">=4,<5" +typing-extensions = "*" [[package]] name = "py-cpuinfo" @@ -3208,43 +3307,6 @@ h11 = ">=0.8" [package.extras] standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] -[[package]] -name = "vtk" -version = "9.2.6" -description = "VTK is an open-source toolkit for 3D computer graphics, image processing, and visualization" -optional = false -python-versions = "*" -files = [ - {file = "vtk-9.2.6-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:acf8b0e0a2b51b8aa36cee1ea1ba0b73c565871efaa14cf2606d9bef36feba3a"}, - {file = "vtk-9.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ec9827287f1743c736ea9b51572d20dcd15a065170808f97408eebd404275b4"}, - {file = "vtk-9.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3473f3b3dc919d3e2ef0cc9927654731941fd7b79d3dcaa343cdaff3e4e40838"}, - {file = "vtk-9.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:37561b19b4b70c7034d9e689238560d7afec49ff89704b9bb3c9bb89a90ee54e"}, - {file = "vtk-9.2.6-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:9289f6ee5432f0a00aeb7b674d7ca03054bc50fa6c74126751f8b19f931f52fc"}, - {file = "vtk-9.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0b947a33a7c5562ac4d9c8dce389f4ed720cc2559389048993ae45cbed3bbeb1"}, - {file = "vtk-9.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87930115c8067a2d482beebc48a50fcacdc0154d8d7c763471a9be8b5eb76cc3"}, - {file = "vtk-9.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:6c3ca0663f251fbd6e26d93294801ceee6c3cc329f6070dccde3b68046ab9ee7"}, - {file = "vtk-9.2.6-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:86c548da22a0bd9ce9e060364925e8fa0a551064f9660ae1d486ac3118ffb770"}, - {file = "vtk-9.2.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7622e98b3590bf909f056a1bad55575760727fd673c3e8e224134d52b11d00d"}, - {file = "vtk-9.2.6-cp36-cp36m-win_amd64.whl", hash = "sha256:bb28432277136774e91eb1084a8f5f1c4c952d4e74f74626a16ac6e199eba5c5"}, - {file = "vtk-9.2.6-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:feb9d211583d7b1dd45ca6616bf2f9622d0eadf9e3084f53d20de819e5808d42"}, - {file = "vtk-9.2.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3bc1123b6f2f3746d35325cf1a48630c8f49a3516c9970a5bdea15823909bbca"}, - {file = "vtk-9.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:78103568e97d947026cd39a70e6719277d7341f984c06abaec64d3429e200a6f"}, - {file = "vtk-9.2.6-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8236e00d0ad24730f4b783bbc038108426e6a78c892fd6ae9a8e8eb846f3e8e3"}, - {file = "vtk-9.2.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:affbb15762bcb6d9632a668ec53c6a3102d4f6c14c4178f01489c0b711114521"}, - {file = "vtk-9.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:f9d3450c00ced28f942a0a7dc5f27a667cf6b171d9ef5a090cb7c8e21dd1a121"}, - {file = "vtk-9.2.6-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9b396909a4372d3be4a01fde4a0af4f3e410742d73d185982c6f48a61090ebfe"}, - {file = "vtk-9.2.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9d41a18bbd6fac18a814bbaeeb615044da036afc2bd98cdf7ea52853fd1ef950"}, - {file = "vtk-9.2.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79991dccbc483a8e5d23e5d3ae1e358fee0526fe8f710d07868ecae58c6b9535"}, - {file = "vtk-9.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:2d905a686ee1b28dd2ac3c3595a3fbcbd171e4b1f9aabac3c8019c6f3a4f8157"}, -] - -[package.dependencies] -matplotlib = ">=2.0.0" - -[package.extras] -numpy = ["numpy (>=1.9)"] -web = ["wslink (>=1.0.4)"] - [[package]] name = "wrapt" version = "1.15.0" @@ -3390,4 +3452,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "915e85bde4cdc6ef0a517edef88864703109e1623a43b2f3f4ea1ad99d7af5bb" +content-hash = "0138b0a8902575704f558577c37e40fc9128819a11bde186e407ce839da40cf3" diff --git a/backend/src/__init__.py b/backend_py/primary/primary/__init__.py similarity index 100% rename from backend/src/__init__.py rename to backend_py/primary/primary/__init__.py diff --git a/backend/src/backend/auth/auth_helper.py b/backend_py/primary/primary/auth/auth_helper.py similarity index 98% rename from backend/src/backend/auth/auth_helper.py rename to backend_py/primary/primary/auth/auth_helper.py index 9d5678df6..58846258f 100644 --- a/backend/src/backend/auth/auth_helper.py +++ b/backend_py/primary/primary/auth/auth_helper.py @@ -1,5 +1,5 @@ -import os import base64 +import os import time from typing import List, Optional @@ -8,10 +8,10 @@ import starsessions from fastapi import APIRouter, Request, Response from fastapi.responses import RedirectResponse +from webviz_pkg.core_utils.perf_timer import PerfTimer -from src.services.utils.authenticated_user import AuthenticatedUser -from src.services.utils.perf_timer import PerfTimer -from src import config +from primary import config +from primary.services.utils.authenticated_user import AuthenticatedUser class AuthHelper: diff --git a/backend/src/backend/auth/enforce_logged_in_middleware.py b/backend_py/primary/primary/auth/enforce_logged_in_middleware.py similarity index 98% rename from backend/src/backend/auth/enforce_logged_in_middleware.py rename to backend_py/primary/primary/auth/enforce_logged_in_middleware.py index 255fe5649..57a6e27e2 100644 --- a/backend/src/backend/auth/enforce_logged_in_middleware.py +++ b/backend_py/primary/primary/auth/enforce_logged_in_middleware.py @@ -6,7 +6,7 @@ from fastapi.responses import PlainTextResponse, RedirectResponse from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint -from src.backend.auth.auth_helper import AuthHelper +from .auth_helper import AuthHelper class EnforceLoggedInMiddleware(BaseHTTPMiddleware): diff --git a/backend/src/config.py b/backend_py/primary/primary/config.py similarity index 100% rename from backend/src/config.py rename to backend_py/primary/primary/config.py diff --git a/backend_py/primary/primary/main.py b/backend_py/primary/primary/main.py new file mode 100644 index 000000000..c5e6f030b --- /dev/null +++ b/backend_py/primary/primary/main.py @@ -0,0 +1,122 @@ +import datetime +import logging +import os + +from fastapi import FastAPI +from fastapi.responses import ORJSONResponse +from fastapi.routing import APIRoute +from starsessions import SessionMiddleware +from starsessions.stores.redis import RedisStore +from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware + +from primary.auth.auth_helper import AuthHelper +from primary.auth.enforce_logged_in_middleware import EnforceLoggedInMiddleware +from primary.middleware.add_process_time_to_server_timing_middleware import AddProcessTimeToServerTimingMiddleware +from primary.routers.correlations.router import router as correlations_router +from primary.routers.dev.router import router as dev_router +from primary.routers.explore import router as explore_router +from primary.routers.general import router as general_router +from primary.routers.graph.router import router as graph_router +from primary.routers.grid3d.router import router as grid3d_router +from primary.routers.grid3d.router_vtk import router as grid3d_router_vtk +from primary.routers.inplace_volumetrics.router import router as inplace_volumetrics_router +from primary.routers.observations.router import router as observations_router +from primary.routers.parameters.router import router as parameters_router +from primary.routers.polygons.router import router as polygons_router +from primary.routers.pvt.router import router as pvt_router +from primary.routers.rft.router import router as rft_router +from primary.routers.seismic.router import router as seismic_router +from primary.routers.surface.router import router as surface_router +from primary.routers.timeseries.router import router as timeseries_router +from primary.routers.well.router import router as well_router +from primary.routers.well_completions.router import router as well_completions_router +from primary.utils.azure_monitor_setup import setup_azure_monitor_telemetry +from primary.utils.exception_handlers import configure_service_level_exception_handlers +from primary.utils.exception_handlers import override_default_fastapi_exception_handlers +from primary.utils.logging_setup import ensure_console_log_handler_is_configured, setup_normal_log_levels + +from . import config + + +ensure_console_log_handler_is_configured() +setup_normal_log_levels() + +# temporarily set some loggers to DEBUG +# logging.getLogger().setLevel(logging.DEBUG) +logging.getLogger("primary.services.sumo_access").setLevel(logging.DEBUG) +logging.getLogger("primary.services.user_session_manager").setLevel(logging.DEBUG) +logging.getLogger("primary.routers.dev").setLevel(logging.DEBUG) + +LOGGER = logging.getLogger(__name__) + + +def custom_generate_unique_id(route: APIRoute) -> str: + return f"{route.name}" + + +app = FastAPI( + generate_unique_id_function=custom_generate_unique_id, + root_path="/api", + default_response_class=ORJSONResponse, +) + +if os.environ.get("APPLICATIONINSIGHTS_CONNECTION_STRING"): + LOGGER.info("Configuring Azure Monitor telemetry for primary backend") + setup_azure_monitor_telemetry(app) +else: + LOGGER.warning("Skipping telemetry configuration, APPLICATIONINSIGHTS_CONNECTION_STRING env variable not set.") + + +# The tags we add here will determine the name of the frontend api service for our endpoints as well as +# providing some grouping when viewing the openapi documentation. +app.include_router(explore_router, tags=["explore"]) +app.include_router(timeseries_router, prefix="/timeseries", tags=["timeseries"]) +app.include_router(inplace_volumetrics_router, prefix="/inplace_volumetrics", tags=["inplace_volumetrics"]) +app.include_router(surface_router, prefix="/surface", tags=["surface"]) +app.include_router(parameters_router, prefix="/parameters", tags=["parameters"]) +app.include_router(correlations_router, prefix="/correlations", tags=["correlations"]) +app.include_router(grid3d_router, prefix="/grid3d", tags=["grid3d"]) +app.include_router(grid3d_router_vtk, prefix="/grid3d", tags=["grid3d"]) +app.include_router(pvt_router, prefix="/pvt", tags=["pvt"]) +app.include_router(well_completions_router, prefix="/well_completions", tags=["well_completions"]) +app.include_router(well_router, prefix="/well", tags=["well"]) +app.include_router(seismic_router, prefix="/seismic", tags=["seismic"]) +app.include_router(polygons_router, prefix="/polygons", tags=["polygons"]) +app.include_router(graph_router, prefix="/graph", tags=["graph"]) +app.include_router(observations_router, prefix="/observations", tags=["observations"]) +app.include_router(rft_router, prefix="/rft", tags=["rft"]) +app.include_router(dev_router, prefix="/dev", tags=["dev"], include_in_schema=False) + +auth_helper = AuthHelper() +app.include_router(auth_helper.router) +app.include_router(general_router) + +configure_service_level_exception_handlers(app) +override_default_fastapi_exception_handlers(app) + + +# This middleware instance approximately measures execution time of the route handler itself +app.add_middleware(AddProcessTimeToServerTimingMiddleware, metric_name="total-exec-route") + +# Add out custom middleware to enforce that user is logged in +# Also redirects to /login endpoint for some select paths +unprotected_paths = ["/logged_in_user", "/alive", "/openapi.json"] +paths_redirected_to_login = ["/", "/alive_protected"] +app.add_middleware( + EnforceLoggedInMiddleware, + unprotected_paths=unprotected_paths, + paths_redirected_to_login=paths_redirected_to_login, +) + +session_store = RedisStore(config.REDIS_USER_SESSION_URL, prefix="user-auth:") +app.add_middleware(SessionMiddleware, store=session_store) + +app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*") + +# This middleware instance measures execution time of the endpoints, including the cost of other middleware +app.add_middleware(AddProcessTimeToServerTimingMiddleware, metric_name="total") + + +@app.get("/") +async def root() -> str: + return f"Primary backend is alive at this time: {datetime.datetime.now()}" diff --git a/backend/src/backend/utils/add_process_time_to_server_timing_middleware.py b/backend_py/primary/primary/middleware/add_process_time_to_server_timing_middleware.py similarity index 100% rename from backend/src/backend/utils/add_process_time_to_server_timing_middleware.py rename to backend_py/primary/primary/middleware/add_process_time_to_server_timing_middleware.py diff --git a/backend/src/backend/__init__.py b/backend_py/primary/primary/routers/__init__.py similarity index 100% rename from backend/src/backend/__init__.py rename to backend_py/primary/primary/routers/__init__.py diff --git a/backend/src/backend/auth/__init__.py b/backend_py/primary/primary/routers/correlations/__init__.py similarity index 100% rename from backend/src/backend/auth/__init__.py rename to backend_py/primary/primary/routers/correlations/__init__.py diff --git a/backend/src/backend/primary/routers/correlations/router.py b/backend_py/primary/primary/routers/correlations/router.py similarity index 100% rename from backend/src/backend/primary/routers/correlations/router.py rename to backend_py/primary/primary/routers/correlations/router.py diff --git a/backend_py/primary/primary/routers/dev/router.py b/backend_py/primary/primary/routers/dev/router.py new file mode 100644 index 000000000..5b859b3a3 --- /dev/null +++ b/backend_py/primary/primary/routers/dev/router.py @@ -0,0 +1,171 @@ +import asyncio +import logging +from typing import Annotated + +import httpx +from fastapi import APIRouter, Depends, HTTPException, Query, Path + +from primary.auth.auth_helper import AuthenticatedUser, AuthHelper +from primary.services.user_session_manager.user_session_manager import UserSessionManager +from primary.services.user_session_manager.user_session_manager import UserComponent +from primary.services.user_session_manager.user_session_manager import _USER_SESSION_DEFS +from primary.services.user_session_manager._radix_helpers import create_new_radix_job, RadixResourceRequests +from primary.services.user_session_manager._radix_helpers import get_all_radix_jobs, get_radix_job_state +from primary.services.user_session_manager._radix_helpers import delete_all_radix_jobs +from primary.services.user_session_manager._user_session_directory import UserSessionDirectory +from primary.services.user_session_manager._background_tasks import run_in_background_task + +LOGGER = logging.getLogger(__name__) + + +router = APIRouter() + + +@router.get("/usersession/{user_component}/call") +async def usersession_call( + authenticated_user: Annotated[AuthenticatedUser, Depends(AuthHelper.get_authenticated_user)], + user_component: Annotated[UserComponent, Path(description="User session component")], + instance_str: Annotated[str, Query(description="Instance string")] = "myInst", +) -> str: + LOGGER.debug(f"usersession_call() {user_component=}, {instance_str=}") + + manager = UserSessionManager(authenticated_user.get_user_id()) + session_base_url = await manager.get_or_create_session_async(user_component, instance_str) + if session_base_url is None: + LOGGER.error("Failed to get user session URL") + raise HTTPException(status_code=500, detail="Failed to get user session URL") + + endpoint = f"{session_base_url}/dowork?duration=5" + + LOGGER.debug("======================") + LOGGER.debug(f"{session_base_url=}") + LOGGER.debug(f"{endpoint=}") + LOGGER.debug("======================") + + LOGGER.debug(f"before call to: {endpoint=}") + + async with httpx.AsyncClient(timeout=30) as client: + response = await client.get(endpoint) + response.raise_for_status() + + LOGGER.debug(f"after call to: {endpoint=}") + + resp_text = response.text + LOGGER.debug(f"{type(resp_text)=}") + LOGGER.debug(f"{resp_text=}") + + return resp_text + + +@router.get("/usersession/{user_component}/radixlist") +async def usersession_radixlist(user_component: UserComponent) -> str: + LOGGER.debug(f"usersession_radixlist() {user_component=}") + + session_def = _USER_SESSION_DEFS[user_component] + + job_list = await get_all_radix_jobs(session_def.job_component_name, session_def.port) + LOGGER.debug("---") + LOGGER.debug(job_list) + LOGGER.debug("---") + return str(job_list) + + +@router.get("/usersession/{user_component}/radixcreate") +async def usersession_radixcreate(user_component: UserComponent) -> str: + LOGGER.debug(f"usersession_radixcreate() {user_component=}") + + session_def = _USER_SESSION_DEFS[user_component] + + resource_req = RadixResourceRequests(cpu="50m", memory="100Mi") + new_radix_job_name = await create_new_radix_job(session_def.job_component_name, session_def.port, resource_req) + LOGGER.debug(f"Created new job: {new_radix_job_name=}") + if new_radix_job_name is None: + return "Failed to create new job" + + LOGGER.debug(f"Polling job until receiving running status: {new_radix_job_name=}") + max_state_calls = 20 + for _i in range(max_state_calls): + radix_job_state = await get_radix_job_state( + session_def.job_component_name, session_def.port, new_radix_job_name + ) + session_status = radix_job_state.status if radix_job_state else "N/A" + LOGGER.debug(f"Status: {session_status=}") + await asyncio.sleep(0.1) + + return str(radix_job_state) + + +@router.get("/usersession/{user_component}/radixdelete") +async def usersession_radixdelete(user_component: UserComponent) -> str: + LOGGER.debug(f"usersession_radixdelete() {user_component=}") + + session_def = _USER_SESSION_DEFS[user_component] + + await delete_all_radix_jobs(session_def.job_component_name, session_def.port) + return "Delete done" + + +@router.get("/usersession/dirlist") +async def usersession_dirlist( + authenticated_user: Annotated[AuthenticatedUser, Depends(AuthHelper.get_authenticated_user)], + user_component: UserComponent | None = None, +) -> str: + LOGGER.debug(f"usersession_dirlist() {user_component=}") + + job_component_name: str | None = None + if user_component is not None: + job_component_name = _USER_SESSION_DEFS[user_component].job_component_name + + session_dir = UserSessionDirectory(authenticated_user.get_user_id()) + session_info_arr = session_dir.get_session_info_arr(job_component_name) + + resp_text = "" + + LOGGER.debug("======================") + for session_info in session_info_arr: + LOGGER.debug(f"{session_info=}") + resp_text += str(session_info) + "\n" + LOGGER.debug("======================") + + return resp_text + + +@router.get("/usersession/dirdel") +async def usersession_dirdel( + authenticated_user: Annotated[AuthenticatedUser, Depends(AuthHelper.get_authenticated_user)], + user_component: UserComponent | None = None, +) -> str: + LOGGER.debug(f"usersession_dirdel() {user_component=}") + + job_component_name: str | None = None + if user_component is not None: + job_component_name = _USER_SESSION_DEFS[user_component].job_component_name + + session_dir = UserSessionDirectory(authenticated_user.get_user_id()) + session_dir.delete_session_info(job_component_name) + + session_info_arr = session_dir.get_session_info_arr(None) + LOGGER.debug("======================") + for session_info in session_info_arr: + LOGGER.debug(f"{session_info=}") + LOGGER.debug("======================") + + return "Session info deleted" + + +@router.get("/bgtask") +async def bgtask() -> str: + LOGGER.debug(f"bgtask() - start") + + async def funcThatThrows() -> None: + raise ValueError("This is a test error") + + async def funcThatLogs(msg: str) -> None: + LOGGER.debug(f"This is a test log {msg=}") + + run_in_background_task(funcThatThrows()) + run_in_background_task(funcThatLogs(msg="HELO HELLO")) + + LOGGER.debug(f"bgtask() - done") + + return "Background tasks were run" diff --git a/backend/src/backend/primary/routers/explore.py b/backend_py/primary/primary/routers/explore.py similarity index 80% rename from backend/src/backend/primary/routers/explore.py rename to backend_py/primary/primary/routers/explore.py index 8838100c5..b2ba82811 100644 --- a/backend/src/backend/primary/routers/explore.py +++ b/backend_py/primary/primary/routers/explore.py @@ -3,10 +3,10 @@ from fastapi import APIRouter, Depends, Path, Query from pydantic import BaseModel -from src.backend.auth.auth_helper import AuthHelper -from src.services.sumo_access.sumo_explore import SumoExplore -from src.services.utils.authenticated_user import AuthenticatedUser -from src.services.sumo_access._helpers import SumoEnsemble +from primary.auth.auth_helper import AuthHelper +from primary.services.sumo_access.sumo_explore import SumoExplore +from primary.services.utils.authenticated_user import AuthenticatedUser +from primary.services.sumo_access._helpers import SumoEnsemble router = APIRouter() @@ -29,6 +29,7 @@ class EnsembleInfo(BaseModel): class EnsembleDetails(BaseModel): name: str + field_identifier: str case_name: str case_uuid: str realizations: Sequence[int] @@ -89,5 +90,15 @@ async def get_ensemble_details( iteration = await SumoEnsemble.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) case_name = iteration.get_case_name() realizations = iteration.get_realizations() - - return EnsembleDetails(name=ensemble_name, case_name=case_name, case_uuid=case_uuid, realizations=realizations) + field_identifiers = await iteration.get_field_identifiers() + + if len(field_identifiers) != 1: + raise NotImplementedError("Multiple field identifiers not supported") + + return EnsembleDetails( + name=ensemble_name, + case_name=case_name, + case_uuid=case_uuid, + realizations=realizations, + field_identifier=field_identifiers[0], + ) diff --git a/backend/src/backend/primary/routers/general.py b/backend_py/primary/primary/routers/general.py similarity index 79% rename from backend/src/backend/primary/routers/general.py rename to backend_py/primary/primary/routers/general.py index bcb6b6b45..58ede3f30 100644 --- a/backend/src/backend/primary/routers/general.py +++ b/backend_py/primary/primary/routers/general.py @@ -4,13 +4,11 @@ import httpx import starsessions -from starlette.responses import StreamingResponse -from fastapi import APIRouter, HTTPException, Request, status, Depends, Query +from fastapi import APIRouter, HTTPException, Request, status, Query from pydantic import BaseModel -from src.backend.auth.auth_helper import AuthHelper, AuthenticatedUser -from src.backend.primary.user_session_proxy import proxy_to_user_session -from src.services.graph_access.graph_access import GraphApiAccess +from primary.auth.auth_helper import AuthHelper +from primary.services.graph_access.graph_access import GraphApiAccess LOGGER = logging.getLogger(__name__) @@ -81,11 +79,3 @@ async def logged_in_user( print("Error while fetching user avatar and info from Microsoft Graph API (Invalid URL):\n", exc) return user_info - - -@router.get("/user_session_container") -async def user_session_container( - request: Request, authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user) -) -> StreamingResponse: - """Get information about user session container (note that one is started if not already running).""" - return await proxy_to_user_session(request, authenticated_user) diff --git a/backend/src/backend/primary/__init__.py b/backend_py/primary/primary/routers/graph/__init__.py similarity index 100% rename from backend/src/backend/primary/__init__.py rename to backend_py/primary/primary/routers/graph/__init__.py diff --git a/backend/src/backend/primary/routers/graph/router.py b/backend_py/primary/primary/routers/graph/router.py similarity index 86% rename from backend/src/backend/primary/routers/graph/router.py rename to backend_py/primary/primary/routers/graph/router.py index 0deb3fb2a..9d913a09b 100644 --- a/backend/src/backend/primary/routers/graph/router.py +++ b/backend_py/primary/primary/routers/graph/router.py @@ -3,9 +3,9 @@ import httpx from fastapi import APIRouter, Depends, Query -from src.backend.auth.auth_helper import AuthHelper -from src.services.utils.authenticated_user import AuthenticatedUser -from src.services.graph_access.graph_access import GraphApiAccess +from primary.auth.auth_helper import AuthHelper +from primary.services.utils.authenticated_user import AuthenticatedUser +from primary.services.graph_access.graph_access import GraphApiAccess from .schemas import GraphUserPhoto diff --git a/backend/src/backend/primary/routers/graph/schemas.py b/backend_py/primary/primary/routers/graph/schemas.py similarity index 100% rename from backend/src/backend/primary/routers/graph/schemas.py rename to backend_py/primary/primary/routers/graph/schemas.py diff --git a/backend/src/backend/primary/routers/__init__.py b/backend_py/primary/primary/routers/grid3d/__init__.py similarity index 100% rename from backend/src/backend/primary/routers/__init__.py rename to backend_py/primary/primary/routers/grid3d/__init__.py diff --git a/backend_py/primary/primary/routers/grid3d/router.py b/backend_py/primary/primary/routers/grid3d/router.py new file mode 100644 index 000000000..85adb6c68 --- /dev/null +++ b/backend_py/primary/primary/routers/grid3d/router.py @@ -0,0 +1,68 @@ +from typing import List + +from fastapi import APIRouter, Depends, Query, HTTPException, status +from starlette.requests import Request + +from primary.services.utils.authenticated_user import AuthenticatedUser +from primary.auth.auth_helper import AuthHelper + +from primary.services.sumo_access.grid_access import GridAccess +from .schemas import GridSurface + +router = APIRouter() + +# pylint: disable=unused-argument +# pylint: disable=unused-variable + + +@router.get("/grid_model_names/") +async def get_grid_model_names( + authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), + case_uuid: str = Query(description="Sumo case uuid"), + ensemble_name: str = Query(description="Ensemble name"), +) -> List[str]: + """ + Get a list of grid model names + """ + access = await GridAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) + return await access.grid_model_names() + + +@router.get("/parameter_names/") +async def get_parameter_names( + authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), + case_uuid: str = Query(description="Sumo case uuid"), + ensemble_name: str = Query(description="Ensemble name"), + grid_name: str = Query(description="Grid name"), +) -> List[str]: + """ + Get a list of grid parameter names + """ + access = await GridAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) + return await access.static_parameter_names(grid_name) + + +# Primary backend +@router.get("/grid_surface") +async def grid_surface( + request: Request, + case_uuid: str = Query(description="Sumo case uuid"), + ensemble_name: str = Query(description="Ensemble name"), + grid_name: str = Query(description="Grid name"), + realization: str = Query(description="Realization"), + authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), +) -> GridSurface: + raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED) + + +@router.get("/grid_parameter") +async def grid_parameter( + request: Request, + case_uuid: str = Query(description="Sumo case uuid"), + ensemble_name: str = Query(description="Ensemble name"), + grid_name: str = Query(description="Grid name"), + parameter_name: str = Query(description="Grid parameter"), + realization: str = Query(description="Realization"), + authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), +) -> List[float]: + raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED) diff --git a/backend/src/backend/primary/routers/grid/router.py b/backend_py/primary/primary/routers/grid3d/router_vtk.py similarity index 69% rename from backend/src/backend/primary/routers/grid/router.py rename to backend_py/primary/primary/routers/grid3d/router_vtk.py index fc064fec3..dba4f65e2 100644 --- a/backend/src/backend/primary/routers/grid/router.py +++ b/backend_py/primary/primary/routers/grid3d/router_vtk.py @@ -1,55 +1,29 @@ from typing import List -from fastapi import APIRouter, Depends, Query +from fastapi import APIRouter, Depends, Query, HTTPException, status from starlette.requests import Request -from src.services.utils.authenticated_user import AuthenticatedUser -from src.backend.auth.auth_helper import AuthHelper -from src.backend.primary.user_session_proxy import proxy_to_user_session +from primary.services.utils.authenticated_user import AuthenticatedUser +from primary.auth.auth_helper import AuthHelper -from src.services.sumo_access.grid_access import GridAccess -from .schemas import GridSurface, GridIntersection +from .schemas_vtk import GridSurfaceVtk, GridIntersectionVtk router = APIRouter() - -@router.get("/grid_model_names/") -async def get_grid_model_names( - authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), - case_uuid: str = Query(description="Sumo case uuid"), - ensemble_name: str = Query(description="Ensemble name"), -) -> List[str]: - """ - Get a list of grid model names - """ - access = await GridAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) - return await access.grid_model_names() - - -@router.get("/parameter_names/") -async def get_parameter_names( - authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), - case_uuid: str = Query(description="Sumo case uuid"), - ensemble_name: str = Query(description="Ensemble name"), - grid_name: str = Query(description="Grid name"), -) -> List[str]: - """ - Get a list of grid parameter names - """ - access = await GridAccess.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) - return await access.static_parameter_names(grid_name) +# pylint: disable=unused-argument +# pylint: disable=unused-variable # Primary backend -@router.get("/grid_surface") -async def grid_surface( +@router.get("/grid_surface_vtk") +async def grid_surface_vtk( request: Request, case_uuid: str = Query(description="Sumo case uuid"), ensemble_name: str = Query(description="Ensemble name"), grid_name: str = Query(description="Grid name"), realization: str = Query(description="Realization"), authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), -) -> GridSurface: +) -> GridSurfaceVtk: """Get a grid""" query_params = { @@ -71,12 +45,11 @@ async def grid_surface( receive=request._receive, # pylint: disable=protected-access ) - response = await proxy_to_user_session(updated_request, authenticated_user) - return response + raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED) -@router.get("/grid_parameter") -async def grid_parameter( +@router.get("/grid_parameter_vtk") +async def grid_parameter_vtk( request: Request, case_uuid: str = Query(description="Sumo case uuid"), ensemble_name: str = Query(description="Ensemble name"), @@ -107,12 +80,11 @@ async def grid_parameter( receive=request._receive, # pylint: disable=protected-access ) - response = await proxy_to_user_session(updated_request, authenticated_user) - return response + raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED) -@router.get("/grid_parameter_intersection") -async def grid_parameter_intersection( +@router.get("/grid_parameter_intersection_vtk") +async def grid_parameter_intersection_vtk( request: Request, case_uuid: str = Query(description="Sumo case uuid"), ensemble_name: str = Query(description="Ensemble name"), @@ -120,7 +92,7 @@ async def grid_parameter_intersection( parameter_name: str = Query(description="Grid parameter"), realization: str = Query(description="Realization"), authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), -) -> GridIntersection: +) -> GridIntersectionVtk: """Get a grid parameter""" query_params = { @@ -143,12 +115,11 @@ async def grid_parameter_intersection( receive=request._receive, # pylint: disable=protected-access ) - response = await proxy_to_user_session(updated_request, authenticated_user) - return response + raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED) -@router.get("/statistical_grid_parameter_intersection") -async def statistical_grid_parameter_intersection( +@router.get("/statistical_grid_parameter_intersection_vtk") +async def statistical_grid_parameter_intersection_vtk( request: Request, case_uuid: str = Query(description="Sumo case uuid"), ensemble_name: str = Query(description="Ensemble name"), @@ -156,7 +127,7 @@ async def statistical_grid_parameter_intersection( parameter_name: str = Query(description="Grid parameter"), realizations: List[str] = Query(description="Realizations"), authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), -) -> GridIntersection: +) -> GridIntersectionVtk: """Get a grid parameter""" query_params = { @@ -179,12 +150,11 @@ async def statistical_grid_parameter_intersection( receive=request._receive, # pylint: disable=protected-access ) - response = await proxy_to_user_session(updated_request, authenticated_user) - return response + raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED) -@router.get("/statistical_grid_parameter") -async def statistical_grid_parameter( +@router.get("/statistical_grid_parameter_vtk") +async def statistical_grid_parameter_vtk( request: Request, case_uuid: str = Query(description="Sumo case uuid"), ensemble_name: str = Query(description="Ensemble name"), @@ -214,5 +184,4 @@ async def statistical_grid_parameter( receive=request._receive, # pylint: disable=protected-access ) - response = await proxy_to_user_session(updated_request, authenticated_user) - return response + raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED) diff --git a/backend_py/primary/primary/routers/grid3d/schemas.py b/backend_py/primary/primary/routers/grid3d/schemas.py new file mode 100644 index 000000000..a38049a89 --- /dev/null +++ b/backend_py/primary/primary/routers/grid3d/schemas.py @@ -0,0 +1,13 @@ +from pydantic import BaseModel +from webviz_pkg.core_utils.b64 import B64FloatArray, B64UintArray + + +class GridSurface(BaseModel): + polys_b64arr: B64UintArray + points_b64arr: B64FloatArray + xmin: float + xmax: float + ymin: float + ymax: float + zmin: float + zmax: float diff --git a/backend/src/backend/primary/routers/grid/schemas.py b/backend_py/primary/primary/routers/grid3d/schemas_vtk.py similarity index 72% rename from backend/src/backend/primary/routers/grid/schemas.py rename to backend_py/primary/primary/routers/grid3d/schemas_vtk.py index c8a3abe03..1e91d0b4d 100644 --- a/backend/src/backend/primary/routers/grid/schemas.py +++ b/backend_py/primary/primary/routers/grid3d/schemas_vtk.py @@ -1,11 +1,10 @@ from typing import List from pydantic import BaseModel +from webviz_pkg.core_utils.b64 import B64FloatArray, B64UintArray -from src.services.utils.b64 import B64FloatArray, B64UintArray - -class GridSurface(BaseModel): +class GridSurfaceVtk(BaseModel): polys_b64arr: B64UintArray points_b64arr: B64FloatArray xmin: float @@ -16,7 +15,7 @@ class GridSurface(BaseModel): zmax: float -class GridIntersection(BaseModel): +class GridIntersectionVtk(BaseModel): image: str polyline_x: List[float] polyline_y: List[float] diff --git a/backend/src/backend/primary/routers/correlations/__init__.py b/backend_py/primary/primary/routers/inplace_volumetrics/__init__.py similarity index 100% rename from backend/src/backend/primary/routers/correlations/__init__.py rename to backend_py/primary/primary/routers/inplace_volumetrics/__init__.py diff --git a/backend/src/backend/primary/routers/inplace_volumetrics/router.py b/backend_py/primary/primary/routers/inplace_volumetrics/router.py similarity index 93% rename from backend/src/backend/primary/routers/inplace_volumetrics/router.py rename to backend_py/primary/primary/routers/inplace_volumetrics/router.py index 7e2df6b0e..828a53bf7 100644 --- a/backend/src/backend/primary/routers/inplace_volumetrics/router.py +++ b/backend_py/primary/primary/routers/inplace_volumetrics/router.py @@ -1,16 +1,16 @@ from typing import List, Optional, Sequence from fastapi import APIRouter, Depends, Query -from src.services.sumo_access.inplace_volumetrics_access import ( +from primary.services.sumo_access.inplace_volumetrics_access import ( InplaceVolumetricsAccess, InplaceVolumetricsTableMetaData, InplaceVolumetricsCategoricalMetaData, ) -from src.services.sumo_access.generic_types import EnsembleScalarResponse -from src.services.utils.authenticated_user import AuthenticatedUser +from primary.services.sumo_access.generic_types import EnsembleScalarResponse +from primary.services.utils.authenticated_user import AuthenticatedUser -from src.backend.auth.auth_helper import AuthHelper +from primary.auth.auth_helper import AuthHelper router = APIRouter() diff --git a/backend/src/backend/primary/routers/observations/router.py b/backend_py/primary/primary/routers/observations/router.py similarity index 75% rename from backend/src/backend/primary/routers/observations/router.py rename to backend_py/primary/primary/routers/observations/router.py index c22f9f020..33578c4cd 100644 --- a/backend/src/backend/primary/routers/observations/router.py +++ b/backend_py/primary/primary/routers/observations/router.py @@ -1,11 +1,10 @@ import logging -from typing import List, Optional, Literal from fastapi import APIRouter, Depends, Query -from src.backend.auth.auth_helper import AuthHelper -from src.services.sumo_access.observation_access import ObservationAccess -from src.services.utils.authenticated_user import AuthenticatedUser +from primary.auth.auth_helper import AuthHelper +from primary.services.sumo_access.observation_access import ObservationAccess +from primary.services.utils.authenticated_user import AuthenticatedUser from . import schemas diff --git a/backend/src/backend/primary/routers/observations/schemas.py b/backend_py/primary/primary/routers/observations/schemas.py similarity index 100% rename from backend/src/backend/primary/routers/observations/schemas.py rename to backend_py/primary/primary/routers/observations/schemas.py diff --git a/backend/src/backend/primary/routers/parameters/__init__,py b/backend_py/primary/primary/routers/parameters/__init__,py similarity index 100% rename from backend/src/backend/primary/routers/parameters/__init__,py rename to backend_py/primary/primary/routers/parameters/__init__,py diff --git a/backend/src/backend/primary/routers/parameters/router.py b/backend_py/primary/primary/routers/parameters/router.py similarity index 93% rename from backend/src/backend/primary/routers/parameters/router.py rename to backend_py/primary/primary/routers/parameters/router.py index d670d31d1..983b88d6a 100644 --- a/backend/src/backend/primary/routers/parameters/router.py +++ b/backend_py/primary/primary/routers/parameters/router.py @@ -3,10 +3,10 @@ from fastapi import APIRouter, Depends, Query -from src.backend.auth.auth_helper import AuthHelper -from src.services.sumo_access.parameter_access import ParameterAccess -from src.services.sumo_access.parameter_types import EnsembleParameter, EnsembleSensitivity -from src.services.utils.authenticated_user import AuthenticatedUser +from primary.auth.auth_helper import AuthHelper +from primary.services.sumo_access.parameter_access import ParameterAccess +from primary.services.sumo_access.parameter_types import EnsembleParameter, EnsembleSensitivity +from primary.services.utils.authenticated_user import AuthenticatedUser from . import schemas diff --git a/backend/src/backend/primary/routers/parameters/schemas.py b/backend_py/primary/primary/routers/parameters/schemas.py similarity index 100% rename from backend/src/backend/primary/routers/parameters/schemas.py rename to backend_py/primary/primary/routers/parameters/schemas.py diff --git a/backend/src/backend/primary/routers/graph/__init__.py b/backend_py/primary/primary/routers/polygons/__init__.py similarity index 100% rename from backend/src/backend/primary/routers/graph/__init__.py rename to backend_py/primary/primary/routers/polygons/__init__.py diff --git a/backend/src/backend/primary/routers/polygons/converters.py b/backend_py/primary/primary/routers/polygons/converters.py similarity index 95% rename from backend/src/backend/primary/routers/polygons/converters.py rename to backend_py/primary/primary/routers/polygons/converters.py index fbcf8b1f6..6aef1f0d3 100644 --- a/backend/src/backend/primary/routers/polygons/converters.py +++ b/backend_py/primary/primary/routers/polygons/converters.py @@ -1,8 +1,8 @@ from typing import List import xtgeo -from src.services.smda_access.types import StratigraphicSurface -from src.services.sumo_access.polygons_types import PolygonsMeta as SumoPolygonsMeta +from primary.services.smda_access.types import StratigraphicSurface +from primary.services.sumo_access.polygons_types import PolygonsMeta as SumoPolygonsMeta from . import schemas diff --git a/backend/src/backend/primary/routers/polygons/router.py b/backend_py/primary/primary/routers/polygons/router.py similarity index 80% rename from backend/src/backend/primary/routers/polygons/router.py rename to backend_py/primary/primary/routers/polygons/router.py index cc0b683c1..d3f4a79c9 100644 --- a/backend/src/backend/primary/routers/polygons/router.py +++ b/backend_py/primary/primary/routers/polygons/router.py @@ -2,19 +2,17 @@ from typing import List, Union from fastapi import APIRouter, Depends, HTTPException, Query +from webviz_pkg.core_utils.perf_timer import PerfTimer -from src.services.sumo_access._helpers import SumoCase -from src.services.smda_access.stratigraphy_access import StratigraphyAccess -from src.services.sumo_access.polygons_access import PolygonsAccess -from src.services.smda_access.stratigraphy_utils import sort_stratigraphic_names_by_hierarchy -from src.services.smda_access.mocked_drogon_smda_access import _mocked_stratigraphy_access -from src.services.utils.authenticated_user import AuthenticatedUser -from src.services.utils.perf_timer import PerfTimer -from src.backend.auth.auth_helper import AuthHelper +from primary.auth.auth_helper import AuthHelper +from primary.services.smda_access.mocked_drogon_smda_access import _mocked_stratigraphy_access +from primary.services.smda_access.stratigraphy_access import StratigraphyAccess +from primary.services.smda_access.stratigraphy_utils import sort_stratigraphic_names_by_hierarchy +from primary.services.sumo_access._helpers import SumoCase +from primary.services.sumo_access.polygons_access import PolygonsAccess +from primary.services.utils.authenticated_user import AuthenticatedUser - -from . import schemas -from . import converters +from . import converters, schemas LOGGER = logging.getLogger(__name__) diff --git a/backend/src/backend/primary/routers/polygons/schemas.py b/backend_py/primary/primary/routers/polygons/schemas.py similarity index 100% rename from backend/src/backend/primary/routers/polygons/schemas.py rename to backend_py/primary/primary/routers/polygons/schemas.py diff --git a/backend/src/backend/primary/routers/grid/__init__.py b/backend_py/primary/primary/routers/pvt/__init__.py similarity index 100% rename from backend/src/backend/primary/routers/grid/__init__.py rename to backend_py/primary/primary/routers/pvt/__init__.py diff --git a/backend/src/backend/primary/routers/pvt/converters.py b/backend_py/primary/primary/routers/pvt/converters.py similarity index 100% rename from backend/src/backend/primary/routers/pvt/converters.py rename to backend_py/primary/primary/routers/pvt/converters.py diff --git a/backend/src/backend/primary/routers/pvt/router.py b/backend_py/primary/primary/routers/pvt/router.py similarity index 92% rename from backend/src/backend/primary/routers/pvt/router.py rename to backend_py/primary/primary/routers/pvt/router.py index 6aeb694ff..a50e1175a 100644 --- a/backend/src/backend/primary/routers/pvt/router.py +++ b/backend_py/primary/primary/routers/pvt/router.py @@ -3,9 +3,9 @@ from fastapi import APIRouter, Depends, HTTPException, Query -from src.backend.auth.auth_helper import AuthHelper -from src.services.sumo_access.table_access import TableAccess -from src.services.utils.authenticated_user import AuthenticatedUser +from primary.auth.auth_helper import AuthHelper +from primary.services.sumo_access.table_access import TableAccess +from primary.services.utils.authenticated_user import AuthenticatedUser from .converters import pvt_dataframe_to_api_data from .schemas import PvtData diff --git a/backend/src/backend/primary/routers/pvt/schemas.py b/backend_py/primary/primary/routers/pvt/schemas.py similarity index 100% rename from backend/src/backend/primary/routers/pvt/schemas.py rename to backend_py/primary/primary/routers/pvt/schemas.py diff --git a/backend/src/backend/primary/routers/rft/router.py b/backend_py/primary/primary/routers/rft/router.py similarity index 76% rename from backend/src/backend/primary/routers/rft/router.py rename to backend_py/primary/primary/routers/rft/router.py index 178313a1d..7de32ceee 100644 --- a/backend/src/backend/primary/routers/rft/router.py +++ b/backend_py/primary/primary/routers/rft/router.py @@ -1,16 +1,11 @@ import logging from typing import Annotated -import pyarrow as pa -import pyarrow.compute as pc -from fastapi import APIRouter, Depends, HTTPException, Query - -from src.backend.auth.auth_helper import AuthHelper -from src.services.summary_vector_statistics import compute_vector_statistics -from src.services.sumo_access.generic_types import EnsembleScalarResponse -from src.services.sumo_access.parameter_access import ParameterAccess -from src.services.sumo_access.rft_access import RftAccess -from src.services.utils.authenticated_user import AuthenticatedUser +from fastapi import APIRouter, Depends, Query + +from primary.auth.auth_helper import AuthHelper +from primary.services.sumo_access.rft_access import RftAccess +from primary.services.utils.authenticated_user import AuthenticatedUser from . import schemas diff --git a/backend/src/backend/primary/routers/rft/schemas.py b/backend_py/primary/primary/routers/rft/schemas.py similarity index 100% rename from backend/src/backend/primary/routers/rft/schemas.py rename to backend_py/primary/primary/routers/rft/schemas.py diff --git a/backend/src/backend/primary/routers/inplace_volumetrics/__init__.py b/backend_py/primary/primary/routers/seismic/__init__.py similarity index 100% rename from backend/src/backend/primary/routers/inplace_volumetrics/__init__.py rename to backend_py/primary/primary/routers/seismic/__init__.py diff --git a/backend/src/backend/primary/routers/seismic/router.py b/backend_py/primary/primary/routers/seismic/router.py similarity index 87% rename from backend/src/backend/primary/routers/seismic/router.py rename to backend_py/primary/primary/routers/seismic/router.py index 90b037e50..781a548f3 100644 --- a/backend/src/backend/primary/routers/seismic/router.py +++ b/backend_py/primary/primary/routers/seismic/router.py @@ -1,19 +1,18 @@ import logging from typing import List, Optional -from fastapi import APIRouter, Depends, HTTPException, Query, Body +from fastapi import APIRouter, Body, Depends, HTTPException, Query +from webviz_pkg.core_utils.b64 import b64_encode_float_array_as_float32 -from src.services.sumo_access.seismic_access import SeismicAccess, VdsHandle -from src.services.vds_access.vds_access import VdsAccess -from src.services.utils.authenticated_user import AuthenticatedUser -from src.backend.auth.auth_helper import AuthHelper -from src.services.utils.b64 import b64_encode_float_array_as_float32 -from src.services.vds_access.response_types import VdsMetadata -from src.services.vds_access.request_types import VdsCoordinateSystem, VdsCoordinates +from primary.auth.auth_helper import AuthHelper +from primary.services.sumo_access.seismic_access import SeismicAccess, VdsHandle +from primary.services.utils.authenticated_user import AuthenticatedUser +from primary.services.vds_access.request_types import VdsCoordinates, VdsCoordinateSystem +from primary.services.vds_access.response_types import VdsMetadata +from primary.services.vds_access.vds_access import VdsAccess from . import schemas - LOGGER = logging.getLogger(__name__) router = APIRouter() diff --git a/backend/src/backend/primary/routers/seismic/schemas.py b/backend_py/primary/primary/routers/seismic/schemas.py similarity index 97% rename from backend/src/backend/primary/routers/seismic/schemas.py rename to backend_py/primary/primary/routers/seismic/schemas.py index 3e2c4b087..dbcf7430c 100644 --- a/backend/src/backend/primary/routers/seismic/schemas.py +++ b/backend_py/primary/primary/routers/seismic/schemas.py @@ -1,8 +1,7 @@ from typing import List from pydantic import BaseModel - -from src.services.utils.b64 import B64FloatArray +from webviz_pkg.core_utils.b64 import B64FloatArray class SeismicCubeMeta(BaseModel): diff --git a/backend/src/backend/primary/routers/polygons/__init__.py b/backend_py/primary/primary/routers/surface/__init__.py similarity index 100% rename from backend/src/backend/primary/routers/polygons/__init__.py rename to backend_py/primary/primary/routers/surface/__init__.py diff --git a/backend/src/backend/primary/routers/surface/converters.py b/backend_py/primary/primary/routers/surface/converters.py similarity index 92% rename from backend/src/backend/primary/routers/surface/converters.py rename to backend_py/primary/primary/routers/surface/converters.py index 5b6a48f3f..26568174b 100644 --- a/backend/src/backend/primary/routers/surface/converters.py +++ b/backend_py/primary/primary/routers/surface/converters.py @@ -3,12 +3,12 @@ import numpy as np import xtgeo from numpy.typing import NDArray +from webviz_pkg.core_utils.b64 import b64_encode_float_array_as_float32 -from src.services.smda_access.types import StratigraphicSurface -from src.services.sumo_access.surface_types import SurfaceMeta as SumoSurfaceMeta -from src.services.sumo_access.surface_types import XtgeoSurfaceIntersectionPolyline, XtgeoSurfaceIntersectionResult -from src.services.utils.b64 import b64_encode_float_array_as_float32 -from src.services.utils.surface_to_float32 import surface_to_float32_numpy_array +from primary.services.smda_access.types import StratigraphicSurface +from primary.services.sumo_access.surface_types import SurfaceMeta as SumoSurfaceMeta +from primary.services.sumo_access.surface_types import XtgeoSurfaceIntersectionPolyline, XtgeoSurfaceIntersectionResult +from primary.services.utils.surface_to_float32 import surface_to_float32_numpy_array from . import schemas diff --git a/backend/src/backend/primary/routers/surface/router.py b/backend_py/primary/primary/routers/surface/router.py similarity index 92% rename from backend/src/backend/primary/routers/surface/router.py rename to backend_py/primary/primary/routers/surface/router.py index 4ac930c5a..8fcf3e863 100644 --- a/backend/src/backend/primary/routers/surface/router.py +++ b/backend_py/primary/primary/routers/surface/router.py @@ -2,19 +2,19 @@ from typing import List, Union, Optional from fastapi import APIRouter, Depends, HTTPException, Query, Response, Body - -from src.services.sumo_access.surface_access import SurfaceAccess -from src.services.smda_access.stratigraphy_access import StratigraphyAccess -from src.services.smda_access.stratigraphy_utils import sort_stratigraphic_names_by_hierarchy -from src.services.smda_access.mocked_drogon_smda_access import _mocked_stratigraphy_access -from src.services.utils.statistic_function import StatisticFunction -from src.services.utils.authenticated_user import AuthenticatedUser -from src.services.utils.perf_timer import PerfTimer -from src.backend.auth.auth_helper import AuthHelper -from src.backend.utils.perf_metrics import PerfMetrics -from src.services.sumo_access._helpers import SumoCase -from src.services.surface_query_service.surface_query_service import batch_sample_surface_in_points_async -from src.services.surface_query_service.surface_query_service import RealizationSampleResult +from webviz_pkg.core_utils.perf_timer import PerfTimer + +from primary.services.sumo_access.surface_access import SurfaceAccess +from primary.services.smda_access.stratigraphy_access import StratigraphyAccess +from primary.services.smda_access.stratigraphy_utils import sort_stratigraphic_names_by_hierarchy +from primary.services.smda_access.mocked_drogon_smda_access import _mocked_stratigraphy_access +from primary.services.utils.statistic_function import StatisticFunction +from primary.services.utils.authenticated_user import AuthenticatedUser +from primary.auth.auth_helper import AuthHelper +from primary.utils.perf_metrics import PerfMetrics +from primary.services.sumo_access._helpers import SumoCase +from primary.services.surface_query_service.surface_query_service import batch_sample_surface_in_points_async +from primary.services.surface_query_service.surface_query_service import RealizationSampleResult from . import converters from . import schemas diff --git a/backend/src/backend/primary/routers/surface/schemas.py b/backend_py/primary/primary/routers/surface/schemas.py similarity index 96% rename from backend/src/backend/primary/routers/surface/schemas.py rename to backend_py/primary/primary/routers/surface/schemas.py index 2a2b7338c..43a807c8e 100644 --- a/backend/src/backend/primary/routers/surface/schemas.py +++ b/backend_py/primary/primary/routers/surface/schemas.py @@ -2,9 +2,9 @@ from typing import List, Optional from pydantic import BaseModel +from webviz_pkg.core_utils.b64 import B64FloatArray -from src.services.smda_access.types import StratigraphicFeature -from src.services.utils.b64 import B64FloatArray +from primary.services.smda_access.types import StratigraphicFeature class SurfaceStatisticFunction(str, Enum): diff --git a/backend/src/backend/primary/routers/timeseries/converters.py b/backend_py/primary/primary/routers/timeseries/converters.py similarity index 89% rename from backend/src/backend/primary/routers/timeseries/converters.py rename to backend_py/primary/primary/routers/timeseries/converters.py index da715f8e2..e449e6e8d 100644 --- a/backend/src/backend/primary/routers/timeseries/converters.py +++ b/backend_py/primary/primary/routers/timeseries/converters.py @@ -1,8 +1,8 @@ from typing import List, Optional, Sequence -from src.services.summary_vector_statistics import VectorStatistics -from src.services.sumo_access.summary_access import VectorMetadata -from src.services.utils.statistic_function import StatisticFunction +from primary.services.summary_vector_statistics import VectorStatistics +from primary.services.sumo_access.summary_access import VectorMetadata +from primary.services.utils.statistic_function import StatisticFunction from . import schemas diff --git a/backend/src/backend/primary/routers/timeseries/router.py b/backend_py/primary/primary/routers/timeseries/router.py similarity index 96% rename from backend/src/backend/primary/routers/timeseries/router.py rename to backend_py/primary/primary/routers/timeseries/router.py index 45882129b..9e494a642 100644 --- a/backend/src/backend/primary/routers/timeseries/router.py +++ b/backend_py/primary/primary/routers/timeseries/router.py @@ -5,13 +5,13 @@ import pyarrow.compute as pc from fastapi import APIRouter, Depends, HTTPException, Query, Response -from src.backend.auth.auth_helper import AuthHelper -from src.backend.utils.perf_metrics import PerfMetrics -from src.services.summary_vector_statistics import compute_vector_statistics -from src.services.sumo_access.generic_types import EnsembleScalarResponse -from src.services.sumo_access.parameter_access import ParameterAccess -from src.services.sumo_access.summary_access import Frequency, SummaryAccess -from src.services.utils.authenticated_user import AuthenticatedUser +from primary.auth.auth_helper import AuthHelper +from primary.utils.perf_metrics import PerfMetrics +from primary.services.summary_vector_statistics import compute_vector_statistics +from primary.services.sumo_access.generic_types import EnsembleScalarResponse +from primary.services.sumo_access.parameter_access import ParameterAccess +from primary.services.sumo_access.summary_access import Frequency, SummaryAccess +from primary.services.utils.authenticated_user import AuthenticatedUser from . import converters, schemas diff --git a/backend/src/backend/primary/routers/timeseries/schemas.py b/backend_py/primary/primary/routers/timeseries/schemas.py similarity index 100% rename from backend/src/backend/primary/routers/timeseries/schemas.py rename to backend_py/primary/primary/routers/timeseries/schemas.py diff --git a/backend/src/backend/primary/routers/pvt/__init__.py b/backend_py/primary/primary/routers/well/__init__.py similarity index 100% rename from backend/src/backend/primary/routers/pvt/__init__.py rename to backend_py/primary/primary/routers/well/__init__.py diff --git a/backend/src/backend/primary/routers/well/converters.py b/backend_py/primary/primary/routers/well/converters.py similarity index 91% rename from backend/src/backend/primary/routers/well/converters.py rename to backend_py/primary/primary/routers/well/converters.py index d52be1ce3..21e8569d8 100644 --- a/backend/src/backend/primary/routers/well/converters.py +++ b/backend_py/primary/primary/routers/well/converters.py @@ -1,7 +1,8 @@ from typing import List -from src.backend.primary.routers.well import schemas -from src.services.smda_access.types import WellBorePick, StratigraphicUnit +from primary.services.smda_access.types import WellBorePick, StratigraphicUnit + +from . import schemas def convert_wellbore_picks_to_schema(wellbore_picks: List[WellBorePick]) -> List[schemas.WellBorePick]: diff --git a/backend/src/backend/primary/routers/well/router.py b/backend_py/primary/primary/routers/well/router.py similarity index 91% rename from backend/src/backend/primary/routers/well/router.py rename to backend_py/primary/primary/routers/well/router.py index f3583cf9e..605f6694c 100644 --- a/backend/src/backend/primary/routers/well/router.py +++ b/backend_py/primary/primary/routers/well/router.py @@ -3,13 +3,13 @@ from fastapi import APIRouter, Depends, Query -from src.services.smda_access import mocked_drogon_smda_access -from src.services.smda_access.well_access import WellAccess -from src.services.smda_access.stratigraphy_access import StratigraphyAccess -from src.services.utils.authenticated_user import AuthenticatedUser -from src.backend.auth.auth_helper import AuthHelper -from src.services.sumo_access._helpers import SumoCase -from src.services.smda_access.types import WellBoreHeader, WellBoreTrajectory +from primary.services.smda_access import mocked_drogon_smda_access +from primary.services.smda_access.well_access import WellAccess +from primary.services.smda_access.stratigraphy_access import StratigraphyAccess +from primary.services.utils.authenticated_user import AuthenticatedUser +from primary.auth.auth_helper import AuthHelper +from primary.services.sumo_access._helpers import SumoCase +from primary.services.smda_access.types import WellBoreHeader, WellBoreTrajectory from . import schemas from . import converters diff --git a/backend/src/backend/primary/routers/well/schemas.py b/backend_py/primary/primary/routers/well/schemas.py similarity index 100% rename from backend/src/backend/primary/routers/well/schemas.py rename to backend_py/primary/primary/routers/well/schemas.py diff --git a/backend/src/backend/primary/routers/well_completions/router.py b/backend_py/primary/primary/routers/well_completions/router.py similarity index 77% rename from backend/src/backend/primary/routers/well_completions/router.py rename to backend_py/primary/primary/routers/well_completions/router.py index 8e7dc5eaa..acce7fec6 100644 --- a/backend/src/backend/primary/routers/well_completions/router.py +++ b/backend_py/primary/primary/routers/well_completions/router.py @@ -2,11 +2,11 @@ from fastapi import APIRouter, Depends, HTTPException, Query -from src.backend.auth.auth_helper import AuthHelper -from src.services.utils.authenticated_user import AuthenticatedUser +from primary.auth.auth_helper import AuthHelper +from primary.services.utils.authenticated_user import AuthenticatedUser -from src.services.sumo_access.well_completions_access import WellCompletionsAccess -from src.services.sumo_access.well_completions_types import WellCompletionsData +from primary.services.sumo_access.well_completions_access import WellCompletionsAccess +from primary.services.sumo_access.well_completions_types import WellCompletionsData router = APIRouter() diff --git a/backend/src/backend/primary/routers/seismic/__init__.py b/backend_py/primary/primary/services/__init__.py similarity index 100% rename from backend/src/backend/primary/routers/seismic/__init__.py rename to backend_py/primary/primary/services/__init__.py diff --git a/backend/src/services/graph_access/graph_access.py b/backend_py/primary/primary/services/graph_access/graph_access.py similarity index 100% rename from backend/src/services/graph_access/graph_access.py rename to backend_py/primary/primary/services/graph_access/graph_access.py diff --git a/backend/src/services/parameter_correlations.py b/backend_py/primary/primary/services/parameter_correlations.py similarity index 92% rename from backend/src/services/parameter_correlations.py rename to backend_py/primary/primary/services/parameter_correlations.py index 227204f69..ba5894e73 100644 --- a/backend/src/services/parameter_correlations.py +++ b/backend_py/primary/primary/services/parameter_correlations.py @@ -2,8 +2,8 @@ import pandas as pd -from src.services.sumo_access.parameter_access import EnsembleParameter -from src.services.sumo_access.generic_types import EnsembleScalarResponse, EnsembleCorrelations +from primary.services.sumo_access.parameter_access import EnsembleParameter +from primary.services.sumo_access.generic_types import EnsembleScalarResponse, EnsembleCorrelations def correlate_parameters_with_response( diff --git a/backend/src/services/service_exceptions.py b/backend_py/primary/primary/services/service_exceptions.py similarity index 100% rename from backend/src/services/service_exceptions.py rename to backend_py/primary/primary/services/service_exceptions.py diff --git a/backend/src/backend/primary/routers/surface/__init__.py b/backend_py/primary/primary/services/smda_access/__init__.py similarity index 100% rename from backend/src/backend/primary/routers/surface/__init__.py rename to backend_py/primary/primary/services/smda_access/__init__.py diff --git a/backend/src/services/smda_access/mocked_drogon_smda_access/__init__.py b/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/__init__.py similarity index 100% rename from backend/src/services/smda_access/mocked_drogon_smda_access/__init__.py rename to backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/__init__.py diff --git a/backend/src/services/smda_access/mocked_drogon_smda_access/_generate_testdata.py b/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_generate_testdata.py similarity index 100% rename from backend/src/services/smda_access/mocked_drogon_smda_access/_generate_testdata.py rename to backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_generate_testdata.py diff --git a/backend/src/services/smda_access/mocked_drogon_smda_access/_mocked_stratigraphy_access.py b/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_mocked_stratigraphy_access.py similarity index 100% rename from backend/src/services/smda_access/mocked_drogon_smda_access/_mocked_stratigraphy_access.py rename to backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_mocked_stratigraphy_access.py diff --git a/backend/src/services/smda_access/mocked_drogon_smda_access/_mocked_well_access.py b/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_mocked_well_access.py similarity index 100% rename from backend/src/services/smda_access/mocked_drogon_smda_access/_mocked_well_access.py rename to backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_mocked_well_access.py diff --git a/backend/src/services/smda_access/mocked_drogon_smda_access/_mocked_wellbore_picks.py b/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_mocked_wellbore_picks.py similarity index 98% rename from backend/src/services/smda_access/mocked_drogon_smda_access/_mocked_wellbore_picks.py rename to backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_mocked_wellbore_picks.py index 39ef35785..efd6f282a 100644 --- a/backend/src/services/smda_access/mocked_drogon_smda_access/_mocked_wellbore_picks.py +++ b/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_mocked_wellbore_picks.py @@ -1,6 +1,6 @@ from typing import List -from src.services.smda_access.types import WellBorePick +from primary.services.smda_access.types import WellBorePick mocked_wellbore_picks: List[WellBorePick] = [ WellBorePick( diff --git a/backend/src/backend/primary/routers/well/__init__.py b/backend_py/primary/primary/services/smda_access/queries/__init__.py similarity index 100% rename from backend/src/backend/primary/routers/well/__init__.py rename to backend_py/primary/primary/services/smda_access/queries/__init__.py diff --git a/backend/src/services/smda_access/queries/_get_request.py b/backend_py/primary/primary/services/smda_access/queries/_get_request.py similarity index 95% rename from backend/src/services/smda_access/queries/_get_request.py rename to backend_py/primary/primary/services/smda_access/queries/_get_request.py index b03ed4abd..52c14d3fe 100644 --- a/backend/src/services/smda_access/queries/_get_request.py +++ b/backend_py/primary/primary/services/smda_access/queries/_get_request.py @@ -2,9 +2,9 @@ import httpx from dotenv import load_dotenv +from webviz_pkg.core_utils.perf_timer import PerfTimer -from src import config -from src.services.utils.perf_timer import PerfTimer +from primary import config load_dotenv() diff --git a/backend/src/services/smda_access/queries/get_field_wellbore_trajectories.py b/backend_py/primary/primary/services/smda_access/queries/get_field_wellbore_trajectories.py similarity index 96% rename from backend/src/services/smda_access/queries/get_field_wellbore_trajectories.py rename to backend_py/primary/primary/services/smda_access/queries/get_field_wellbore_trajectories.py index 4fc498163..f8cf57d69 100644 --- a/backend/src/services/smda_access/queries/get_field_wellbore_trajectories.py +++ b/backend_py/primary/primary/services/smda_access/queries/get_field_wellbore_trajectories.py @@ -2,7 +2,7 @@ import pandas as pd -from src.services.utils.perf_timer import PerfTimer +from webviz_pkg.core_utils.perf_timer import PerfTimer from ..types import WellBoreTrajectory from ._get_request import get diff --git a/backend/src/services/smda_access/queries/get_picks_for_wellbore.py b/backend_py/primary/primary/services/smda_access/queries/get_picks_for_wellbore.py similarity index 100% rename from backend/src/services/smda_access/queries/get_picks_for_wellbore.py rename to backend_py/primary/primary/services/smda_access/queries/get_picks_for_wellbore.py diff --git a/backend/src/services/smda_access/queries/get_stratigraphic_units.py b/backend_py/primary/primary/services/smda_access/queries/get_stratigraphic_units.py similarity index 93% rename from backend/src/services/smda_access/queries/get_stratigraphic_units.py rename to backend_py/primary/primary/services/smda_access/queries/get_stratigraphic_units.py index 2828affdf..6d5ac97c7 100644 --- a/backend/src/services/smda_access/queries/get_stratigraphic_units.py +++ b/backend_py/primary/primary/services/smda_access/queries/get_stratigraphic_units.py @@ -1,6 +1,6 @@ from typing import List -from src.services.utils.perf_timer import PerfTimer +from webviz_pkg.core_utils.perf_timer import PerfTimer from ..types import StratigraphicUnit from ._get_request import get diff --git a/backend/src/services/smda_access/queries/get_well_headers.py b/backend_py/primary/primary/services/smda_access/queries/get_well_headers.py similarity index 93% rename from backend/src/services/smda_access/queries/get_well_headers.py rename to backend_py/primary/primary/services/smda_access/queries/get_well_headers.py index 978035344..746e97236 100644 --- a/backend/src/services/smda_access/queries/get_well_headers.py +++ b/backend_py/primary/primary/services/smda_access/queries/get_well_headers.py @@ -1,7 +1,7 @@ from typing import List -from src.services.utils.perf_timer import PerfTimer +from webviz_pkg.core_utils.perf_timer import PerfTimer from ..types import WellBoreHeader from ._get_request import get diff --git a/backend/src/services/smda_access/queries/get_wellbore_picks_for_field.py b/backend_py/primary/primary/services/smda_access/queries/get_wellbore_picks_for_field.py similarity index 94% rename from backend/src/services/smda_access/queries/get_wellbore_picks_for_field.py rename to backend_py/primary/primary/services/smda_access/queries/get_wellbore_picks_for_field.py index 2f6537558..4cf513703 100644 --- a/backend/src/services/smda_access/queries/get_wellbore_picks_for_field.py +++ b/backend_py/primary/primary/services/smda_access/queries/get_wellbore_picks_for_field.py @@ -1,6 +1,6 @@ from typing import List, Optional -from src.services.utils.perf_timer import PerfTimer +from webviz_pkg.core_utils.perf_timer import PerfTimer from ..types import WellBorePick from ._get_request import get diff --git a/backend/src/services/smda_access/queries/get_wellbore_trajectory.py b/backend_py/primary/primary/services/smda_access/queries/get_wellbore_trajectory.py similarity index 96% rename from backend/src/services/smda_access/queries/get_wellbore_trajectory.py rename to backend_py/primary/primary/services/smda_access/queries/get_wellbore_trajectory.py index 698fb0278..2f8c8da5c 100644 --- a/backend/src/services/smda_access/queries/get_wellbore_trajectory.py +++ b/backend_py/primary/primary/services/smda_access/queries/get_wellbore_trajectory.py @@ -2,7 +2,7 @@ import pandas as pd -from src.services.utils.perf_timer import PerfTimer +from webviz_pkg.core_utils.perf_timer import PerfTimer from ..types import WellBoreTrajectory from ._get_request import get diff --git a/backend/src/services/smda_access/stratigraphy_access.py b/backend_py/primary/primary/services/smda_access/stratigraphy_access.py similarity index 100% rename from backend/src/services/smda_access/stratigraphy_access.py rename to backend_py/primary/primary/services/smda_access/stratigraphy_access.py diff --git a/backend/src/services/smda_access/stratigraphy_utils.py b/backend_py/primary/primary/services/smda_access/stratigraphy_utils.py similarity index 100% rename from backend/src/services/smda_access/stratigraphy_utils.py rename to backend_py/primary/primary/services/smda_access/stratigraphy_utils.py diff --git a/backend/src/services/smda_access/types.py b/backend_py/primary/primary/services/smda_access/types.py similarity index 100% rename from backend/src/services/smda_access/types.py rename to backend_py/primary/primary/services/smda_access/types.py diff --git a/backend/src/services/smda_access/well_access.py b/backend_py/primary/primary/services/smda_access/well_access.py similarity index 100% rename from backend/src/services/smda_access/well_access.py rename to backend_py/primary/primary/services/smda_access/well_access.py diff --git a/backend/src/services/summary_vector_statistics.py b/backend_py/primary/primary/services/summary_vector_statistics.py similarity index 100% rename from backend/src/services/summary_vector_statistics.py rename to backend_py/primary/primary/services/summary_vector_statistics.py diff --git a/backend/src/backend/user_session/__init__.py b/backend_py/primary/primary/services/sumo_access/__init__.py similarity index 100% rename from backend/src/backend/user_session/__init__.py rename to backend_py/primary/primary/services/sumo_access/__init__.py diff --git a/backend/src/services/sumo_access/_field_metadata.py b/backend_py/primary/primary/services/sumo_access/_field_metadata.py similarity index 100% rename from backend/src/services/sumo_access/_field_metadata.py rename to backend_py/primary/primary/services/sumo_access/_field_metadata.py diff --git a/backend/src/services/sumo_access/_helpers.py b/backend_py/primary/primary/services/sumo_access/_helpers.py similarity index 90% rename from backend/src/services/sumo_access/_helpers.py rename to backend_py/primary/primary/services/sumo_access/_helpers.py index 6285710c4..e6167c1b0 100644 --- a/backend/src/services/sumo_access/_helpers.py +++ b/backend_py/primary/primary/services/sumo_access/_helpers.py @@ -2,9 +2,10 @@ from sumo.wrapper import SumoClient from fmu.sumo.explorer.objects import CaseCollection, Case +from fmu.sumo.explorer.explorer import Pit -from src import config -from src.services.service_exceptions import Service, NoDataError, MultipleDataMatchesError +from primary import config +from primary.services.service_exceptions import Service, NoDataError, MultipleDataMatchesError from .queries.case import get_stratigraphic_column_identifier, get_field_identifiers @@ -15,7 +16,7 @@ def create_sumo_client_instance(access_token: str) -> SumoClient: async def _init_helper(access_token: str, case_uuid: str) -> Tuple[SumoClient, Case]: sumo_client: SumoClient = create_sumo_client_instance(access_token) - case_collection = CaseCollection(sumo_client).filter(uuid=case_uuid) + case_collection = CaseCollection(sumo_client, pit=Pit(sumo_client, keep_alive="1m")).filter(uuid=case_uuid) matching_case_count = await case_collection.length_async() if matching_case_count == 0: diff --git a/backend/src/services/sumo_access/_resampling.py b/backend_py/primary/primary/services/sumo_access/_resampling.py similarity index 100% rename from backend/src/services/sumo_access/_resampling.py rename to backend_py/primary/primary/services/sumo_access/_resampling.py diff --git a/backend/src/services/sumo_access/dev/dev_summary_access_test_driver.py b/backend_py/primary/primary/services/sumo_access/dev/dev_summary_access_test_driver.py similarity index 97% rename from backend/src/services/sumo_access/dev/dev_summary_access_test_driver.py rename to backend_py/primary/primary/services/sumo_access/dev/dev_summary_access_test_driver.py index 0f7ac2ef5..bf84280d4 100644 --- a/backend/src/services/sumo_access/dev/dev_summary_access_test_driver.py +++ b/backend_py/primary/primary/services/sumo_access/dev/dev_summary_access_test_driver.py @@ -7,7 +7,7 @@ from fmu.sumo.explorer.explorer import SumoClient -from src.services.summary_vector_statistics import compute_vector_statistics_table, compute_vector_statistics +from primary.services.summary_vector_statistics import compute_vector_statistics_table, compute_vector_statistics from ..summary_access import SummaryAccess, RealizationVector, Frequency from ..sumo_explore import SumoExplore diff --git a/backend/src/services/sumo_access/generic_types.py b/backend_py/primary/primary/services/sumo_access/generic_types.py similarity index 100% rename from backend/src/services/sumo_access/generic_types.py rename to backend_py/primary/primary/services/sumo_access/generic_types.py diff --git a/backend/src/services/sumo_access/grid_access.py b/backend_py/primary/primary/services/sumo_access/grid_access.py similarity index 97% rename from backend/src/services/sumo_access/grid_access.py rename to backend_py/primary/primary/services/sumo_access/grid_access.py index a59b9229e..fa9b5b9b6 100644 --- a/backend/src/services/sumo_access/grid_access.py +++ b/backend_py/primary/primary/services/sumo_access/grid_access.py @@ -4,7 +4,7 @@ import xtgeo -from src.services.utils.perf_timer import PerfTimer +from webviz_pkg.core_utils.perf_timer import PerfTimer from ._helpers import SumoEnsemble from .queries.cpgrid import ( diff --git a/backend/src/services/sumo_access/inplace_volumetrics_access.py b/backend_py/primary/primary/services/sumo_access/inplace_volumetrics_access.py similarity index 100% rename from backend/src/services/sumo_access/inplace_volumetrics_access.py rename to backend_py/primary/primary/services/sumo_access/inplace_volumetrics_access.py diff --git a/backend/src/services/sumo_access/observation_access.py b/backend_py/primary/primary/services/sumo_access/observation_access.py similarity index 100% rename from backend/src/services/sumo_access/observation_access.py rename to backend_py/primary/primary/services/sumo_access/observation_access.py diff --git a/backend/src/services/sumo_access/observation_types.py b/backend_py/primary/primary/services/sumo_access/observation_types.py similarity index 100% rename from backend/src/services/sumo_access/observation_types.py rename to backend_py/primary/primary/services/sumo_access/observation_types.py diff --git a/backend/src/services/sumo_access/parameter_access.py b/backend_py/primary/primary/services/sumo_access/parameter_access.py similarity index 98% rename from backend/src/services/sumo_access/parameter_access.py rename to backend_py/primary/primary/services/sumo_access/parameter_access.py index 119c4a869..ac4dafd3b 100644 --- a/backend/src/services/sumo_access/parameter_access.py +++ b/backend_py/primary/primary/services/sumo_access/parameter_access.py @@ -6,7 +6,7 @@ import pyarrow as pa import pyarrow.parquet as pq -from ..utils.perf_timer import PerfTimer +from webviz_pkg.core_utils.perf_timer import PerfTimer from ._helpers import SumoEnsemble from .parameter_types import ( EnsembleParameter, diff --git a/backend/src/services/sumo_access/parameter_types.py b/backend_py/primary/primary/services/sumo_access/parameter_types.py similarity index 100% rename from backend/src/services/sumo_access/parameter_types.py rename to backend_py/primary/primary/services/sumo_access/parameter_types.py diff --git a/backend/src/services/sumo_access/polygons_access.py b/backend_py/primary/primary/services/sumo_access/polygons_access.py similarity index 98% rename from backend/src/services/sumo_access/polygons_access.py rename to backend_py/primary/primary/services/sumo_access/polygons_access.py index cb65a225c..81bc44018 100644 --- a/backend/src/services/sumo_access/polygons_access.py +++ b/backend_py/primary/primary/services/sumo_access/polygons_access.py @@ -6,7 +6,7 @@ import xtgeo from fmu.sumo.explorer.objects import PolygonsCollection -from src.services.utils.perf_timer import PerfTimer +from webviz_pkg.core_utils.perf_timer import PerfTimer from ._helpers import SumoEnsemble from .polygons_types import PolygonsMeta diff --git a/backend/src/services/sumo_access/polygons_types.py b/backend_py/primary/primary/services/sumo_access/polygons_types.py similarity index 100% rename from backend/src/services/sumo_access/polygons_types.py rename to backend_py/primary/primary/services/sumo_access/polygons_types.py diff --git a/backend/src/backend/user_session/routers/__init__.py b/backend_py/primary/primary/services/sumo_access/queries/__init__.py similarity index 100% rename from backend/src/backend/user_session/routers/__init__.py rename to backend_py/primary/primary/services/sumo_access/queries/__init__.py diff --git a/backend/src/services/sumo_access/queries/case.py b/backend_py/primary/primary/services/sumo_access/queries/case.py similarity index 100% rename from backend/src/services/sumo_access/queries/case.py rename to backend_py/primary/primary/services/sumo_access/queries/case.py diff --git a/backend/src/services/sumo_access/queries/cpgrid.py b/backend_py/primary/primary/services/sumo_access/queries/cpgrid.py similarity index 100% rename from backend/src/services/sumo_access/queries/cpgrid.py rename to backend_py/primary/primary/services/sumo_access/queries/cpgrid.py diff --git a/backend/src/services/sumo_access/rft_access.py b/backend_py/primary/primary/services/sumo_access/rft_access.py similarity index 99% rename from backend/src/services/sumo_access/rft_access.py rename to backend_py/primary/primary/services/sumo_access/rft_access.py index be5b49d4c..602383947 100644 --- a/backend/src/services/sumo_access/rft_access.py +++ b/backend_py/primary/primary/services/sumo_access/rft_access.py @@ -7,9 +7,9 @@ import pyarrow.compute as pc import pyarrow.parquet as pq from fmu.sumo.explorer.objects import Case, TableCollection +from webviz_pkg.core_utils.perf_timer import PerfTimer from ._helpers import SumoEnsemble -from ..utils.perf_timer import PerfTimer from .rft_types import RftInfo, RftRealizationData LOGGER = logging.getLogger(__name__) diff --git a/backend/src/services/sumo_access/rft_types.py b/backend_py/primary/primary/services/sumo_access/rft_types.py similarity index 100% rename from backend/src/services/sumo_access/rft_types.py rename to backend_py/primary/primary/services/sumo_access/rft_types.py diff --git a/backend/src/services/sumo_access/seismic_access.py b/backend_py/primary/primary/services/sumo_access/seismic_access.py similarity index 100% rename from backend/src/services/sumo_access/seismic_access.py rename to backend_py/primary/primary/services/sumo_access/seismic_access.py diff --git a/backend/src/services/sumo_access/seismic_types.py b/backend_py/primary/primary/services/sumo_access/seismic_types.py similarity index 100% rename from backend/src/services/sumo_access/seismic_types.py rename to backend_py/primary/primary/services/sumo_access/seismic_types.py diff --git a/backend/src/services/sumo_access/summary_access.py b/backend_py/primary/primary/services/sumo_access/summary_access.py similarity index 98% rename from backend/src/services/sumo_access/summary_access.py rename to backend_py/primary/primary/services/sumo_access/summary_access.py index 225c8529b..cb1077028 100644 --- a/backend/src/services/sumo_access/summary_access.py +++ b/backend_py/primary/primary/services/sumo_access/summary_access.py @@ -6,11 +6,11 @@ import pyarrow as pa import pyarrow.compute as pc from fmu.sumo.explorer.objects import Case, TableCollection, Table +from webviz_pkg.core_utils.perf_timer import PerfTimer -from src.services.utils.arrow_helpers import sort_table_on_real_then_date, is_date_column_monotonically_increasing -from src.services.utils.arrow_helpers import find_first_non_increasing_date_pair -from src.services.utils.perf_timer import PerfTimer -from src.services.service_exceptions import ( +from primary.services.utils.arrow_helpers import sort_table_on_real_then_date, is_date_column_monotonically_increasing +from primary.services.utils.arrow_helpers import find_first_non_increasing_date_pair +from primary.services.service_exceptions import ( Service, NoDataError, InvalidDataError, diff --git a/backend/src/services/sumo_access/summary_types.py b/backend_py/primary/primary/services/sumo_access/summary_types.py similarity index 100% rename from backend/src/services/sumo_access/summary_types.py rename to backend_py/primary/primary/services/sumo_access/summary_types.py diff --git a/backend/src/services/sumo_access/sumo_explore.py b/backend_py/primary/primary/services/sumo_access/sumo_explore.py similarity index 100% rename from backend/src/services/sumo_access/sumo_explore.py rename to backend_py/primary/primary/services/sumo_access/sumo_explore.py diff --git a/backend/src/services/sumo_access/surface_access.py b/backend_py/primary/primary/services/sumo_access/surface_access.py similarity index 98% rename from backend/src/services/sumo_access/surface_access.py rename to backend_py/primary/primary/services/sumo_access/surface_access.py index 90f7a8ab3..d0353be1b 100644 --- a/backend/src/services/sumo_access/surface_access.py +++ b/backend_py/primary/primary/services/sumo_access/surface_access.py @@ -8,8 +8,8 @@ from fmu.sumo.explorer import TimeFilter, TimeType from fmu.sumo.explorer.objects import SurfaceCollection, Surface -from src.services.utils.perf_timer import PerfTimer -from src.services.utils.statistic_function import StatisticFunction +from webviz_pkg.core_utils.perf_timer import PerfTimer +from primary.services.utils.statistic_function import StatisticFunction from ._helpers import SumoEnsemble from .surface_types import SurfaceMeta, XtgeoSurfaceIntersectionResult, XtgeoSurfaceIntersectionPolyline diff --git a/backend/src/services/sumo_access/surface_types.py b/backend_py/primary/primary/services/sumo_access/surface_types.py similarity index 100% rename from backend/src/services/sumo_access/surface_types.py rename to backend_py/primary/primary/services/sumo_access/surface_types.py diff --git a/backend/src/services/sumo_access/table_access.py b/backend_py/primary/primary/services/sumo_access/table_access.py similarity index 100% rename from backend/src/services/sumo_access/table_access.py rename to backend_py/primary/primary/services/sumo_access/table_access.py diff --git a/backend/src/services/sumo_access/well_completions_access.py b/backend_py/primary/primary/services/sumo_access/well_completions_access.py similarity index 100% rename from backend/src/services/sumo_access/well_completions_access.py rename to backend_py/primary/primary/services/sumo_access/well_completions_access.py diff --git a/backend/src/services/sumo_access/well_completions_types.py b/backend_py/primary/primary/services/sumo_access/well_completions_types.py similarity index 100% rename from backend/src/services/sumo_access/well_completions_types.py rename to backend_py/primary/primary/services/sumo_access/well_completions_types.py diff --git a/backend/src/services/surface_query_service/surface_query_service.py b/backend_py/primary/primary/services/surface_query_service/surface_query_service.py similarity index 97% rename from backend/src/services/surface_query_service/surface_query_service.py rename to backend_py/primary/primary/services/surface_query_service/surface_query_service.py index ffefda94c..c6ba807ff 100644 --- a/backend/src/services/surface_query_service/surface_query_service.py +++ b/backend_py/primary/primary/services/surface_query_service/surface_query_service.py @@ -9,8 +9,8 @@ from pydantic import BaseModel from sumo.wrapper import SumoClient -from src import config -from src.services.service_exceptions import AuthorizationError, Service +from primary import config +from primary.services.service_exceptions import AuthorizationError, Service LOGGER = logging.getLogger(__name__) diff --git a/backend_py/primary/primary/services/user_session_manager/_background_tasks.py b/backend_py/primary/primary/services/user_session_manager/_background_tasks.py new file mode 100644 index 000000000..3c0c45d8f --- /dev/null +++ b/backend_py/primary/primary/services/user_session_manager/_background_tasks.py @@ -0,0 +1,37 @@ +import logging +import asyncio +from typing import Coroutine + + +LOGGER = logging.getLogger(__name__) + +_background_tasks: set[asyncio.Task] = set() + + +def _task_done_cb(task: asyncio.Task) -> None: + # To prevent keeping references to finished tasks forever, make each task remove its + # own reference from the set after completion + _background_tasks.discard(task) + + # Look for exceptions and log them + try: + # Also marks that the exception has been handled + exc = task.exception() + if exc: + LOGGER.exception("Background task raised an exception", exc_info=exc) + except asyncio.exceptions.CancelledError: + pass + + +def run_in_background_task(coroutine: Coroutine) -> asyncio.Task: + """ + Create a background task and schedule it to run in a fire-and-forget fashion + """ + task = asyncio.create_task(coroutine) + + # Add task to the set, creating a strong reference to the task, which prevents it from being garbage collected + # See https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task + _background_tasks.add(task) + + task.add_done_callback(_task_done_cb) + return task diff --git a/backend_py/primary/primary/services/user_session_manager/_radix_helpers.py b/backend_py/primary/primary/services/user_session_manager/_radix_helpers.py new file mode 100644 index 000000000..1687d00cc --- /dev/null +++ b/backend_py/primary/primary/services/user_session_manager/_radix_helpers.py @@ -0,0 +1,206 @@ +import asyncio +import logging +import os +from typing import List, Literal + +import httpx +from pydantic import BaseModel, TypeAdapter + +LOGGER = logging.getLogger(__name__) + + +# This is a bit of a hack, but it's one way to know if we're running in Radix or locally +IS_ON_RADIX_PLATFORM = True if os.getenv("RADIX_APP") is not None else False +print(f"{IS_ON_RADIX_PLATFORM=}") + + +# Notes on RadixResourceRequests: +# * cpu: typical units are 'm' or no unit. '100m' means 100 milli-cpu, which is 0.1 cpu. 1000m is 1 cpu +# * memory: typical units are 'Mi' or 'Gi'. 500Mi means 500 Mebibytes, 4Gi means 4 Gibibyte (i.e. 4 * 1024^3 bytes) +class RadixResourceRequests(BaseModel): + cpu: str + memory: str + + +# Notes on RadixJobState: +# * The 'Waiting' status is not documented, but it seems to be the status of a job that has been created but not yet running +# * We're not always getting a job status, in particular when querying the status of a named job, so include a None entry for status +# * Sometimes we get an extra (undocumented) field returned, 'message' +class RadixJobState(BaseModel): + name: str + status: Literal["Waiting", "Running", "Succeeded", "Stopped", "Failed"] | None = None + started: str | None = None + ended: str | None = None + message: str | None = None + + +async def create_new_radix_job( + job_component_name: str, job_scheduler_port: int, resource_req: RadixResourceRequests +) -> str | None: + LOGGER.debug(f"create_new_radix_job() - {job_component_name=}, {resource_req=}") + + radix_job_manager_url = f"http://{job_component_name}:{job_scheduler_port}/api/v1/jobs" + + # Setting memory request equal to memory limit on purpose + # Also, cpu limit is omitted on purpose. + # Following advice here: + # https://home.robusta.dev/blog/kubernetes-memory-limit + # https://home.robusta.dev/blog/stop-using-cpu-limits + # + # Note that this might not be the best solution if we end up running golang apps inside the jobs since there + # we might want to auto discover the number of available cpus and set the GOMAXPROCS environment variable accordingly. + # As of now, it seems that it's the cpu limit value that will be picked up by for example by automaxprocs. + request_body = { + "resources": { + "requests": { + "memory": resource_req.memory, + "cpu": resource_req.cpu, + }, + "limits": { + "memory": resource_req.memory, + }, + } + } + + async with httpx.AsyncClient() as client: + try: + response = await client.post(url=radix_job_manager_url, json=request_body) + response.raise_for_status() + except httpx.RequestError as e: + LOGGER.error(f"Error creating radix job, request error occurred for POST to: {e.request.url}") + return None + except httpx.HTTPStatusError as e: + LOGGER.error(f"Error creating radix job, HTTP error {e.response.status_code} for POST to {e.request.url}") + return None + + # According to the docs it seems we should be getting a json back that contains + # a status field, which should be "Running" if the job was started successfully. + # Apparently this is not the case, as of Feb 2024, the only useful piece of information we're + # getting back from this call is the name of the newly created job. + response_dict = response.json() + + LOGGER.debug("------") + LOGGER.debug(f"{response_dict=}") + LOGGER.debug("------") + + radix_job_name = response_dict["name"] + LOGGER.debug(f"create_new_radix_job() - new job created {radix_job_name=}") + + return radix_job_name + + +async def get_radix_job_state( + job_component_name: str, job_scheduler_port: int, radix_job_name: str +) -> RadixJobState | None: + LOGGER.debug(f"get_radix_job_state() - {job_component_name=}, {radix_job_name=}") + + url = f"http://{job_component_name}:{job_scheduler_port}/api/v1/jobs/{radix_job_name}" + + async with httpx.AsyncClient() as client: + try: + response = await client.get(url=url) + response.raise_for_status() + except httpx.HTTPError as exception: + LOGGER.debug(f"get_radix_job_state() - could not get job state {exception=}") + return None + + # LOGGER.debug("------") + # LOGGER.debug(f"{response.json()=}") + # LOGGER.debug("------") + + # Note that the response we're getting back does not always contain an entry for status + # Therefore we're allowing None for the status field of RadixJobState + radix_job_state = RadixJobState.model_validate_json(response.content) + LOGGER.debug(f"get_radix_job_state() - got job state {radix_job_state=}") + return radix_job_state + + +async def is_radix_job_running(job_component_name: str, job_scheduler_port: int, radix_job_name: str) -> bool: + radix_job_state = await get_radix_job_state(job_component_name, job_scheduler_port, radix_job_name) + if radix_job_state and radix_job_state.status == "Running": + return True + + return False + + +async def get_all_radix_jobs(job_component_name: str, job_scheduler_port: int) -> List[RadixJobState]: + LOGGER.debug(f"get_all_radix_jobs() - {job_component_name=}") + + url = f"http://{job_component_name}:{job_scheduler_port}/api/v1/jobs" + async with httpx.AsyncClient() as client: + try: + response = await client.get(url) + response.raise_for_status() + except httpx.RequestError as e: + LOGGER.error(f"Error getting radix jobs, request error occurred for GET to: {e.request.url}") + return [] + except httpx.HTTPStatusError as e: + LOGGER.error(f"Error getting radix jobs, HTTP error {e.response.status_code} for GET to {e.request.url}") + return [] + + # LOGGER.debug("------") + # LOGGER.debug(f"{response.json()=}") + # LOGGER.debug("------") + + tadapter = TypeAdapter(List[RadixJobState]) + ret_list = tadapter.validate_json(response.content) + + LOGGER.debug(f"get_all_radix_jobs() - got list with {len(ret_list)} jobs") + + return ret_list + + +async def delete_named_radix_job(job_component_name: str, job_scheduler_port: int, radix_job_name: str) -> bool: + async with httpx.AsyncClient() as client: + return await _delete_named_radix_job_with_client(client, job_component_name, job_scheduler_port, radix_job_name) + + +async def _delete_named_radix_job_with_client( + client: httpx.AsyncClient, job_component_name: str, job_scheduler_port: int, radix_job_name: str +) -> bool: + LOGGER.debug(f"_delete_named_radix_job_with_client() - {job_component_name=}, {radix_job_name=}") + + url = f"http://{job_component_name}:{job_scheduler_port}/api/v1/jobs/{radix_job_name}" + try: + response = await client.delete(url) + response.raise_for_status() + except httpx.RequestError as e: + LOGGER.error(f"Error deleting radix job, request error occurred for DELETE to: {e.request.url}.") + return False + except httpx.HTTPStatusError as e: + LOGGER.error(f"Error deleting radix job, HTTP error {e.response.status_code} for DELETE to {e.request.url}") + return False + + LOGGER.debug(f"_delete_named_radix_job_with_client() - deleted radix job {radix_job_name=}") + return True + + +async def delete_all_radix_jobs(job_component_name: str, job_scheduler_port: int) -> None: + LOGGER.debug(f"delete_all_radix_jobs() - {job_component_name=}") + + job_list = await get_all_radix_jobs(job_component_name, job_scheduler_port) + if not job_list: + LOGGER.debug(f"delete_all_radix_jobs() - no jobs to delete") + return + + delete_coros_arr = [] + + async with httpx.AsyncClient() as client: + for job in job_list: + radix_job_name = job.name + LOGGER.debug(f"delete_all_radix_jobs() - deleting job {radix_job_name}") + del_coro = _delete_named_radix_job_with_client( + client=client, + job_component_name=job_component_name, + job_scheduler_port=job_scheduler_port, + radix_job_name=radix_job_name, + ) + delete_coros_arr.append(del_coro) + + result_arr = await asyncio.gather(*delete_coros_arr) + + # LOGGER.debug("------") + # LOGGER.debug(f"{result_arr=}") + # LOGGER.debug("------") + + LOGGER.debug(f"delete_all_radix_jobs() - finished") diff --git a/backend_py/primary/primary/services/user_session_manager/_user_session_directory.py b/backend_py/primary/primary/services/user_session_manager/_user_session_directory.py new file mode 100644 index 000000000..328a6d330 --- /dev/null +++ b/backend_py/primary/primary/services/user_session_manager/_user_session_directory.py @@ -0,0 +1,180 @@ +import logging +from dataclasses import dataclass +from enum import Enum + +import redis + +from primary import config + +LOGGER = logging.getLogger(__name__) + + +class SessionRunState(str, Enum): + CREATING_RADIX_JOB = "CREATING_RADIX_JOB" + WAITING_TO_COME_ONLINE = "WAITING_TO_COME_ONLINE" + RUNNING = "RUNNING" + + +_USER_SESSIONS_REDIS_PREFIX = "user-session" + + +@dataclass(frozen=True, kw_only=True) +class JobAddress: + user_id: str + job_component_name: str + instance_str: str + + +def _encode_redis_hash_name_str(address: JobAddress) -> str: + if not address.user_id: + raise ValueError("address.user_id cannot be empty") + if address.user_id.find(":") != -1: + raise ValueError("address.user_id cannot contain ':'") + + return f"{_USER_SESSIONS_REDIS_PREFIX}:{address.user_id}:{address.job_component_name}:{address.instance_str}" + + +def _decode_redis_hash_name_str(hash_name_str: str) -> JobAddress: + key_parts = hash_name_str.split(":") + if len(key_parts) != 4: + raise ValueError(f"Unexpected hash_name_str format {hash_name_str=}") + if key_parts[0] != _USER_SESSIONS_REDIS_PREFIX: + raise ValueError(f"Unexpected hash_name_str format, wrong prefix, {hash_name_str=}") + if not key_parts[1]: + raise ValueError(f"Unexpected hash_name_str format, empty user_id, {hash_name_str=}") + if not key_parts[2]: + raise ValueError(f"Unexpected hash_name_str format, empty job_component_name, {hash_name_str=}") + + return JobAddress(user_id=key_parts[1], job_component_name=key_parts[2], instance_str=key_parts[3]) + + +class SessionInfoUpdater: + def __init__(self, redis_client: redis.Redis, user_id: str, job_component_name: str, instance_str: str) -> None: + self._redis_client = redis_client + self._user_id = user_id + self._job_component_name = job_component_name + self._instance_str = instance_str + + def delete_all_state(self) -> None: + hash_name = self._make_hash_name() + self._redis_client.delete(hash_name) + + def set_state_creating(self) -> None: + hash_name = self._make_hash_name() + self._redis_client.hset( + name=hash_name, + mapping={ + "state": SessionRunState.CREATING_RADIX_JOB, + "radix_job_name": "", + }, + ) + + def set_state_waiting(self, radix_job_name: str) -> None: + hash_name = self._make_hash_name() + self._redis_client.hset( + name=hash_name, + mapping={ + "state": SessionRunState.WAITING_TO_COME_ONLINE, + "radix_job_name": radix_job_name, + }, + ) + + def set_state_running(self) -> None: + hash_name = self._make_hash_name() + self._redis_client.hset( + name=hash_name, + mapping={ + "state": SessionRunState.RUNNING, + }, + ) + + def _make_hash_name(self) -> str: + addr = JobAddress( + user_id=self._user_id, job_component_name=self._job_component_name, instance_str=self._instance_str + ) + return _encode_redis_hash_name_str(addr) + + +@dataclass(frozen=True, kw_only=True) +class SessionInfo: + job_component_name: str + instance_str: str + run_state: SessionRunState + radix_job_name: str | None + + +class UserSessionDirectory: + def __init__(self, user_id: str) -> None: + self._user_id = user_id + self._redis_client = redis.Redis.from_url(config.REDIS_USER_SESSION_URL, decode_responses=True) + + def get_session_info(self, job_component_name: str, instance_str: str) -> SessionInfo | None: + addr = JobAddress(user_id=self._user_id, job_component_name=job_component_name, instance_str=instance_str) + hash_name = _encode_redis_hash_name_str(addr) + value_dict = self._redis_client.hgetall(name=hash_name) + if not value_dict: + return None + + state_str = value_dict.get("state") + radix_job_name = value_dict.get("radix_job_name") + if not state_str: + return None + + run_state = SessionRunState(state_str) + return SessionInfo( + job_component_name=job_component_name, + instance_str=instance_str, + run_state=run_state, + radix_job_name=radix_job_name, + ) + + def get_session_info_arr(self, job_component_name: str | None) -> list[SessionInfo]: + if job_component_name is None: + job_component_name = "*" + + pattern = f"{_USER_SESSIONS_REDIS_PREFIX}:{self._user_id}:{job_component_name}:*" + LOGGER.debug(f"Redis scan pattern pattern {pattern=}") + + ret_list: list[SessionInfo] = [] + for key in self._redis_client.scan_iter(pattern): + LOGGER.debug(f"{key=}") + job_address = _decode_redis_hash_name_str(key) + if job_address.user_id != self._user_id: + raise ValueError(f"Unexpected key format, mismatch in user_id {key=}") + matched_job_component_name = job_address.job_component_name + matched_instance_str = job_address.instance_str + job_info = self.get_session_info(matched_job_component_name, matched_instance_str) + if job_info is not None: + ret_list.append(job_info) + + return ret_list + + def delete_session_info(self, job_component_name: str | None) -> None: + if job_component_name is None: + job_component_name = "*" + + pattern = f"{_USER_SESSIONS_REDIS_PREFIX}:{self._user_id}:{job_component_name}:*" + LOGGER.debug(f"Redis scan pattern pattern {pattern=}") + + key_list = [] + for key in self._redis_client.scan_iter(pattern): + LOGGER.debug(f"{key=}") + key_list.append(key) + + self._redis_client.delete(*key_list) + + def make_lock_key(self, job_component_name: str, instance_str: str) -> str: + addr = JobAddress(user_id=self._user_id, job_component_name=job_component_name, instance_str=instance_str) + hash_name = _encode_redis_hash_name_str(addr) + return f"{hash_name}:lock" + + def create_session_info_updater(self, job_component_name: str, instance_str: str) -> SessionInfoUpdater: + return SessionInfoUpdater( + redis_client=self._redis_client, + user_id=self._user_id, + job_component_name=job_component_name, + instance_str=instance_str, + ) + + def get_redis_client(self) -> redis.Redis: + return self._redis_client diff --git a/backend_py/primary/primary/services/user_session_manager/_util_classes.py b/backend_py/primary/primary/services/user_session_manager/_util_classes.py new file mode 100644 index 000000000..e350eb9a5 --- /dev/null +++ b/backend_py/primary/primary/services/user_session_manager/_util_classes.py @@ -0,0 +1,47 @@ +import logging +import time +from contextlib import AbstractContextManager +from types import TracebackType + +from pottery import Redlock + +LOGGER = logging.getLogger(__name__) + + +class TimeCounter: + def __init__(self, duration_s: float) -> None: + self._start_s = time.perf_counter() + self._end_s = self._start_s + duration_s + + def elapsed_s(self) -> float: + return time.perf_counter() - self._start_s + + def remaining_s(self) -> float: + time_now = time.perf_counter() + remaining = self._end_s - time_now + return remaining if remaining > 0 else 0 + + +class LockReleasingContext(AbstractContextManager): + def __init__(self, acquired_lock: Redlock) -> None: + self._acquired_lock: Redlock = acquired_lock + + def __enter__(self) -> Redlock: + LOGGER.debug("LockReleasingContext.__enter__()") + return self._acquired_lock + + def __exit__( + self, _exc_type: type[BaseException] | None, _exc_value: BaseException | None, _traceback: TracebackType | None + ) -> bool | None: + LOGGER.debug("LockReleasingContext.__exit__() - releasing lock") + self._acquired_lock.release() + + # What is the correct return value here? + # If there was an exception, AND we want to suppress it, return True + return None + + # We may want to silence this exception, but not until we have control + # try: + # self._acquired_lock.release() + # except ReleaseUnlockedLock: + # pass diff --git a/backend_py/primary/primary/services/user_session_manager/user_session_manager.py b/backend_py/primary/primary/services/user_session_manager/user_session_manager.py new file mode 100644 index 000000000..b269c29b0 --- /dev/null +++ b/backend_py/primary/primary/services/user_session_manager/user_session_manager.py @@ -0,0 +1,317 @@ +import asyncio +import logging +from dataclasses import dataclass +from enum import Enum +from typing import Tuple + +import httpx +from pottery import Redlock + +from webviz_pkg.core_utils.perf_timer import PerfTimer + +from ._radix_helpers import IS_ON_RADIX_PLATFORM +from ._radix_helpers import create_new_radix_job, RadixResourceRequests +from ._radix_helpers import is_radix_job_running, delete_named_radix_job +from ._user_session_directory import SessionInfo, SessionRunState, UserSessionDirectory +from ._util_classes import LockReleasingContext, TimeCounter +from ._background_tasks import run_in_background_task + +LOGGER = logging.getLogger(__name__) + + +class UserComponent(str, Enum): + GRID3D_RI = "GRID3D_RI" + GRID3D_VTK = "GRID3D_VTK" + MOCK = "MOCK" + + +@dataclass(frozen=True, kw_only=True) +class _UserSessionDef: + # fmt:off + job_component_name: str # The job's component name in radix, or the service name in docker compose, e.g. "user-mock" + port: int # The port number for the radix job manager AND the actual port of the service. These must be the same for our current docker compose setup + resource_req: RadixResourceRequests # The resource requests for the radix job + # fmt:on + + +_USER_SESSION_DEFS: dict[UserComponent, _UserSessionDef] = { + UserComponent.MOCK: _UserSessionDef( + job_component_name="user-mock", port=8001, resource_req=RadixResourceRequests(cpu="100m", memory="200Mi") + ), + UserComponent.GRID3D_RI: _UserSessionDef( + job_component_name="user-grid3d-ri", port=8002, resource_req=RadixResourceRequests(cpu="200m", memory="400Mi") + ), + UserComponent.GRID3D_VTK: _UserSessionDef( + job_component_name="user-grid3d-vtk", port=8003, resource_req=RadixResourceRequests(cpu="200m", memory="400Mi") + ), +} + + +class UserSessionManager: + def __init__(self, user_id: str) -> None: + self._user_id = user_id + + async def get_or_create_session_async(self, user_component: UserComponent, instance_str: str | None) -> str | None: + timer = PerfTimer() + LOGGER.debug(f"Get or create user session for: {user_component=}, {instance_str=}") + + session_def = _USER_SESSION_DEFS[user_component] + effective_instance_str = instance_str if instance_str else "DEFAULT" + actual_service_port = session_def.port + + session_dir = UserSessionDirectory(self._user_id) + + # Note that currently the timeout values (approx_timeout_s) used here are purely experimental at the moment. + # We may be able to get some insights from the Radix team on this, but still this will have to + # be weighed up against how long a timeout is acceptable from a user standpoint. + + existing_session_info = await _get_info_for_running_session( + session_dir=session_dir, + job_component_name=session_def.job_component_name, + job_scheduler_port=session_def.port, + instance_str=effective_instance_str, + actual_service_port=actual_service_port, + approx_timeout_s=40, + ) + if existing_session_info: + session_url = f"http://{existing_session_info.radix_job_name}:{actual_service_port}" + LOGGER.info( + f"Got existing user session in: {timer.elapsed_ms()}ms ({user_component=}, {instance_str=}, {session_url=})" + ) + return session_url + + LOGGER.debug( + f"Unable to get existing user session, starting new session for: {user_component=}, {instance_str=}" + ) + new_session_info = await _create_new_session( + session_dir=session_dir, + job_component_name=session_def.job_component_name, + job_scheduler_port=session_def.port, + resource_req=session_def.resource_req, + instance_str=effective_instance_str, + actual_service_port=actual_service_port, + approx_timeout_s=30, + ) + + if not new_session_info: + LOGGER.error(f"Failed to create new user session for: {user_component=}, {instance_str=}") + return None + + session_url = f"http://{new_session_info.radix_job_name}:{actual_service_port}" + LOGGER.info( + f"Created new user session in: {timer.elapsed_ms()}ms ({user_component=}, {instance_str=}, {session_url=})" + ) + + return session_url + + +# Look for existing session info, possibly waiting for a partially created session to come online +# +# Reasons why this function may return None +# 1. We found the session, but it's not running yet (it's in the process of being created/spinning +# up or has got stuck while being created) +# 2. We did not find the session in the directory, and it needs to be created +# 3. We found the session and the info says it is running, but our verification probe against +# radix or a probe against the service's health endpoint says it's not +async def _get_info_for_running_session( + session_dir: UserSessionDirectory, + job_component_name: str, + job_scheduler_port: int, + instance_str: str, + actual_service_port: int, + approx_timeout_s: float, +) -> SessionInfo | None: + + time_counter = TimeCounter(approx_timeout_s) + sleep_time_s = 1 + num_calls = 1 + + session_info = session_dir.get_session_info(job_component_name, instance_str) + if not session_info: + return None + + # Given that we found info on the session and it's not in the running state, we will try and wait for it to come online. + # The job/session might be in the process of being created and spinning up, so we will try and wait for it. + # How much time should we spend here before giving up? Currently we just consume an approximate timeout here, and + # leave it to the caller to decide how much time should be allowed. + while session_info and session_info.run_state != SessionRunState.RUNNING: + elapsed_s = time_counter.elapsed_s() + if elapsed_s + sleep_time_s > approx_timeout_s: + LOGGER.debug( + "Giving up waiting for user session to enter running state after {num_calls} failed attempts, time spent: {elapsed_s:.2f}s" + ) + return None + + num_calls += 1 + LOGGER.debug(f"Waiting for user session to enter running state, attempt {num_calls}") + await asyncio.sleep(sleep_time_s) + session_info = session_dir.get_session_info(job_component_name, instance_str) + + # So by now the session either evaporated from the directory, or it has entered the running state + # Bail out if it is gone or for some reason is missing the crucial radix job name + if not session_info or not session_info.radix_job_name: + return None + + if IS_ON_RADIX_PLATFORM: + LOGGER.debug("Found user session, verifying its existence against radix job manager") + radix_job_is_running = await is_radix_job_running( + job_component_name, job_scheduler_port, session_info.radix_job_name + ) + if not radix_job_is_running: + LOGGER.debug("Could not find running job in radix job manager") + return None + + # Can we afford the more extensive live check against the service's health endpoint? + live_endpoint = f"http://{session_info.radix_job_name}:{actual_service_port}/health/live" + LOGGER.debug(f"Job is running in radix, probing health endpoint of contained service at: {live_endpoint=}") + is_ready, msg = await call_health_endpoint(live_endpoint, timeout_s=2) + if not is_ready: + LOGGER.debug(f"Contained service seems to be dead {msg=}") + return None + + LOGGER.debug("Contained service is alive") + + return session_info + + +# Try and create a new session with a new radix job and wait for it to come online +async def _create_new_session( + session_dir: UserSessionDirectory, + job_component_name: str, + job_scheduler_port: int, + resource_req: RadixResourceRequests, + instance_str: str, + actual_service_port: int, + approx_timeout_s: float, +) -> SessionInfo | None: + + time_counter = TimeCounter(approx_timeout_s) + + # We're going to be modifying the directory which means we need to acquire a lock + redis_client = session_dir.get_redis_client() + lock_key_name: str = session_dir.make_lock_key(job_component_name, instance_str) + + # May have to look closer into the auto release timeout here + # Using redlock in our case is probably a bit overkill, there's a ready implementation + # For our use case it may be better to implement our own locking akin to this: https://redis.io/commands/set/#patterns + LOGGER.debug(f"Trying to acquire distributed redlock {lock_key_name=}") + distributed_lock = Redlock(key=lock_key_name, masters={redis_client}, auto_release_time=approx_timeout_s + 30) + got_the_lock = distributed_lock.acquire(blocking=False, timeout=-1) + if not got_the_lock: + LOGGER.error(f"Failed to acquire distributed redlock {lock_key_name=}") + return None + + with LockReleasingContext(distributed_lock): + # Now that we have the lock, kill off existing job info and start creating new job + # But before proceeding, grab the old session info so we can try and whack the radix job if possible + old_session_info = session_dir.get_session_info(job_component_name, instance_str) + session_info_updater = session_dir.create_session_info_updater(job_component_name, instance_str) + session_info_updater.delete_all_state() + session_info_updater.set_state_creating() + + if IS_ON_RADIX_PLATFORM: + if old_session_info and old_session_info.radix_job_name: + LOGGER.debug(f"Trying to delete old radix job {old_session_info.radix_job_name=}") + run_in_background_task( + delete_named_radix_job(job_component_name, job_scheduler_port, old_session_info.radix_job_name) + ) + + LOGGER.debug(f"Creating new job using radix job manager ({job_component_name=}, {job_scheduler_port=})") + new_radix_job_name = await create_new_radix_job(job_component_name, job_scheduler_port, resource_req) + if new_radix_job_name is None: + LOGGER.error(f"Failed to create new job in radix ({job_component_name=}, {job_scheduler_port=})") + session_info_updater.delete_all_state() + return None + + LOGGER.debug(f"New radix job created, will try and wait for it to come alive {new_radix_job_name=}") + session_info_updater.set_state_waiting(new_radix_job_name) + else: + LOGGER.debug("Running locally, will not create a radix job") + new_radix_job_name = job_component_name + session_info_updater.set_state_waiting(new_radix_job_name) + + LOGGER.debug(f"lock status, {distributed_lock.locked()=}") + + # It is a bit hard to decide on how long we should wait here before giving up. + # This must be aligned with the auto release time for our lock and also the polling for session info that is done against redis + ready_endpoint = f"http://{new_radix_job_name}:{actual_service_port}/health/ready" + probe_time_budget_s = time_counter.remaining_s() + is_ready, msg = await call_health_endpoint_with_retries(ready_endpoint, probe_time_budget_s) + if not is_ready: + LOGGER.error("The newly created radix job failed to come online, giving up and deleting it") + session_info_updater.delete_all_state() + # Should delete the radix job as well + run_in_background_task(delete_named_radix_job(job_component_name, job_scheduler_port, new_radix_job_name)) + return None + + session_info_updater.set_state_running() + + session_info = session_dir.get_session_info(job_component_name, instance_str) + if not session_info: + LOGGER.error("Failed to get session info after creating new radix job") + return None + + if session_info.run_state != SessionRunState.RUNNING: + LOGGER.error(f"Unexpected session info, expected run_state to be running but got {session_info.run_state=}") + return None + + if not session_info.radix_job_name: + LOGGER.error("Unexpected empty radix_job_name in session info after creating new radix job") + return None + + LOGGER.debug(f"New radix job created and online: {session_info.radix_job_name=}") + + return session_info + + +async def call_health_endpoint_with_retries(health_url: str, stop_after_delay_s: float) -> Tuple[bool, str]: + LOGGER.debug(f"call_health_endpoint_with_retries() - {health_url=} {stop_after_delay_s=}") + + target_request_timeout_s = 3 + min_request_timeout_s = 1 + sleep_time_s = 1 + + time_counter = TimeCounter(stop_after_delay_s) + num_calls = 0 + + async with httpx.AsyncClient(timeout=target_request_timeout_s) as client: + while True: + request_timeout_s = min(target_request_timeout_s, max(min_request_timeout_s, time_counter.remaining_s())) + LOGGER.debug(f"call_health_endpoint_with_retries() - querying endpoint with {request_timeout_s=}") + success, msg = await _call_health_endpoint_with_client(client, health_url, request_timeout_s) + num_calls += 1 + if success: + LOGGER.debug( + f"call_health_endpoint_with_retries() - succeeded on attempt {num_calls}, time spent: {time_counter.elapsed_s():.2f}s, {msg=}" + ) + return success, msg + + LOGGER.debug(f"call_health_endpoint_with_retries() - attempt {num_calls} failed with error: {msg=}") + + elapsed_s = time_counter.elapsed_s() + if elapsed_s + sleep_time_s + min_request_timeout_s > stop_after_delay_s: + LOGGER.debug( + f"call_health_endpoint_with_retries() - giving up after {num_calls} failed attempts, time spent: {elapsed_s:.2f}s" + ) + return False, f"Giving up after {num_calls} failed attempts, time spent: {elapsed_s:.2f}s" + + await asyncio.sleep(sleep_time_s) + + +async def call_health_endpoint(health_url: str, timeout_s: float) -> Tuple[bool, str]: + async with httpx.AsyncClient() as client: + return await _call_health_endpoint_with_client(client, health_url, timeout_s) + + +async def _call_health_endpoint_with_client( + client: httpx.AsyncClient, health_url: str, timeout_s: float +) -> Tuple[bool, str]: + try: + response = await client.get(url=health_url, timeout=timeout_s) + if response.status_code == 200: + return True, f"{response.text=}" + + return False, f"{response.status_code=}, {response.text=}" + + except httpx.RequestError as exception: + return False, f"Request error: {exception=}" diff --git a/backend/src/backend/user_session/routers/grid/__init__.py b/backend_py/primary/primary/services/utils/__init__.py similarity index 100% rename from backend/src/backend/user_session/routers/grid/__init__.py rename to backend_py/primary/primary/services/utils/__init__.py diff --git a/backend/src/services/utils/arrow_helpers.py b/backend_py/primary/primary/services/utils/arrow_helpers.py similarity index 100% rename from backend/src/services/utils/arrow_helpers.py rename to backend_py/primary/primary/services/utils/arrow_helpers.py diff --git a/backend/src/services/utils/authenticated_user.py b/backend_py/primary/primary/services/utils/authenticated_user.py similarity index 94% rename from backend/src/services/utils/authenticated_user.py rename to backend_py/primary/primary/services/utils/authenticated_user.py index 2b3561420..4e38d3ba7 100644 --- a/backend/src/services/utils/authenticated_user.py +++ b/backend_py/primary/primary/services/utils/authenticated_user.py @@ -2,7 +2,7 @@ from typing import Any, Optional, TypedDict -from src.services.service_exceptions import Service, AuthorizationError +from primary.services.service_exceptions import Service, AuthorizationError class AccessTokens(TypedDict): @@ -30,6 +30,9 @@ def __hash__(self) -> int: def __eq__(self, other: Any) -> bool: return isinstance(other, AuthenticatedUser) and self._user_id == other._user_id + def get_user_id(self) -> str: + return self._user_id + def get_username(self) -> str: return self._username diff --git a/backend/src/services/utils/statistic_function.py b/backend_py/primary/primary/services/utils/statistic_function.py similarity index 100% rename from backend/src/services/utils/statistic_function.py rename to backend_py/primary/primary/services/utils/statistic_function.py diff --git a/backend/src/services/utils/surface_orientation.py b/backend_py/primary/primary/services/utils/surface_orientation.py similarity index 100% rename from backend/src/services/utils/surface_orientation.py rename to backend_py/primary/primary/services/utils/surface_orientation.py diff --git a/backend/src/services/utils/surface_to_float32.py b/backend_py/primary/primary/services/utils/surface_to_float32.py similarity index 100% rename from backend/src/services/utils/surface_to_float32.py rename to backend_py/primary/primary/services/utils/surface_to_float32.py diff --git a/backend/src/services/utils/surface_to_png.py b/backend_py/primary/primary/services/utils/surface_to_png.py similarity index 100% rename from backend/src/services/utils/surface_to_png.py rename to backend_py/primary/primary/services/utils/surface_to_png.py diff --git a/backend/src/services/__init__.py b/backend_py/primary/primary/services/vds_access/__init__.py similarity index 100% rename from backend/src/services/__init__.py rename to backend_py/primary/primary/services/vds_access/__init__.py diff --git a/backend/src/services/vds_access/request_types.py b/backend_py/primary/primary/services/vds_access/request_types.py similarity index 100% rename from backend/src/services/vds_access/request_types.py rename to backend_py/primary/primary/services/vds_access/request_types.py diff --git a/backend/src/services/vds_access/response_types.py b/backend_py/primary/primary/services/vds_access/response_types.py similarity index 100% rename from backend/src/services/vds_access/response_types.py rename to backend_py/primary/primary/services/vds_access/response_types.py diff --git a/backend/src/services/vds_access/vds_access.py b/backend_py/primary/primary/services/vds_access/vds_access.py similarity index 99% rename from backend/src/services/vds_access/vds_access.py rename to backend_py/primary/primary/services/vds_access/vds_access.py index af79d88ba..acc99de4c 100644 --- a/backend/src/services/vds_access/vds_access.py +++ b/backend_py/primary/primary/services/vds_access/vds_access.py @@ -7,7 +7,7 @@ from requests_toolbelt.multipart.decoder import MultipartDecoder, BodyPart import httpx -from src import config +from primary import config from .response_types import VdsMetadata, VdsFenceMetadata from .request_types import ( diff --git a/backend/src/backend/utils/azure_monitor_setup.py b/backend_py/primary/primary/utils/azure_monitor_setup.py similarity index 100% rename from backend/src/backend/utils/azure_monitor_setup.py rename to backend_py/primary/primary/utils/azure_monitor_setup.py diff --git a/backend/src/backend/utils/exception_handlers.py b/backend_py/primary/primary/utils/exception_handlers.py similarity index 98% rename from backend/src/backend/utils/exception_handlers.py rename to backend_py/primary/primary/utils/exception_handlers.py index 0030cf9d4..988921077 100644 --- a/backend/src/backend/utils/exception_handlers.py +++ b/backend_py/primary/primary/utils/exception_handlers.py @@ -10,7 +10,7 @@ from starlette.responses import JSONResponse, Response from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY, HTTP_500_INTERNAL_SERVER_ERROR -from src.services.service_exceptions import ServiceLayerException +from primary.services.service_exceptions import ServiceLayerException def my_http_exception_handler(request: Request, exc: StarletteHTTPException) -> Response | JSONResponse: diff --git a/backend/src/backend/utils/logging_setup.py b/backend_py/primary/primary/utils/logging_setup.py similarity index 100% rename from backend/src/backend/utils/logging_setup.py rename to backend_py/primary/primary/utils/logging_setup.py diff --git a/backend/src/backend/utils/perf_metrics.py b/backend_py/primary/primary/utils/perf_metrics.py similarity index 97% rename from backend/src/backend/utils/perf_metrics.py rename to backend_py/primary/primary/utils/perf_metrics.py index 6c7ee71ad..24540a9c4 100644 --- a/backend/src/backend/utils/perf_metrics.py +++ b/backend_py/primary/primary/utils/perf_metrics.py @@ -1,6 +1,6 @@ from starlette.responses import MutableHeaders, Response -from src.services.utils.perf_timer import PerfTimer +from webviz_pkg.core_utils.perf_timer import PerfTimer class PerfMetrics: diff --git a/backend/pyproject.toml b/backend_py/primary/pyproject.toml similarity index 91% rename from backend/pyproject.toml rename to backend_py/primary/pyproject.toml index 37ea3f1d2..da59054f1 100644 --- a/backend/pyproject.toml +++ b/backend_py/primary/pyproject.toml @@ -1,7 +1,6 @@ [tool.poetry] -name = "backend" -version = "0.1.0" -description = "" +package-mode = false +name = "primary" authors = ["R&T Equinor", "Ceetron Solutions AS"] readme = "README.md" @@ -12,7 +11,7 @@ fastapi = "^0.103.1" uvicorn = "^0.20.0" msal = "1.20.0" # Lock version until we fix issues introduced by 1.21.0, see https://github.com/equinor/webviz/issues/105 starsessions = "^2.1.2" -redis = "^4.4.2" +redis = "^4.6.0" pyarrow = "^12.0.1" python-dotenv = "^0.21.0" pyjwt = "^2.6.0" @@ -21,13 +20,12 @@ numpy = "^1.24.1" orjson = "^3.8.10" pandas = {version = "2.0.1", extras = ["performance"]} httpx = "^0.24.0" -psutil = "^5.9.5" -vtk = "^9.2.6" fmu-sumo = "1.0.3" sumo-wrapper-python = "1.0.6" azure-monitor-opentelemetry = "^1.1.0" requests-toolbelt = "^1.0.0" - +pottery = "^3.0.0" +core_utils = {path = "../libs/core_utils", develop = true} [tool.poetry.group.dev.dependencies] black = "^22.12.0" diff --git a/backend/src/services/smda_access/__init__.py b/backend_py/primary/tests/__init__.py similarity index 100% rename from backend/src/services/smda_access/__init__.py rename to backend_py/primary/tests/__init__.py diff --git a/backend/tests/integration/services/conftest.py b/backend_py/primary/tests/integration/services/conftest.py similarity index 100% rename from backend/tests/integration/services/conftest.py rename to backend_py/primary/tests/integration/services/conftest.py diff --git a/backend/tests/integration/services/sumo_access/test_parameter_access.py b/backend_py/primary/tests/integration/services/sumo_access/test_parameter_access.py similarity index 100% rename from backend/tests/integration/services/sumo_access/test_parameter_access.py rename to backend_py/primary/tests/integration/services/sumo_access/test_parameter_access.py diff --git a/backend/src/services/smda_access/queries/__init__.py b/backend_py/primary/tests/unit/__init__.py similarity index 100% rename from backend/src/services/smda_access/queries/__init__.py rename to backend_py/primary/tests/unit/__init__.py diff --git a/backend/src/services/sumo_access/__init__.py b/backend_py/primary/tests/unit/services/__init__.py similarity index 100% rename from backend/src/services/sumo_access/__init__.py rename to backend_py/primary/tests/unit/services/__init__.py diff --git a/backend/tests/unit/services/smda_access/test_stratigraphy_utils.py b/backend_py/primary/tests/unit/services/smda_access/test_stratigraphy_utils.py similarity index 89% rename from backend/tests/unit/services/smda_access/test_stratigraphy_utils.py rename to backend_py/primary/tests/unit/services/smda_access/test_stratigraphy_utils.py index 13eab9066..913f50bf9 100644 --- a/backend/tests/unit/services/smda_access/test_stratigraphy_utils.py +++ b/backend_py/primary/tests/unit/services/smda_access/test_stratigraphy_utils.py @@ -1,12 +1,12 @@ from typing import List import pytest -from services.smda_access.stratigraphy_utils import ( +from primary.services.smda_access.stratigraphy_utils import ( sort_stratigraphic_names_by_hierarchy, sort_stratigraphic_units_by_hierarchy, ) -from services.smda_access.types import StratigraphicUnit, StratigraphicSurface, StratigraphicFeature -from services.smda_access.mocked_drogon_smda_access._mocked_stratigraphy_access import DROGON_STRAT_UNITS +from primary.services.smda_access.types import StratigraphicUnit, StratigraphicSurface, StratigraphicFeature +from primary.services.smda_access.mocked_drogon_smda_access._mocked_stratigraphy_access import DROGON_STRAT_UNITS @pytest.mark.parametrize( @@ -48,7 +48,7 @@ def test_sort_stratigraphic_units_by_hierarchy( ], ) def test_sort_stratigraphic_names_by_hierarchy( - strat_units: List[StratigraphicUnit], expected_output: List[StratigraphicUnit] + strat_units: List[StratigraphicUnit], expected_output: List[StratigraphicSurface] ) -> None: sorted_surfaces = sort_stratigraphic_names_by_hierarchy(strat_units) sorted_surface_names = [surf.name for surf in sorted_surfaces] diff --git a/backend/tests/unit/services/sumo_access/test_resampling.py b/backend_py/primary/tests/unit/services/sumo_access/test_resampling.py similarity index 99% rename from backend/tests/unit/services/sumo_access/test_resampling.py rename to backend_py/primary/tests/unit/services/sumo_access/test_resampling.py index 7f59d3031..b5475711c 100644 --- a/backend/tests/unit/services/sumo_access/test_resampling.py +++ b/backend_py/primary/tests/unit/services/sumo_access/test_resampling.py @@ -2,7 +2,7 @@ import pyarrow as pa import pyarrow.compute as pc -from services.sumo_access._resampling import ( +from primary.services.sumo_access._resampling import ( Frequency, generate_normalized_sample_dates, interpolate_backfill, diff --git a/backend/tests/unit/services/utils/test_arrow_helpers.py b/backend_py/primary/tests/unit/services/utils/test_arrow_helpers.py similarity index 88% rename from backend/tests/unit/services/utils/test_arrow_helpers.py rename to backend_py/primary/tests/unit/services/utils/test_arrow_helpers.py index 40108f117..a7feb2acd 100644 --- a/backend/tests/unit/services/utils/test_arrow_helpers.py +++ b/backend_py/primary/tests/unit/services/utils/test_arrow_helpers.py @@ -1,5 +1,6 @@ -from services.utils.arrow_helpers import is_date_column_monotonically_increasing, find_first_non_increasing_date_pair -from services.utils.arrow_helpers import detect_missing_realizations +from primary.services.utils.arrow_helpers import is_date_column_monotonically_increasing +from primary.services.utils.arrow_helpers import find_first_non_increasing_date_pair +from primary.services.utils.arrow_helpers import detect_missing_realizations import pyarrow as pa import numpy as np diff --git a/backend_py/user_grid3d_ri/Dockerfile b/backend_py/user_grid3d_ri/Dockerfile new file mode 100644 index 000000000..7deadac18 --- /dev/null +++ b/backend_py/user_grid3d_ri/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.11-slim + +RUN useradd --create-home --uid 1234 appuser +USER 1234 + +COPY --chown=appuser ./backend_py/user_grid3d_ri /home/appuser/backend_py/user_grid3d_ri + +WORKDIR /home/appuser/backend_py/user_grid3d_ri + +ENV PATH="${PATH}:/home/appuser/.local/bin" + +RUN pip install -r requirements.txt + +# Relevant uvicorn environment variables are: UVICORN_PORT, UVICORN_RELOAD +CMD ["uvicorn", "user_grid3d_ri_app:app", "--host", "0.0.0.0"] diff --git a/backend_py/user_grid3d_ri/requirements.txt b/backend_py/user_grid3d_ri/requirements.txt new file mode 100644 index 000000000..f0615cfd0 --- /dev/null +++ b/backend_py/user_grid3d_ri/requirements.txt @@ -0,0 +1,2 @@ +fastapi +uvicorn \ No newline at end of file diff --git a/backend_py/user_grid3d_ri/user_grid3d_ri_app.py b/backend_py/user_grid3d_ri/user_grid3d_ri_app.py new file mode 100644 index 000000000..aa34fa43d --- /dev/null +++ b/backend_py/user_grid3d_ri/user_grid3d_ri_app.py @@ -0,0 +1,55 @@ +import asyncio +import datetime +import logging +from typing import Annotated + +from fastapi import FastAPI +from fastapi import Query + +logging.basicConfig(format="%(asctime)s %(levelname)-3s [%(name)s]: %(message)s", datefmt="%H:%M:%S") +logging.getLogger().setLevel(logging.DEBUG) + +LOGGER = logging.getLogger(__name__) + + +app = FastAPI() + + +@app.get("/") +async def root() -> str: + ret_str = f"user-grid3d-ri is alive at this time: {datetime.datetime.now()}" + LOGGER.debug("Sending: ", ret_str) + return ret_str + + +# Probe if service is alive +# HTTP status code 200 means we're alive, all other status codes indicate trouble +# The response is only for debugging, and is basically ignored. +@app.get("/health/live") +async def health_live() -> str: + ret_str = f"LIVE at: {datetime.datetime.now()}" + LOGGER.debug(f"health_live() returning: {ret_str!r}") + return ret_str + + +# Probe if service is ready to receive requests +# HTTP status code 200 means we're ready, 500 (and all other status codes) signals we're not ready +# The response is only for debugging, and is basically ignored. +@app.get("/health/ready") +async def health_ready() -> str: + ret_str = f"READY at: {datetime.datetime.now()}" + LOGGER.debug(f"health_ready() returning: {ret_str!r}") + return ret_str + + +# Simulate doing some work +@app.get("/dowork") +async def dowork( + duration: Annotated[float, Query(description="Duration of work in seconds")] = 1.0, +) -> str: + LOGGER.debug(f"dowork() doing fake GRID3D work for: {duration=}s") + await asyncio.sleep(duration) + + ret_str = f"GRID3D work done at: {datetime.datetime.now()}" + LOGGER.debug(f"dowork() GRID3D returning: {ret_str!r}") + return ret_str diff --git a/backend_py/user_grid3d_vtk/Dockerfile b/backend_py/user_grid3d_vtk/Dockerfile new file mode 100644 index 000000000..199ffc8d1 --- /dev/null +++ b/backend_py/user_grid3d_vtk/Dockerfile @@ -0,0 +1,26 @@ +FROM python:3.11-slim + +RUN useradd --create-home --uid 1234 appuser +USER 1234 + +ENV PATH="${PATH}:/home/appuser/.local/bin" + +RUN python3 -m pip install --user pipx +RUN python3 -m pipx ensurepath +RUN pipx install poetry==1.8.2 + +ENV VIRTUAL_ENV=/home/appuser/venv +RUN python3 -m venv $VIRTUAL_ENV +ENV PATH="$VIRTUAL_ENV/bin:$PATH" + +WORKDIR /home/appuser/backend_py/user_grid3d_vtk + +COPY --chown=appuser ./backend_py/user_grid3d_vtk/pyproject.toml /home/appuser/backend_py/user_grid3d_vtk +COPY --chown=appuser ./backend_py/user_grid3d_vtk/poetry.lock /home/appuser/backend_py/user_grid3d_vtk +RUN poetry install --only main --no-root --no-directory + +COPY --chown=appuser ./backend_py/user_grid3d_vtk/user_grid3d_vtk /home/appuser/backend_py/user_grid3d_vtk/user_grid3d_vtk +RUN poetry install --only main + +# Relevant uvicorn environment variables are: UVICORN_PORT, UVICORN_RELOAD +CMD ["uvicorn", "user_grid3d_vtk.fastapi_app:app", "--host", "0.0.0.0"] diff --git a/backend_py/user_grid3d_vtk/poetry.lock b/backend_py/user_grid3d_vtk/poetry.lock new file mode 100644 index 000000000..97fcd2ff8 --- /dev/null +++ b/backend_py/user_grid3d_vtk/poetry.lock @@ -0,0 +1,271 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + +[[package]] +name = "anyio" +version = "3.7.1" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.7" +files = [ + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "fastapi" +version = "0.103.2" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.7" +files = [ + {file = "fastapi-0.103.2-py3-none-any.whl", hash = "sha256:3270de872f0fe9ec809d4bd3d4d890c6d5cc7b9611d721d6438f9dacc8c4ef2e"}, + {file = "fastapi-0.103.2.tar.gz", hash = "sha256:75a11f6bfb8fc4d2bec0bd710c2d5f2829659c0e8c0afd5560fdda6ce25ec653"}, +] + +[package.dependencies] +anyio = ">=3.7.1,<4.0.0" +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.27.0,<0.28.0" +typing-extensions = ">=4.5.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "pydantic" +version = "2.6.4" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.6.4-py3-none-any.whl", hash = "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"}, + {file = "pydantic-2.6.4.tar.gz", hash = "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.16.3" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.16.3" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, + {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, + {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, + {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, + {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, + {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, + {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, + {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, + {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, + {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, + {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, + {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, + {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, + {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "starlette" +version = "0.27.0" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.7" +files = [ + {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"}, + {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] + +[[package]] +name = "typing-extensions" +version = "4.10.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, +] + +[[package]] +name = "uvicorn" +version = "0.20.0" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.7" +files = [ + {file = "uvicorn-0.20.0-py3-none-any.whl", hash = "sha256:c3ed1598a5668208723f2bb49336f4509424ad198d6ab2615b7783db58d919fd"}, + {file = "uvicorn-0.20.0.tar.gz", hash = "sha256:a4e12017b940247f836bc90b72e725d7dfd0c8ed1c51eb365f5ba30d9f5127d8"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "1c1900c103a60137142a48f7698b943ffebf2eab40628b1bf69c65fa232af688" diff --git a/backend_py/user_grid3d_vtk/pyproject.toml b/backend_py/user_grid3d_vtk/pyproject.toml new file mode 100644 index 000000000..53cad62ac --- /dev/null +++ b/backend_py/user_grid3d_vtk/pyproject.toml @@ -0,0 +1,12 @@ +[tool.poetry] +package-mode = false +name = "user-grid3d-vtk" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.dependencies] +python = "^3.11" +fastapi = "^0.103.1" +uvicorn = "^0.20.0" diff --git a/backend_py/user_grid3d_vtk/user_grid3d_vtk/fastapi_app.py b/backend_py/user_grid3d_vtk/user_grid3d_vtk/fastapi_app.py new file mode 100644 index 000000000..b4de8582d --- /dev/null +++ b/backend_py/user_grid3d_vtk/user_grid3d_vtk/fastapi_app.py @@ -0,0 +1,55 @@ +import asyncio +import datetime +import logging +from typing import Annotated + +from fastapi import FastAPI +from fastapi import Query + +logging.basicConfig(format="%(asctime)s %(levelname)-3s [%(name)s]: %(message)s", datefmt="%H:%M:%S") +logging.getLogger().setLevel(logging.DEBUG) + +LOGGER = logging.getLogger(__name__) + + +app = FastAPI() + + +@app.get("/") +async def root() -> str: + ret_str = f"user-grid3d-vtk is alive at this time: {datetime.datetime.now()}" + LOGGER.debug("Sending: ", ret_str) + return ret_str + + +# Probe if service is alive +# HTTP status code 200 means we're alive, all other status codes indicate trouble +# The response is only for debugging, and is basically ignored. +@app.get("/health/live") +async def health_live() -> str: + ret_str = f"LIVE at: {datetime.datetime.now()}" + LOGGER.debug(f"health_live() returning: {ret_str!r}") + return ret_str + + +# Probe if service is ready to receive requests +# HTTP status code 200 means we're ready, 500 (and all other status codes) signals we're not ready +# The response is only for debugging, and is basically ignored. +@app.get("/health/ready") +async def health_ready() -> str: + ret_str = f"READY at: {datetime.datetime.now()}" + LOGGER.debug(f"health_ready() returning: {ret_str!r}") + return ret_str + + +# Simulate doing some work +@app.get("/dowork") +async def dowork( + duration: Annotated[float, Query(description="Duration of work in seconds")] = 1.0, +) -> str: + LOGGER.debug(f"dowork() doing fake GRID3D VTK work for: {duration=}s") + await asyncio.sleep(duration) + + ret_str = f"GRID3D VTK work done at: {datetime.datetime.now()}" + LOGGER.debug(f"dowork() GRID3D VTK returning: {ret_str!r}") + return ret_str diff --git a/backend_py/user_mock/Dockerfile b/backend_py/user_mock/Dockerfile new file mode 100644 index 000000000..0fb88d556 --- /dev/null +++ b/backend_py/user_mock/Dockerfile @@ -0,0 +1,26 @@ +FROM python:3.11-slim + +RUN useradd --create-home --uid 1234 appuser +USER 1234 + +ENV PATH="${PATH}:/home/appuser/.local/bin" + +RUN python3 -m pip install --user pipx +RUN python3 -m pipx ensurepath +RUN pipx install poetry==1.8.2 + +ENV VIRTUAL_ENV=/home/appuser/venv +RUN python3 -m venv $VIRTUAL_ENV +ENV PATH="$VIRTUAL_ENV/bin:$PATH" + +WORKDIR /home/appuser/backend_py/user_mock + +COPY --chown=appuser ./backend_py/user_mock/pyproject.toml /home/appuser/backend_py/user_mock +COPY --chown=appuser ./backend_py/user_mock/poetry.lock /home/appuser/backend_py/user_mock +RUN poetry install --only main --no-root --no-directory + +COPY --chown=appuser ./backend_py/user_mock/user_mock /home/appuser/backend_py/user_mock/user_mock +RUN poetry install --only main + +# Relevant uvicorn environment variables are: UVICORN_PORT, UVICORN_RELOAD +CMD ["uvicorn", "user_mock.user_mock_app:app", "--host", "0.0.0.0"] diff --git a/backend_py/user_mock/poetry.lock b/backend_py/user_mock/poetry.lock new file mode 100644 index 000000000..97fcd2ff8 --- /dev/null +++ b/backend_py/user_mock/poetry.lock @@ -0,0 +1,271 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + +[[package]] +name = "anyio" +version = "3.7.1" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.7" +files = [ + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "fastapi" +version = "0.103.2" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.7" +files = [ + {file = "fastapi-0.103.2-py3-none-any.whl", hash = "sha256:3270de872f0fe9ec809d4bd3d4d890c6d5cc7b9611d721d6438f9dacc8c4ef2e"}, + {file = "fastapi-0.103.2.tar.gz", hash = "sha256:75a11f6bfb8fc4d2bec0bd710c2d5f2829659c0e8c0afd5560fdda6ce25ec653"}, +] + +[package.dependencies] +anyio = ">=3.7.1,<4.0.0" +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.27.0,<0.28.0" +typing-extensions = ">=4.5.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "pydantic" +version = "2.6.4" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.6.4-py3-none-any.whl", hash = "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"}, + {file = "pydantic-2.6.4.tar.gz", hash = "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.16.3" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.16.3" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, + {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, + {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, + {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, + {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, + {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, + {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, + {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, + {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, + {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, + {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, + {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, + {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, + {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "starlette" +version = "0.27.0" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.7" +files = [ + {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"}, + {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] + +[[package]] +name = "typing-extensions" +version = "4.10.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, +] + +[[package]] +name = "uvicorn" +version = "0.20.0" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.7" +files = [ + {file = "uvicorn-0.20.0-py3-none-any.whl", hash = "sha256:c3ed1598a5668208723f2bb49336f4509424ad198d6ab2615b7783db58d919fd"}, + {file = "uvicorn-0.20.0.tar.gz", hash = "sha256:a4e12017b940247f836bc90b72e725d7dfd0c8ed1c51eb365f5ba30d9f5127d8"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "1c1900c103a60137142a48f7698b943ffebf2eab40628b1bf69c65fa232af688" diff --git a/backend_py/user_mock/pyproject.toml b/backend_py/user_mock/pyproject.toml new file mode 100644 index 000000000..53cad62ac --- /dev/null +++ b/backend_py/user_mock/pyproject.toml @@ -0,0 +1,12 @@ +[tool.poetry] +package-mode = false +name = "user-grid3d-vtk" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.dependencies] +python = "^3.11" +fastapi = "^0.103.1" +uvicorn = "^0.20.0" diff --git a/backend/src/backend/user_session/inactivity_shutdown.py b/backend_py/user_mock/user_mock/inactivity_shutdown.py similarity index 54% rename from backend/src/backend/user_session/inactivity_shutdown.py rename to backend_py/user_mock/user_mock/inactivity_shutdown.py index a86f49a64..ff317d2b1 100644 --- a/backend/src/backend/user_session/inactivity_shutdown.py +++ b/backend_py/user_mock/user_mock/inactivity_shutdown.py @@ -2,10 +2,11 @@ import time from threading import Timer from typing import Callable, Any +import logging from fastapi import Request, FastAPI -LOCALHOST_DEVELOPMENT = os.environ.get("UVICORN_RELOAD") == "true" +LOGGER = logging.getLogger(__name__) class InactivityShutdown: @@ -18,11 +19,15 @@ async def _update_time_last_request(request: Request, call_next: Callable) -> An self._time_last_request = time.time() return await call_next(request) - if not LOCALHOST_DEVELOPMENT: - Timer(60.0, self.check_inactivity_threshold).start() + LOGGER.info(f"Enabled shutdown after {inactivity_limit_minutes} min of inactivity") + Timer(10.0, self.check_inactivity_threshold).start() def check_inactivity_threshold(self) -> None: - if time.time() > self._time_last_request + self._inactivity_limit_seconds: + inactive_time_s = time.time() - self._time_last_request + LOGGER.debug(f"check_inactivity_threshold() {inactive_time_s=:.2f}") + + if inactive_time_s > self._inactivity_limit_seconds: + LOGGER.info(f"Shutting down due to inactivity for {inactive_time_s=:.2f}") os._exit(0) - else: - Timer(60.0, self.check_inactivity_threshold).start() + + Timer(10.0, self.check_inactivity_threshold).start() diff --git a/backend_py/user_mock/user_mock/user_mock_app.py b/backend_py/user_mock/user_mock/user_mock_app.py new file mode 100644 index 000000000..903ee8d36 --- /dev/null +++ b/backend_py/user_mock/user_mock/user_mock_app.py @@ -0,0 +1,85 @@ +import asyncio +import datetime +import os +import logging +from typing import Annotated + +from fastapi import FastAPI +from fastapi import Query + +from .inactivity_shutdown import InactivityShutdown + + +logging.basicConfig(format="%(asctime)s %(levelname)-3s [%(name)s]: %(message)s", datefmt="%H:%M:%S") +logging.getLogger().setLevel(logging.DEBUG) + +# Seems to be one way of know if we're running in Radix or locally +IS_ON_RADIX_PLATFORM = True if os.getenv("RADIX_APP") is not None else False + +LOGGER = logging.getLogger(__name__) + + +RADIX_JOB_NAME = os.getenv("RADIX_JOB_NAME") +RADIX_APP = os.getenv("RADIX_APP") +RADIX_ENVIRONMENT = os.getenv("RADIX_ENVIRONMENT") +RADIX_COMPONENT = os.getenv("RADIX_COMPONENT") +LOGGER.debug(f"{RADIX_JOB_NAME=}") +LOGGER.debug(f"{RADIX_APP=}") +LOGGER.debug(f"{RADIX_ENVIRONMENT=}") +LOGGER.debug(f"{RADIX_COMPONENT=}") + + +def dump_env_vars(): + LOGGER.debug(f"{RADIX_JOB_NAME=}") + LOGGER.debug(f"{RADIX_APP=}") + LOGGER.debug(f"{RADIX_ENVIRONMENT=}") + LOGGER.debug(f"{RADIX_COMPONENT=}") + + +app = FastAPI() + + +@app.get("/") +async def root() -> str: + dump_env_vars() + ret_str = f"user-mock is alive at this time: {datetime.datetime.now()} [RADIX_JOB_NAME={RADIX_JOB_NAME}] [RADIX_APP={RADIX_APP}] [RADIX_ENVIRONMENT={RADIX_ENVIRONMENT}] [RADIX_COMPONENT={RADIX_COMPONENT}" + LOGGER.debug("Sending: ", ret_str) + return ret_str + + +# Probe if service is alive +# HTTP status code 200 means we're alive, all other status codes indicate trouble +# The response is only for debugging, and is basically ignored. +@app.get("/health/live") +async def health_live() -> str: + ret_str = f"LIVE at: {datetime.datetime.now()}" + LOGGER.debug(f"health_live() returning: {ret_str!r}") + return ret_str + + +# Probe if service is ready to receive requests +# HTTP status code 200 means we're ready, 500 (and all other status codes) signals we're not ready +# The response is only for debugging, and is basically ignored. +@app.get("/health/ready") +async def health_ready() -> str: + ret_str = f"READY at: {datetime.datetime.now()}" + LOGGER.debug(f"health_ready() returning: {ret_str!r}") + return ret_str + + +# Simulate doing some work +@app.get("/dowork") +async def dowork( + duration: Annotated[float, Query(description="Duration of work in seconds")] = 1.0, +) -> str: + LOGGER.debug(f"dowork() doing MOCK work for: {duration=}s") + + await asyncio.sleep(duration) + + ret_str = f"MOCK work done at: {datetime.datetime.now()}" + LOGGER.debug(f"dowork() MOCK returning: {ret_str!r}") + return ret_str + + +if IS_ON_RADIX_PLATFORM: + InactivityShutdown(app, inactivity_limit_minutes=1.0) diff --git a/docker-compose.yml b/docker-compose.yml index 49471b007..914a6c77e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,14 +19,13 @@ services: backend-primary: build: context: . - dockerfile: backend.Dockerfile + dockerfile: ./backend_py/primary/Dockerfile ports: - 5000:5000 - 5678:5678 environment: - UVICORN_PORT=5000 - UVICORN_RELOAD=true - - UVICORN_ENTRYPOINT=src.backend.primary.main:app - WEBVIZ_CLIENT_SECRET - WEBVIZ_SMDA_RESOURCE_SCOPE - WEBVIZ_SMDA_SUBSCRIPTION_KEY @@ -37,12 +36,13 @@ services: - OTEL_RESOURCE_ATTRIBUTES=service.namespace=local, service.version=dummy0.1.2 - CODESPACE_NAME # Automatically set env. variable by GitHub codespace volumes: - - ./backend/src:/home/appuser/backend/src + - ./backend_py/primary/primary:/home/appuser/backend_py/primary/primary + - ./backend_py/libs:/home/appuser/backend_py/libs command: [ "sh", "-c", - "pip install debugpy && python -m debugpy --listen 0.0.0.0:5678 -m uvicorn --proxy-headers --host=0.0.0.0 $${UVICORN_ENTRYPOINT}", + "pip install debugpy && python -m debugpy --listen 0.0.0.0:5678 -m uvicorn --proxy-headers --host=0.0.0.0 primary.main:app", ] surface-query: @@ -54,26 +54,41 @@ services: volumes: - ./backend_go/surface_query:/home/appuser/backend_go/surface_query - backend-user-session: + user-mock: build: context: . - dockerfile: backend.Dockerfile + dockerfile: ./backend_py/user_mock/Dockerfile ports: - - 8000:8000 + - 8001:8001 environment: - - UVICORN_PORT=8000 + - UVICORN_PORT=8001 + - UVICORN_RELOAD=true + volumes: + - ./backend_py/user_mock:/home/appuser/backend_py/user_mock + + user-grid3d-ri: + build: + context: . + dockerfile: ./backend_py/user_grid3d_ri/Dockerfile + ports: + - 8002:8002 + environment: + - UVICORN_PORT=8002 + - UVICORN_RELOAD=true + volumes: + - ./backend_py/user_grid3d_ri:/home/appuser/backend_py/user_grid3d_ri + + user-grid3d-vtk: + build: + context: . + dockerfile: ./backend_py/user_grid3d_vtk/Dockerfile + ports: + - 8003:8003 + environment: + - UVICORN_PORT=8003 - UVICORN_RELOAD=true - - UVICORN_ENTRYPOINT=src.backend.user_session.main:app - - WEBVIZ_CLIENT_SECRET - - WEBVIZ_SMDA_RESOURCE_SCOPE - - WEBVIZ_SMDA_SUBSCRIPTION_KEY - - WEBVIZ_SUMO_ENV - - WEBVIZ_VDS_HOST_ADDRESS - - APPLICATIONINSIGHTS_CONNECTION_STRING - - OTEL_SERVICE_NAME=user-session - - OTEL_RESOURCE_ATTRIBUTES=service.namespace=local, service.version=dummy0.1.2 volumes: - - ./backend/src:/home/appuser/backend/src + - ./backend_py/user_grid3d_vtk:/home/appuser/backend_py/user_grid3d_vtk redis-user-session: image: bitnami/redis:6.2.10@sha256:bd42fcdab5959ce2b21b6ea8410d4b3ee87ecb2e320260326ec731ecfcffbd0e diff --git a/frontend-dev.Dockerfile b/frontend-dev.Dockerfile index 5f0488058..57606c4d9 100644 --- a/frontend-dev.Dockerfile +++ b/frontend-dev.Dockerfile @@ -8,6 +8,9 @@ COPY --chown=node:node . /usr/src/app WORKDIR /usr/src/app/frontend +RUN npm config set fetch-retry-mintimeout 100000 +RUN npm config set fetch-retry-maxtimeout 600000 + RUN npm ci --ignore-scripts CMD ["npm", "run", "dev"] diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 67998246d..524180f7b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,18 +12,23 @@ "@headlessui/react": "^1.7.8", "@mui/base": "^5.0.0-beta.3", "@mui/icons-material": "^5.14.9", + "@tanstack/query-core": "^5.17.19", "@tanstack/react-query": "^5.0.5", "@tanstack/react-query-devtools": "^5.4.2", - "@webviz/subsurface-viewer": "^0.3.1", + "@webviz/subsurface-viewer": "^0.21.0", "@webviz/well-completions-plot": "^0.0.1-alpha.1", "animate.css": "^4.1.1", "axios": "^1.6.5", "culori": "^3.2.0", + "jotai": "^2.6.2", + "jotai-scope": "^0.5.1", + "jotai-tanstack-query": "^0.8.2", "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", "react-plotly.js": "^2.6.0", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "wonka": "^6.3.4" }, "devDependencies": { "@playwright/experimental-ct-react": "^1.39.0", @@ -85,6 +90,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -128,6 +134,7 @@ "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true, "engines": { "node": ">=6.9.0" } @@ -136,6 +143,7 @@ "version": "7.23.7", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", + "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", @@ -164,12 +172,14 @@ "node_modules/@babel/core/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true }, "node_modules/@babel/generator": { "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, "dependencies": { "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", @@ -180,22 +190,11 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "peer": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, "dependencies": { "@babel/compat-data": "^7.23.5", "@babel/helper-validator-option": "^7.23.5", @@ -211,6 +210,7 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, "engines": { "node": ">=6.9.0" } @@ -219,6 +219,7 @@ "version": "7.23.0", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, "dependencies": { "@babel/template": "^7.22.15", "@babel/types": "^7.23.0" @@ -231,6 +232,7 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -253,6 +255,7 @@ "version": "7.23.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", @@ -271,6 +274,7 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, "engines": { "node": ">=6.9.0" } @@ -279,6 +283,7 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -290,6 +295,7 @@ "version": "7.22.6", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -317,6 +323,7 @@ "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, "engines": { "node": ">=6.9.0" } @@ -325,6 +332,7 @@ "version": "7.23.7", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.7.tgz", "integrity": "sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==", + "dev": true, "dependencies": { "@babel/template": "^7.22.15", "@babel/traverse": "^7.23.7", @@ -351,6 +359,7 @@ "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -358,156 +367,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-react-jsx-self": { "version": "7.23.3", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", @@ -539,9 +398,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", - "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", + "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -553,6 +412,7 @@ "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, "dependencies": { "@babel/code-frame": "^7.22.13", "@babel/parser": "^7.22.15", @@ -566,6 +426,7 @@ "version": "7.23.7", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", + "dev": true, "dependencies": { "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", @@ -607,12 +468,6 @@ "findup": "bin/findup.js" } }, - "node_modules/@choojs/findup/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "peer": true - }, "node_modules/@danmarshall/deckgl-typings": { "version": "4.9.12", "resolved": "https://registry.npmjs.org/@danmarshall/deckgl-typings/-/deckgl-typings-4.9.12.tgz", @@ -625,13 +480,13 @@ } }, "node_modules/@deck.gl/aggregation-layers": { - "version": "8.9.22", - "resolved": "https://registry.npmjs.org/@deck.gl/aggregation-layers/-/aggregation-layers-8.9.22.tgz", - "integrity": "sha512-cw2duTxcZI0i0+295ivo/tsLffB6GPhuwkCYhqrVfO61JsK9GFuISGB7C5tMcfSOSxzgN6JwNA4fJEK1bCGozQ==", + "version": "8.9.35", + "resolved": "https://registry.npmjs.org/@deck.gl/aggregation-layers/-/aggregation-layers-8.9.35.tgz", + "integrity": "sha512-OjJhHt/ZXLIP3cs8yD2xwa9KACBOjfvJpdMkly704y6SuM0k+qSj81VwkpdOA54DCvAhQQTAYCJ4rvnwmtcPQQ==", "dependencies": { "@babel/runtime": "^7.0.0", - "@luma.gl/constants": "^8.5.20", - "@luma.gl/shadertools": "^8.5.20", + "@luma.gl/constants": "^8.5.21", + "@luma.gl/shadertools": "^8.5.21", "@math.gl/web-mercator": "^3.6.2", "d3-hexbin": "^0.2.1" }, @@ -641,56 +496,17 @@ "@luma.gl/core": "^8.0.0" } }, - "node_modules/@deck.gl/carto": { - "version": "8.9.22", - "resolved": "https://registry.npmjs.org/@deck.gl/carto/-/carto-8.9.22.tgz", - "integrity": "sha512-sfSmzUtXrhvMX5JaFiIpQtmfqVHRxcVIxnSZz6F5Q7DtvBhjNMEt3jq3qJ3IWH0EyxX0Xu+HMjmw6ScnsVAcXQ==", - "dependencies": { - "@babel/runtime": "^7.0.0", - "@loaders.gl/gis": "^3.4.2", - "@loaders.gl/loader-utils": "^3.4.2", - "@loaders.gl/mvt": "^3.4.2", - "@loaders.gl/tiles": "^3.4.2", - "@luma.gl/constants": "^8.5.20", - "@math.gl/web-mercator": "^3.6.2", - "cartocolor": "^4.0.2", - "d3-array": "^3.2.0", - "d3-color": "^3.1.0", - "d3-format": "^3.1.0", - "d3-scale": "^4.0.0", - "h3-js": "^3.7.0", - "moment-timezone": "^0.5.33", - "pbf": "^3.2.1", - "quadbin": "^0.1.9" - }, - "peerDependencies": { - "@deck.gl/aggregation-layers": "^8.0.0", - "@deck.gl/core": "^8.0.0", - "@deck.gl/extensions": "^8.0.0", - "@deck.gl/geo-layers": "^8.0.0", - "@deck.gl/layers": "^8.0.0", - "@loaders.gl/core": "^3.4.2" - } - }, - "node_modules/@deck.gl/carto/node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", - "engines": { - "node": ">=12" - } - }, "node_modules/@deck.gl/core": { - "version": "8.9.22", - "resolved": "https://registry.npmjs.org/@deck.gl/core/-/core-8.9.22.tgz", - "integrity": "sha512-HnUPB6qKgRfKQ3Dstw6vlI7+fyO9Ni4gukh8ysQM94mw89BRLkVcuQV0XvlzbbojrG1Z6QpC7KZmodW0y5o2og==", + "version": "8.9.35", + "resolved": "https://registry.npmjs.org/@deck.gl/core/-/core-8.9.35.tgz", + "integrity": "sha512-xOASWScUCB5fpfuSjPaJrwas8pCJpbKXNIfwQElhvnfP3Yk8GGkAcRbPgiPNCfpkbEno7eDpAWJt6+6UJsSp9g==", "dependencies": { "@babel/runtime": "^7.0.0", - "@loaders.gl/core": "^3.4.2", - "@loaders.gl/images": "^3.4.2", - "@luma.gl/constants": "^8.5.20", - "@luma.gl/core": "^8.5.20", - "@luma.gl/webgl": "^8.5.20", + "@loaders.gl/core": "^3.4.13", + "@loaders.gl/images": "^3.4.13", + "@luma.gl/constants": "^8.5.21", + "@luma.gl/core": "^8.5.21", + "@luma.gl/webgl": "^8.5.21", "@math.gl/core": "^3.6.2", "@math.gl/sun": "^3.6.2", "@math.gl/web-mercator": "^3.6.2", @@ -703,12 +519,12 @@ } }, "node_modules/@deck.gl/extensions": { - "version": "8.9.22", - "resolved": "https://registry.npmjs.org/@deck.gl/extensions/-/extensions-8.9.22.tgz", - "integrity": "sha512-MYyWjnf94Ibs3BNfTIq/oJJLrNMWUpS2+qkmkQie03EGeO7/wJ9BAQAXWYdFdrbvCykw60mLBX78a68+XLvbmQ==", + "version": "8.9.35", + "resolved": "https://registry.npmjs.org/@deck.gl/extensions/-/extensions-8.9.35.tgz", + "integrity": "sha512-qMZzeQpvtcw4zbh4HJJIF8Q73/u0Unwwe8aSC2r+apjuyUVPQInwlCil++LznBY3JA37M7SvW2TSK6IgCuaSyA==", "dependencies": { "@babel/runtime": "^7.0.0", - "@luma.gl/shadertools": "^8.5.20" + "@luma.gl/shadertools": "^8.5.21" }, "peerDependencies": { "@deck.gl/core": "^8.0.0", @@ -720,21 +536,21 @@ } }, "node_modules/@deck.gl/geo-layers": { - "version": "8.9.22", - "resolved": "https://registry.npmjs.org/@deck.gl/geo-layers/-/geo-layers-8.9.22.tgz", - "integrity": "sha512-/P6kGxlWhfRPAaG0UDklANTQpQGevAE1zGTOyFVYEAMyRBws/CetLCq5rKfYibTjWBa5FO2U0u837HzG0PJRUw==", + "version": "8.9.35", + "resolved": "https://registry.npmjs.org/@deck.gl/geo-layers/-/geo-layers-8.9.35.tgz", + "integrity": "sha512-7sczznSjC7GjpDxuXPvPN7/WbVgSYHnFNNyWr8hMGm/WlUMK4z9QpZo6UFvrHL5rQ5Uudd4vD56xB1fs3b2lMA==", "dependencies": { "@babel/runtime": "^7.0.0", - "@loaders.gl/3d-tiles": "^3.4.2", - "@loaders.gl/gis": "^3.4.2", - "@loaders.gl/loader-utils": "^3.4.2", - "@loaders.gl/mvt": "^3.4.2", - "@loaders.gl/schema": "^3.4.2", - "@loaders.gl/terrain": "^3.4.2", - "@loaders.gl/tiles": "^3.4.2", - "@loaders.gl/wms": "^3.4.2", - "@luma.gl/constants": "^8.5.20", - "@luma.gl/experimental": "^8.5.20", + "@loaders.gl/3d-tiles": "^3.4.13", + "@loaders.gl/gis": "^3.4.13", + "@loaders.gl/loader-utils": "^3.4.13", + "@loaders.gl/mvt": "^3.4.13", + "@loaders.gl/schema": "^3.4.13", + "@loaders.gl/terrain": "^3.4.13", + "@loaders.gl/tiles": "^3.4.13", + "@loaders.gl/wms": "^3.4.13", + "@luma.gl/constants": "^8.5.21", + "@luma.gl/experimental": "^8.5.21", "@math.gl/core": "^3.6.2", "@math.gl/culling": "^3.6.2", "@math.gl/web-mercator": "^3.6.2", @@ -747,28 +563,14 @@ "@deck.gl/extensions": "^8.0.0", "@deck.gl/layers": "^8.0.0", "@deck.gl/mesh-layers": "^8.0.0", - "@loaders.gl/core": "^3.4.2", + "@loaders.gl/core": "^3.4.13", "@luma.gl/core": "^8.0.0" } }, - "node_modules/@deck.gl/google-maps": { - "version": "8.9.22", - "resolved": "https://registry.npmjs.org/@deck.gl/google-maps/-/google-maps-8.9.22.tgz", - "integrity": "sha512-x+9yqxkjLN0P5uKxYB6GEaM0KV2djBotvHX0BaHL9LbyWzdAvGJP1kmTg4SOpi0I7XG8jdkoca/tAZ2yy7A9RQ==", - "dependencies": { - "@babel/runtime": "^7.0.0" - }, - "peerDependencies": { - "@deck.gl/core": "^8.0.0", - "@luma.gl/constants": "^8.5.0", - "@luma.gl/core": "^8.5.0", - "@math.gl/core": "^3.6.0" - } - }, "node_modules/@deck.gl/json": { - "version": "8.9.22", - "resolved": "https://registry.npmjs.org/@deck.gl/json/-/json-8.9.22.tgz", - "integrity": "sha512-rYFZqp7mW8XvExtLAfGiAxTV7oLPaLmPVDPTYmMU2owSkfGYE3q6YvwqzHq37k5/5jLfJMA0cZy1fQrfJW3+gg==", + "version": "8.9.35", + "resolved": "https://registry.npmjs.org/@deck.gl/json/-/json-8.9.35.tgz", + "integrity": "sha512-0mp4ckb1n0kRGPPFqtx6Q13uGNRBYGrpYN/NuibQZSW1EMNqmxTqSJekoqzXrlI1W6u35Tu9E5+DHpw9WJiJkg==", "dependencies": { "@babel/runtime": "^7.0.0", "d3-dsv": "^1.0.8", @@ -778,52 +580,15 @@ "@deck.gl/core": "^8.0.0" } }, - "node_modules/@deck.gl/json/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/@deck.gl/json/node_modules/d3-dsv": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", - "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", - "dependencies": { - "commander": "2", - "iconv-lite": "0.4", - "rw": "1" - }, - "bin": { - "csv2json": "bin/dsv2json", - "csv2tsv": "bin/dsv2dsv", - "dsv2dsv": "bin/dsv2dsv", - "dsv2json": "bin/dsv2json", - "json2csv": "bin/json2dsv", - "json2dsv": "bin/json2dsv", - "json2tsv": "bin/json2dsv", - "tsv2csv": "bin/dsv2dsv", - "tsv2json": "bin/dsv2json" - } - }, - "node_modules/@deck.gl/json/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@deck.gl/layers": { - "version": "8.9.22", - "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-8.9.22.tgz", - "integrity": "sha512-1IipECeORaT2fdU9k8KPnZ3DjUeH02h6oZYl4ffihNAIoVXKNivghgJ8ZeELLiEOkrxlbK+KjoH1zej7yuMwmg==", + "version": "8.9.35", + "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-8.9.35.tgz", + "integrity": "sha512-4amaGO+tGvaCNi2KZ90twmajGh5xUAaQzBIyh42dnM10GRj/62sOIYD9uT032oV/KpjKY+TfOstx5ooXBGKDjg==", "dependencies": { "@babel/runtime": "^7.0.0", - "@loaders.gl/images": "^3.4.2", - "@loaders.gl/schema": "^3.4.2", - "@luma.gl/constants": "^8.5.20", + "@loaders.gl/images": "^3.4.13", + "@loaders.gl/schema": "^3.4.13", + "@luma.gl/constants": "^8.5.21", "@mapbox/tiny-sdf": "^2.0.5", "@math.gl/core": "^3.6.2", "@math.gl/polygon": "^3.6.2", @@ -832,32 +597,20 @@ }, "peerDependencies": { "@deck.gl/core": "^8.0.0", - "@loaders.gl/core": "^3.4.2", + "@loaders.gl/core": "^3.4.13", "@luma.gl/core": "^8.0.0" } }, - "node_modules/@deck.gl/mapbox": { - "version": "8.9.22", - "resolved": "https://registry.npmjs.org/@deck.gl/mapbox/-/mapbox-8.9.22.tgz", - "integrity": "sha512-VanA1V/BE6vmHn/W9f8ug4ilMDEfP3JUQWegVaCiG2EA8yjML6QVmO31gNARTefBjgMsfkWDBwm325coIUkxBQ==", - "dependencies": { - "@babel/runtime": "^7.0.0", - "@types/mapbox-gl": "^2.6.3" - }, - "peerDependencies": { - "@deck.gl/core": "^8.0.0" - } - }, "node_modules/@deck.gl/mesh-layers": { - "version": "8.9.22", - "resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-8.9.22.tgz", - "integrity": "sha512-XJ6WdHphBtdumQYseFQCfGQ8KLRzgOGu89qsiEcuRTQwgW5GxXi4WJSpZmXZGtXObubwuEX6YqrN4BkX3F/nWg==", + "version": "8.9.35", + "resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-8.9.35.tgz", + "integrity": "sha512-0l7+zi/6bkYVz7zZ0J4+WLoxOh+hFMPUQKCujTOcqYWbvJ2fEQze0Z1ZHCAG0RQOFJITEMamDigo9dN2BMxPBA==", "dependencies": { "@babel/runtime": "^7.0.0", - "@loaders.gl/gltf": "^3.4.2", - "@luma.gl/constants": "^8.5.20", - "@luma.gl/experimental": "^8.5.20", - "@luma.gl/shadertools": "^8.5.20" + "@loaders.gl/gltf": "^3.4.13", + "@luma.gl/constants": "^8.5.21", + "@luma.gl/experimental": "^8.5.21", + "@luma.gl/shadertools": "^8.5.21" }, "peerDependencies": { "@deck.gl/core": "^8.0.0", @@ -865,9 +618,9 @@ } }, "node_modules/@deck.gl/react": { - "version": "8.9.22", - "resolved": "https://registry.npmjs.org/@deck.gl/react/-/react-8.9.22.tgz", - "integrity": "sha512-NZcWBlOWYPs+DQqYSZQFLxfguFe0GP2AE1ojlrstnckV1N3bsMf1dT6AI5AvX5h9DbiBc216ZrV9iwVhlfkLng==", + "version": "8.9.35", + "resolved": "https://registry.npmjs.org/@deck.gl/react/-/react-8.9.35.tgz", + "integrity": "sha512-eDwYCeBBzHXIpgZbAxcAEnpjnAoMiWcec8rMgYot7VOB+RnBTPYUaO3GPPImlk1uiezNGQel0YK4OLI1f6cDHg==", "dependencies": { "@babel/runtime": "^7.0.0" }, @@ -879,17 +632,17 @@ } }, "node_modules/@emerson-eps/color-tables": { - "version": "0.4.61", - "resolved": "https://registry.npmjs.org/@emerson-eps/color-tables/-/color-tables-0.4.61.tgz", - "integrity": "sha512-hQZgEoTbfllCI6by14RI/Jpqftn9m3wBT6afNOu/ffM4/IxjxpKYaG1ycle310AOAJWoTj9wByIwE7YlkJIFoQ==", + "version": "0.4.80", + "resolved": "https://registry.npmjs.org/@emerson-eps/color-tables/-/color-tables-0.4.80.tgz", + "integrity": "sha512-2G0u+ZOREJA4g1Tmj9FQYCUDZdC6FP2740ps47jyq9r4a9aYQn8dGOipmtjkwQcV3LANk4G6xDEL4nJbILfWWg==", "dependencies": { - "@emotion/react": "^11.10.6", + "@emotion/react": "^11.11.4", "@emotion/styled": "^11.10.0", "d3": "^7.8.4", "d3-color": "^3.0.1", "d3-interpolate": "^3.0.1", "react-color": "^2.19.3", - "react-resize-detector": "^9.1.0" + "react-resize-detector": "^9.1.1" }, "peerDependencies": { "@mui/icons-material": "^5.11.16", @@ -948,14 +701,14 @@ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" }, "node_modules/@emotion/react": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", - "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", + "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.11.0", "@emotion/cache": "^11.11.0", - "@emotion/serialize": "^1.1.2", + "@emotion/serialize": "^1.1.3", "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", "@emotion/utils": "^1.2.1", "@emotion/weak-memoize": "^0.3.1", @@ -971,9 +724,9 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", - "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz", + "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==", "dependencies": { "@emotion/hash": "^0.9.1", "@emotion/memoize": "^0.8.1", @@ -1009,12 +762,6 @@ } } }, - "node_modules/@emotion/stylis": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", - "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==", - "peer": true - }, "node_modules/@emotion/unitless": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", @@ -1039,17 +786,17 @@ "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, "node_modules/@equinor/eds-core-react": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/@equinor/eds-core-react/-/eds-core-react-0.32.4.tgz", - "integrity": "sha512-UTUE8TImQkVxl3srR0dOkPHTkLtmqsp+lcZz509sl3WRaGFlBseBiPgWrCTdgfT4EtAKRimmXhqsrAHllhGgJA==", + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/@equinor/eds-core-react/-/eds-core-react-0.36.1.tgz", + "integrity": "sha512-cFpmsT4+EEFDhGE1DLNDT9Scr6SNBF4xnIfAgkMZcK6wmmZZT30lV2zdGgFC1JN9FfyvlisQukgpurynuBoJTw==", "dependencies": { - "@babel/runtime": "^7.22.5", - "@equinor/eds-icons": "^0.19.3", + "@babel/runtime": "^7.24.0", + "@equinor/eds-icons": "^0.21.0", "@equinor/eds-tokens": "0.9.2", - "@equinor/eds-utils": "0.8.1", - "@floating-ui/react": "^0.25.1", - "@tanstack/react-virtual": "3.0.0-beta.54", - "downshift": "^8.1.0" + "@equinor/eds-utils": "0.8.4", + "@floating-ui/react": "^0.26.9", + "@tanstack/react-virtual": "3.1.3", + "downshift": "8.3.3" }, "engines": { "node": ">=10.0.0", @@ -1058,13 +805,13 @@ "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8", - "styled-components": ">=4.2 < 6" + "styled-components": ">=4.2" } }, "node_modules/@equinor/eds-icons": { - "version": "0.19.3", - "resolved": "https://registry.npmjs.org/@equinor/eds-icons/-/eds-icons-0.19.3.tgz", - "integrity": "sha512-Sh0W01PrwXPCi8/p9YKj0qKNtRU9R/xYJORinIzsNNRllpiu9wvuGAsQNE0gQaDDnrprsiRBH3+MdMSRXVs3Wg==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@equinor/eds-icons/-/eds-icons-0.21.0.tgz", + "integrity": "sha512-k2keACHou9h9D5QLfSBeojTApqbPCkHNBWplUA/B9FQv8FMCMSBbjJAo2L/3yAExMylQN9LdwKo81T2tijRXoA==", "engines": { "node": ">=10.0.0", "pnpm": ">=4" @@ -1080,13 +827,12 @@ } }, "node_modules/@equinor/eds-utils": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@equinor/eds-utils/-/eds-utils-0.8.1.tgz", - "integrity": "sha512-MqKqHhfOxGxbZPhHlZqiR58ei11CQ+UGJ/qEa7wC5pA/c+W9kyu7OveiK9OVvaswRiOmLaahnhSEC9JTFa7ICw==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@equinor/eds-utils/-/eds-utils-0.8.4.tgz", + "integrity": "sha512-njvqXd3Hzfy5vkEqnx+uEBAu00vnG/5R+gDgWCReVDjjUoHdQNcrqfjBLsGF2UungtO0LbYV8YuBP+9l4V7ywQ==", "dependencies": { - "@babel/runtime": "^7.22.5", - "@equinor/eds-tokens": "0.9.2", - "babel-jest": "^29.5.0" + "@babel/runtime": "^7.23.8", + "@equinor/eds-tokens": "0.9.2" }, "engines": { "node": ">=10.0.0", @@ -1629,13 +1375,13 @@ } }, "node_modules/@floating-ui/react": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.25.2.tgz", - "integrity": "sha512-3e10G9LFOgl32/SMWLBOwT7oVCtB+d5zBsU2GxTSVOvRgZexwno5MlYbc0BaXr+TR5EEGpqe9tg9OUbjlrVRnQ==", + "version": "0.26.10", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.10.tgz", + "integrity": "sha512-sh6f9gVvWQdEzLObrWbJ97c0clJObiALsFe0LiR/kb3tDRKwEhObASEH2QyfdoO/ZBPzwxa9j+nYFo+sqgbioA==", "dependencies": { - "@floating-ui/react-dom": "^2.0.1", - "@floating-ui/utils": "^0.1.1", - "tabbable": "^6.0.1" + "@floating-ui/react-dom": "^2.0.0", + "@floating-ui/utils": "^0.2.0", + "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=16.8.0", @@ -1654,6 +1400,11 @@ "react-dom": ">=16.8.0" } }, + "node_modules/@floating-ui/react/node_modules/@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, "node_modules/@floating-ui/utils": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.1.tgz", @@ -1748,305 +1499,43 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.2.tgz", - "integrity": "sha512-ZqCqEISr58Ce3U+buNFJYUktLJZOggfyvR+bZMaiV1e8B1SIvJbwZMrYz3gx/KAPn9EXmOmN+uB08yLCjWkQQg==", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.1", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.2", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.6.2", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/transform/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types": { - "version": "29.6.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", - "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", - "dependencies": { - "@jest/schemas": "^29.6.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { - "color-name": "~1.1.4" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=7.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, "engines": { "node": ">=8" } }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -2060,6 +1549,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, "engines": { "node": ">=6.0.0" } @@ -2068,6 +1558,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, "engines": { "node": ">=6.0.0" } @@ -2075,12 +1566,14 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.18", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, "dependencies": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" @@ -2089,7 +1582,8 @@ "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", @@ -2098,15 +1592,15 @@ "dev": true }, "node_modules/@loaders.gl/3d-tiles": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@loaders.gl/3d-tiles/-/3d-tiles-3.4.8.tgz", - "integrity": "sha512-v4Ejtp4Pb5RhFQyI37oDy9IZtBKEUZ+2xWNtyDgeV8Vni5ht532hsPVGY8CkUMMZFAyzl3zcDFUQo0cIbiqKKg==", - "dependencies": { - "@loaders.gl/draco": "3.4.8", - "@loaders.gl/gltf": "3.4.8", - "@loaders.gl/loader-utils": "3.4.8", - "@loaders.gl/math": "3.4.8", - "@loaders.gl/tiles": "3.4.8", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@loaders.gl/3d-tiles/-/3d-tiles-3.4.15.tgz", + "integrity": "sha512-JR67bEfLrD7Lzb6pWyEIRg2L6W3n6y43DKcWofRLpwPqLA7qHuY7SlO7E72Lz7Tniye8VhawqY1wO8gCx8T72Q==", + "dependencies": { + "@loaders.gl/draco": "3.4.15", + "@loaders.gl/gltf": "3.4.15", + "@loaders.gl/loader-utils": "3.4.15", + "@loaders.gl/math": "3.4.15", + "@loaders.gl/tiles": "3.4.15", "@math.gl/core": "^3.5.1", "@math.gl/geospatial": "^3.5.1", "long": "^5.2.1" @@ -2121,237 +1615,204 @@ "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "node_modules/@loaders.gl/core": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@loaders.gl/core/-/core-3.4.8.tgz", - "integrity": "sha512-pAaAISNmxiUQn8iz0yDhX5DCt3geaaJtSTxAHgz25G2Z5kWqYS00g5bc7XIGNT2BwqD5pgVLRch+BdTQ/Q8lJA==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@loaders.gl/core/-/core-3.4.15.tgz", + "integrity": "sha512-rPOOTuusWlRRNMWg7hymZBoFmPCXWThsA5ZYRfqqXnsgVeQIi8hzcAhJ7zDUIFAd/OSR8ravtqb0SH+3k6MOFQ==", "dependencies": { "@babel/runtime": "^7.3.1", - "@loaders.gl/loader-utils": "3.4.8", - "@loaders.gl/worker-utils": "3.4.8", - "@probe.gl/log": "^4.0.1" - } - }, - "node_modules/@loaders.gl/core/node_modules/@probe.gl/env": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@probe.gl/env/-/env-4.0.4.tgz", - "integrity": "sha512-sYNGqesDfWD6dFP5oNZtTeFA4Z6ak5T4a8BNPdNhoqy7PK9w70JHrb6mv+RKWqKXq33KiwCDWL7fYxx2HuEH2w==", - "dependencies": { - "@babel/runtime": "^7.0.0" - } - }, - "node_modules/@loaders.gl/core/node_modules/@probe.gl/log": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@probe.gl/log/-/log-4.0.4.tgz", - "integrity": "sha512-WpmXl6njlBMwrm8HBh/b4kSp/xnY1VVmeT4PWUKF+RkVbFuKQbsU11dA1IxoMd7gSY+5DGIwxGfAv1H5OMzA4A==", - "dependencies": { - "@babel/runtime": "^7.0.0", - "@probe.gl/env": "4.0.4" + "@loaders.gl/loader-utils": "3.4.15", + "@loaders.gl/worker-utils": "3.4.15", + "@probe.gl/log": "^3.5.0" } }, "node_modules/@loaders.gl/draco": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@loaders.gl/draco/-/draco-3.4.8.tgz", - "integrity": "sha512-x127QX/CCzbSXTk5IL4WBmL6RjvMZhedzMoEiWE4NhAEiwTFKXvWUZLluoFlGzJhxSL6xiR2KBF8tN1EMv4L1Q==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@loaders.gl/draco/-/draco-3.4.15.tgz", + "integrity": "sha512-SStmyP0ZnS4JbWZb2NhrfiHW65uy3pVTTzQDTgXfkR5cD9oDAEu4nCaHbQ8x38/m39FHliCPgS9b1xWvLKQo8w==", "dependencies": { "@babel/runtime": "^7.3.1", - "@loaders.gl/loader-utils": "3.4.8", - "@loaders.gl/schema": "3.4.8", - "@loaders.gl/worker-utils": "3.4.8", + "@loaders.gl/loader-utils": "3.4.15", + "@loaders.gl/schema": "3.4.15", + "@loaders.gl/worker-utils": "3.4.15", "draco3d": "1.5.5" } }, "node_modules/@loaders.gl/gis": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@loaders.gl/gis/-/gis-3.4.8.tgz", - "integrity": "sha512-I0Q8n1XhxXfZ6QoHvW6ayUkrOiifARGvSm4xTZxkydFNM5MULp9ICrZkFeqGMAVeevbssR3h9JKR111AmQvrPw==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@loaders.gl/gis/-/gis-3.4.15.tgz", + "integrity": "sha512-h+LJI35P6ze8DFPSUylTKuml0l4HIfHMczML6u+ZXG6E2w5tbdM3Eh5AzHjXGQPuwUnaYPn3Mq/2t2N1rz98pg==", "dependencies": { - "@loaders.gl/loader-utils": "3.4.8", - "@loaders.gl/schema": "3.4.8", + "@loaders.gl/loader-utils": "3.4.15", + "@loaders.gl/schema": "3.4.15", "@mapbox/vector-tile": "^1.3.1", "@math.gl/polygon": "^3.5.1", "pbf": "^3.2.1" } }, "node_modules/@loaders.gl/gltf": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@loaders.gl/gltf/-/gltf-3.4.8.tgz", - "integrity": "sha512-nvDj0LmkOXtQWUr7MkGShQ2WUmZlUnWTs6PatPxueevdL49vR16SB8VAWTx+8XUPq3Wno+gVZJxG8HBRkFVHng==", - "dependencies": { - "@loaders.gl/draco": "3.4.8", - "@loaders.gl/images": "3.4.8", - "@loaders.gl/loader-utils": "3.4.8", - "@loaders.gl/textures": "3.4.8", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@loaders.gl/gltf/-/gltf-3.4.15.tgz", + "integrity": "sha512-Y6kMNPLiHQPr6aWQw/4BMTxgPHWx3fcib4LPpZCbhyfM8PRn6pOqATVngUXdoOf5XY0QtdKVld6N1kxlr4pJtw==", + "dependencies": { + "@loaders.gl/draco": "3.4.15", + "@loaders.gl/images": "3.4.15", + "@loaders.gl/loader-utils": "3.4.15", + "@loaders.gl/textures": "3.4.15", "@math.gl/core": "^3.5.1" } }, "node_modules/@loaders.gl/images": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@loaders.gl/images/-/images-3.4.8.tgz", - "integrity": "sha512-cKoQ20aMBgBXYKVVJuDLuH1wTeXZyinbG4otSBf4D+gAhQ09Gd3dVMYF7RWIxXp0CdL4jiqGHnJ470y0AvrT4w==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@loaders.gl/images/-/images-3.4.15.tgz", + "integrity": "sha512-QpjYhEetHabY/z9mWZYJXZZp4XJAxa38f9Ii/DjPlnJErepzY5GLBUTDHMu4oZ6n99gGImtuGFicDnFV6mb60g==", "dependencies": { - "@loaders.gl/loader-utils": "3.4.8" + "@loaders.gl/loader-utils": "3.4.15" } }, "node_modules/@loaders.gl/loader-utils": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-3.4.8.tgz", - "integrity": "sha512-/3sZ2J3Y29t50HSo6X6mtLNDYID+s77cmFWxjq+R4/uDekYx2vmgiw/WCFbupKse6NrAKe1KgqEBac1Q7yARSQ==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-3.4.15.tgz", + "integrity": "sha512-uUx6tCaky6QgCRkqCNuuXiUfpTzKV+ZlJOf6C9bKp62lpvFOv9AwqoXmL23j8nfsENdlzsX3vPhc3en6QQyksA==", "dependencies": { "@babel/runtime": "^7.3.1", - "@loaders.gl/worker-utils": "3.4.8", - "@probe.gl/stats": "^4.0.1" - } - }, - "node_modules/@loaders.gl/loader-utils/node_modules/@probe.gl/stats": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@probe.gl/stats/-/stats-4.0.4.tgz", - "integrity": "sha512-SDuSY/D4yDL6LQDa69l/GCcnZLRiGYdyvYkxWb0CgnzTPdPrcdrzGkzkvpC3zsA4fEFw2smlDje370QGHwlisg==", - "dependencies": { - "@babel/runtime": "^7.0.0" + "@loaders.gl/worker-utils": "3.4.15", + "@probe.gl/stats": "^3.5.0" } }, "node_modules/@loaders.gl/math": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@loaders.gl/math/-/math-3.4.8.tgz", - "integrity": "sha512-+9Hy1k44tj+v8IYKh+Zp0xXOijH94HoT/8ILdt5kG8MdjDU6VYyz2t/D4ZawSM+DBgXLYhDIIKkqrdND5ct2AQ==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@loaders.gl/math/-/math-3.4.15.tgz", + "integrity": "sha512-zTN8BUU/1fcppyVc8WzvdZcCyNGVYmNinxcn/A+a7mi1ug4OBGwEsZOj09Wjg0/s52c/cAL3h9ylPIZdjntscQ==", "dependencies": { - "@loaders.gl/images": "3.4.8", - "@loaders.gl/loader-utils": "3.4.8", + "@loaders.gl/images": "3.4.15", + "@loaders.gl/loader-utils": "3.4.15", "@math.gl/core": "^3.5.1" } }, "node_modules/@loaders.gl/mvt": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@loaders.gl/mvt/-/mvt-3.4.8.tgz", - "integrity": "sha512-JvgSsg7+oypRAhGmUKvLSH8h146HRopxf25c9hzyPWX1MeAPfVWgwTou0syy5TwOyRlrLtGp4Rs/6AmNAWBtnw==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@loaders.gl/mvt/-/mvt-3.4.15.tgz", + "integrity": "sha512-Q8e1ZyfNkJtPF/C4WSZ2qhWDEbzOvquP7OyG1NzQ2cp8R6eUfbexu48IgcnL/oAu8VPql3zIxQ+bQUyDReyN5g==", "dependencies": { - "@loaders.gl/gis": "3.4.8", - "@loaders.gl/loader-utils": "3.4.8", - "@loaders.gl/schema": "3.4.8", + "@loaders.gl/gis": "3.4.15", + "@loaders.gl/loader-utils": "3.4.15", + "@loaders.gl/schema": "3.4.15", "@math.gl/polygon": "^3.5.1", "pbf": "^3.2.1" } }, "node_modules/@loaders.gl/schema": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-3.4.8.tgz", - "integrity": "sha512-nu+mT8LgeoCF65xs+F9qegJsIWqYOzwkzJLEd7zmHGRMMYqaFDqcp63J3CUQy+UvS4dGTZph38aQuVfncS/fJA==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-3.4.15.tgz", + "integrity": "sha512-8oRtstz0IsqES7eZd2jQbmCnmExCMtL8T6jWd1+BfmnuyZnQ0B6TNccy++NHtffHdYuzEoQgSELwcdmhSApYew==", "dependencies": { "@types/geojson": "^7946.0.7" } }, "node_modules/@loaders.gl/terrain": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@loaders.gl/terrain/-/terrain-3.4.8.tgz", - "integrity": "sha512-bWLePOb6+4N8DNMXP0D3aqkwyU3phuUjzjQLvfoP9dySUuX4E9kwarCcmkaCYyqp2swp57cHaylSd6tHXOFR6w==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@loaders.gl/terrain/-/terrain-3.4.15.tgz", + "integrity": "sha512-ouv41J84uOnLEtXLM+iPEPFfeq7aRgIOls6esdvhBx2/dXJRNkt8Mx0wShXAi8VNHz77D+gZFrKARa7wqjmftg==", "dependencies": { "@babel/runtime": "^7.3.1", - "@loaders.gl/images": "3.4.8", - "@loaders.gl/loader-utils": "3.4.8", - "@loaders.gl/schema": "3.4.8", + "@loaders.gl/images": "3.4.15", + "@loaders.gl/loader-utils": "3.4.15", + "@loaders.gl/schema": "3.4.15", "@mapbox/martini": "^0.2.0" } }, "node_modules/@loaders.gl/textures": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@loaders.gl/textures/-/textures-3.4.8.tgz", - "integrity": "sha512-lFlV1/3yQqBANP5FoywuQwh+Br++BU3/6eT/Eb/zDbKOqTuqtHp7yAUz207tAO3mRqTkPvtKi9fv+0QrBGoxtw==", - "dependencies": { - "@loaders.gl/images": "3.4.8", - "@loaders.gl/loader-utils": "3.4.8", - "@loaders.gl/schema": "3.4.8", - "@loaders.gl/worker-utils": "3.4.8", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@loaders.gl/textures/-/textures-3.4.15.tgz", + "integrity": "sha512-QHnmxBYtLvTdG1uMz2KWcxVY8KPb1+XyPJUoZV9GMcQkulz+CwFB8BaX8nROfMDz9KKYoPfksCzjig0LZ0WBJQ==", + "dependencies": { + "@loaders.gl/images": "3.4.15", + "@loaders.gl/loader-utils": "3.4.15", + "@loaders.gl/schema": "3.4.15", + "@loaders.gl/worker-utils": "3.4.15", "ktx-parse": "^0.0.4", "texture-compressor": "^1.0.2" } }, "node_modules/@loaders.gl/tiles": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@loaders.gl/tiles/-/tiles-3.4.8.tgz", - "integrity": "sha512-rA3ULOokiUTH6Y3sr37wGaTx3P0g/hRti9yB6q/8kSBy+fE1gSD801O4VluNx6DbOELR2zWkM8TqLTep6sBN1A==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@loaders.gl/tiles/-/tiles-3.4.15.tgz", + "integrity": "sha512-o85aRSXq+YeVSK2ndW9aBwTMi5FhEsQ7k18J4DG+T5Oc+zz3tKui5X1SuBDiKbQN+kYtFpj0oYO1QG3ndNI6jg==", "dependencies": { - "@loaders.gl/loader-utils": "3.4.8", - "@loaders.gl/math": "3.4.8", + "@loaders.gl/loader-utils": "3.4.15", + "@loaders.gl/math": "3.4.15", "@math.gl/core": "^3.5.1", "@math.gl/culling": "^3.5.1", "@math.gl/geospatial": "^3.5.1", "@math.gl/web-mercator": "^3.5.1", - "@probe.gl/stats": "^4.0.1" + "@probe.gl/stats": "^3.5.0" }, "peerDependencies": { "@loaders.gl/core": "^3.4.0" } }, - "node_modules/@loaders.gl/tiles/node_modules/@probe.gl/stats": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@probe.gl/stats/-/stats-4.0.4.tgz", - "integrity": "sha512-SDuSY/D4yDL6LQDa69l/GCcnZLRiGYdyvYkxWb0CgnzTPdPrcdrzGkzkvpC3zsA4fEFw2smlDje370QGHwlisg==", - "dependencies": { - "@babel/runtime": "^7.0.0" - } - }, "node_modules/@loaders.gl/wms": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@loaders.gl/wms/-/wms-3.4.8.tgz", - "integrity": "sha512-4ESwVUa6dStmGqHIqdz6XFo/b/uyB6O+A1iy/mhMGTW8JodrzTcPQl5Iq8fA6EEYP0xviXFL652IEn+dR/BsGg==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@loaders.gl/wms/-/wms-3.4.15.tgz", + "integrity": "sha512-zY++Oxx+cNGF9ptuSTFxCmEnpRbR5VZYjvyLraylaRbuynZv+JiWrehymFzEfq3hJcQ/cGvIjaG6rSVtPuqCIA==", "dependencies": { "@babel/runtime": "^7.3.1", - "@loaders.gl/images": "3.4.8", - "@loaders.gl/loader-utils": "3.4.8", - "@loaders.gl/schema": "3.4.8", - "@loaders.gl/xml": "3.4.8", + "@loaders.gl/images": "3.4.15", + "@loaders.gl/loader-utils": "3.4.15", + "@loaders.gl/schema": "3.4.15", + "@loaders.gl/xml": "3.4.15", "@turf/rewind": "^5.1.5", "deep-strict-equal": "^0.2.0", "lerc": "^4.0.1" } }, "node_modules/@loaders.gl/worker-utils": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-3.4.8.tgz", - "integrity": "sha512-cioo1rKtUkbDPJVQZ5ytEc6r/LdR1eIOCszgRh1VymtYvWGT+prZxfCIAmM1uZBV2SLWYTnH17dR0PR+cNopzA==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-3.4.15.tgz", + "integrity": "sha512-zUUepOYRYmcYIcr/c4Mchox9h5fBFNkD81rsGnLlZyq19QvyHzN+93SVxrLc078gw93t2RKrVcOOZY13zT3t1w==", "dependencies": { "@babel/runtime": "^7.3.1" } }, "node_modules/@loaders.gl/xml": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@loaders.gl/xml/-/xml-3.4.8.tgz", - "integrity": "sha512-8lrmdsnaobENpEPalEAzP720UY3kA/w6w7vCUKABB60QHnq+D5Rvw1PLPfQCACMFhYF+vR56KcQjtEbuqhs5wA==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@loaders.gl/xml/-/xml-3.4.15.tgz", + "integrity": "sha512-iMWHaDzYSe8JoS8W5k9IbxQ6S3VHPr7M+UBejIVeYGCp1jzWF0ri498olwJWWDRvg4kqAWolrkj8Pcgkg8Jf8A==", "dependencies": { "@babel/runtime": "^7.3.1", - "@loaders.gl/loader-utils": "3.4.8", - "@loaders.gl/schema": "3.4.8", + "@loaders.gl/loader-utils": "3.4.15", + "@loaders.gl/schema": "3.4.15", "fast-xml-parser": "^4.2.5" } }, "node_modules/@luma.gl/constants": { - "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-8.5.20.tgz", - "integrity": "sha512-5yG+ybkUZ4j6kLPWMZjN4Hun2yLB0MyEpNCRKAUN9/yS9UIWA7unyVxjSf2vnE7k/7dywtxlbXegASNFgNVGxw==" + "version": "8.5.21", + "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-8.5.21.tgz", + "integrity": "sha512-aJxayGxTT+IRd1vfpcgD/cKSCiVJjBNiuiChS96VulrmCvkzUOLvYXr42y5qKB4RyR7vOIda5uQprNzoHrhQAA==" }, "node_modules/@luma.gl/core": { - "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-8.5.20.tgz", - "integrity": "sha512-xJr96G6vhYcznYHC84fbeOG3fgNM4lFwj9bd0VPcg/Kfe8otUeN1Hl0AKHCCtNn48PiMSg3LKbaiRfNUMhaffQ==", + "version": "8.5.21", + "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-8.5.21.tgz", + "integrity": "sha512-11jQJQEMoR/IN2oIsd4zFxiQJk6FE+xgVIMUcsCTBuzafTtQZ8Po9df8mt+MVewpDyBlTVs6g8nxHRH4np1ukA==", "dependencies": { "@babel/runtime": "^7.0.0", - "@luma.gl/constants": "8.5.20", - "@luma.gl/engine": "8.5.20", - "@luma.gl/gltools": "8.5.20", - "@luma.gl/shadertools": "8.5.20", - "@luma.gl/webgl": "8.5.20" + "@luma.gl/constants": "8.5.21", + "@luma.gl/engine": "8.5.21", + "@luma.gl/gltools": "8.5.21", + "@luma.gl/shadertools": "8.5.21", + "@luma.gl/webgl": "8.5.21" } }, "node_modules/@luma.gl/engine": { - "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/engine/-/engine-8.5.20.tgz", - "integrity": "sha512-+0ryJ/4gL1pWaEgZimY21jUPt1LYiO6Cqte8TNUprCfAHoAStsuzD7jwgEqnM6jJOUEdIxQ3w0z3Dzw/0KIE+w==", + "version": "8.5.21", + "resolved": "https://registry.npmjs.org/@luma.gl/engine/-/engine-8.5.21.tgz", + "integrity": "sha512-IG3WQSKXFNUEs8QG7ZjHtGiOtsakUu+BAxtJ6997A6/F06yynZ44tPe5NU70jG9Yfu3kV0LykPZg7hO3vXZDiA==", "dependencies": { "@babel/runtime": "^7.0.0", - "@luma.gl/constants": "8.5.20", - "@luma.gl/gltools": "8.5.20", - "@luma.gl/shadertools": "8.5.20", - "@luma.gl/webgl": "8.5.20", + "@luma.gl/constants": "8.5.21", + "@luma.gl/gltools": "8.5.21", + "@luma.gl/shadertools": "8.5.21", + "@luma.gl/webgl": "8.5.21", "@math.gl/core": "^3.5.0", "@probe.gl/env": "^3.5.0", "@probe.gl/stats": "^3.5.0", @@ -2359,11 +1820,11 @@ } }, "node_modules/@luma.gl/experimental": { - "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/experimental/-/experimental-8.5.20.tgz", - "integrity": "sha512-V1Jp68rYMPtwMdf+50r3NSYsGV3srjwZ+lcK2ew4DshjedDbYwLqTGMWcOyBhY3K3aCl2LH3Fhn0hAY+3NTLGA==", + "version": "8.5.21", + "resolved": "https://registry.npmjs.org/@luma.gl/experimental/-/experimental-8.5.21.tgz", + "integrity": "sha512-uFKPChGofyihOKxtqJy78QCQCDFnuMTK4QHrUX/qiTnvFSO8BgtTUevKvWGN9lBvq+uDD0lSieeF9yBzhQfAzw==", "dependencies": { - "@luma.gl/constants": "8.5.20", + "@luma.gl/constants": "8.5.21", "@math.gl/core": "^3.5.0", "earcut": "^2.0.6" }, @@ -2377,34 +1838,34 @@ } }, "node_modules/@luma.gl/gltools": { - "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/gltools/-/gltools-8.5.20.tgz", - "integrity": "sha512-5pP6ph9FSX5gHiVWQM1DmYRUnriklzKUG9yaqlQsKEqCFsOcKB0EfK3MfBVXIfsOdP/1bJZ9Dlz/zV19soWVhg==", + "version": "8.5.21", + "resolved": "https://registry.npmjs.org/@luma.gl/gltools/-/gltools-8.5.21.tgz", + "integrity": "sha512-6qZ0LaT2Mxa4AJT5F44TFoaziokYiHUwO45vnM/NYUOIu9xevcmS6VtToawytMEACGL6PDeDyVqP3Y80SDzq5g==", "dependencies": { "@babel/runtime": "^7.0.0", - "@luma.gl/constants": "8.5.20", + "@luma.gl/constants": "8.5.21", "@probe.gl/env": "^3.5.0", "@probe.gl/log": "^3.5.0", "@types/offscreencanvas": "^2019.7.0" } }, "node_modules/@luma.gl/shadertools": { - "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-8.5.20.tgz", - "integrity": "sha512-q1lrCZy1ncIFb4mMjsYgISLzNP6eMnhLUY+Oltj/qjAMcPEssCeHN2+XGfP/CVtU+O7sC+5JY2bQGaTs6HQ/Qw==", + "version": "8.5.21", + "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-8.5.21.tgz", + "integrity": "sha512-WQah7yFDJ8cNCLPYpIm3r0wSlXLvjoA279fcknmATvvkW3/i8PcCJ/nYEBJO3hHEwwMQxD16+YZu/uwGiifLMg==", "dependencies": { "@babel/runtime": "^7.0.0", "@math.gl/core": "^3.5.0" } }, "node_modules/@luma.gl/webgl": { - "version": "8.5.20", - "resolved": "https://registry.npmjs.org/@luma.gl/webgl/-/webgl-8.5.20.tgz", - "integrity": "sha512-p/kt9KztywH4l+09XHoZ4cPFOoE7xlZXIBMT8rxRVgfe1w0lvi7QYh4tOG7gk+iixQ34EyDQacoHCsabdpmqQg==", + "version": "8.5.21", + "resolved": "https://registry.npmjs.org/@luma.gl/webgl/-/webgl-8.5.21.tgz", + "integrity": "sha512-ZVLO4W5UuaOlzZIwmFWhnmZ1gYoU97a+heMqxLrSSmCUAsSu3ZETUex9gOmzdM1WWxcdWaa3M68rvKCNEgwz0Q==", "dependencies": { "@babel/runtime": "^7.0.0", - "@luma.gl/constants": "8.5.20", - "@luma.gl/gltools": "8.5.20", + "@luma.gl/constants": "8.5.21", + "@luma.gl/gltools": "8.5.21", "@probe.gl/env": "^3.5.0", "@probe.gl/stats": "^3.5.0" } @@ -2456,14 +1917,6 @@ "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==" }, - "node_modules/@mapbox/tile-cover": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@mapbox/tile-cover/-/tile-cover-3.0.1.tgz", - "integrity": "sha512-R8aoFY/87HWBOL9E2eBqzOY2lpfWYXCcTNgBpIxAv67rqQeD4IfnHD0iPXg/Z1cqXrklegEYZCp/7ZR/RsWqBQ==", - "dependencies": { - "tilebelt": "^1.0.1" - } - }, "node_modules/@mapbox/tiny-sdf": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", @@ -4134,12 +3587,13 @@ "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true }, "node_modules/@tanstack/query-core": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.0.5.tgz", - "integrity": "sha512-MThCETMkHDHTnFZHp71L+SqTtD5d6XHftFCVR1xRJdWM3qGrlQ2VCXaj0SKVcyJej2e1Opa2c7iknu1llxCDNQ==", + "version": "5.17.19", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.17.19.tgz", + "integrity": "sha512-Lzw8FUtnLCc9Jwz0sw9xOjZB+/mCCmJev38v2wHMUl/ioXNIhnNWeMxu0NKUjIhAd62IRB3eAtvxAGDJ55UkyA==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -4196,25 +3650,35 @@ "react-dom": "^18.0.0" } }, + "node_modules/@tanstack/react-query/node_modules/@tanstack/query-core": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.0.5.tgz", + "integrity": "sha512-MThCETMkHDHTnFZHp71L+SqTtD5d6XHftFCVR1xRJdWM3qGrlQ2VCXaj0SKVcyJej2e1Opa2c7iknu1llxCDNQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/react-virtual": { - "version": "3.0.0-beta.54", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.0.0-beta.54.tgz", - "integrity": "sha512-D1mDMf4UPbrtHRZZriCly5bXTBMhylslm4dhcHqTtDJ6brQcgGmk8YD9JdWBGWfGSWPKoh2x1H3e7eh+hgPXtQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.1.3.tgz", + "integrity": "sha512-YCzcbF/Ws/uZ0q3Z6fagH+JVhx4JLvbSflgldMgLsuvB8aXjZLLb3HvrEVxY480F9wFlBiXlvQxOyXb5ENPrNA==", "dependencies": { - "@tanstack/virtual-core": "3.0.0-beta.54" + "@tanstack/virtual-core": "3.1.3" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/@tanstack/virtual-core": { - "version": "3.0.0-beta.54", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.0.0-beta.54.tgz", - "integrity": "sha512-jtkwqdP2rY2iCCDVAFuaNBH3fiEi29aTn2RhtIoky8DTTiCdc48plpHHreLwmv1PICJ4AJUUESaq3xa8fZH8+g==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.1.3.tgz", + "integrity": "sha512-Y5B4EYyv1j9V8LzeAoOVeTg0LI7Fo5InYKgAjkY1Pu9GjtUwX/EKxNcU7ng3sKr99WEf+bPTcktAeybyMOYo+g==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -4820,6 +4284,7 @@ "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -4832,6 +4297,7 @@ "version": "7.6.4", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, "dependencies": { "@babel/types": "^7.0.0" } @@ -4840,6 +4306,7 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -4849,6 +4316,7 @@ "version": "7.20.1", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", + "dev": true, "dependencies": { "@babel/types": "^7.20.7" } @@ -4882,14 +4350,6 @@ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" }, - "node_modules/@types/graceful-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/hammerjs": { "version": "2.0.41", "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz", @@ -4904,27 +4364,6 @@ "hoist-non-react-statics": "^3.3.0" } }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -4961,18 +4400,11 @@ "@types/lodash": "*" } }, - "node_modules/@types/mapbox-gl": { - "version": "2.7.12", - "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-2.7.12.tgz", - "integrity": "sha512-aEMMOWlSTn2lp0liLqHsI/vAkV8858mkhn4fFYZELdLO1o6PEKCkkUfQ/GBp603Xfc3xmasLZsoELJBbje+atw==", - "dependencies": { - "@types/geojson": "*" - } - }, "node_modules/@types/node": { "version": "18.17.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.1.tgz", - "integrity": "sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==" + "integrity": "sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==", + "dev": true }, "node_modules/@types/offscreencanvas": { "version": "2019.7.0", @@ -5052,6 +4484,12 @@ "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, + "node_modules/@types/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==", + "peer": true + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", @@ -5063,19 +4501,6 @@ "integrity": "sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==", "dev": true }, - "node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", @@ -5585,29 +5010,36 @@ } }, "node_modules/@webviz/subsurface-viewer": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@webviz/subsurface-viewer/-/subsurface-viewer-0.3.1.tgz", - "integrity": "sha512-1sfL50xZIn+Jub/Xkv9kJ+d2x4rpOzj2rIIoWlV1AI4SVRhT1jseQUtrM9AOnraB53USYeZZl22T7xp94zmNVA==", - "dependencies": { - "@deck.gl/core": "^8.8.25", - "@emerson-eps/color-tables": "^0.4.61", - "@equinor/eds-core-react": "^0.32.3", - "@equinor/eds-icons": "^0.19.1", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@webviz/subsurface-viewer/-/subsurface-viewer-0.21.0.tgz", + "integrity": "sha512-tX2H8C/H05EcY/DABnIWL2FxfeN4n2ALDy08MgYCs8gXnEHSTGaNSQExMnOL4xmYh5egEV21BkXZAXcKFFAjSQ==", + "dependencies": { + "@deck.gl/aggregation-layers": "^8.9.35", + "@deck.gl/core": "^8.9.35", + "@deck.gl/extensions": "^8.9.35", + "@deck.gl/geo-layers": "^8.9.35", + "@deck.gl/json": "^8.9.35", + "@deck.gl/layers": "^8.9.35", + "@deck.gl/mesh-layers": "^8.9.35", + "@deck.gl/react": "^8.9.35", + "@emerson-eps/color-tables": "^0.4.71", + "@equinor/eds-core-react": "^0.36.0", + "@equinor/eds-icons": "^0.21.0", "@nebula.gl/layers": "^1.0.4", "@reduxjs/toolkit": "^1.7.2", "@turf/simplify": "^6.5.0", "@vivaxy/png": "^1.3.0", "@webviz/wsc-common": "*", - "ajv": "^7.2.1", + "ajv": "^8.12.0", "convert-units": "^2.3.4", "d3": "^7.8.2", "d3-color": "^3.1.0", - "d3-format": "^1.4.5", - "deck.gl": "^8.9.19", + "d3-format": "^3.1.0", "gl-matrix": "^3.4.3", "lodash": "^4.17.21", - "mathjs": "^9.4.2", - "react-redux": "^8.1.1" + "mathjs": "^12.4.1", + "react-redux": "^8.1.1", + "workerpool": "^9.1.0" }, "peerDependencies": { "@mui/material": "^5.11", @@ -5616,6 +5048,29 @@ "react-dom": "^17 || ^18" } }, + "node_modules/@webviz/subsurface-viewer/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@webviz/subsurface-viewer/node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, "node_modules/@webviz/well-completions-plot": { "version": "0.0.1-alpha.1", "resolved": "https://registry.npmjs.org/@webviz/well-completions-plot/-/well-completions-plot-0.0.1-alpha.1.tgz", @@ -5758,6 +5213,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -5941,188 +5397,75 @@ }, "funding": { "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/autoprefixer": { - "version": "10.4.14", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", - "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - } - ], - "dependencies": { - "browserslist": "^4.21.5", - "caniuse-lite": "^1.0.30001464", - "fraction.js": "^4.2.0", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axios": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", - "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", - "dependencies": { - "follow-redirects": "^1.15.4", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/babel-jest": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.2.tgz", - "integrity": "sha512-BYCzImLos6J3BH/+HvUCHG1dTf2MzmAB4jaVxHV+29RZLjR29XuYTmsf2sdDwkrb+FczkGo3kOhE7ga6sI0P4A==", - "dependencies": { - "@jest/transform": "^29.6.2", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.5.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, "engines": { - "node": ">=8" + "node": "*" } }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/autoprefixer": { + "version": "10.4.14", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", + "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], "dependencies": { - "has-flag": "^4.0.0" + "browserslist": "^4.21.5", + "caniuse-lite": "^1.0.30001464", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" }, "engines": { - "node": ">=8" + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", - "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "node_modules/axios": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", + "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/babel-plugin-macros": { @@ -6139,63 +5482,11 @@ "npm": ">=6" } }, - "node_modules/babel-plugin-styled-components": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", - "integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==", - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.22.5", - "lodash": "^4.17.21", - "picomatch": "^2.3.1" - }, - "peerDependencies": { - "styled-components": ">= 2" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", - "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", - "dependencies": { - "babel-plugin-jest-hoist": "^29.5.0", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/binary-extensions": { "version": "2.2.0", @@ -6238,6 +5529,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6247,6 +5539,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -6258,6 +5551,7 @@ "version": "4.22.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "dev": true, "funding": [ { "type": "opencollective", @@ -6285,14 +5579,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dependencies": { - "node-int64": "^0.4.0" - } - }, "node_modules/buf-compare": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buf-compare/-/buf-compare-1.0.1.tgz", @@ -6336,14 +5622,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "engines": { - "node": ">=6" - } - }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -6366,6 +5644,7 @@ "version": "1.0.30001572", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001572.tgz", "integrity": "sha512-1Pbh5FLmn5y4+QhNyJE9j3/7dK44dGB83/ZMjv/qJk86TvDbjk0LosiZo0i0WB0Vx607qMX9jYrn1VLHCkN4rw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -6390,14 +5669,6 @@ "element-size": "^1.1.1" } }, - "node_modules/cartocolor": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cartocolor/-/cartocolor-4.0.2.tgz", - "integrity": "sha512-+Gh9mb6lFxsDOLQlBLPxAHCnWXlg2W8q3AcVwqRcy95TdBbcOU89Wrb6h2Hd/6Ww1Kc1pzXmUdpnWD+xeCG0dg==", - "dependencies": { - "colorbrewer": "1.0.0" - } - }, "node_modules/chai": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.0.tgz", @@ -6488,20 +5759,6 @@ "node": ">= 6" } }, - "node_modules/ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, "node_modules/clamp": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz", @@ -6595,11 +5852,6 @@ "mumath": "^3.3.4" } }, - "node_modules/colorbrewer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/colorbrewer/-/colorbrewer-1.0.0.tgz", - "integrity": "sha512-NZuIOVdErK/C6jDH3jWT/roxWJbJAinMiqEpbuWniKvQAoWdg6lGra3pPrSHvaIf8PlX8wLs/RAC6nULFJbgmg==" - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -6612,12 +5864,9 @@ } }, "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "engines": { - "node": ">= 10" - } + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "node_modules/complex.js": { "version": "2.1.1", @@ -6632,14 +5881,15 @@ } }, "node_modules/compute-scroll-into-view": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", - "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", + "integrity": "sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==" }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/concat-stream": { "version": "1.6.2", @@ -6848,9 +6098,9 @@ } }, "node_modules/d3": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", - "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", "dependencies": { "d3-array": "3", "d3-axis": "3", @@ -6989,27 +6239,24 @@ } }, "node_modules/d3-dsv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", + "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", "dependencies": { - "commander": "7", - "iconv-lite": "0.6", + "commander": "2", + "iconv-lite": "0.4", "rw": "1" }, "bin": { - "csv2json": "bin/dsv2json.js", - "csv2tsv": "bin/dsv2dsv.js", - "dsv2dsv": "bin/dsv2dsv.js", - "dsv2json": "bin/dsv2json.js", - "json2csv": "bin/json2dsv.js", - "json2dsv": "bin/json2dsv.js", - "json2tsv": "bin/json2dsv.js", - "tsv2csv": "bin/dsv2dsv.js", - "tsv2json": "bin/dsv2json.js" - }, - "engines": { - "node": ">=12" + "csv2json": "bin/dsv2json", + "csv2tsv": "bin/dsv2dsv", + "dsv2dsv": "bin/dsv2dsv", + "dsv2json": "bin/dsv2json", + "json2csv": "bin/json2dsv", + "json2dsv": "bin/json2dsv", + "json2tsv": "bin/json2dsv", + "tsv2csv": "bin/dsv2dsv", + "tsv2json": "bin/dsv2json" } }, "node_modules/d3-ease": { @@ -7076,12 +6323,6 @@ "geostitch": "bin/geostitch" } }, - "node_modules/d3-geo-projection/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "peer": true - }, "node_modules/d3-geo-projection/node_modules/d3-array": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", @@ -7174,9 +6415,9 @@ } }, "node_modules/d3-scale-chromatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", - "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" @@ -7267,6 +6508,38 @@ "node": ">=12" } }, + "node_modules/d3/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3/node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3/node_modules/d3-format": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", @@ -7276,9 +6549,9 @@ } }, "node_modules/d3/node_modules/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", "dependencies": { "d3-array": "2.5.0 - 3" }, @@ -7286,10 +6559,22 @@ "node": ">=12" } }, + "node_modules/d3/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -7307,25 +6592,6 @@ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" }, - "node_modules/deck.gl": { - "version": "8.9.22", - "resolved": "https://registry.npmjs.org/deck.gl/-/deck.gl-8.9.22.tgz", - "integrity": "sha512-CkJ/Wtyquh4wpG7Os6n5j4D9fm0NFROa6Go0hEToVU8kbfQoj6drD76ixBnsx47dOtcHNQ11AVaPaaAeHmnTjg==", - "dependencies": { - "@babel/runtime": "^7.0.0", - "@deck.gl/aggregation-layers": "8.9.22", - "@deck.gl/carto": "8.9.22", - "@deck.gl/core": "8.9.22", - "@deck.gl/extensions": "8.9.22", - "@deck.gl/geo-layers": "8.9.22", - "@deck.gl/google-maps": "8.9.22", - "@deck.gl/json": "8.9.22", - "@deck.gl/layers": "8.9.22", - "@deck.gl/mapbox": "8.9.22", - "@deck.gl/mesh-layers": "8.9.22", - "@deck.gl/react": "8.9.22" - } - }, "node_modules/deep-eql": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", @@ -7380,11 +6646,11 @@ } }, "node_modules/delaunator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", - "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", "dependencies": { - "robust-predicates": "^3.0.0" + "robust-predicates": "^3.0.2" } }, "node_modules/delayed-stream": { @@ -7578,25 +6844,20 @@ "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" }, "node_modules/downshift": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/downshift/-/downshift-8.1.0.tgz", - "integrity": "sha512-e9EBBLZvB2G73qT272x3hExttGCH1q1usbjirm+1aMcFXuzSWhgBdbnAHPlFI2rEq61cU/kDrEIMrY+ozMhvmg==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-8.3.3.tgz", + "integrity": "sha512-f9znQFYF/3AWBkFiEc4H05Vdh41XFgJ80IatLBKIFoA3p86mAXc/iM9/XJ24loF9djtABD5NBEYL7b1b7xh2pw==", "dependencies": { - "@babel/runtime": "^7.14.8", - "compute-scroll-into-view": "^2.0.4", - "prop-types": "^15.7.2", - "react-is": "^17.0.2", - "tslib": "^2.3.0" + "@babel/runtime": "^7.22.15", + "compute-scroll-into-view": "^3.0.3", + "prop-types": "^15.8.1", + "react-is": "^18.2.0", + "tslib": "^2.6.2" }, "peerDependencies": { "react": ">=16.12.0" } }, - "node_modules/downshift/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, "node_modules/draco3d": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.5.tgz", @@ -7653,7 +6914,8 @@ "node_modules/electron-to-chromium": { "version": "1.4.617", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.617.tgz", - "integrity": "sha512-sYNE3QxcDS4ANW1k4S/wWYMXjCVcFSOX3Bg8jpuMFaXt/x8JCmp0R1Xe1ZXDX4WXnSRBf+GJ/3eGWicUuQq5cg==" + "integrity": "sha512-sYNE3QxcDS4ANW1k4S/wWYMXjCVcFSOX3Bg8jpuMFaXt/x8JCmp0R1Xe1ZXDX4WXnSRBf+GJ/3eGWicUuQq5cg==", + "dev": true }, "node_modules/element-size": { "version": "1.1.1", @@ -7889,6 +7151,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, "engines": { "node": ">=6" } @@ -8442,6 +7705,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "peer": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -8677,7 +7941,8 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -8685,17 +7950,17 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, "node_modules/fast-xml-parser": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.7.tgz", - "integrity": "sha512-J8r6BriSLO1uj2miOk1NW0YVm8AGOOu3Si2HQp/cSmo6EA4m3fcwu2WKjJ4RK9wMLBtg69y1kS8baDiQBR41Ig==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.6.tgz", + "integrity": "sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw==", "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - }, { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" } ], "dependencies": { @@ -8714,14 +7979,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dependencies": { - "bser": "2.1.1" - } - }, "node_modules/figures": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", @@ -8766,6 +8023,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -8898,15 +8156,15 @@ } }, "node_modules/fraction.js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", - "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.4.tgz", + "integrity": "sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==", "engines": { "node": "*" }, "funding": { "type": "patreon", - "url": "https://www.patreon.com/infusion" + "url": "https://github.com/sponsors/rawify" } }, "node_modules/from2": { @@ -8935,12 +8193,14 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -8986,6 +8246,7 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "engines": { "node": ">=6.9.0" } @@ -9055,14 +8316,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -9228,6 +8481,7 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, "engines": { "node": ">=4" } @@ -9676,11 +8930,11 @@ } }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { "node": ">=0.10.0" @@ -9759,6 +9013,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "engines": { "node": ">=0.8.19" } @@ -9791,6 +9046,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -10047,6 +9303,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "engines": { "node": ">=0.12.0" } @@ -10235,21 +9492,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, + "dev": true, "engines": { "node": ">=8" } @@ -10348,161 +9591,52 @@ "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==" }, - "node_modules/jest-haste-map": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.2.tgz", - "integrity": "sha512-+51XleTDAAysvU8rT6AnS1ZJ+WHVNqhj1k6nTvN2PYP+HjU3kqlaKQ1Lnw3NYW3bm2r8vq82X0Z1nDDHZMzHVA==", - "dependencies": { - "@jest/types": "^29.6.1", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.6.2", - "jest-worker": "^29.6.2", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-regex-util": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.2.tgz", - "integrity": "sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w==", - "dependencies": { - "@jest/types": "^29.6.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "node_modules/jiti": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz", + "integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" } }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jotai": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.6.2.tgz", + "integrity": "sha512-kl4KguU1Fr+tFiLi3A3h9qPEzhvLTTDA10DO3QZAz6k7BEaQJ+qvSBwolzonnfNI4QzEovyQfUqVgnRxfnnQVQ==", "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" + "node": ">=12.20.0" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.2.tgz", - "integrity": "sha512-l3ccBOabTdkng8I/ORCkADz4eSMKejTYv1vB/Z83UiubqhC1oQ5Li6dWCyqOIvSifGjUBxuvxvlm6KGK2DtuAQ==", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.6.2", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "peerDependencies": { + "@types/react": ">=17.0.0", + "react": ">=17.0.0" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node_modules/jotai-scope": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/jotai-scope/-/jotai-scope-0.5.1.tgz", + "integrity": "sha512-GKaqtCj1Hv36nyl63PVm+s5jo+hQqz+wAb81iqA8VuUXp5ot4eXGOZt4Hc66lFZMP0N/yEOISMlazprmDI6kFA==", + "peerDependencies": { + "jotai": ">=2.5.0", + "react": ">=17.0.0" } }, - "node_modules/jiti": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz", - "integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==", - "dev": true, - "bin": { - "jiti": "bin/jiti.js" + "node_modules/jotai-tanstack-query": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/jotai-tanstack-query/-/jotai-tanstack-query-0.8.2.tgz", + "integrity": "sha512-NXHbfSTBdH1M+NrnEVG0pUcUcPHzEtqhRJl8JHHoay33whvGkQZW93/ETj4fa1vgA/N7gzScPMRPQFzpp68P+g==", + "peerDependencies": { + "@tanstack/query-core": "*", + "jotai": ">=2.0.0", + "wonka": "^6.3.4" } }, "node_modules/js-tokens": { @@ -10534,6 +9668,7 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, "bin": { "jsesc": "bin/jsesc" }, @@ -10561,6 +9696,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "bin": { "json5": "lib/cli.js" }, @@ -10618,9 +9754,9 @@ "integrity": "sha512-LY3nrmfXl+wZZdPxgJ3ZmLvG+wkOZZP3/dr4RbQj1Pk3Qwz44esOOSFFVQJcNWpXAtiNIC66WgXufX/SYgYz6A==" }, "node_modules/lerc": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lerc/-/lerc-4.0.1.tgz", - "integrity": "sha512-b351eOjY3DKm1H2hDVhXswsd2RCK6bgREBK6Z639ctClOuYXTi9a44l8yO3zm1pYM2o4WrriloTAKgyrb/0EyA==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lerc/-/lerc-4.0.4.tgz", + "integrity": "sha512-nHZH+ffiGPkgKUQtiZrljGUGV2GddvPcVTV5E345ZFncbKz+/rBIjDPrSxkiqW0EAtg1Jw7qAgRdaCwV+95Fow==" }, "node_modules/levn": { "version": "0.4.1", @@ -10904,6 +10040,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, "dependencies": { "yallist": "^3.0.2" } @@ -10979,14 +10116,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dependencies": { - "tmpl": "1.0.5" - } - }, "node_modules/map-limit": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/map-limit/-/map-limit-0.0.1.tgz", @@ -11068,31 +10197,32 @@ } }, "node_modules/mathjs": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-9.5.2.tgz", - "integrity": "sha512-c0erTq0GP503/Ch2OtDOAn50GIOsuxTMjmE00NI/vKJFSWrDaQHRjx6ai+16xYv70yBSnnpUgHZGNf9FR9IwmA==", + "version": "12.4.1", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-12.4.1.tgz", + "integrity": "sha512-welnW3khgwYjPYvECFHO+xkCxAx9IKIIPDDWPi8B5rKAvmgoEHnQX9slEmHKZTNaJiE+OS4qrJJcB4sfDn/4sw==", "dependencies": { - "@babel/runtime": "^7.15.4", - "complex.js": "^2.0.15", - "decimal.js": "^10.3.1", + "@babel/runtime": "^7.24.0", + "complex.js": "^2.1.1", + "decimal.js": "^10.4.3", "escape-latex": "^1.2.0", - "fraction.js": "^4.1.1", + "fraction.js": "4.3.4", "javascript-natural-sort": "^0.7.1", "seedrandom": "^3.0.5", "tiny-emitter": "^2.1.0", - "typed-function": "^2.0.0" + "typed-function": "^4.1.1" }, "bin": { "mathjs": "bin/cli.js" }, "engines": { - "node": ">= 12" + "node": ">= 18" } }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true }, "node_modules/merge2": { "version": "1.4.1", @@ -11107,6 +10237,7 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -11158,6 +10289,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -11207,25 +10339,6 @@ "ufo": "^1.3.0" } }, - "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "engines": { - "node": "*" - } - }, - "node_modules/moment-timezone": { - "version": "0.5.43", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz", - "integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==", - "dependencies": { - "moment": "^2.29.4" - }, - "engines": { - "node": "*" - } - }, "node_modules/mouse-change": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/mouse-change/-/mouse-change-1.4.0.tgz", @@ -11294,7 +10407,6 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, "funding": [ { "type": "github", @@ -11346,18 +10458,6 @@ "ms": "^2.1.1" } }, - "node_modules/needle/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -11370,20 +10470,17 @@ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "peer": true }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" - }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -11724,14 +10821,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -11796,6 +10885,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "engines": { "node": ">=8" } @@ -11804,6 +10894,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -11903,6 +10994,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "engines": { "node": ">=8.6" }, @@ -11923,6 +11015,7 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, "engines": { "node": ">= 6" } @@ -12446,17 +11539,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/quadbin": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/quadbin/-/quadbin-0.1.9.tgz", - "integrity": "sha512-5V6m6+cL/6+uBl3hYL+CWF06rRvlHkIepYKGQjTLYaHhu9InPppql0+0ROiCaOQdz8gPNlgge3glk5Qg1mWOYw==", - "dependencies": { - "@mapbox/tile-cover": "3.0.1" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -12604,9 +11686,9 @@ } }, "node_modules/react-resize-detector": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-9.1.0.tgz", - "integrity": "sha512-vGFbfkIZp4itJqR4yl+GhjrZHtdlQvou1r10ek0yZUMkizKbPdekKTpPb003IV3b8E5BJFThVG0oocjE3lNsug==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-9.1.1.tgz", + "integrity": "sha512-siLzop7i4xIvZIACE/PHTvRegA8QRCEt0TfmvJ/qCIFQJ4U+3NuYcF8tNDmDWxfIn+X1eNCyY2rauH4KRxge8w==", "dependencies": { "lodash": "^4.17.21" }, @@ -13115,6 +12197,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "bin": { "semver": "bin/semver.js" } @@ -13244,6 +12327,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, "engines": { "node": ">=8" } @@ -13260,7 +12344,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -13561,24 +12644,23 @@ "peer": true }, "node_modules/styled-components": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", - "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz", + "integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==", "peer": true, "dependencies": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/traverse": "^7.4.5", - "@emotion/is-prop-valid": "^1.1.0", - "@emotion/stylis": "^0.8.4", - "@emotion/unitless": "^0.7.4", - "babel-plugin-styled-components": ">= 1.12.0", - "css-to-react-native": "^3.0.0", - "hoist-non-react-statics": "^3.0.0", - "shallowequal": "^1.1.0", - "supports-color": "^5.5.0" + "@emotion/is-prop-valid": "1.2.1", + "@emotion/unitless": "0.8.0", + "@types/stylis": "4.2.0", + "css-to-react-native": "3.2.0", + "csstype": "3.1.2", + "postcss": "8.4.31", + "shallowequal": "1.1.0", + "stylis": "4.3.1", + "tslib": "2.5.0" }, "engines": { - "node": ">=10" + "node": ">= 16" }, "funding": { "type": "opencollective", @@ -13586,14 +12668,53 @@ }, "peerDependencies": { "react": ">= 16.8.0", - "react-dom": ">= 16.8.0", - "react-is": ">= 16.8.0" + "react-dom": ">= 16.8.0" } }, "node_modules/styled-components/node_modules/@emotion/unitless": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==", + "peer": true + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/stylis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", + "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==", + "peer": true + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "peer": true }, "node_modules/stylis": { @@ -13790,6 +12911,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -13803,6 +12925,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -13875,12 +12998,6 @@ "xtend": "~4.0.1" } }, - "node_modules/tilebelt": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tilebelt/-/tilebelt-1.0.1.tgz", - "integrity": "sha512-cxHzpa5JgsugY9NUVRH43gPaGJw/29LecAn4X7UGOP64+kB8pU4VQ3bIhSyfb5Mk4jDxwl3yk330L/EIhbJ5aw==", - "deprecated": "This module is now under the @mapbox namespace: install @mapbox/tilebelt instead" - }, "node_modules/tiny-emitter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", @@ -13927,11 +13044,6 @@ "node": ">=14.0.0" } }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" - }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -13959,6 +13071,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -13980,12 +13093,6 @@ "topoquantize": "bin/topoquantize" } }, - "node_modules/topojson-client/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "peer": true - }, "node_modules/ts-api-utils": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.0.tgz", @@ -14259,11 +13366,11 @@ } }, "node_modules/typed-function": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-2.1.0.tgz", - "integrity": "sha512-bctQIOqx2iVbWGDGPWwIm18QScpu2XRmkC19D8rQGFsjKSgteq/o1hTZvIG/wuDq8fanpBDrLkLq+aEN/6y5XQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.1.tgz", + "integrity": "sha512-Pq1DVubcvibmm8bYcMowjVnnMwPVMeh0DIdA8ad8NZY2sJgapANJmiigSUwlt+EgXxpfIv8MWrQXTIzkfYZLYQ==", "engines": { - "node": ">= 10" + "node": ">= 14" } }, "node_modules/typedarray": { @@ -14347,6 +13454,7 @@ "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -14916,14 +14024,6 @@ "pbf": "^3.2.1" } }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dependencies": { - "makeerror": "1.0.12" - } - }, "node_modules/watskeburt": { "version": "0.11.6", "resolved": "https://registry.npmjs.org/watskeburt/-/watskeburt-0.11.6.tgz", @@ -15017,6 +14117,11 @@ "node": ">=8" } }, + "node_modules/wonka": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.4.tgz", + "integrity": "sha512-CjpbqNtBGNAeyNS/9W6q3kSkKE52+FjIj7AkFlLr11s/VWGUu6a2CdYSdGxocIhIVjaW/zchesBQUKPVU69Cqg==" + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -15032,6 +14137,11 @@ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true }, + "node_modules/workerpool": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.1.0.tgz", + "integrity": "sha512-+wRWfm9yyJghvXLSHMQj3WXDxHbibHAQmRrWbqKBfy0RjftZNeQaW+Std5bSYc83ydkrxoPTPOWVlXUR9RWJdQ==" + }, "node_modules/world-calendars": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/world-calendars/-/world-calendars-1.0.3.tgz", @@ -15173,23 +14283,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -15202,7 +14295,8 @@ "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true }, "node_modules/yaml": { "version": "1.10.2", diff --git a/frontend/package.json b/frontend/package.json index 1be7af583..721bdb17e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,18 +23,23 @@ "@headlessui/react": "^1.7.8", "@mui/base": "^5.0.0-beta.3", "@mui/icons-material": "^5.14.9", + "@tanstack/query-core": "^5.17.19", "@tanstack/react-query": "^5.0.5", "@tanstack/react-query-devtools": "^5.4.2", - "@webviz/subsurface-viewer": "^0.3.1", + "@webviz/subsurface-viewer": "^0.21.0", "@webviz/well-completions-plot": "^0.0.1-alpha.1", "animate.css": "^4.1.1", "axios": "^1.6.5", "culori": "^3.2.0", + "jotai": "^2.6.2", + "jotai-scope": "^0.5.1", + "jotai-tanstack-query": "^0.8.2", "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", "react-plotly.js": "^2.6.0", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "wonka": "^6.3.4" }, "devDependencies": { "@playwright/experimental-ct-react": "^1.39.0", @@ -70,4 +75,4 @@ "vite-plugin-checker": "^0.6.0", "vitest": "^1.1.3" } -} \ No newline at end of file +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2adc24f3e..74ed0657d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -126,7 +126,7 @@ function App() { ); } - const isInitialisingApp = initAppState !== InitAppState.InitCompleted; + const isInitializingApp = initAppState !== InitAppState.InitCompleted; return ( <> @@ -138,12 +138,12 @@ function App() { ) : ( - isInitialisingApp && ( + isInitializingApp && (
@@ -155,8 +155,8 @@ function App() { )}
diff --git a/frontend/src/api/ApiService.ts b/frontend/src/api/ApiService.ts index 2ecd7eaf7..9f9a1a29a 100644 --- a/frontend/src/api/ApiService.ts +++ b/frontend/src/api/ApiService.ts @@ -8,7 +8,7 @@ import { AxiosHttpRequest } from './core/AxiosHttpRequest'; import { DefaultService } from './services/DefaultService'; import { ExploreService } from './services/ExploreService'; import { GraphService } from './services/GraphService'; -import { GridService } from './services/GridService'; +import { Grid3DService } from './services/Grid3DService'; import { InplaceVolumetricsService } from './services/InplaceVolumetricsService'; import { ObservationsService } from './services/ObservationsService'; import { ParametersService } from './services/ParametersService'; @@ -25,7 +25,7 @@ export class ApiService { public readonly default: DefaultService; public readonly explore: ExploreService; public readonly graph: GraphService; - public readonly grid: GridService; + public readonly grid3D: Grid3DService; public readonly inplaceVolumetrics: InplaceVolumetricsService; public readonly observations: ObservationsService; public readonly parameters: ParametersService; @@ -53,7 +53,7 @@ export class ApiService { this.default = new DefaultService(this.request); this.explore = new ExploreService(this.request); this.graph = new GraphService(this.request); - this.grid = new GridService(this.request); + this.grid3D = new Grid3DService(this.request); this.inplaceVolumetrics = new InplaceVolumetricsService(this.request); this.observations = new ObservationsService(this.request); this.parameters = new ParametersService(this.request); diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 9e0df87e7..c69cd790b 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -28,8 +28,9 @@ export type { EnsembleSensitivityCase as EnsembleSensitivityCase_api } from './m export type { FieldInfo as FieldInfo_api } from './models/FieldInfo'; export { Frequency as Frequency_api } from './models/Frequency'; export type { GraphUserPhoto as GraphUserPhoto_api } from './models/GraphUserPhoto'; -export type { GridIntersection as GridIntersection_api } from './models/GridIntersection'; +export type { GridIntersectionVtk as GridIntersectionVtk_api } from './models/GridIntersectionVtk'; export type { GridSurface as GridSurface_api } from './models/GridSurface'; +export type { GridSurfaceVtk as GridSurfaceVtk_api } from './models/GridSurfaceVtk'; export type { HTTPValidationError as HTTPValidationError_api } from './models/HTTPValidationError'; export type { InplaceVolumetricsCategoricalMetaData as InplaceVolumetricsCategoricalMetaData_api } from './models/InplaceVolumetricsCategoricalMetaData'; export type { InplaceVolumetricsTableMetaData as InplaceVolumetricsTableMetaData_api } from './models/InplaceVolumetricsTableMetaData'; @@ -80,7 +81,7 @@ export type { WellCompletionsZone as WellCompletionsZone_api } from './models/We export { DefaultService } from './services/DefaultService'; export { ExploreService } from './services/ExploreService'; export { GraphService } from './services/GraphService'; -export { GridService } from './services/GridService'; +export { Grid3DService } from './services/Grid3DService'; export { InplaceVolumetricsService } from './services/InplaceVolumetricsService'; export { ObservationsService } from './services/ObservationsService'; export { ParametersService } from './services/ParametersService'; diff --git a/frontend/src/api/models/EnsembleDetails.ts b/frontend/src/api/models/EnsembleDetails.ts index eeb5d36ca..43f2c4103 100644 --- a/frontend/src/api/models/EnsembleDetails.ts +++ b/frontend/src/api/models/EnsembleDetails.ts @@ -4,6 +4,7 @@ /* eslint-disable */ export type EnsembleDetails = { name: string; + field_identifier: string; case_name: string; case_uuid: string; realizations: Array; diff --git a/frontend/src/api/models/GridIntersection.ts b/frontend/src/api/models/GridIntersectionVtk.ts similarity index 89% rename from frontend/src/api/models/GridIntersection.ts rename to frontend/src/api/models/GridIntersectionVtk.ts index 0419adea1..1e4672669 100644 --- a/frontend/src/api/models/GridIntersection.ts +++ b/frontend/src/api/models/GridIntersectionVtk.ts @@ -2,7 +2,7 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -export type GridIntersection = { +export type GridIntersectionVtk = { image: string; polyline_x: Array; polyline_y: Array; diff --git a/frontend/src/api/models/GridSurfaceVtk.ts b/frontend/src/api/models/GridSurfaceVtk.ts new file mode 100644 index 000000000..00065a55d --- /dev/null +++ b/frontend/src/api/models/GridSurfaceVtk.ts @@ -0,0 +1,17 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { B64FloatArray } from './B64FloatArray'; +import type { B64UintArray } from './B64UintArray'; +export type GridSurfaceVtk = { + polys_b64arr: B64UintArray; + points_b64arr: B64FloatArray; + xmin: number; + xmax: number; + ymin: number; + ymax: number; + zmin: number; + zmax: number; +}; + diff --git a/frontend/src/api/services/DefaultService.ts b/frontend/src/api/services/DefaultService.ts index 926930b66..a314d8e9f 100644 --- a/frontend/src/api/services/DefaultService.ts +++ b/frontend/src/api/services/DefaultService.ts @@ -80,18 +80,6 @@ export class DefaultService { }, }); } - /** - * User Session Container - * Get information about user session container (note that one is started if not already running). - * @returns any Successful Response - * @throws ApiError - */ - public userSessionContainer(): CancelablePromise { - return this.httpRequest.request({ - method: 'GET', - url: '/user_session_container', - }); - } /** * Root * @returns string Successful Response diff --git a/frontend/src/api/services/GridService.ts b/frontend/src/api/services/Grid3DService.ts similarity index 68% rename from frontend/src/api/services/GridService.ts rename to frontend/src/api/services/Grid3DService.ts index 8ffd2aba6..7d2090fbf 100644 --- a/frontend/src/api/services/GridService.ts +++ b/frontend/src/api/services/Grid3DService.ts @@ -2,11 +2,12 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { GridIntersection } from '../models/GridIntersection'; +import type { GridIntersectionVtk } from '../models/GridIntersectionVtk'; import type { GridSurface } from '../models/GridSurface'; +import type { GridSurfaceVtk } from '../models/GridSurfaceVtk'; import type { CancelablePromise } from '../core/CancelablePromise'; import type { BaseHttpRequest } from '../core/BaseHttpRequest'; -export class GridService { +export class Grid3DService { constructor(public readonly httpRequest: BaseHttpRequest) {} /** * Get Grid Model Names @@ -22,7 +23,7 @@ export class GridService { ): CancelablePromise> { return this.httpRequest.request({ method: 'GET', - url: '/grid/grid_model_names/', + url: '/grid3d/grid_model_names/', query: { 'case_uuid': caseUuid, 'ensemble_name': ensembleName, @@ -48,7 +49,7 @@ export class GridService { ): CancelablePromise> { return this.httpRequest.request({ method: 'GET', - url: '/grid/parameter_names/', + url: '/grid3d/parameter_names/', query: { 'case_uuid': caseUuid, 'ensemble_name': ensembleName, @@ -61,7 +62,6 @@ export class GridService { } /** * Grid Surface - * Get a grid * @param caseUuid Sumo case uuid * @param ensembleName Ensemble name * @param gridName Grid name @@ -77,7 +77,7 @@ export class GridService { ): CancelablePromise { return this.httpRequest.request({ method: 'GET', - url: '/grid/grid_surface', + url: '/grid3d/grid_surface', query: { 'case_uuid': caseUuid, 'ensemble_name': ensembleName, @@ -91,7 +91,6 @@ export class GridService { } /** * Grid Parameter - * Get a grid parameter * @param caseUuid Sumo case uuid * @param ensembleName Ensemble name * @param gridName Grid name @@ -109,7 +108,70 @@ export class GridService { ): CancelablePromise> { return this.httpRequest.request({ method: 'GET', - url: '/grid/grid_parameter', + url: '/grid3d/grid_parameter', + query: { + 'case_uuid': caseUuid, + 'ensemble_name': ensembleName, + 'grid_name': gridName, + 'parameter_name': parameterName, + 'realization': realization, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Grid Surface Vtk + * Get a grid + * @param caseUuid Sumo case uuid + * @param ensembleName Ensemble name + * @param gridName Grid name + * @param realization Realization + * @returns GridSurfaceVtk Successful Response + * @throws ApiError + */ + public gridSurfaceVtk( + caseUuid: string, + ensembleName: string, + gridName: string, + realization: string, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'GET', + url: '/grid3d/grid_surface_vtk', + query: { + 'case_uuid': caseUuid, + 'ensemble_name': ensembleName, + 'grid_name': gridName, + 'realization': realization, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Grid Parameter Vtk + * Get a grid parameter + * @param caseUuid Sumo case uuid + * @param ensembleName Ensemble name + * @param gridName Grid name + * @param parameterName Grid parameter + * @param realization Realization + * @returns number Successful Response + * @throws ApiError + */ + public gridParameterVtk( + caseUuid: string, + ensembleName: string, + gridName: string, + parameterName: string, + realization: string, + ): CancelablePromise> { + return this.httpRequest.request({ + method: 'GET', + url: '/grid3d/grid_parameter_vtk', query: { 'case_uuid': caseUuid, 'ensemble_name': ensembleName, @@ -123,26 +185,26 @@ export class GridService { }); } /** - * Grid Parameter Intersection + * Grid Parameter Intersection Vtk * Get a grid parameter * @param caseUuid Sumo case uuid * @param ensembleName Ensemble name * @param gridName Grid name * @param parameterName Grid parameter * @param realization Realization - * @returns GridIntersection Successful Response + * @returns GridIntersectionVtk Successful Response * @throws ApiError */ - public gridParameterIntersection( + public gridParameterIntersectionVtk( caseUuid: string, ensembleName: string, gridName: string, parameterName: string, realization: string, - ): CancelablePromise { + ): CancelablePromise { return this.httpRequest.request({ method: 'GET', - url: '/grid/grid_parameter_intersection', + url: '/grid3d/grid_parameter_intersection_vtk', query: { 'case_uuid': caseUuid, 'ensemble_name': ensembleName, @@ -156,26 +218,26 @@ export class GridService { }); } /** - * Statistical Grid Parameter Intersection + * Statistical Grid Parameter Intersection Vtk * Get a grid parameter * @param caseUuid Sumo case uuid * @param ensembleName Ensemble name * @param gridName Grid name * @param parameterName Grid parameter * @param realizations Realizations - * @returns GridIntersection Successful Response + * @returns GridIntersectionVtk Successful Response * @throws ApiError */ - public statisticalGridParameterIntersection( + public statisticalGridParameterIntersectionVtk( caseUuid: string, ensembleName: string, gridName: string, parameterName: string, realizations: Array, - ): CancelablePromise { + ): CancelablePromise { return this.httpRequest.request({ method: 'GET', - url: '/grid/statistical_grid_parameter_intersection', + url: '/grid3d/statistical_grid_parameter_intersection_vtk', query: { 'case_uuid': caseUuid, 'ensemble_name': ensembleName, @@ -189,7 +251,7 @@ export class GridService { }); } /** - * Statistical Grid Parameter + * Statistical Grid Parameter Vtk * Get a grid parameter * @param caseUuid Sumo case uuid * @param ensembleName Ensemble name @@ -199,7 +261,7 @@ export class GridService { * @returns number Successful Response * @throws ApiError */ - public statisticalGridParameter( + public statisticalGridParameterVtk( caseUuid: string, ensembleName: string, gridName: string, @@ -208,7 +270,7 @@ export class GridService { ): CancelablePromise> { return this.httpRequest.request({ method: 'GET', - url: '/grid/statistical_grid_parameter', + url: '/grid3d/statistical_grid_parameter_vtk', query: { 'case_uuid': caseUuid, 'ensemble_name': ensembleName, diff --git a/frontend/src/framework/AtomStoreMaster.ts b/frontend/src/framework/AtomStoreMaster.ts new file mode 100644 index 000000000..43356331a --- /dev/null +++ b/frontend/src/framework/AtomStoreMaster.ts @@ -0,0 +1,38 @@ +import { WritableAtom, createStore } from "jotai"; + +export type AtomStore = ReturnType; + +export class AtomStoreMaster { + private _atomStates: Map, any> = new Map(); + private _atomStores: Map = new Map(); + + setAtomValue(atom: WritableAtom, value: any) { + this._atomStates.set(atom, value); + + this._atomStores.forEach((atomStore) => { + atomStore.set(atom, value); + }); + } + + makeAtomStoreForModuleInstance(moduleInstanceId: string) { + const atomStore = createStore(); + this._atomStores.set(moduleInstanceId, atomStore); + + const atomStates = Array.from(this._atomStates.entries()); + for (const [atom, value] of atomStates) { + atomStore.set(atom, value); + } + } + + getAtomStoreForModuleInstance(moduleInstanceId: string): AtomStore { + const atomStore = this._atomStores.get(moduleInstanceId); + if (!atomStore) { + throw new Error(`No atom store found for module instance with id: ${moduleInstanceId}`); + } + return atomStore; + } + + removeAtomStoreForModuleInstance(moduleInstanceId: string) { + this._atomStores.delete(moduleInstanceId); + } +} diff --git a/frontend/src/framework/Ensemble.ts b/frontend/src/framework/Ensemble.ts index 146e6857c..55152dc5e 100644 --- a/frontend/src/framework/Ensemble.ts +++ b/frontend/src/framework/Ensemble.ts @@ -4,12 +4,14 @@ import { EnsembleSensitivities, Sensitivity } from "./EnsembleSensitivities"; export class Ensemble { private _ensembleIdent: EnsembleIdent; + private _fieldIdentifier: string; private _caseName: string; private _realizationsArr: number[]; private _parameters: EnsembleParameters; private _sensitivities: EnsembleSensitivities | null; constructor( + fieldIdentifier: string, caseUuid: string, caseName: string, ensembleName: string, @@ -18,6 +20,7 @@ export class Ensemble { sensitivityArr: Sensitivity[] | null ) { this._ensembleIdent = new EnsembleIdent(caseUuid, ensembleName); + this._fieldIdentifier = fieldIdentifier; this._caseName = caseName; this._realizationsArr = Array.from(realizationsArr).sort((a, b) => a - b); this._parameters = new EnsembleParameters(parameterArr); @@ -32,6 +35,10 @@ export class Ensemble { return this._ensembleIdent; } + getFieldIdentifier(): string { + return this._fieldIdentifier; + } + getDisplayName(): string { return `${this._ensembleIdent.getEnsembleName()} (${this._caseName})`; } diff --git a/frontend/src/framework/GlobalAtoms.ts b/frontend/src/framework/GlobalAtoms.ts new file mode 100644 index 000000000..1dfc282ce --- /dev/null +++ b/frontend/src/framework/GlobalAtoms.ts @@ -0,0 +1,11 @@ +import { EnsembleSet } from "@framework/EnsembleSet"; + +import { atom } from "jotai"; +import { isEqual } from "lodash"; + +import { EnsembleRealizationFilterFunction } from "./WorkbenchSession"; +import { atomWithCompare } from "./utils/atomUtils"; + +export const EnsembleSetAtom = atomWithCompare(new EnsembleSet([]), isEqual); + +export const EnsembleRealizationFilterFunctionAtom = atom(null); diff --git a/frontend/src/framework/Module.tsx b/frontend/src/framework/Module.tsx index ec9bef6ef..a979d58c6 100644 --- a/frontend/src/framework/Module.tsx +++ b/frontend/src/framework/Module.tsx @@ -4,25 +4,60 @@ import { cloneDeep } from "lodash"; import { ChannelDefinition, ChannelReceiverDefinition } from "./DataChannelTypes"; import { InitialSettings } from "./InitialSettings"; -import { ModuleContext } from "./ModuleContext"; +import { SettingsContext, ViewContext } from "./ModuleContext"; import { ModuleInstance } from "./ModuleInstance"; import { DrawPreviewFunc } from "./Preview"; import { StateBaseType, StateOptions } from "./StateStore"; import { SyncSettingKey } from "./SyncSettings"; +import { InterfaceBaseType, InterfaceInitialization } from "./UniDirectionalSettingsToViewInterface"; import { Workbench } from "./Workbench"; import { WorkbenchServices } from "./WorkbenchServices"; import { WorkbenchSession } from "./WorkbenchSession"; import { WorkbenchSettings } from "./WorkbenchSettings"; -export type ModuleFCProps = { - moduleContext: ModuleContext; +export type ModuleSettingsProps< + TTStateType extends StateBaseType, + TInterfaceType extends InterfaceBaseType = { + baseStates: Record; + derivedStates: Record; + } +> = { + settingsContext: SettingsContext; + workbenchSession: WorkbenchSession; + workbenchServices: WorkbenchServices; + workbenchSettings: WorkbenchSettings; + initialSettings?: InitialSettings; +}; + +export type ModuleViewProps< + TTStateType extends StateBaseType, + TInterfaceType extends InterfaceBaseType = { + baseStates: Record; + derivedStates: Record; + } +> = { + viewContext: ViewContext; workbenchSession: WorkbenchSession; workbenchServices: WorkbenchServices; workbenchSettings: WorkbenchSettings; initialSettings?: InitialSettings; }; -export type ModuleFC = React.FC>; +export type ModuleSettings< + TTStateType extends StateBaseType, + TInterfaceType extends InterfaceBaseType = { + baseStates: Record; + derivedStates: Record; + } +> = React.FC>; + +export type ModuleView< + TTStateType extends StateBaseType, + TInterfaceType extends InterfaceBaseType = { + baseStates: Record; + derivedStates: Record; + } +> = React.FC>; export enum ImportState { NotImported = "NotImported", @@ -41,15 +76,16 @@ export interface ModuleOptions { channelReceiverDefinitions?: ChannelReceiverDefinition[]; } -export class Module { +export class Module { private _name: string; private _defaultTitle: string; - public viewFC: ModuleFC; - public settingsFC: ModuleFC; + public viewFC: ModuleView; + public settingsFC: ModuleSettings; protected _importState: ImportState; - private _moduleInstances: ModuleInstance[]; - private _defaultState: StateType | null; - private _stateOptions: StateOptions | undefined; + private _moduleInstances: ModuleInstance[]; + private _defaultState: TStateType | null; + private _settingsToViewInterfaceInitialization: InterfaceInitialization | null; + private _stateOptions: StateOptions | undefined; private _workbench: Workbench | null; private _syncableSettingKeys: SyncSettingKey[]; private _drawPreviewFunc: DrawPreviewFunc | null; @@ -65,6 +101,7 @@ export class Module { this._importState = ImportState.NotImported; this._moduleInstances = []; this._defaultState = null; + this._settingsToViewInterfaceInitialization = null; this._workbench = null; this._syncableSettingKeys = options.syncableSettingKeys ?? []; this._drawPreviewFunc = options.drawPreviewFunc ?? null; @@ -97,16 +134,20 @@ export class Module { this._workbench = workbench; } - setDefaultState(defaultState: StateType, options?: StateOptions): void { + setDefaultState(defaultState: TStateType, options?: StateOptions): void { this._defaultState = defaultState; this._stateOptions = options; this._moduleInstances.forEach((instance) => { - if (this._defaultState && !instance.isInitialised()) { + if (this._defaultState && !instance.isInitialized()) { instance.setDefaultState(cloneDeep(this._defaultState), cloneDeep(this._stateOptions)); } }); } + setSettingsToViewInterfaceInitialization(interfaceInitialization: InterfaceInitialization): void { + this._settingsToViewInterfaceInitialization = interfaceInitialization; + } + getSyncableSettingKeys(): SyncSettingKey[] { return this._syncableSettingKeys; } @@ -115,13 +156,14 @@ export class Module { return this._syncableSettingKeys.includes(key); } - makeInstance(instanceNumber: number): ModuleInstance { + makeInstance(instanceNumber: number): ModuleInstance { if (!this._workbench) { throw new Error("Module must be added to a workbench before making an instance"); } - const instance = new ModuleInstance({ + const instance = new ModuleInstance({ module: this, + workbench: this._workbench, instanceNumber, channelDefinitions: this._channelDefinitions, channelReceiverDefinitions: this._channelReceiverDefinitions, @@ -146,9 +188,15 @@ export class Module { if (this._importState !== ImportState.NotImported) { if (this._defaultState && this._importState === ImportState.Imported) { this._moduleInstances.forEach((instance) => { - if (this._defaultState && !instance.isInitialised()) { + if (instance.isInitialized()) { + return; + } + if (this._defaultState) { instance.setDefaultState(cloneDeep(this._defaultState), cloneDeep(this._stateOptions)); } + if (this._settingsToViewInterfaceInitialization) { + instance.makeSettingsToViewInterface(this._settingsToViewInterfaceInitialization); + } }); } return; @@ -160,9 +208,12 @@ export class Module { .then(() => { this.setImportState(ImportState.Imported); this._moduleInstances.forEach((instance) => { - if (this._defaultState && !instance.isInitialised()) { + if (this._defaultState) { instance.setDefaultState(cloneDeep(this._defaultState), cloneDeep(this._stateOptions)); } + if (this._settingsToViewInterfaceInitialization) { + instance.makeSettingsToViewInterface(this._settingsToViewInterfaceInitialization); + } }); }) .catch((e) => { diff --git a/frontend/src/framework/ModuleContext.ts b/frontend/src/framework/ModuleContext.ts index 0accb5518..c8c71b4e8 100644 --- a/frontend/src/framework/ModuleContext.ts +++ b/frontend/src/framework/ModuleContext.ts @@ -17,14 +17,20 @@ import { ModuleInstance } from "./ModuleInstance"; import { ModuleInstanceStatusController } from "./ModuleInstanceStatusController"; import { StateBaseType, StateStore, useSetStoreValue, useStoreState, useStoreValue } from "./StateStore"; import { SyncSettingKey } from "./SyncSettings"; +import { + InterfaceBaseType, + useSetSettingsToViewInterfaceValue, + useSettingsToViewInterfaceState, + useSettingsToViewInterfaceValue, +} from "./UniDirectionalSettingsToViewInterface"; import { useChannelReceiver } from "./internal/DataChannels/hooks/useChannelReceiver"; import { usePublishChannelContents } from "./internal/DataChannels/hooks/usePublishChannelContents"; -export class ModuleContext { - private _moduleInstance: ModuleInstance; - private _stateStore: StateStore; +export class ModuleContext { + protected _moduleInstance: ModuleInstance; + private _stateStore: StateStore; - constructor(moduleInstance: ModuleInstance, stateStore: StateStore) { + constructor(moduleInstance: ModuleInstance, stateStore: StateStore) { this._moduleInstance = moduleInstance; this._stateStore = stateStore; } @@ -33,19 +39,23 @@ export class ModuleContext { return this._moduleInstance.getId(); } - getStateStore(): StateStore { + getStateStore(): StateStore { return this._stateStore; } - useStoreState(key: K): [S[K], (value: S[K] | ((prev: S[K]) => S[K])) => void] { + useStoreState( + key: K + ): [TStateType[K], (value: TStateType[K] | ((prev: TStateType[K]) => TStateType[K])) => void] { return useStoreState(this._stateStore, key); } - useStoreValue(key: K): S[K] { + useStoreValue(key: K): TStateType[K] { return useStoreValue(this._stateStore, key); } - useSetStoreValue(key: K): (newValue: S[K] | ((prev: S[K]) => S[K])) => void { + useSetStoreValue( + key: K + ): (newValue: TStateType[K] | ((prev: TStateType[K]) => TStateType[K])) => void { return useSetStoreValue(this._stateStore, key); } @@ -102,4 +112,38 @@ export class ModuleContext { ...options, }); } + + useSettingsToViewInterfaceState( + key: TKey + ): [Awaited, (value: TInterfaceType["baseStates"][TKey]) => void] { + return useSettingsToViewInterfaceState(this._moduleInstance.getUniDirectionalSettingsToViewInterface(), key); + } + + useSettingsToViewInterfaceValue( + key: TKey + ): TInterfaceType["baseStates"][TKey]; + useSettingsToViewInterfaceValue( + key: TKey + ): TInterfaceType["derivedStates"][TKey]; + useSettingsToViewInterfaceValue< + TKey extends keyof (TInterfaceType["baseStates"] | TInterfaceType["derivedStates"]) + >(key: TKey): TInterfaceType["baseStates"][TKey] | TInterfaceType["derivedStates"][TKey] { + return useSettingsToViewInterfaceValue(this._moduleInstance.getUniDirectionalSettingsToViewInterface(), key); + } + + useSetSettingsToViewInterfaceValue( + key: TKey + ): (value: TInterfaceType["baseStates"][TKey]) => void { + return useSetSettingsToViewInterfaceValue(this._moduleInstance.getUniDirectionalSettingsToViewInterface(), key); + } } + +export type ViewContext = Omit< + ModuleContext, + "useInterfaceState" | "useSetInterfaceValue" +>; + +export type SettingsContext = ModuleContext< + StateType, + TInterfaceType +>; diff --git a/frontend/src/framework/ModuleInstance.ts b/frontend/src/framework/ModuleInstance.ts index e3cfd3589..fd0914974 100644 --- a/frontend/src/framework/ModuleInstance.ts +++ b/frontend/src/framework/ModuleInstance.ts @@ -2,12 +2,19 @@ import { ErrorInfo } from "react"; import { cloneDeep } from "lodash"; +import { AtomStore } from "./AtomStoreMaster"; import { ChannelDefinition, ChannelReceiverDefinition } from "./DataChannelTypes"; import { InitialSettings } from "./InitialSettings"; -import { ImportState, Module, ModuleFC } from "./Module"; +import { ImportState, Module, ModuleSettings, ModuleView } from "./Module"; import { ModuleContext } from "./ModuleContext"; import { StateBaseType, StateOptions, StateStore } from "./StateStore"; import { SyncSettingKey } from "./SyncSettings"; +import { + InterfaceBaseType, + InterfaceInitialization, + UniDirectionalSettingsToViewInterface, +} from "./UniDirectionalSettingsToViewInterface"; +import { Workbench } from "./Workbench"; import { ChannelManager } from "./internal/DataChannels/ChannelManager"; import { ModuleInstanceStatusControllerInternal } from "./internal/ModuleInstanceStatusControllerInternal"; @@ -18,34 +25,37 @@ export enum ModuleInstanceState { RESETTING, } -export interface ModuleInstanceOptions { - module: Module; +export interface ModuleInstanceOptions { + module: Module; + workbench: Workbench; instanceNumber: number; channelDefinitions: ChannelDefinition[] | null; channelReceiverDefinitions: ChannelReceiverDefinition[] | null; } -export class ModuleInstance { +export class ModuleInstance { private _id: string; private _title: string; private _initialised: boolean; private _moduleInstanceState: ModuleInstanceState; private _fatalError: { err: Error; errInfo: ErrorInfo } | null; private _syncedSettingKeys: SyncSettingKey[]; - private _stateStore: StateStore | null; - private _module: Module; - private _context: ModuleContext | null; + private _stateStore: StateStore | null; + private _module: Module; + private _context: ModuleContext | null; private _importStateSubscribers: Set<() => void>; private _moduleInstanceStateSubscribers: Set<(moduleInstanceState: ModuleInstanceState) => void>; private _syncedSettingsSubscribers: Set<(syncedSettings: SyncSettingKey[]) => void>; private _titleChangeSubscribers: Set<(title: string) => void>; - private _cachedDefaultState: StateType | null; - private _cachedStateStoreOptions?: StateOptions; + private _cachedDefaultState: TStateType | null; + private _cachedStateStoreOptions?: StateOptions; private _initialSettings: InitialSettings | null; private _statusController: ModuleInstanceStatusControllerInternal; private _channelManager: ChannelManager; + private _workbench: Workbench; + private _settingsViewInterface: UniDirectionalSettingsToViewInterface | null; - constructor(options: ModuleInstanceOptions) { + constructor(options: ModuleInstanceOptions) { this._id = `${options.module.getName()}-${options.instanceNumber}`; this._title = options.module.getDefaultTitle(); this._stateStore = null; @@ -62,6 +72,8 @@ export class ModuleInstance { this._cachedDefaultState = null; this._initialSettings = null; this._statusController = new ModuleInstanceStatusControllerInternal(); + this._workbench = options.workbench; + this._settingsViewInterface = null; this._channelManager = new ChannelManager(this._id); @@ -79,22 +91,37 @@ export class ModuleInstance { } } + getAtomStore(): AtomStore { + return this._workbench.getAtomStoreMaster().getAtomStoreForModuleInstance(this._id); + } + + getUniDirectionalSettingsToViewInterface(): UniDirectionalSettingsToViewInterface { + if (!this._settingsViewInterface) { + throw `Module instance '${this._title}' does not have an interface yet. Did you forget to init the module?`; + } + return this._settingsViewInterface; + } + getChannelManager(): ChannelManager { return this._channelManager; } - setDefaultState(defaultState: StateType, options?: StateOptions): void { + setDefaultState(defaultState: TStateType, options?: StateOptions): void { if (this._cachedDefaultState === null) { this._cachedDefaultState = defaultState; this._cachedStateStoreOptions = options; } - this._stateStore = new StateStore(cloneDeep(defaultState), options); - this._context = new ModuleContext(this, this._stateStore); + this._stateStore = new StateStore(cloneDeep(defaultState), options); + this._context = new ModuleContext(this, this._stateStore); this._initialised = true; this.setModuleInstanceState(ModuleInstanceState.OK); } + makeSettingsToViewInterface(interfaceInitialization: InterfaceInitialization) { + this._settingsViewInterface = new UniDirectionalSettingsToViewInterface(interfaceInitialization); + } + addSyncedSetting(settingKey: SyncSettingKey): void { this._syncedSettingKeys.push(settingKey); this.notifySubscribersAboutSyncedSettingKeysChange(); @@ -124,15 +151,15 @@ export class ModuleInstance { }; } - isInitialised(): boolean { + isInitialized(): boolean { return this._initialised; } - getViewFC(): ModuleFC { + getViewFC(): ModuleView { return this._module.viewFC; } - getSettingsFC(): ModuleFC { + getSettingsFC(): ModuleSettings { return this._module.settingsFC; } @@ -140,7 +167,7 @@ export class ModuleInstance { return this._module.getImportState(); } - getContext(): ModuleContext { + getContext(): ModuleContext { if (!this._context) { throw `Module context is not available yet. Did you forget to init the module '${this._title}.'?`; } @@ -177,7 +204,7 @@ export class ModuleInstance { }); } - getModule(): Module { + getModule(): Module { return this._module; } @@ -245,7 +272,7 @@ export class ModuleInstance { this.setModuleInstanceState(ModuleInstanceState.RESETTING); return new Promise((resolve) => { - this.setDefaultState(this._cachedDefaultState as StateType, this._cachedStateStoreOptions); + this.setDefaultState(this._cachedDefaultState as TStateType, this._cachedStateStoreOptions); resolve(); }); } diff --git a/frontend/src/framework/ModuleRegistry.ts b/frontend/src/framework/ModuleRegistry.ts index 3f5417e43..90979b4cb 100644 --- a/frontend/src/framework/ModuleRegistry.ts +++ b/frontend/src/framework/ModuleRegistry.ts @@ -3,6 +3,7 @@ import { Module } from "./Module"; import { DrawPreviewFunc } from "./Preview"; import { StateBaseType, StateOptions } from "./StateStore"; import { SyncSettingKey } from "./SyncSettings"; +import { InterfaceBaseType, InterfaceInitialization } from "./UniDirectionalSettingsToViewInterface"; import { ModuleNotFoundPlaceholder } from "./internal/ModuleNotFoundPlaceholder"; export type RegisterModuleOptions = { @@ -26,14 +27,20 @@ export class ModuleNotFoundError extends Error { } export class ModuleRegistry { - private static _registeredModules: Record> = {}; - private static _moduleNotFoundPlaceholders: Record> = {}; + private static _registeredModules: Record> = {}; + private static _moduleNotFoundPlaceholders: Record> = {}; /* eslint-disable-next-line @typescript-eslint/no-empty-function */ private constructor() {} - static registerModule(options: RegisterModuleOptions): Module { - const module = new Module({ + static registerModule< + TStateType extends StateBaseType, + TInterfaceType extends InterfaceBaseType = { + baseStates: Record; + derivedStates: Record; + } + >(options: RegisterModuleOptions): Module { + const module = new Module({ name: options.moduleName, defaultTitle: options.defaultTitle, syncableSettingKeys: options.syncableSettingKeys ?? [], @@ -46,33 +53,43 @@ export class ModuleRegistry { return module; } - static initModule( + static initModule< + TStateType extends StateBaseType, + TInterfaceType extends InterfaceBaseType = { + baseStates: Record; + derivedStates: Record; + } + >( moduleName: string, defaultState: TStateType, - options?: StateOptions - ): Module { + options?: StateOptions, + interfaceInitialization?: InterfaceInitialization + ): Module { const module = this._registeredModules[moduleName]; if (module) { module.setDefaultState(defaultState, options); - return module as Module; + if (interfaceInitialization) { + module.setSettingsToViewInterfaceInitialization(interfaceInitialization); + } + return module as Module; } throw new ModuleNotFoundError(moduleName); } - static getModule(moduleName: string): Module { + static getModule(moduleName: string): Module { const module = this._registeredModules[moduleName]; if (module) { - return module as Module; + return module as Module; } const placeholder = this._moduleNotFoundPlaceholders[moduleName]; if (placeholder) { - return placeholder as Module; + return placeholder as Module; } this._moduleNotFoundPlaceholders[moduleName] = new ModuleNotFoundPlaceholder(moduleName); - return this._moduleNotFoundPlaceholders[moduleName] as Module; + return this._moduleNotFoundPlaceholders[moduleName] as Module; } - static getRegisteredModules(): Record> { + static getRegisteredModules(): Record> { return this._registeredModules; } } diff --git a/frontend/src/framework/StatusWriter.ts b/frontend/src/framework/StatusWriter.ts index 6611ab1f5..633860617 100644 --- a/frontend/src/framework/StatusWriter.ts +++ b/frontend/src/framework/StatusWriter.ts @@ -1,6 +1,6 @@ import React from "react"; -import { ModuleContext } from "./ModuleContext"; +import { SettingsContext, ViewContext } from "./ModuleContext"; import { ModuleInstanceStatusController, StatusMessageType, StatusSource } from "./ModuleInstanceStatusController"; export class ViewStatusWriter { @@ -47,8 +47,8 @@ export class SettingsStatusWriter { } } -export function useViewStatusWriter(moduleContext: ModuleContext): ViewStatusWriter { - const statusController = moduleContext.getStatusController(); +export function useViewStatusWriter(viewContext: ViewContext): ViewStatusWriter { + const statusController = viewContext.getStatusController(); const statusWriter = React.useRef(new ViewStatusWriter(statusController)); @@ -62,8 +62,8 @@ export function useViewStatusWriter(moduleContext: ModuleContext): ViewStat return statusWriter.current; } -export function useSettingsStatusWriter(moduleContext: ModuleContext): SettingsStatusWriter { - const statusController = moduleContext.getStatusController(); +export function useSettingsStatusWriter(settingsContext: SettingsContext): SettingsStatusWriter { + const statusController = settingsContext.getStatusController(); const statusWriter = React.useRef(new SettingsStatusWriter(statusController)); diff --git a/frontend/src/framework/UniDirectionalSettingsToViewInterface.ts b/frontend/src/framework/UniDirectionalSettingsToViewInterface.ts new file mode 100644 index 000000000..d51a3261a --- /dev/null +++ b/frontend/src/framework/UniDirectionalSettingsToViewInterface.ts @@ -0,0 +1,114 @@ +import { Atom, Getter, PrimitiveAtom, atom, useAtom, useAtomValue } from "jotai"; + +export type InterfaceBaseType = { + baseStates?: Record; + derivedStates?: Record; +}; +export type InterfaceInitialization = { + baseStates?: { + [K in keyof T["baseStates"]]: T["baseStates"][K]; + }; + derivedStates?: { + [K in keyof T["derivedStates"]]: (get: Getter) => T["derivedStates"][K]; + }; +}; + +export class UniDirectionalSettingsToViewInterface { + private _baseAtoms: Map< + keyof TInterfaceType["baseStates"], + PrimitiveAtom + > = new Map(); + private _derivedAtoms: Map< + keyof TInterfaceType["derivedStates"], + Atom + > = new Map(); + + constructor(initialization: InterfaceInitialization) { + // Make sure we don't have any overlapping keys + for (const key in initialization.baseStates) { + if (key in (initialization.derivedStates ?? {})) { + throw new Error(`Key '${String(key)}' is present in both baseStates and derivedStates`); + } + } + + for (const key in initialization.baseStates) { + const value = initialization.baseStates[key]; + this._baseAtoms.set(key, atom(value as TInterfaceType["baseStates"][keyof TInterfaceType["baseStates"]])); + } + + for (const key in initialization.derivedStates) { + const value = initialization.derivedStates[key]; + this._derivedAtoms.set( + key, + atom((get) => value(get)) + ); + } + } + + getAtom(key: T): PrimitiveAtom; + getAtom(key: T): Atom { + const derivedAtom = this._derivedAtoms.get(key); + if (derivedAtom) { + return derivedAtom as Atom; + } + + const baseAtom = this._baseAtoms.get(key); + if (baseAtom) { + return baseAtom as PrimitiveAtom; + } + + throw new Error(`Atom for key '${String(key)}' not found`); + } +} + +export function useSettingsToViewInterfaceState< + TInterfaceType extends InterfaceBaseType, + TKey extends keyof TInterfaceType["baseStates"] +>( + interfaceInstance: UniDirectionalSettingsToViewInterface, + key: TKey +): [Awaited, (value: TInterfaceType["baseStates"][TKey]) => void] { + const [value, set] = useAtom(interfaceInstance.getAtom(key)); + + return [ + value, + (value: TInterfaceType["baseStates"][TKey]) => { + set(value); + }, + ]; +} + +export function useSettingsToViewInterfaceValue< + TInterfaceType extends InterfaceBaseType, + TKey extends keyof TInterfaceType["baseStates"] +>( + interfaceInstance: UniDirectionalSettingsToViewInterface, + key: TKey +): TInterfaceType["baseStates"][TKey]; +export function useSettingsToViewInterfaceValue< + InterfaceType extends InterfaceBaseType, + K extends keyof InterfaceType["derivedStates"] +>(interfaceInstance: UniDirectionalSettingsToViewInterface, key: K): InterfaceType["derivedStates"][K]; +export function useSettingsToViewInterfaceValue< + InterfaceType extends InterfaceBaseType, + K extends keyof InterfaceType["baseStates"] | keyof InterfaceType["derivedStates"] +>( + interfaceInstance: UniDirectionalSettingsToViewInterface, + key: K +): InterfaceType["baseStates"][K] | InterfaceType["derivedStates"][K] { + return useAtomValue(interfaceInstance.getAtom(key)); +} + +export function useSetSettingsToViewInterfaceValue< + TInterfaceType extends InterfaceBaseType, + TKey extends keyof TInterfaceType["baseStates"] +>( + interfaceInstance: UniDirectionalSettingsToViewInterface, + key: TKey +): (value: TInterfaceType["baseStates"][TKey]) => void { + const [, set] = useAtom(interfaceInstance.getAtom(key)); + + return (value: TInterfaceType["baseStates"][TKey]) => { + set(value); + }; +} diff --git a/frontend/src/framework/Workbench.ts b/frontend/src/framework/Workbench.ts index 2ebee3115..a5a4438f9 100644 --- a/frontend/src/framework/Workbench.ts +++ b/frontend/src/framework/Workbench.ts @@ -1,5 +1,6 @@ import { QueryClient } from "@tanstack/react-query"; +import { AtomStoreMaster } from "./AtomStoreMaster"; import { EnsembleIdent } from "./EnsembleIdent"; import { GuiMessageBroker, GuiState } from "./GuiMessageBroker"; import { InitialSettings } from "./InitialSettings"; @@ -27,7 +28,7 @@ export type LayoutElement = { }; export class Workbench { - private _moduleInstances: ModuleInstance[]; + private _moduleInstances: ModuleInstance[]; private _workbenchSession: WorkbenchSessionPrivate; private _workbenchServices: PrivateWorkbenchServices; private _workbenchSettings: PrivateWorkbenchSettings; @@ -35,10 +36,12 @@ export class Workbench { private _subscribersMap: { [key: string]: Set<() => void> }; private _layout: LayoutElement[]; private _perModuleRunningInstanceNumber: Record; + private _atomStoreMaster: AtomStoreMaster; constructor() { this._moduleInstances = []; - this._workbenchSession = new WorkbenchSessionPrivate(); + this._atomStoreMaster = new AtomStoreMaster(); + this._workbenchSession = new WorkbenchSessionPrivate(this._atomStoreMaster); this._workbenchServices = new PrivateWorkbenchServices(this); this._workbenchSettings = new PrivateWorkbenchSettings(); this._guiMessageBroker = new GuiMessageBroker(); @@ -60,6 +63,10 @@ export class Workbench { return this._layout; } + getAtomStoreMaster(): AtomStoreMaster { + return this._atomStoreMaster; + } + getWorkbenchSession(): WorkbenchSessionPrivate { return this._workbenchSession; } @@ -94,11 +101,11 @@ export class Workbench { }; } - getModuleInstances(): ModuleInstance[] { + getModuleInstances(): ModuleInstance[] { return this._moduleInstances; } - getModuleInstance(id: string): ModuleInstance | undefined { + getModuleInstance(id: string): ModuleInstance | undefined { return this._moduleInstances.find((moduleInstance) => moduleInstance.getId() === id); } @@ -122,6 +129,7 @@ export class Workbench { module.setWorkbench(this); const moduleInstance = module.makeInstance(this.getNextModuleInstanceNumber(module.getName())); + this._atomStoreMaster.makeAtomStoreForModuleInstance(moduleInstance.getId()); this._moduleInstances.push(moduleInstance); this._layout[index] = { ...this._layout[index], moduleInstanceId: moduleInstance.getId() }; this.notifySubscribers(WorkbenchEvents.ModuleInstancesChanged); @@ -143,7 +151,7 @@ export class Workbench { this.notifySubscribers(WorkbenchEvents.ModuleInstancesChanged); } - makeAndAddModuleInstance(moduleName: string, layout: LayoutElement): ModuleInstance { + makeAndAddModuleInstance(moduleName: string, layout: LayoutElement): ModuleInstance { const module = ModuleRegistry.getModule(moduleName); if (!module) { throw new Error(`Module ${moduleName} not found`); @@ -152,6 +160,7 @@ export class Workbench { module.setWorkbench(this); const moduleInstance = module.makeInstance(this.getNextModuleInstanceNumber(module.getName())); + this._atomStoreMaster.makeAtomStoreForModuleInstance(moduleInstance.getId()); this._moduleInstances.push(moduleInstance); this._layout.push({ ...layout, moduleInstanceId: moduleInstance.getId() }); @@ -169,6 +178,8 @@ export class Workbench { this._moduleInstances = this._moduleInstances.filter((el) => el.getId() !== moduleInstanceId); + this._atomStoreMaster.removeAtomStoreForModuleInstance(moduleInstanceId); + const newLayout = this._layout.filter((el) => el.moduleInstanceId !== moduleInstanceId); this.setLayout(newLayout); const activeModuleInstanceId = this.getGuiMessageBroker().getState(GuiState.ActiveModuleInstanceId); diff --git a/frontend/src/framework/components/SingleEnsembleSelect/singleEnsembleSelect.tsx b/frontend/src/framework/components/EnsembleDropdown/ensembleDropdown.tsx similarity index 89% rename from frontend/src/framework/components/SingleEnsembleSelect/singleEnsembleSelect.tsx rename to frontend/src/framework/components/EnsembleDropdown/ensembleDropdown.tsx index 5956af8a1..0e496b5ba 100644 --- a/frontend/src/framework/components/SingleEnsembleSelect/singleEnsembleSelect.tsx +++ b/frontend/src/framework/components/EnsembleDropdown/ensembleDropdown.tsx @@ -2,13 +2,13 @@ import { EnsembleIdent } from "@framework/EnsembleIdent"; import { EnsembleSet } from "@framework/EnsembleSet"; import { Dropdown, DropdownOption, DropdownProps } from "@lib/components/Dropdown"; -type SingleEnsembleSelectProps = { +type EnsembleDropdownProps = { ensembleSet: EnsembleSet; value: EnsembleIdent | null; onChange: (ensembleIdent: EnsembleIdent | null) => void; } & Omit; -export function SingleEnsembleSelect(props: SingleEnsembleSelectProps): JSX.Element { +export function EnsembleDropdown(props: EnsembleDropdownProps): JSX.Element { const { ensembleSet, value, onChange, ...rest } = props; function handleSelectionChanged(selectedEnsembleIdentStr: string) { diff --git a/frontend/src/framework/components/EnsembleDropdown/index.ts b/frontend/src/framework/components/EnsembleDropdown/index.ts new file mode 100644 index 000000000..85b8ec2a6 --- /dev/null +++ b/frontend/src/framework/components/EnsembleDropdown/index.ts @@ -0,0 +1 @@ +export { EnsembleDropdown } from "./ensembleDropdown"; diff --git a/frontend/src/framework/components/MultiEnsembleSelect/multiEnsembleSelect.tsx b/frontend/src/framework/components/EnsembleSelect/ensembleSelect.tsx similarity index 69% rename from frontend/src/framework/components/MultiEnsembleSelect/multiEnsembleSelect.tsx rename to frontend/src/framework/components/EnsembleSelect/ensembleSelect.tsx index 260af11f9..c5a5902e2 100644 --- a/frontend/src/framework/components/MultiEnsembleSelect/multiEnsembleSelect.tsx +++ b/frontend/src/framework/components/EnsembleSelect/ensembleSelect.tsx @@ -2,14 +2,14 @@ import { EnsembleIdent } from "@framework/EnsembleIdent"; import { EnsembleSet } from "@framework/EnsembleSet"; import { Select, SelectOption, SelectProps } from "@lib/components/Select"; -type MultiEnsembleSelectProps = { +type EnsembleSelectProps = { ensembleSet: EnsembleSet; value: EnsembleIdent[]; onChange: (ensembleIdentArr: EnsembleIdent[]) => void; -} & Omit; +} & Omit; -export function MultiEnsembleSelect(props: MultiEnsembleSelectProps): JSX.Element { - const { ensembleSet, value, onChange, ...rest } = props; +export function EnsembleSelect(props: EnsembleSelectProps): JSX.Element { + const { ensembleSet, value, onChange, multiple, ...rest } = props; function handleSelectionChanged(selectedEnsembleIdentStrArr: string[]) { const identArr: EnsembleIdent[] = []; @@ -33,7 +33,15 @@ export function MultiEnsembleSelect(props: MultiEnsembleSelectProps): JSX.Elemen selectedArr.push(ident.toString()); } + const isMultiple = multiple ?? true; + return ( - ); } diff --git a/frontend/src/framework/components/EnsembleSelect/index.ts b/frontend/src/framework/components/EnsembleSelect/index.ts new file mode 100644 index 000000000..5aa25198b --- /dev/null +++ b/frontend/src/framework/components/EnsembleSelect/index.ts @@ -0,0 +1 @@ +export { EnsembleSelect } from "./ensembleSelect"; diff --git a/frontend/src/framework/components/MultiEnsembleSelect/index.ts b/frontend/src/framework/components/MultiEnsembleSelect/index.ts deleted file mode 100644 index 9f1ae82d1..000000000 --- a/frontend/src/framework/components/MultiEnsembleSelect/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { MultiEnsembleSelect } from "./multiEnsembleSelect"; diff --git a/frontend/src/framework/components/SingleEnsembleSelect/index.ts b/frontend/src/framework/components/SingleEnsembleSelect/index.ts deleted file mode 100644 index de8bffbb0..000000000 --- a/frontend/src/framework/components/SingleEnsembleSelect/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SingleEnsembleSelect } from "./singleEnsembleSelect"; diff --git a/frontend/src/framework/internal/EnsembleSetLoader.ts b/frontend/src/framework/internal/EnsembleSetLoader.ts index 9383037b7..313791e8c 100644 --- a/frontend/src/framework/internal/EnsembleSetLoader.ts +++ b/frontend/src/framework/internal/EnsembleSetLoader.ts @@ -90,6 +90,7 @@ export async function loadEnsembleSetMetadataFromBackend( outEnsembleArr.push( new Ensemble( + ensembleDetails.field_identifier, ensembleDetails.case_uuid, ensembleDetails.case_name, ensembleDetails.name, diff --git a/frontend/src/framework/internal/ModuleNotFoundPlaceholder.tsx b/frontend/src/framework/internal/ModuleNotFoundPlaceholder.tsx index 7039c179a..b3c6643da 100644 --- a/frontend/src/framework/internal/ModuleNotFoundPlaceholder.tsx +++ b/frontend/src/framework/internal/ModuleNotFoundPlaceholder.tsx @@ -4,7 +4,13 @@ import { Button } from "@lib/components/Button"; import { Tag } from "@lib/components/Tag"; import { BugReport, Forum, WebAssetOff } from "@mui/icons-material"; -export class ModuleNotFoundPlaceholder extends Module> { +export class ModuleNotFoundPlaceholder extends Module< + Record, + { + baseStates: Record; + derivedStates: Record; + } +> { constructor(moduleName: string) { super({ name: moduleName, @@ -13,7 +19,13 @@ export class ModuleNotFoundPlaceholder extends Module> { this._importState = ImportState.Imported; } - makeInstance(instanceNumber: number): ModuleInstance> { + makeInstance(instanceNumber: number): ModuleInstance< + Record, + { + baseStates: Record; + derivedStates: Record; + } + > { const instance = super.makeInstance(instanceNumber); instance.setDefaultState({}); return instance; diff --git a/frontend/src/framework/internal/WorkbenchSessionPrivate.ts b/frontend/src/framework/internal/WorkbenchSessionPrivate.ts index c7c1c17bf..b1927c8a3 100644 --- a/frontend/src/framework/internal/WorkbenchSessionPrivate.ts +++ b/frontend/src/framework/internal/WorkbenchSessionPrivate.ts @@ -1,9 +1,16 @@ +import { AtomStoreMaster } from "@framework/AtomStoreMaster"; +import { EnsembleIdent } from "@framework/EnsembleIdent"; + import { EnsembleSet } from "../EnsembleSet"; +import { EnsembleRealizationFilterFunctionAtom, EnsembleSetAtom } from "../GlobalAtoms"; import { WorkbenchSession, WorkbenchSessionEvent } from "../WorkbenchSession"; export class WorkbenchSessionPrivate extends WorkbenchSession { - constructor() { + private _atomStoreMaster: AtomStoreMaster; + + constructor(atomStoreMaster: AtomStoreMaster) { super(); + this._atomStoreMaster = atomStoreMaster; } setEnsembleSetLoadingState(isLoading: boolean): void { @@ -13,6 +20,12 @@ export class WorkbenchSessionPrivate extends WorkbenchSession { setEnsembleSet(newEnsembleSet: EnsembleSet): void { this._ensembleSet = newEnsembleSet; this._realizationFilterSet.synchronizeWithEnsembleSet(this._ensembleSet); + this._atomStoreMaster.setAtomValue(EnsembleSetAtom, newEnsembleSet); + this._atomStoreMaster.setAtomValue( + EnsembleRealizationFilterFunctionAtom, + () => (ensembleIdent: EnsembleIdent) => + this._realizationFilterSet.getRealizationFilterForEnsembleIdent(ensembleIdent).getFilteredRealizations() + ); this.notifySubscribers(WorkbenchSessionEvent.EnsembleSetChanged); this.notifySubscribers(WorkbenchSessionEvent.RealizationFilterSetChanged); } diff --git a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/channelReceiverNodesWrapper.tsx b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/channelReceiverNodesWrapper.tsx index b5bd58faf..c1f5ab76e 100644 --- a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/channelReceiverNodesWrapper.tsx +++ b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/channelReceiverNodesWrapper.tsx @@ -14,7 +14,7 @@ import { ChannelReceiverNode } from "./channelReceiverNode"; export type ChannelReceiverNodesWrapperProps = { forwardedRef: React.RefObject; - moduleInstance: ModuleInstance; + moduleInstance: ModuleInstance; workbench: Workbench; }; diff --git a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/header.tsx b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/header.tsx index 17f4106c4..be825b7a1 100644 --- a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/header.tsx +++ b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/header.tsx @@ -14,7 +14,7 @@ import { resolveClassNames } from "@lib/utils/resolveClassNames"; import { Close, Error, Input, Output, Warning } from "@mui/icons-material"; export type HeaderProps = { - moduleInstance: ModuleInstance; + moduleInstance: ModuleInstance; isDragged: boolean; onPointerDown: (event: React.PointerEvent) => void; onRemoveClick: (event: React.PointerEvent) => void; diff --git a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/viewContent.tsx b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/viewContent.tsx index 6a97cc7f9..bdf8e102a 100644 --- a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/viewContent.tsx +++ b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/viewContent.tsx @@ -6,12 +6,15 @@ import { StatusSource } from "@framework/ModuleInstanceStatusController"; import { Workbench } from "@framework/Workbench"; import { DebugProfiler } from "@framework/internal/components/DebugProfiler"; import { ErrorBoundary } from "@framework/internal/components/ErrorBoundary"; +import { HydrateQueryClientAtom } from "@framework/internal/components/HydrateQueryClientAtom"; import { CircularProgress } from "@lib/components/CircularProgress"; +import { Provider } from "jotai"; + import { CrashView } from "./crashView"; type ViewContentProps = { - moduleInstance: ModuleInstance; + moduleInstance: ModuleInstance; workbench: Workbench; }; @@ -21,6 +24,8 @@ export const ViewContent = React.memo((props: ViewContentProps) => { ModuleInstanceState.INITIALIZING ); + const atomStore = props.moduleInstance.getAtomStore(); + React.useEffect( function handleMount() { setModuleInstanceState(props.moduleInstance.getModuleInstanceState()); @@ -72,11 +77,11 @@ export const ViewContent = React.memo((props: ViewContentProps) => { ); } - if (!props.moduleInstance.isInitialised()) { + if (!props.moduleInstance.isInitialized()) { return (
-
Initialising...
+
Initializing...
); } @@ -84,7 +89,7 @@ export const ViewContent = React.memo((props: ViewContentProps) => { if (importState === ImportState.Failed) { return (
- Module could not be imported. Please check the spelling when registering and initialising the module. + Module could not be imported. Please check the spelling when registering and initializing the module.
); } @@ -126,13 +131,17 @@ export const ViewContent = React.memo((props: ViewContentProps) => { source={StatusSource.View} guiMessageBroker={props.workbench.getGuiMessageBroker()} > - + + + + +
diff --git a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/viewWrapper.tsx b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/viewWrapper.tsx index ca4c5fdf3..9eccae52c 100644 --- a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/viewWrapper.tsx +++ b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/viewWrapper.tsx @@ -14,7 +14,7 @@ import { ViewWrapperPlaceholder } from "../viewWrapperPlaceholder"; type ViewWrapperProps = { isActive: boolean; - moduleInstance: ModuleInstance; + moduleInstance: ModuleInstance; workbench: Workbench; width: number; height: number; diff --git a/frontend/src/framework/internal/components/ErrorBoundary/errorBoundary.tsx b/frontend/src/framework/internal/components/ErrorBoundary/errorBoundary.tsx index f9497bf5b..2eb78a66c 100644 --- a/frontend/src/framework/internal/components/ErrorBoundary/errorBoundary.tsx +++ b/frontend/src/framework/internal/components/ErrorBoundary/errorBoundary.tsx @@ -3,7 +3,7 @@ import React from "react"; import { ModuleInstance } from "@framework/ModuleInstance"; export type Props = { - moduleInstance: ModuleInstance; + moduleInstance: ModuleInstance; children?: React.ReactNode; }; diff --git a/frontend/src/framework/internal/components/HydrateQueryClientAtom/hydrateQueryClientAtom.tsx b/frontend/src/framework/internal/components/HydrateQueryClientAtom/hydrateQueryClientAtom.tsx new file mode 100644 index 000000000..b3bcc9284 --- /dev/null +++ b/frontend/src/framework/internal/components/HydrateQueryClientAtom/hydrateQueryClientAtom.tsx @@ -0,0 +1,20 @@ +import React from "react"; + +import { useQueryClient } from "@tanstack/react-query"; + +import { queryClientAtom } from "jotai-tanstack-query"; +import { useHydrateAtoms } from "jotai/utils"; + +export type HydrateQueryClientAtomProps = { + children?: React.ReactNode; +}; + +export const HydrateQueryClientAtom: React.FC = (props) => { + const queryClient = useQueryClient(); + const map = new Map(); + map.set(queryClientAtom, queryClient); + useHydrateAtoms(map); + return <>{props.children}; +}; + +HydrateQueryClientAtom.displayName = "HydrateQueryClientAtom"; diff --git a/frontend/src/framework/internal/components/HydrateQueryClientAtom/index.ts b/frontend/src/framework/internal/components/HydrateQueryClientAtom/index.ts new file mode 100644 index 000000000..92776e93f --- /dev/null +++ b/frontend/src/framework/internal/components/HydrateQueryClientAtom/index.ts @@ -0,0 +1,2 @@ +export { HydrateQueryClientAtom } from "./hydrateQueryClientAtom"; +export type { HydrateQueryClientAtomProps } from "./hydrateQueryClientAtom"; diff --git a/frontend/src/framework/internal/components/LeftSettingsPanel/private-components/moduleSettings.tsx b/frontend/src/framework/internal/components/LeftSettingsPanel/private-components/moduleSettings.tsx index f5da76f1e..f44a73792 100644 --- a/frontend/src/framework/internal/components/LeftSettingsPanel/private-components/moduleSettings.tsx +++ b/frontend/src/framework/internal/components/LeftSettingsPanel/private-components/moduleSettings.tsx @@ -10,10 +10,13 @@ import { CircularProgress } from "@lib/components/CircularProgress"; import { resolveClassNames } from "@lib/utils/resolveClassNames"; import { Settings as SettingsIcon } from "@mui/icons-material"; +import { Provider } from "jotai"; + import { DebugProfiler } from "../../DebugProfiler"; +import { HydrateQueryClientAtom } from "../../HydrateQueryClientAtom"; type ModuleSettingsProps = { - moduleInstance: ModuleInstance; + moduleInstance: ModuleInstance; activeModuleInstanceId: string; workbench: Workbench; }; @@ -23,6 +26,7 @@ export const ModuleSettings: React.FC = (props) => { const [moduleInstanceState, setModuleInstanceState] = React.useState( ModuleInstanceState.INITIALIZING ); + const atomStore = props.moduleInstance.getAtomStore(); React.useEffect(() => { setModuleInstanceState(props.moduleInstance.getModuleInstanceState()); @@ -38,7 +42,7 @@ export const ModuleSettings: React.FC = (props) => { return unsubscribeFunc; }, [props.moduleInstance]); - if (importState !== ImportState.Imported || !props.moduleInstance.isInitialised()) { + if (importState !== ImportState.Imported || !props.moduleInstance.isInitialized()) { return null; } @@ -99,13 +103,17 @@ export const ModuleSettings: React.FC = (props) => { statusController={props.moduleInstance.getStatusController()} guiMessageBroker={props.workbench.getGuiMessageBroker()} > - + + + + +
diff --git a/frontend/src/framework/internal/components/LeftSettingsPanel/private-components/modulesList.tsx b/frontend/src/framework/internal/components/LeftSettingsPanel/private-components/modulesList.tsx index 0c346c105..ec6945814 100644 --- a/frontend/src/framework/internal/components/LeftSettingsPanel/private-components/modulesList.tsx +++ b/frontend/src/framework/internal/components/LeftSettingsPanel/private-components/modulesList.tsx @@ -15,6 +15,7 @@ import { pointSubtraction, pointerEventToPoint, } from "@lib/utils/geometry"; +import { resolveClassNames } from "@lib/utils/resolveClassNames"; import { Help, WebAsset } from "@mui/icons-material"; type ModulesListItemProps = { @@ -150,7 +151,10 @@ const ModulesListItem: React.FC = (props) => { {isDragged &&
}
diff --git a/frontend/src/framework/internal/components/NavBar/private-components/UserSessionState.tsx b/frontend/src/framework/internal/components/NavBar/private-components/UserSessionState.tsx index a0a71a129..35e611e56 100644 --- a/frontend/src/framework/internal/components/NavBar/private-components/UserSessionState.tsx +++ b/frontend/src/framework/internal/components/NavBar/private-components/UserSessionState.tsx @@ -1,23 +1,20 @@ import { Memory } from "@mui/icons-material"; -import { useQuery } from "@tanstack/react-query"; -import { apiService } from "@framework/ApiService"; +export const UserSessionState = ({ expanded }: { expanded: boolean }) => { + // const sessionState = useUserSessionState(); + // const memoryPercent = Math.round(sessionState.data?.memorySystem?.percent) || "-" + // const cpuPercent = Math.round(sessionState.data?.cpuPercent) || "-" + const memoryPercent = "-"; + const cpuPercent = "-"; -const useUserSessionState = () => useQuery({ - queryKey: ["default.userSessionContainer"], - queryFn: () => apiService.default.userSessionContainer(), - refetchInterval: 2000 -}); - -export const UserSessionState = ({expanded}: {expanded: boolean}) => { - - const sessionState = useUserSessionState(); - - const memoryPercent = Math.round(sessionState.data?.memorySystem?.percent) || "-" - const cpuPercent = Math.round(sessionState.data?.cpuPercent) || "-" - - return
-
{expanded ? "Memory:" : "M"} {memoryPercent} %
-
{expanded ? "CPU:" : "C"} {cpuPercent} %
-
-} + return ( +
+
+ {expanded ? "Memory:" : "M"} {memoryPercent} % +
+
+ {expanded ? "CPU:" : "C"} {cpuPercent} % +
+
+ ); +}; diff --git a/frontend/src/framework/internal/hooks/moduleHooks.ts b/frontend/src/framework/internal/hooks/moduleHooks.ts index 534258722..995e2f01b 100644 --- a/frontend/src/framework/internal/hooks/moduleHooks.ts +++ b/frontend/src/framework/internal/hooks/moduleHooks.ts @@ -3,7 +3,7 @@ import React from "react"; import { ImportState } from "@framework/Module"; import { ModuleInstance } from "@framework/ModuleInstance"; -export const useImportState = (moduleInstance: ModuleInstance): ImportState => { +export const useImportState = (moduleInstance: ModuleInstance): ImportState => { const [importState, setImportState] = React.useState(moduleInstance.getImportState()); React.useEffect(() => { diff --git a/frontend/src/framework/internal/hooks/workbenchHooks.ts b/frontend/src/framework/internal/hooks/workbenchHooks.ts index 3fbba8d1c..73cceaa16 100644 --- a/frontend/src/framework/internal/hooks/workbenchHooks.ts +++ b/frontend/src/framework/internal/hooks/workbenchHooks.ts @@ -3,8 +3,8 @@ import React from "react"; import { ModuleInstance } from "@framework/ModuleInstance"; import { Workbench, WorkbenchEvents } from "@framework/Workbench"; -export function useModuleInstances(workbench: Workbench): ModuleInstance[] { - const [moduleInstances, setModuleInstances] = React.useState[]>([]); +export function useModuleInstances(workbench: Workbench): ModuleInstance[] { + const [moduleInstances, setModuleInstances] = React.useState[]>([]); React.useEffect(() => { function handleModuleInstancesChange() { diff --git a/frontend/src/framework/internal/providers/QueryClientProvider.tsx b/frontend/src/framework/internal/providers/QueryClientProvider.tsx index bb421a0b2..fedebb4fd 100644 --- a/frontend/src/framework/internal/providers/QueryClientProvider.tsx +++ b/frontend/src/framework/internal/providers/QueryClientProvider.tsx @@ -3,8 +3,12 @@ import React from "react"; import { QueryCache, QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { Provider } from "jotai"; + import { AuthState, useAuthProvider } from "./AuthProvider"; +import { HydrateQueryClientAtom } from "../components/HydrateQueryClientAtom"; + type QueryError = { url: string; status: number; @@ -40,7 +44,9 @@ export const CustomQueryClientProvider: React.FC<{ children: React.ReactElement return ( - {props.children} + + {props.children} + ); diff --git a/frontend/src/framework/utils/atomUtils.ts b/frontend/src/framework/utils/atomUtils.ts new file mode 100644 index 000000000..bfbf784ae --- /dev/null +++ b/frontend/src/framework/utils/atomUtils.ts @@ -0,0 +1,59 @@ +import { DefaultError, QueryClient, QueryKey, QueryObserverOptions, QueryObserverResult } from "@tanstack/query-core"; + +import { Atom, Getter, atom } from "jotai"; +import { atomWithQuery } from "jotai-tanstack-query"; +import { atomWithReducer } from "jotai/utils"; + +export function atomWithCompare(initialValue: Value, areEqualFunc: (prev: Value, next: Value) => boolean) { + return atomWithReducer(initialValue, (prev: Value, next: Value) => { + if (areEqualFunc(prev, next)) { + return prev; + } + + return next; + }); +} + +type QueriesOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey +> = ((get: Getter) => Omit, "suspense">)[]; + +export function atomWithQueries< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TCombinedResult = QueryObserverResult[] +>( + getOptions: (get: Getter) => { + queries: readonly [...QueriesOptions]; + combine?: (result: QueryObserverResult[]) => TCombinedResult; + }, + getQueryClient?: (get: Getter) => QueryClient +): Atom { + const optionsAtom = atom(getOptions); + const atoms = atom((get) => { + const options = get(optionsAtom); + + const queries = options.queries.map((option) => { + return atomWithQuery(option, getQueryClient); + }); + + return queries; + }); + return atom((get) => { + const options = get(optionsAtom); + const results = get(atoms).map((atom) => get(atom)); + + if (options.combine) { + return options.combine(results) as TCombinedResult; + } + + return results as TCombinedResult; + }); +} diff --git a/frontend/src/lib/components/Input/input.tsx b/frontend/src/lib/components/Input/input.tsx index be81b0ccd..8391dab3b 100644 --- a/frontend/src/lib/components/Input/input.tsx +++ b/frontend/src/lib/components/Input/input.tsx @@ -38,21 +38,24 @@ export const Input = React.forwardRef((props: InputProps, ref: React.ForwardedRe }, []); const handleInputChange = React.useCallback( - (event: React.ChangeEvent) => { + function handleInputChange(event: React.ChangeEvent) { if (props.type === "number") { - let newValue = parseFloat(event.target.value || "0"); - if (props.min !== undefined) { - newValue = Math.max(props.min, newValue); - } + let newValue = 0; + if (!isNaN(parseFloat(event.target.value))) { + newValue = parseFloat(event.target.value || "0"); + if (props.min !== undefined) { + newValue = Math.max(props.min, newValue); + } - if (props.max !== undefined) { - newValue = Math.min(props.max, newValue); + if (props.max !== undefined) { + newValue = Math.min(props.max, newValue); + } + } else { + setValue(event.target.value); + return; } - if (newValue !== prevValue) { - setValue(newValue); - setPrevValue(newValue); - } + setValue(newValue); event.target.value = newValue.toString(); } @@ -60,7 +63,7 @@ export const Input = React.forwardRef((props: InputProps, ref: React.ForwardedRe onChange(event); } }, - [props.min, props.max, onChange, props.type, prevValue] + [props.min, props.max, onChange, props.type] ); return ( @@ -103,8 +106,9 @@ export const Input = React.forwardRef((props: InputProps, ref: React.ForwardedRe className: "grow", }, input: { - className: - "h-full focus:border-indigo-500 block w-full sm:text-sm border-gray-300 outline-none", + className: resolveClassNames( + "h-full focus:border-indigo-500 block w-full sm:text-sm border-gray-300 outline-none" + ), }, }} /> diff --git a/frontend/src/lib/components/PendingWrapper/pendingWrapper.tsx b/frontend/src/lib/components/PendingWrapper/pendingWrapper.tsx index 8eaeca0a1..5ef3cd648 100644 --- a/frontend/src/lib/components/PendingWrapper/pendingWrapper.tsx +++ b/frontend/src/lib/components/PendingWrapper/pendingWrapper.tsx @@ -6,6 +6,7 @@ import { resolveClassNames } from "@lib/utils/resolveClassNames"; // Base state wrapper props export type PendingWrapperProps = { isPending: boolean; + errorMessage?: string; children: React.ReactNode; }; @@ -14,6 +15,7 @@ export const PendingWrapper: React.FC = (props) => {
{props.isPending && ( @@ -21,6 +23,11 @@ export const PendingWrapper: React.FC = (props) => {
)} + {!props.isPending && props.errorMessage && ( +
+ {props.errorMessage} +
+ )} {props.children}
); diff --git a/frontend/src/lib/components/RadioGroup/index.ts b/frontend/src/lib/components/RadioGroup/index.ts index 1301fddcb..9540de2ef 100644 --- a/frontend/src/lib/components/RadioGroup/index.ts +++ b/frontend/src/lib/components/RadioGroup/index.ts @@ -1 +1,2 @@ export { RadioGroup, Radio } from "./radioGroup"; +export type { RadioGroupProps, RadioProps, RadioGroupOption } from "./radioGroup"; diff --git a/frontend/src/lib/components/RadioGroup/radioGroup.tsx b/frontend/src/lib/components/RadioGroup/radioGroup.tsx index 4592dd83e..a6b374632 100644 --- a/frontend/src/lib/components/RadioGroup/radioGroup.tsx +++ b/frontend/src/lib/components/RadioGroup/radioGroup.tsx @@ -6,33 +6,16 @@ import { v4 } from "uuid"; import { BaseComponent } from "../BaseComponent"; import { BaseComponentProps } from "../BaseComponent"; -import { OptionalValues, withDefaults } from "../_component-utils/components"; -export type RadioGroupProps = { - name?: string; - options: { - label: React.ReactNode; - value: T; - disabled?: boolean; - }[]; - value: T; - onChange?: (event: React.ChangeEvent, value: string | number) => void; - direction?: "horizontal" | "vertical"; -} & BaseComponentProps; - -const defaultProps: OptionalValues = { - direction: "vertical", -}; - -export type RadioProps = { +export type RadioProps = { name: string; label: React.ReactNode; - value: string | number; + value: T; checked: boolean; - onChange?: (event: React.ChangeEvent, value: string | number) => void; + onChange?: (event: React.ChangeEvent, value: T) => void; } & BaseComponentProps; -export const Radio: React.FC = (props) => { +export function Radio(props: RadioProps): JSX.Element { return ( ); +} + +export type RadioGroupOption = { + label: React.ReactNode; + value: T; + disabled?: boolean; }; -export const RadioGroup = withDefaults()(defaultProps, (props) => { +export type RadioGroupProps = { + name?: string; + options: RadioGroupOption[]; + value: T; + onChange?: (event: React.ChangeEvent, value: T) => void; + direction?: "horizontal" | "vertical"; +} & BaseComponentProps; + +export function RadioGroup(props: RadioGroupProps): JSX.Element { const name = React.useRef(props.name || v4()); return ( @@ -87,7 +84,7 @@ export const RadioGroup = withDefaults()(defaultProps, (props) {props.name}
{props.options.map((option) => ( @@ -105,6 +102,4 @@ export const RadioGroup = withDefaults()(defaultProps, (props)
); -}); - -RadioGroup.displayName = "RadioGroup"; +} diff --git a/frontend/src/lib/components/ResizablePanels/resizablePanels.tsx b/frontend/src/lib/components/ResizablePanels/resizablePanels.tsx index 4970d9cf6..5f7b4683f 100644 --- a/frontend/src/lib/components/ResizablePanels/resizablePanels.tsx +++ b/frontend/src/lib/components/ResizablePanels/resizablePanels.tsx @@ -1,15 +1,15 @@ import React from "react"; import { useElementSize } from "@lib/hooks/useElementSize"; +import { Point2D } from "@lib/utils/geometry"; import { resolveClassNames } from "@lib/utils/resolveClassNames"; import { isEqual } from "lodash"; -type ResizablePanelsProps = { +export type ResizablePanelsProps = { id: string; direction: "horizontal" | "vertical"; children: React.ReactNode[]; - initialSizesPercent?: number[]; minSizes?: number[]; sizesInPercent?: number[]; onSizesChange?: (sizesInPercent: number[]) => void; @@ -28,6 +28,36 @@ function storeConfigurationInLocalStorage(id: string, sizes: number[]) { localStorage.setItem(`resizable-panels-${id}`, JSON.stringify(sizes)); } +type DragBarProps = { + direction: "horizontal" | "vertical"; + index: number; + isDragging: boolean; +}; + +const DragBar: React.FC = (props) => { + return ( +
+
+
+ ); +}; + export const ResizablePanels: React.FC = (props) => { const { onSizesChange } = props; @@ -39,18 +69,25 @@ export const ResizablePanels: React.FC = (props) => { throw new Error("visible must have the same length as children"); } - const [isDragging, setIsDragging] = React.useState(); - const [sizes, setSizes] = React.useState( - props.sizesInPercent || - loadConfigurationFromLocalStorage(props.id) || - props.initialSizesPercent || - Array(props.children.length).fill(1.0 / props.children.length) - ); + function getInitialSizes() { + if (props.sizesInPercent) { + return props.sizesInPercent; + } + const loadedSizes = loadConfigurationFromLocalStorage(props.id); + if (loadedSizes) { + return loadedSizes; + } + return Array(props.children.length).fill(100.0 / props.children.length); + } + + const [isDragging, setIsDragging] = React.useState(false); + const [draggingIndex, setDraggingIndex] = React.useState(0); + const [sizes, setSizes] = React.useState(getInitialSizes); const [prevSizes, setPrevSizes] = React.useState(sizes); const [prevNumChildren, setPrevNumChildren] = React.useState(props.children.length); const resizablePanelsRef = React.useRef(null); - const resizablePanelRefs = React.useRef<(HTMLDivElement | null)[]>([]); + const individualPanelRefs = React.useRef<(HTMLDivElement | null)[]>([]); const { width: totalWidth, height: totalHeight } = useElementSize(resizablePanelsRef); @@ -60,7 +97,7 @@ export const ResizablePanels: React.FC = (props) => { } if (props.children.length !== prevNumChildren) { - resizablePanelRefs.current = resizablePanelRefs.current.slice(0, props.children.length); + individualPanelRefs.current = individualPanelRefs.current.slice(0, props.children.length); setPrevNumChildren(props.children.length); } @@ -72,6 +109,7 @@ export const ResizablePanels: React.FC = (props) => { function handlePointerDown(e: PointerEvent) { if (e.target instanceof HTMLElement && e.target.dataset.handle) { index = parseInt(e.target.dataset.handle, 10); + setDraggingIndex(index); dragging = true; setIsDragging(true); e.preventDefault(); @@ -90,38 +128,88 @@ export const ResizablePanels: React.FC = (props) => { e.stopPropagation(); let totalSize = 0; + const containerBoundingRect = resizablePanelsRef.current?.getBoundingClientRect(); if (props.direction === "horizontal") { - totalSize = resizablePanelsRef.current?.getBoundingClientRect().width || 0; + totalSize = containerBoundingRect?.width || 0; } else if (props.direction === "vertical") { - totalSize = resizablePanelsRef.current?.getBoundingClientRect().height || 0; + totalSize = containerBoundingRect?.height || 0; } - const firstElement = resizablePanelRefs.current[index]; - const secondElement = resizablePanelRefs.current[index + 1]; + const firstElementBoundingRect = individualPanelRefs.current[index]?.getBoundingClientRect(); + const secondElementBoundingRect = individualPanelRefs.current[index + 1]?.getBoundingClientRect(); + + if (containerBoundingRect && firstElementBoundingRect && secondElementBoundingRect) { + const cursorWithinBounds: Point2D = { + x: Math.max( + containerBoundingRect.left, + Math.min(e.clientX, containerBoundingRect.left + containerBoundingRect.width) + ), + y: Math.max( + containerBoundingRect.top, + Math.min(e.clientY, containerBoundingRect.top + containerBoundingRect.height) + ), + }; - if (firstElement && secondElement) { setSizes((prev) => { + const minSizesToggleVisibilityValue = + 100 * (props.direction === "horizontal" ? 50 / totalWidth : 50 / totalHeight); + const newSizes = prev.map((size, i) => { if (i === index) { - const newSize = - props.direction === "horizontal" - ? e.clientX - firstElement.getBoundingClientRect().left - : e.clientY - firstElement.getBoundingClientRect().top; - return (newSize / totalSize) * 100; + let newSize = cursorWithinBounds.x - firstElementBoundingRect.left; + if (props.direction === "vertical") { + newSize = cursorWithinBounds.y - firstElementBoundingRect.top; + } + return Math.max((newSize / totalSize) * 100, 0); } if (i === index + 1) { - const newSize = - props.direction === "horizontal" - ? secondElement.getBoundingClientRect().right - e.clientX - : secondElement.getBoundingClientRect().bottom - e.clientY; - return (newSize / totalSize) * 100; + let newSize = + secondElementBoundingRect.right - + Math.max(firstElementBoundingRect.left, cursorWithinBounds.x); + if (props.direction === "vertical") { + newSize = + secondElementBoundingRect.bottom - + Math.max(firstElementBoundingRect.top, cursorWithinBounds.y); + } + return Math.max((newSize / totalSize) * 100, 0); } return size; }) as number[]; - changedSizes = newSizes; + const adjustedSizes: number[] = [...newSizes]; + + for (let i = 0; i < newSizes.length; i++) { + const minSizeInPercent = ((props.minSizes?.at(i) || 0) / totalWidth) * 100; - return newSizes; + if (props.visible?.at(i) === false) { + adjustedSizes[i] = 0; + if (i < newSizes.length - 1) { + adjustedSizes[i + 1] = adjustedSizes[i + 1] + newSizes[i]; + } else { + adjustedSizes[i - 1] = adjustedSizes[i - 1] + newSizes[i]; + } + } + if (newSizes[i] < minSizesToggleVisibilityValue) { + adjustedSizes[i] = 0; + if (i < newSizes.length - 1) { + adjustedSizes[i + 1] = adjustedSizes[i + 1] + newSizes[i]; + } else { + adjustedSizes[i - 1] = adjustedSizes[i - 1] + newSizes[i]; + } + } else if (newSizes[i] < minSizeInPercent) { + adjustedSizes[i] = minSizeInPercent; + + if (i < newSizes.length - 1) { + adjustedSizes[i + 1] = newSizes[i + 1] + newSizes[i] - minSizeInPercent; + } else { + adjustedSizes[i - 1] = adjustedSizes[i - 1] + newSizes[i] - minSizeInPercent; + } + } + } + + changedSizes = adjustedSizes; + + return adjustedSizes; }); } } @@ -142,13 +230,13 @@ export const ResizablePanels: React.FC = (props) => { function addEventListeners() { document.addEventListener("pointermove", handlePointerMove); document.addEventListener("pointerup", handlePointerUp); - document.addEventListener("blur", handlePointerUp); + window.addEventListener("blur", handlePointerUp); } function removeEventListeners() { document.removeEventListener("pointermove", handlePointerMove); document.removeEventListener("pointerup", handlePointerUp); - document.removeEventListener("blur", handlePointerUp); + window.removeEventListener("blur", handlePointerUp); } document.addEventListener("pointerdown", handlePointerDown); @@ -158,33 +246,81 @@ export const ResizablePanels: React.FC = (props) => { removeEventListeners(); }; - }, [props.direction, props.id, onSizesChange]); + }, [props.direction, props.id, props.minSizes, onSizesChange, totalWidth, totalHeight, props.visible]); + + function maybeMakeDragBar(index: number) { + if (index < props.children.length - 1) { + return ( + + ); + } + return null; + } + + function makeStyle(index: number): React.CSSProperties { + const style: React.CSSProperties = {}; + const minSizesToggleVisibilityValue = + 100 * (props.direction === "horizontal" ? 50 / totalWidth : 50 / totalHeight); + let subtractHandleSize = 1; + if (index === 0 || index === props.children.length - 1) { + subtractHandleSize = 0.5; + } + if (props.direction === "horizontal") { + style.width = `calc(${sizes[index]}% - ${subtractHandleSize}px)`; + style.minWidth = undefined; + if (props.visible?.at(index) !== false && sizes[index] >= minSizesToggleVisibilityValue) { + const minSize = props.minSizes?.at(index); + if (minSize) { + style.minWidth = minSize - subtractHandleSize; + } + } - const minSizesToggleVisibilityValue = 100 * (props.direction === "horizontal" ? 50 / totalWidth : 50 / totalHeight); + if (sizes[index] < minSizesToggleVisibilityValue && props.minSizes?.at(index)) { + style.maxWidth = 0; + } else if (props.visible?.at(index) === false) { + style.maxWidth = 0; + } else { + style.maxWidth = undefined; + } + } else { + style.height = `calc(${sizes[index]}% - ${subtractHandleSize}px)`; + style.minHeight = undefined; + if (props.visible?.at(index) !== false && sizes[index] >= minSizesToggleVisibilityValue) { + const minSize = props.minSizes?.at(index); + if (minSize) { + style.minHeight = minSize - subtractHandleSize; + } + } + + if (sizes[index] < minSizesToggleVisibilityValue && props.minSizes?.at(index)) { + style.maxHeight = 0; + } else if (props.visible?.at(index) === false) { + style.maxHeight = 0; + } else { + style.maxHeight = undefined; + } + } + + return style; + } return (
{props.children.map((el: React.ReactNode, index: number) => ( @@ -193,67 +329,12 @@ export const ResizablePanels: React.FC = (props) => {
(resizablePanelRefs.current[index] = element)} - style={ - props.direction === "horizontal" - ? { - width: `calc(${sizes[index]}% - 3px)`, - minWidth: - props.visible?.at(index) === false - ? 0 - : sizes[index] > minSizesToggleVisibilityValue - ? props.minSizes?.at(index) || 0 - : 0, - maxWidth: - (sizes[index] < minSizesToggleVisibilityValue && props.minSizes?.at(index)) || - props.visible?.at(index) === false - ? 0 - : undefined, - } - : { - height: `calc(${sizes[index]}% - 3px)`, - minHeight: - props.visible?.at(index) === false - ? 0 - : sizes[index] > minSizesToggleVisibilityValue - ? props.minSizes?.at(index) || 0 - : 0, - maxHeight: - (sizes[index] < minSizesToggleVisibilityValue && props.minSizes?.at(index)) || - props.visible?.at(index) === false - ? 0 - : undefined, - } - } + ref={(element) => (individualPanelRefs.current[index] = element)} + style={makeStyle(index)} > {el}
- {index < props.children.length - 1 && ( -
-
-
- )} + {maybeMakeDragBar(index)} ))}
diff --git a/frontend/src/modules/DbgWorkbenchSpy/implementation.tsx b/frontend/src/modules/DbgWorkbenchSpy/implementation.tsx index 575705112..b7b0b5b7a 100644 --- a/frontend/src/modules/DbgWorkbenchSpy/implementation.tsx +++ b/frontend/src/modules/DbgWorkbenchSpy/implementation.tsx @@ -1,7 +1,7 @@ import React from "react"; import { EnsembleSet } from "@framework/EnsembleSet"; -import { ModuleFCProps } from "@framework/Module"; +import { ModuleSettingsProps, ModuleViewProps } from "@framework/Module"; import { AllTopicDefinitions, WorkbenchServices } from "@framework/WorkbenchServices"; import { useEnsembleSet } from "@framework/WorkbenchSession"; import { timestampUtcMsToIsoString } from "@framework/utils/timestampUtils"; @@ -12,8 +12,8 @@ export type SharedState = { }; //----------------------------------------------------------------------------------------------------------- -export function WorkbenchSpySettings(props: ModuleFCProps) { - const setRefreshCounter = props.moduleContext.useSetStoreValue("triggeredRefreshCounter"); +export function WorkbenchSpySettings(props: ModuleSettingsProps) { + const setRefreshCounter = props.settingsContext.useSetStoreValue("triggeredRefreshCounter"); return (
@@ -22,14 +22,14 @@ export function WorkbenchSpySettings(props: ModuleFCProps) { } //----------------------------------------------------------------------------------------------------------- -export function WorkbenchSpyView(props: ModuleFCProps) { +export function WorkbenchSpyView(props: ModuleViewProps) { const ensembleSet = useEnsembleSet(props.workbenchSession); const [hoverRealization, hoverRealization_TS] = useServiceValueWithTS( "global.hoverRealization", props.workbenchServices ); const [hoverTimestamp, hoverTimestamp_TS] = useServiceValueWithTS("global.hoverTimestamp", props.workbenchServices); - const triggeredRefreshCounter = props.moduleContext.useStoreValue("triggeredRefreshCounter"); + const triggeredRefreshCounter = props.viewContext.useStoreValue("triggeredRefreshCounter"); const componentRenderCount = React.useRef(0); React.useEffect(function incrementComponentRenderCount() { @@ -48,7 +48,10 @@ export function WorkbenchSpyView(props: ModuleFCProps) { {makeTableRow("hoverRealization", hoverRealization?.realization, hoverRealization_TS)} {makeTableRow("hoverTimestamp", hoverTimestamp?.timestampUtcMs, hoverTimestamp_TS)} - {makeTableRow("hoverTimestamp isoStr", hoverTimestamp ? timestampUtcMsToIsoString(hoverTimestamp.timestampUtcMs) : "UNDEF")} + {makeTableRow( + "hoverTimestamp isoStr", + hoverTimestamp ? timestampUtcMsToIsoString(hoverTimestamp.timestampUtcMs) : "UNDEF" + )}
diff --git a/frontend/src/modules/DistributionPlot/settings.tsx b/frontend/src/modules/DistributionPlot/settings.tsx index 14a220129..1109082d2 100644 --- a/frontend/src/modules/DistributionPlot/settings.tsx +++ b/frontend/src/modules/DistributionPlot/settings.tsx @@ -1,7 +1,7 @@ import React from "react"; import { useApplyInitialSettingsToState } from "@framework/InitialSettings"; -import { ModuleFCProps } from "@framework/Module"; +import { ModuleSettingsProps } from "@framework/Module"; import { CollapsibleGroup } from "@lib/components/CollapsibleGroup"; import { Dropdown } from "@lib/components/Dropdown"; import { Label } from "@lib/components/Label"; @@ -30,10 +30,10 @@ const plotTypes = [ ]; //----------------------------------------------------------------------------------------------------------- -export function Settings({ moduleContext, initialSettings }: ModuleFCProps) { - const [plotType, setPlotType] = moduleContext.useStoreState("plotType"); - const [numBins, setNumBins] = moduleContext.useStoreState("numBins"); - const [orientation, setOrientation] = moduleContext.useStoreState("orientation"); +export function Settings({ settingsContext, initialSettings }: ModuleSettingsProps) { + const [plotType, setPlotType] = settingsContext.useStoreState("plotType"); + const [numBins, setNumBins] = settingsContext.useStoreState("numBins"); + const [orientation, setOrientation] = settingsContext.useStoreState("orientation"); useApplyInitialSettingsToState(initialSettings, "plotType", "string", setPlotType); useApplyInitialSettingsToState(initialSettings, "numBins", "number", setNumBins); diff --git a/frontend/src/modules/DistributionPlot/view.tsx b/frontend/src/modules/DistributionPlot/view.tsx index 2b3e0b042..7bfc7b1c5 100644 --- a/frontend/src/modules/DistributionPlot/view.tsx +++ b/frontend/src/modules/DistributionPlot/view.tsx @@ -1,12 +1,13 @@ import React from "react"; import { ChannelReceiverChannelContent, KeyKind } from "@framework/DataChannelTypes"; -import { ModuleFCProps } from "@framework/Module"; +import { ModuleViewProps } from "@framework/Module"; import { useViewStatusWriter } from "@framework/StatusWriter"; import { Tag } from "@lib/components/Tag"; import { useElementSize } from "@lib/hooks/useElementSize"; import { ColorScaleGradientType } from "@lib/utils/ColorScale"; import { Size2D } from "@lib/utils/geometry"; +import { makeSubplots } from "@modules/_shared/Figure"; import { ContentInfo } from "@modules/_shared/components/ContentMessage"; import { ContentWarning } from "@modules/_shared/components/ContentMessage/contentMessage"; import { Warning } from "@mui/icons-material"; @@ -14,7 +15,6 @@ import { Warning } from "@mui/icons-material"; import { Layout, PlotData } from "plotly.js"; import { PlotType, State } from "./state"; -import { makeSubplots } from "./utils/Figure"; import { makeHistogramTrace } from "./utils/histogram"; import { makeHoverText, makeHoverTextWithColor, makeTitleFromChannelContent } from "./utils/stringUtils"; import { calcTextSize } from "./utils/textSize"; @@ -33,7 +33,7 @@ const MaxNumberPlotsExceededMessage: React.FC = () => { MaxNumberPlotsExceededMessage.displayName = "MaxNumberPlotsExceededMessage"; -export const View = ({ moduleContext, workbenchSettings }: ModuleFCProps) => { +export const View = ({ viewContext, workbenchSettings }: ModuleViewProps) => { const [isPending, startTransition] = React.useTransition(); const [content, setContent] = React.useState(null); const [revNumberX, setRevNumberX] = React.useState(0); @@ -44,11 +44,11 @@ export const View = ({ moduleContext, workbenchSettings }: ModuleFCProps) const [prevOrientation, setPrevOrientation] = React.useState<"v" | "h" | null>(null); const [prevSize, setPrevSize] = React.useState(null); - const plotType = moduleContext.useStoreValue("plotType"); - const numBins = moduleContext.useStoreValue("numBins"); - const orientation = moduleContext.useStoreValue("orientation"); + const plotType = viewContext.useStoreValue("plotType"); + const numBins = viewContext.useStoreValue("numBins"); + const orientation = viewContext.useStoreValue("orientation"); - const statusWriter = useViewStatusWriter(moduleContext); + const statusWriter = useViewStatusWriter(viewContext); const colorSet = workbenchSettings.useColorSet(); const seqColorScale = workbenchSettings.useContinuousColorScale({ @@ -58,15 +58,15 @@ export const View = ({ moduleContext, workbenchSettings }: ModuleFCProps) const wrapperDivRef = React.useRef(null); const wrapperDivSize = useElementSize(wrapperDivRef); - const receiverX = moduleContext.useChannelReceiver({ + const receiverX = viewContext.useChannelReceiver({ receiverIdString: "channelX", expectedKindsOfKeys: [KeyKind.REALIZATION], }); - const receiverY = moduleContext.useChannelReceiver({ + const receiverY = viewContext.useChannelReceiver({ receiverIdString: "channelY", expectedKindsOfKeys: [KeyKind.REALIZATION], }); - const receiverColorMapping = moduleContext.useChannelReceiver({ + const receiverColorMapping = viewContext.useChannelReceiver({ receiverIdString: "channelColorMapping", expectedKindsOfKeys: [KeyKind.REALIZATION], }); diff --git a/frontend/src/modules/Grid3D/queryHooks.ts b/frontend/src/modules/Grid3D/queryHooks.ts index 88e217ff5..3efe5c4c8 100644 --- a/frontend/src/modules/Grid3D/queryHooks.ts +++ b/frontend/src/modules/Grid3D/queryHooks.ts @@ -15,7 +15,7 @@ export function useGridSurface( return useQuery({ queryKey: ["getGridSurface", caseUuid, ensembleName, gridName, realization], queryFn: () => - apiService.grid.gridSurface(caseUuid ?? "", ensembleName ?? "", gridName ?? "", realization ?? ""), + apiService.grid3D.gridSurface(caseUuid ?? "", ensembleName ?? "", gridName ?? "", realization ?? ""), select: transformGridSurface, staleTime: STALE_TIME, gcTime: 0, @@ -29,12 +29,11 @@ export function useGridParameter( gridName: string | null, parameterName: string | null, realization: string | null, - useStatistics: boolean ): UseQueryResult { return useQuery({ queryKey: ["getGridParameter", caseUuid, ensembleName, gridName, parameterName, realization], queryFn: () => - apiService.grid.gridParameter( + apiService.grid3D.gridParameter( caseUuid ?? "", ensembleName ?? "", gridName ?? "", @@ -43,38 +42,14 @@ export function useGridParameter( ), staleTime: STALE_TIME, gcTime: 0, - enabled: caseUuid && ensembleName && gridName && parameterName && realization && !useStatistics ? true : false, - }); -} - -export function useStatisticalGridParameter( - caseUuid: string | null, - ensembleName: string | null, - gridName: string | null, - parameterName: string | null, - realizations: string[] | null, - useStatistics: boolean -): UseQueryResult { - return useQuery({ - queryKey: ["getStatisticalGridParameter", caseUuid, ensembleName, gridName, parameterName, realizations], - queryFn: () => - apiService.grid.statisticalGridParameter( - caseUuid ?? "", - ensembleName ?? "", - gridName ?? "", - parameterName ?? "", - realizations ?? [] - ), - staleTime: STALE_TIME, - gcTime: 0, - enabled: caseUuid && ensembleName && gridName && parameterName && realizations && useStatistics ? true : false, + enabled: caseUuid && ensembleName && gridName && parameterName && realization ? true : false, }); } export function useGridModelNames(caseUuid: string | null, ensembleName: string | null): UseQueryResult { return useQuery({ queryKey: ["getGridModelNames", caseUuid, ensembleName], - queryFn: () => apiService.grid.getGridModelNames(caseUuid ?? "", ensembleName ?? ""), + queryFn: () => apiService.grid3D.getGridModelNames(caseUuid ?? "", ensembleName ?? ""), staleTime: STALE_TIME, gcTime: CACHE_TIME, enabled: caseUuid && ensembleName ? true : false, @@ -88,7 +63,7 @@ export function useGridParameterNames( ): UseQueryResult { return useQuery({ queryKey: ["getParameterNames", caseUuid, ensembleName, gridName], - queryFn: () => apiService.grid.getParameterNames(caseUuid ?? "", ensembleName ?? "", gridName ?? ""), + queryFn: () => apiService.grid3D.getParameterNames(caseUuid ?? "", ensembleName ?? "", gridName ?? ""), staleTime: STALE_TIME, gcTime: CACHE_TIME, enabled: caseUuid && ensembleName && gridName ? true : false, diff --git a/frontend/src/modules/Grid3D/settings.tsx b/frontend/src/modules/Grid3D/settings.tsx index fab0d73e0..917ed4c64 100644 --- a/frontend/src/modules/Grid3D/settings.tsx +++ b/frontend/src/modules/Grid3D/settings.tsx @@ -1,7 +1,7 @@ import React from "react"; import { EnsembleIdent } from "@framework/EnsembleIdent"; -import { ModuleFCProps } from "@framework/Module"; +import { ModuleSettingsProps } from "@framework/Module"; import { SyncSettingKey, SyncSettingsHelper } from "@framework/SyncSettings"; import { useEnsembleSet } from "@framework/WorkbenchSession"; import { fixupEnsembleIdent, maybeAssignFirstSyncedEnsemble } from "@framework/utils/ensembleUiHelpers"; @@ -18,18 +18,18 @@ import { useGridModelNames, useGridParameterNames } from "./queryHooks"; import state from "./state"; //----------------------------------------------------------------------------------------------------------- -export function Settings({ moduleContext, workbenchServices, workbenchSession }: ModuleFCProps) { +export function Settings({ settingsContext, workbenchServices, workbenchSession }: ModuleSettingsProps) { // From Workbench const ensembleSet = useEnsembleSet(workbenchSession); const [selectedEnsembleIdent, setSelectedEnsembleIdent] = React.useState(null); // State - const [gridName, setGridName] = moduleContext.useStoreState("gridName"); - const [parameterName, setParameterName] = moduleContext.useStoreState("parameterName"); - const [realizations, setRealizations] = moduleContext.useStoreState("realizations"); - const [useStatistics, setUseStatistics] = moduleContext.useStoreState("useStatistics"); - const [selectedWellUuids, setSelectedWellUuids] = moduleContext.useStoreState("selectedWellUuids"); - const syncedSettingKeys = moduleContext.useSyncedSettingKeys(); + const [gridName, setGridName] = settingsContext.useStoreState("gridName"); + const [parameterName, setParameterName] = settingsContext.useStoreState("parameterName"); + const [realizations, setRealizations] = settingsContext.useStoreState("realizations"); + const [useStatistics, setUseStatistics] = settingsContext.useStoreState("useStatistics"); + const [selectedWellUuids, setSelectedWellUuids] = settingsContext.useStoreState("selectedWellUuids"); + const syncedSettingKeys = settingsContext.useSyncedSettingKeys(); const syncHelper = new SyncSettingsHelper(syncedSettingKeys, workbenchServices); const syncedValueEnsembles = syncHelper.useValue(SyncSettingKey.ENSEMBLE, "global.syncValue.ensembles"); diff --git a/frontend/src/modules/Grid3D/view.tsx b/frontend/src/modules/Grid3D/view.tsx index 06afb2eb4..12790d2d6 100644 --- a/frontend/src/modules/Grid3D/view.tsx +++ b/frontend/src/modules/Grid3D/view.tsx @@ -1,6 +1,6 @@ import { WellBoreTrajectory_api } from "@api"; import { ContinuousLegend } from "@emerson-eps/color-tables"; -import { ModuleFCProps } from "@framework/Module"; +import { ModuleViewProps } from "@framework/Module"; import { useFirstEnsembleInEnsembleSet } from "@framework/WorkbenchSession"; import { ColorScaleGradientType } from "@lib/utils/ColorScale"; import { @@ -13,12 +13,12 @@ import { useFieldWellsTrajectoriesQuery } from "@modules/_shared/WellBore/queryH import SubsurfaceViewer from "@webviz/subsurface-viewer"; import { ViewAnnotation } from "@webviz/subsurface-viewer/dist/components/ViewAnnotation"; -import { useGridParameter, useGridSurface, useStatisticalGridParameter } from "./queryHooks"; +import { useGridParameter, useGridSurface } from "./queryHooks"; import state from "./state"; //----------------------------------------------------------------------------------------------------------- -export function View({ moduleContext, workbenchSettings, workbenchSession }: ModuleFCProps) { - const myInstanceIdStr = moduleContext.getInstanceIdString(); +export function View({ viewContext, workbenchSettings, workbenchSession }: ModuleViewProps) { + const myInstanceIdStr = viewContext.getInstanceIdString(); const viewIds = { view: `${myInstanceIdStr}--view`, annotation: `${myInstanceIdStr}--annotation`, @@ -27,11 +27,10 @@ export function View({ moduleContext, workbenchSettings, workbenchSession }: Mod const firstEnsemble = useFirstEnsembleInEnsembleSet(workbenchSession); // State - const gridName = moduleContext.useStoreValue("gridName"); - const parameterName = moduleContext.useStoreValue("parameterName"); - const realizations = moduleContext.useStoreValue("realizations"); - const useStatistics = moduleContext.useStoreValue("useStatistics"); - const selectedWellUuids = moduleContext.useStoreValue("selectedWellUuids"); + const gridName = viewContext.useStoreValue("gridName"); + const parameterName = viewContext.useStoreValue("parameterName"); + const realizations = viewContext.useStoreValue("realizations"); + const selectedWellUuids = viewContext.useStoreValue("selectedWellUuids"); const colorScale = workbenchSettings.useContinuousColorScale({ gradientType: ColorScaleGradientType.Sequential, }); @@ -51,16 +50,7 @@ export function View({ moduleContext, workbenchSettings, workbenchSession }: Mod firstEnsembleName, gridName, parameterName, - realizations ? realizations[0] : "0", - useStatistics - ); - const statisticalGridParameterQuery = useStatisticalGridParameter( - firstCaseUuid, - firstEnsembleName, - gridName, - parameterName, - realizations, - useStatistics + realizations ? realizations[0] : "0" ); const wellTrajectoriesQuery = useFieldWellsTrajectoriesQuery(firstCaseUuid ?? undefined); const bounds = gridSurfaceQuery?.data @@ -84,10 +74,8 @@ export function View({ moduleContext, workbenchSettings, workbenchSession }: Mod ]; let propertiesArray: number[] = [0, 1]; - if (!useStatistics && gridParameterQuery?.data) { + if (gridParameterQuery?.data) { propertiesArray = Array.from(gridParameterQuery.data); - } else if (useStatistics && statisticalGridParameterQuery?.data) { - propertiesArray = Array.from(statisticalGridParameterQuery.data); } if (gridSurfaceQuery.data) { @@ -125,7 +113,6 @@ export function View({ moduleContext, workbenchSettings, workbenchSession }: Mod bounds={[bounds[0], bounds[1], bounds[3], bounds[4]]} colorTables={colorTables} layers={newLayers} - toolbar={{ visible: true }} views={{ layout: [1, 1], showLabel: false, @@ -151,7 +138,7 @@ export function View({ moduleContext, workbenchSettings, workbenchSession }: Mod -
{moduleContext.getInstanceIdString()}
+
{viewContext.getInstanceIdString()}
); } diff --git a/frontend/src/modules/Grid3DIntersection/plotlyGridIntersection.tsx b/frontend/src/modules/Grid3DIntersection/plotlyGridIntersection.tsx index 16dbf22ec..0116c57dc 100644 --- a/frontend/src/modules/Grid3DIntersection/plotlyGridIntersection.tsx +++ b/frontend/src/modules/Grid3DIntersection/plotlyGridIntersection.tsx @@ -1,12 +1,12 @@ import React from "react"; import Plot from "react-plotly.js"; -import { GridIntersection_api } from "@api"; +import { GridIntersectionVtk_api } from "@api"; import { Layout, PlotData } from "plotly.js"; export interface PlotlyGridIntersectionProps { - data: GridIntersection_api; + data: GridIntersectionVtk_api; width?: number | 100; height?: number | 100; } diff --git a/frontend/src/modules/Grid3DIntersection/queryHooks.ts b/frontend/src/modules/Grid3DIntersection/queryHooks.ts index 28b19c30a..237aec063 100644 --- a/frontend/src/modules/Grid3DIntersection/queryHooks.ts +++ b/frontend/src/modules/Grid3DIntersection/queryHooks.ts @@ -1,4 +1,4 @@ -import { GridIntersection_api } from "@api"; +import { GridIntersectionVtk_api } from "@api"; import { apiService } from "@framework/ApiService"; import { UseQueryResult, useQuery } from "@tanstack/react-query"; @@ -12,11 +12,11 @@ export function useGridIntersection( parameterName: string | null, realization: string | null, useStatistics: boolean -): UseQueryResult { +): UseQueryResult { return useQuery({ queryKey: ["gridParameterIntersection", caseUuid, ensembleName, gridName, parameterName, realization], queryFn: () => - apiService.grid.gridParameterIntersection( + apiService.grid3D.gridParameterIntersectionVtk( caseUuid ?? "", ensembleName ?? "", gridName ?? "", @@ -35,7 +35,7 @@ export function useStatisticalGridIntersection( parameterName: string | null, realizations: string[] | null, useStatistics: boolean -): UseQueryResult { +): UseQueryResult { return useQuery({ queryKey: [ "statisticalGridParameterIntersection", @@ -46,7 +46,7 @@ export function useStatisticalGridIntersection( realizations, ], queryFn: () => - apiService.grid.statisticalGridParameterIntersection( + apiService.grid3D.statisticalGridParameterIntersectionVtk( caseUuid ?? "", ensembleName ?? "", gridName ?? "", @@ -62,7 +62,7 @@ export function useStatisticalGridIntersection( export function useGridModelNames(caseUuid: string | null, ensembleName: string | null): UseQueryResult { return useQuery({ queryKey: ["getGridModelNames", caseUuid, ensembleName], - queryFn: () => apiService.grid.getGridModelNames(caseUuid ?? "", ensembleName ?? ""), + queryFn: () => apiService.grid3D.getGridModelNames(caseUuid ?? "", ensembleName ?? ""), staleTime: STALE_TIME, gcTime: CACHE_TIME, enabled: caseUuid && ensembleName ? true : false, @@ -76,7 +76,7 @@ export function useGridParameterNames( ): UseQueryResult { return useQuery({ queryKey: ["getParameterNames", caseUuid, ensembleName, gridName], - queryFn: () => apiService.grid.getParameterNames(caseUuid ?? "", ensembleName ?? "", gridName ?? ""), + queryFn: () => apiService.grid3D.getParameterNames(caseUuid ?? "", ensembleName ?? "", gridName ?? ""), staleTime: STALE_TIME, gcTime: CACHE_TIME, enabled: caseUuid && ensembleName && gridName ? true : false, diff --git a/frontend/src/modules/Grid3DIntersection/settings.tsx b/frontend/src/modules/Grid3DIntersection/settings.tsx index 13f8deba0..e9551aac8 100644 --- a/frontend/src/modules/Grid3DIntersection/settings.tsx +++ b/frontend/src/modules/Grid3DIntersection/settings.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { ModuleFCProps } from "@framework/Module"; +import { ModuleSettingsProps } from "@framework/Module"; import { useFirstEnsembleInEnsembleSet } from "@framework/WorkbenchSession"; import { Checkbox } from "@lib/components/Checkbox"; import { CircularProgress } from "@lib/components/CircularProgress"; @@ -12,15 +12,15 @@ import { useGridModelNames, useGridParameterNames } from "./queryHooks"; import state from "./state"; //----------------------------------------------------------------------------------------------------------- -export function Settings({ moduleContext, workbenchSession }: ModuleFCProps) { +export function Settings({ settingsContext, workbenchSession }: ModuleSettingsProps) { // From Workbench const firstEnsemble = useFirstEnsembleInEnsembleSet(workbenchSession); // State - const [gridName, setGridName] = moduleContext.useStoreState("gridName"); - const [parameterName, setParameterName] = moduleContext.useStoreState("parameterName"); - const [realizations, setRealizations] = moduleContext.useStoreState("realizations"); - const [useStatistics, setUseStatistics] = moduleContext.useStoreState("useStatistics"); + const [gridName, setGridName] = settingsContext.useStoreState("gridName"); + const [parameterName, setParameterName] = settingsContext.useStoreState("parameterName"); + const [realizations, setRealizations] = settingsContext.useStoreState("realizations"); + const [useStatistics, setUseStatistics] = settingsContext.useStoreState("useStatistics"); // Queries const firstCaseUuid = firstEnsemble?.getCaseUuid() ?? null; diff --git a/frontend/src/modules/Grid3DIntersection/view.tsx b/frontend/src/modules/Grid3DIntersection/view.tsx index 768015ebc..9432f175c 100644 --- a/frontend/src/modules/Grid3DIntersection/view.tsx +++ b/frontend/src/modules/Grid3DIntersection/view.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { ModuleFCProps } from "@framework/Module"; +import { ModuleViewProps } from "@framework/Module"; import { useFirstEnsembleInEnsembleSet } from "@framework/WorkbenchSession"; import { useElementSize } from "@lib/hooks/useElementSize"; @@ -9,7 +9,7 @@ import { useGridIntersection, useStatisticalGridIntersection } from "./queryHook import state from "./state"; //----------------------------------------------------------------------------------------------------------- -export function View({ moduleContext, workbenchSession }: ModuleFCProps) { +export function View({ viewContext, workbenchSession }: ModuleViewProps) { // Viewport size const wrapperDivRef = React.useRef(null); const wrapperDivSize = useElementSize(wrapperDivRef); @@ -18,10 +18,10 @@ export function View({ moduleContext, workbenchSession }: ModuleFCProps) const firstEnsemble = useFirstEnsembleInEnsembleSet(workbenchSession); // State - const gridName = moduleContext.useStoreValue("gridName"); - const parameterName = moduleContext.useStoreValue("parameterName"); - const realizations = moduleContext.useStoreValue("realizations"); - const useStatistics = moduleContext.useStoreValue("useStatistics"); + const gridName = viewContext.useStoreValue("gridName"); + const parameterName = viewContext.useStoreValue("parameterName"); + const realizations = viewContext.useStoreValue("realizations"); + const useStatistics = viewContext.useStoreValue("useStatistics"); // Queries const firstCaseUuid = firstEnsemble?.getCaseUuid() ?? null; @@ -62,7 +62,7 @@ export function View({ moduleContext, workbenchSession }: ModuleFCProps) width={wrapperDivSize.width} height={wrapperDivSize.height} /> -
{moduleContext.getInstanceIdString()}
+
{viewContext.getInstanceIdString()}
); } diff --git a/frontend/src/modules/Grid3DVTK/loadModule.tsx b/frontend/src/modules/Grid3DVTK/loadModule.tsx new file mode 100644 index 000000000..d1e25c1b3 --- /dev/null +++ b/frontend/src/modules/Grid3DVTK/loadModule.tsx @@ -0,0 +1,18 @@ +import { ModuleRegistry } from "@framework/ModuleRegistry"; + +import { Settings } from "./settings"; +import state from "./state"; +import { View } from "./view"; + +const defaultState: state = { + gridName: "Simgrid", + parameterName: "PORO", + selectedWellUuids: [], + realizations: ["1"], + useStatistics: false, +}; + +const module = ModuleRegistry.initModule("Grid3DVTK", defaultState, {}); + +module.viewFC = View; +module.settingsFC = Settings; diff --git a/frontend/src/modules/Grid3DVTK/preview.tsx b/frontend/src/modules/Grid3DVTK/preview.tsx new file mode 100644 index 000000000..2a9746ab7 --- /dev/null +++ b/frontend/src/modules/Grid3DVTK/preview.tsx @@ -0,0 +1,8 @@ +import { DrawPreviewFunc } from "@framework/Preview"; +import previewImg from "./preview.webp"; + +export const preview: DrawPreviewFunc = function (width: number, height: number) { + return ( + + ); +}; diff --git a/frontend/src/modules/Grid3DVTK/preview.webp b/frontend/src/modules/Grid3DVTK/preview.webp new file mode 100644 index 000000000..d180acb38 Binary files /dev/null and b/frontend/src/modules/Grid3DVTK/preview.webp differ diff --git a/frontend/src/modules/Grid3DVTK/queryDataTransforms.ts b/frontend/src/modules/Grid3DVTK/queryDataTransforms.ts new file mode 100644 index 000000000..29cba253c --- /dev/null +++ b/frontend/src/modules/Grid3DVTK/queryDataTransforms.ts @@ -0,0 +1,26 @@ +import { GridSurfaceVtk_api } from "@api"; + +import { b64DecodeFloatArrayToFloat32, b64DecodeUintArrayToUint32} from "@modules_shared/base64"; + +// Data structure for the transformed GridSurface data +// Removes the base64 encoded data and replaces them with typed arrays +export type GridSurfaceVtk_trans = Omit & { + pointsFloat32Arr: Float32Array; + polysUint32Arr: Uint32Array; +}; + +export function transformGridSurface(apiData: GridSurfaceVtk_api): GridSurfaceVtk_trans { + const startTS = performance.now(); + + const { points_b64arr, polys_b64arr, ...untransformedData } = apiData; + const pointsFloat32Arr = b64DecodeFloatArrayToFloat32(points_b64arr); + const polysUint32Arr = b64DecodeUintArrayToUint32(polys_b64arr); + + console.debug(`transformGridSurface() took: ${(performance.now() - startTS).toFixed(1)}ms`); + + return { + ...untransformedData, + pointsFloat32Arr: pointsFloat32Arr, + polysUint32Arr: polysUint32Arr, + }; +} diff --git a/frontend/src/modules/Grid3DVTK/queryHooks.ts b/frontend/src/modules/Grid3DVTK/queryHooks.ts new file mode 100644 index 000000000..63e085730 --- /dev/null +++ b/frontend/src/modules/Grid3DVTK/queryHooks.ts @@ -0,0 +1,96 @@ +import { apiService } from "@framework/ApiService"; +import { UseQueryResult, useQuery } from "@tanstack/react-query"; + +import { GridSurfaceVtk_trans, transformGridSurface } from "./queryDataTransforms"; + +const STALE_TIME = 60 * 1000; +const CACHE_TIME = 60 * 1000; + +export function useGridSurfaceVtk( + caseUuid: string | null, + ensembleName: string | null, + gridName: string | null, + realization: string | null +): UseQueryResult { + return useQuery({ + queryKey: ["getGridSurfaceVtk", caseUuid, ensembleName, gridName, realization], + queryFn: () => + apiService.grid3D.gridSurfaceVtk(caseUuid ?? "", ensembleName ?? "", gridName ?? "", realization ?? ""), + select: transformGridSurface, + staleTime: STALE_TIME, + gcTime: 0, + enabled: caseUuid && ensembleName && gridName && realization ? true : false, + }); +} + +export function useGridParameterVtk( + caseUuid: string | null, + ensembleName: string | null, + gridName: string | null, + parameterName: string | null, + realization: string | null, + useStatistics: boolean +): UseQueryResult { + return useQuery({ + queryKey: ["gridParameterVtk", caseUuid, ensembleName, gridName, parameterName, realization], + queryFn: () => + apiService.grid3D.gridParameterVtk( + caseUuid ?? "", + ensembleName ?? "", + gridName ?? "", + parameterName ?? "", + realization ?? "" + ), + staleTime: STALE_TIME, + gcTime: 0, + enabled: caseUuid && ensembleName && gridName && parameterName && realization && !useStatistics ? true : false, + }); +} + +export function useStatisticalGridParameterVtk( + caseUuid: string | null, + ensembleName: string | null, + gridName: string | null, + parameterName: string | null, + realizations: string[] | null, + useStatistics: boolean +): UseQueryResult { + return useQuery({ + queryKey: ["getStatisticalGridParameterVtk", caseUuid, ensembleName, gridName, parameterName, realizations], + queryFn: () => + apiService.grid3D.statisticalGridParameterVtk( + caseUuid ?? "", + ensembleName ?? "", + gridName ?? "", + parameterName ?? "", + realizations ?? [] + ), + staleTime: STALE_TIME, + gcTime: 0, + enabled: caseUuid && ensembleName && gridName && parameterName && realizations && useStatistics ? true : false, + }); +} + +export function useGridModelNames(caseUuid: string | null, ensembleName: string | null): UseQueryResult { + return useQuery({ + queryKey: ["getGridModelNames", caseUuid, ensembleName], + queryFn: () => apiService.grid3D.getGridModelNames(caseUuid ?? "", ensembleName ?? ""), + staleTime: STALE_TIME, + gcTime: CACHE_TIME, + enabled: caseUuid && ensembleName ? true : false, + }); +} + +export function useGridParameterNames( + caseUuid: string | null, + ensembleName: string | null, + gridName: string | null +): UseQueryResult { + return useQuery({ + queryKey: ["getParameterNames", caseUuid, ensembleName, gridName], + queryFn: () => apiService.grid3D.getParameterNames(caseUuid ?? "", ensembleName ?? "", gridName ?? ""), + staleTime: STALE_TIME, + gcTime: CACHE_TIME, + enabled: caseUuid && ensembleName && gridName ? true : false, + }); +} diff --git a/frontend/src/modules/Grid3DVTK/registerModule.tsx b/frontend/src/modules/Grid3DVTK/registerModule.tsx new file mode 100644 index 000000000..5f280275d --- /dev/null +++ b/frontend/src/modules/Grid3DVTK/registerModule.tsx @@ -0,0 +1,6 @@ +import { ModuleRegistry } from "@framework/ModuleRegistry"; + +import { preview } from "./preview"; +import state from "./state"; + +ModuleRegistry.registerModule({ moduleName: "Grid3DVTK", defaultTitle: "3D grid (VTK)", preview }); diff --git a/frontend/src/modules/Grid3DVTK/settings.tsx b/frontend/src/modules/Grid3DVTK/settings.tsx new file mode 100644 index 000000000..917ed4c64 --- /dev/null +++ b/frontend/src/modules/Grid3DVTK/settings.tsx @@ -0,0 +1,178 @@ +import React from "react"; + +import { EnsembleIdent } from "@framework/EnsembleIdent"; +import { ModuleSettingsProps } from "@framework/Module"; +import { SyncSettingKey, SyncSettingsHelper } from "@framework/SyncSettings"; +import { useEnsembleSet } from "@framework/WorkbenchSession"; +import { fixupEnsembleIdent, maybeAssignFirstSyncedEnsemble } from "@framework/utils/ensembleUiHelpers"; +import { Button } from "@lib/components/Button"; +import { Checkbox } from "@lib/components/Checkbox"; +import { CircularProgress } from "@lib/components/CircularProgress"; +import { CollapsibleGroup } from "@lib/components/CollapsibleGroup"; +import { Label } from "@lib/components/Label"; +import { QueryStateWrapper } from "@lib/components/QueryStateWrapper"; +import { Select, SelectOption } from "@lib/components/Select"; +import { useWellHeadersQuery } from "@modules/_shared/WellBore/queryHooks"; + +import { useGridModelNames, useGridParameterNames } from "./queryHooks"; +import state from "./state"; + +//----------------------------------------------------------------------------------------------------------- +export function Settings({ settingsContext, workbenchServices, workbenchSession }: ModuleSettingsProps) { + // From Workbench + + const ensembleSet = useEnsembleSet(workbenchSession); + const [selectedEnsembleIdent, setSelectedEnsembleIdent] = React.useState(null); + // State + const [gridName, setGridName] = settingsContext.useStoreState("gridName"); + const [parameterName, setParameterName] = settingsContext.useStoreState("parameterName"); + const [realizations, setRealizations] = settingsContext.useStoreState("realizations"); + const [useStatistics, setUseStatistics] = settingsContext.useStoreState("useStatistics"); + const [selectedWellUuids, setSelectedWellUuids] = settingsContext.useStoreState("selectedWellUuids"); + const syncedSettingKeys = settingsContext.useSyncedSettingKeys(); + const syncHelper = new SyncSettingsHelper(syncedSettingKeys, workbenchServices); + const syncedValueEnsembles = syncHelper.useValue(SyncSettingKey.ENSEMBLE, "global.syncValue.ensembles"); + + const candidateEnsembleIdent = maybeAssignFirstSyncedEnsemble(selectedEnsembleIdent, syncedValueEnsembles); + const computedEnsembleIdent = fixupEnsembleIdent(candidateEnsembleIdent, ensembleSet); + if (computedEnsembleIdent && !computedEnsembleIdent.equals(selectedEnsembleIdent)) { + setSelectedEnsembleIdent(computedEnsembleIdent); + } + + // Queries + const firstCaseUuid = computedEnsembleIdent?.getCaseUuid() ?? null; + const firstEnsembleName = computedEnsembleIdent?.getEnsembleName() ?? null; + const gridNamesQuery = useGridModelNames(firstCaseUuid, firstEnsembleName); + const parameterNamesQuery = useGridParameterNames(firstCaseUuid, firstEnsembleName, gridName); + const wellHeadersQuery = useWellHeadersQuery(computedEnsembleIdent?.getCaseUuid()); + // Handle Linked query + React.useEffect(() => { + if (parameterNamesQuery.data) { + if (gridName && parameterNamesQuery.data.find((name) => name === parameterName)) { + // New grid has same parameter + } else { + // New grid has different parameter. Set to first + setParameterName(parameterNamesQuery.data[0]); + } + } + }, [parameterNamesQuery.data, parameterName, gridName, setParameterName]); + + const parameterNames = parameterNamesQuery.data ? parameterNamesQuery.data : []; + const allRealizations = computedEnsembleIdent + ? ensembleSet + .findEnsemble(computedEnsembleIdent) + ?.getRealizations() + .map((real) => JSON.stringify(real)) ?? [] + : []; + + let wellHeaderOptions: SelectOption[] = []; + + if (wellHeadersQuery.data) { + wellHeaderOptions = wellHeadersQuery.data.map((header) => ({ + label: header.unique_wellbore_identifier, + value: header.wellbore_uuid, + })); + } + + function handleWellsChange(selectedWellUuids: string[], allWellUuidsOptions: SelectOption[]) { + const newSelectedWellUuids = selectedWellUuids.filter((wellUuid) => + allWellUuidsOptions.some((wellHeader) => wellHeader.value === wellUuid) + ); + setSelectedWellUuids(newSelectedWellUuids); + } + function showAllWells(allWellUuidsOptions: SelectOption[]) { + const newSelectedWellUuids = allWellUuidsOptions.map((wellHeader) => wellHeader.value); + + setSelectedWellUuids(newSelectedWellUuids); + } + function hideAllWells() { + setSelectedWellUuids([]); + } + const gridNames: string[] = gridNamesQuery.data ? gridNamesQuery.data : []; + return ( +
+ + + + + + +
+ ); +} + +const stringToOptions = (strings: string[]): SelectOption[] => { + return strings.map((string) => ({ label: string, value: string })); +}; diff --git a/frontend/src/modules/Grid3DVTK/state.ts b/frontend/src/modules/Grid3DVTK/state.ts new file mode 100644 index 000000000..1a1e52b95 --- /dev/null +++ b/frontend/src/modules/Grid3DVTK/state.ts @@ -0,0 +1,7 @@ +export default interface state { + gridName: string | null; + parameterName: string | null; + selectedWellUuids: string[]; + realizations: string[] | null; + useStatistics: boolean; +} diff --git a/frontend/src/modules/Grid3DVTK/view.tsx b/frontend/src/modules/Grid3DVTK/view.tsx new file mode 100644 index 000000000..eb301422d --- /dev/null +++ b/frontend/src/modules/Grid3DVTK/view.tsx @@ -0,0 +1,156 @@ +import { WellBoreTrajectory_api } from "@api"; +import { ContinuousLegend } from "@emerson-eps/color-tables"; +import { ModuleViewProps } from "@framework/Module"; +import { useFirstEnsembleInEnsembleSet } from "@framework/WorkbenchSession"; +import { ColorScaleGradientType } from "@lib/utils/ColorScale"; +import { + createContinuousColorScaleForMap, + createNorthArrowLayer, + createWellBoreHeaderLayer, + createWellboreTrajectoryLayer, +} from "@modules/SubsurfaceMap/_utils"; +import { useFieldWellsTrajectoriesQuery } from "@modules/_shared/WellBore/queryHooks"; +import SubsurfaceViewer from "@webviz/subsurface-viewer"; +import { ViewAnnotation } from "@webviz/subsurface-viewer/dist/components/ViewAnnotation"; + +import { useGridParameterVtk, useGridSurfaceVtk, useStatisticalGridParameterVtk } from "./queryHooks"; +import state from "./state"; + +//----------------------------------------------------------------------------------------------------------- +export function View({ viewContext, workbenchSettings, workbenchSession }: ModuleViewProps) { + const myInstanceIdStr = viewContext.getInstanceIdString(); + const viewIds = { + view: `${myInstanceIdStr}--view`, + annotation: `${myInstanceIdStr}--annotation`, + }; + // From Workbench + const firstEnsemble = useFirstEnsembleInEnsembleSet(workbenchSession); + + // State + const gridName = viewContext.useStoreValue("gridName"); + const parameterName = viewContext.useStoreValue("parameterName"); + const realizations = viewContext.useStoreValue("realizations"); + const useStatistics = viewContext.useStoreValue("useStatistics"); + const selectedWellUuids = viewContext.useStoreValue("selectedWellUuids"); + const colorScale = workbenchSettings.useContinuousColorScale({ + gradientType: ColorScaleGradientType.Sequential, + }); + const colorTables = createContinuousColorScaleForMap(colorScale); + + //Queries + const firstCaseUuid = firstEnsemble?.getCaseUuid() ?? null; + const firstEnsembleName = firstEnsemble?.getEnsembleName() ?? null; + const gridSurfaceQuery = useGridSurfaceVtk( + firstCaseUuid, + firstEnsembleName, + gridName, + realizations ? realizations[0] : "0" + ); + const gridParameterQuery = useGridParameterVtk( + firstCaseUuid, + firstEnsembleName, + gridName, + parameterName, + realizations ? realizations[0] : "0", + useStatistics + ); + const statisticalGridParameterQuery = useStatisticalGridParameterVtk( + firstCaseUuid, + firstEnsembleName, + gridName, + parameterName, + realizations, + useStatistics + ); + const wellTrajectoriesQuery = useFieldWellsTrajectoriesQuery(firstCaseUuid ?? undefined); + const bounds = gridSurfaceQuery?.data + ? [ + gridSurfaceQuery.data.xmin, + gridSurfaceQuery.data.ymin, + gridSurfaceQuery.data.zmin, + gridSurfaceQuery.data.xmax, + gridSurfaceQuery.data.ymax, + gridSurfaceQuery.data.zmax, + ] + : [0, 0, 0, 100, 100, 100]; + + const newLayers: Record[] = [ + createNorthArrowLayer(), + { + "@@type": "AxesLayer", + id: "axes-layer", + bounds: bounds, + }, + ]; + + let propertiesArray: number[] = [0, 1]; + if (!useStatistics && gridParameterQuery?.data) { + propertiesArray = Array.from(gridParameterQuery.data); + } else if (useStatistics && statisticalGridParameterQuery?.data) { + propertiesArray = Array.from(statisticalGridParameterQuery.data); + } + + if (gridSurfaceQuery.data) { + const points: Float32Array = gridSurfaceQuery.data.pointsFloat32Arr; + const polys: Uint32Array = gridSurfaceQuery.data.polysUint32Arr; + newLayers.push({ + "@@type": "Grid3DLayer", + id: "grid3d-layer", + material: false, + pointsData: Array.from(points), + polysData: Array.from(polys), + propertiesData: propertiesArray, + colorMapName: "Continuous", + ZIncreasingDownwards: false, + }); + } + + if (wellTrajectoriesQuery.data) { + const wellTrajectories: WellBoreTrajectory_api[] = wellTrajectoriesQuery.data.filter((well) => + selectedWellUuids.includes(well.wellbore_uuid) + ); + const wellTrajectoryLayer: Record = createWellboreTrajectoryLayer(wellTrajectories); + const wellBoreHeaderLayer: Record = createWellBoreHeaderLayer(wellTrajectories); + newLayers.push(wellTrajectoryLayer); + newLayers.push(wellBoreHeaderLayer); + } + const propertyRange = [ + propertiesArray.reduce((a, b) => Math.min(a, b)), + propertiesArray.reduce((a, b) => Math.max(a, b)), + ]; + return ( +
+ layer.id) as string[], + }, + ], + }} + > + {" "} + + + + + +
{viewContext.getInstanceIdString()}
+
+ ); +} diff --git a/frontend/src/modules/InplaceVolumetrics/settings.tsx b/frontend/src/modules/InplaceVolumetrics/settings.tsx index 15ce7eb8a..a115b5ed6 100644 --- a/frontend/src/modules/InplaceVolumetrics/settings.tsx +++ b/frontend/src/modules/InplaceVolumetrics/settings.tsx @@ -2,9 +2,9 @@ import React from "react"; import { InplaceVolumetricsCategoricalMetaData_api, InplaceVolumetricsTableMetaData_api } from "@api"; import { EnsembleIdent } from "@framework/EnsembleIdent"; -import { ModuleFCProps } from "@framework/Module"; +import { ModuleSettingsProps } from "@framework/Module"; import { useEnsembleSet } from "@framework/WorkbenchSession"; -import { SingleEnsembleSelect } from "@framework/components/SingleEnsembleSelect"; +import { EnsembleDropdown } from "@framework/components/EnsembleDropdown"; import { fixupEnsembleIdent } from "@framework/utils/ensembleUiHelpers"; import { CircularProgress } from "@lib/components/CircularProgress"; import { Dropdown } from "@lib/components/Dropdown"; @@ -96,12 +96,12 @@ function getTableResponseOptions( return responsesToSelectOptions(responses); } -export function Settings({ moduleContext, workbenchSession }: ModuleFCProps) { +export function Settings({ settingsContext, workbenchSession }: ModuleSettingsProps) { const ensembleSet = useEnsembleSet(workbenchSession); - const [ensembleIdent, setEnsembleIdent] = moduleContext.useStoreState("ensembleIdent"); - const [tableName, setTableName] = moduleContext.useStoreState("tableName"); - const [categoricalFilter, setCategoricalFilter] = moduleContext.useStoreState("categoricalFilter"); - const [responseName, setResponseName] = moduleContext.useStoreState("responseName"); + const [ensembleIdent, setEnsembleIdent] = settingsContext.useStoreState("ensembleIdent"); + const [tableName, setTableName] = settingsContext.useStoreState("tableName"); + const [categoricalFilter, setCategoricalFilter] = settingsContext.useStoreState("categoricalFilter"); + const [responseName, setResponseName] = settingsContext.useStoreState("responseName"); const tableDescriptionsQuery = useTableDescriptionsQuery(ensembleIdent, true); @@ -166,7 +166,7 @@ export function Settings({ moduleContext, workbenchSession }: ModuleFCProps