Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Asset context overview page. #1369

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions flexmeasures/ui/crud/assets/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from flexmeasures.auth.policy import check_access
from flexmeasures.data import db
from flexmeasures import Asset
from flexmeasures.data.models.generic_assets import GenericAsset, GenericAssetType
from flexmeasures.data.models.user import Account
from flexmeasures.data.models.time_series import Sensor
Expand All @@ -18,6 +19,7 @@
is_energy_unit,
is_power_unit,
)
from flexmeasures.ui.utils.view_utils import svg_asset_icon_name


def get_allowed_price_sensor_data(account_id: Optional[int]) -> Dict[int, str]:
Expand Down Expand Up @@ -194,3 +196,81 @@ def get_assets_by_account(account_id: int | str | None) -> list[GenericAsset]:
process_internal_api_response(ad, make_obj=True)
for ad in get_assets_response.json()
]


def serialize_asset(asset: Asset, is_head=False) -> dict:
serialized_asset = {
"name": asset.name,
"id": asset.id,
"asset_type": asset.generic_asset_type.name,
"link": url_for("AssetCrudUI:get", id=asset.id),
"icon": svg_asset_icon_name(asset.generic_asset_type.name),
"tooltip": {
"name": asset.name,
"ID": asset.id,
"Asset Type": asset.generic_asset_type.name,
},
"sensors": [
{"name": sensor.name, "unit": sensor.unit, "link": url_for("SensorUI:get", id=sensor.id)} for sensor in asset.sensors
],
}

if asset.parent_asset and not is_head:
serialized_asset["parent"] = asset.parent_asset.id

return serialized_asset


def get_list_assets_chart(
asset: Asset,
base_asset: Asset,
parent_depth=0,
child_depth=0,
look_for_child=False,
is_head=False,
) -> list[dict]:
"""
Recursively builds a tree of assets from a given asset and its parents and children up to a certain depth.

Args:
asset: The asset to start the recursion from
base_asset: The asset that is the base of the chart
parent_depth: The current depth of the parents hierarchy
child_depth: The current depth of the children hierarchy
look_for_child: If True, start looking for children of the current asset
is_head: If True, the current asset is the head of the chart

Returns:
A list of dictionaries representing the assets in the tree
"""
assets = list()
asset_def = serialize_asset(asset, is_head=is_head)

# Fetch parents if there is parent asset and parent_depth is less than 2
if asset.parent_asset and parent_depth < 2 and not look_for_child:
parent_depth += 1
assets += get_list_assets_chart(
asset=asset.parent_asset,
base_asset=base_asset,
parent_depth=parent_depth,
is_head=False if parent_depth < 2 else True,
)
else:
look_for_child = True
parent_depth = (
2 # Auto increase depth in the parents hierarchy is less than two
)

assets.append(asset_def)

if look_for_child and child_depth < 2:
child_depth += 1
for child in base_asset.child_assets:
assets += get_list_assets_chart(
child,
parent_depth=parent_depth,
child_depth=child_depth,
base_asset=child,
)

return assets
91 changes: 89 additions & 2 deletions flexmeasures/ui/crud/assets/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
from flask_security import login_required, current_user
from webargs.flaskparser import use_kwargs

from flexmeasures.ui.utils.view_utils import svg_asset_icon_name

from flexmeasures.data import db
from flexmeasures.auth.error_handling import unauthorized_handler
from flexmeasures.data.schemas import StartEndTimeSchema
from flexmeasures.data.services.job_cache import NoRedisConfigured
from flexmeasures.data.models.generic_assets import (
GenericAsset,
get_center_location_of_assets,
GenericAssetType,
)
from flexmeasures.ui.utils.view_utils import ICON_MAPPING
from flexmeasures.ui.utils.view_utils import ICON_MAPPING, available_units
from flexmeasures.data.models.user import Account
from flexmeasures.ui.utils.view_utils import render_flexmeasures_template
from flexmeasures.ui.crud.api_wrapper import InternalApi
Expand All @@ -23,13 +26,19 @@
user_can_create_assets,
user_can_delete,
user_can_update,
get_list_assets_chart,
)
from flexmeasures.data.services.sensors import (
build_sensor_status_data,
build_asset_jobs_data,
)
from flexmeasures.ui.utils.view_utils import available_units

