Skip to content

Commit

Permalink
Merge branch 'main' into 928-fix-discrepancies-between-db-and-models
Browse files Browse the repository at this point in the history
  • Loading branch information
nhoening committed Dec 20, 2023
2 parents 2871628 + a646fed commit 03a71dc
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 3 deletions.
1 change: 1 addition & 0 deletions documentation/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ New features
-------------

* Better navigation experience through listings (sensors / assets / users / accounts) in the :abbr:`UI (user interface)`, by heading to the selected entity upon a click (or CTRL + click) anywhere within a row [see `PR #923 <https://github.com/FlexMeasures/flexmeasures/pull/923>`_]
* Introduce a breadcrumb to navigate through assets and sensor pages using its child-parent relationship [see `PR #930 <https://github.com/FlexMeasures/flexmeasures/pull/930>`_]
* Define device-level power constraints as sensors to create schedules with changing power limits. [see `PR #897 <https://github.com/FlexMeasures/flexmeasures/pull/897>`_]
* Allow to provide external storage usage or gain components using the ``soc-usage`` and ``soc-gain`` fields of the `flex-model` [see `PR #906 <https://github.com/FlexMeasures/flexmeasures/pull/906>`_]

Expand Down
4 changes: 2 additions & 2 deletions documentation/concepts/device_scheduler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ The cost function quantifies the total cost of upwards and downwards deviations
.. math::
:name: cost_function
\min [\sum_{c,j} \Delta _{up}(c,j) \cdot Price_{up}(c,j) + \Delta_{down}(c,j) \cdot Price_{down}(c,j)]
\min [\sum_{c,j} \Delta_{up}(c,j) \cdot Price_{up}(c,j) + \Delta_{down}(c,j) \cdot Price_{down}(c,j)]
State dynamics
Expand Down Expand Up @@ -191,5 +191,5 @@ Power coupling constraints
.. math::
:name: ems_flow_commitment_equalities
\sum_d P^{ems}(d,j) = \sum_c Commitment(c,j) + \Delta {up}(c,j) + \Delta {down}(c,j)
\sum_d P^{ems}(d,j) = \sum_c Commitment(c,j) + \Delta_{up}(c,j) + \Delta_{down}(c,j)
8 changes: 8 additions & 0 deletions flexmeasures/cli/data_add.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
from flexmeasures.data.schemas.generic_assets import (
GenericAssetSchema,
GenericAssetTypeSchema,
GenericAssetIdField,
)
from flexmeasures.data.models.generic_assets import GenericAsset, GenericAssetType
from flexmeasures.data.models.user import User
Expand Down Expand Up @@ -369,6 +370,13 @@ def add_asset_type(**args):
type=int,
help="Asset type to assign to this asset",
)
@click.option(
"--parent-asset",
"parent_asset",
required=False,
type=GenericAssetIdField(),
help="Parent of this asset. The entity needs to exists on the database.",
)
def add_asset(**args):
"""Add an asset."""
check_errors(GenericAssetSchema().validate(args))
Expand Down
6 changes: 6 additions & 0 deletions flexmeasures/ui/crud/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ def expunge_asset():
Sensor.generic_asset_id == asset_data["id"]
).all()
expunge_asset()
if asset_data.get("parent_asset_id", None) is not None:
asset.parent_asset = GenericAsset.query.filter(
GenericAsset.id == asset_data["parent_asset_id"]
).one_or_none()
expunge_asset()
return asset
return asset_data

Expand Down Expand Up @@ -349,6 +354,7 @@ def post(self, id: str):
asset = process_internal_api_response(
asset_info, int(id), make_obj=True
)

return render_flexmeasures_template(
"crud/asset.html",
asset_form=asset_form,
Expand Down
16 changes: 16 additions & 0 deletions flexmeasures/ui/static/css/flexmeasures.css
Original file line number Diff line number Diff line change
Expand Up @@ -1847,4 +1847,20 @@ div.heading-group {
.ellipsify {
overflow: visible !important;
white-space: normal !important;
}

/* Breadcrumb */
.breadcrumb {
margin: 0px;
background: var(--light-gray);
}
.breadcrumb a {
color: var(--primary-color);
}
.breadcrumb a:hover {
color: var(--primary-hover-color);
}
.breadcrumb-item + .breadcrumb-item::before {
content: " > ";
color: var(--primary-color);
}
16 changes: 16 additions & 0 deletions flexmeasures/ui/templates/crud/asset.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,23 @@

{% block title %} {{asset.name}} {% endblock %}



{% block divs %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
{% for breadcrumb in breadcrumb_info["ancestors"] %}
<li class="breadcrumb-item{% if loop.last %} active{% endif %}" {% if loop.last %}aria-current="page"{% endif %}>
{% if breadcrumb["url"] is not none and not loop.last %}
<a href="{{ breadcrumb['url'] }}">{{ breadcrumb['name'] }}</a>
{% else %}
{{ breadcrumb['name'] }}
{% endif %}
</li>
{% endfor %}
</ol>
</nav>


<div class="container-fluid">
<div class="row">
Expand Down
16 changes: 15 additions & 1 deletion flexmeasures/ui/templates/views/sensors.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,22 @@

{% block title %} Sensor data {% endblock %}

{% block divs %}


{% block divs %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
{% for breadcrumb in breadcrumb_info["ancestors"] %}
<li class="breadcrumb-item{% if loop.last %} active{% endif %}" {% if loop.last %}aria-current="page"{% endif %}>
{% if breadcrumb["url"] is not none and not loop.last %}
<a href="{{ breadcrumb['url'] }}">{{ breadcrumb['name'] }}</a>
{% else %}
{{ breadcrumb['name'] }}
{% endif %}
</li>
{% endfor %}
</ol>
</nav>
<div class="sensor-data charts text-center">
<div class="row">
<div class="alert alert-info" id="tzwarn" style="display:none;"></div>
Expand Down
49 changes: 49 additions & 0 deletions flexmeasures/ui/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from flexmeasures import Asset, AssetType, Account, Sensor
from flexmeasures.ui.utils.breadcrumb_utils import get_ancestry


def test_get_ancestry(app, db):
account = Account(name="Test Account")
asset_type = AssetType(name="TestAssetType")

parent_asset = Asset(name="Parent", generic_asset_type=asset_type, owner=account)
assets = [parent_asset]
for i in range(4):
child_asset = Asset(
name=f"Child {i}",
generic_asset_type=asset_type,
owner=account,
parent_asset=parent_asset,
)
assets.append(child_asset)
parent_asset = child_asset

sensor = Sensor(name="Test Sensor", generic_asset=child_asset)

db.session.add_all([account, asset_type, sensor] + assets)
db.session.commit()

# ancestry of a public account
assert get_ancestry(None) == [{"url": None, "name": "PUBLIC", "type": "Account"}]

# ancestry of an account
account_id = account.id
assert get_ancestry(account) == [
{"url": f"/accounts/{account_id}", "name": "Test Account", "type": "Account"}
]

# ancestry of a parentless asset
assert get_ancestry(assets[0]) == [
{"url": f"/accounts/{account_id}", "name": "Test Account", "type": "Account"},
{"url": f"/assets/{assets[0].id}/", "name": "Parent", "type": "Asset"},
]

# check that the number of elements of the ancestry of each assets corresponds to 2 + levels
for i, asset in enumerate(assets):
assert len(get_ancestry(asset)) == i + 2

# ancestry of the sensor
sensor_ancestry = get_ancestry(sensor)
assert sensor_ancestry[-1]["type"] == "Sensor"
assert sensor_ancestry[0]["type"] == "Account"
assert all(b["type"] == "Asset" for b in sensor_ancestry[1:-1])
57 changes: 57 additions & 0 deletions flexmeasures/ui/utils/breadcrumb_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from __future__ import annotations

from flexmeasures import Sensor, Asset, Account
from flask import url_for


def get_breadcrumb_info(entity: Sensor | Asset | Account | None) -> dict:
return {
"ancestors": get_ancestry(entity),
}


def get_ancestry(entity: Sensor | Asset | Account | None) -> list[dict]:

# Public account
if entity is None:
return [{"url": None, "name": "PUBLIC", "type": "Account"}]

# account
if isinstance(entity, Account):
return [
{
"url": url_for("AccountCrudUI:get", account_id=entity.id),
"name": entity.name,
"type": "Account",
}
]

# sensor
if isinstance(entity, Sensor):
current_entity_info = [
{
"url": url_for("SensorUI:get", id=entity.id),
"name": entity.name,
"type": "Sensor",
}
]

return get_ancestry(entity.generic_asset) + current_entity_info

# asset
if isinstance(entity, Asset):
current_entity_info = [
{
"url": url_for("AssetCrudUI:get", id=entity.id),
"name": entity.name,
"type": "Asset",
}
]

# asset without parent
if entity.parent_asset is None:
return get_ancestry(entity.owner) + current_entity_info
else: # asset with parent
return get_ancestry(entity.parent_asset) + current_entity_info

return []
4 changes: 4 additions & 0 deletions flexmeasures/ui/utils/view_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from flexmeasures import __version__ as flexmeasures_version
from flexmeasures.auth.policy import user_has_admin_access
from flexmeasures.ui.utils.breadcrumb_utils import get_breadcrumb_info
from flexmeasures.utils import time_utils
from flexmeasures.ui import flexmeasures_ui
from flexmeasures.data.models.user import User, Account
Expand Down Expand Up @@ -82,6 +83,9 @@ def render_flexmeasures_template(html_filename: str, **variables):
variables["menu_logo"] = current_app.config.get("FLEXMEASURES_MENU_LOGO_PATH")
variables["extra_css"] = current_app.config.get("FLEXMEASURES_EXTRA_CSS_PATH")

if "asset" in variables:
variables["breadcrumb_info"] = get_breadcrumb_info(asset)

return render_template(html_filename, **variables)


Expand Down
3 changes: 3 additions & 0 deletions flexmeasures/ui/views/sensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@

from flexmeasures.data.schemas.times import AwareDateTimeField
from flexmeasures.api.dev.sensors import SensorAPI
from flexmeasures import Sensor
from flexmeasures.ui.utils.view_utils import render_flexmeasures_template
from flexmeasures.ui.utils.chart_defaults import chart_options
from flexmeasures.ui.utils.breadcrumb_utils import get_breadcrumb_info


class SensorUI(FlaskView):
Expand Down Expand Up @@ -70,4 +72,5 @@ def get(self, id: int):
"views/sensors.html",
sensor_id=id,
msg="",
breadcrumb_info=get_breadcrumb_info(Sensor.query.get(id)),
)

0 comments on commit 03a71dc

Please sign in to comment.