Skip to content

Commit

Permalink
task/WG-138-WG-140: improve logs for analytics to include application…
Browse files Browse the repository at this point in the history
… and related DS system (#161)

* Improve logs for anlytics to include application and related DesignSafe project/systems

* Add unit tests

* Remove unused user1

* Use 'project_uuid' for uuids and 'project' for id

* Move analytics related logging to features endpoint

* Check query params for analytic information (due to WG-191)

Headers can't be used due to https://tacc-main.atlassian.net/browse/WG-191

* Add public view information to log statement

* Fix public-view check

* Update tests

* Fix linting
  • Loading branch information
nathanfranklin authored Dec 15, 2023
1 parent 2d612c6 commit 553106f
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 7 deletions.
2 changes: 2 additions & 0 deletions geoapi/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class Project(Base):
id = Column(Integer, primary_key=True)
uuid = Column(UUID(as_uuid=True), default=uuid.uuid4, nullable=False)
tenant_id = Column(String, nullable=False)
# Project system_id/system_path really not used except for analytics.
# This could be improved; see https://jira.tacc.utexas.edu/browse/WG-185
system_id = Column(String, nullable=True)
system_path = Column(String, nullable=True)
system_file = Column(String, nullable=True)
Expand Down
24 changes: 20 additions & 4 deletions geoapi/routes/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,15 @@ def get(self):
uuid_subset = query.get("uuid")

if uuid_subset:
logger.info("Get a subset of projects for user:{} projects:{}".format(u.username, uuid_subset))
logger.info(f"Getting a subset of projects for user:{u.username} project_uuid:{uuid_subset}")

# Check each project and abort if user (authenticated or anonymous) can't access the project
subset = [check_access_and_get_project(request.current_user, uuid=uuid, allow_public_use=True) for uuid in uuid_subset]
return subset
else:
if is_anonymous(u):
abort(403, "Access denied")
logger.info("Get all projects for user:{}".format(u.username))
logger.info(f"Get all projects for user:{u.username}")
return ProjectsService.list(db_session, u)

@api.doc(id="createProject",
Expand Down Expand Up @@ -290,8 +291,23 @@ class ProjectFeaturesResource(Resource):
@api.marshal_with(feature_collection_model, as_list=True)
@project_permissions_allow_public
def get(self, projectId: int):
logger.info("Get features of project:{} for user:{}".format(
projectId, request.current_user.username))
# Following log is for analytics, see https://confluence.tacc.utexas.edu/display/DES/Hazmapper+Logging
application = request.headers.get('X-Geoapi-Application')
if application is None:
# Check if in query parameters due to https://tacc-main.atlassian.net/browse/WG-192 and WG-191 */
application = request.args.get('application')

if application is None:
application = "Unknown"

from geoapi.routes.public_projects import PublicProjectFeaturesResource
is_public_view = issubclass(self.__class__, PublicProjectFeaturesResource)

prj = ProjectsService.get(db_session, project_id=projectId, user=request.current_user)
logger.info(f"Get features of project for user:{request.current_user.username} application:{application}"
f" public_view:{is_public_view} project_uuid:{prj.uuid} project:{prj.id} tapis_system_id:{prj.system_id} "
f"tapis_system_path:{prj.system_path}")

query = self.parser.parse_args()
return ProjectsService.getFeatures(db_session, projectId, query)

Expand Down
35 changes: 32 additions & 3 deletions geoapi/tests/api_tests/test_projects_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ def test_get_projects_using_single_uuid(test_client, projects_fixture, projects_
assert data[0]["deletable"] is True


def test_get_projects_using_single_uuid_observable_project(test_client, observable_projects_fixture, user1):
resp = test_client.get('/projects/',
query_string='uuid={}'.format(observable_projects_fixture.project.uuid),
headers={'x-jwt-assertion-test': user1.jwt})
data = resp.get_json()
assert resp.status_code == 200
assert len(data) == 1
assert data[0]["uuid"] == str(observable_projects_fixture.project.uuid)


def test_get_projects_using_single_uuid_that_is_wrong(test_client, user1):
resp = test_client.get('/projects/',
query_string='uuid={}'.format(uuid.uuid4()),
Expand Down Expand Up @@ -262,20 +272,39 @@ def test_get_point_cloud(test_client, projects_fixture, point_cloud_fixture, use
assert resp.status_code == 200


def test_get_project_features_empty(test_client, projects_fixture, user1):
def test_get_project_features_empty(test_client, projects_fixture, user1, caplog):
resp = test_client.get(f'/projects/{projects_fixture.id}/features/',
headers={'x-jwt-assertion-test': user1.jwt})
assert resp.status_code == 200

data = resp.get_json()
assert len(data['features']) == 0
log_statement_for_analytics = (f"Get features of project for user:{user1.username} application:Unknown public_view:False "
f"project_uuid:{projects_fixture.uuid} project:{projects_fixture.id} "
f"tapis_system_id:None tapis_system_path:None")
assert log_statement_for_analytics in caplog.text


def test_get_project_features_empty_public_access(test_client, public_projects_fixture):
resp = test_client.get('/projects/{}/features/'.format(public_projects_fixture.id))
def test_get_project_features_empty_public_access(test_client, public_projects_fixture, caplog):
resp = test_client.get('/public-projects/{}/features/'.format(public_projects_fixture.id))
assert resp.status_code == 200
data = resp.get_json()
assert len(data['features']) == 0
log_statement_for_analytics = (f"Get features of project for user:Guest_Unknown application:Unknown public_view:True "
f"project_uuid:{public_projects_fixture.uuid} project:{public_projects_fixture.id} "
f"tapis_system_id:None tapis_system_path:None")
assert log_statement_for_analytics in caplog.text


def test_get_project_features_analytics_with_query_params(test_client, public_projects_fixture, caplog):
# send analytics-related params to projects endpoint only (until we use headers again
# in https://tacc-main.atlassian.net/browse/WG-192)
query = {'application': 'hazmapper', 'guest_uuid': "1234"}
test_client.get('/public-projects/{}/features/'.format(public_projects_fixture.id), query_string=query)
log_statement_for_analytics = (f"Get features of project for user:Guest_1234 application:hazmapper public_view:True "
f"project_uuid:{public_projects_fixture.uuid} project:{public_projects_fixture.id} "
f"tapis_system_id:None tapis_system_path:None")
assert log_statement_for_analytics in caplog.text


def test_get_project_features_single_feature(test_client, projects_fixture, feature_fixture, user1):
Expand Down
6 changes: 6 additions & 0 deletions geoapi/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ def observable_projects_fixture():
path="/testPath",
watch_content=True
)

# Project system_id/system_path really not used except for analytics.
# This could be improved; see https://jira.tacc.utexas.edu/browse/WG-185
proj.system_id = obs.system_id
proj.system_path = obs.path

obs.project = proj
proj.users.append(u1)
db_session.add(obs)
Expand Down
3 changes: 3 additions & 0 deletions geoapi/utils/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ def wrapper(*args, **kwargs):
except ValueError:
# if not JWT information is provided in header, then this is a guest user
guest_uuid = request.headers.get('X-Guest-UUID')
if guest_uuid is None:
# Check if in query parameters due to https://tacc-main.atlassian.net/browse/WG-192 and WG-191 */
guest_uuid = request.args.get('guest_uuid')
user = AnonymousUser(guest_unique_id=guest_uuid)
if user is None:
try:
Expand Down

0 comments on commit 553106f

Please sign in to comment.