Skip to content

New query route - #1536

Closed
francescov1 wants to merge 11 commits into
diffgram:masterfrom
francescov1:fvirga/new-query-route
Closed

New query route#1536
francescov1 wants to merge 11 commits into
diffgram:masterfrom
francescov1:fvirga/new-query-route

Conversation

@francescov1

@francescov1 francescov1 commented Aug 22, 2023

Copy link
Copy Markdown
Contributor

Description

  • New query route to eventually replace /file/list.
  • Refactor File_Browser
  • Added some unit tests and validation
  • Small cleanups

return buffer


@routes.route('/api/project/<string:project_string_id>/files', methods = ['GET'])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This route is mostly a copy of the original route /file/list below, with a few tweaks (discussed in comments)

The route is now GET /project/<project_id>/files and accepts a query parameter called query. This feels more natural, since we are fetching a list of files and can optionally provide a query string to filter the files

return jsonify("Error no metadata"), 400

# Add query to the proposed metadata so that File_Browser doesnt need to be changed
metadata_proposed['query'] = query

@francescov1 francescov1 Aug 22, 2023

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid changes to File_Browser, I simply added the query field to metadata_proposed.

Wasnt sure how much of the metadata_proposed and File_Browser we want to change, so left them mostly intact for now

metadata = file_browser_instance.metadata), 200


# TODO: Would like to take the `directory` param out of this class, but there's a lot of dependencies on it still.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesnt make sense to pass a single directory to File_Browser since the queries support multiple directories. But there's a few areas in this class that depend on it, so left as is for now. Should I look at removing it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general I wasnt sure how much we want to change File_Browser since its used in a few different places. We could just duplicate it for now so that we avoid breaking anything but start refactoring the new version of it?

return False
file_count += count
else:
# TODO: I think we can remove this now?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this policy check can be removed now, but not sure if anywhere else in code needs it

from methods.regular.regular_api import *
from methods.source_control.file.remove import remove_core as file_remove_core
from methods.source_control.file.file_browser import File_Browser

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These imports were unused

# TODO: add tests
def can_member_view_datasets(session, project, member, dataset_ids):
# TODO: How come we do this inline?
from shared.database.permissions.roles import ValidObjectTypes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious why this is done inline vs top-level (copied from other Policy checks in this class)

Comment thread shared/query_engine/expressions/dataset.py
self.compare_op_raw = compare_op_raw

self.member = member
self.project = project

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added these two properties to the Expression class, I think there may be some more cleanup that can be done now that we have these available

Comment thread shared/query_engine/query_creator.py Outdated
self.member = member
self.directory = directory
# Additional security check just for sanity
# TODO: This is duplicated from sqlalchemy_query_executor __init__, I think we can remove?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be removed, exact same check is made in both the query creator and the query executor

"""

def __init__(self, tree, project, member, directory = None):
def __init__(self, tree, project, member):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for directory property, and Im pretty sure it isnt used anywhere

@github-actions

github-actions Bot commented Aug 23, 2023

Copy link
Copy Markdown

Unit Test Results

0 tests  ±0   0 ✔️ ±0   0s ⏱️ ±0s
0 suites ±0   0 💤 ±0 
0 files   ±0   0 ±0 

Results for commit 1c3f423. ± Comparison against base commit 0df190f.

♻️ This comment has been updated with latest results.

mock_perm_result = Mock()
mock_perm_result.allow_all = False
mock_perm_result.allowed_object_id_list = [ds1.id, ds2.id, ds3.id]
mock_get_dataset_viewing_permissions.return_value = mock_perm_result

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

list now depends on get_dataset_viewing_permissions, so we can just mock out that method rather than using real RoleMembers

)
dirs_id_list = [x.id for x in dirs]
self.assertEqual(len(dirs), 6)
self.assertEqual(len(dirs), 3)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this was checking 6 before, but with new mocking we only get the 3 directories created above


@patch("shared.database.source_control.working_dir.PolicyEngine")
def test_get_dataset_viewing_permissions(self, MockPolicyEngine):
# Arrange

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A pattern I commonly use is Arrange, Act, Assert. Arrange is for setup, mocking & defining variables, act is for triggering the action to test, and assert is for assertions.

The idea is to split every test method into these three sections. It helps ensure tests dont get too big, keeps them clean and makes it easy to understand for others. When you're expecting this pattern for a test, its much quicker to parse & understand it.

Lemme know your thoughts!

self.member = self.auth_api.member

def test_build_expression_subquery(self):
def initialize_compare_expression(self):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if its common convention to define helper methods as part of the test class?



@routes.route('/api/project/<string:project_string_id>/files', methods = ['GET'])
@Project_permissions.user_has_project([

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New route, only changes here are the new query URL param, and new route path GET /api/project/<string:project_string_id>/files

self.log['error']['file_view_mode'] = 'Invalid file_view_mode "{}"'.format(self.metadata['file_view_mode'])
return None
ignore_id_list = None
def get_query_file_list(self):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've pulled apart the file_view_core function into smaller functions, while trying to keep all logic the same. file_view_core still exists and does the same thing, but it calls a set of other functions instead of keeping all code inline.

I tried to leave all logic the same, and tested as much as I could, but someone who understands the full system should do a good QA to ensure I didnt break anything.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get_query_file_list method replaces this block of code

# For creating / viewing Jobs
return working_dir_file_list, count

def get_job_file_list(self):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_job_file_list replaces this block of code

output_file_list.append(file_serialized)
output_file_list[index_file_attach]['attached_to_job'] = True

def get_all_file_list(self, ignore_id_list = None):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_all_file_list replaces this block & this block of code

)
if not perm_result.allowed:
self.log['error']['unauthorized'] = f'Cannot view dataset ID: {self.directory.id}'
return None, None

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, the following error handling would occur in this case:

resp = jsonify(log=self.log)
resp.status = 401
raise Unauthorized(response = resp)

now instead we just write to the log, since it allows us to pull out the error handling here. I tested the behaviour of both from the UI and found there was no difference, but let me know if its important to still raise an exception here

working_dir_file_list = query.all()
return working_dir_file_list, count

def add_pagination_metadata(self, file_count):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add_pagination_metadata replaces this block of code

for index_file, file in enumerate(working_dir_file_list):
if self.metadata['file_view_mode'] == 'explorer':
file_serialized = file.serialize_with_annotations(self.session, regen_url = self.metadata["regen_url"])
def serialize_file_list(self, file_list):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

serialize_file_list replaces this block of code

issues_filter = self.metadata["issues_filter"],
offset = self.metadata["start_index"],
original_filename = self.metadata['search_term'],
order_by_class_and_attribute = File.id,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed we have logic to determine order_by_class_and_attribute above, but then we dont use the result and just set the field to File.id. Is this a bug or on purpose?

@francescov1
francescov1 marked this pull request as ready for review August 25, 2023 23:33
@francescov1 francescov1 changed the title WIP: New query route New query route Aug 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants