Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
132 commits
Select commit Hold shift + click to select a range
8fbcdd8
wip
rubenthoms May 4, 2025
597946c
wip
rubenthoms May 5, 2025
74808b4
wip
rubenthoms May 5, 2025
dbe7aec
First working version
rubenthoms May 5, 2025
a9d3080
wip
rubenthoms May 5, 2025
36ac786
wip
rubenthoms May 6, 2025
d77ed6e
wip
rubenthoms May 6, 2025
aae82f6
Merge remote-tracking branch 'equinor/main' into spike-database
rubenthoms Jun 12, 2025
7757f15
fix: poetry lock file
rubenthoms Jun 12, 2025
b35d944
First implementations
rubenthoms Jun 13, 2025
34232b4
wip
rubenthoms Jun 16, 2025
62c8657
wip
rubenthoms Jun 16, 2025
a15a576
wip
rubenthoms Jun 16, 2025
0f5c275
wip
rubenthoms Jun 16, 2025
244df84
wip
rubenthoms Jun 17, 2025
4cb1a9d
wip
rubenthoms Jun 17, 2025
ebba9fa
wip
rubenthoms Jun 17, 2025
26d029b
wip
rubenthoms Jun 18, 2025
29e7c7a
Merge remote-tracking branch 'equinor/main' into persistence/manager
rubenthoms Jun 18, 2025
c1ed6c5
wip
rubenthoms Jun 18, 2025
21c78f0
wip
rubenthoms Jun 19, 2025
ed6f380
wip
rubenthoms Jun 20, 2025
a02ad4b
wip
rubenthoms Jun 20, 2025
f0475ed
wip
rubenthoms Jun 23, 2025
222c3d2
wip
rubenthoms Jun 23, 2025
12e7bd1
wip
rubenthoms Jun 23, 2025
526f6bb
wip
rubenthoms Jun 23, 2025
0864ea8
wip
rubenthoms Jun 24, 2025
d630597
wip
rubenthoms Jun 24, 2025
222bf20
wip
rubenthoms Jun 24, 2025
1440f37
wip
rubenthoms Jun 25, 2025
caaab05
wip
rubenthoms Jun 26, 2025
2e4c8b3
wip
rubenthoms Jun 27, 2025
76a50d0
wip
rubenthoms Jun 30, 2025
5add20b
wip
rubenthoms Jun 30, 2025
fe17cc8
wip
rubenthoms Jun 30, 2025
7379beb
wip
rubenthoms Jul 1, 2025
c3ffed6
Removed unused file
rubenthoms Jul 1, 2025
8b5f3fa
wip
rubenthoms Jul 1, 2025
0e52ad6
wip
rubenthoms Jul 2, 2025
93dcd43
wip
rubenthoms Jul 2, 2025
76b27d5
wip
rubenthoms Jul 2, 2025
06669ae
wip
rubenthoms Jul 2, 2025
4efca0f
wip
rubenthoms Jul 3, 2025
56f5f4a
Adjusted nginx to let SPA handle routing
rubenthoms Jul 3, 2025
5323d31
fix
rubenthoms Jul 3, 2025
9cee7e6
Previews for snapshots
rubenthoms Jul 3, 2025
4b7d81e
URL fix
rubenthoms Jul 3, 2025
681a02f
Adjust nginx config
rubenthoms Jul 3, 2025
43eeb75
Adjustments
rubenthoms Jul 3, 2025
e94e9db
Revert
rubenthoms Jul 3, 2025
164fefb
Multiple fixes in backend classes
rubenthoms Jul 4, 2025
3ca3d3b
Minor improvements
rubenthoms Jul 7, 2025
a85fbde
fix: formatting and linting
rubenthoms Jul 7, 2025
f6ab0ec
Removed unnecessary async
rubenthoms Jul 7, 2025
c34540f
fix: remove async for create functions
rubenthoms Jul 7, 2025
26129a3
fix: additional fixes related to partition keys and pydantic models
rubenthoms Jul 7, 2025
d20ca6c
WIP -- Show recently visited snapshots
Anders2303 Jul 4, 2025
a8fa191
Fixed snapshot navigation more smooth
Anders2303 Jul 7, 2025
f7dcae0
wip
rubenthoms Jul 7, 2025
df2690f
first spike
rubenthoms Jul 7, 2025
121ff73
Wip: Renamed "!model" to "models"
Anders2303 Jul 8, 2025
16ce933
Resolved feedback
Anders2303 Jul 8, 2025
c86a13b
Change access log model to include snapshot owner id and refactored u…
Anders2303 Jul 8, 2025
297c7ba
Merge pull request #2 from Anders2303/persistence/manager--recent-sna…
rubenthoms Jul 8, 2025
5d2187d
wip
rubenthoms Jul 8, 2025
2dcf431
wip
rubenthoms Jul 9, 2025
a0b6dc1
Merge branch 'persistence/updates-after-recent-snapshots-PR' into per…
rubenthoms Jul 10, 2025
073074e
Adjustments after merging
rubenthoms Jul 10, 2025
a105e6c
Merge branch 'persistence/manager' into persistence/module-states
rubenthoms Jul 10, 2025
9158daa
wip
rubenthoms Jul 10, 2025
905e3e9
wip
rubenthoms Jul 10, 2025
527c974
Hide module close button for snapshots
rubenthoms Jul 24, 2025
d869dd4
wip
rubenthoms Jul 24, 2025
058ce08
Revert "wip"
rubenthoms Jul 28, 2025
c6048ee
Merge branch 'persistence/manager' into persistence/module-states
rubenthoms Jul 28, 2025
afb0eff
Improved `persistableFixableAtom`
rubenthoms Jul 28, 2025
b8f6750
wip
rubenthoms Jul 28, 2025
bdf3421
wip
rubenthoms Jul 28, 2025
751ffb3
wip
rubenthoms Jul 28, 2025
9d21808
Improved cosmos-db startup logic
rubenthoms Jul 29, 2025
2b8502e
Started adding support for templates
rubenthoms Jul 29, 2025
3ff1abd
wip
rubenthoms Jul 29, 2025
3ad2f9d
Add: deserialization of data channels
rubenthoms Jul 30, 2025
939cb91
wip
rubenthoms Jul 30, 2025
552860a
Implementation of template initial state system
rubenthoms Jul 31, 2025
42fc35c
Merge remote-tracking branch 'equinor/main' into persistence/manager
rubenthoms Aug 4, 2025
5db70e2
Intermediate merge result without ensemble polling
rubenthoms Aug 4, 2025
d3de259
wip
rubenthoms Aug 4, 2025
b7d09c1
wip
rubenthoms Aug 5, 2025
619eb1e
wip
rubenthoms Aug 5, 2025
ce296a0
Improved ensemble timestamps logic
rubenthoms Aug 6, 2025
aae40c3
Added comment
rubenthoms Aug 6, 2025
5eb3d2b
Improved logic
rubenthoms Aug 6, 2025
daaed23
Multiple improvements
rubenthoms Aug 7, 2025
cf3a32a
Merge branch 'persistence/manager' into persistence/module-states
rubenthoms Aug 8, 2025
815dc5d
wip
rubenthoms Aug 8, 2025
405a848
wip refactoring layout
rubenthoms Aug 11, 2025
f98aa23
wip
rubenthoms Aug 12, 2025
47fcfc8
wip
rubenthoms Aug 12, 2025
fc670a2
wip
rubenthoms Aug 13, 2025
fcbb421
Fixed issues in layout system
rubenthoms Aug 14, 2025
f350b03
wip
rubenthoms Aug 18, 2025
42250a3
wip
rubenthoms Aug 18, 2025
2795eaf
wip
rubenthoms Aug 19, 2025
5cac3ff
wip
rubenthoms Aug 20, 2025
60aacd7
wip
rubenthoms Aug 20, 2025
63bdade
Immediate fixes to layout system
rubenthoms Aug 21, 2025
59e324f
fix: persistence loop intermediate fix
rubenthoms Aug 21, 2025
f0c6240
Added precompute function to persistableFixableAtom
rubenthoms Aug 22, 2025
93f47b5
wip
rubenthoms Aug 22, 2025
44341a1
wip
HansKallekleiv Aug 25, 2025
b5994af
wip
HansKallekleiv Aug 25, 2025
696c3ce
wip
HansKallekleiv Aug 25, 2025
ae2a8b5
wip
HansKallekleiv Aug 25, 2025
c023f9c
wip
HansKallekleiv Aug 25, 2025
5da7aab
wip
rubenthoms Aug 25, 2025
b8e4e5c
Setting annotations wrapper, utils, and schemas
rubenthoms Aug 26, 2025
5f5447f
wip
HansKallekleiv Aug 26, 2025
04da371
Merge remote-tracking branch 'r/persistence/module-states' into persi…
HansKallekleiv Aug 26, 2025
78cd555
wip
HansKallekleiv Aug 26, 2025
d19531c
Merge remote-tracking branch 'r/module-states/inplace-volumes-table' …
HansKallekleiv Aug 26, 2025
68388fa
fix: persistable atoms
rubenthoms Aug 26, 2025
abf43d1
wip
HansKallekleiv Aug 26, 2025
bdb429d
fix templates
HansKallekleiv Aug 26, 2025
2b95241
wip
HansKallekleiv Aug 26, 2025
06ee3a9
Merge remote-tracking branch 'equinor/main' into persistence/manager
rubenthoms Aug 27, 2025
8b35b14
Merge branch 'persistence/manager' into persistence/module-states
rubenthoms Aug 27, 2025
654291f
Merge branch 'persistence/module-states' into module-states/inplace-v…
rubenthoms Aug 27, 2025
8ab6116
Merge remote-tracking branch 'r/module-states/inplace-volumes-table' …
HansKallekleiv Sep 4, 2025
ac0ee88
fix initial ensemble select
HansKallekleiv Sep 8, 2025
d7390ee
Merge remote-tracking branch 'upstream/main' into module-states/inpla…
HansKallekleiv Sep 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "generate-api",
"path": "frontend",
"problemMatcher": [],
"label": "Generate frontend code from OpenAPI"
}
]
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "generate:api",
"path": "frontend",
"problemMatcher": [],
"label": "Generate frontend code from OpenAPI"
}
]
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ e.g. typically when a new dependency is added, the relevant component needs to b
All the content in `/frontend/src/api/autogen` is auto-generated using the defined endpoints
in the Python backend. In order to update the auto-generated code you can either

