Skip to content

Commit

Permalink
Pilot 4734: distinguish project folders and name folders when listing (
Browse files Browse the repository at this point in the history
…#133)

* add [p] prefix for project folder when listing items under project

* group itemprefix class with itemtype class

* add double quotation when item name contains space

* change enum type PROJECTFOLDER to SHAREDFOLDER

* bumpup versions

---------

Co-authored-by: zhiren <zzhan@indocresearch.org>
  • Loading branch information
colorzzr and zhiren authored Apr 4, 2024
1 parent 873d290 commit 8b37666
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 48 deletions.
22 changes: 0 additions & 22 deletions app/models/folder.py

This file was deleted.

38 changes: 38 additions & 0 deletions app/models/item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright (C) 2023-2024 Indoc Systems
#
# Contact Indoc Systems for any questions regarding the use of this source code.

from enum import Enum


class ItemType(str, Enum):
"""The class to reflect the type of item in database."""

FILE = 'file'
Folder = 'folder'
NAMEFOLDER = 'name_folder'
SHAREDFOLDER = 'project_folder'

@classmethod
def get_type_from_keyword(self, keyword: str):
"""The function will return the type of the item based on the keyword.
- name folder will have keyword 'namefolder' as input
- project folder will not have any keyword
"""

alternative_mapping = {
'projectfolder': self.SHAREDFOLDER,
}

return alternative_mapping.get(keyword, self.NAMEFOLDER)

def get_prefix_by_type(self) -> str:
"""Get the prefix for the folder type."""

prefix = {
self.NAMEFOLDER: '',
self.SHAREDFOLDER: 'shared/',
}

return prefix.get(self.value, '')
15 changes: 13 additions & 2 deletions app/services/file_manager/file_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import app.services.logger_services.log_functions as logger
from app.configs.app_config import AppConfig
from app.configs.user_config import UserConfig
from app.models.item import ItemType
from app.models.service_meta_class import MetaService
from app.services.output_manager.error_handler import ECustomizedError
from app.services.output_manager.error_handler import SrvErrorHandler
Expand Down Expand Up @@ -59,10 +60,20 @@ def list_files(self, paths, zone, page, page_size):
# then format the console output
files, folders = '', ''
for f in res:
if 'file' == f.get('type'):
item_type = ItemType(f.get('type'))
# if there is space within the nane add double quotation to aviod confusion
if ' ' in f.get('name'):
f['name'] = f'"{f.get("name")}"'

if item_type == ItemType.FILE:
files = files + f.get('name') + ' ...'
elif f.get('type') in ['folder', 'name_folder', 'project_folder']:
else:
# add [p] in front of the project folder
if item_type == ItemType.SHAREDFOLDER:
f['name'] = f'[p]{f.get("name")}'

folders = folders + f"\033[34m{f.get('name')}\033[0m ..."

f_string = folders + files
return f_string

Expand Down
8 changes: 4 additions & 4 deletions app/services/file_manager/file_upload/file_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import app.services.logger_services.log_functions as logger
import app.services.output_manager.message_handler as mhandler
from app.configs.app_config import AppConfig
from app.models.folder import FolderType
from app.models.item import ItemType
from app.services.file_manager.file_upload.models import FileObject
from app.services.file_manager.file_upload.models import ItemStatus
from app.services.file_manager.file_upload.models import UploadType
Expand All @@ -44,7 +44,7 @@ def compress_folder_to_zip(path):


def assemble_path(
f: str, target_folder: str, project_code: str, folder_type: FolderType, zone: str
f: str, target_folder: str, project_code: str, folder_type: ItemType, zone: str
) -> Tuple[str, Dict, bool, str]:
'''
Summary:
Expand Down Expand Up @@ -80,8 +80,8 @@ def assemble_path(
current_folder_node = target_folder if os.path.isfile(f) else current_file_path
create_folder_flag = False
# add prefix to folder
current_folder_node = folder_type.get_prefix() + current_folder_node
target_folder = folder_type.get_prefix() + target_folder
current_folder_node = folder_type.get_prefix_by_type() + current_folder_node
target_folder = folder_type.get_prefix_by_type() + target_folder

if len(current_file_path.split('/')) > 2:
sub_path = target_folder.split('/')
Expand Down
11 changes: 5 additions & 6 deletions app/utils/aggregated.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from app.configs.app_config import AppConfig
from app.configs.config import ConfigClass
from app.configs.user_config import UserConfig
from app.models.folder import FolderType
from app.models.item import ItemType
from app.services.output_manager.error_handler import ECustomizedError
from app.services.output_manager.error_handler import SrvErrorHandler
from app.services.user_authentication.decorator import require_valid_token
Expand Down Expand Up @@ -166,7 +166,7 @@ def get_file_in_folder(path):
return files_list


def identify_target_folder(project_path: str) -> Tuple[str, FolderType, str]:
def identify_target_folder(project_path: str) -> Tuple[str, ItemType, str]:
'''
Summary:
the function will validate if input folder path doesn't
Expand All @@ -187,14 +187,13 @@ def identify_target_folder(project_path: str) -> Tuple[str, FolderType, str]:
# check folder type if is project folder or name folder
# there will be a extra string for project folder between project code and folder name
if len(temp_paths) == 2:
folder_type = FolderType.NAMEFOLDER
folder_type = ItemType.NAMEFOLDER
folder_name = temp_paths[1]
elif len(temp_paths) >= 3:
if temp_paths[1] == FolderType.PROJECTFOLDER.value:
folder_type = FolderType.PROJECTFOLDER
folder_type = ItemType.get_type_from_keyword(temp_paths[1])
if folder_type == ItemType.SHAREDFOLDER:
folder_name = temp_paths[2]
else:
folder_type = FolderType.NAMEFOLDER
folder_name = os.path.join(temp_paths[1], temp_paths[2])
else:
SrvErrorHandler.customized_handle(ECustomizedError.INVALID_NAMEFOLDER, True)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "app"
version = "2.10.0"
version = "2.10.1"
description = "This service is designed to support pilot platform"
authors = ["Indoc Systems"]

Expand Down
7 changes: 5 additions & 2 deletions tests/app/commands/test_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,18 @@ def test_file_list_with_pagination_with_name_project_folder(requests_mock, mocke
'result': [
{'type': 'folder', 'name': 'folder1'},
{'type': 'name_folder', 'name': 'name_folder1'},
{'type': 'project_folder', 'name': 'project_folder1'},
{'type': 'project_folder', 'name': 'project folder1'},
{'type': 'folder', 'name': 'test folder2'},
{'type': 'project_folder', 'name': 'project folder2'},
],
},
)
mocker.patch.object(questionary, 'select')
questionary.select.return_value.ask.return_value = 'exit'
result = cli_runner.invoke(file_list, ['testproject/admin', '-z', 'greenroom'])
outputs = result.output.split('\n')
assert outputs[0] == 'folder1 name_folder1 project_folder1 '
assert outputs[0] == 'folder1 name_folder1 [p]"project folder1" '
assert outputs[1] == '"test folder2" [p]"project folder2" '


