Skip to content

Commit

Permalink
Implemented new start screen logic, new components, adjustments
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenthoms committed Oct 31, 2023
1 parent f797c2c commit 389bdf1
Show file tree
Hide file tree
Showing 24 changed files with 1,943 additions and 111 deletions.
1,437 changes: 1,437 additions & 0 deletions backend/requirements.txt

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion backend/src/backend/primary/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,5 @@ async def exception_handler(request: Request, exc: Exception):
)
return JSONResponse(
status_code=500,
content={"message": exc},
content={"message": str(exc)},
)
13 changes: 10 additions & 3 deletions backend/src/backend/primary/routers/inplace_volumetrics/router.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import List, Optional, Sequence
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, Query, Response

from src.services.sumo_access.inplace_volumetrics_access import (
InplaceVolumetricsAccess,
Expand All @@ -12,25 +12,32 @@

from src.backend.auth.auth_helper import AuthHelper

from src.backend.primary.exceptions import ResultNotMatchingExpectations

from src.services.utils.perf_timer import PerfTimer
from src.backend.auth.auth_helper import AuthHelper
from src.backend.utils.perf_metrics import PerfMetrics

router = APIRouter()


@router.get("/table_names_and_metadata/", tags=["inplace_volumetrics"])
async def get_table_names_and_metadata(
# fmt:off
response: Response,
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
case_uuid: str = Query(description="Sumo case uuid"),
ensemble_name: str = Query(description="Ensemble name"),
# fmt:on
) -> List[InplaceVolumetricsTableMetaData]:
"""Get all volumetric tables for a given ensemble."""
perf_metrics = PerfMetrics(response)
access = await InplaceVolumetricsAccess.from_case_uuid(
authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name
)
perf_metrics.record_lap("create-accessor")
table_names_and_metadata = await access.get_table_names_and_metadata()
perf_metrics.record_lap("get-table-names-and-metadata")
# Only in order to make developing easier
response.headers["cache-control"] = "max-age=3600"
return table_names_and_metadata


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from enum import Enum
from io import BytesIO
from typing import List, Optional, Sequence, Union
import time

from concurrent.futures import ThreadPoolExecutor
import pandas as pd
Expand Down Expand Up @@ -78,7 +79,6 @@ async def get_table_names_and_metadata(
vol_table_collections: TableCollection = self._case.tables.filter(
aggregation="collection", tagname="vol", iteration=self._iteration_name
)

vol_tables_metadata = []
vol_table_names = await vol_table_collections.names_async
for vol_table_name in vol_table_names:
Expand Down
124 changes: 109 additions & 15 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,139 @@
import React from "react";

import { DrawerContent, GuiState } from "@framework/GuiMessageBroker";
import WebvizLogo from "@assets/webviz.svg";
import { DrawerContent, GuiState, useGuiState } from "@framework/GuiMessageBroker";

Check warning on line 4 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / frontend

'useGuiState' is defined but never used
import { LayoutElement, Workbench } from "@framework/Workbench";
import { NavBar } from "@framework/internal/components/NavBar";
import { SettingsContentPanels } from "@framework/internal/components/SettingsContentPanels";
import { AuthState, useAuthProvider } from "@framework/internal/providers/AuthProvider";
import { Button } from "@lib/components/Button";
import { WebvizSpinner } from "@lib/components/WebvizSpinner";
import { resolveClassNames } from "@lib/utils/resolveClassNames";
import { useQueryClient } from "@tanstack/react-query";

import "./modules/registerAllModules";
import "./templates/registerAllTemplates";

enum InitAppState {
CheckingIfUserIsSignedIn = "checking-if-user-is-signed-in",
LoadingEnsembles = "loading-ensembles",
InitCompleted = "init-completed",
}

const layout: LayoutElement[] = [];

function App() {
const [isMounted, setIsMounted] = React.useState<boolean>(false);
const [initAppState, setInitAppState] = React.useState<InitAppState>(InitAppState.CheckingIfUserIsSignedIn);

const workbench = React.useRef<Workbench>(new Workbench());
const queryClient = useQueryClient();
const { authState } = useAuthProvider();

React.useEffect(function handleMount() {
function initApp() {
if (!workbench.current.loadLayoutFromLocalStorage()) {
workbench.current.makeLayout(layout);
}

if (workbench.current.getLayout().length === 0) {
workbench.current.getGuiMessageBroker().setState(GuiState.DrawerContent, DrawerContent.ModulesList);
} else {
workbench.current.getGuiMessageBroker().setState(GuiState.DrawerContent, DrawerContent.ModuleSettings);
}
setInitAppState(InitAppState.InitCompleted);
}

const storedEnsembleIdents = workbench.current.maybeLoadEnsembleSetFromLocalStorage();
if (storedEnsembleIdents) {
workbench.current.loadAndSetupEnsembleSetInSession(queryClient, storedEnsembleIdents);
}
React.useEffect(
function handleMountWhenSignedIn() {
if (authState !== AuthState.LoggedIn || isMounted) {
return;
}

setIsMounted(true);

const storedEnsembleIdents = workbench.current.maybeLoadEnsembleSetFromLocalStorage();
if (storedEnsembleIdents) {
setInitAppState(InitAppState.LoadingEnsembles);
workbench.current.loadAndSetupEnsembleSetInSession(queryClient, storedEnsembleIdents).finally(() => {
initApp();
});
}

return function handleUnmount() {
workbench.current.clearLayout();
workbench.current.resetModuleInstanceNumbers();
};
},
[authState, isMounted]
);

function signIn() {
window.location.href = `/api/login?redirect_url_after_login=${btoa("/")}`;
}

function makeStateMessages() {
return (
<div className="relative mt-4 w-full">
<div
className={resolveClassNames(
"absolute top-0 transition-opacity duration-500 ease-in-out w-full text-center",
{
"opacity-0": initAppState !== InitAppState.CheckingIfUserIsSignedIn,
"opacity-100": initAppState === InitAppState.CheckingIfUserIsSignedIn,
}
)}
>
Checking if user is signed in...
</div>
<div
className={resolveClassNames(
"absolute top-0 transition-opacity duration-500 ease-in-out w-full text-center",
{
"opacity-0": initAppState !== InitAppState.LoadingEnsembles,
"opacity-100": initAppState === InitAppState.LoadingEnsembles,
}
)}
>
Restoring working session...
</div>
</div>
);
}

return function handleUnmount() {
workbench.current.clearLayout();
workbench.current.resetModuleInstanceNumbers();
};
}, []);
const isInitialisingApp = initAppState !== InitAppState.InitCompleted;

return (
<div className="h-screen flex flex-row">
<NavBar workbench={workbench.current} />
<SettingsContentPanels workbench={workbench.current} />
</div>
<>
{authState === AuthState.NotLoggedIn && (
<div className="w-screen h-screen flex flex-col items-center justify-center gap-8">
<img src={WebvizLogo} alt="Webviz logo" className="w-32 h-32" />
<p className="text-lg">Please sign in to continue.</p>
<Button onClick={signIn}>Sign in</Button>
</div>
)}
{isInitialisingApp && (
<div
className={resolveClassNames(
"absolute inset-0 w-screen h-screen flex flex-col items-center justify-center gap-8",
{
hidden: !isInitialisingApp,
}
)}
>
<WebvizSpinner size={100} />
{makeStateMessages()}
</div>
)}
<div
className={resolveClassNames("h-screen flex flex-row transition-opacity ease-in-out duration-1000", {
"opacity-0": isInitialisingApp,
"opacity-100": !isInitialisingApp,
})}
>
<NavBar workbench={workbench.current} />
<SettingsContentPanels workbench={workbench.current} />
</div>
</>
);
}

Expand Down
1 change: 1 addition & 0 deletions frontend/src/framework/components/LayoutBox/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { LayoutBox, LayoutBoxComponents, makeLayoutBoxes } from "./layoutBox";
2 changes: 2 additions & 0 deletions frontend/src/framework/components/RealizationPicker/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { RealizationPicker } from "./realizationPicker";
export type { RealizationPickerProps } from "./realizationPicker";
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import React from "react";

import { EnsembleIdent } from "@framework/EnsembleIdent";
import { BaseComponent, BaseComponentProps } from "@lib/components/BaseComponent";
import { resolveClassNames } from "@lib/utils/resolveClassNames";
import { getTextWidthWithFont } from "@lib/utils/textSize";
import { Close } from "@mui/icons-material";

type RealizationRange = {
start: number;
end?: number;
};

type RealizationRangeTag = {
range: RealizationRange;
};

const realizationRangeRegex = /^\d+(\-\d+)?$/;

Check failure on line 18 in frontend/src/framework/components/RealizationPicker/realizationPicker.tsx

View workflow job for this annotation

GitHub Actions / frontend

Unnecessary escape character: \-

const RealizationRangeTag: React.FC<RealizationRangeTag> = (props) => {
const [valid, setValid] = React.useState<boolean>(true);
const [hasFocus, setHasFocus] = React.useState<boolean>(false);

const ref = React.useRef<HTMLInputElement>(null);

function makeValue() {
if (props.range.end) {
return `${props.range.start}-${props.range.end}`;
} else {
return `${props.range.start}`;
}
}

function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
const value = event.target.value;
if (realizationRangeRegex.test(value)) {
setValid(true);
} else {
setValid(false);
}
}

function handleFocus(e: React.FocusEvent<HTMLInputElement>) {
e.stopPropagation();
setHasFocus(true);
}

return (
<li
className={resolveClassNames("flex rounded px-2 py-1 mr-1 text-sm", {
"bg-blue-200": !hasFocus,
"bg-red-300": !valid,
})}
>
<input
ref={ref}
className="bg-transparent outline-none"
style={{ width: getTextWidthWithFont(makeValue(), "0.75rem") + 10 }}
type="text"
defaultValue={makeValue()}
onChange={handleChange}
onFocus={handleFocus}
onBlur={() => setHasFocus(false)}
/>
<div
className={resolveClassNames("text-slate-800 hover:text-slate-600 text-sm cursor-pointer", {
invisible: hasFocus,
})}
>
<Close fontSize="inherit" />
</div>
</li>
);
};

export type RealizationPickerProps = {
ensembleIdents: EnsembleIdent[];
onChange?: (selectedRealizations: number[]) => void;
} & BaseComponentProps;

export const RealizationPicker: React.FC<RealizationPickerProps> = (props) => {
const [selectedRealizations, setSelectedRealizations] = React.useState<RealizationRange[]>([]);
const [numSelectedRealizations, setNumSelectedRealizations] = React.useState<number>(0);

const debounceTimeout = React.useRef<ReturnType<typeof setTimeout> | null>(null);
const inputRef = React.useRef<HTMLInputElement>(null);

function handleTotalChange(event: React.ChangeEvent<HTMLInputElement>) {

Check warning on line 88 in frontend/src/framework/components/RealizationPicker/realizationPicker.tsx

View workflow job for this annotation

GitHub Actions / frontend

'handleTotalChange' is defined but never used
const selectedRealizations = event.target.value
.split(",")
.map((valueOrRange) => {
const range = valueOrRange.split("-");
if (range.length === 1) {
return parseInt(range[0]);
} else if (range.length === 2) {
return Array.from(
{ length: parseInt(range[1]) - parseInt(range[0]) + 1 },
(_, i) => i + parseInt(range[0])
);
} else {
return [];
}
})
.flat()
.filter((realization) => realization > 0);

setNumSelectedRealizations(selectedRealizations.length);

if (debounceTimeout.current) {
clearTimeout(debounceTimeout.current);
}
debounceTimeout.current = setTimeout(() => {
if (props.onChange) {
props.onChange(selectedRealizations);
}
}, 500);
}

function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
const value = event.target.value;
if (value[value.length - 1] === ",") {
const range = value.split("-");
const realizations: number[] = [];
if (range.length === 1) {
realizations.push(parseInt(range[0]));
setSelectedRealizations([...selectedRealizations, { start: parseInt(range[0]) }]);
} else if (range.length === 2) {
for (let i = parseInt(range[0]); i <= parseInt(range[1]); i++) {
realizations.push(i);
}
setSelectedRealizations([
...selectedRealizations,
{ start: parseInt(range[0]), end: parseInt(range[1]) },
]);
}
event.target.value = "";
}
}

function handlePointerDown() {
if (inputRef.current) {
inputRef.current.focus();
}
}

return (
<BaseComponent disabled={props.disabled}>
<ul
className="border border-gray-300 rounded p-2 flex flex-wrap items-center cursor-text"
onPointerDown={handlePointerDown}
>
{selectedRealizations.map((realizationRange) => (
<RealizationRangeTag key={realizationRange.start} range={realizationRange} />
))}
<li className="flex-grow">
<input
ref={inputRef}
type="text"
onChange={handleChange}
className="outline-none"
style={{ width: 30 }}
/>
</li>
</ul>
<div className="text-sm text-gray-500 text-right mt-2">
{numSelectedRealizations} realization{numSelectedRealizations === 1 ? "" : "s"} selected
</div>
</BaseComponent>
);
};
Loading

0 comments on commit 389bdf1

Please sign in to comment.