1. Run `npm run generate-api --prefix ./frontend`.
1. Run `npm run generate:api --prefix ./frontend`.
2. Use the VSCode tasks shortcut:
a) `Ctrl + P` to open the command palette.
b) Type `> Tasks` and enter to filter to commands only.
Expand Down
647 changes: 646 additions & 1 deletion backend_py/primary/poetry.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,10 @@ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -

path_is_protected = True

if path_to_check in ["/login", "/auth-callback"] + self._unprotected_paths:
path_is_protected = False
for unprotected in ["/login", "/auth-callback"] + self._unprotected_paths:
if path_to_check.startswith(unprotected):
path_is_protected = False
break

if path_is_protected:

Expand Down
8 changes: 8 additions & 0 deletions backend_py/primary/primary/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,11 @@
DEFAULT_STALE_WHILE_REVALIDATE = 3600 * 24 # 24 hour
REDIS_USER_SESSION_URL = "redis://redis-user-session:6379"
REDIS_CACHE_URL = "redis://redis-cache:6379"

COSMOS_DB_PROD_CONNECTION_STRING = os.environ.get("WEBVIZ_DB_CONNECTION_STRING", None)
# pylint: disable=line-too-long
COSMOS_DB_EMULATOR_URI = "https://host.docker.internal:8081/"
COSMOS_DB_EMULATOR_KEY = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==;"

PERSISTENCE_DB_NAME = "persistence"
DASHBOARDS_CONTAINER_NAME = "dashboards"
12 changes: 11 additions & 1 deletion backend_py/primary/primary/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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.services.database_access.setup_local_database import maybe_setup_local_database

from primary.middleware.add_browser_cache import AddBrowserCacheMiddleware
from primary.routers.dev.router import router as dev_router
Expand All @@ -32,6 +33,9 @@
from primary.routers.vfp.router import router as vfp_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.routers.persistence.sessions.router import router as sessions_router
from primary.routers.persistence.snapshots.router import router as snapshots_router
from primary.routers.persistence.snapshot_preview.router import router as snapshot_preview_router
from primary.services.utils.httpx_async_client_wrapper import HTTPX_ASYNC_CLIENT_WRAPPER
from primary.utils.azure_monitor_setup import setup_azure_monitor_telemetry
from primary.utils.exception_handlers import configure_service_level_exception_handlers
Expand All @@ -58,6 +62,9 @@

LOGGER = logging.getLogger(__name__)

# Setup Cosmos DB emulator database if running locally
maybe_setup_local_database()


def custom_generate_unique_id(route: APIRoute) -> str:
return f"{route.name}"
Expand Down Expand Up @@ -106,6 +113,9 @@ async def shutdown_event_async() -> None:
app.include_router(rft_router, prefix="/rft", tags=["rft"])
app.include_router(vfp_router, prefix="/vfp", tags=["vfp"])
app.include_router(dev_router, prefix="/dev", tags=["dev"], include_in_schema=False)
app.include_router(sessions_router, prefix="/sessions", tags=["sessions"])
app.include_router(snapshots_router, prefix="/snapshots", tags=["snapshots"])
app.include_router(snapshot_preview_router, prefix="/snapshot-preview", tags=["snapshot_preview"])

auth_helper = AuthHelper()
app.include_router(auth_helper.router)
Expand All @@ -120,7 +130,7 @@ async def shutdown_event_async() -> None:

# Add out custom middleware to enforce that user is logged in
# Also redirects to /login endpoint for some select paths
unprotected_paths = ["/logout", "/logged_in_user", "/alive", "/openapi.json"]
unprotected_paths = ["/logout", "/logged_in_user", "/alive", "/openapi.json", "/snapshot-preview"]
paths_redirected_to_login = ["/", "/alive_protected"]

app.add_middleware(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from primary.services.database_access.session_access.model import SessionDocument
from primary.services.database_access.session_access.types import SessionMetadata, SessionMetadataWithId
from . import schemas


def to_api_session_metadata_summary(metadata: SessionMetadataWithId) -> schemas.SessionMetadataWithId:
return schemas.SessionMetadataWithId(
id=metadata.id,
title=metadata.title,
description=metadata.description,
createdAt=metadata.created_at.isoformat(),
updatedAt=metadata.updated_at.isoformat(),
version=metadata.version,
)


def to_api_session_metadata(metadata: SessionMetadata) -> schemas.SessionMetadata:
return schemas.SessionMetadata(
title=metadata.title,
description=metadata.description,
createdAt=metadata.created_at.isoformat(),
updatedAt=metadata.updated_at.isoformat(),
version=metadata.version,
hash=metadata.hash,
)


def to_api_session_record(document: SessionDocument) -> schemas.SessionDocument:
return schemas.SessionDocument(
id=document.id,
ownerId=document.owner_id,
metadata=to_api_session_metadata(document.metadata),
content=document.content,
)
88 changes: 88 additions & 0 deletions backend_py/primary/primary/routers/persistence/sessions/router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import logging
from typing import List, Optional

from fastapi import APIRouter, Depends, HTTPException, Query

from primary.middleware.add_browser_cache import no_cache
from primary.services.database_access.session_access.session_access import SessionAccess
from primary.auth.auth_helper import AuthHelper, AuthenticatedUser
from primary.services.database_access.session_access.types import (
NewSession,
SessionUpdate,
SortBy,
SortDirection,
)
from primary.routers.persistence.sessions.converters import (
to_api_session_metadata_summary,
to_api_session_metadata,
to_api_session_record,
)

from . import schemas

LOGGER = logging.getLogger(__name__)
router = APIRouter()


@router.get("/sessions", response_model=List[schemas.SessionMetadataWithId])
@no_cache
async def get_sessions_metadata(
user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
sort_by: Optional[SortBy] = Query(None, description="Sort the result by"),
sort_direction: Optional[SortDirection] = Query(SortDirection.ASC, description="Sort direction: 'asc' or 'desc'"),
limit: Optional[int] = Query(10, ge=1, le=100, description="Limit the number of results"),
):
access = SessionAccess.create(user.get_user_id())
async with access:
items = await access.get_filtered_sessions_metadata_for_user_async(
sort_by=sort_by, sort_direction=sort_direction, limit=limit
)
return [to_api_session_metadata_summary(item) for item in items]


@router.get("/sessions/{session_id}", response_model=schemas.SessionDocument)
@no_cache
async def get_session(session_id: str, user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user)):
access = SessionAccess.create(user.get_user_id())
async with access:
session = await access.get_session_by_id_async(session_id)
if not session:
raise HTTPException(status_code=404, detail="Session not found")
return to_api_session_record(session)


@router.get("/sessions/metadata/{session_id}", response_model=schemas.SessionMetadata)
@no_cache
async def get_session_metadata(session_id: str, user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user)):
access = SessionAccess.create(user.get_user_id())
async with access:
metadata = await access.get_session_metadata_async(session_id)
if not metadata:
raise HTTPException(status_code=404, detail="Session metadata not found")
return to_api_session_metadata(metadata)


