Skip to content

Commit

Permalink
Merge pull request #116 from dangunter/branding
Browse files Browse the repository at this point in the history
Add support for branding/re-theming
  • Loading branch information
MichaelPesce authored Jun 21, 2024
2 parents 4380106 + ea24718 commit 4ebfaa3
Show file tree
Hide file tree
Showing 51 changed files with 3,315 additions and 1,402 deletions.
44 changes: 42 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# watertap-ui

This repository is for work on the user interface (UI) for the WaterTAP library. The UI installer can be downloaded from our homepage at: https://watertap-org.github.io/
This repository is for work on the user interface (UI) for the WaterTAP library.
The UI installer can be downloaded from our homepage at: https://watertap-org.github.io/

## Getting started (developer)

Expand Down Expand Up @@ -101,10 +102,49 @@ npm run electron-start

# Running developer tests

There are three sets of tests that can be run: Python tests, JavaScript unit tests, and JavaScript end-to-end tests.

## Running Python tests

To run the Python tests, make sure you have the appropriate version of watertap in your conda env. Then from the repository root directory run:

`pytest backend/tests`
To run:
```shell
cd backend
pytest
```

This will take several minutes since one set of tests solves the UI flowsheets.

## Running JS unit tests

The unit tests are written using the [testing-library](https://testing-library.com/) package.

To run:
```shell
cd electron/ui
npm run test
```

Hit 'a' for "run all tests" if you want to run tests regardless of what changed.

## Running JS E2E tests

The end-to-end tests are written in Cypress.

Before running the tests, start the back-end server, in another process (or shell):
```shell
cd electron/ui
npm run start-backend
```

Then, to run the tests:
```shell
cd electron/ui # if needed
npx cypress run
```

If there are errors, screenshots and videos can be found (in subdirectories named for each test) under `electron/ui/cypress/screenshots` and `electron/ui/cypress/videos`.

# Building production Electron app

Expand Down
36 changes: 22 additions & 14 deletions backend/app/internal/flowsheet_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@

if sys.version_info < (3, 10):
from importlib_resources import files
importlib_old = True
else:
from importlib.resources import files
try:
from importlib import metadata
except ImportError:
from importlib_metadata import metadata
importlib_old = False
from importlib import metadata
from pathlib import Path
import time
from types import ModuleType
Expand All @@ -30,7 +29,7 @@
import idaes.logger as idaeslog

_log = idaeslog.getLogger(__name__)
# _log.setLevel(logging.DEBUG)
_log.setLevel(idaeslog.DEBUG)
VERSION = 3


Expand Down Expand Up @@ -79,7 +78,7 @@ def __init__(self, **kwargs):
self.startup_time = time.time()

# Add custom flowsheets path to the system path
self.custom_flowsheets_path = Path.home() / ".watertap" / "custom_flowsheets"
self.custom_flowsheets_path = self.app_settings.data_basedir / "custom_flowsheets"
sys.path.append(str(self.custom_flowsheets_path))

for package in self.app_settings.packages:
Expand Down Expand Up @@ -425,17 +424,26 @@ def _get_flowsheet_interfaces(
Returns:
Mapping with keys the module names and values FlowsheetInterface objects
"""
group_name = package_name + ".flowsheets"
try:
entry_points = metadata.entry_points()[group_name]
except KeyError:
_log.error(f"No interfaces found for package: {package_name}")
# Find the entry points for the package
eps = metadata.entry_points
project_pkg = package_name + ".flowsheets"
if importlib_old:
try:
pkg_eps = eps()[project_pkg] # old Python <= 3.9
except KeyError:
pkg_eps = []
else:
pkg_eps = eps(group=project_pkg) # new Python >= 3.10

# If none are found print an erorr and abort
if not pkg_eps:
_log.error(f"No entry points found for package {project_pkg}")
return {}

interfaces = {}
_log.debug(f"Loading {len(list(entry_points))} entry points")
for ep in entry_points:
_log.debug(f"ep = {ep}")
_log.debug(f"Loading {len(list(pkg_eps))} entry points")
for ep in pkg_eps:
_log.debug(f"flowsheet-entry-point={ep}")
module_name = ep.value
try:
module = ep.load()
Expand Down
93 changes: 74 additions & 19 deletions backend/app/internal/settings.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,103 @@
"""
Configuration for the backend
"""

import os
from pathlib import Path
import logging
from typing import List, Union
from pydantic import validator, field_validator
from pydantic import field_validator
from pydantic_settings import BaseSettings

__author__ = "Dan Gunter"

_log = logging.getLogger(__name__)
h = logging.StreamHandler()
h.setFormatter(logging.Formatter("[%(levelname)s] %(name)s - %(message)s"))
_log.addHandler(h)
_log.setLevel(logging.WARNING)


class Deployment:
"""Values related to the deployment context of the UI,
e.g., NAWI WaterTAP, PROMMIS, or IDAES.
"""
# env var for project name
PROJECT_ENV = "PSE_PROJECT"
# projects and their associated packages
PROJ = {
"nawi": ("watertap",),
"idaes": ("idaes",),
"prommis": ("prommis",)
}
DEFAULT_PROJ = "nawi"

def __init__(self):
try:
project = os.environ[self.PROJECT_ENV].lower()
except KeyError:
project = self.DEFAULT_PROJ
_log.warning(f"Project name not found in environment variable '{self.PROJECT_ENV}',"
f"using default")
_log.info(f"Deploy for project={project}")
if project not in self.PROJ.keys():
valid_projects = ", ".join((str(x) for x in self.PROJ))
raise ValueError(f"project '{project}' not in ({valid_projects})")
self.project = project
self.package = self.PROJ[project]
self.data_basedir = Path.home() / f".{self.project}"
try:
self.data_basedir.mkdir(parents=True, exist_ok=True)
except (FileNotFoundError, OSError) as err:
_log.error(f"error creating project data directory '{self.data_basedir}'")
raise
_log.info(f"Deployment: project={self.project} package={self.package} data_basedir={self.data_basedir}")


_dpy = Deployment()


class AppSettings(BaseSettings):
#: List of package names in which to look for flowsheets
packages: List[str] = ["watertap"]
data_basedir: Union[Path, None] = None
packages: List[str] = list(_dpy.package)
log_dir: Union[Path, None] = None
custom_flowsheets_dir: Union[Path, None] = None

# @validator("data_basedir", always=True)
@field_validator("data_basedir")
def validate_data_basedir(cls, v):
if v is None:
v = Path.home() / ".watertap" / "flowsheets"
v.mkdir(parents=True, exist_ok=True)
return v
data_basedir: Path = _dpy.data_basedir

# @validator("log_dir", always=True)
@field_validator("log_dir")
def validate_log_dir(cls, v):
if v is None:
v = Path.home() / ".watertap" / "logs"
v = _dpy.data_basedir / "logs"
_log.info(f"Creating log directory '{v}'")
v.mkdir(parents=True, exist_ok=True)

loggingFormat = "[%(levelname)s] %(asctime)s %(name)s (%(filename)s:%(lineno)s): %(message)s"
loggingFileHandler = logging.handlers.RotatingFileHandler(v / "watertap-ui_backend_logs.log", backupCount=2, maxBytes=5000000)
logging.basicConfig(level=logging.INFO, format=loggingFormat, handlers=[loggingFileHandler])

logging_format = "[%(levelname)s] %(asctime)s %(name)s " \
"(%(filename)s:%(lineno)s): %(message)s"
project_log_file = f"{_dpy.project}-ui_backend_logs.log"
_log.info(f"Logs will be in rotating files with base name "
f"'{v/project_log_file}'")
logging_file_handler = logging.handlers.RotatingFileHandler(
v / project_log_file,
backupCount=2,
maxBytes=5000000,
)
logging.basicConfig(
level=logging.INFO, format=logging_format, handlers=[logging_file_handler]
)
return v

# @validator("custom_flowsheets_dir", always=True)
@field_validator("custom_flowsheets_dir")
def validate_custom_flowsheets_dir(cls, v):
if v is None:
v = Path.home() / ".watertap" / "custom_flowsheets"
v = _dpy.data_basedir / "custom_flowsheets"
v.mkdir(parents=True, exist_ok=True)
return v

class Config:
env_prefix = "watertap_"
env_prefix = f"{_dpy.project.upper()}_"


def get_deployment() -> Deployment:
return _dpy
Loading

0 comments on commit 4ebfaa3

Please sign in to comment.