Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions documentation/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ New features
-------------

* A single automation can now be run on demand, from the CLI (``flexmeasures jobs run-automation``), the API (``POST /assets/<id>/automations/<automation_id>/trigger``) and the asset's *Automations* page (a *Run now* button), which is useful to try out a new automation, to re-run one after fixing what made it fail, or to refresh its results after late input data arrived [see `PR #2460 <https://www.github.com/FlexMeasures/flexmeasures/pull/2460>`_]
* The asset's status page now splits its sensor data and its jobs over two tabs, of which only the opened one loads its data, and it opens the tab you last looked at [see `PR #2470 <https://www.github.com/FlexMeasures/flexmeasures/pull/2470>`_]
* Changing the selected time range on an asset or sensor chart now only loads the data that is actually new, instead of reloading the whole range, which makes stepping through or extending a long period much faster; reloading the page, or leaving it open for five minutes, still fetches everything afresh [see `PR #2433 <https://www.github.com/FlexMeasures/flexmeasures/pull/2433>`_]

Infrastructure / Support
Expand Down
2 changes: 2 additions & 0 deletions flexmeasures/api/v3_0/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
flex_context_schema_openAPI,
AssetAPIQuerySchema,
DefaultAssetViewJSONSchema,
StatusPageTabJSONSchema,
)
from flexmeasures.data.schemas.annotations import AnnotationSchema
from flexmeasures.data.schemas.generic_assets import GenericAssetSchema as AssetSchema
Expand Down Expand Up @@ -224,6 +225,7 @@ def create_openapi_specs(app: Flask):
("AnnotationSchema", AnnotationSchema),
("CopyAssetSchema", CopyAssetSchema),
("DefaultAssetViewJSONSchema", DefaultAssetViewJSONSchema),
("StatusPageTabJSONSchema", StatusPageTabJSONSchema),
("AccountSchema", AccountSchema(partial=True)),
("AccountCreateSchema", AccountCreateSchema()),
("AccountPatchSchema", AccountPatchSchema()),
Expand Down
66 changes: 66 additions & 0 deletions flexmeasures/api/v3_0/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,17 @@ class DefaultAssetViewJSONSchema(Schema):
)


class StatusPageTabJSONSchema(Schema):
status_page_tab = fields.Str(
required=True,
validate=validate.OneOf(["jobs", "sensors"]),
metadata={
"enum": ["jobs", "sensors"],
"description": "The tab to open on the asset's status page.",
},
)


class KPIKwargsSchema(Schema):
event_starts_after = AwareDateTimeField(format="iso", required=False)
event_ends_before = AwareDateTimeField(format="iso", required=False)
Expand Down Expand Up @@ -1832,6 +1843,61 @@ def update_default_asset_view(self, **kwargs):
"message": "Default asset view updated successfully.",
}, 200

@route("/status_page_tab", methods=["POST"])
@as_json
@use_kwargs(StatusPageTabJSONSchema, location="json")
def update_status_page_tab(self, **kwargs):
"""
.. :quickref: Assets; Remember which tab of the asset status page the current user last opened.
---
post:
summary: Remember which tab of the asset status page the current user last opened.
description: |
The status page shows a sensor data tab and a jobs tab, of which only the opened one loads its data.
This endpoint records the user's choice in their session, so their next visit to a status page opens the same tab.
Without a recorded choice, the jobs tab opens.
security:
- ApiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema: StatusPageTabJSONSchema
examples:
status_page_tab:
summary: Opening the sensor data tab from now on
value:
status_page_tab: "sensors"
responses:
200:
description: PROCESSED
content:
application/json:
examples:
message:
summary: Message
value:
message: "Preferred status page tab updated successfully."
400:
description: INVALID_REQUEST, REQUIRED_INFO_MISSING, UNEXPECTED_PARAMS
401:
description: UNAUTHORIZED
422:
description: UNPROCESSABLE_ENTITY
tags:
- Assets
"""
# Update the request.values, as that is where set_session_variables reads from.
request_values = request.values.copy()
request_values.update(kwargs)
request.values = request_values

set_session_variables("status_page_tab")

return {
"message": "Preferred status page tab updated successfully.",
}, 200

@route("/keep_legends_below_graphs", methods=["POST"])
@as_json
@use_kwargs(
Expand Down
31 changes: 31 additions & 0 deletions flexmeasures/api/v3_0/tests/test_assets_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2070,3 +2070,34 @@ def local_day(day: int) -> str:
assert sum(totals.values()) == pytest.approx(
222.0
), "each event counts once across neighbouring days, not twice"


@pytest.mark.parametrize(
"requesting_user", ["test_prosumer_user@seita.nl"], indirect=True
)
@pytest.mark.parametrize("tab", ["jobs", "sensors"])
def test_update_status_page_tab(client, setup_api_test_data, requesting_user, tab):
"""Posting a status page tab records it in the session, for the next status page the user opens."""
response = client.post(
url_for("AssetAPI:update_status_page_tab"),
json={"status_page_tab": tab},
)
assert response.status_code == 200
with client.session_transaction() as session:
assert session["status_page_tab"] == tab


@pytest.mark.parametrize(
"requesting_user", ["test_prosumer_user@seita.nl"], indirect=True
)
def test_update_status_page_tab_rejects_unknown_tab(
client, setup_api_test_data, requesting_user
):
"""Only the two tabs the status page actually has are accepted."""
response = client.post(
url_for("AssetAPI:update_status_page_tab"),
json={"status_page_tab": "automations"},
)
assert response.status_code == 422
with client.session_transaction() as session:
assert "status_page_tab" not in session
78 changes: 78 additions & 0 deletions flexmeasures/ui/static/openapi-specs.json
Original file line number Diff line number Diff line change
Expand Up @@ -4861,6 +4861,67 @@
]
}
},
"/api/v3_0/assets/status_page_tab": {
"post": {
"summary": "Remember which tab of the asset status page the current user last opened.",
"description": "The status page shows a sensor data tab and a jobs tab, of which only the opened one loads its data.\nThis endpoint records the user's choice in their session, so their next visit to a status page opens the same tab.\nWithout a recorded choice, the jobs tab opens.\n",
"security": [
{
"ApiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StatusPageTabJSONSchema"
},
"examples": {
"status_page_tab": {
"summary": "Opening the sensor data tab from now on",
"value": {
"status_page_tab": "sensors"
}
}
}
}
}
},
"responses": {
"200": {
"description": "PROCESSED",
"content": {
"application/json": {
"examples": {
"message": {
"summary": "Message",
"value": {
"message": "Preferred status page tab updated successfully."
}
}
}
}
}
},
"400": {
"description": "INVALID_REQUEST, REQUIRED_INFO_MISSING, UNEXPECTED_PARAMS"
},
"401": {
"description": "UNAUTHORIZED"
},
"422": {
"description": "UNPROCESSABLE_ENTITY"
},
"429": {
"description": "TOO_MANY_REQUESTS - You called the API more often than your rate limit allows. Wait for as long as the Retry-After header says, then try again."
}
},
"tags": [
"Assets"
]
}
},
"/api/v3_0/assets/types": {
"get": {
"summary": "Get list of available asset types",
Expand Down Expand Up @@ -6337,6 +6398,23 @@
],
"additionalProperties": false
},
"StatusPageTabJSONSchema": {
"type": "object",
"properties": {
"status_page_tab": {
"type": "string",
"enum": [
"jobs",
"sensors"
],
"description": "The tab to open on the asset's status page."
}
},
"required": [
"status_page_tab"
],
"additionalProperties": false
},
"AccountRole": {
"type": "object",
"properties": {
Expand Down
Loading
Loading