def test_empty_file_list_with_pagination(requests_mock, mocker, cli_runner):
Expand Down
11 changes: 5 additions & 6 deletions tests/app/services/file_manager/file_upload/test_file_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Contact Indoc Systems for any questions regarding the use of this source code.

from app.configs.app_config import AppConfig
from app.models.folder import FolderType
from app.models.item import ItemType
from app.services.file_manager.file_upload.file_upload import assemble_path
from app.services.file_manager.file_upload.file_upload import resume_upload
from app.services.file_manager.file_upload.file_upload import simple_upload
Expand Down Expand Up @@ -34,7 +34,7 @@ def test_assemble_path_at_name_folder(mocker):
)

current_file_path, parent_folder, create_folder_flag, _ = assemble_path(
local_file_path, target_folder, project_code, FolderType.NAMEFOLDER, zone
local_file_path, target_folder, project_code, ItemType.NAMEFOLDER, zone
)
assert current_file_path == 'admin/file.txt'
assert parent_folder.get('name') == 'admin'
Expand Down Expand Up @@ -71,9 +71,8 @@ def test_assemble_path_at_exsting_folder(mocker):
]

mocker.patch('app.services.file_manager.file_upload.file_upload.search_item', side_effect=node_list)

current_file_path, parent_folder, create_folder_flag, _ = assemble_path(
local_file_path, target_folder, project_code, FolderType.NAMEFOLDER, zone
local_file_path, target_folder, project_code, ItemType.NAMEFOLDER, zone
)
assert current_file_path == 'admin/test_folder_exist/file.txt'
assert parent_folder.get('name') == 'test_folder_exist'
Expand Down Expand Up @@ -104,7 +103,7 @@ def test_assemble_path_at_non_existing_folder(mocker):
mocker.patch('app.services.file_manager.file_upload.file_upload.click.confirm', return_value=None)

