Commit fb0c0801 authored by Lauri Himanen's avatar Lauri Himanen
Browse files

Added new report endpoint for user error reporting. This endpoint requires...

Added new report endpoint for user error reporting. This endpoint requires proper authentication through keycloak. Added additional checks to phonon data.
parent aabbf939
Pipeline #79279 failed with stages
in 41 minutes and 37 seconds
Subproject commit 134d89fc0a25ea367c454d299c8603af893b3b66 Subproject commit 15cb97c9cf61fd59309a50f6700b99733fc6a659
...@@ -17,6 +17,7 @@ The encyclopedia API of the nomad@FAIRDI APIs. ...@@ -17,6 +17,7 @@ The encyclopedia API of the nomad@FAIRDI APIs.
""" """
import re import re
import math import math
import json
import numpy as np import numpy as np
from flask_restplus import Resource, abort, fields, marshal from flask_restplus import Resource, abort, fields, marshal
...@@ -24,12 +25,12 @@ from flask import request ...@@ -24,12 +25,12 @@ from flask import request
from elasticsearch_dsl import Search, Q, A from elasticsearch_dsl import Search, Q, A
from elasticsearch_dsl.utils import AttrDict from elasticsearch_dsl.utils import AttrDict
from nomad import config, files from nomad import config, files, infrastructure
from nomad.units import ureg from nomad.units import ureg
from nomad.atomutils import get_hill_decomposition from nomad.atomutils import get_hill_decomposition
from nomad.datamodel.datamodel import EntryArchive from nomad.datamodel.datamodel import EntryArchive
from .api import api from .api import api
from .common import enable_gzip from .auth import authenticate
ns = api.namespace("encyclopedia", description="Access encyclopedia metadata.") ns = api.namespace("encyclopedia", description="Access encyclopedia metadata.")
re_formula = re.compile(r"([A-Z][a-z]?)(\d*)") re_formula = re.compile(r"([A-Z][a-z]?)(\d*)")
...@@ -791,7 +792,6 @@ calculations_result = api.model("calculations_result", { ...@@ -791,7 +792,6 @@ calculations_result = api.model("calculations_result", {
@ns.route("/materials/<string:material_id>/calculations") @ns.route("/materials/<string:material_id>/calculations")
class EncCalculationsResource(Resource): class EncCalculationsResource(Resource):
@enable_gzip()
@api.response(404, "Suggestion not found") @api.response(404, "Suggestion not found")
@api.response(400, "Bad request") @api.response(400, "Bad request")
@api.response(200, "Metadata send", fields.Raw) @api.response(200, "Metadata send", fields.Raw)
...@@ -1133,7 +1133,6 @@ calculation_property_result = api.model("calculation_property_result", { ...@@ -1133,7 +1133,6 @@ calculation_property_result = api.model("calculation_property_result", {
@ns.route("/materials/<string:material_id>/calculations/<string:calc_id>") @ns.route("/materials/<string:material_id>/calculations/<string:calc_id>")
class EncCalculationResource(Resource): class EncCalculationResource(Resource):
@enable_gzip()
@api.response(404, "Material or calculation not found") @api.response(404, "Material or calculation not found")
@api.response(400, "Bad request") @api.response(400, "Bad request")
@api.response(200, "Metadata send", fields.Raw) @api.response(200, "Metadata send", fields.Raw)
...@@ -1283,6 +1282,63 @@ class EncCalculationResource(Resource): ...@@ -1283,6 +1282,63 @@ class EncCalculationResource(Resource):
return result, 200 return result, 200
report_query = api.model("report_query", {
"server": fields.String,
"username": fields.String,
"email": fields.String,
"first_name": fields.String,
"last_name": fields.String,
"category": fields.String,
"subcategory": fields.String(allow_null=True),
"representatives": fields.Raw(Raw=True),
"message": fields.String,
})
@ns.route("/materials/<string:material_id>/reports")
class ReportsResource(Resource):
@api.response(500, "Error sending report")
@api.response(400, "Bad request")
@api.response(204, "Report succesfully sent", fields.Raw)
@api.expect(calculation_property_query, validate=False)
@api.marshal_with(calculation_property_result, skip_none=True)
@api.doc("enc_calculation")
@authenticate(required=True)
def post(self, material_id):
# Get query parameters as json
try:
query = marshal(request.get_json(), report_query)
except Exception as e:
abort(400, message=str(e))
# Send the report as an email
query["material_id"] = material_id
representatives = query["representatives"]
if representatives is not None:
representatives = "\n" + "\n".join([" {}: {}".format(key, value) for key, value in representatives.items()])
query["representatives"] = representatives
mail = (
"Server: {server}\n\n"
"Username: {username}\n"
"First name: {first_name}\n"
"Last name: {last_name}\n"
"Email: {email}\n\n"
"Material id: {material_id}\n"
"Category: {category}\n"
"Subcategory: {subcategory}\n"
"Representative calculations: {representatives}\n\n"
"Message: {message}"
).format(**query)
try:
infrastructure.send_mail(
name="webmaster", email="lauri.himanen@gmail.com", message=mail, subject='Encyclopedia error report')
except Exception as e:
abort(500, message="Error sending error report email.")
print(mail)
return "", 204
def read_archive(upload_id: str, calc_id: str) -> EntryArchive: def read_archive(upload_id: str, calc_id: str) -> EntryArchive:
"""Used to read data from the archive. """Used to read data from the archive.
......
...@@ -453,7 +453,6 @@ def send_mail(name: str, email: str, message: str, subject: str): ...@@ -453,7 +453,6 @@ def send_mail(name: str, email: str, message: str, subject: str):
msg = MIMEText(message) msg = MIMEText(message)
msg['Subject'] = subject msg['Subject'] = subject
msg['From'] = 'The nomad team <%s>' % config.mail.from_address
msg['To'] = name msg['To'] = name
to_addrs = [email] to_addrs = [email]
......
...@@ -207,6 +207,7 @@ class EncyclopediaNormalizer(Normalizer): ...@@ -207,6 +207,7 @@ class EncyclopediaNormalizer(Normalizer):
""" """
sec_enc = self.backend.entry_archive.section_metadata.m_create(EncyclopediaMetadata) sec_enc = self.backend.entry_archive.section_metadata.m_create(EncyclopediaMetadata)
status_enums = EncyclopediaMetadata.status.type status_enums = EncyclopediaMetadata.status.type
calc_enums = Calculation.calculation_type.type
# Do nothing if section_run is not present # Do nothing if section_run is not present
if self.section_run is None: if self.section_run is None:
...@@ -261,7 +262,6 @@ class EncyclopediaNormalizer(Normalizer): ...@@ -261,7 +262,6 @@ class EncyclopediaNormalizer(Normalizer):
"unsupported method type for encyclopedia", "unsupported method type for encyclopedia",
enc_status=status_enums.unsupported_method_type, enc_status=status_enums.unsupported_method_type,
) )
calc_enums = Calculation.calculation_type.type
if calc_type != calc_enums.phonon_calculation: if calc_type != calc_enums.phonon_calculation:
return return
...@@ -292,7 +292,7 @@ class EncyclopediaNormalizer(Normalizer): ...@@ -292,7 +292,7 @@ class EncyclopediaNormalizer(Normalizer):
if functional_type is None: if functional_type is None:
sec_enc.status = status_enums.unsupported_method_type sec_enc.status = status_enums.unsupported_method_type
self.logger.info( self.logger.info(
"unsupported functinoal type for encyclopedia", "unsupported functional type for encyclopedia",
enc_status=status_enums.unsupported_method_type, enc_status=status_enums.unsupported_method_type,
) )
return return
......
...@@ -444,7 +444,7 @@ class Calc(Proc): ...@@ -444,7 +444,7 @@ class Calc(Proc):
# Get encyclopedia method information directly from the referenced calculation. # Get encyclopedia method information directly from the referenced calculation.
ref_enc_method = ref_archive.section_metadata.encyclopedia.method ref_enc_method = ref_archive.section_metadata.encyclopedia.method
if ref_enc_method is None or len(ref_enc_method) == 0: if ref_enc_method is None or len(ref_enc_method) == 0 or ref_enc_method.functional_type is None:
raise ValueError("No method information available in referenced calculation.") raise ValueError("No method information available in referenced calculation.")
backend.entry_archive.section_metadata.encyclopedia.method = ref_enc_method backend.entry_archive.section_metadata.encyclopedia.method = ref_enc_method
...@@ -455,6 +455,7 @@ class Calc(Proc): ...@@ -455,6 +455,7 @@ class Calc(Proc):
self._entry_metadata.dft.xc_functional = ref_archive.section_metadata.dft.xc_functional self._entry_metadata.dft.xc_functional = ref_archive.section_metadata.dft.xc_functional
self._entry_metadata.dft.basis_set = ref_archive.section_metadata.dft.basis_set self._entry_metadata.dft.basis_set = ref_archive.section_metadata.dft.basis_set
self._entry_metadata.dft.update_group_hash() self._entry_metadata.dft.update_group_hash()
self._entry_metadata.encyclopedia.status = EncyclopediaMetadata.status.type.success
except Exception as e: except Exception as e:
logger.error("Could not retrieve method information for phonon calculation.", exception=e) logger.error("Could not retrieve method information for phonon calculation.", exception=e)
if self._entry_metadata.encyclopedia is None: if self._entry_metadata.encyclopedia is None:
......
...@@ -520,9 +520,9 @@ def test_phonon(phonon: EntryArchive): ...@@ -520,9 +520,9 @@ def test_phonon(phonon: EntryArchive):
dos = prop.phonon_dos dos = prop.phonon_dos
thermo_props = prop.thermodynamical_properties thermo_props = prop.thermodynamical_properties
assert calc_type == Calculation.calculation_type.type.phonon_calculation assert calc_type == Calculation.calculation_type.type.phonon_calculation
assert status == EncyclopediaMetadata.status.type.success
# TODO: Check method information # The method information is filled after the whole upload has been processed.
assert status == EncyclopediaMetadata.status.type.unsupported_method_type
# Check dos # Check dos
assert dos is not None assert dos is not None
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment