New query route - #1536
Conversation
| return buffer | ||
|
|
||
|
|
||
| @routes.route('/api/project/<string:project_string_id>/files', methods = ['GET']) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
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 | ||
|
|
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Curious why this is done inline vs top-level (copied from other Policy checks in this class)
| self.compare_op_raw = compare_op_raw | ||
|
|
||
| self.member = member | ||
| self.project = project |
There was a problem hiding this comment.
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
| 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? |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
No need for directory property, and Im pretty sure it isnt used anywhere
| 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 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
Not sure if its common convention to define helper methods as part of the test class?
Since we error here should return too
|
|
||
|
|
||
| @routes.route('/api/project/<string:project_string_id>/files', methods = ['GET']) | ||
| @Project_permissions.user_has_project([ |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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?
Description
/file/list.