from flexmeasures.auth.policy import user_has_admin_access
from flexmeasures.data.queries.generic_assets import get_asset_group_queries
from flexmeasures.data.services.asset_grouping import (
AssetGroup,
)
from sqlalchemy import select

"""
Asset crud view.
Expand Down Expand Up @@ -92,6 +101,45 @@ def owned_by(self, account_id: str):
user_can_create_assets=user_can_create_assets(),
)

@login_required
@route("/<id>/context")
def context(self, id: str):
"""/assets/<id>/context"""
asset = db.session.query(GenericAsset).filter_by(id=id).first()
if asset is None:
assets = []
else:
assets = get_list_assets_chart(asset, base_asset=asset)

current_asset_sensors = [
{
"name": sensor.name,
"unit": sensor.unit,
"link": url_for("SensorUI:get", id=sensor.id),
}
for sensor in asset.sensors
]
# Add Extra node to the current asset
add_child_asset = {
"name": "Add Child Asset",
"id": "new",
"asset_type": asset.generic_asset_type.name,
"link": url_for("AssetCrudUI:get", id="new"),
"icon": svg_asset_icon_name("add_asset"),
"tooltip": "",
"sensors": [],
"parent": asset.id,
}

assets.append(add_child_asset)

return render_flexmeasures_template(
"crud/asset_context.html",
assets=assets,
asset=asset,
current_asset_sensors=current_asset_sensors,
)

@use_kwargs(StartEndTimeSchema, location="query")
@login_required
def get(self, id: str, **kwargs):
Expand Down Expand Up @@ -304,3 +352,42 @@ def auditlog(self, id: str):
"crud/asset_audit_log.html",
asset=asset,
)

@login_required
def map(self, msg="", **kwargs):
"""GET from /assets/map"""

aggregate_type_groups = current_app.config.get(
"FLEXMEASURES_ASSET_TYPE_GROUPS", {}
)
group_by_accounts = request.args.get("group_by_accounts", "0") != "0"
if user_has_admin_access(current_user, "read") and group_by_accounts:
print("group_by_accounts", group_by_accounts)
asset_groups = get_asset_group_queries(
group_by_type=False, group_by_account=True
)
# print("aggregate_type_groups", aggregate_type_groups)
else:
print("in else ")
asset_groups = get_asset_group_queries(
group_by_type=True, custom_aggregate_type_groups=aggregate_type_groups
)

map_asset_groups = {}
for asset_group_name, asset_group_query in asset_groups.items():
asset_group = AssetGroup(asset_group_name, asset_query=asset_group_query)
if any([a.location for a in asset_group.assets]):
map_asset_groups[asset_group_name] = asset_group

known_asset_types = [
gat.name for gat in db.session.scalars(select(GenericAssetType)).all()
]

return render_flexmeasures_template(
"crud/context_map.html",
mapboxAccessToken=current_app.config.get("MAPBOX_ACCESS_TOKEN", ""),
map_center=get_center_location_of_assets(user=current_user),
known_asset_types=known_asset_types,
asset_groups=map_asset_groups,
aggregate_type_groups=aggregate_type_groups,
)
9 changes: 9 additions & 0 deletions flexmeasures/ui/static/css/flexmeasures.css
Original file line number Diff line number Diff line change
Expand Up @@ -1963,3 +1963,12 @@ body.touched [title]:hover:after {
color: #000;
}
/* FlexContext Modal Dialogue - End*/


/* Custom CSS for SVG */
.mark-image image {
transform: translate(1px, 10px) !important;
width: 34px !important;
height: 20px !important;
vertical-align: middle;
}
Loading
Loading