@router.post("/sessions", response_model=str)
async def create_session(session: NewSession, user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user)):
access = SessionAccess.create(user.get_user_id())
async with access:
session_id = await access.insert_session_async(session)
return session_id


@router.put("/sessions/{session_id}")
async def update_session(
session_id: str,
session_update: SessionUpdate,
user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
):
access = SessionAccess.create(user.get_user_id())
async with access:
await access.update_session_async(session_id, session_update)


@router.delete("/sessions/{session_id}")
async def delete_session(session_id: str, user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user)):
access = SessionAccess.create(user.get_user_id())
async with access:
await access.delete_session_async(session_id)
27 changes: 27 additions & 0 deletions backend_py/primary/primary/routers/persistence/sessions/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import Optional
from pydantic import BaseModel


class SessionMetadataWithId(BaseModel):
id: str
title: str
description: Optional[str]
createdAt: str
updatedAt: str
version: int


class SessionMetadata(BaseModel):
title: str
description: Optional[str]
createdAt: str
updatedAt: str
version: int
hash: str


class SessionDocument(BaseModel):
id: str
ownerId: str
metadata: SessionMetadata
content: str
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import html
from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import HTMLResponse

from primary.services.database_access.snapshot_access.snapshot_access import SnapshotAccess

router = APIRouter()


@router.get("/{snapshot_id}", response_class=HTMLResponse)
async def snapshot_preview(snapshot_id: str, request: Request):
access = await SnapshotAccess.create("")
async with access:
metadata = await access.get_snapshot_metadata(snapshot_id)
if not metadata:
raise HTTPException(status_code=404, detail="Snapshot metadata not found")

base_url = get_external_base_url(request)
snapshot_url = f"{base_url}/snapshot/{snapshot_id}"

title = html.escape(metadata.title)
description = html.escape(metadata.description or "No description available")

return f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta property="og:title" content="{title}" />
<meta property="og:description" content="{description}" />
<meta property="og:url" content="{snapshot_url}" />
<meta property="og:type" content="website" />
</head>
<body>
Redirecting… <script>window.location = "{snapshot_url}";</script>
</body>
</html>
"""


def get_external_base_url(request: Request) -> str:
forwarded_proto = request.headers.get("x-forwarded-proto", "http")
forwarded_host = request.headers.get("x-forwarded-host", request.headers.get("host", "localhost"))
return f"{forwarded_proto}://{forwarded_host}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from primary.services.database_access.snapshot_access.models import SnapshotAccessLog
from primary.services.database_access.snapshot_access.types import Snapshot, SnapshotMetadata, SnapshotMetadataWithId

from . import schemas


def to_api_snapshot_metadata_summary(metadata: SnapshotMetadataWithId) -> schemas.SnapshotMetadataWithId:
return schemas.SnapshotMetadataWithId(
id=metadata.id,
ownerId=metadata.owner_id,
title=metadata.title,
description=metadata.description,
createdAt=metadata.created_at.isoformat(),
updatedAt=metadata.updated_at.isoformat(),
hash=metadata.hash,
)


def to_api_snapshot_metadata(metadata: SnapshotMetadata) -> schemas.SnapshotMetadata:
return schemas.SnapshotMetadata(
ownerId=metadata.owner_id,
title=metadata.title,
description=metadata.description,
createdAt=metadata.created_at.isoformat(),
updatedAt=metadata.updated_at.isoformat(),
hash=metadata.hash,
)


def to_api_snapshot(snapshot: Snapshot) -> schemas.Snapshot:
return schemas.Snapshot(
id=snapshot.id,
metadata=to_api_snapshot_metadata(snapshot.metadata),
content=snapshot.content,
)


def to_api_snapshot_access_log(access_log: SnapshotAccessLog, metadata: SnapshotMetadata) -> schemas.SnapshotAccessLog:
return schemas.SnapshotAccessLog(
visitorId=access_log.visitor_id,
snapshotId=access_log.snapshot_id,
visits=access_log.visits,
firstVisitedAt=access_log.first_visited_at.isoformat() if access_log.first_visited_at else None,
lastVisitedAt=access_log.last_visited_at.isoformat() if access_log.last_visited_at else None,
snapshotMetadata=to_api_snapshot_metadata(metadata),
)
Loading
Loading