From dc926f5d14a4f516b8f6003836a6d2c900fa7635 Mon Sep 17 00:00:00 2001 From: Ruben Thoms <69145689+rubenthoms@users.noreply.github.com> Date: Sat, 23 Sep 2023 09:55:44 +0200 Subject: [PATCH 1/4] Improvements to GUI (#340) --- frontend/package-lock.json | 25 ++---- frontend/package.json | 2 +- frontend/src/App.tsx | 12 +-- frontend/src/GlobalErrorBoundary.tsx | 6 +- frontend/src/framework/Module.tsx | 16 ++-- frontend/src/framework/ModuleRegistry.ts | 4 +- .../private-components/crashView.tsx | 14 ++-- .../ViewWrapper/private-components/header.tsx | 4 +- .../ViewWrapper/viewWrapper.tsx | 4 +- .../internal/components/Drawer/drawer.tsx | 6 +- .../components/LoginButton/loginButton.tsx | 8 +- .../internal/components/LoginDialog/index.ts | 1 - .../components/LoginDialog/loginDialog.tsx | 33 -------- .../internal/components/NavBar/navBar.tsx | 76 ++++++++++++------- .../selectEnsemblesDialog.tsx | 10 +-- .../colorPaletteSettings.tsx | 10 ++- .../private-components/modulesList.tsx | 71 +++++++++-------- .../Settings/private-components/setting.tsx | 4 +- .../private-components/syncSettings.tsx | 12 +-- .../private-components/templatesList.tsx | 4 +- .../internal/components/Settings/settings.tsx | 4 +- .../internal/components/StartScreen/index.ts | 1 + .../components/StartScreen/startScreen.tsx | 40 ++++++++++ .../CollapsibleGroup/collapsibleGroup.tsx | 4 +- frontend/src/lib/components/Dialog/dialog.tsx | 4 +- .../src/lib/components/Dropdown/dropdown.tsx | 4 +- frontend/src/lib/components/Label/label.tsx | 4 +- .../src/lib/components/ListBox/list-box.tsx | 6 +- .../private-components/tag.tsx | 21 ++--- .../SmartNodeSelector/smartNodeSelector.tsx | 4 +- frontend/src/lib/components/Table/table.tsx | 8 +- frontend/src/main.tsx | 9 ++- .../src/modules/MyModule/registerModule.ts | 6 +- frontend/src/modules/Sensitivity/view.tsx | 15 ++-- .../SimulationTimeSeries/registerModule.ts | 3 +- 35 files changed, 246 insertions(+), 209 deletions(-) delete mode 100644 frontend/src/framework/internal/components/LoginDialog/index.ts delete mode 100644 frontend/src/framework/internal/components/LoginDialog/loginDialog.tsx create mode 100644 frontend/src/framework/internal/components/StartScreen/index.ts create mode 100644 frontend/src/framework/internal/components/StartScreen/startScreen.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9dc9be156..06fad83bd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,8 +9,8 @@ "version": "0.0.0", "dependencies": { "@headlessui/react": "^1.7.8", - "@heroicons/react": "^2.0.14", "@mui/base": "^5.0.0-beta.3", + "@mui/icons-material": "^5.14.9", "@tanstack/react-query": "^4.24.10", "@tanstack/react-query-devtools": "^4.24.12", "@webviz/subsurface-viewer": "^0.0.2-alpha.9", @@ -647,9 +647,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", - "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", + "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1408,14 +1408,6 @@ "react-dom": "^16 || ^17 || ^18" } }, - "node_modules/@heroicons/react": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.0.18.tgz", - "integrity": "sha512-7TyMjRrZZMBPa+/5Y8lN0iyvUU/01PeMGX2+RE7cQWpEUIcb4QotzUObFkJDejj/HUH4qjP/eQ0gzzKs2f+6Yw==", - "peerDependencies": { - "react": ">= 16" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", @@ -2791,12 +2783,11 @@ } }, "node_modules/@mui/icons-material": { - "version": "5.14.7", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.14.7.tgz", - "integrity": "sha512-mWp4DwMa8c1Gx9yOEtPgxM4b+e6hAbtZyzfSubdBwrnEE6G5D2rbAJ5MB+If6kfI48JaYaJ5j8+zAdmZLuZc0A==", - "peer": true, + "version": "5.14.9", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.14.9.tgz", + "integrity": "sha512-xTRQbDsogsJo7tY5Og8R9zbuG2q+KIPVIM6JQoKxtJlz9DPOw1u0T2fGrvwD+XAOVifQf6epNMcGCDLfJAz4Nw==", "dependencies": { - "@babel/runtime": "^7.22.10" + "@babel/runtime": "^7.22.15" }, "engines": { "node": ">=12.0.0" diff --git a/frontend/package.json b/frontend/package.json index 968995756..dc3cd32be 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,8 +16,8 @@ }, "dependencies": { "@headlessui/react": "^1.7.8", - "@heroicons/react": "^2.0.14", "@mui/base": "^5.0.0-beta.3", + "@mui/icons-material": "^5.14.9", "@tanstack/react-query": "^4.24.10", "@tanstack/react-query-devtools": "^4.24.12", "@webviz/subsurface-viewer": "^0.0.2-alpha.9", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4a0e9b82f..5f7e01cd2 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,7 +1,6 @@ import React from "react"; import { DrawerContent, LayoutElement, Workbench } from "@framework/Workbench"; -import { LoginDialog } from "@framework/internal/components/LoginDialog"; import { NavBar } from "@framework/internal/components/NavBar"; import { SettingsContentPanels } from "@framework/internal/components/SettingsContentPanels"; import { useQueryClient } from "@tanstack/react-query"; @@ -41,13 +40,10 @@ function App() { }, []); return ( - <> - -
- - -
- +
+ + +
); } diff --git a/frontend/src/GlobalErrorBoundary.tsx b/frontend/src/GlobalErrorBoundary.tsx index 5c1adc268..446b666f1 100644 --- a/frontend/src/GlobalErrorBoundary.tsx +++ b/frontend/src/GlobalErrorBoundary.tsx @@ -1,9 +1,9 @@ import React from "react"; -import { BugAntIcon, Square2StackIcon } from "@heroicons/react/20/solid"; import { Button } from "@lib/components/Button"; import { IconButton } from "@lib/components/IconButton"; import { resolveClassNames } from "@lib/utils/resolveClassNames"; +import { BugReport, ContentCopy } from "@mui/icons-material"; type Props = { children?: React.ReactNode; @@ -68,7 +68,7 @@ export class GlobalErrorBoundary extends React.Component { {freshStartUrl.toString()} - +
{ this.state.error?.stack ?? "" ) } - startIcon={} + startIcon={} > Report issue diff --git a/frontend/src/framework/Module.tsx b/frontend/src/framework/Module.tsx index c58fd420e..f9eb9dc96 100644 --- a/frontend/src/framework/Module.tsx +++ b/frontend/src/framework/Module.tsx @@ -44,13 +44,15 @@ export class Module { private _syncableSettingKeys: SyncSettingKey[]; private _channelsDef: BroadcastChannelsDef; private _drawPreviewFunc: DrawPreviewFunc | null; + private _description: string | null; constructor( name: string, defaultTitle: string, syncableSettingKeys: SyncSettingKey[] = [], broadcastChannelsDef: BroadcastChannelsDef = {}, - drawPreviewFunc: DrawPreviewFunc | null = null + drawPreviewFunc: DrawPreviewFunc | null = null, + description: string | null = null ) { this._name = name; this._defaultTitle = defaultTitle; @@ -63,6 +65,7 @@ export class Module { this._syncableSettingKeys = syncableSettingKeys; this._channelsDef = broadcastChannelsDef; this._drawPreviewFunc = drawPreviewFunc; + this._description = description; } getDrawPreviewFunc(): DrawPreviewFunc | null { @@ -73,14 +76,18 @@ export class Module { return this._importState; } - getName() { + getName(): string { return this._name; } - getDefaultTitle() { + getDefaultTitle(): string { return this._defaultTitle; } + getDescription(): string | null { + return this._description; + } + setWorkbench(workbench: Workbench): void { this._workbench = workbench; } @@ -99,14 +106,11 @@ export class Module { return this._syncableSettingKeys; } - hasSyncableSettingKey(key: SyncSettingKey): boolean { return this._syncableSettingKeys.includes(key); } - makeInstance(instanceNumber: number): ModuleInstance { - if (!this._workbench) { throw new Error("Module must be added to a workbench before making an instance"); } diff --git a/frontend/src/framework/ModuleRegistry.ts b/frontend/src/framework/ModuleRegistry.ts index 113f3eb08..34064da6f 100644 --- a/frontend/src/framework/ModuleRegistry.ts +++ b/frontend/src/framework/ModuleRegistry.ts @@ -10,6 +10,7 @@ export type RegisterModuleOptions = { syncableSettingKeys?: SyncSettingKey[]; broadcastChannelsDef?: BroadcastChannelsDef; preview?: DrawPreviewFunc; + description?: string; }; export class ModuleRegistry { @@ -26,7 +27,8 @@ export class ModuleRegistry { options.defaultTitle, options.syncableSettingKeys, options.broadcastChannelsDef, - options.preview || null + options.preview ?? null, + options.description ?? null ); this._registeredModules[options.moduleName] = module; return module; diff --git a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/crashView.tsx b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/crashView.tsx index 179ed3ad5..51d067680 100644 --- a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/crashView.tsx +++ b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/crashView.tsx @@ -1,8 +1,8 @@ import React from "react"; -import { ArrowPathIcon, DocumentMagnifyingGlassIcon, FaceFrownIcon, MegaphoneIcon } from "@heroicons/react/20/solid"; import { Button } from "@lib/components/Button"; import { Dialog } from "@lib/components/Dialog"; +import { BugReport, Info, MoodBad, Refresh } from "@mui/icons-material"; export type FormattedErrorProps = { moduleName: string; @@ -79,24 +79,20 @@ export const CrashView: React.FC = (props) => { return (
- +
{props.error.message}
The above error made your module instance crash. Unfortunately, this means that its state is lost. You can try to reset the instance to its initial state in order to start over.
- - -
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 c9acc4517..2208b481b 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 @@ -2,8 +2,8 @@ import React from "react"; import { ModuleInstance } from "@framework/ModuleInstance"; import { SyncSettingKey, SyncSettingsMeta } from "@framework/SyncSettings"; -import { XMarkIcon } from "@heroicons/react/20/solid"; import { isDevMode } from "@lib/utils/devMode"; +import { Close } from "@mui/icons-material"; export type HeaderProps = { moduleInstance: ModuleInstance; @@ -75,7 +75,7 @@ export const Header: React.FC = (props) => { onPointerDown={props.onRemoveClick} title="Remove this module" > - +
); 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 c00cb8e2a..c284464fd 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 @@ -92,6 +92,8 @@ export const ViewWrapper: React.FC = (props) => { handleModuleClick(); } + const showAsActive = props.isActive && [DrawerContent.ModuleSettings, DrawerContent.SyncSettings].includes(drawerContent); + return ( <> {props.isDragged && ( @@ -111,7 +113,7 @@ export const ViewWrapper: React.FC = (props) => { >
= (props) => { return (
- {props.icon && React.cloneElement(props.icon, { className: "w-5 h-5 mr-2" })} + {props.icon && React.cloneElement(props.icon, { fontSize: "small", className: "mr-2" })} {props.title}
@@ -25,7 +25,7 @@ export const Drawer: React.FC = (props) => {
} + startAdornment={} onChange={props.onFilterChange} />
diff --git a/frontend/src/framework/internal/components/LoginButton/loginButton.tsx b/frontend/src/framework/internal/components/LoginButton/loginButton.tsx index bc365854c..6e514c6dc 100644 --- a/frontend/src/framework/internal/components/LoginButton/loginButton.tsx +++ b/frontend/src/framework/internal/components/LoginButton/loginButton.tsx @@ -1,13 +1,13 @@ import React from "react"; import { AuthState, useAuthProvider } from "@framework/internal/providers/AuthProvider"; -import { ArrowLeftOnRectangleIcon, ArrowRightOnRectangleIcon, UserIcon } from "@heroicons/react/20/solid"; import { CircularProgress } from "@lib/components/CircularProgress"; import { Menu } from "@lib/components/Menu"; import { MenuItem } from "@lib/components/MenuItem"; import { resolveClassNames } from "@lib/utils/resolveClassNames"; import { getTextWidth } from "@lib/utils/textSize"; import { Dropdown, MenuButton } from "@mui/base"; +import { AccountCircle, Login, Logout } from "@mui/icons-material"; export type LoginButtonProps = { className?: string; @@ -29,9 +29,9 @@ export const LoginButton: React.FC = (props) => { function makeIcon() { if (authState === AuthState.LoggedIn) { - return ; + return ; } else if (authState === AuthState.NotLoggedIn) { - return ; + return ; } else { return ; } @@ -81,7 +81,7 @@ export const LoginButton: React.FC = (props) => { - + Sign out diff --git a/frontend/src/framework/internal/components/LoginDialog/index.ts b/frontend/src/framework/internal/components/LoginDialog/index.ts deleted file mode 100644 index 3b09d5b99..000000000 --- a/frontend/src/framework/internal/components/LoginDialog/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { LoginDialog } from "./loginDialog"; diff --git a/frontend/src/framework/internal/components/LoginDialog/loginDialog.tsx b/frontend/src/framework/internal/components/LoginDialog/loginDialog.tsx deleted file mode 100644 index dfd5cb46d..000000000 --- a/frontend/src/framework/internal/components/LoginDialog/loginDialog.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from "react"; - -import { AuthState, useAuthProvider } from "@framework/internal/providers/AuthProvider"; -import { Button } from "@lib/components/Button"; -import { CircularProgress } from "@lib/components/CircularProgress"; -import { Dialog } from "@lib/components/Dialog"; - -export const LoginDialog: React.FC = () => { - const auth = useAuthProvider(); - - function signIn() { - window.location.href = `/api/login?redirect_url_after_login=${btoa("/")}`; - } - - if (auth.authState !== AuthState.LoggedIn) { - return ( - - {auth.authState === AuthState.NotLoggedIn ? "Sign in" : } - - } - > - You have to sign in in order to use this application. - - ); - } else { - return null; - } -}; diff --git a/frontend/src/framework/internal/components/NavBar/navBar.tsx b/frontend/src/framework/internal/components/NavBar/navBar.tsx index ccff4ee24..11c0c6f40 100644 --- a/frontend/src/framework/internal/components/NavBar/navBar.tsx +++ b/frontend/src/framework/internal/components/NavBar/navBar.tsx @@ -3,27 +3,27 @@ import React from "react"; import WebvizLogo from "@assets/webviz.svg"; import { EnsembleIdent } from "@framework/EnsembleIdent"; import { useStoreState } from "@framework/StateStore"; -import { DrawerContent, Workbench } from "@framework/Workbench"; +import { DrawerContent, Workbench, WorkbenchEvents } from "@framework/Workbench"; import { useEnsembleSet } from "@framework/WorkbenchSession"; import { LoginButton } from "@framework/internal/components/LoginButton"; import { SelectEnsemblesDialog } from "@framework/internal/components/SelectEnsemblesDialog"; import { EnsembleItem } from "@framework/internal/components/SelectEnsemblesDialog/selectEnsemblesDialog"; -import { - ChevronLeftIcon, - ChevronRightIcon, - Cog6ToothIcon, - LinkIcon, - QueueListIcon, - Squares2X2Icon, - StarIcon, - SwatchIcon, - WindowIcon, -} from "@heroicons/react/20/solid"; import { Badge } from "@lib/components/Badge"; import { Button } from "@lib/components/Button"; import { CircularProgress } from "@lib/components/CircularProgress"; import { isDevMode } from "@lib/utils/devMode"; import { resolveClassNames } from "@lib/utils/resolveClassNames"; +import { + ChevronLeft, + ChevronRight, + GitHub, + GridView, + Link, + List, + Palette, + Settings, + WebAsset, +} from "@mui/icons-material"; import { useQueryClient } from "@tanstack/react-query"; type NavBarProps = { @@ -36,6 +36,7 @@ const NavBarDivider: React.FC = () => { export const NavBar: React.FC = (props) => { const [ensembleDialogOpen, setEnsembleDialogOpen] = React.useState(false); + const [layoutEmpty, setLayoutEmpty] = React.useState(props.workbench.getLayout().length === 0); const [expanded, setExpanded] = React.useState(localStorage.getItem("navBarExpanded") === "true"); const [loadingEnsembleSet, setLoadingEnsembleSet] = useStoreState( props.workbench.getGuiStateStore(), @@ -50,6 +51,27 @@ export const NavBar: React.FC = (props) => { const queryClient = useQueryClient(); + React.useEffect( + function reactToModuleInstancesChanged() { + function listener() { + if ( + props.workbench.getLayout().length === 0 && + [DrawerContent.ModuleSettings, DrawerContent.SyncSettings].includes(drawerContent) + ) { + setDrawerContent(DrawerContent.ModulesList); + } + setLayoutEmpty(props.workbench.getLayout().length === 0); + } + + const unsubscribeFunc = props.workbench.subscribe(WorkbenchEvents.ModuleInstancesChanged, listener); + + return () => { + unsubscribeFunc(); + }; + }, + [drawerContent] + ); + function ensureSettingsPanelIsVisible() { if (settingsPanelWidth <= 5) { setSettingsPanelWidth(20); @@ -126,7 +148,7 @@ export const NavBar: React.FC = (props) => { className="!text-slate-800" title={expanded ? "Collapse menu" : "Expand menu"} > - {expanded ? : } + {expanded ? : }
@@ -136,7 +158,7 @@ export const NavBar: React.FC = (props) => { className="w-full !text-slate-800 h-10" startIcon={ selectedEnsembles.length === 0 && !loadingEnsembleSet ? ( - + ) : ( = (props) => { ) } > - + ) } @@ -160,24 +182,26 @@ export const NavBar: React.FC = (props) => { @@ -185,11 +209,11 @@ export const NavBar: React.FC = (props) => { diff --git a/frontend/src/framework/internal/components/SelectEnsemblesDialog/selectEnsemblesDialog.tsx b/frontend/src/framework/internal/components/SelectEnsemblesDialog/selectEnsemblesDialog.tsx index f3be24941..618bc9666 100644 --- a/frontend/src/framework/internal/components/SelectEnsemblesDialog/selectEnsemblesDialog.tsx +++ b/frontend/src/framework/internal/components/SelectEnsemblesDialog/selectEnsemblesDialog.tsx @@ -2,7 +2,6 @@ import React from "react"; import { CaseInfo_api, EnsembleInfo_api } from "@api"; import { apiService } from "@framework/ApiService"; -import { CheckIcon, PlusIcon, TrashIcon } from "@heroicons/react/20/solid"; import { ApiStateWrapper } from "@lib/components/ApiStateWrapper"; import { Button } from "@lib/components/Button"; import { CircularProgress } from "@lib/components/CircularProgress"; @@ -12,6 +11,7 @@ import { IconButton } from "@lib/components/IconButton"; import { Label } from "@lib/components/Label"; import { Select } from "@lib/components/Select"; import { useValidState } from "@lib/hooks/useValidState"; +import { Add, Check, Remove } from "@mui/icons-material"; import { useQuery } from "@tanstack/react-query"; import { isEqual } from "lodash"; @@ -232,11 +232,7 @@ export const SelectEnsemblesDialog: React.FC = (prop color={ensembleAlreadySelected ? "success" : "primary"} disabled={ensembleAlreadySelected || ensembleOpts.length === 0} startIcon={ - ensembleAlreadySelected ? ( - - ) : ( - - ) + ensembleAlreadySelected ? : } > {ensembleAlreadySelected ? "Ensemble already selected" : "Add Ensemble"} @@ -282,7 +278,7 @@ export const SelectEnsemblesDialog: React.FC = (prop } color="danger" > - + {" "} diff --git a/frontend/src/framework/internal/components/Settings/private-components/colorPaletteSettings.tsx b/frontend/src/framework/internal/components/Settings/private-components/colorPaletteSettings.tsx index e21fd805d..eb9af71fa 100644 --- a/frontend/src/framework/internal/components/Settings/private-components/colorPaletteSettings.tsx +++ b/frontend/src/framework/internal/components/Settings/private-components/colorPaletteSettings.tsx @@ -5,7 +5,6 @@ import { useStoreValue } from "@framework/StateStore"; import { DrawerContent, Workbench } from "@framework/Workbench"; import { ColorPaletteType, ColorScaleDiscreteSteps } from "@framework/WorkbenchSettings"; import { Drawer } from "@framework/internal/components/Drawer"; -import { ChevronDownIcon } from "@heroicons/react/20/solid"; import { ColorGradient } from "@lib/components/ColorGradient"; import { ColorTileGroup } from "@lib/components/ColorTileGroup"; import { IconButton } from "@lib/components/IconButton"; @@ -16,6 +15,7 @@ import { useElementBoundingRect } from "@lib/hooks/useElementBoundingRect"; import { ColorPalette } from "@lib/utils/ColorPalette"; import { resolveClassNames } from "@lib/utils/resolveClassNames"; import { convertRemToPixels } from "@lib/utils/screenUnitConversions"; +import { ExpandMore, Palette } from "@mui/icons-material"; enum ColorPaletteSelectorType { Categorical = "categorical", @@ -144,7 +144,7 @@ const ColorPaletteSelector: React.FC = (props) => {
{makeColorPalettePreview(selectedColorPalette, props.type, props.steps)}
- + {open && ReactDOM.createPortal( @@ -201,7 +201,11 @@ export const ColorPaletteSettings: React.FC = (props) } return ( - + } + visible={drawerContent === DrawerContent.ColorPaletteSettings} + >
@@ -171,25 +177,28 @@ export const ModulesList: React.FC = (props) => { }; return ( - } - showFilter - filterPlaceholder="Filter modules..." - onFilterChange={handleSearchQueryChange} - > - {Object.values(ModuleRegistry.getRegisteredModules()) - .filter((mod) => mod.getDefaultTitle().toLowerCase().includes(searchQuery.toLowerCase())) - .map((mod) => ( - - ))} - + <> + } + showFilter + filterPlaceholder="Filter modules..." + onFilterChange={handleSearchQueryChange} + > + {Object.values(ModuleRegistry.getRegisteredModules()) + .filter((mod) => mod.getDefaultTitle().toLowerCase().includes(searchQuery.toLowerCase())) + .map((mod) => ( + + ))} + + ); }; diff --git a/frontend/src/framework/internal/components/Settings/private-components/setting.tsx b/frontend/src/framework/internal/components/Settings/private-components/setting.tsx index c589cb01b..eb8f4386f 100644 --- a/frontend/src/framework/internal/components/Settings/private-components/setting.tsx +++ b/frontend/src/framework/internal/components/Settings/private-components/setting.tsx @@ -5,9 +5,9 @@ import { ModuleInstance, ModuleInstanceState } from "@framework/ModuleInstance"; import { Workbench } from "@framework/Workbench"; import { ErrorBoundary } from "@framework/internal/components/ErrorBoundary"; import { useImportState } from "@framework/internal/hooks/moduleHooks"; -import { Cog6ToothIcon } from "@heroicons/react/20/solid"; import { CircularProgress } from "@lib/components/CircularProgress"; import { resolveClassNames } from "@lib/utils/resolveClassNames"; +import { Settings as SettingsIcon } from "@mui/icons-material"; import { DebugProfiler } from "../../DebugProfiler"; @@ -81,7 +81,7 @@ export const Setting: React.FC = (props) => { >
- {" "} + {" "} = (props) => { - - @@ -120,7 +120,7 @@ export const SyncSettings: React.FC = (props) => { } return ( - } visible={drawerContent === DrawerContent.SyncSettings}> + } visible={drawerContent === DrawerContent.SyncSettings}> {makeContent()} ); diff --git a/frontend/src/framework/internal/components/Settings/private-components/templatesList.tsx b/frontend/src/framework/internal/components/Settings/private-components/templatesList.tsx index ca1a27df2..8cf27295d 100644 --- a/frontend/src/framework/internal/components/Settings/private-components/templatesList.tsx +++ b/frontend/src/framework/internal/components/Settings/private-components/templatesList.tsx @@ -5,7 +5,7 @@ import { useStoreState } from "@framework/StateStore"; import { Template, TemplateRegistry } from "@framework/TemplateRegistry"; import { DrawerContent, Workbench } from "@framework/Workbench"; import { Drawer } from "@framework/internal/components/Drawer"; -import { Squares2X2Icon } from "@heroicons/react/20/solid"; +import { GridView } from "@mui/icons-material"; function drawTemplatePreview(template: Template, width: number, height: number): React.ReactNode { return ( @@ -115,7 +115,7 @@ export const TemplatesList: React.FC = (props) => { onFilterChange={handleSearchQueryChange} filterPlaceholder="Filter templates..." title="Select a template" - icon={} + icon={} visible={drawerContent === DrawerContent.TemplatesList} > {Object.keys(TemplateRegistry.getRegisteredTemplates()) diff --git a/frontend/src/framework/internal/components/Settings/settings.tsx b/frontend/src/framework/internal/components/Settings/settings.tsx index c9e2324fb..59e6b31f3 100644 --- a/frontend/src/framework/internal/components/Settings/settings.tsx +++ b/frontend/src/framework/internal/components/Settings/settings.tsx @@ -3,8 +3,8 @@ import React from "react"; import { useStoreValue } from "@framework/StateStore"; import { DrawerContent, Workbench } from "@framework/Workbench"; import { useActiveModuleId, useModuleInstances } from "@framework/internal/hooks/workbenchHooks"; -import { Cog6ToothIcon } from "@heroicons/react/20/solid"; import { resolveClassNames } from "@lib/utils/resolveClassNames"; +import { Settings as SettingsIcon } from "@mui/icons-material"; import { ColorPaletteSettings } from "./private-components/colorPaletteSettings"; import { ModulesList } from "./private-components/modulesList"; @@ -51,7 +51,7 @@ export const Settings: React.FC = (props) => { ))} {moduleInstances.length === 0 && (
- +
)} diff --git a/frontend/src/framework/internal/components/StartScreen/index.ts b/frontend/src/framework/internal/components/StartScreen/index.ts new file mode 100644 index 000000000..2e17b79d3 --- /dev/null +++ b/frontend/src/framework/internal/components/StartScreen/index.ts @@ -0,0 +1 @@ +export { StartScreen } from "./startScreen"; diff --git a/frontend/src/framework/internal/components/StartScreen/startScreen.tsx b/frontend/src/framework/internal/components/StartScreen/startScreen.tsx new file mode 100644 index 000000000..268b0f991 --- /dev/null +++ b/frontend/src/framework/internal/components/StartScreen/startScreen.tsx @@ -0,0 +1,40 @@ +import React from "react"; + +import WebvizLogo from "@assets/webviz.svg"; +import { AuthState, useAuthProvider } from "@framework/internal/providers/AuthProvider"; +import { Button } from "@lib/components/Button"; +import { CircularProgress } from "@lib/components/CircularProgress"; + +export type StartScreenProps = { + children?: React.ReactNode; +}; + +export const StartScreen: React.FC = (props) => { + const { authState } = useAuthProvider(); + + function signIn() { + window.location.href = `/api/login?redirect_url_after_login=${btoa("/")}`; + } + + if (authState === AuthState.Loading) { + return ( +
+ +
+ ); + } + + if (authState === AuthState.NotLoggedIn) { + return ( +
+ Webviz logo +

Please sign in to continue.

+ +
+ ); + } + + return <>{props.children}; +}; + +StartScreen.displayName = "StartScreen"; diff --git a/frontend/src/lib/components/CollapsibleGroup/collapsibleGroup.tsx b/frontend/src/lib/components/CollapsibleGroup/collapsibleGroup.tsx index bcb7aee29..af32352a2 100644 --- a/frontend/src/lib/components/CollapsibleGroup/collapsibleGroup.tsx +++ b/frontend/src/lib/components/CollapsibleGroup/collapsibleGroup.tsx @@ -1,7 +1,7 @@ import React from "react"; -import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/20/solid"; import { resolveClassNames } from "@lib/utils/resolveClassNames"; +import { ExpandLess, ExpandMore } from "@mui/icons-material"; import { BaseComponent, BaseComponentProps } from "../BaseComponent"; @@ -40,7 +40,7 @@ export const CollapsibleGroup: React.FC = (props) => { > {props.icon && React.cloneElement(props.icon, { className: "w-4 h-4" })}

{props.title}

- {expanded ? : } + {expanded ? : }
= (props) => { onPointerDown={handleClose} title="Close dialog" > - +
)} diff --git a/frontend/src/lib/components/Dropdown/dropdown.tsx b/frontend/src/lib/components/Dropdown/dropdown.tsx index 870f38617..57826cc5f 100644 --- a/frontend/src/lib/components/Dropdown/dropdown.tsx +++ b/frontend/src/lib/components/Dropdown/dropdown.tsx @@ -1,10 +1,10 @@ import React from "react"; import ReactDOM from "react-dom"; -import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/20/solid"; import { useElementBoundingRect } from "@lib/hooks/useElementBoundingRect"; import { resolveClassNames } from "@lib/utils/resolveClassNames"; import { getTextWidth } from "@lib/utils/textSize"; +import { ExpandLess, ExpandMore } from "@mui/icons-material"; import { BaseComponent, BaseComponentProps } from "../BaseComponent"; import { IconButton } from "../IconButton"; @@ -273,7 +273,7 @@ export const Dropdown = withDefaults()(defaultProps, (props) => { onClick={() => handleInputClick()} endAdornment={ setDropdownVisible((prev) => !prev)}> - {dropdownVisible ? : } + {dropdownVisible ? : } } onChange={handleInputChange} diff --git a/frontend/src/lib/components/Label/label.tsx b/frontend/src/lib/components/Label/label.tsx index ac83f2259..9f1bf49c5 100644 --- a/frontend/src/lib/components/Label/label.tsx +++ b/frontend/src/lib/components/Label/label.tsx @@ -1,7 +1,7 @@ import React from "react"; -import { LinkIcon } from "@heroicons/react/20/solid"; import { resolveClassNames } from "@lib/utils/resolveClassNames"; +import { Link } from "@mui/icons-material"; import { v4 } from "uuid"; @@ -41,7 +41,7 @@ export const Label: React.FC = (props) => { className="bg-indigo-700 w-5 h-5 flex justify-center items-center rounded mr-2" title={`"${props.text}" is synced on the current page`} > - + )} {props.text} diff --git a/frontend/src/lib/components/ListBox/list-box.tsx b/frontend/src/lib/components/ListBox/list-box.tsx index 9eb520cb5..437299a80 100644 --- a/frontend/src/lib/components/ListBox/list-box.tsx +++ b/frontend/src/lib/components/ListBox/list-box.tsx @@ -1,7 +1,7 @@ import React from "react"; import { Listbox, Transition } from "@headlessui/react"; -import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/20/solid"; +import { Check, UnfoldMore } from "@mui/icons-material"; export type ListBoxItem = { value: string | number; @@ -24,7 +24,7 @@ export const ListBoxDeprecated: React.FC = (props) => { {selectedItemLabel} - = (props) => { {selected ? ( - ) : null} diff --git a/frontend/src/lib/components/SmartNodeSelector/private-components/tag.tsx b/frontend/src/lib/components/SmartNodeSelector/private-components/tag.tsx index f9734183c..f58253968 100644 --- a/frontend/src/lib/components/SmartNodeSelector/private-components/tag.tsx +++ b/frontend/src/lib/components/SmartNodeSelector/private-components/tag.tsx @@ -1,14 +1,7 @@ import React from "react"; -import { - ChevronDownIcon, - ChevronUpIcon, - ExclamationCircleIcon, - ExclamationTriangleIcon, - QuestionMarkCircleIcon, - XMarkIcon, -} from "@heroicons/react/20/solid"; import { resolveClassNames } from "@lib/utils/resolveClassNames"; +import { Close, Error, ExpandLess, ExpandMore, Help, Warning } from "@mui/icons-material"; import "animate.css"; @@ -174,7 +167,7 @@ export class Tag extends React.Component { e.stopPropagation(); }} > - + ); @@ -386,7 +379,7 @@ export class Tag extends React.Component { title="Remove" onClick={(e): void => removeTag(e, index)} > - + )} {this.createBrowseButtons(treeNodeSelection, index)} @@ -406,15 +399,15 @@ export class Tag extends React.Component { } > {this.addAdditionalClasses(!valid) && !valid && !currentTag && ( - + )} {this.addAdditionalClasses(!valid) && valid && duplicate && ( - + )} {this.addAdditionalClasses(!valid) && (valid || currentTag) && !duplicate && - treeNodeSelection.icons().length > 1 && } + treeNodeSelection.icons().length > 1 && } {this.createMatchesCounter(treeNodeSelection, index)}
- +
{showSuggestions && ( diff --git a/frontend/src/lib/components/Table/table.tsx b/frontend/src/lib/components/Table/table.tsx index 3bbac9515..b74d83f0d 100644 --- a/frontend/src/lib/components/Table/table.tsx +++ b/frontend/src/lib/components/Table/table.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { ChevronDownIcon, ChevronUpIcon, XMarkIcon } from "@heroicons/react/20/solid"; +import { Close, ExpandLess, ExpandMore } from "@mui/icons-material"; import { v4 } from "uuid"; @@ -182,7 +182,7 @@ export const Table: React.FC> = (props) => { : undefined } > - + > = (props) => { : undefined } > - + @@ -206,7 +206,7 @@ export const Table: React.FC> = (props) => { onChange={(e) => handleFilterChange(col, e.target.value)} endAdornment={ handleFilterChange(col, "")}> - + } /> diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index bd15e2748..86e1f2ce4 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,6 +1,7 @@ import React from "react"; import { createRoot } from "react-dom/client"; +import { StartScreen } from "@framework/internal/components/StartScreen"; import { AuthProvider } from "@framework/internal/providers/AuthProvider"; import { CustomQueryClientProvider } from "@framework/internal/providers/QueryClientProvider"; @@ -32,9 +33,11 @@ root.render( - - - + + + + + diff --git a/frontend/src/modules/MyModule/registerModule.ts b/frontend/src/modules/MyModule/registerModule.ts index 4f34a3185..a55be4116 100644 --- a/frontend/src/modules/MyModule/registerModule.ts +++ b/frontend/src/modules/MyModule/registerModule.ts @@ -2,4 +2,8 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; import { State } from "./state"; -ModuleRegistry.registerModule({ moduleName: "MyModule", defaultTitle: "My Module" }); +ModuleRegistry.registerModule({ + moduleName: "MyModule", + defaultTitle: "My Module", + description: "My module description", +}); diff --git a/frontend/src/modules/Sensitivity/view.tsx b/frontend/src/modules/Sensitivity/view.tsx index 1dd06118c..2989e09b6 100644 --- a/frontend/src/modules/Sensitivity/view.tsx +++ b/frontend/src/modules/Sensitivity/view.tsx @@ -4,8 +4,8 @@ import { BroadcastChannelData, BroadcastChannelMeta } from "@framework/Broadcast import { Ensemble } from "@framework/Ensemble"; import { ModuleFCProps } from "@framework/Module"; import { useEnsembleSet } from "@framework/WorkbenchSession"; -import { AdjustmentsHorizontalIcon, ChartBarIcon, TableCellsIcon } from "@heroicons/react/20/solid"; import { useElementSize } from "@lib/hooks/useElementSize"; +import { BarChart, TableChart, Tune } from "@mui/icons-material"; import SensitivityChart from "./sensitivityChart"; import { EnsembleScalarResponse, SensitivityResponseCalculator } from "./sensitivityResponseCalculator"; @@ -114,18 +114,23 @@ export const view = ({ moduleContext, workbenchSession, workbenchServices }: Mod
-
diff --git a/frontend/src/modules/SimulationTimeSeries/registerModule.ts b/frontend/src/modules/SimulationTimeSeries/registerModule.ts index 780d98c67..729c04808 100644 --- a/frontend/src/modules/SimulationTimeSeries/registerModule.ts +++ b/frontend/src/modules/SimulationTimeSeries/registerModule.ts @@ -10,5 +10,6 @@ ModuleRegistry.registerModule({ defaultTitle: "Simulation time series", syncableSettingKeys: [SyncSettingKey.ENSEMBLE, SyncSettingKey.TIME_SERIES], broadcastChannelsDef, - preview + preview, + description: "Time series of simulation results", }); From 4a837905ee51428f8ace7921b98320d6bd5e7312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Herje?= <82032112+jorgenherje@users.noreply.github.com> Date: Mon, 25 Sep 2023 08:54:55 +0200 Subject: [PATCH 2/4] Refactor WellCompletions back-end code (#317) --- backend/src/backend/primary/main.py | 4 +- .../primary/routers/well_completion/router.py | 30 -- .../routers/well_completion/schemas.py | 7 - .../routers/well_completions/router.py | 29 ++ .../sumo_access/well_completion_access.py | 70 ----- .../sumo_access/well_completions_access.py | 277 ++++++++++++++++++ .../sumo_access/well_completions_types.py | 45 +++ .../services/types/well_completion_types.py | 48 --- .../services/utils/well_completion_utils.py | 187 ------------ frontend/src/api/ApiService.ts | 6 +- frontend/src/api/index.ts | 13 +- frontend/src/api/models/WellCompletionData.ts | 10 - .../src/api/models/WellCompletionDataSet.ts | 19 -- .../src/api/models/WellCompletionUnits.ts | 10 - .../src/api/models/WellCompletionsData.ts | 19 ++ ...UnitInfo.ts => WellCompletionsUnitInfo.ts} | 2 +- .../src/api/models/WellCompletionsUnits.ts | 10 + ...mpletionWell.ts => WellCompletionsWell.ts} | 2 +- ...mpletionZone.ts => WellCompletionsZone.ts} | 4 +- ...onService.ts => WellCompletionsService.ts} | 14 +- .../loadModule.tsx | 2 +- .../queryHooks.tsx | 10 +- .../registerModule.ts | 2 +- .../settings.tsx | 22 +- .../state.ts | 0 .../utils/stringUtils.ts | 2 +- .../utils/wellCompletionsDataAccessor.ts | 49 ++-- .../view.tsx | 2 +- frontend/src/modules/registerAllModules.ts | 2 +- 29 files changed, 447 insertions(+), 450 deletions(-) delete mode 100644 backend/src/backend/primary/routers/well_completion/router.py delete mode 100644 backend/src/backend/primary/routers/well_completion/schemas.py create mode 100644 backend/src/backend/primary/routers/well_completions/router.py delete mode 100644 backend/src/services/sumo_access/well_completion_access.py create mode 100644 backend/src/services/sumo_access/well_completions_access.py create mode 100644 backend/src/services/sumo_access/well_completions_types.py delete mode 100644 backend/src/services/types/well_completion_types.py delete mode 100644 backend/src/services/utils/well_completion_utils.py delete mode 100644 frontend/src/api/models/WellCompletionData.ts delete mode 100644 frontend/src/api/models/WellCompletionDataSet.ts delete mode 100644 frontend/src/api/models/WellCompletionUnits.ts create mode 100644 frontend/src/api/models/WellCompletionsData.ts rename frontend/src/api/models/{WellCompletionUnitInfo.ts => WellCompletionsUnitInfo.ts} (74%) create mode 100644 frontend/src/api/models/WellCompletionsUnits.ts rename frontend/src/api/models/{WellCompletionWell.ts => WellCompletionsWell.ts} (87%) rename frontend/src/api/models/{WellCompletionZone.ts => WellCompletionsZone.ts} (56%) rename frontend/src/api/services/{WellCompletionService.ts => WellCompletionsService.ts} (72%) rename frontend/src/modules/{WellCompletion => WellCompletions}/loadModule.tsx (81%) rename frontend/src/modules/{WellCompletion => WellCompletions}/queryHooks.tsx (58%) rename frontend/src/modules/{WellCompletion => WellCompletions}/registerModule.ts (78%) rename frontend/src/modules/{WellCompletion => WellCompletions}/settings.tsx (95%) rename frontend/src/modules/{WellCompletion => WellCompletions}/state.ts (100%) rename frontend/src/modules/{WellCompletion => WellCompletions}/utils/stringUtils.ts (92%) rename frontend/src/modules/{WellCompletion => WellCompletions}/utils/wellCompletionsDataAccessor.ts (86%) rename frontend/src/modules/{WellCompletion => WellCompletions}/view.tsx (93%) diff --git a/backend/src/backend/primary/main.py b/backend/src/backend/primary/main.py index 9386e9b9b..36c29c12e 100644 --- a/backend/src/backend/primary/main.py +++ b/backend/src/backend/primary/main.py @@ -17,7 +17,7 @@ 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_completion.router import router as well_completion_router +from .routers.well_completions.router import router as well_completions_router from .routers.well.router import router as well_router from .routers.surface_polygons.router import router as surface_polygons_router @@ -53,7 +53,7 @@ def custom_generate_unique_id(route: APIRoute) -> str: 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_completion_router, prefix="/well_completion", tags=["well_completion"]) +app.include_router(well_completions_router, prefix="/well_completions", tags=["well_completions"]) app.include_router(well_router, prefix="/well", tags=["well"]) app.include_router(surface_polygons_router, prefix="/surface_polygons", tags=["surface_polygons"]) diff --git a/backend/src/backend/primary/routers/well_completion/router.py b/backend/src/backend/primary/routers/well_completion/router.py deleted file mode 100644 index f9044792a..000000000 --- a/backend/src/backend/primary/routers/well_completion/router.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Optional - -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.sumo_access.well_completion_access import WellCompletionAccess -from src.services.utils.well_completion_utils import WellCompletionDataModel - -from . import schemas - -router = APIRouter() - - -@router.get("/well_completion_data/") -def get_well_completion_data( - # fmt:off - authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), - case_uuid: str = Query(description="Sumo case uuid"), - ensemble_name: str = Query(description="Ensemble name"), - realization: Optional[int] = Query(None, description="Optional realization to include. If not specified, all realizations will be returned."), - # fmt:on -) -> schemas.WellCompletionData: - access = WellCompletionAccess(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) - - well_completion_df = access.get_well_completion_data(realization=realization) - well_completion_data_model = WellCompletionDataModel(well_completion_df) - - return schemas.WellCompletionData(json_data=well_completion_data_model.create_well_completion_dataset()) diff --git a/backend/src/backend/primary/routers/well_completion/schemas.py b/backend/src/backend/primary/routers/well_completion/schemas.py deleted file mode 100644 index e04e65187..000000000 --- a/backend/src/backend/primary/routers/well_completion/schemas.py +++ /dev/null @@ -1,7 +0,0 @@ -from pydantic import BaseModel - -from src.services.utils.well_completion_utils import WellCompletionDataSet - - -class WellCompletionData(BaseModel): - json_data: WellCompletionDataSet diff --git a/backend/src/backend/primary/routers/well_completions/router.py b/backend/src/backend/primary/routers/well_completions/router.py new file mode 100644 index 000000000..de3bb50a6 --- /dev/null +++ b/backend/src/backend/primary/routers/well_completions/router.py @@ -0,0 +1,29 @@ +from typing import Optional + +from fastapi import APIRouter, Depends, HTTPException, Query + +from src.backend.auth.auth_helper import AuthHelper +from src.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 + +router = APIRouter() + + +@router.get("/well_completions_data/") +def get_well_completions_data( + # fmt:off + authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), + case_uuid: str = Query(description="Sumo case uuid"), + ensemble_name: str = Query(description="Ensemble name"), + realization: Optional[int] = Query(None, description="Optional realization to include. If not specified, all realizations will be returned."), + # fmt:on +) -> WellCompletionsData: + access = WellCompletionsAccess(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name) + well_completions_data = access.get_well_completions_data(realization=realization) + + if not well_completions_data: + raise HTTPException(status_code=404, detail="Well completions data not found") + + return well_completions_data diff --git a/backend/src/services/sumo_access/well_completion_access.py b/backend/src/services/sumo_access/well_completion_access.py deleted file mode 100644 index d226d0ee5..000000000 --- a/backend/src/services/sumo_access/well_completion_access.py +++ /dev/null @@ -1,70 +0,0 @@ -from typing import Optional - -import pandas as pd - -from fmu.sumo.explorer.explorer import CaseCollection, Case, SumoClient -from ._helpers import create_sumo_client_instance - - -class WellCompletionAccess: - """ - Class for accessing and retrieving well completion data - """ - - def __init__(self, access_token: str, case_uuid: str, iteration_name: str) -> None: - sumo_client: SumoClient = create_sumo_client_instance(access_token) - case_collection = CaseCollection(sumo_client).filter(uuid=case_uuid) - if len(case_collection) > 1: - raise ValueError(f"Multiple sumo cases found {case_uuid=}") - if len(case_collection) < 1: - raise ValueError(f"No sumo cases found {case_uuid=}") - - self._case: Case = case_collection[0] - self._iteration_name = iteration_name - self._tagname = str("wellcompletiondata") # Should tagname be hard coded? - - def get_well_completion_data(self, realization: Optional[int]) -> pd.DataFrame: - """Get well completion data for case and iteration""" - - # With single realization, return the table including additional column REAL - if realization is not None: - well_completion_tables = self._case.tables.filter( - tagname=self._tagname, realization=realization, iteration=self._iteration_name - ) - well_completion_df = well_completion_tables[0].to_pandas if len(well_completion_tables) > 0 else None - if well_completion_df is None: - return {} - - well_completion_df["REAL"] = realization - return well_completion_df - - # With multiple realizations, retrieve each column and concatenate - # Expect one table with aggregated OP/SH and one with aggregate KH data - well_completion_tables = self._case.tables.filter( - tagname=self._tagname, aggregation="collection", iteration=self._iteration_name - ) - - # Improve code (iterate over tables and concatenate) - concat gives issue? See jupyter-notebook - if len(well_completion_tables) < 2: - return {} - - first_df = well_completion_tables[0].to_pandas - second_df = well_completion_tables[1].to_pandas - - expected_columns = set(["WELL", "DATE", "ZONE", "REAL"]) - if not set(first_df.columns).issuperset(expected_columns) or not set(second_df.columns).issuperset( - expected_columns - ): - raise ValueError( - f"Expected df columns to be superset of columns: {expected_columns} - got: {first_df.columns} and {second_df.columns}" - ) - - if "OP/SH" in first_df.columns and "KH" in second_df.columns: - first_df["KH"] = second_df["KH"] - return first_df - - if "OP/SH" in second_df.columns and "KH" in first_df.columns: - second_df["KH"] = first_df["KH"] - return second_df - - raise ValueError('Expected columns "OP/SH" and "KH" not found in tables') diff --git a/backend/src/services/sumo_access/well_completions_access.py b/backend/src/services/sumo_access/well_completions_access.py new file mode 100644 index 000000000..276e03140 --- /dev/null +++ b/backend/src/services/sumo_access/well_completions_access.py @@ -0,0 +1,277 @@ +import itertools +from typing import Dict, Iterator, List, Optional, Set, Tuple + +import pandas as pd + +from fmu.sumo.explorer.explorer import CaseCollection, Case, SumoClient +from ._helpers import create_sumo_client_instance + +from .well_completions_types import ( + Completions, + WellCompletionsAttributeType, + WellCompletionsWell, + WellCompletionsData, + WellCompletionsZone, + WellCompletionsUnitInfo, + WellCompletionsUnits, +) + + +class WellCompletionsAccess: + """ + Class for accessing and retrieving well completions data + """ + + def __init__(self, access_token: str, case_uuid: str, iteration_name: str) -> None: + sumo_client: SumoClient = create_sumo_client_instance(access_token) + case_collection = CaseCollection(sumo_client).filter(uuid=case_uuid) + if len(case_collection) > 1: + raise ValueError(f"Multiple sumo cases found {case_uuid=}") + if len(case_collection) < 1: + raise ValueError(f"No sumo cases found {case_uuid=}") + + self._case: Case = case_collection[0] + self._iteration_name = iteration_name + self._tagname = str("wellcompletiondata") # Should tagname be hard coded? + + def get_well_completions_data(self, realization: Optional[int]) -> Optional[WellCompletionsData]: + """Get well completions data for case and iteration""" + + # With single realization, filter on realization + if realization is not None: + well_completions_tables = self._case.tables.filter( + tagname=self._tagname, realization=realization, iteration=self._iteration_name + ) + well_completions_df = well_completions_tables[0].to_pandas if len(well_completions_tables) > 0 else None + if well_completions_df is None: + return None + + return WellCompletionDataConverter(well_completions_df).create_data() + + # With multiple realizations, expect one table with aggregated OP/SH and one with aggregate KH data + well_completions_tables = self._case.tables.filter( + tagname=self._tagname, aggregation="collection", iteration=self._iteration_name + ) + + # As of now, two tables are expected - one with OP/SH and one with KH + if len(well_completions_tables) < 2: + return None + + expected_common_columns = set(["WELL", "DATE", "ZONE", "REAL"]) + first_df = well_completions_tables[0].to_pandas + second_df = well_completions_tables[1].to_pandas + + # Validate columns and ensure equal column content in both tables + self._validate_common_dataframe_columns(expected_common_columns, first_df, second_df) + + # Assign "KH" column to the dataframe with missing column + if "OP/SH" in first_df.columns and "KH" in second_df.columns: + first_df["KH"] = second_df["KH"] + return WellCompletionDataConverter(first_df).create_data() + if "OP/SH" in second_df.columns and "KH" in first_df.columns: + second_df["KH"] = first_df["KH"] + return WellCompletionDataConverter(second_df).create_data() + + raise ValueError('Expected columns "OP/SH" and "KH" not found in tables') + + def _validate_common_dataframe_columns( + self, common_column_names: Set[str], first_df: pd.DataFrame, second_df: pd.DataFrame + ) -> None: + """ + Validates that the two dataframes contains same common columns and that the columns have the same content, + raises value error if not matching. + """ + # Ensure expected columns are present + if not common_column_names.issubset(first_df.columns): + raise ValueError(f"Expected columns of first table: {common_column_names} - got: {first_df.columns}") + if not common_column_names.issubset(second_df.columns): + raise ValueError(f"Expected columns of second table: {common_column_names} - got: {second_df.columns}") + + # Verify equal columns in both tables + for column_name in common_column_names: + if not (first_df[column_name] == second_df[column_name]).all(): + raise ValueError(f'Expected equal column content, "{column_name}", in first and second dataframe') + + +class WellCompletionDataConverter: + """ + Class for converter into WellCompletionData type from a pandas dataframe with well completions data + + Accessor retrieves well completions data from Sumo as table data. This converter class handles + the pandas dataframe and provides a data structure for API to consume. + """ + + def __init__(self, well_completions_df: pd.DataFrame) -> None: + # NOTE: Which level of verification? + # - Only columns names? + # - Verify dtype of columns? + # - Verify dimension of columns - only 2D df? + + # Based on realization filtering in Accessor, the "REAL" column is optional - not expected + expected_columns = set(["WELL", "DATE", "ZONE", "OP/SH", "KH"]) + + if not expected_columns.issubset(well_completions_df.columns): + raise ValueError(f"Expected columns: {expected_columns} - got: {well_completions_df.columns}") + + self._well_completions_df = well_completions_df + + # NOTE: Metadata should be provided by Sumo? + # _kh_unit = ( + # kh_metadata.unit + # if kh_metadata is not None and kh_metadata.unit is not None + # else "" + # ) + self._kh_unit = "mDm" # NOTE: How to find metadata? + self._kh_decimal_places = 2 + self._datemap = {dte: i for i, dte in enumerate(sorted(self._well_completions_df["DATE"].unique()))} + self._zones = list(sorted(self._well_completions_df["ZONE"].unique())) + + self._well_completions_df["TIMESTEP"] = self._well_completions_df["DATE"].map(self._datemap) + + # NOTE: + # - How to handle well attributes? Should be provided by Sumo? + # - How to handle theme colors? + self._well_attributes: Dict[ + str, Dict[str, WellCompletionsAttributeType] + ] = {} # Each well has dict of attributes + self._theme_colors = ["#6EA35A", "#EDAF4C", "#CA413D"] # Hard coded + + def _dummy_stratigraphy(self) -> List[WellCompletionsZone]: + """ + Returns a default stratigraphy for TESTING, should be provided by Sumo + """ + return [ + WellCompletionsZone( + name="TopVolantis_BaseVolantis", + color="#6EA35A", + subzones=[ + WellCompletionsZone(name="Valysar", color="#6EA35A"), + WellCompletionsZone(name="Therys", color="#EDAF4C"), + WellCompletionsZone(name="Volon", color="#CA413D"), + ], + ), + ] + + def create_data(self) -> WellCompletionsData: + """Creates well completions dataset for front-end""" + + return WellCompletionsData( + version="1.1.0", + units=WellCompletionsUnits( + kh=WellCompletionsUnitInfo(unit=self._kh_unit, decimalPlaces=self._kh_decimal_places) + ), + stratigraphy=self._extract_stratigraphy(self._dummy_stratigraphy(), self._zones), + timeSteps=[pd.to_datetime(str(dte)).strftime("%Y-%m-%d") for dte in self._datemap.keys()], + wells=self._extract_wells(), + ) + + def _extract_wells(self) -> List[WellCompletionsWell]: + """Generates the wells part of the dataset to front-end""" + # Optional "REAL" column, i.e. no column implies only one realization + no_real = self._well_completions_df["REAL"].nunique() if "REAL" in self._well_completions_df.columns else 1 + + well_list = [] + for well_name, well_group in self._well_completions_df.groupby("WELL"): + well_data = self._extract_well(well_group, well_name, no_real) + well_data.attributes = self._well_attributes[well_name] if well_name in self._well_attributes else {} + well_list.append(well_data) + return well_list + + def _extract_well(self, well_group: pd.DataFrame, well_name: str, no_real: int) -> WellCompletionsWell: + """Extract completions events and kh values for a single well""" + well: WellCompletionsWell = WellCompletionsWell(name=well_name, attributes={}, completions={}) + + completions: Dict[str, Completions] = {} + for (zone, timestep), group_df in well_group.groupby(["ZONE", "TIMESTEP"]): + data = group_df["OP/SH"].value_counts() + if zone not in completions: + completions[zone] = Completions(t=[], open=[], shut=[], kh_mean=[], kh_min=[], kh_max=[]) + + zone_completions = completions[zone] + zone_completions.t.append(int(timestep)) + zone_completions.open.append(float(data["OPEN"] / no_real if "OPEN" in data else 0)) + zone_completions.shut.append(float(data["SHUT"] / no_real if "SHUT" in data else 0)) + zone_completions.kh_mean.append(round(float(group_df["KH"].mean()), 2)) + zone_completions.kh_min.append(round(float(group_df["KH"].min()), 2)) + zone_completions.kh_max.append(round(float(group_df["KH"].max()), 2)) + + well.completions = completions + return well + + def _extract_stratigraphy( + self, stratigraphy: Optional[List[WellCompletionsZone]], zones: List[str] + ) -> List[WellCompletionsZone]: + """Returns the stratigraphy part of the dataset to front-end""" + color_iterator = itertools.cycle(self._theme_colors) + + # If no stratigraphy file is found then the stratigraphy is + # created from the unique zones in the well completions data input. + # They will then probably not come in the correct order. + if stratigraphy is None: + return [WellCompletionsZone(name=zone, color=next(color_iterator)) for zone in zones] + + # If stratigraphy is not None the following is done: + stratigraphy, remaining_valid_zones = self._filter_valid_nodes(stratigraphy, zones) + + if remaining_valid_zones: + raise ValueError( + "The following zones are defined in the well completions data, " + f"but not in the stratigraphy: {remaining_valid_zones}" + ) + + return self._add_colors_to_stratigraphy(stratigraphy, color_iterator) + + def _add_colors_to_stratigraphy( + self, + stratigraphy: List[WellCompletionsZone], + color_iterator: Iterator, + zone_color_mapping: Optional[Dict[str, str]] = None, + ) -> List[WellCompletionsZone]: + """Add colors to the stratigraphy tree. The function will recursively parse the tree. + + There are tree sources of color: + 1. The color is given in the stratigraphy list, in which case nothing is done to the node + 2. The color is the optional the zone->color map + 3. If none of the above applies, the color will be taken from the theme color iterable for \ + the leaves. For other levels, a dummy color grey is used + """ + for zone in stratigraphy: + if zone.color == "": + if zone_color_mapping is not None and zone.name in zone_color_mapping: + zone.color = zone_color_mapping[zone.name] + elif zone.subzones is None: + zone = next(color_iterator) # theme colors only applied on leaves + else: + zone.color = "#808080" # grey + if zone.subzones is not None: + zone.subzones = self._add_colors_to_stratigraphy( + zone.subzones, + color_iterator, + zone_color_mapping=zone_color_mapping, + ) + return stratigraphy + + def _filter_valid_nodes( + self, stratigraphy: List[WellCompletionsZone], valid_zone_names: List[str] + ) -> Tuple[List[WellCompletionsZone], List[str]]: + """Returns the stratigraphy tree with only valid nodes. + A node is considered valid if it self or one of it's subzones are in the + valid zone names list (passed from the lyr file) + + The function recursively parses the tree to add valid nodes. + """ + + output = [] + remaining_valid_zones = valid_zone_names + for zone in stratigraphy: + if zone.subzones is not None: + zone.subzones, remaining_valid_zones = self._filter_valid_nodes(zone.subzones, remaining_valid_zones) + if zone.name in remaining_valid_zones: + output.append(zone) + remaining_valid_zones = [ + elm for elm in remaining_valid_zones if elm != zone.name + ] # remove zone name from valid zones if it is found in the stratigraphy + elif zone.subzones is not None: + output.append(zone) + + return output, remaining_valid_zones diff --git a/backend/src/services/sumo_access/well_completions_types.py b/backend/src/services/sumo_access/well_completions_types.py new file mode 100644 index 000000000..4e23be989 --- /dev/null +++ b/backend/src/services/sumo_access/well_completions_types.py @@ -0,0 +1,45 @@ +from typing import Dict, List, Optional, Union +from pydantic import BaseModel + + +WellCompletionsAttributeType = Union[str, int, bool] + + +class Completions(BaseModel): + t: List[int] + open: List[float] + shut: List[float] + kh_mean: List[float] + kh_min: List[float] + kh_max: List[float] + + +class WellCompletionsWell(BaseModel): + name: str + attributes: Dict[str, WellCompletionsAttributeType] + completions: Dict[str, Completions] + + +class WellCompletionsZone(BaseModel): + name: str + color: str + subzones: Optional[List["WellCompletionsZone"]] = None + + +class WellCompletionsUnitInfo(BaseModel): + unit: str + decimalPlaces: int + + +class WellCompletionsUnits(BaseModel): + kh: WellCompletionsUnitInfo + + +class WellCompletionsData(BaseModel): + """Type definition for well completions data""" + + version: str + units: WellCompletionsUnits + stratigraphy: List[WellCompletionsZone] + timeSteps: List[str] + wells: List[WellCompletionsWell] diff --git a/backend/src/services/types/well_completion_types.py b/backend/src/services/types/well_completion_types.py deleted file mode 100644 index 0df76ca7a..000000000 --- a/backend/src/services/types/well_completion_types.py +++ /dev/null @@ -1,48 +0,0 @@ -from pydantic import BaseModel -from typing import Dict, List, Optional, Union - - -WellCompletionAttributeType = Union[str, int, bool] - - -class Completions(BaseModel): - t: List[int] - open: List[float] - shut: List[float] - kh_mean: List[float] - kh_min: List[float] - kh_max: List[float] - - -class WellCompletionWellInfo(BaseModel): - name: str - attributes: Dict[str, WellCompletionAttributeType] - - -class WellCompletionWell(WellCompletionWellInfo): - completions: Dict[str, Completions] - - -class WellCompletionZone(BaseModel): - name: str - color: str - subzones: Optional[List["WellCompletionZone"]] = None - - -class WellCompletionUnitInfo(BaseModel): - unit: str - decimalPlaces: int - - -class WellCompletionUnits(BaseModel): - kh: WellCompletionUnitInfo - - -class WellCompletionDataSet(BaseModel): - """Type definition for well completion data set""" - - version: str - units: WellCompletionUnits - stratigraphy: List[WellCompletionZone] - timeSteps: List[str] - wells: List[WellCompletionWell] diff --git a/backend/src/services/utils/well_completion_utils.py b/backend/src/services/utils/well_completion_utils.py deleted file mode 100644 index 7f51e26e4..000000000 --- a/backend/src/services/utils/well_completion_utils.py +++ /dev/null @@ -1,187 +0,0 @@ -import itertools -from typing import Dict, Iterator, List, Optional, Tuple - -import pandas as pd - -from src.services.types.well_completion_types import ( - Completions, - WellCompletionAttributeType, - WellCompletionWell, - WellCompletionDataSet, - WellCompletionZone, - WellCompletionUnitInfo, - WellCompletionUnits, -) - - -class WellCompletionDataModel: - def __init__(self, well_completion_data: pd.DataFrame) -> None: - # NOTE: Which level of verification? - # - Only columns names? - # - Verify dtype of columns? - # - Verify dimension of columns - only 2D df? - - expected_columns = set(["WELL", "DATE", "ZONE", "REAL", "OP/SH", "KH"]) - if expected_columns != set(well_completion_data.columns): - raise ValueError(f"Expected columns: {expected_columns} - got: {well_completion_data.columns}") - - self._well_completion_df = well_completion_data - - # NOTE: Metadata should be provided by Sumo? - # _kh_unit = ( - # kh_metadata.unit - # if kh_metadata is not None and kh_metadata.unit is not None - # else "" - # ) - self._kh_unit = "mDm" # NOTE: How to find metadata? - self._kh_decimal_places = 2 - self._datemap = {dte: i for i, dte in enumerate(sorted(self._well_completion_df["DATE"].unique()))} - self._zones = list(sorted(self._well_completion_df["ZONE"].unique())) - - self._well_completion_df["TIMESTEP"] = self._well_completion_df["DATE"].map(self._datemap) - - # NOTE: - # - How to handle well attributes? Should be provided by Sumo? - # - How to handle theme colors? - self._well_attributes: Dict[ - str, Dict[str, WellCompletionAttributeType] - ] = {} # Each well has dict of attributes - self._theme_colors = ["#6EA35A", "#EDAF4C", "#CA413D"] # Hard coded - - def _dummy_stratigraphy(self) -> List[WellCompletionZone]: - """ - Returns a default stratigraphy for TESTING, should be provided by Sumo - """ - return [ - WellCompletionZone( - name="TopVolantis_BaseVolantis", - color="#6EA35A", - subzones=[ - WellCompletionZone(name="Valysar", color="#6EA35A"), - WellCompletionZone(name="Therys", color="#EDAF4C"), - WellCompletionZone(name="Volon", color="#CA413D"), - ], - ), - ] - - def create_well_completion_dataset(self) -> WellCompletionDataSet: - """Creates well completion dataset for front-end""" - - return WellCompletionDataSet( - version="1.1.0", - units=WellCompletionUnits( - kh=WellCompletionUnitInfo(unit=self._kh_unit, decimalPlaces=self._kh_decimal_places) - ), - stratigraphy=self._extract_stratigraphy(self._dummy_stratigraphy(), self._zones), - timeSteps=[pd.to_datetime(str(dte)).strftime("%Y-%m-%d") for dte in self._datemap.keys()], - wells=self._extract_wells(), - ) - - def _extract_wells(self) -> List[WellCompletionWell]: - """Generates the wells part of the dataset to front-end""" - well_list = [] - no_real = self._well_completion_df["REAL"].nunique() - for well_name, well_group in self._well_completion_df.groupby("WELL"): - well_data = self._extract_well(well_group, well_name, no_real) - well_data.attributes = self._well_attributes[well_name] if well_name in self._well_attributes else {} - well_list.append(well_data) - return well_list - - def _extract_well(self, well_group: pd.DataFrame, well_name: str, no_real: int) -> WellCompletionWell: - """Extract completion events and kh values for a single well""" - well: WellCompletionWell = WellCompletionWell(name=well_name, attributes={}, completions={}) - - completions: Dict[str, Completions] = {} - for (zone, timestep), group_df in well_group.groupby(["ZONE", "TIMESTEP"]): - data = group_df["OP/SH"].value_counts() - if zone not in completions: - completions[zone] = Completions(t=[], open=[], shut=[], kh_mean=[], kh_min=[], kh_max=[]) - - zone_completions = completions[zone] - zone_completions.t.append(int(timestep)) - zone_completions.open.append(float(data["OPEN"] / no_real if "OPEN" in data else 0)) - zone_completions.shut.append(float(data["SHUT"] / no_real if "SHUT" in data else 0)) - zone_completions.kh_mean.append(round(float(group_df["KH"].mean()), 2)) - zone_completions.kh_min.append(round(float(group_df["KH"].min()), 2)) - zone_completions.kh_max.append(round(float(group_df["KH"].max()), 2)) - - well.completions = completions - return well - - def _extract_stratigraphy( - self, stratigraphy: Optional[List[WellCompletionZone]], zones: List[str] - ) -> List[WellCompletionZone]: - """Returns the stratigraphy part of the dataset to front-end""" - color_iterator = itertools.cycle(self._theme_colors) - - # If no stratigraphy file is found then the stratigraphy is - # created from the unique zones in the wellcompletiondata input. - # They will then probably not come in the correct order. - if stratigraphy is None: - return [WellCompletionZone(name=zone, color=next(color_iterator)) for zone in zones] - - # If stratigraphy is not None the following is done: - stratigraphy, remaining_valid_zones = self._filter_valid_nodes(stratigraphy, zones) - - if remaining_valid_zones: - raise ValueError( - "The following zones are defined in the well completion data, " - f"but not in the stratigraphy: {remaining_valid_zones}" - ) - - return self._add_colors_to_stratigraphy(stratigraphy, color_iterator) - - def _add_colors_to_stratigraphy( - self, - stratigraphy: List[WellCompletionZone], - color_iterator: Iterator, - zone_color_mapping: Optional[Dict[str, str]] = None, - ) -> List[WellCompletionZone]: - """Add colors to the stratigraphy tree. The function will recursively parse the tree. - - There are tree sources of color: - 1. The color is given in the stratigraphy list, in which case nothing is done to the node - 2. The color is the optional the zone->color map - 3. If none of the above applies, the color will be taken from the theme color iterable for \ - the leaves. For other levels, a dummy color grey is used - """ - for zone in stratigraphy: - if zone.color == "": - if zone_color_mapping is not None and zone.name in zone_color_mapping: - zone.color = zone_color_mapping[zone.name] - elif zone.subzones is None: - zone = next(color_iterator) # theme colors only applied on leaves - else: - zone.color = "#808080" # grey - if zone.subzones is not None: - zone.subzones = self._add_colors_to_stratigraphy( - zone.subzones, - color_iterator, - zone_color_mapping=zone_color_mapping, - ) - return stratigraphy - - def _filter_valid_nodes( - self, stratigraphy: List[WellCompletionZone], valid_zone_names: List[str] - ) -> Tuple[List[WellCompletionZone], List[str]]: - """Returns the stratigraphy tree with only valid nodes. - A node is considered valid if it self or one of it's subzones are in the - valid zone names list (passed from the lyr file) - - The function recursively parses the tree to add valid nodes. - """ - - output = [] - remaining_valid_zones = valid_zone_names - for zone in stratigraphy: - if zone.subzones is not None: - zone.subzones, remaining_valid_zones = self._filter_valid_nodes(zone.subzones, remaining_valid_zones) - if zone.name in remaining_valid_zones: - output.append(zone) - remaining_valid_zones = [ - elm for elm in remaining_valid_zones if elm != zone.name - ] # remove zone name from valid zones if it is found in the stratigraphy - elif zone.subzones is not None: - output.append(zone) - - return output, remaining_valid_zones diff --git a/frontend/src/api/ApiService.ts b/frontend/src/api/ApiService.ts index 44c7fe0f9..d0fab3543 100644 --- a/frontend/src/api/ApiService.ts +++ b/frontend/src/api/ApiService.ts @@ -15,7 +15,7 @@ import { SurfaceService } from './services/SurfaceService'; import { SurfacePolygonsService } from './services/SurfacePolygonsService'; import { TimeseriesService } from './services/TimeseriesService'; import { WellService } from './services/WellService'; -import { WellCompletionService } from './services/WellCompletionService'; +import { WellCompletionsService } from './services/WellCompletionsService'; type HttpRequestConstructor = new (config: OpenAPIConfig) => BaseHttpRequest; @@ -31,7 +31,7 @@ export class ApiService { public readonly surfacePolygons: SurfacePolygonsService; public readonly timeseries: TimeseriesService; public readonly well: WellService; - public readonly wellCompletion: WellCompletionService; + public readonly wellCompletions: WellCompletionsService; public readonly request: BaseHttpRequest; @@ -58,7 +58,7 @@ export class ApiService { this.surfacePolygons = new SurfacePolygonsService(this.request); this.timeseries = new TimeseriesService(this.request); this.well = new WellService(this.request); - this.wellCompletion = new WellCompletionService(this.request); + this.wellCompletions = new WellCompletionsService(this.request); } } diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 1923f75da..174ba475c 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -47,12 +47,11 @@ export type { VectorStatisticData as VectorStatisticData_api } from './models/Ve export type { VectorStatisticSensitivityData as VectorStatisticSensitivityData_api } from './models/VectorStatisticSensitivityData'; export type { WellBoreHeader as WellBoreHeader_api } from './models/WellBoreHeader'; export type { WellBoreTrajectory as WellBoreTrajectory_api } from './models/WellBoreTrajectory'; -export type { WellCompletionData as WellCompletionData_api } from './models/WellCompletionData'; -export type { WellCompletionDataSet as WellCompletionDataSet_api } from './models/WellCompletionDataSet'; -export type { WellCompletionUnitInfo as WellCompletionUnitInfo_api } from './models/WellCompletionUnitInfo'; -export type { WellCompletionUnits as WellCompletionUnits_api } from './models/WellCompletionUnits'; -export type { WellCompletionWell as WellCompletionWell_api } from './models/WellCompletionWell'; -export type { WellCompletionZone as WellCompletionZone_api } from './models/WellCompletionZone'; +export type { WellCompletionsData as WellCompletionsData_api } from './models/WellCompletionsData'; +export type { WellCompletionsUnitInfo as WellCompletionsUnitInfo_api } from './models/WellCompletionsUnitInfo'; +export type { WellCompletionsUnits as WellCompletionsUnits_api } from './models/WellCompletionsUnits'; +export type { WellCompletionsWell as WellCompletionsWell_api } from './models/WellCompletionsWell'; +export type { WellCompletionsZone as WellCompletionsZone_api } from './models/WellCompletionsZone'; export { DefaultService } from './services/DefaultService'; export { ExploreService } from './services/ExploreService'; @@ -64,4 +63,4 @@ export { SurfaceService } from './services/SurfaceService'; export { SurfacePolygonsService } from './services/SurfacePolygonsService'; export { TimeseriesService } from './services/TimeseriesService'; export { WellService } from './services/WellService'; -export { WellCompletionService } from './services/WellCompletionService'; +export { WellCompletionsService } from './services/WellCompletionsService'; diff --git a/frontend/src/api/models/WellCompletionData.ts b/frontend/src/api/models/WellCompletionData.ts deleted file mode 100644 index 9d9d24caf..000000000 --- a/frontend/src/api/models/WellCompletionData.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { WellCompletionDataSet } from './WellCompletionDataSet'; - -export type WellCompletionData = { - json_data: WellCompletionDataSet; -}; - diff --git a/frontend/src/api/models/WellCompletionDataSet.ts b/frontend/src/api/models/WellCompletionDataSet.ts deleted file mode 100644 index add799c58..000000000 --- a/frontend/src/api/models/WellCompletionDataSet.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { WellCompletionUnits } from './WellCompletionUnits'; -import type { WellCompletionWell } from './WellCompletionWell'; -import type { WellCompletionZone } from './WellCompletionZone'; - -/** - * Type definition for well completion data set - */ -export type WellCompletionDataSet = { - version: string; - units: WellCompletionUnits; - stratigraphy: Array; - timeSteps: Array; - wells: Array; -}; - diff --git a/frontend/src/api/models/WellCompletionUnits.ts b/frontend/src/api/models/WellCompletionUnits.ts deleted file mode 100644 index 9aa8c07cd..000000000 --- a/frontend/src/api/models/WellCompletionUnits.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { WellCompletionUnitInfo } from './WellCompletionUnitInfo'; - -export type WellCompletionUnits = { - kh: WellCompletionUnitInfo; -}; - diff --git a/frontend/src/api/models/WellCompletionsData.ts b/frontend/src/api/models/WellCompletionsData.ts new file mode 100644 index 000000000..272ec94d2 --- /dev/null +++ b/frontend/src/api/models/WellCompletionsData.ts @@ -0,0 +1,19 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { WellCompletionsUnits } from './WellCompletionsUnits'; +import type { WellCompletionsWell } from './WellCompletionsWell'; +import type { WellCompletionsZone } from './WellCompletionsZone'; + +/** + * Type definition for well completions data + */ +export type WellCompletionsData = { + version: string; + units: WellCompletionsUnits; + stratigraphy: Array; + timeSteps: Array; + wells: Array; +}; + diff --git a/frontend/src/api/models/WellCompletionUnitInfo.ts b/frontend/src/api/models/WellCompletionsUnitInfo.ts similarity index 74% rename from frontend/src/api/models/WellCompletionUnitInfo.ts rename to frontend/src/api/models/WellCompletionsUnitInfo.ts index b3b6e2512..8eafff910 100644 --- a/frontend/src/api/models/WellCompletionUnitInfo.ts +++ b/frontend/src/api/models/WellCompletionsUnitInfo.ts @@ -2,7 +2,7 @@ /* tslint:disable */ /* eslint-disable */ -export type WellCompletionUnitInfo = { +export type WellCompletionsUnitInfo = { unit: string; decimalPlaces: number; }; diff --git a/frontend/src/api/models/WellCompletionsUnits.ts b/frontend/src/api/models/WellCompletionsUnits.ts new file mode 100644 index 000000000..26ea69dfa --- /dev/null +++ b/frontend/src/api/models/WellCompletionsUnits.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { WellCompletionsUnitInfo } from './WellCompletionsUnitInfo'; + +export type WellCompletionsUnits = { + kh: WellCompletionsUnitInfo; +}; + diff --git a/frontend/src/api/models/WellCompletionWell.ts b/frontend/src/api/models/WellCompletionsWell.ts similarity index 87% rename from frontend/src/api/models/WellCompletionWell.ts rename to frontend/src/api/models/WellCompletionsWell.ts index d0ee31077..d9afb14c2 100644 --- a/frontend/src/api/models/WellCompletionWell.ts +++ b/frontend/src/api/models/WellCompletionsWell.ts @@ -4,7 +4,7 @@ import type { Completions } from './Completions'; -export type WellCompletionWell = { +export type WellCompletionsWell = { name: string; attributes: Record; completions: Record; diff --git a/frontend/src/api/models/WellCompletionZone.ts b/frontend/src/api/models/WellCompletionsZone.ts similarity index 56% rename from frontend/src/api/models/WellCompletionZone.ts rename to frontend/src/api/models/WellCompletionsZone.ts index 15202c456..02047e005 100644 --- a/frontend/src/api/models/WellCompletionZone.ts +++ b/frontend/src/api/models/WellCompletionsZone.ts @@ -2,9 +2,9 @@ /* tslint:disable */ /* eslint-disable */ -export type WellCompletionZone = { +export type WellCompletionsZone = { name: string; color: string; - subzones: (Array | null); + subzones: (Array | null); }; diff --git a/frontend/src/api/services/WellCompletionService.ts b/frontend/src/api/services/WellCompletionsService.ts similarity index 72% rename from frontend/src/api/services/WellCompletionService.ts rename to frontend/src/api/services/WellCompletionsService.ts index 99c919b59..23bf1ecf6 100644 --- a/frontend/src/api/services/WellCompletionService.ts +++ b/frontend/src/api/services/WellCompletionsService.ts @@ -1,31 +1,31 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { WellCompletionData } from '../models/WellCompletionData'; +import type { WellCompletionsData } from '../models/WellCompletionsData'; import type { CancelablePromise } from '../core/CancelablePromise'; import type { BaseHttpRequest } from '../core/BaseHttpRequest'; -export class WellCompletionService { +export class WellCompletionsService { constructor(public readonly httpRequest: BaseHttpRequest) {} /** - * Get Well Completion Data + * Get Well Completions Data * @param caseUuid Sumo case uuid * @param ensembleName Ensemble name * @param realization Optional realization to include. If not specified, all realizations will be returned. - * @returns WellCompletionData Successful Response + * @returns WellCompletionsData Successful Response * @throws ApiError */ - public getWellCompletionData( + public getWellCompletionsData( caseUuid: string, ensembleName: string, realization?: (number | null), - ): CancelablePromise { + ): CancelablePromise { return this.httpRequest.request({ method: 'GET', - url: '/well_completion/well_completion_data/', + url: '/well_completions/well_completions_data/', query: { 'case_uuid': caseUuid, 'ensemble_name': ensembleName, diff --git a/frontend/src/modules/WellCompletion/loadModule.tsx b/frontend/src/modules/WellCompletions/loadModule.tsx similarity index 81% rename from frontend/src/modules/WellCompletion/loadModule.tsx rename to frontend/src/modules/WellCompletions/loadModule.tsx index 715bc51e8..ce61ed7ad 100644 --- a/frontend/src/modules/WellCompletion/loadModule.tsx +++ b/frontend/src/modules/WellCompletions/loadModule.tsx @@ -10,7 +10,7 @@ const initialState: State = { plotData: null, }; -const module = ModuleRegistry.initModule("WellCompletion", initialState); +const module = ModuleRegistry.initModule("WellCompletions", initialState); module.viewFC = view; module.settingsFC = settings; diff --git a/frontend/src/modules/WellCompletion/queryHooks.tsx b/frontend/src/modules/WellCompletions/queryHooks.tsx similarity index 58% rename from frontend/src/modules/WellCompletion/queryHooks.tsx rename to frontend/src/modules/WellCompletions/queryHooks.tsx index dbb4915a7..77e3689d0 100644 --- a/frontend/src/modules/WellCompletion/queryHooks.tsx +++ b/frontend/src/modules/WellCompletions/queryHooks.tsx @@ -1,19 +1,19 @@ -import { WellCompletionData_api } from "@api"; +import { WellCompletionsData_api } from "@api"; import { apiService } from "@framework/ApiService"; import { UseQueryResult, useQuery } from "@tanstack/react-query"; const STALE_TIME = 60 * 1000; const CACHE_TIME = 60 * 1000; -export function useWellCompletionQuery( +export function useWellCompletionsQuery( caseUuid: string | undefined, ensembleName: string | undefined, realizationNumber: number | undefined -): UseQueryResult { +): UseQueryResult { return useQuery({ - queryKey: ["getWellCompletion", caseUuid, ensembleName, realizationNumber], + queryKey: ["getWellCompletions", caseUuid, ensembleName, realizationNumber], queryFn: () => - apiService.wellCompletion.getWellCompletionData(caseUuid ?? "", ensembleName ?? "", realizationNumber), + apiService.wellCompletions.getWellCompletionsData(caseUuid ?? "", ensembleName ?? "", realizationNumber), staleTime: STALE_TIME, cacheTime: CACHE_TIME, enabled: caseUuid && ensembleName ? true : false, diff --git a/frontend/src/modules/WellCompletion/registerModule.ts b/frontend/src/modules/WellCompletions/registerModule.ts similarity index 78% rename from frontend/src/modules/WellCompletion/registerModule.ts rename to frontend/src/modules/WellCompletions/registerModule.ts index c0c559d90..ed66141d7 100644 --- a/frontend/src/modules/WellCompletion/registerModule.ts +++ b/frontend/src/modules/WellCompletions/registerModule.ts @@ -2,4 +2,4 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; import { State } from "./state"; -ModuleRegistry.registerModule({ moduleName: "WellCompletion", defaultTitle: "Well Completion" }); +ModuleRegistry.registerModule({ moduleName: "WellCompletions", defaultTitle: "Well Completions" }); diff --git a/frontend/src/modules/WellCompletion/settings.tsx b/frontend/src/modules/WellCompletions/settings.tsx similarity index 95% rename from frontend/src/modules/WellCompletion/settings.tsx rename to frontend/src/modules/WellCompletions/settings.tsx index e4839f37c..c57ca179c 100644 --- a/frontend/src/modules/WellCompletion/settings.tsx +++ b/frontend/src/modules/WellCompletions/settings.tsx @@ -18,7 +18,7 @@ import { resolveClassNames } from "@lib/utils/resolveClassNames"; import { isEqual } from "lodash"; -import { useWellCompletionQuery } from "./queryHooks"; +import { useWellCompletionsQuery } from "./queryHooks"; import { DataLoadingStatus, State } from "./state"; import { TimeAggregationType, WellCompletionsDataAccessor } from "./utils/wellCompletionsDataAccessor"; @@ -62,7 +62,7 @@ export const settings = ({ moduleContext, workbenchSession, workbenchServices }: setSelectedEnsembleIdent(computedEnsembleIdent, acceptInvalidState); } - const wellCompletionQuery = useWellCompletionQuery( + const wellCompletionsQuery = useWellCompletionsQuery( selectedEnsembleIdent?.getCaseUuid(), selectedEnsembleIdent?.getEnsembleName(), realizationSelection === RealizationSelection.Single ? selectedRealizationNumber : undefined @@ -73,14 +73,14 @@ export const settings = ({ moduleContext, workbenchSession, workbenchServices }: React.useEffect( function handleNewQueryData() { - if (!wellCompletionQuery.data) { + if (!wellCompletionsQuery.data) { wellCompletionsDataAccessor.current.clearWellCompletionsData(); setAvailableTimeSteps(null); setPlotData(null); return; } - wellCompletionsDataAccessor.current.parseWellCompletionsData(wellCompletionQuery.data); + wellCompletionsDataAccessor.current.parseWellCompletionsData(wellCompletionsQuery.data); // Update available time steps const allTimeSteps = wellCompletionsDataAccessor.current.getTimeSteps(); @@ -116,20 +116,20 @@ export const settings = ({ moduleContext, workbenchSession, workbenchServices }: } createAndSetPlotData(allTimeSteps, timeStepIndex, selectedTimeStepOptions.timeAggregationType); }, - [wellCompletionQuery.data, selectedTimeStepOptions] + [wellCompletionsQuery.data, selectedTimeStepOptions] ); React.useEffect( function handleQueryStateChange() { - if (wellCompletionQuery.status === "loading" && wellCompletionQuery.fetchStatus === "fetching") { + if (wellCompletionsQuery.status === "loading" && wellCompletionsQuery.fetchStatus === "fetching") { setDataLoadingStatus(DataLoadingStatus.Loading); - } else if (wellCompletionQuery.status === "error") { + } else if (wellCompletionsQuery.status === "error") { setDataLoadingStatus(DataLoadingStatus.Error); - } else if (wellCompletionQuery.status === "success") { + } else if (wellCompletionsQuery.status === "success") { setDataLoadingStatus(DataLoadingStatus.Idle); } }, - [wellCompletionQuery.status, wellCompletionQuery.fetchStatus] + [wellCompletionsQuery.status, wellCompletionsQuery.fetchStatus] ); function createAndSetPlotData( @@ -279,14 +279,14 @@ export const settings = ({ moduleContext, workbenchSession, workbenchServices }: /> {
- {wellCompletionQuery.isError + {wellCompletionsQuery.isError ? "Current ensemble does not contain well completions data" : ""}
} -
+
- + + - + +