data service: add toggle env var to make it optional
This commit is contained in:
parent
3d2e7f57d7
commit
e732fb48d7
7 changed files with 94 additions and 26 deletions
33
README.md
33
README.md
|
|
@ -168,7 +168,6 @@ export COOKIE_SECRET=$(openssl rand -hex 50)
|
||||||
export OIDC_SERVER=https://auth.cern.ch/auth
|
export OIDC_SERVER=https://auth.cern.ch/auth
|
||||||
export OIDC_REALM=CERN
|
export OIDC_REALM=CERN
|
||||||
export CLIENT_ID=caimira-test
|
export CLIENT_ID=caimira-test
|
||||||
export CLIENT_SECRET
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Run docker-compose:
|
Run docker-compose:
|
||||||
|
|
@ -324,6 +323,38 @@ Please note that there is no need for keys on this API call. It is **free-of-cha
|
||||||
- **Humidity and Inside Temperature:**
|
- **Humidity and Inside Temperature:**
|
||||||
There is the possibility of using one external API call to fetch information related to a location specified in the UI. The data is related to the inside temperature and humidity taken from an indoor measurement device. Note that the API currently used from ARVE is only available for the `CERN theme` as the authorised sensors are installed at CERN."
|
There is the possibility of using one external API call to fetch information related to a location specified in the UI. The data is related to the inside temperature and humidity taken from an indoor measurement device. Note that the API currently used from ARVE is only available for the `CERN theme` as the authorised sensors are installed at CERN."
|
||||||
|
|
||||||
|
- **ARVE:**
|
||||||
|
|
||||||
|
The ARVE Swiss Air Quality System provides trusted air data for commercial buildings in real-time and analyzes it with the help of AI and machine learning algorithms to create actionable insights.
|
||||||
|
|
||||||
|
Create secret:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ read ARVE_CLIENT_ID
|
||||||
|
$ read ARVE_CLIENT_SECRET
|
||||||
|
$ read ARVE_API_KEY
|
||||||
|
$ oc create secret generic \
|
||||||
|
--from-literal="ARVE_CLIENT_ID=$ARVE_CLIENT_ID" \
|
||||||
|
--from-literal="ARVE_CLIENT_SECRET=$ARVE_CLIENT_SECRET" \
|
||||||
|
--from-literal="ARVE_API_KEY=$ARVE_API_KEY" \
|
||||||
|
arve-api
|
||||||
|
```
|
||||||
|
|
||||||
|
- **CERN Data Service:**
|
||||||
|
|
||||||
|
The CERN data service collects data from various sources and expose them via a REST API endpoint.
|
||||||
|
|
||||||
|
Create secret:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ read DATA_SERVICE_CLIENT_EMAIL
|
||||||
|
$ read DATA_SERVICE_CLIENT_PASSWORD
|
||||||
|
$ oc create secret generic \
|
||||||
|
--from-literal="DATA_SERVICE_CLIENT_EMAIL=$DATA_SERVICE_CLIENT_EMAIL" \
|
||||||
|
--from-literal="DATA_SERVICE_CLIENT_PASSWORD=$DATA_SERVICE_CLIENT_PASSWORD" \
|
||||||
|
data-service-api
|
||||||
|
```
|
||||||
|
|
||||||
## Update configuration
|
## Update configuration
|
||||||
|
|
||||||
If you need to **update** existing configuration, then modify this repository and after having logged in, run:
|
If you need to **update** existing configuration, then modify this repository and after having logged in, run:
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ if [[ "$APP_NAME" == "calculator-app" ]]; then
|
||||||
|
|
||||||
export "EXTRA_PAGES"="$EXTRA_PAGES"
|
export "EXTRA_PAGES"="$EXTRA_PAGES"
|
||||||
|
|
||||||
|
export "DATA_SERVICE_ENABLED"="${DATA_SERVICE_ENABLED:=False}"
|
||||||
export "DATA_SERVICE_CLIENT_EMAIL"="$DATA_SERVICE_CLIENT_EMAIL"
|
export "DATA_SERVICE_CLIENT_EMAIL"="$DATA_SERVICE_CLIENT_EMAIL"
|
||||||
export "DATA_SERVICE_CLIENT_PASSWORD"="$DATA_SERVICE_CLIENT_PASSWORD"
|
export "DATA_SERVICE_CLIENT_PASSWORD"="$DATA_SERVICE_CLIENT_PASSWORD"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ services:
|
||||||
- APPLICATION_ROOT=/
|
- APPLICATION_ROOT=/
|
||||||
- CAIMIRA_CALCULATOR_PREFIX=/calculator-cern
|
- CAIMIRA_CALCULATOR_PREFIX=/calculator-cern
|
||||||
- CAIMIRA_THEME=caimira/apps/templates/cern
|
- CAIMIRA_THEME=caimira/apps/templates/cern
|
||||||
|
- DATA_SERVICE_ENABLED=False
|
||||||
user: ${CURRENT_UID}
|
user: ${CURRENT_UID}
|
||||||
|
|
||||||
calculator-open-app:
|
calculator-open-app:
|
||||||
|
|
@ -29,6 +30,7 @@ services:
|
||||||
- APP_NAME=calculator-app
|
- APP_NAME=calculator-app
|
||||||
- APPLICATION_ROOT=/
|
- APPLICATION_ROOT=/
|
||||||
- CAIMIRA_CALCULATOR_PREFIX=/calculator-open
|
- CAIMIRA_CALCULATOR_PREFIX=/calculator-open
|
||||||
|
- DATA_SERVICE_ENABLED=False
|
||||||
user: ${CURRENT_UID}
|
user: ${CURRENT_UID}
|
||||||
|
|
||||||
auth-service:
|
auth-service:
|
||||||
|
|
|
||||||
|
|
@ -283,6 +283,18 @@
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
key: ARVE_API_KEY
|
key: ARVE_API_KEY
|
||||||
name: arve-api
|
name: arve-api
|
||||||
|
- name: DATA_SERVICE_ENABLED
|
||||||
|
value: False
|
||||||
|
- name: DATA_SERVICE_CLIENT_EMAIL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
key: DATA_SERVICE_CLIENT_EMAIL
|
||||||
|
name: data-service-api
|
||||||
|
- name: DATA_SERVICE_CLIENT_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
key: DATA_SERVICE_CLIENT_PASSWORD
|
||||||
|
name: data-service-api
|
||||||
image: '${PROJECT_NAME}/calculator-app'
|
image: '${PROJECT_NAME}/calculator-app'
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
|
|
@ -360,6 +372,8 @@
|
||||||
value: /
|
value: /
|
||||||
- name: CAIMIRA_CALCULATOR_PREFIX
|
- name: CAIMIRA_CALCULATOR_PREFIX
|
||||||
value: /calculator-open
|
value: /calculator-open
|
||||||
|
- name: DATA_SERVICE_ENABLED
|
||||||
|
value: False
|
||||||
image: '${PROJECT_NAME}/calculator-app'
|
image: '${PROJECT_NAME}/calculator-app'
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
|
|
|
||||||
|
|
@ -107,15 +107,16 @@ class ConcentrationModel(BaseRequestHandler):
|
||||||
start = datetime.datetime.now()
|
start = datetime.datetime.now()
|
||||||
|
|
||||||
# Data Service API Integration
|
# Data Service API Integration
|
||||||
|
fetched_service_data = None
|
||||||
data_service: DataService = self.settings["data_service"]
|
data_service: DataService = self.settings["data_service"]
|
||||||
try:
|
if self.settings["data_service"]:
|
||||||
access_token = await data_service.login()
|
try:
|
||||||
service_data = await data_service.fetch(access_token)
|
fetched_service_data = await data_service.fetch()
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
error_message = f"Something went wrong with the data service: {str(err)}"
|
error_message = f"Something went wrong with the data service: {str(err)}"
|
||||||
LOG.error(error_message, exc_info=True)
|
LOG.error(error_message, exc_info=True)
|
||||||
self.send_error(500, reason=error_message)
|
self.send_error(500, reason=error_message)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
form = model_generator.FormData.from_dict(requested_model_config)
|
form = model_generator.FormData.from_dict(requested_model_config)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
|
|
@ -134,7 +135,7 @@ class ConcentrationModel(BaseRequestHandler):
|
||||||
timeout=300,
|
timeout=300,
|
||||||
)
|
)
|
||||||
# Re-generate the report with the conditional probability of infection plot
|
# Re-generate the report with the conditional probability of infection plot
|
||||||
if self.get_cookie('conditional_plot'):
|
if self.get_cookie('conditional_plot'):
|
||||||
form.conditional_probability_plot = True if self.get_cookie('conditional_plot') == '1' else False
|
form.conditional_probability_plot = True if self.get_cookie('conditional_plot') == '1' else False
|
||||||
self.clear_cookie('conditional_plot') # Clears cookie after changing the form value.
|
self.clear_cookie('conditional_plot') # Clears cookie after changing the form value.
|
||||||
|
|
||||||
|
|
@ -260,7 +261,7 @@ class ArveData(BaseRequestHandler):
|
||||||
http_client = AsyncHTTPClient()
|
http_client = AsyncHTTPClient()
|
||||||
|
|
||||||
URL = 'https://arveapi.auth.eu-central-1.amazoncognito.com/oauth2/token'
|
URL = 'https://arveapi.auth.eu-central-1.amazoncognito.com/oauth2/token'
|
||||||
headers = { "Content-Type": "application/x-www-form-urlencoded",
|
headers = { "Content-Type": "application/x-www-form-urlencoded",
|
||||||
"Authorization": b"Basic " + base64.b64encode(f'{client_id}:{client_secret}'.encode())
|
"Authorization": b"Basic " + base64.b64encode(f'{client_id}:{client_secret}'.encode())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -390,7 +391,7 @@ def make_app(
|
||||||
'active_page': 'about',
|
'active_page': 'about',
|
||||||
'filename': 'about.html.j2'}),
|
'filename': 'about.html.j2'}),
|
||||||
(get_root_calculator_url(r'/user-guide'), GenericExtraPage, {
|
(get_root_calculator_url(r'/user-guide'), GenericExtraPage, {
|
||||||
'active_page': 'calculator/user-guide',
|
'active_page': 'calculator/user-guide',
|
||||||
'filename': 'userguide.html.j2'}),
|
'filename': 'userguide.html.j2'}),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -407,7 +408,7 @@ def make_app(
|
||||||
pass
|
pass
|
||||||
|
|
||||||
for extra in pages:
|
for extra in pages:
|
||||||
urls.append((get_root_url(r'%s' % extra['url_path']),
|
urls.append((get_root_url(r'%s' % extra['url_path']),
|
||||||
GenericExtraPage, {
|
GenericExtraPage, {
|
||||||
'active_page': extra['url_path'].strip('/'),
|
'active_page': extra['url_path'].strip('/'),
|
||||||
'filename': extra['filename'], }))
|
'filename': extra['filename'], }))
|
||||||
|
|
@ -433,6 +434,9 @@ def make_app(
|
||||||
'data_service_client_email': os.environ.get('DATA_SERVICE_CLIENT_EMAIL', None),
|
'data_service_client_email': os.environ.get('DATA_SERVICE_CLIENT_EMAIL', None),
|
||||||
'data_service_client_password': os.environ.get('DATA_SERVICE_CLIENT_PASSWORD', None),
|
'data_service_client_password': os.environ.get('DATA_SERVICE_CLIENT_PASSWORD', None),
|
||||||
}
|
}
|
||||||
|
data_service = None
|
||||||
|
if bool(os.environ.get('DATA_SERVICE_ENABLED', False)):
|
||||||
|
data_service = DataService(data_service_credentials)
|
||||||
|
|
||||||
if debug:
|
if debug:
|
||||||
tornado.log.enable_pretty_logging()
|
tornado.log.enable_pretty_logging()
|
||||||
|
|
@ -452,7 +456,7 @@ def make_app(
|
||||||
arve_api_key=os.environ.get('ARVE_API_KEY', None),
|
arve_api_key=os.environ.get('ARVE_API_KEY', None),
|
||||||
|
|
||||||
# Data Service Integration
|
# Data Service Integration
|
||||||
data_service = DataService(data_service_credentials),
|
data_service=data_service,
|
||||||
|
|
||||||
# Process parallelism controls. There is a balance between serving a single report
|
# Process parallelism controls. There is a balance between serving a single report
|
||||||
# requests quickly or serving multiple requests concurrently.
|
# requests quickly or serving multiple requests concurrently.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import typing
|
||||||
|
|
||||||
from tornado.httpclient import AsyncHTTPClient, HTTPRequest
|
from tornado.httpclient import AsyncHTTPClient, HTTPRequest
|
||||||
|
|
||||||
|
|
@ -10,8 +11,8 @@ LOG = logging.getLogger(__name__)
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class DataService():
|
class DataService():
|
||||||
'''
|
'''
|
||||||
Responsible for establishing a connection to a
|
Responsible for establishing a connection to a
|
||||||
database through a REST API by handling authentication
|
database through a REST API by handling authentication
|
||||||
and fetching data. It utilizes the Tornado web framework
|
and fetching data. It utilizes the Tornado web framework
|
||||||
for asynchronous HTTP requests.
|
for asynchronous HTTP requests.
|
||||||
'''
|
'''
|
||||||
|
|
@ -20,8 +21,20 @@ class DataService():
|
||||||
|
|
||||||
# Host URL for the CAiMIRA Data Service API
|
# Host URL for the CAiMIRA Data Service API
|
||||||
host: str = 'https://caimira-data-api.app.cern.ch'
|
host: str = 'https://caimira-data-api.app.cern.ch'
|
||||||
|
|
||||||
async def login(self):
|
# Cached access token
|
||||||
|
_access_token: typing.Optional[str] = None
|
||||||
|
|
||||||
|
def _is_valid(self, access_token):
|
||||||
|
# decode access_token
|
||||||
|
# check validity
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _login(self):
|
||||||
|
if self._is_valid(self._access_token):
|
||||||
|
return self._access_token
|
||||||
|
|
||||||
|
# invalid access_token, fetch it again
|
||||||
client_email = self.credentials["data_service_client_email"]
|
client_email = self.credentials["data_service_client_email"]
|
||||||
client_password = self.credentials['data_service_client_password']
|
client_password = self.credentials['data_service_client_password']
|
||||||
|
|
||||||
|
|
@ -41,9 +54,12 @@ class DataService():
|
||||||
),
|
),
|
||||||
raise_error=True)
|
raise_error=True)
|
||||||
|
|
||||||
return json.loads(response.body)['access_token']
|
self._access_token = json.loads(response.body)['access_token']
|
||||||
|
return self._access_token
|
||||||
async def fetch(self, access_token: str):
|
|
||||||
|
async def fetch(self):
|
||||||
|
access_token = await self._login()
|
||||||
|
|
||||||
http_client = AsyncHTTPClient()
|
http_client = AsyncHTTPClient()
|
||||||
headers = {'Authorization': f'Bearer {access_token}'}
|
headers = {'Authorization': f'Bearer {access_token}'}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class DataServiceTests(unittest.TestCase):
|
||||||
mock_http_client.return_value.fetch = mock_fetch
|
mock_http_client.return_value.fetch = mock_fetch
|
||||||
|
|
||||||
# Call the login method
|
# Call the login method
|
||||||
access_token = await self.data_service.login()
|
access_token = await self.data_service._login()
|
||||||
|
|
||||||
# Assert that the access token is returned correctly
|
# Assert that the access token is returned correctly
|
||||||
self.assertEqual(access_token, "dummy_token")
|
self.assertEqual(access_token, "dummy_token")
|
||||||
|
|
@ -60,8 +60,8 @@ class DataServiceTests(unittest.TestCase):
|
||||||
mock_http_client.return_value.fetch = mock_fetch
|
mock_http_client.return_value.fetch = mock_fetch
|
||||||
|
|
||||||
# Call the fetch method with a mock access token
|
# Call the fetch method with a mock access token
|
||||||
access_token = "dummy_token"
|
self.data_service._access_token = "dummy_token"
|
||||||
data = await self.data_service.fetch(access_token)
|
data = await self.data_service.fetch()
|
||||||
|
|
||||||
# Assert that the data is returned correctly
|
# Assert that the data is returned correctly
|
||||||
self.assertEqual(data, {"data": "dummy_data"})
|
self.assertEqual(data, {"data": "dummy_data"})
|
||||||
|
|
@ -80,8 +80,8 @@ class DataServiceTests(unittest.TestCase):
|
||||||
mock_http_client.return_value.fetch = mock_fetch
|
mock_http_client.return_value.fetch = mock_fetch
|
||||||
|
|
||||||
# Call the fetch method with a mock access token
|
# Call the fetch method with a mock access token
|
||||||
access_token = "dummy_token"
|
self.data_service._access_token = "dummy_token"
|
||||||
data = await self.data_service.fetch(access_token)
|
data = await self.data_service.fetch()
|
||||||
|
|
||||||
# Assert that the fetch method returns None in case of an error
|
# Assert that the fetch method returns None in case of an error
|
||||||
self.assertIsNone(data)
|
self.assertIsNone(data)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue