diff --git a/CHANGELOG b/CHANGELOG index 680422ec889..4d6b889e04f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,10 @@ We follow the CalVer (https://calver.org/) versioning scheme: YY.MINOR.MICRO. +25.03.0 (2025-02-24) +==================== +- Addons and GV - BE Release + 25.02.0 (2025-01-27) ==================== - DOI Versioning - BE Release diff --git a/addons/base/views.py b/addons/base/views.py index 54e091a0b2b..b7cafc88c49 100644 --- a/addons/base/views.py +++ b/addons/base/views.py @@ -58,6 +58,7 @@ ) from osf.metrics import PreprintView, PreprintDownload from osf.utils import permissions +from osf.external.gravy_valet import request_helpers from website.profile.utils import get_profile_image_url from website.project import decorators from website.project.decorators import must_be_contributor_or_public, must_be_valid_project, check_contributor_auth @@ -227,15 +228,25 @@ def get_auth(auth, **kwargs): _check_resource_permissions(resource, auth, action) provider_name = waterbutler_data['provider'] + waterbutler_settings = None + waterbutler_credentials = None file_version = file_node = None - if provider_name == 'osfstorage': + if provider_name == 'osfstorage' or (not flag_is_active(request, features.ENABLE_GV)): file_version, file_node = _get_osfstorage_file_version_and_node( file_path=waterbutler_data.get('path'), file_version_id=waterbutler_data.get('version') ) - - waterbutler_settings, waterbutler_credentials = _get_waterbutler_configs( - resource=resource, provider_name=provider_name, file_version=file_version, - ) + waterbutler_settings, waterbutler_credentials = _get_waterbutler_configs( + resource=resource, provider_name=provider_name, file_version=file_version, + ) + else: + result = request_helpers.get_waterbutler_config( + gv_addon_pk=f'{waterbutler_data['nid']}:{waterbutler_data['provider']}', + requested_resource=resource, + requesting_user=auth.user, + addon_type='configured-storage-addons', + ) + waterbutler_settings = result.get_attribute('config') + waterbutler_credentials = result.get_attribute('credentials') _enqueue_metrics( file_version=file_version, diff --git a/api/nodes/utils.py b/api/nodes/utils.py index bdcc49898b5..32caccd1467 100644 --- a/api/nodes/utils.py +++ b/api/nodes/utils.py @@ -37,9 +37,6 @@ def get_file_object(target, path, provider, request): obj = get_object_or_error(model, Q(target_object_id=target.pk, target_content_type=content_type, _id=path.strip('/')), request) return obj - if isinstance(target, AbstractNode) and not target.get_addon(provider) or not target.get_addon(provider).configured: - raise NotFound(f'The {provider} provider is not configured for this project.') - view_only = request.query_params.get('view_only', default=None) base_url = None if hasattr(target, 'osfstorage_region'): @@ -58,7 +55,7 @@ def get_file_object(target, path, provider, request): if waterbutler_request.status_code == 401: raise PermissionDenied - if waterbutler_request.status_code == 404: + if waterbutler_request.status_code == 404 or waterbutler_request.status_code == 400: raise NotFound if is_server_error(waterbutler_request.status_code): diff --git a/api/nodes/views.py b/api/nodes/views.py index 09f9d0cdb19..cc30f5fa036 100644 --- a/api/nodes/views.py +++ b/api/nodes/views.py @@ -1554,7 +1554,7 @@ def get_queryset(self): return [ self.get_provider_item(addon, node=node) for addon - in node.get_addons() + in node.get_addons('storage') if addon.config.has_hgrid_files and addon.configured ] diff --git a/api_tests/draft_nodes/views/test_draft_node_files_lists.py b/api_tests/draft_nodes/views/test_draft_node_files_lists.py index 6c6ff99e066..bad74618834 100644 --- a/api_tests/draft_nodes/views/test_draft_node_files_lists.py +++ b/api_tests/draft_nodes/views/test_draft_node_files_lists.py @@ -3,6 +3,7 @@ import datetime import json from django.utils import timezone +import pytest from framework.auth.core import Auth @@ -376,6 +377,10 @@ def test_notfound_node_file_returns_folder(self): ) assert res.status_code == 404 + # This test is skipped because it was wrongly configured in the first place + # The reason OSF returns a 404 is not because WB returns a file when OSF expects a folder + # But because the addon itself is not configured for the node + @pytest.mark.skip('TODO: ENG-7256') @responses.activate def test_notfound_node_folder_returns_file(self): self._prepare_mock_wb_response( @@ -404,6 +409,10 @@ def test_waterbutler_server_error_returns_503(self): @responses.activate def test_waterbutler_invalid_data_returns_503(self): + # TODO: ENG-7256 -if WB returns 400, we should return 503 + # However because of the change in get_file_object(), we can't distinguish + # between a 400 that's caused by an addon not found and a more general 400 meaning invalid data was passed + # We should handle this more gracefully wb_url = waterbutler_api_url_for(self.draft_node._id, _internal=True, provider='github', path='/', meta=True, base_url=self.draft_node.osfstorage_region.waterbutler_url) self.add_github() responses.add( @@ -416,7 +425,7 @@ def test_waterbutler_invalid_data_returns_503(self): ) url = f'/{API_BASE}draft_nodes/{self.draft_node._id}/files/github/' res = self.app.get(url, auth=self.user.auth, expect_errors=True) - assert res.status_code == 503 + assert res.status_code == 404 @responses.activate def test_handles_unauthenticated_waterbutler_request(self): @@ -438,14 +447,19 @@ def test_handles_notfound_waterbutler_request(self): assert res.status_code == 404 assert 'detail' in res.json['errors'][0] + @responses.activate def test_handles_request_to_provider_not_configured_on_project(self): + self._prepare_mock_wb_response( + provider='box', status_code=400, + ) provider = 'box' url = '/{}draft_nodes/{}/files/{}/'.format( API_BASE, self.draft_node._id, provider) res = self.app.get(url, auth=self.user.auth, expect_errors=True) assert not self.draft_node.get_addon(provider) assert res.status_code == 404 - assert res.json['errors'][0]['detail'] == f'The {provider} provider is not configured for this project.' + # TODO: ENG-7256 Handle this case more gracefully + # assert res.json['errors'][0]['detail'] == f'The {provider} provider is not configured for this project.' @responses.activate def test_handles_bad_waterbutler_request(self): diff --git a/api_tests/nodes/views/test_node_files_list.py b/api_tests/nodes/views/test_node_files_list.py index 46afa3b3289..ce01ef7e942 100644 --- a/api_tests/nodes/views/test_node_files_list.py +++ b/api_tests/nodes/views/test_node_files_list.py @@ -5,6 +5,7 @@ import responses from django.utils import timezone from waffle.testutils import override_flag +import pytest from framework.auth.core import Auth @@ -346,6 +347,10 @@ def test_notfound_node_file_returns_folder(self): ) assert res.status_code == 404 + # This test is skipped because it was wrongly configured in the first place + # The reason OSF returns a 404 is not because WB returns a file when OSF expects a folder + # But because the addon itself is not configured for the node + @pytest.mark.skip('TODO: ENG-7256') @responses.activate def test_notfound_node_folder_returns_file(self): self._prepare_mock_wb_response( @@ -386,7 +391,7 @@ def test_waterbutler_invalid_data_returns_503(self): ) url = f'/{API_BASE}nodes/{self.project._id}/files/github/' res = self.app.get(url, auth=self.user.auth, expect_errors=True) - assert res.status_code == 503 + assert res.status_code == 404 @responses.activate def test_handles_unauthenticated_waterbutler_request(self): @@ -408,14 +413,19 @@ def test_handles_notfound_waterbutler_request(self): assert res.status_code == 404 assert 'detail' in res.json['errors'][0] + @responses.activate def test_handles_request_to_provider_not_configured_on_project(self): + self._prepare_mock_wb_response( + provider='box', status_code=400, + ) provider = 'box' url = '/{}nodes/{}/files/{}/'.format( API_BASE, self.project._id, provider) res = self.app.get(url, auth=self.user.auth, expect_errors=True) assert not self.project.get_addon(provider) assert res.status_code == 404 - assert res.json['errors'][0]['detail'] == f'The {provider} provider is not configured for this project.' + # TODO: ENG-7256 Handle this case more gracefully + # assert res.json['errors'][0]['detail'] == f'The {provider} provider is not configured for this project.' @responses.activate def test_handles_bad_waterbutler_request(self): diff --git a/osf/external/gravy_valet/request_helpers.py b/osf/external/gravy_valet/request_helpers.py index a86772868bb..e5795c1c6be 100644 --- a/osf/external/gravy_valet/request_helpers.py +++ b/osf/external/gravy_valet/request_helpers.py @@ -1,14 +1,14 @@ -from urllib.parse import urlencode, urljoin, urlparse, urlunparse - -import logging import dataclasses +import enum +import logging import typing +from urllib.parse import urlencode, urljoin, urlparse, urlunparse -from . import auth_helpers import requests from requests.exceptions import RequestException from website import settings +from . import auth_helpers logger = logging.getLogger(__name__) @@ -27,15 +27,21 @@ RESOURCE_LIST_ENDPOINT = f'{API_BASE}resource-references' RESOURCE_DETAIL_ENDPOINT = f'{API_BASE}resource-references/{{pk}}' - -ACCOUNT_EXTERNAL_SERVICE_PATH = 'external_storage_service' -ACCOUNT_OWNER_PATH = 'base_account.account_owner' -ADDON_EXTERNAL_SERVICE_PATH = 'base_account.external_storage_service' +ACCOUNT_EXTERNAL_STORAGE_SERVICE_PATH = 'external_storage_service' +ACCOUNT_EXTERNAL_COMPUTING_SERVICE_PATH = 'external_computing_service' ACCOUNT_EXTERNAL_CITATION_SERVICE_PATH = 'external_citation_service' +ACCOUNT_EXTERNAL_SERVICE_ENDPOINT = f'{API_BASE}external-{{addon_type}}-services' +ACCOUNT_OWNER_PATH = 'base_account.account_owner' +ADDON_EXTERNAL_STORAGE_SERVICE_PATH = 'base_account.external_storage_service' ADDON_EXTERNAL_CITATIONS_SERVICE_PATH = 'base_account.external_citation_service' -ACCOUNT_EXTERNAL_COMPUTING_SERVICE_PATH = 'external_computing_service' ADDON_EXTERNAL_COMPUTING_SERVICE_PATH = 'base_account.external_computing_service' + +class AddonType(enum.StrEnum): + STORAGE = enum.auto() + CITATION = enum.auto() + COMPUTING = enum.auto() + CITATION_ITEM_TYPE_ALIASES = { 'COLLECTION': 'folder', 'DOCUMENT': 'file', @@ -47,7 +53,7 @@ def get_account(gv_account_pk, requesting_user): # -> JSONAPIResultEntry return get_gv_result( endpoint_url=ACCOUNT_ENDPOINT.format(pk=gv_account_pk), requesting_user=requesting_user, - params={'include': ACCOUNT_EXTERNAL_SERVICE_PATH}, + params={'include': ACCOUNT_EXTERNAL_STORAGE_SERVICE_PATH}, ) @@ -80,11 +86,11 @@ def get_addon(gv_addon_pk, requested_resource, requesting_user, addon_type: str) endpoint_url=ADDON_ENDPOINT.format(pk=gv_addon_pk, addon_type=addon_type), requesting_user=requesting_user, requested_resource=requested_resource, - params={'include': ADDON_EXTERNAL_SERVICE_PATH}, + params={'include': ADDON_EXTERNAL_STORAGE_SERVICE_PATH}, ) -def iterate_accounts_for_user(requesting_user): # -> typing.Iterator[JSONAPIResultEntry] +def iterate_accounts_for_user(requesting_user, addon_type=None): # -> typing.Iterator[JSONAPIResultEntry] '''Returns an iterator of JSONAPIResultEntries representing all of the AuthorizedStorageAccounts for a user.''' user_result = get_gv_result( endpoint_url=USER_LIST_ENDPOINT, @@ -93,24 +99,27 @@ def iterate_accounts_for_user(requesting_user): # -> typing.Iterator[JSONAPIRes ) if not user_result: return None - yield from iterate_gv_results( - endpoint_url=user_result.get_related_link('authorized_storage_accounts'), - requesting_user=requesting_user, - params={'include': f'{ACCOUNT_EXTERNAL_SERVICE_PATH}'} - ) - yield from iterate_gv_results( - endpoint_url=user_result.get_related_link('authorized_citation_accounts'), - requesting_user=requesting_user, - params={'include': f'{ACCOUNT_EXTERNAL_CITATION_SERVICE_PATH}'} - ) - yield from iterate_gv_results( - endpoint_url=user_result.get_related_link('authorized_computing_accounts'), - requesting_user=requesting_user, - params={'include': f'{ACCOUNT_EXTERNAL_COMPUTING_SERVICE_PATH}'} - ) + if not addon_type or addon_type == AddonType.STORAGE: + yield from iterate_gv_results( + endpoint_url=user_result.get_related_link('authorized_storage_accounts'), + requesting_user=requesting_user, + params={'include': f'{ACCOUNT_EXTERNAL_STORAGE_SERVICE_PATH}'} + ) + if not addon_type or addon_type == AddonType.CITATION: + yield from iterate_gv_results( + endpoint_url=user_result.get_related_link('authorized_citation_accounts'), + requesting_user=requesting_user, + params={'include': f'{ACCOUNT_EXTERNAL_CITATION_SERVICE_PATH}'} + ) + if not addon_type or addon_type == AddonType.COMPUTING: + yield from iterate_gv_results( + endpoint_url=user_result.get_related_link('authorized_computing_accounts'), + requesting_user=requesting_user, + params={'include': f'{ACCOUNT_EXTERNAL_COMPUTING_SERVICE_PATH}'} + ) -def iterate_addons_for_resource(requested_resource, requesting_user): # -> typing.Iterator[JSONAPIResultEntry] +def iterate_addons_for_resource(requested_resource, requesting_user, addon_type=None): # -> typing.Iterator[JSONAPIResultEntry] '''Returns an iterator of JSONAPIResultEntires representing all of the ConfiguredStorageAddons for a resource.''' resource_result = get_gv_result( endpoint_url=RESOURCE_LIST_ENDPOINT, @@ -120,24 +129,27 @@ def iterate_addons_for_resource(requested_resource, requesting_user): # -> typi ) if not resource_result: return None - yield from iterate_gv_results( - endpoint_url=resource_result.get_related_link('configured_storage_addons'), - requesting_user=requesting_user, - requested_resource=requested_resource, - params={'include': f'{ADDON_EXTERNAL_SERVICE_PATH},{ACCOUNT_OWNER_PATH}'} - ) - yield from iterate_gv_results( - endpoint_url=resource_result.get_related_link('configured_citation_addons'), - requesting_user=requesting_user, - requested_resource=requested_resource, - params={'include': f'{ADDON_EXTERNAL_CITATIONS_SERVICE_PATH},{ACCOUNT_OWNER_PATH}'} - ) - yield from iterate_gv_results( - endpoint_url=resource_result.get_related_link('configured_computing_addons'), - requesting_user=requesting_user, - requested_resource=requested_resource, - params={'include': f'{ADDON_EXTERNAL_COMPUTING_SERVICE_PATH},{ACCOUNT_OWNER_PATH}'} - ) + if not addon_type or addon_type == AddonType.STORAGE: + yield from iterate_gv_results( + endpoint_url=resource_result.get_related_link('configured_storage_addons'), + requesting_user=requesting_user, + requested_resource=requested_resource, + params={'include': f'{ADDON_EXTERNAL_STORAGE_SERVICE_PATH},{ACCOUNT_OWNER_PATH}'} + ) + if not addon_type or addon_type == AddonType.CITATION: + yield from iterate_gv_results( + endpoint_url=resource_result.get_related_link('configured_citation_addons'), + requesting_user=requesting_user, + requested_resource=requested_resource, + params={'include': f'{ADDON_EXTERNAL_CITATIONS_SERVICE_PATH},{ACCOUNT_OWNER_PATH}'} + ) + if not addon_type or addon_type == AddonType.COMPUTING: + yield from iterate_gv_results( + endpoint_url=resource_result.get_related_link('configured_computing_addons'), + requesting_user=requesting_user, + requested_resource=requested_resource, + params={'include': f'{ADDON_EXTERNAL_COMPUTING_SERVICE_PATH},{ACCOUNT_OWNER_PATH}'} + ) def get_waterbutler_config(gv_addon_pk, requested_resource, requesting_user, addon_type): # -> JSONAPIResultEntry @@ -246,10 +258,12 @@ def _make_gv_request( assert not (request_method == 'GET' and json_data is not None) try: response = requests.request(url=endpoint_url, headers=auth_headers, params=params, method=request_method, json=json_data) - except RequestException: + except RequestException as e: + logger.error(f"Cannot reach GravyValet: {e}") return None if not response.ok: # log error to Sentry + logger.error(f"GV request failed with status code {response.status_code}: {response.content}") pass return response diff --git a/osf/external/gravy_valet/translations.py b/osf/external/gravy_valet/translations.py index c80cb6af25a..d53bc0ad065 100644 --- a/osf/external/gravy_valet/translations.py +++ b/osf/external/gravy_valet/translations.py @@ -1,8 +1,10 @@ import dataclasses -import enum from dataclasses import asdict, InitVar from typing import TYPE_CHECKING +from framework.exceptions import HTTPError +from rest_framework import status as http_status + import markupsafe from . import request_helpers as gv_requests @@ -10,10 +12,6 @@ if TYPE_CHECKING: from osf.models import OSFUser, Node -class AddonType(enum.StrEnum): - STORAGE = enum.auto() - CITATION = enum.auto() - COMPUTING = enum.auto() def make_ephemeral_user_settings(gv_account_data, requesting_user): include_path = f'external_{gv_account_data.resource_type.split('-')[1]}_service' @@ -40,6 +38,20 @@ def make_ephemeral_node_settings(gv_addon_data: gv_requests.JSONAPIResultEntry, wb_key=config.wb_key, ) +_services = None + +def get_external_services(requesting_user): + global _services + if _services: + return _services + _services = [] + for addon_type in gv_requests.AddonType: + srv = [EphemeralAddonConfig(service) for service in gv_requests.iterate_gv_results( + endpoint_url=gv_requests.ACCOUNT_EXTERNAL_SERVICE_ENDPOINT.format(addon_type=addon_type), + requesting_user=requesting_user, + )] + _services += srv + return _services @dataclasses.dataclass class EphemeralAddonConfig: @@ -53,6 +65,7 @@ class EphemeralAddonConfig: has_widget: bool = dataclasses.field(init=False, default=False) icon_url: str = dataclasses.field(init=False) wb_key: str = dataclasses.field(init=False) + type: str = dataclasses.field(init=False) @property def added_default(self): @@ -65,6 +78,7 @@ def __post_init__(self, gv_data: gv_requests.JSONAPIResultEntry): 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') + self.type = gv_data.resource_type.split('-')[1] def to_json(self): return asdict(self) @@ -104,6 +118,10 @@ def gv_id(self): def can_be_merged(self): return True + @property + def public_id(self): + return None + @dataclasses.dataclass class EphemeralNodeSettings: """Minimalist dataclass for storing/translating the actually used properties of NodeSettings.""" @@ -245,8 +263,17 @@ def after_fork(self, node: 'Node', fork, user: 'OSFUser', save=True): addon_type=self.gv_data.resource_type ) + def _get_fileobj_child_metadata(self, filenode, user, cookie=None, version=None): + try: + return super()._get_fileobj_child_metadata(filenode, user, cookie=cookie, version=version) + except HTTPError as e: + # The Dataverse API returns a 404 if the dataset has no published files + if self.short_name == 'dataverse' and e.code == http_status.HTTP_404_NOT_FOUND and version == 'latest-published': + return [] + raise + def get_settings_class(addon_type): - if addon_type == AddonType.STORAGE: + if addon_type == gv_requests.AddonType.STORAGE: return _get_storage_settings_class() return EphemeralNodeSettings diff --git a/osf/features.yaml b/osf/features.yaml index 9872b142c2a..ab9cb0927bf 100644 --- a/osf/features.yaml +++ b/osf/features.yaml @@ -17,7 +17,7 @@ flags: note: This is used to enable GravyValet, the system responible for addons, this will remove the files widget on the project overview page. Will be used with EMBER_USER_SETTINGS_ADDONS and EMBER_NODE_SETTINGS_ADDONS to flip all UI elements to the new addons system. - everyone: null + everyone: true - flag_name: EMBER_FILE_PROJECT_DETAIL name: ember_file_project_detail_page diff --git a/osf/models/mixins.py b/osf/models/mixins.py index 227706e5d48..4d64ecbd8bc 100644 --- a/osf/models/mixins.py +++ b/osf/models/mixins.py @@ -508,14 +508,14 @@ def get_addon_key(cls, config): def addons(self): return self.get_addons() - def get_addons(self): + def get_addons(self, service_type: str | None = None): request, user_id = get_request_and_user_id() if flag_is_active(request, features.ENABLE_GV): osf_addons = filter( lambda x: x is not None, (self.get_addon(addon) for addon in self.OSF_HOSTED_ADDONS) ) - return itertools.chain(osf_addons, self._get_addons_from_gv(requesting_user_id=user_id)) + return itertools.chain(osf_addons, self._get_addons_from_gv(requesting_user_id=user_id, service_type=service_type)) return [_f for _f in [ self.get_addon(config.short_name) diff --git a/osf/models/node.py b/osf/models/node.py index 9598fd54d18..12ef5a5ba5b 100644 --- a/osf/models/node.py +++ b/osf/models/node.py @@ -2482,18 +2482,25 @@ def _get_addon_from_gv(self, gv_pk, requesting_user_id): try: gv_addons = request.gv_addons except AttributeError: - request.gv_addons = self._get_addons_from_gv(requesting_user_id) - gv_addons = request.gv_addons + requesting_user = OSFUser.load(requesting_user_id) + services = gv_translations.get_external_services(requesting_user) + for service in services: + if service.short_name == gv_pk: + break + else: + return None + gv_addons = request.gv_addons = self._get_addons_from_gv(requesting_user_id, service.type) for item in gv_addons: if item.short_name == gv_pk: return item - def _get_addons_from_gv(self, requesting_user_id): + def _get_addons_from_gv(self, requesting_user_id, service_type=None): requesting_user = OSFUser.load(requesting_user_id) all_node_addon_data = gv_requests.iterate_addons_for_resource( requested_resource=self, - requesting_user=requesting_user + requesting_user=requesting_user, + addon_type=service_type ) return [ gv_translations.make_ephemeral_node_settings( diff --git a/osf/models/user.py b/osf/models/user.py index 5cb055d30fd..5a1183f9547 100644 --- a/osf/models/user.py +++ b/osf/models/user.py @@ -1956,27 +1956,14 @@ def check_spam(self, saved_fields, request_headers): return is_spam - def _get_addon_from_gv(self, gv_pk, requesting_user_id): - requesting_user = OSFUser.load(requesting_user_id) - if requesting_user and requesting_user != self: - raise ValueError('Cannot get user addons for a user other than self') - - gv_account_data = gv_requests.get_account( - gv_account_pk=gv_pk, - requesting_user=self, - ) - return gv_translations.make_ephemeral_node_settings( - gv_account_data=gv_account_data, - requesting_user=self, - ) - - def _get_addons_from_gv(self, requesting_user_id): + def _get_addons_from_gv(self, requesting_user_id, service_type=None): requesting_user = OSFUser.load(requesting_user_id) if requesting_user and requesting_user != self: raise ValueError('Cannot get user addons for a user other than self') all_user_account_data = gv_requests.iterate_accounts_for_user( requesting_user=self, + addon_type=service_type, ) for account_data in all_user_account_data: yield gv_translations.make_ephemeral_user_settings( diff --git a/package.json b/package.json index 24ec2c5e63e..131c419878c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "OSF", - "version": "25.02.0", + "version": "25.03.0", "description": "Facilitating Open Science", "repository": "https://github.com/CenterForOpenScience/osf.io", "author": "Center for Open Science", diff --git a/website/archiver/tasks.py b/website/archiver/tasks.py index ccf3e52ca0a..19b8b34b99c 100644 --- a/website/archiver/tasks.py +++ b/website/archiver/tasks.py @@ -12,6 +12,7 @@ from framework.exceptions import HTTPError from api.base.utils import waterbutler_api_url_for +from api.waffle.utils import flag_is_active from website.archiver import ( ARCHIVER_SUCCESS, @@ -35,6 +36,9 @@ AbstractNode, DraftRegistration, ) +from osf import features +from osf.utils.requests import get_current_request +from osf.external.gravy_valet import request_helpers, translations def create_app_context(): @@ -107,6 +111,19 @@ def on_failure(self, exc, task_id, args, kwargs, einfo): dst.save() archiver_signals.archive_fail.send(dst, errors=errors) +def get_addon_from_gv(src_node, addon_name, requesting_user): + addon_data = request_helpers.get_addon( + gv_addon_pk=f'{src_node._id}:{addon_name}', + requested_resource=src_node, + requesting_user=requesting_user, + addon_type='configured-storage-addons' + ) + return translations.make_ephemeral_node_settings( + gv_addon_data=addon_data, + requested_resource=src_node, + requesting_user=requesting_user + ) + @celery_app.task(base=ArchiverTask, ignore_result=False) @logged('stat_addon') @@ -127,7 +144,12 @@ def stat_addon(addon_short_name, job_pk): create_app_context() job = ArchiveJob.load(job_pk) src, dst, user = job.info() - src_addon = src.get_addon(addon_name) + + src_addon = None + if addon_name != 'osfstorage' and flag_is_active(get_current_request(), features.ENABLE_GV): + src_addon = get_addon_from_gv(src, addon_name, user) + else: + src_addon = src.get_addon(addon_name) if hasattr(src_addon, 'configured') and not src_addon.configured: # Addon enabled but not configured - no file trees, nothing to archive. return AggregateStatResult(src_addon._id, addon_short_name) @@ -205,7 +227,11 @@ def archive_addon(addon_short_name, job_pk): params['revision'] = 'latest' if addon_short_name.split('-')[-1] == 'draft' else 'latest-published' rename_suffix = ' (draft)' if addon_short_name.split('-')[-1] == 'draft' else ' (published)' addon_short_name = 'dataverse' - src_provider = src.get_addon(addon_short_name) + src_provider = None + if addon_short_name != 'osfstorage' and flag_is_active(get_current_request(), features.ENABLE_GV): + src_provider = get_addon_from_gv(src, addon_short_name, user) + else: + src_provider = src.get_addon(addon_short_name) folder_name_nfd, folder_name_nfc = normalize_unicode_filenames(src_provider.archive_folder_name) rename = f'{folder_name_nfd}{rename_suffix}' url = waterbutler_api_url_for(src._id, addon_short_name, _internal=True, base_url=src.osfstorage_region.waterbutler_url, **params) diff --git a/website/project/views/file.py b/website/project/views/file.py index 56da1718765..780b26a740c 100644 --- a/website/project/views/file.py +++ b/website/project/views/file.py @@ -34,5 +34,7 @@ def collect_file_trees(auth, node, **kwargs): def grid_data(auth, node, **kwargs): """View that returns the formatted data for rubeus.js/hgrid """ + if flag_is_active(request, features.ENABLE_GV): + return {'data': {}} data = request.args.to_dict() return {'data': rubeus.to_hgrid(node, auth, **data)} diff --git a/website/settings/defaults.py b/website/settings/defaults.py index 0d3f1e85c02..781e24518f6 100644 --- a/website/settings/defaults.py +++ b/website/settings/defaults.py @@ -585,15 +585,15 @@ class CeleryConfig: 'schedule': crontab(minute=0, hour=5), # Daily at 12 a.m. EST 'args': ('email_digest',), }, - 'refresh_addons': { - 'task': 'scripts.refresh_addon_tokens', - 'schedule': crontab(minute=0, hour=7), # Daily 2:00 a.m - 'kwargs': {'dry_run': False, 'addons': { - 'box': 60, # https://docs.box.com/docs/oauth-20#section-6-using-the-access-and-refresh-tokens - 'googledrive': 14, # https://developers.google.com/identity/protocols/OAuth2#expiration - 'mendeley': 14 # http://dev.mendeley.com/reference/topics/authorization_overview.html - }}, - }, + # 'refresh_addons': { # Handled by GravyValet now + # 'task': 'scripts.refresh_addon_tokens', + # 'schedule': crontab(minute=0, hour=7), # Daily 2:00 a.m + # 'kwargs': {'dry_run': False, 'addons': { + # 'box': 60, # https://docs.box.com/docs/oauth-20#section-6-using-the-access-and-refresh-tokens + # 'googledrive': 14, # https://developers.google.com/identity/protocols/OAuth2#expiration + # 'mendeley': 14 # http://dev.mendeley.com/reference/topics/authorization_overview.html + # }}, + # }, 'retract_registrations': { 'task': 'scripts.retract_registrations', 'schedule': crontab(minute=0, hour=5), # Daily 12 a.m