diff --git a/swh/graphql/resolvers/directory_entry.py b/swh/graphql/resolvers/directory_entry.py --- a/swh/graphql/resolvers/directory_entry.py +++ b/swh/graphql/resolvers/directory_entry.py @@ -17,7 +17,8 @@ @property def targetType(self): # To support the schema naming convention - return self._node.type + mapping = {"file": "content", "dir": "directory", "rev": "revision"} + return mapping.get(self._node.type) class DirectoryEntryNode(BaseDirectoryEntryNode): @@ -51,4 +52,10 @@ # To remove localpagination, just drop the paginated call # STORAGE-TODO entries = self.archive.get_directory_entries(self.obj.swhid.object_id).results + name_include = self.kwargs.get("nameInclude") + if name_include is not None: + # STORAGE-TODO, move this filter to swh-storage + entries = [ + x for x in entries if name_include.lower().encode() in x.name.lower() + ] return utils.paginated(entries, self._get_first_arg(), self._get_after_arg()) diff --git a/swh/graphql/resolvers/resolver_factory.py b/swh/graphql/resolvers/resolver_factory.py --- a/swh/graphql/resolvers/resolver_factory.py +++ b/swh/graphql/resolvers/resolver_factory.py @@ -52,9 +52,9 @@ "directory-entry": DirectoryEntryNode, "content": ContentNode, "content-by-hash": HashContentNode, - "dir-entry-file": TargetContentNode, - "dir-entry-dir": TargetDirectoryNode, - "dir-entry-rev": TargetRevisionNode, + "dir-entry-content": TargetContentNode, + "dir-entry-directory": TargetDirectoryNode, + "dir-entry-revision": TargetRevisionNode, "search-result-origin": TargetOriginNode, "search-result-snapshot": TargetSnapshotNode, "search-result-revision": TargetRevisionNode, diff --git a/swh/graphql/resolvers/resolvers.py b/swh/graphql/resolvers/resolvers.py --- a/swh/graphql/resolvers/resolvers.py +++ b/swh/graphql/resolvers/resolvers.py @@ -192,7 +192,7 @@ """ directory entry target can be a directory, content or a revision """ - resolver_type = f"dir-entry-{obj.type}" + resolver_type = f"dir-entry-{obj.targetType}" resolver = get_node_resolver(resolver_type) return resolver(obj, info, **kw) diff --git a/swh/graphql/schema/schema.graphql b/swh/graphql/schema/schema.graphql --- a/swh/graphql/schema/schema.graphql +++ b/swh/graphql/schema/schema.graphql @@ -704,9 +704,9 @@ Possible directory entry types """ enum DirectoryEntryTargetType { - dir - file - rev + directory + content + revision } """ @@ -756,6 +756,11 @@ Returns the page after this cursor """ after: String + + """ + Filter by entry name + """ + nameInclude: String ): DirectoryEntryConnection } diff --git a/swh/graphql/tests/functional/test_directory_entry.py b/swh/graphql/tests/functional/test_directory_entry.py --- a/swh/graphql/tests/functional/test_directory_entry.py +++ b/swh/graphql/tests/functional/test_directory_entry.py @@ -12,6 +12,11 @@ from ..data import get_directories, get_directories_with_nested_path +def get_target_type(target_type): + mapping = {"file": "content", "dir": "directory", "rev": "revision"} + return mapping.get(target_type) + + def test_get_directory_entry_missing_path(client): directory = get_directories()[0] path = "missing" @@ -87,7 +92,7 @@ assert data["directoryEntry"] == { "name": {"text": entry["name"].decode()}, "target": {"swhid": str(swhid)} if swhid else None, - "targetType": entry["type"], + "targetType": get_target_type(entry["type"]), } @@ -112,8 +117,37 @@ directory_entries = data["directory"]["entries"]["nodes"] assert len(directory_entries) == len(directory.entries) output = [ - {"name": {"text": de.name.decode()}, "targetType": de.type} + {"name": {"text": de.name.decode()}, "targetType": get_target_type(de.type)} for de in directory.entries ] for each_entry in output: assert each_entry in directory_entries + + +@pytest.mark.parametrize("directory", get_directories()) +def test_directory_entry_connection_filter_by_name(client, directory): + storage = server.get_storage() + for dir_entry in storage.directory_ls(directory.id): + name_include = dir_entry["name"][:-1].decode() + query_str = """ + { + directory(swhid: "%s") { + swhid + entries(nameInclude: "%s") { + nodes { + targetType + name { + text + } + } + } + } + } + """ % ( + directory.swhid(), + name_include, + ) + data, _ = utils.get_query_response(client, query_str) + for entry in data["directory"]["entries"]["nodes"]: + assert name_include in entry["name"]["text"] + assert entry["targetType"] == get_target_type(dir_entry["type"]) diff --git a/swh/graphql/tests/unit/resolvers/test_resolver_factory.py b/swh/graphql/tests/unit/resolvers/test_resolver_factory.py --- a/swh/graphql/tests/unit/resolvers/test_resolver_factory.py +++ b/swh/graphql/tests/unit/resolvers/test_resolver_factory.py @@ -32,8 +32,9 @@ ("release-content", "TargetContentNode"), ("directory", "DirectoryNode"), ("content", "ContentNode"), - ("dir-entry-dir", "TargetDirectoryNode"), - ("dir-entry-file", "TargetContentNode"), + ("dir-entry-directory", "TargetDirectoryNode"), + ("dir-entry-content", "TargetContentNode"), + ("dir-entry-revision", "TargetRevisionNode"), ("search-result-snapshot", "TargetSnapshotNode"), ("search-result-revision", "TargetRevisionNode"), ("search-result-release", "TargetReleaseNode"), diff --git a/swh/graphql/tests/unit/resolvers/test_resolvers.py b/swh/graphql/tests/unit/resolvers/test_resolvers.py --- a/swh/graphql/tests/unit/resolvers/test_resolvers.py +++ b/swh/graphql/tests/unit/resolvers/test_resolvers.py @@ -103,14 +103,15 @@ @pytest.mark.parametrize( "target_type, node_cls", [ - ("dir", resolvers.directory.TargetDirectoryNode), - ("file", resolvers.content.TargetContentNode), + ("directory", resolvers.directory.TargetDirectoryNode), + ("content", resolvers.content.TargetContentNode), + ("revision", resolvers.revision.TargetRevisionNode), ], ) def test_directory_entry_target_resolver( self, mocker, dummy_node, target_type, node_cls ): - obj = mocker.Mock(type=target_type) + obj = mocker.Mock(targetType=target_type) mock_get = mocker.patch.object(node_cls, "_get_node", return_value=dummy_node) node_obj = rs.directory_entry_target_resolver(obj, None) assert isinstance(node_obj, node_cls)