current_file_path, parent_folder, create_folder_flag, _ = assemble_path(
local_file_path, target_folder, project_code, FolderType.NAMEFOLDER, zone
local_file_path, target_folder, project_code, ItemType.NAMEFOLDER, zone
)
assert current_file_path == 'admin/test_folder_not_exist'
assert parent_folder.get('name') == 'admin'
Expand Down Expand Up @@ -132,7 +131,7 @@ def test_assemble_path_at_project_folder(mocker):
)

current_file_path, parent_folder, create_folder_flag, target_folder = assemble_path(
local_file_path, target_folder, project_code, FolderType.PROJECTFOLDER, zone
local_file_path, target_folder, project_code, ItemType.SHAREDFOLDER, zone
)
assert current_file_path == 'shared/project_folder/file.txt'
assert parent_folder.get('name') == 'project_folder'
Expand Down
10 changes: 5 additions & 5 deletions tests/app/utils/test_aggregated.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pytest

from app.configs.app_config import AppConfig
from app.models.folder import FolderType
from app.models.item import ItemType
from app.utils.aggregated import check_item_duplication
from app.utils.aggregated import identify_target_folder
from app.utils.aggregated import search_item
Expand Down Expand Up @@ -136,10 +136,10 @@ def test_validate_folder_name(folder_name):
@pytest.mark.parametrize(
'input_path,expected_result',
[
('project_code/username', ('project_code', FolderType.NAMEFOLDER, 'username')),
('project_code/username/folder1', ('project_code', FolderType.NAMEFOLDER, 'username/folder1')),
('project_code/projectfolder/folder1', ('project_code', FolderType.PROJECTFOLDER, 'folder1')),
('project_code/projectfolder/folder1/folder2', ('project_code', FolderType.PROJECTFOLDER, 'folder1/folder2')),
('project_code/username', ('project_code', ItemType.NAMEFOLDER, 'username')),
('project_code/username/folder1', ('project_code', ItemType.NAMEFOLDER, 'username/folder1')),
('project_code/projectfolder/folder1', ('project_code', ItemType.SHAREDFOLDER, 'folder1')),
('project_code/projectfolder/folder1/folder2', ('project_code', ItemType.SHAREDFOLDER, 'folder1/folder2')),
],
)
def test_identify_target_folder_success_with_different_path(mocker, input_path, expected_result):
Expand Down

1 comment on commit 8b37666

@github-actions
Copy link

Choose a reason for hiding this comment

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

Coverage

Coverage Report
FileStmtsMissCoverMissing
__init__.py00100% 
pilotcli.py0310%5, 7–8, 10–14, 17–21, 23–28, 31–39, 42–44
commands
   __init__.py00100% 
   container_registry.py01959%16, 22–24, 31–33, 41–44, 50–53, 62–65
   dataset.py04844%19, 36–38, 41–48, 50–60, 78–80, 83–91, 93–103, 123, 127
   entry_point.py01085%39, 45, 89–91, 93–97
   file.py04084%40, 152–154, 158, 163, 166–168, 184–185, 227, 312–319, 321, 323, 337–343, 345, 374, 376, 379, 425, 428, 445–447, 453–454
   project.py0197%17
   user.py0685%23, 40, 48, 56, 72–73
configs
   __init__.py00100% 
   app_config.py00100% 
   config.py00100% 
   user_config.py01784%61, 70, 114, 124, 131, 135, 139, 143, 147, 151, 155, 159, 163, 167, 171, 175, 179
