From d6a00eb0a2c174641571f1c100261daabf51db1c Mon Sep 17 00:00:00 2001 From: Oleh Paduchak Date: Thu, 9 Jan 2025 14:28:28 +0200 Subject: [PATCH 1/2] removed LegacyConfigsForWBKey --- osf/external/gravy_valet/request_helpers.py | 96 ++++++++++----------- osf/external/gravy_valet/translations.py | 96 ++++++--------------- website/project/views/node.py | 49 ++++------- 3 files changed, 86 insertions(+), 155 deletions(-) diff --git a/osf/external/gravy_valet/request_helpers.py b/osf/external/gravy_valet/request_helpers.py index 16a1ef164c9..40124808bcc 100644 --- a/osf/external/gravy_valet/request_helpers.py +++ b/osf/external/gravy_valet/request_helpers.py @@ -240,7 +240,7 @@ def _make_gv_request( return response -def get_gv_citation_url_list_for_project(auth, addon_short_name, project, request=None, pid=None) -> list: +def get_gv_citation_url_list_for_project(auth, project, request=None, pid=None) -> list: if pid: project_url = settings.DOMAIN + pid else: @@ -252,20 +252,15 @@ def get_gv_citation_url_list_for_project(auth, addon_short_name, project, reques params={'filter[resource_uri]': project_url}, ) if not resource_references_response: - return [] - configured_citation_addons_url = resource_references_response.get_related_link( - 'configured_citation_addons') - addons_url_list = get_raw_gv_result( + return {} + configured_citation_addons_url = resource_references_response.get_related_link('configured_citation_addons') + return get_raw_gv_result( endpoint_url=configured_citation_addons_url, requesting_user=auth.user, requested_resource=project, request_method='GET', params={} ) - gv_addon_name = addon_short_name if addon_short_name != 'zotero' else 'zotero_org' - citation_url_list = list( - filter(lambda x: x['attributes']['external_service_name'] == gv_addon_name, addons_url_list)) - return citation_url_list def _invoke_gv_citation_operation_invocations(auth, addon, project, list_id): @@ -300,51 +295,49 @@ def _invoke_gv_citation_operation_invocations(auth, addon, project, list_id): def citation_list_gv_request(auth, request, addon_short_name, list_id, show): from osf.models import Node - response = {'contents': []} + contents = [] project = Node.objects.filter(guids___id__in=[request.view_args.get('pid')]).first() - citation_url_list = get_gv_citation_url_list_for_project( + addon = get_gv_citation_url_list_for_project( auth=auth, request=request, - addon_short_name=addon_short_name, project=project + )[addon_short_name] + gv_response = _invoke_gv_citation_operation_invocations( + auth=auth, + addon=addon, + project=project, + list_id=list_id ) - for addon in citation_url_list: - gv_response = _invoke_gv_citation_operation_invocations( - auth=auth, - addon=addon, - project=project, - list_id=list_id - ) - if gv_response.status_code == 201: - attributes_dict = gv_response.json()['data']['attributes'] - items = attributes_dict.get('operation_result').get('items') - for item in items: - item_id = item.get('item_id', '') - kind = CITATION_ITEM_TYPE_ALIASES.get(item.get('item_type', '')) - if 'csl' in item.keys(): - change_response = item - change_response['kind'] = kind - change_response['id'] = item_id - else: - change_response = { - 'data': { - 'addon': addon_short_name, - 'kind': kind, - 'id': item_id, - 'name': item.get('item_name', ''), - 'path': item.get('item_path', '/'), - 'parent_list_id': item.get('parent_list_id', 'ROOT'), - 'provider_list_id': item_id, - }, + if gv_response.status_code == 201: + attributes_dict = gv_response.json()['data']['attributes'] + items = attributes_dict.get('operation_result').get('items') + for item in items: + item_id = item.get('item_id', '') + kind = CITATION_ITEM_TYPE_ALIASES.get(item.get('item_type', '')) + if 'csl' in item.keys(): + change_response = item + change_response['kind'] = kind + change_response['id'] = item_id + else: + change_response = { + 'data': { + 'addon': addon_short_name, 'kind': kind, - 'name': item.get('item_name', ''), 'id': item_id, - 'urls': { - 'fetch': f'/api/v1/project/{project._id}/{addon_short_name}/citations/{item_id}/', - } + 'name': item.get('item_name', ''), + 'path': item.get('item_path', '/'), + 'parent_list_id': item.get('parent_list_id', 'ROOT'), + 'provider_list_id': item_id, + }, + 'kind': kind, + 'name': item.get('item_name', ''), + 'id': item_id, + 'urls': { + 'fetch': f'/api/v1/project/{project._id}/{addon_short_name}/citations/{item_id}/', } - response['contents'].append(change_response) - return response + } + contents.append(change_response) + return {'contents': contents} def _format_included_entities(included_entities_json): @@ -394,13 +387,14 @@ def get_related_id(self, relationship_name): def get_related_link(self, relationship_name): return self._relationships[relationship_name].related_link - def get_included_member(self, relationship_name): - return self._includes.get(relationship_name) + def get_included_member(self, *relationship_path: str): + related_object = self + for relationship_name in relationship_path: + related_object = related_object._includes.get(relationship_name) + return related_object def get_included_attribute(self, include_path: typing.Iterable[str], attribute_name: str): - related_object = self - for relationship_name in include_path: - related_object = related_object.get_included_member(relationship_name) + related_object = self.get_included_member(*include_path) if related_object: return related_object.get_attribute(attribute_name) diff --git a/osf/external/gravy_valet/translations.py b/osf/external/gravy_valet/translations.py index 73fcaf63679..60f13af6748 100644 --- a/osf/external/gravy_valet/translations.py +++ b/osf/external/gravy_valet/translations.py @@ -1,24 +1,10 @@ import dataclasses import enum -from dataclasses import asdict +from dataclasses import asdict, InitVar from typing import TYPE_CHECKING import markupsafe -from addons.bitbucket.apps import BitbucketAddonConfig -from addons.boa.apps import BoaAddonAppConfig -from addons.box.apps import BoxAddonAppConfig -from addons.dataverse.apps import DataverseAddonAppConfig -from addons.dropbox.apps import DropboxAddonAppConfig -from addons.figshare.apps import FigshareAddonAppConfig -from addons.github.apps import GitHubAddonConfig -from addons.gitlab.apps import GitLabAddonConfig -from addons.googledrive.apps import GoogleDriveAddonConfig -from addons.zotero.apps import ZoteroAddonAppConfig -from addons.mendeley.apps import MendeleyAddonConfig -from addons.s3.apps import S3AddonAppConfig -from addons.onedrive.apps import OneDriveAddonAppConfig -from addons.owncloud.apps import OwnCloudAddonAppConfig from . import request_helpers as gv_requests if TYPE_CHECKING: @@ -29,34 +15,12 @@ class AddonType(enum.StrEnum): CITATION = enum.auto() COMPUTING = enum.auto() -class _LegacyConfigsForWBKey(enum.Enum): - """Mapping from a GV ExternalStorageService's waterbutler key to the legacy Addon config.""" - - box = BoxAddonAppConfig - bitbucket = BitbucketAddonConfig - dataverse = DataverseAddonAppConfig - dropbox = DropboxAddonAppConfig - figshare = FigshareAddonAppConfig - github = GitHubAddonConfig - gitlab = GitLabAddonConfig - googledrive = GoogleDriveAddonConfig - onedrive = OneDriveAddonAppConfig - owncloud = OwnCloudAddonAppConfig - s3 = S3AddonAppConfig - zotero_org = ZoteroAddonAppConfig - mendeley = MendeleyAddonConfig - boa = BoaAddonAppConfig - - def make_ephemeral_user_settings(gv_account_data, requesting_user): - include_path = f'external_{gv_account_data.resource_type.split('-')[1]}_service', - service_wb_key = gv_account_data.get_included_attribute( - include_path=include_path, - attribute_name='wb_key' - ) - legacy_config = _LegacyConfigsForWBKey[service_wb_key].value + include_path = f'external_{gv_account_data.resource_type.split('-')[1]}_service' + raw_config = gv_account_data.get_included_member(include_path) + config = EphemeralAddonConfig(raw_config) return EphemeralUserSettings( - config=EphemeralAddonConfig.from_legacy_config(legacy_config), + config=config, gv_data=gv_account_data, active_user=requesting_user, ) @@ -65,50 +29,38 @@ def make_ephemeral_user_settings(gv_account_data, requesting_user): def make_ephemeral_node_settings(gv_addon_data: gv_requests.JSONAPIResultEntry, requested_resource, requesting_user): addon_type = gv_addon_data.resource_type.split('-')[1] include_path = ('base_account', f'external_{addon_type}_service') - service_wb_key = gv_addon_data.get_included_attribute( - include_path=include_path, - attribute_name='wb_key' - ) - legacy_config = _LegacyConfigsForWBKey[service_wb_key].value + config = EphemeralAddonConfig(gv_addon_data.get_included_member(*include_path)) settings_class = get_settings_class(addon_type) return settings_class( - config=EphemeralAddonConfig.from_legacy_config(legacy_config), + config=config, gv_data=gv_addon_data, user_settings=make_ephemeral_user_settings(gv_addon_data.get_included_member('base_account'), requesting_user), configured_resource=requested_resource, active_user=requesting_user, - wb_key=service_wb_key, + wb_key=config.wb_key, ) @dataclasses.dataclass class EphemeralAddonConfig: """Minimalist dataclass for storing the actually used properties of an AddonConfig""" - - name: str - label: str - short_name: str - full_name: str - has_hgrid_files: bool + gv_data: InitVar[gv_requests.JSONAPIResultEntry] + # name: str = dataclasses.field(init=False, default=False) + # label: str = dataclasses.field(init=False, default=False) + short_name: str = dataclasses.field(init=False) + full_name: str = dataclasses.field(init=False) + has_hgrid_files: bool = dataclasses.field(init=False, default=False) has_widget: bool = dataclasses.field(init=False, default=False) - - def __post_init__(self): - if self.short_name in ['zotero', 'mendeley']: - self.has_widget = True - - @property - def icon_url(self): - return '' - - @classmethod - def from_legacy_config(cls, legacy_config): - return cls( - name=legacy_config.name, - label=legacy_config.label, - full_name=legacy_config.full_name, - short_name=legacy_config.short_name, - has_hgrid_files=legacy_config.has_hgrid_files - ) + icon_url: str = dataclasses.field(init=False) + wb_key: str = dataclasses.field(init=False) + + def __post_init__(self, gv_data: gv_requests.JSONAPIResultEntry): + self.short_name = gv_data.get_attribute('external_service_name') + self.full_name = gv_data.get_attribute('display_name') + self.has_hgrid_files = gv_data.resource_type == 'external-storage-services' + self.has_widget = gv_data.resource_type == 'external-citation-services' + self.icon_url = gv_data.get_attribute('icon_url') + self.wb_key = gv_data.get_attribute('wb_key') def to_json(self): return asdict(self) diff --git a/website/project/views/node.py b/website/project/views/node.py index f7c967b4399..3e703d051f2 100644 --- a/website/project/views/node.py +++ b/website/project/views/node.py @@ -447,31 +447,17 @@ def configure_requests(node, **kwargs): ############################################################################## # View Project ############################################################################## - -CITATION_WIDGET_DATA = { - 'mendeley': { - 'addon_capabilities': '\n\n

Mendeley Add-on Terms

\n\n\n\n \n \n ...n allows you to store files using an external service. Files added to this add-on are not stored within the OSF.\n\n', - 'capabilities': True, - 'full_name': 'Mendeley', - 'has_page': False, - 'has_widget': True, - 'icon': '/static/addons/mendeley/comicon.png', - 'list_id': 'ROOT', - 'short_name': 'mendeley' - }, - 'zotero': { - 'addon_capabilities': '\n\n

Zotero Add-on Terms

\n\n
\n\n \n \n ...n allows you to store files using an external service. Files added to this add-on are not stored within the OSF.\n\n', +def make_citation_widget_data(addon_name: str): + return { + 'addon_capabilities': '', 'capabilities': True, - 'full_name': 'Zotero', + 'full_name': addon_name.capitalize(), 'has_page': False, 'has_widget': True, - 'icon': '/static/addons/zotero/comicon.png', - 'library_id': 'personal', + 'icon': f'/static/addons/{addon_name}/comicon.png', 'list_id': 'ROOT', - 'short_name': 'zotero' - }, -} - + 'short_name': addon_name + } @process_token_or_pass @must_be_valid_project(retractions_valid=True) @@ -516,18 +502,17 @@ def view_project(auth, node, **kwargs): if waffle.flag_is_active(request, features.ENABLE_GV): project = Node.objects.filter(guids___id__in=[kwargs['pid']]).first() - for item in ['zotero', 'mendeley']: - citation_list_urls = get_gv_citation_url_list_for_project( - auth=auth, - pid=kwargs.get('pid'), - addon_short_name=item, - project=project - ) - data = CITATION_WIDGET_DATA[item] - data['complete'] = bool(citation_list_urls) + citation_list_urls = get_gv_citation_url_list_for_project( + auth=auth, + pid=kwargs.get('pid'), + project=project + ) + for key, value in citation_list_urls.items(): + data = make_citation_widget_data(key) + data['complete'] = bool(value) if data['complete']: - data['list_id'] = citation_list_urls[0]['attributes']['root_folder'] - addons_widget_data[item] = data + data['list_id'] = value['attributes']['root_folder'] + addons_widget_data[key] = data else: if 'zotero' in ret['addons']: node_addon = node.get_addon('zotero') From 8438af49ea9dd8c3e52098b0cd2828e8de15676f Mon Sep 17 00:00:00 2001 From: Oleh Paduchak Date: Thu, 9 Jan 2025 15:10:50 +0200 Subject: [PATCH 2/2] fixed tests --- osf/external/gravy_valet/request_helpers.py | 8 +++++--- osf_tests/external/gravy_valet/gv_fakes.py | 1 + osf_tests/test_gv_utils.py | 2 -- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osf/external/gravy_valet/request_helpers.py b/osf/external/gravy_valet/request_helpers.py index 40124808bcc..51fd006dc94 100644 --- a/osf/external/gravy_valet/request_helpers.py +++ b/osf/external/gravy_valet/request_helpers.py @@ -240,7 +240,7 @@ def _make_gv_request( return response -def get_gv_citation_url_list_for_project(auth, project, request=None, pid=None) -> list: +def get_gv_citation_url_list_for_project(auth, project, request=None, pid=None) -> dict: if pid: project_url = settings.DOMAIN + pid else: @@ -254,14 +254,16 @@ def get_gv_citation_url_list_for_project(auth, project, request=None, pid=None) if not resource_references_response: return {} configured_citation_addons_url = resource_references_response.get_related_link('configured_citation_addons') - return get_raw_gv_result( + addon_list = get_raw_gv_result( endpoint_url=configured_citation_addons_url, requesting_user=auth.user, requested_resource=project, request_method='GET', params={} ) - + return { + addon['attributes']['external_service_name']: addon for addon in addon_list + } def _invoke_gv_citation_operation_invocations(auth, addon, project, list_id): data = { diff --git a/osf_tests/external/gravy_valet/gv_fakes.py b/osf_tests/external/gravy_valet/gv_fakes.py index 1cd8f6c59c9..f7ebf08747b 100644 --- a/osf_tests/external/gravy_valet/gv_fakes.py +++ b/osf_tests/external/gravy_valet/gv_fakes.py @@ -139,6 +139,7 @@ class _FakeAddonProvider(_FakeGVEntity): def _serialize_attributes(self): return { + 'external_service_name': self.wb_key or self.name, 'display_name': self.name, 'max_upload_mb': self.max_upload_mb, 'max_concurrent_uploads': self.max_concurrent_uploads, diff --git a/osf_tests/test_gv_utils.py b/osf_tests/test_gv_utils.py index a258dff1b19..acee15c8df6 100644 --- a/osf_tests/test_gv_utils.py +++ b/osf_tests/test_gv_utils.py @@ -532,7 +532,6 @@ def test_make_ephemeral_user_settings(self, contributor, fake_box_account, fake_ ephemeral_config = translations.make_ephemeral_user_settings(account_data, requesting_user=contributor) assert ephemeral_config.short_name == 'box' assert ephemeral_config.gv_id == fake_box_account.pk - assert ephemeral_config.config.name == 'addons.box' def test_make_ephemeral_node_settings(self, contributor, project, fake_box_addon, fake_gv): with fake_gv.run_fake(): @@ -547,7 +546,6 @@ def test_make_ephemeral_node_settings(self, contributor, project, fake_box_addon ) assert ephemeral_config.short_name == 'box' assert ephemeral_config.gv_id == fake_box_addon.pk - assert ephemeral_config.config.name == 'addons.box' assert ephemeral_config.serialize_waterbutler_settings() == { 'folder': fake_box_addon.root_folder.split(':')[1], 'service': 'box'