summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-api/opendc
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2021-07-08 16:08:02 +0200
committerGitHub <noreply@github.com>2021-07-08 16:08:02 +0200
commit1a2416043f0b877f570e89da74e0d0a4aff1d8ae (patch)
tree1bed18bb62d223be954faca87b0736d2a571b443 /opendc-web/opendc-web-api/opendc
parentdfd2ded56780995cec6d91af37443b710d4ddb3b (diff)
parent2c8d675c2cf140eac05988065a9d20fd2773399a (diff)
ui: Simplify data fetching in frontend
This pull request aims to simplify the data fetching logic in the OpenDC frontend. Previously, the frontend used Redux extensively to sync the server state with the client state, which introduced a lot of unnecessary complexity. With this pull request, we move most of the data fetching logic out of Redux and instead use React Query to perform the logic for fetching and caching API requests. * Move all server data except topologies outside Redux * Use React Query for fetching server data * (De)normalize topology using Normalizr * Remove current ids state from Redux * Combine fetching of project relations
Diffstat (limited to 'opendc-web/opendc-web-api/opendc')
-rw-r--r--opendc-web/opendc-web-api/opendc/api/portfolios.py14
-rw-r--r--opendc-web/opendc-web-api/opendc/api/projects.py27
-rw-r--r--opendc-web/opendc-web-api/opendc/auth.py5
-rw-r--r--opendc-web/opendc-web-api/opendc/exts.py7
-rw-r--r--opendc-web/opendc-web-api/opendc/models/portfolio.py7
-rw-r--r--opendc-web/opendc-web-api/opendc/models/scenario.py15
-rw-r--r--opendc-web/opendc-web-api/opendc/models/topology.py9
7 files changed, 74 insertions, 10 deletions
diff --git a/opendc-web/opendc-web-api/opendc/api/portfolios.py b/opendc-web/opendc-web-api/opendc/api/portfolios.py
index eea82289..4d8f54fd 100644
--- a/opendc-web/opendc-web-api/opendc/api/portfolios.py
+++ b/opendc-web/opendc-web-api/opendc/api/portfolios.py
@@ -103,6 +103,20 @@ class PortfolioScenarios(Resource):
"""
method_decorators = [requires_auth]
+ def get(self, portfolio_id):
+ """
+ Get all scenarios belonging to a portfolio.
+ """
+ portfolio = PortfolioModel.from_id(portfolio_id)
+
+ portfolio.check_exists()
+ portfolio.check_user_access(current_user['sub'], True)
+
+ scenarios = Scenario.get_for_portfolio(portfolio_id)
+
+ data = ScenarioSchema().dump(scenarios, many=True)
+ return {'data': data}
+
def post(self, portfolio_id):
"""
Add a new scenario to this portfolio
diff --git a/opendc-web/opendc-web-api/opendc/api/projects.py b/opendc-web/opendc-web-api/opendc/api/projects.py
index 05f02a84..2b47c12e 100644
--- a/opendc-web/opendc-web-api/opendc/api/projects.py
+++ b/opendc-web/opendc-web-api/opendc/api/projects.py
@@ -132,6 +132,18 @@ class ProjectTopologies(Resource):
"""
method_decorators = [requires_auth]
+ def get(self, project_id):
+ """Get all topologies belonging to the project."""
+ project = ProjectModel.from_id(project_id)
+
+ project.check_exists()
+ project.check_user_access(current_user['sub'], True)
+
+ topologies = Topology.get_for_project(project_id)
+ data = TopologySchema().dump(topologies, many=True)
+
+ return {'data': data}
+
def post(self, project_id):
"""Add a new Topology to the specified project and return it"""
schema = ProjectTopologies.PutSchema()
@@ -170,6 +182,18 @@ class ProjectPortfolios(Resource):
"""
method_decorators = [requires_auth]
+ def get(self, project_id):
+ """Get all portfolios belonging to the project."""
+ project = ProjectModel.from_id(project_id)
+
+ project.check_exists()
+ project.check_user_access(current_user['sub'], True)
+
+ portfolios = Portfolio.get_for_project(project_id)
+ data = PortfolioSchema().dump(portfolios, many=True)
+
+ return {'data': data}
+
def post(self, project_id):
"""Add a new Portfolio for this Project."""
schema = ProjectPortfolios.PutSchema()
@@ -190,7 +214,8 @@ class ProjectPortfolios(Resource):
project.obj['portfolioIds'].append(portfolio.get_id())
project.update()
- return {'data': portfolio.obj}
+ data = PortfolioSchema().dump(portfolio.obj)
+ return {'data': data}
class PutSchema(Schema):
"""
diff --git a/opendc-web/opendc-web-api/opendc/auth.py b/opendc-web/opendc-web-api/opendc/auth.py
index 6db06fb1..d5da6ee5 100644
--- a/opendc-web/opendc-web-api/opendc/auth.py
+++ b/opendc-web/opendc-web-api/opendc/auth.py
@@ -40,10 +40,7 @@ def get_token():
parts = auth.split()
if parts[0].lower() != "bearer":
- raise AuthError({
- "code": "invalid_header",
- "description": "Authorization header must start with Bearer"
- }, 401)
+ raise AuthError({"code": "invalid_header", "description": "Authorization header must start with Bearer"}, 401)
if len(parts) == 1:
raise AuthError({"code": "invalid_header", "description": "Token not found"}, 401)
if len(parts) > 2:
diff --git a/opendc-web/opendc-web-api/opendc/exts.py b/opendc-web/opendc-web-api/opendc/exts.py
index 17dacd5e..3ee8babb 100644
--- a/opendc-web/opendc-web-api/opendc/exts.py
+++ b/opendc-web/opendc-web-api/opendc/exts.py
@@ -83,10 +83,9 @@ def requires_scope(required_scope):
@wraps(f)
def decorated(*args, **kwargs):
if not has_scope(required_scope):
- raise AuthError({
- "code": "Unauthorized",
- "description": "You don't have access to this resource"
- }, 403)
+ raise AuthError({"code": "Unauthorized", "description": "You don't have access to this resource"}, 403)
return f(*args, **kwargs)
+
return decorated
+
return decorator
diff --git a/opendc-web/opendc-web-api/opendc/models/portfolio.py b/opendc-web/opendc-web-api/opendc/models/portfolio.py
index 1643e23e..eb016947 100644
--- a/opendc-web/opendc-web-api/opendc/models/portfolio.py
+++ b/opendc-web/opendc-web-api/opendc/models/portfolio.py
@@ -1,5 +1,7 @@
+from bson import ObjectId
from marshmallow import Schema, fields
+from opendc.exts import db
from opendc.models.project import Project
from opendc.models.model import Model
@@ -38,3 +40,8 @@ class Portfolio(Model):
"""
project = Project.from_id(self.obj['projectId'])
project.check_user_access(user_id, edit_access)
+
+ @classmethod
+ def get_for_project(cls, project_id):
+ """Get all portfolios for the specified project id."""
+ return db.fetch_all({'projectId': ObjectId(project_id)}, cls.collection_name)
diff --git a/opendc-web/opendc-web-api/opendc/models/scenario.py b/opendc-web/opendc-web-api/opendc/models/scenario.py
index 0fb6c453..47771e06 100644
--- a/opendc-web/opendc-web-api/opendc/models/scenario.py
+++ b/opendc-web/opendc-web-api/opendc/models/scenario.py
@@ -1,5 +1,6 @@
from datetime import datetime
+from bson import ObjectId
from marshmallow import Schema, fields
from opendc.exts import db
@@ -7,6 +8,13 @@ from opendc.models.model import Model
from opendc.models.portfolio import Portfolio
+class SimulationSchema(Schema):
+ """
+ Simulation details.
+ """
+ state = fields.String()
+
+
class TraceSchema(Schema):
"""
Schema for specifying the trace of a scenario.
@@ -41,6 +49,8 @@ class ScenarioSchema(Schema):
trace = fields.Nested(TraceSchema)
topology = fields.Nested(TopologySchema)
operational = fields.Nested(OperationalSchema)
+ simulation = fields.Nested(SimulationSchema, dump_only=True)
+ results = fields.Dict(dump_only=True)
class Scenario(Model):
@@ -65,6 +75,11 @@ class Scenario(Model):
"""
return cls(db.fetch_all({'simulation.state': 'QUEUED'}, cls.collection_name))
+ @classmethod
+ def get_for_portfolio(cls, portfolio_id):
+ """Get all scenarios for the specified portfolio id."""
+ return db.fetch_all({'portfolioId': ObjectId(portfolio_id)}, cls.collection_name)
+
def update_state(self, new_state, results=None):
"""Atomically update the state of the Scenario.
"""
diff --git a/opendc-web/opendc-web-api/opendc/models/topology.py b/opendc-web/opendc-web-api/opendc/models/topology.py
index 71d2cade..592f82c5 100644
--- a/opendc-web/opendc-web-api/opendc/models/topology.py
+++ b/opendc-web/opendc-web-api/opendc/models/topology.py
@@ -1,5 +1,7 @@
+from bson import ObjectId
from marshmallow import Schema, fields
+from opendc.exts import db
from opendc.models.project import Project
from opendc.models.model import Model
@@ -73,7 +75,7 @@ class TopologySchema(Schema):
Schema representing a datacenter topology.
"""
_id = fields.String(dump_only=True)
- projectId = fields.String(dump_only=True)
+ projectId = fields.String()
name = fields.String(required=True)
rooms = fields.List(fields.Nested(RoomSchema), required=True)
@@ -93,3 +95,8 @@ class Topology(Model):
"""
project = Project.from_id(self.obj['projectId'])
project.check_user_access(user_id, edit_access)
+
+ @classmethod
+ def get_for_project(cls, project_id):
+ """Get all topologies for the specified project id."""
+ return db.fetch_all({'projectId': ObjectId(project_id)}, cls.collection_name)