models
   __init__.py00100% 
   enums.py00100% 
   item.py00100% 
   service_meta_class.py0180%9
   singleton.py00100% 
   upload_form.py0625%30, 42–46
resources
   custom_error.py00100% 
   custom_help.py00100% 
services
   __init__.py00100% 
services/container_registry_manager
   container_registry_manager.py010817%19–20, 23–26, 29–33, 36–44, 48–60, 62–64, 68–82, 84–86, 90–99, 101–103, 107–110, 125–136, 140–143, 147–162, 164, 166–169
services/crypto
   __init__.py00100% 
   crypto.py01940%35, 43–47, 49, 59–61, 69–75, 77, 79
services/dataset_manager
   dataset_detail.py02370%42, 44, 61, 68–73, 75–77, 79–88, 90
   dataset_download.py01983%56, 72–73, 84, 96, 98–99, 101–102, 104, 106–108, 116–119, 153, 165
   dataset_list.py0878%37, 43–46, 48–50
   model.py00100% 
services/file_manager
   __init__.py00100% 
   file_list.py01777%30, 55, 57, 81–83, 91–94, 96, 102–107
   file_manifests.py09525%19–24, 39–43, 47–48, 51–59, 61, 65–68, 71–75, 77, 81–82, 85–87, 91–92, 95–102, 104, 107, 109–110, 112–113, 115–118, 123–129, 134–139, 142–148, 150–152, 155–161, 163–165, 167–170
   file_tag.py03931%23, 27–31, 33, 36–48, 50–52, 54–55, 59–61, 64–68, 70–73, 75–76
services/file_manager/file_download
   __init__.py00100% 
   download_client.py017423%44–45, 47–53, 56–57, 59–60, 69, 71–72, 76–79, 86, 91–94, 96, 98–106, 108, 110, 114–119, 121, 124–125, 128–130, 132–134, 136–137, 139–141, 145–153, 156–161, 165–174, 182–184, 186–193, 195–199, 204–214, 216–217, 220–221, 226–227, 231–236, 240–241, 244–246, 248–259, 263–266, 269–273, 275–277, 279–281, 283–284, 286, 290–292, 294–307, 309
   model.py0187%15
services/file_manager/file_metadata
   __init__.py00100% 
   file_metadata_client.py0395%97–99
services/file_manager/file_move
   __init__.py00100% 
   file_move_client.py0790%67, 76, 85–87, 89, 116
services/file_manager/file_upload
   __init__.py00100% 
   exception.py0175%10
   file_upload.py02884%36–43, 97–99, 111, 142–143, 147, 152, 189, 209–211, 252–253, 255–257, 331, 334, 338
   models.py0591%24, 44, 150–152
   upload_client.py07760%101–104, 207–209, 223–229, 231–235, 237–249, 251, 295, 298, 302, 304–306, 308–311, 315–319, 321, 325, 327, 329, 331, 350, 380–381, 386, 390–393, 397, 413, 415–417, 424, 429–430, 432, 434–435, 437–440, 442
   upload_validator.py02156%29–31, 34–39, 42–48, 51–52, 57, 59, 61
services/logger_services
   __init__.py00100% 
   log_functions.py00100% 
services/output_manager
   __init__.py00100% 
   error_handler.py00100% 
   help_page.py00100% 
   message_handler.py05770%23, 45, 50, 65–67, 72, 82, 87, 100, 105, 112, 117, 122, 126, 142, 177, 187, 196, 214, 224, 243, 265–276, 287–292, 294–300, 311, 315–316, 320–321, 323–324, 328, 332, 336
services/project_manager
   __init__.py00100% 
   project.py0878%38, 44–47, 49–51
services/user_authentication
   __init__.py00100% 
   decorator.py0293%27, 30
   token_manager.py0986%26, 41–42, 73, 76, 91, 96, 100, 105
   user_login_logout.py04266%30–31, 41, 122, 126–132, 134–136, 140–141, 145–152, 154–156, 160–161, 168–173, 175–177, 181–184
utils
   __init__.py00100% 
   aggregated.py02284%52, 64, 104, 118–119, 129–130, 132, 156–163, 165–166, 200, 208, 225–226
TOTAL312296469% 

Please sign in to comment.