diff --git a/addons/azureblobstorage/static/azureblobstorageAnonymousLogActionList.json b/addons/azureblobstorage/static/azureblobstorageAnonymousLogActionList.json index f987faecded..f597a354ea0 100644 --- a/addons/azureblobstorage/static/azureblobstorageAnonymousLogActionList.json +++ b/addons/azureblobstorage/static/azureblobstorageAnonymousLogActionList.json @@ -7,5 +7,7 @@ "azureblobstorage_folder_created" : "A user created a folder in an Azure Blob Storage in a project", "azureblobstorage_node_authorized" : "A user authorized the Azure Blob Storage addon for a project", "azureblobstorage_node_deauthorized" : "A user deauthorized the Azure Blob Storage addon for a project", - "azureblobstorage_node_deauthorized_no_user" : "Azure Blob Storage addon for a project deauthorized" + "azureblobstorage_node_deauthorized_no_user" : "Azure Blob Storage addon for a project deauthorized", + "azureblobstorage_file_downloaded": "A user downloaded a file from an Azure Blob Storage container in a project", + "azureblobstorage_folder_downloaded_zip": "A user downloaded a folder as a zip from an Azure Blob Storage container in a project" } diff --git a/addons/azureblobstorage/static/azureblobstorageLogActionList.json b/addons/azureblobstorage/static/azureblobstorageLogActionList.json index 11a95cc59c5..12384d6beec 100644 --- a/addons/azureblobstorage/static/azureblobstorageLogActionList.json +++ b/addons/azureblobstorage/static/azureblobstorageLogActionList.json @@ -7,5 +7,7 @@ "azureblobstorage_folder_created" : "${user} created folder ${path} in Azure Blob Storage container ${bucket} in ${node}", "azureblobstorage_node_authorized" : "${user} authorized the Azure Blob Storage addon for ${node}", "azureblobstorage_node_deauthorized" : "${user} deauthorized the Azure Blob Storage addon for ${node}", - "azureblobstorage_node_deauthorized_no_user" : "Azure Blob Storage addon for ${node} deauthorized" + "azureblobstorage_node_deauthorized_no_user" : "Azure Blob Storage addon for ${node} deauthorized", + "azureblobstorage_file_downloaded": "${user} downloaded file ${path} from Azure Blob Storage container ${bucket} in ${node}", + "azureblobstorage_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from Azure Blob Storage container ${bucket} in ${node}" } diff --git a/addons/base/views.py b/addons/base/views.py index c1bb834b7b0..26a96c57462 100644 --- a/addons/base/views.py +++ b/addons/base/views.py @@ -340,6 +340,12 @@ def get_auth(auth, **kwargs): # as default callback_log is True callback_log = False if isinstance(callback_log, str) and callback_log.lower() == 'false' else True + # Suppress download logging for MFR (Mfr File Renderer) initiated downloads. + # MFR downloads are system-initiated renders/views, not user-initiated actions, + # so they should not appear in the Recent Activity log. + if action == 'download' and download_is_from_mfr(request, payload=data): + callback_log = False + is_node_process = True if node_id == ExportData.EXPORT_DATA_FAKE_NODE_ID: is_node_process = False @@ -480,6 +486,8 @@ def get_auth(auth, **kwargs): 'update': NodeLog.FILE_UPDATED, 'delete': NodeLog.FILE_REMOVED, 'create_folder': NodeLog.FOLDER_CREATED, + 'download_file': NodeLog.FILE_DOWNLOADED, + 'download_zip': NodeLog.FOLDER_DOWNLOADED_ZIP, } DOWNLOAD_ACTIONS = set([ @@ -496,12 +504,6 @@ def create_waterbutler_log(payload, **kwargs): with transaction.atomic(): try: auth = payload['auth'] - # Don't log download actions - if payload['action'] in DOWNLOAD_ACTIONS: - guid = Guid.load(payload['metadata'].get('nid')) - if guid: - node = guid.referent - return {'status': 'success'} user = OSFUser.load(auth['id']) if user is None: @@ -599,6 +601,15 @@ def create_waterbutler_log(payload, **kwargs): # Bail out to avoid file_signals return {'status': 'success'} + elif action in (NodeLog.FILE_DOWNLOADED, NodeLog.FOLDER_DOWNLOADED_ZIP): + # Reuse the same per-addon log path as file_added/updated/etc so the action + # gets addon-prefixed (box_file_downloaded, osf_storage_file_downloaded, ...) + # and params['urls'] gets built the same way as other file actions. + node.create_waterbutler_log(auth, action, payload) + # Bail out to avoid file_signals - downloads only need a Recent Activity + # log, not the notification/subscription pipeline used by file mutations. + return {'status': 'success'} + else: node.create_waterbutler_log(auth, action, payload) diff --git a/addons/bitbucket/static/bitbucketAnonymousLogActionList.json b/addons/bitbucket/static/bitbucketAnonymousLogActionList.json index 6b38a90aa86..3ec68260cfb 100644 --- a/addons/bitbucket/static/bitbucketAnonymousLogActionList.json +++ b/addons/bitbucket/static/bitbucketAnonymousLogActionList.json @@ -6,5 +6,7 @@ "bitbucket_node_authorized" : "A user authorized the Bitbucket addon for a project", "bitbucket_node_deauthorized" : "A user deauthorized the Bitbucket addon for a project", "bitbucket_node_deauthorized_no_user" : "Bitbucket addon for a project deauthorized", - "bitbucket_repo_linked" : "A user linked a Bitbucket repo in a project" + "bitbucket_repo_linked" : "A user linked a Bitbucket repo in a project", + "bitbucket_file_downloaded": "A user downloaded a file from a Bitbucket repo in a project", + "bitbucket_folder_downloaded_zip": "A user downloaded a folder as a zip from a Bitbucket repo in a project" } diff --git a/addons/bitbucket/static/bitbucketLogActionList.json b/addons/bitbucket/static/bitbucketLogActionList.json index 01fcd300500..92e0c3b0013 100644 --- a/addons/bitbucket/static/bitbucketLogActionList.json +++ b/addons/bitbucket/static/bitbucketLogActionList.json @@ -6,5 +6,7 @@ "bitbucket_node_authorized" : "${user} authorized the Bitbucket addon for ${node}", "bitbucket_node_deauthorized" : "${user} deauthorized the Bitbucket addon for ${node}", "bitbucket_node_deauthorized_no_user" : "Bitbucket addon for ${node} deauthorized", - "bitbucket_repo_linked" : "${user} linked Bitbucket repo ${bitbucket_repo} to ${node}" + "bitbucket_repo_linked" : "${user} linked Bitbucket repo ${bitbucket_repo} to ${node}", + "bitbucket_file_downloaded": "${user} downloaded file ${path} from Bitbucket repo ${bitbucket_repo} in ${node}", + "bitbucket_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from Bitbucket repo ${bitbucket_repo} in ${node}" } diff --git a/addons/box/static/boxAnonymousLogActionList.json b/addons/box/static/boxAnonymousLogActionList.json index 9250f15cd8c..31b277173eb 100644 --- a/addons/box/static/boxAnonymousLogActionList.json +++ b/addons/box/static/boxAnonymousLogActionList.json @@ -6,5 +6,7 @@ "box_folder_selected" : "A user linked a Box folder to a project", "box_node_authorized" : "A user authorized the Box addon for a project", "box_node_deauthorized" : "A user deauthorized the Box addon for a project", - "box_node_deauthorized_no_user": "Box addon for a project deauthorized" + "box_node_deauthorized_no_user": "Box addon for a project deauthorized", + "box_file_downloaded": "A user downloaded a file from Box in a project", + "box_folder_downloaded_zip": "A user downloaded a folder as a zip from Box in a project" } diff --git a/addons/box/static/boxLogActionList.json b/addons/box/static/boxLogActionList.json index 1a2687dec1a..7f2fe666bbb 100644 --- a/addons/box/static/boxLogActionList.json +++ b/addons/box/static/boxLogActionList.json @@ -6,5 +6,7 @@ "box_folder_selected": "${user} linked Box folder ${box_folder} to ${node}", "box_node_authorized": "${user} authorized the Box addon in ${node}", "box_node_deauthorized": "${user} deauthorized the Box addon for ${node}", - "box_node_deauthorized_no_user": "Box addon for ${node} deauthorized" + "box_node_deauthorized_no_user": "Box addon for ${node} deauthorized", + "box_file_downloaded": "${user} downloaded file ${path} from Box in ${node}", + "box_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from Box in ${node}" } diff --git a/addons/dataverse/static/dataverseAnonymousLogActionList.json b/addons/dataverse/static/dataverseAnonymousLogActionList.json index 09a9560360e..fc7f72adae4 100644 --- a/addons/dataverse/static/dataverseAnonymousLogActionList.json +++ b/addons/dataverse/static/dataverseAnonymousLogActionList.json @@ -7,5 +7,7 @@ "dataverse_study_released" : "A user published a new version of a Dataverse dataset to a project", "dataverse_node_authorized" : "A user authorized the Dataverse addon for a project", "dataverse_node_deauthorized" : "A user deauthorized the Dataverse addon for a project", - "dataverse_node_deauthorized_no_user" : "Dataverse addon for a project deauthorized" + "dataverse_node_deauthorized_no_user" : "Dataverse addon for a project deauthorized", + "dataverse_file_downloaded": "A user downloaded a file from a Dataverse dataset in a project", + "dataverse_folder_downloaded_zip": "A user downloaded a folder as a zip from a Dataverse dataset in a project" } diff --git a/addons/dataverse/static/dataverseLogActionList.json b/addons/dataverse/static/dataverseLogActionList.json index 80ee71079ec..e61a6ad0365 100644 --- a/addons/dataverse/static/dataverseLogActionList.json +++ b/addons/dataverse/static/dataverseLogActionList.json @@ -7,5 +7,7 @@ "dataverse_study_released" : "${user} published a new version of Dataverse dataset ${study} to ${node}", "dataverse_node_authorized" : "${user} authorized the Dataverse addon for ${node}", "dataverse_node_deauthorized" : "${user} deauthorized the Dataverse addon for ${node}", - "dataverse_node_deauthorized_no_user" : "Dataverse addon for ${node} deauthorized" + "dataverse_node_deauthorized_no_user" : "Dataverse addon for ${node} deauthorized", + "dataverse_file_downloaded": "${user} downloaded file ${filename} from Dataverse dataset ${dataset} in ${node}", + "dataverse_folder_downloaded_zip": "${user} downloaded folder ${filename} as a zip from Dataverse dataset ${dataset} in ${node}" } \ No newline at end of file diff --git a/addons/dropbox/static/dropboxAnonymousLogActionList.json b/addons/dropbox/static/dropboxAnonymousLogActionList.json index 1838056ffa8..40656016a35 100644 --- a/addons/dropbox/static/dropboxAnonymousLogActionList.json +++ b/addons/dropbox/static/dropboxAnonymousLogActionList.json @@ -6,5 +6,7 @@ "dropbox_folder_selected" : "A user linked a Dropbox folder in a project", "dropbox_node_authorized" : "A user authorized the Dropbox addon for a project", "dropbox_node_deauthorized" : "A user deauthorized the Dropbox addon for a project", - "dropbox_node_deauthorized_no_user" : "Dropbox addon for a project deauthorized" + "dropbox_node_deauthorized_no_user" : "Dropbox addon for a project deauthorized", + "dropbox_file_downloaded": "A user downloaded a file from Dropbox in a project", + "dropbox_folder_downloaded_zip": "A user downloaded a folder as a zip from Dropbox in a project" } diff --git a/addons/dropbox/static/dropboxLogActionList.json b/addons/dropbox/static/dropboxLogActionList.json index f448f4bf776..25bb45137e8 100644 --- a/addons/dropbox/static/dropboxLogActionList.json +++ b/addons/dropbox/static/dropboxLogActionList.json @@ -6,5 +6,7 @@ "dropbox_folder_selected" : "${user} linked Dropbox folder ${dropbox_folder} to ${node}", "dropbox_node_authorized" : "${user} authorized the Dropbox addon for ${node}", "dropbox_node_deauthorized" : "${user} deauthorized the Dropbox addon for ${node}", - "dropbox_node_deauthorized_no_user" : "Dropbox addon for ${node} deauthorized" + "dropbox_node_deauthorized_no_user" : "Dropbox addon for ${node} deauthorized", + "dropbox_file_downloaded": "${user} downloaded file ${path} from Dropbox in ${node}", + "dropbox_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from Dropbox in ${node}" } diff --git a/addons/dropboxbusiness/static/dropboxbusinessAnonymousLogActionList.json b/addons/dropboxbusiness/static/dropboxbusinessAnonymousLogActionList.json index d7a80b4fd16..d92157d99b2 100644 --- a/addons/dropboxbusiness/static/dropboxbusinessAnonymousLogActionList.json +++ b/addons/dropboxbusiness/static/dropboxbusinessAnonymousLogActionList.json @@ -6,5 +6,7 @@ "dropboxbusiness_folder_selected" : "A user linked a Dropbox Business folder in a project", "dropboxbusiness_node_authorized" : "A user authorized the Dropbox Business addon for a project", "dropboxbusiness_node_deauthorized" : "A user deauthorized the Dropbox Business addon for a project", - "dropboxbusiness_node_deauthorized_no_user" : "Dropbox Business addon for a project deauthorized" + "dropboxbusiness_node_deauthorized_no_user" : "Dropbox Business addon for a project deauthorized", + "dropboxbusiness_file_downloaded": "A user downloaded a file from Dropbox Business in a project", + "dropboxbusiness_folder_downloaded_zip": "A user downloaded a folder as a zip from Dropbox Business in a project" } diff --git a/addons/dropboxbusiness/static/dropboxbusinessLogActionList.json b/addons/dropboxbusiness/static/dropboxbusinessLogActionList.json index 86485fddead..c529bcebf8a 100644 --- a/addons/dropboxbusiness/static/dropboxbusinessLogActionList.json +++ b/addons/dropboxbusiness/static/dropboxbusinessLogActionList.json @@ -6,5 +6,7 @@ "dropboxbusiness_folder_selected" : "${user} linked Dropbox Business folder ${dropboxbusiness_folder} to ${node}", "dropboxbusiness_node_authorized" : "${user} authorized the Dropbox Business addon for ${node}", "dropboxbusiness_node_deauthorized" : "${user} deauthorized the Dropbox Business addon for ${node}", - "dropboxbusiness_node_deauthorized_no_user" : "Dropbox Business addon for ${node} deauthorized" + "dropboxbusiness_node_deauthorized_no_user" : "Dropbox Business addon for ${node} deauthorized", + "dropboxbusiness_file_downloaded": "${user} downloaded file ${path} from Dropbox Business in ${node}", + "dropboxbusiness_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from Dropbox Business in ${node}" } diff --git a/addons/figshare/static/figshareAnonymousLogActionList.json b/addons/figshare/static/figshareAnonymousLogActionList.json index 382b71bd4c5..ad2d20c7a76 100644 --- a/addons/figshare/static/figshareAnonymousLogActionList.json +++ b/addons/figshare/static/figshareAnonymousLogActionList.json @@ -6,5 +6,7 @@ "figshare_folder_created" : "A user created a folder in figshare in a project", "figshare_node_authorized" : "A user authorized the figshare addon for a project", "figshare_node_deauthorized" : "A user deauthorized the figshare addon for a project", - "figshare_node_deauthorized_no_user" : "figshare addon for a project deauthorized" + "figshare_node_deauthorized_no_user" : "figshare addon for a project deauthorized", + "figshare_file_downloaded": "A user downloaded a file from figshare in a project", + "figshare_folder_downloaded_zip": "A user downloaded a folder as a zip from figshare in a project" } diff --git a/addons/figshare/static/figshareLogActionList.json b/addons/figshare/static/figshareLogActionList.json index c987277398b..33788b8dcb4 100644 --- a/addons/figshare/static/figshareLogActionList.json +++ b/addons/figshare/static/figshareLogActionList.json @@ -6,5 +6,7 @@ "figshare_folder_created" : "${user} created folder ${path} in figshare in ${node}", "figshare_node_authorized" : "${user} authorized the figshare addon for ${node}", "figshare_node_deauthorized" : "${user} deauthorized the figshare addon for ${node}", - "figshare_node_deauthorized_no_user" : "figshare addon for ${node} deauthorized" + "figshare_node_deauthorized_no_user" : "figshare addon for ${node} deauthorized", + "figshare_file_downloaded": "${user} downloaded file ${path} from figshare in ${node}", + "figshare_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from figshare in ${node}" } diff --git a/addons/github/static/githubAnonymousLogActionList.json b/addons/github/static/githubAnonymousLogActionList.json index 3b27657a8c2..82c61cf5f9f 100644 --- a/addons/github/static/githubAnonymousLogActionList.json +++ b/addons/github/static/githubAnonymousLogActionList.json @@ -6,5 +6,7 @@ "github_node_authorized" : "A user authorized the GitHub addon for a project", "github_node_deauthorized" : "A user deauthorized the GitHub addon for a project", "github_node_deauthorized_no_user" : "GitHub addon for a project deauthorized", - "github_repo_linked" : "A user linked a GitHub repo in a project" + "github_repo_linked" : "A user linked a GitHub repo in a project", + "github_file_downloaded": "A user downloaded a file from a GitHub repo in a project", + "github_folder_downloaded_zip": "A user downloaded a folder as a zip from a GitHub repo in a project" } diff --git a/addons/github/static/githubLogActionList.json b/addons/github/static/githubLogActionList.json index 71bdde80e19..d3f5b670d7b 100644 --- a/addons/github/static/githubLogActionList.json +++ b/addons/github/static/githubLogActionList.json @@ -6,5 +6,7 @@ "github_node_authorized" : "${user} authorized the GitHub addon for ${node}", "github_node_deauthorized" : "${user} deauthorized the GitHub addon for ${node}", "github_node_deauthorized_no_user" : "GitHub addon for ${node} deauthorized", - "github_repo_linked" : "${user} linked GitHub repo ${repo} to ${node}" + "github_repo_linked" : "${user} linked GitHub repo ${repo} to ${node}", + "github_file_downloaded": "${user} downloaded file ${path} from GitHub repo ${repo} in ${node}", + "github_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from GitHub repo ${repo} in ${node}" } diff --git a/addons/gitlab/models.py b/addons/gitlab/models.py index 0c8ac76bb14..f26cad967f9 100644 --- a/addons/gitlab/models.py +++ b/addons/gitlab/models.py @@ -263,7 +263,7 @@ def create_waterbutler_log(self, auth, action, metadata): sha = None urls = {} else: - sha = metadata['extra']['fileSha'] + sha = metadata['extra']['commitSha'] urls = { 'view': '{0}?branch={1}'.format(url, sha), 'download': '{0}?action=download&branch={1}'.format(url, sha) diff --git a/addons/gitlab/static/gitlabLogActionList.json b/addons/gitlab/static/gitlabLogActionList.json index 731efcafb2b..1f8f28cfaa9 100644 --- a/addons/gitlab/static/gitlabLogActionList.json +++ b/addons/gitlab/static/gitlabLogActionList.json @@ -6,5 +6,7 @@ "gitlab_node_authorized" : "${user} authorized the GitLab addon for ${node}", "gitlab_node_deauthorized" : "${user} deauthorized the GitLab addon for ${node}", "gitlab_node_deauthorized_no_user" : "GitLab addon for ${node} deauthorized", - "gitlab_repo_linked" : "${user} linked GitLab repo ${gitlab_repo} to ${node}" + "gitlab_repo_linked" : "${user} linked GitLab repo ${gitlab_repo} to ${node}", + "gitlab_file_downloaded": "${user} downloaded file ${path} from GitLab repo ${gitlab_repo} in ${node}", + "gitlab_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from GitLab repo ${gitlab_repo} in ${node}" } diff --git a/addons/gitlab/tests/test_models.py b/addons/gitlab/tests/test_models.py index 8ef6bf9e9ac..6d249f16a6a 100644 --- a/addons/gitlab/tests/test_models.py +++ b/addons/gitlab/tests/test_models.py @@ -225,6 +225,44 @@ def test_does_not_get_copied_to_registrations(self, mock_archive): ) assert_false(registration.has_addon('gitlab')) + def test_create_waterbutler_log_with_extra_uses_commit_sha(self): + # Regression test: 'extra' used to be indexed with the wrong key + # ('fileSha'), which always raised KeyError since waterbutler never + # sends that key for GitLab - both GitLabPath.extra and + # GitLabFileMetadata.extra key the commit sha as 'commitSha'. + metadata = { + 'path': 'proj/file.py', + 'extra': { + 'commitSha': 'deadbeef', + 'branchName': 'main', + }, + } + self.node_settings.create_waterbutler_log( + self.consolidated_auth, 'file_downloaded', metadata + ) + log = self.project.logs.latest() + assert_equal(log.action, 'gitlab_file_downloaded') + assert_equal(log.params['path'], 'proj/file.py') + assert_equal(log.params['gitlab']['sha'], 'deadbeef') + url = self.project.web_url_for( + 'addon_view_or_download_file', path='proj/file.py', provider='gitlab' + ) + assert_equal(log.params['urls']['view'], '{0}?branch=deadbeef'.format(url)) + assert_equal( + log.params['urls']['download'], + '{0}?action=download&branch=deadbeef'.format(url) + ) + + def test_create_waterbutler_log_without_extra(self): + metadata = {'path': '', 'extra': {}} + self.node_settings.create_waterbutler_log( + self.consolidated_auth, 'folder_downloaded_zip', metadata + ) + log = self.project.logs.latest() + assert_equal(log.action, 'gitlab_folder_downloaded_zip') + assert_equal(log.params['urls'], {}) + assert_equal(log.params['gitlab']['sha'], None) + class TestGitLabNodeSettings(unittest.TestCase): diff --git a/addons/googledrive/static/googledriveAnonymousLogActionList.json b/addons/googledrive/static/googledriveAnonymousLogActionList.json index cb595c102ec..e9e41ba89f9 100644 --- a/addons/googledrive/static/googledriveAnonymousLogActionList.json +++ b/addons/googledrive/static/googledriveAnonymousLogActionList.json @@ -6,5 +6,7 @@ "googledrive_folder_selected" : "A user linked a Google Drive folder to a project", "googledrive_node_authorized" : "A user authorized the Google Drive addon for a project", "googledrive_node_deauthorized" : "A user deauthorized the Google Drive addon for a project", - "googledrive_node_deauthorized_no_user" : "Google Drive addon for a project deauthorized" + "googledrive_node_deauthorized_no_user" : "Google Drive addon for a project deauthorized", + "googledrive_file_downloaded": "A user downloaded a file from Google Drive in a project", + "googledrive_folder_downloaded_zip": "A user downloaded a folder as a zip from Google Drive in a project" } diff --git a/addons/googledrive/static/googledriveLogActionList.json b/addons/googledrive/static/googledriveLogActionList.json index b9b55f2ba6d..cbdb59db4ed 100644 --- a/addons/googledrive/static/googledriveLogActionList.json +++ b/addons/googledrive/static/googledriveLogActionList.json @@ -6,5 +6,7 @@ "googledrive_folder_selected" : "${user} linked Google Drive folder ${googledrive_folder} to ${node}", "googledrive_node_authorized" : "${user} authorized the Google Drive addon for ${node}", "googledrive_node_deauthorized" : "${user} deauthorized the Google Drive addon for ${node}", - "googledrive_node_deauthorized_no_user" : "Google Drive addon for ${node} deauthorized" + "googledrive_node_deauthorized_no_user" : "Google Drive addon for ${node} deauthorized", + "googledrive_file_downloaded": "${user} downloaded file ${googledrive_path} from Google Drive in ${node}", + "googledrive_folder_downloaded_zip": "${user} downloaded folder ${googledrive_path} as a zip from Google Drive in ${node}" } diff --git a/addons/iqbrims/static/iqbrimsAnonymousLogActionList.json b/addons/iqbrims/static/iqbrimsAnonymousLogActionList.json index 9635f72c62b..cb7b3cc5a4f 100644 --- a/addons/iqbrims/static/iqbrimsAnonymousLogActionList.json +++ b/addons/iqbrims/static/iqbrimsAnonymousLogActionList.json @@ -6,5 +6,7 @@ "iqbrims_folder_selected" : "A user linked a IQB-RIMS folder to a project", "iqbrims_node_authorized" : "A user authorized the IQB-RIMS addon for a project", "iqbrims_node_deauthorized" : "A user deauthorized the IQB-RIMS addon for a project", - "iqbrims_node_deauthorized_no_user" : "IQB-RIMS addon for a project deauthorized" + "iqbrims_node_deauthorized_no_user" : "IQB-RIMS addon for a project deauthorized", + "iqbrims_file_downloaded": "A user downloaded a file from IQB-RIMS in a project", + "iqbrims_folder_downloaded_zip": "A user downloaded a folder as a zip from IQB-RIMS in a project" } diff --git a/addons/iqbrims/static/iqbrimsLogActionList.json b/addons/iqbrims/static/iqbrimsLogActionList.json index 507f668f1ac..8b5c3a3e051 100644 --- a/addons/iqbrims/static/iqbrimsLogActionList.json +++ b/addons/iqbrims/static/iqbrimsLogActionList.json @@ -10,5 +10,7 @@ "iqbrims_imagescan_workflow_start": "${user} started IQB-RIMS workflow for ${node}", "iqbrims_imagescan_workflow_finish": "Finished workflow of ${user} for ${node}", "iqbrims_paper_workflow_start": "${user} started IQB-RIMS workflow for ${node}", - "iqbrims_paper_workflow_finish": "Finished workflow of ${user} for ${node}" + "iqbrims_paper_workflow_finish": "Finished workflow of ${user} for ${node}", + "iqbrims_file_downloaded": "${user} downloaded file ${iqbrims_path} from IQB-RIMS in ${node}", + "iqbrims_folder_downloaded_zip": "${user} downloaded folder ${iqbrims_path} as a zip from IQB-RIMS in ${node}" } diff --git a/addons/metadata/models.py b/addons/metadata/models.py index cdfeabd79db..2059c211cc3 100644 --- a/addons/metadata/models.py +++ b/addons/metadata/models.py @@ -742,6 +742,8 @@ def download(self): url = furl.furl(website_settings.WATERBUTLER_INTERNAL_URL) file_url = furl.furl(self.links['download']) url.path = str(file_url.path) + # Suppress logging for metadata asset sync downloads (internal system operation) + url.args['callback_log'] = 'false' response = requests.get( url.url, headers={'content-type': 'application/json'}, diff --git a/addons/metadata/packages.py b/addons/metadata/packages.py index aec258c1366..0a4d26acf54 100644 --- a/addons/metadata/packages.py +++ b/addons/metadata/packages.py @@ -185,6 +185,8 @@ def download_to(self, f): url = furl.furl(website_settings.WATERBUTLER_INTERNAL_URL) file_url = furl.furl(self.links['download']) url.path = str(file_url.path) + # Suppress logging for RO-Crate export downloads (internal system operation) + url.args['callback_log'] = 'false' response = requests.get( url.url, headers={'content-type': 'application/json'}, diff --git a/addons/metadata/tests/test_model.py b/addons/metadata/tests/test_model.py index e86f1d713f9..7c06cebfe61 100644 --- a/addons/metadata/tests/test_model.py +++ b/addons/metadata/tests/test_model.py @@ -10,7 +10,7 @@ from osf_tests.factories import ProjectFactory, OsfStorageFileFactory from framework.auth import Auth -from ..models import NodeSettings, FileMetadata +from ..models import NodeSettings, FileMetadata, WaterButlerObject from .factories import NodeSettingsFactory from .utils import remove_fields @@ -855,3 +855,36 @@ def test_duplicated_file_metadata(self): folder=False, project=self.node_settings, ) + + +class TestMetadataAssetSyncCallbackLogSuppression(unittest.TestCase): + """addons/metadata/models.py: WaterButlerObject.download() is used by + safe_download_metadata_asset_pool() (metadata asset sync, an internal system + operation) and must suppress Recent Activity logging via callback_log=false. + + Real behavioral test - previously this method had zero coverage anywhere: + the class name coincidentally shows up all over addons/metadata/tests/test_packages.py + via `rocrate.download_to(...)`, but that's a different class entirely + (BaseROCrateFactory.download_to), and the test helpers there replace + WaterButlerObject with a bare MagicMock whose download_to is stubbed - the + real HTTP-building code was never exercised.""" + + def test_download_suppresses_callback_log(self): + wb = mock.MagicMock() + wb.cookie = 'test-cookie' + wb_object = WaterButlerObject({ + 'links': { + 'download': 'http://waterbutler.example/v1/resources/abc12/providers/osfstorage/xyz', + }, + }, wb) + + mock_response = mock.MagicMock() + mock_response.text = 'file content' + + with mock.patch('addons.metadata.models.requests.get', return_value=mock_response) as mock_get: + content = wb_object.download() + + assert_equal(content, 'file content') + mock_response.raise_for_status.assert_called_once() + called_url = mock_get.call_args[0][0] + assert_true('callback_log=false' in called_url) diff --git a/addons/metadata/tests/test_packages.py b/addons/metadata/tests/test_packages.py index 30aba8f8020..c819116a9b6 100644 --- a/addons/metadata/tests/test_packages.py +++ b/addons/metadata/tests/test_packages.py @@ -21,7 +21,7 @@ from addons.wiki.tests.factories import WikiFactory, WikiVersionFactory from .utils import remove_fields -from ..packages import ROCrateFactory, ROCrateExtractor +from ..packages import ROCrateFactory, ROCrateExtractor, WaterButlerObject logger = logging.getLogger(__name__) @@ -1488,3 +1488,41 @@ def test_simple_extraction_on_error(self): new_wb, new_node_wb, new_wb_upload_file = _create_waterbutler_client_for_single_node(new_node, {}) with assert_raises(Exception): extractor.ensure_node(new_node) + + +class TestWaterButlerObjectDownloadToCallbackLogSuppression: + """addons/metadata/packages.py: WaterButlerObject.download_to() is used by + RO-Crate export (an internal system operation) and must suppress Recent + Activity logging via callback_log=false. + + Real behavioral test - despite this module's ~1490 lines of tests calling + `rocrate.download_to(zip_path)`, none of them exercise this method: `rocrate` + is a ROCrateFactory -> BaseROCrateFactory, whose own download_to (a totally + different method, same name) internally calls file.download_to(df) on + per-file objects - but the test helper `_create_waterbutler_object()` replaces + those with a bare MagicMock whose download_to is stubbed to just + `f.write(file['content'])`. The real WaterButlerObject.download_to, with the + callback_log-suppressing URL construction, was never invoked.""" + + def test_download_to_suppresses_callback_log(self): + wb = mock.MagicMock() + wb.cookie = 'test-cookie' + wb_object = WaterButlerObject({ + 'links': { + 'download': 'http://waterbutler.example/v1/resources/abc12/providers/osfstorage/xyz', + }, + }, wb) + + mock_response = mock.MagicMock() + mock_response.iter_content = mock.MagicMock(return_value=[b'chunk1', b'chunk2']) + + buf = io.BytesIO() + with mock.patch('addons.metadata.packages.requests.get', return_value=mock_response) as mock_get: + wb_object.download_to(buf) + + assert_equals(buf.getvalue(), b'chunk1chunk2') + mock_response.raise_for_status.assert_called_once() + called_url = mock_get.call_args[0][0] + assert_true('callback_log=false' in called_url) + called_kwargs = mock_get.call_args[1] + assert_true(called_kwargs.get('stream') is True) diff --git a/addons/nextcloud/static/nextcloudAnonymousLogActionList.json b/addons/nextcloud/static/nextcloudAnonymousLogActionList.json index 4fb93cbb84a..9a5a4e7cc3b 100644 --- a/addons/nextcloud/static/nextcloudAnonymousLogActionList.json +++ b/addons/nextcloud/static/nextcloudAnonymousLogActionList.json @@ -6,5 +6,7 @@ "nextcloud_folder_selected" : "A user linked a Nextcloud folder in a project", "nextcloud_node_authorized" : "A user authorized the Nextcloud addon for a project", "nextcloud_node_deauthorized" : "A user deauthorized the Nextcloud addon for a project", - "nextcloud_node_deauthorized_no_user" : "Nextcloud addon for a project deauthorized" + "nextcloud_node_deauthorized_no_user" : "Nextcloud addon for a project deauthorized", + "nextcloud_file_downloaded": "A user downloaded a file from Nextcloud in a project", + "nextcloud_folder_downloaded_zip": "A user downloaded a folder as a zip from Nextcloud in a project" } diff --git a/addons/nextcloud/static/nextcloudLogActionList.json b/addons/nextcloud/static/nextcloudLogActionList.json index 48906d78ec1..f97b8079cc6 100644 --- a/addons/nextcloud/static/nextcloudLogActionList.json +++ b/addons/nextcloud/static/nextcloudLogActionList.json @@ -6,6 +6,7 @@ "nextcloud_folder_selected" : "${user} linked Nextcloud folder ${folder} to ${node}", "nextcloud_node_authorized" : "${user} authorized the Nextcloud addon for ${node}", "nextcloud_node_deauthorized" : "${user} deauthorized the Nextcloud addon for ${node}", - "nextcloud_node_deauthorized_no_user" : "Nextcloud addon for ${node} deauthorized" - + "nextcloud_node_deauthorized_no_user" : "Nextcloud addon for ${node} deauthorized", + "nextcloud_file_downloaded": "${user} downloaded file ${path} from Nextcloud in ${node}", + "nextcloud_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from Nextcloud in ${node}" } diff --git a/addons/nextcloudinstitutions/static/nextcloudinstitutionsAnonymousLogActionList.json b/addons/nextcloudinstitutions/static/nextcloudinstitutionsAnonymousLogActionList.json index ea901d51a32..7e803f37d19 100644 --- a/addons/nextcloudinstitutions/static/nextcloudinstitutionsAnonymousLogActionList.json +++ b/addons/nextcloudinstitutions/static/nextcloudinstitutionsAnonymousLogActionList.json @@ -6,5 +6,7 @@ "nextcloudinstitutions_folder_selected" : "A user linked a Nextcloud (for Institutions) folder in a project", "nextcloudinstitutions_node_authorized" : "A user authorized the Nextcloud (for Institutions) addon for a project", "nextcloudinstitutions_node_deauthorized" : "A user deauthorized the Nextcloud (for Institutions) addon for a project", - "nextcloudinstitutions_node_deauthorized_no_user" : "Nextcloud (for Institutions) addon for a project deauthorized" + "nextcloudinstitutions_node_deauthorized_no_user" : "Nextcloud (for Institutions) addon for a project deauthorized", + "nextcloudinstitutions_file_downloaded": "A user downloaded a file from Nextcloud (for Institutions) in a project", + "nextcloudinstitutions_folder_downloaded_zip": "A user downloaded a folder as a zip from Nextcloud (for Institutions) in a project" } diff --git a/addons/nextcloudinstitutions/static/nextcloudinstitutionsLogActionList.json b/addons/nextcloudinstitutions/static/nextcloudinstitutionsLogActionList.json index 80c4ea6f771..f6ce106f01e 100644 --- a/addons/nextcloudinstitutions/static/nextcloudinstitutionsLogActionList.json +++ b/addons/nextcloudinstitutions/static/nextcloudinstitutionsLogActionList.json @@ -6,5 +6,7 @@ "nextcloudinstitutions_folder_selected" : "${user} linked Nextcloud (for Institutions) folder ${folder} to ${node}", "nextcloudinstitutions_node_authorized" : "${user} authorized the Nextcloud (for Institutions) addon for ${node}", "nextcloudinstitutions_node_deauthorized" : "${user} deauthorized the Nextcloud (for Institutions) addon for ${node}", - "nextcloudinstitutions_node_deauthorized_no_user" : "Nextcloud (for Institutions) addon for ${node} deauthorized" + "nextcloudinstitutions_node_deauthorized_no_user" : "Nextcloud (for Institutions) addon for ${node} deauthorized", + "nextcloudinstitutions_file_downloaded": "${user} downloaded file ${path} from Nextcloud (for Institutions) in ${node}", + "nextcloudinstitutions_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from Nextcloud (for Institutions) in ${node}" } diff --git a/addons/ociinstitutions/static/ociinstitutionsAnonymousLogActionList.json b/addons/ociinstitutions/static/ociinstitutionsAnonymousLogActionList.json index bc949e478d1..259f1db8b03 100644 --- a/addons/ociinstitutions/static/ociinstitutionsAnonymousLogActionList.json +++ b/addons/ociinstitutions/static/ociinstitutionsAnonymousLogActionList.json @@ -6,5 +6,7 @@ "ociinstitutions_folder_selected" : "A user linked a Oracle Cloud Infrastructure (for Institutions) folder in a project", "ociinstitutions_node_authorized" : "A user authorized the Oracle Cloud Infrastructure (for Institutions) addon for a project", "ociinstitutions_node_deauthorized" : "A user deauthorized the Oracle Cloud Infrastructure (for Institutions) addon for a project", - "ociinstitutions_node_deauthorized_no_user" : "Oracle Cloud Infrastructure (for Institutions) addon for a project deauthorized" + "ociinstitutions_node_deauthorized_no_user" : "Oracle Cloud Infrastructure (for Institutions) addon for a project deauthorized", + "ociinstitutions_file_downloaded": "A user downloaded a file from Oracle Cloud Infrastructure for Institutions in a project", + "ociinstitutions_folder_downloaded_zip": "A user downloaded a folder as a zip from Oracle Cloud Infrastructure for Institutions in a project" } diff --git a/addons/ociinstitutions/static/ociinstitutionsLogActionList.json b/addons/ociinstitutions/static/ociinstitutionsLogActionList.json index 936b5419557..bcb5c6f494f 100644 --- a/addons/ociinstitutions/static/ociinstitutionsLogActionList.json +++ b/addons/ociinstitutions/static/ociinstitutionsLogActionList.json @@ -6,5 +6,7 @@ "ociinstitutions_folder_selected" : "${user} linked Oracle Cloud Infrastructure for Institutions folder ${folder} to ${node}", "ociinstitutions_node_authorized" : "${user} authorized the Oracle Cloud Infrastructure for Institutions addon for ${node}", "ociinstitutions_node_deauthorized" : "${user} deauthorized the Oracle Cloud Infrastructure for Institutions addon for ${node}", - "ociinstitutions_node_deauthorized_no_user" : "Oracle Cloud Infrastructure for Institutions addon for ${node} deauthorized" + "ociinstitutions_node_deauthorized_no_user" : "Oracle Cloud Infrastructure for Institutions addon for ${node} deauthorized", + "ociinstitutions_file_downloaded": "${user} downloaded file ${path} from Oracle Cloud Infrastructure for Institutions in ${node}", + "ociinstitutions_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from Oracle Cloud Infrastructure for Institutions in ${node}" } diff --git a/addons/onedrive/static/onedriveAnonymousLogActionList.json b/addons/onedrive/static/onedriveAnonymousLogActionList.json index c6695e5e847..1cbf78eeb44 100644 --- a/addons/onedrive/static/onedriveAnonymousLogActionList.json +++ b/addons/onedrive/static/onedriveAnonymousLogActionList.json @@ -6,5 +6,7 @@ "onedrive_folder_selected" : "A user linked a Microsoft OneDrive folder to a project", "onedrive_node_authorized" : "A user authorized the Microsoft OneDrive addon for a project", "onedrive_node_deauthorized" : "A user deauthorized the Microsoft OneDrive addon for a project", - "onedrive_node_deauthorized_no_user" : "Microsoft OneDrive addon for a project deauthorized" + "onedrive_node_deauthorized_no_user" : "Microsoft OneDrive addon for a project deauthorized", + "onedrive_file_downloaded": "A user downloaded a file from Microsoft OneDrive in a project", + "onedrive_folder_downloaded_zip": "A user downloaded a folder as a zip from Microsoft OneDrive in a project" } diff --git a/addons/onedrive/static/onedriveLogActionList.json b/addons/onedrive/static/onedriveLogActionList.json index 850cbaed323..3a2318d4bc6 100644 --- a/addons/onedrive/static/onedriveLogActionList.json +++ b/addons/onedrive/static/onedriveLogActionList.json @@ -6,5 +6,7 @@ "onedrive_folder_selected" : "${user} linked Microsoft OneDrive folder ${onedrive_folder} to ${node}", "onedrive_node_authorized" : "${user} authorized the Microsoft OneDrive addon for ${node}", "onedrive_node_deauthorized" : "${user} deauthorized the Microsoft OneDrive addon for ${node}", - "onedrive_node_deauthorized_no_user" : "Microsoft OneDrive addon for ${node} deauthorized" + "onedrive_node_deauthorized_no_user" : "Microsoft OneDrive addon for ${node} deauthorized", + "onedrive_file_downloaded": "${user} downloaded file ${path} from Microsoft OneDrive in ${node}", + "onedrive_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from Microsoft OneDrive in ${node}" } diff --git a/addons/onedrivebusiness/static/onedrivebusinessAnonymousLogActionList.json b/addons/onedrivebusiness/static/onedrivebusinessAnonymousLogActionList.json index 4e58cb91042..28b3d9c6211 100644 --- a/addons/onedrivebusiness/static/onedrivebusinessAnonymousLogActionList.json +++ b/addons/onedrivebusiness/static/onedrivebusinessAnonymousLogActionList.json @@ -5,5 +5,7 @@ "onedrivebusiness_folder_created" : "A user created a folder in an OneDrive team folder in a project", "onedrivebusiness_node_authorized" : "A user authorized the OneDrive for Office365 addon for a project", "onedrivebusiness_node_deauthorized" : "A user deauthorized the OneDrive for Office365 addon for a project", - "onedrivebusiness_node_deauthorized_no_user" : "OneDrive for Office365 addon for a project deauthorized" + "onedrivebusiness_node_deauthorized_no_user" : "OneDrive for Office365 addon for a project deauthorized", + "onedrivebusiness_file_downloaded": "A user downloaded a file from an OneDrive team folder in a project", + "onedrivebusiness_folder_downloaded_zip": "A user downloaded a folder as a zip from an OneDrive team folder in a project" } diff --git a/addons/onedrivebusiness/static/onedrivebusinessLogActionList.json b/addons/onedrivebusiness/static/onedrivebusinessLogActionList.json index 9e3a646b252..16d5cf34c05 100644 --- a/addons/onedrivebusiness/static/onedrivebusinessLogActionList.json +++ b/addons/onedrivebusiness/static/onedrivebusinessLogActionList.json @@ -5,5 +5,7 @@ "onedrivebusiness_folder_created" : "${user} created folder ${path} in the OneDrive team folder ${folder} in ${node}", "onedrivebusiness_node_authorized" : "${user} authorized the OneDrive for Office365 addon for ${node}", "onedrivebusiness_node_deauthorized" : "${user} deauthorized the OneDrive for Office365 addon for ${node}", - "onedrivebusiness_node_deauthorized_no_user" : "OneDrive for Office365 addon for ${node} deauthorized" + "onedrivebusiness_node_deauthorized_no_user" : "OneDrive for Office365 addon for ${node} deauthorized", + "onedrivebusiness_file_downloaded": "${user} downloaded file ${path} from the OneDrive team folder ${folder} in ${node}", + "onedrivebusiness_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from the OneDrive team folder ${folder} in ${node}" } diff --git a/addons/onlyoffice/tests/test_view.py b/addons/onlyoffice/tests/test_view.py index de31b825f3a..f713d012bf5 100644 --- a/addons/onlyoffice/tests/test_view.py +++ b/addons/onlyoffice/tests/test_view.py @@ -103,3 +103,49 @@ def test_check_file_info(self): with mock.patch.object(onlyoffice_util, 'check_proof_key', return_value=mock_proof_key): res = onlyoffice_check_file_info(file_id='ABCDEFG') assert res['BaseFileName'] == 'filename.docx' + + +class TestONLYOFFICEWOPICallbackLogSuppression(OsfTestCase): + """addons/onlyoffice/views.py: WOPI PutFile (POST) must pass callback_log='true' + explicitly, opting out of the URL-builder's default _internal=True suppression - + otherwise ONLYOFFICE saves would silently stop creating their FILE_UPDATED Recent + Activity log. Zero test coverage existed for this view before (verified via + repo-wide grep for onlyoffice_file_content_view/callback_log).""" + + def test_edit_by_onlyoffice_putfile_suppresses_callback_log_optout(self): + mock_jsonobj = {'data': {'auth': 'cookie string', 'file_id': 'ABCDEFG'}} + mock_filenode = mock.MagicMock() + mock_filenode.name = 'filename.docx' + mock_filenode.generate_waterbutler_url = mock.MagicMock( + return_value='http://waterbutler.example/v1/resources/abc12/providers/osfstorage/xyz?callback_log=true' + ) + mock_user_info = {'user_id': 'userid', 'full_name': 'fullname', 'display_name': 'dispname'} + mock_file_info = {'name': 'filename.docx', 'mtime': '202501010000'} + mock_file_version = '' + + mock_request = mock.MagicMock() + mock_request.method = 'POST' + mock_request.args.get = mock.MagicMock(return_value='access-token-value') + mock_request.headers.get = mock.MagicMock(return_value='10') + + mock_put_response = mock.MagicMock() + mock_put_response.status_code = 200 + + with mock.patch('addons.onlyoffice.views.request', mock_request): + with mock.patch.object(BaseFileNode.objects, 'get', return_value=mock_filenode): + with mock.patch.object(onlyoffice_util, 'get_user_info', return_value=mock_user_info): + with mock.patch.object(onlyoffice_token, 'decrypt', return_value=mock_jsonobj): + with mock.patch.object(onlyoffice_token, 'check_token', return_value=True): + with mock.patch.object(onlyoffice_token, 'get_cookie', return_value='cookie'): + with mock.patch.object(onlyoffice_util, 'get_file_info', return_value=mock_file_info): + with mock.patch.object(onlyoffice_util, 'get_file_version', return_value=mock_file_version): + with mock.patch.object(onlyoffice_util, 'check_proof_key', return_value=True): + with mock.patch('addons.onlyoffice.views.requests.put', return_value=mock_put_response) as mock_put: + res = onlyoffice_views.onlyoffice_file_content_view(file_id='ABCDEFG') + + mock_filenode.generate_waterbutler_url.assert_called_once_with( + direct=None, _internal=True, callback_log='true', kind='file' + ) + called_url = mock_put.call_args[1]['url'] + assert 'callback_log=true' in called_url + assert res.status_code == 200 diff --git a/addons/onlyoffice/views.py b/addons/onlyoffice/views.py index ee6b627b0d0..9ec1aa06789 100644 --- a/addons/onlyoffice/views.py +++ b/addons/onlyoffice/views.py @@ -187,7 +187,9 @@ def generate(): logger.info('ONLYOFFICE: file saved: user id = {}, fullname = {}, file_name = {}' .format(user_info['user_id'], user_info['full_name'], file_info['name'])) - wburl = file_node.generate_waterbutler_url(direct=None, _internal=True) + '?kind=file' + # Explicit opt-out: keep the existing FILE_UPDATED log for ONLYOFFICE saves + # instead of letting the URL builder auto-suppress this internal request. + wburl = file_node.generate_waterbutler_url(direct=None, _internal=True, callback_log='true', kind='file') logger.debug('onlyoffice: wburl = {}'.format(wburl)) content_length = request.headers.get('content-length') diff --git a/addons/owncloud/static/owncloudAnonymousLogActionList.json b/addons/owncloud/static/owncloudAnonymousLogActionList.json index 271d2d5d244..50927f85a6a 100644 --- a/addons/owncloud/static/owncloudAnonymousLogActionList.json +++ b/addons/owncloud/static/owncloudAnonymousLogActionList.json @@ -6,5 +6,7 @@ "owncloud_folder_selected" : "A user linked a ownCloud folder in a project", "owncloud_node_authorized" : "A user authorized the ownCloud addon for a project", "owncloud_node_deauthorized" : "A user deauthorized the ownCloud addon for a project", - "owncloud_node_deauthorized_no_user" : "ownCloud addon for a project deauthorized" + "owncloud_node_deauthorized_no_user" : "ownCloud addon for a project deauthorized", + "owncloud_file_downloaded": "A user downloaded a file from ownCloud in a project", + "owncloud_folder_downloaded_zip": "A user downloaded a folder as a zip from ownCloud in a project" } diff --git a/addons/owncloud/static/owncloudLogActionList.json b/addons/owncloud/static/owncloudLogActionList.json index ec54fcf359b..2e6a186dd10 100644 --- a/addons/owncloud/static/owncloudLogActionList.json +++ b/addons/owncloud/static/owncloudLogActionList.json @@ -6,6 +6,7 @@ "owncloud_folder_selected" : "${user} linked ownCloud folder ${folder} to ${node}", "owncloud_node_authorized" : "${user} authorized the ownCloud addon for ${node}", "owncloud_node_deauthorized" : "${user} deauthorized the ownCloud addon for ${node}", - "owncloud_node_deauthorized_no_user" : "ownCloud addon for ${node} deauthorized" - + "owncloud_node_deauthorized_no_user" : "ownCloud addon for ${node} deauthorized", + "owncloud_file_downloaded": "${user} downloaded file ${path} from ownCloud in ${node}", + "owncloud_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from ownCloud in ${node}" } diff --git a/addons/s3/static/s3AnonymousLogActionList.json b/addons/s3/static/s3AnonymousLogActionList.json index 788ddcfa5fd..236af1bcf94 100644 --- a/addons/s3/static/s3AnonymousLogActionList.json +++ b/addons/s3/static/s3AnonymousLogActionList.json @@ -7,5 +7,7 @@ "s3_folder_created" : "A user created a folder in an Amazon S3 in a project", "s3_node_authorized" : "A user authorized the Amazon S3 addon for a project", "s3_node_deauthorized" : "A user deauthorized the Amazon S3 addon for a project", - "s3_node_deauthorized_no_user" : "Amazon S3 addon for a project deauthorized" + "s3_node_deauthorized_no_user" : "Amazon S3 addon for a project deauthorized", + "s3_file_downloaded": "A user downloaded a file from an Amazon S3 bucket in a project", + "s3_folder_downloaded_zip": "A user downloaded a folder as a zip from an Amazon S3 bucket in a project" } diff --git a/addons/s3/static/s3LogActionList.json b/addons/s3/static/s3LogActionList.json index f2dc7ceef05..b10e4f101f6 100644 --- a/addons/s3/static/s3LogActionList.json +++ b/addons/s3/static/s3LogActionList.json @@ -7,5 +7,7 @@ "s3_folder_created" : "${user} created folder ${path} in Amazon S3 bucket ${bucket} in ${node}", "s3_node_authorized" : "${user} authorized the Amazon S3 addon for ${node}", "s3_node_deauthorized" : "${user} deauthorized the Amazon S3 addon for ${node}", - "s3_node_deauthorized_no_user" : "Amazon S3 addon for ${node} deauthorized" + "s3_node_deauthorized_no_user" : "Amazon S3 addon for ${node} deauthorized", + "s3_file_downloaded": "${user} downloaded file ${path} from Amazon S3 bucket ${bucket} in ${node}", + "s3_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from Amazon S3 bucket ${bucket} in ${node}" } diff --git a/addons/s3compat/static/s3compatAnonymousLogActionList.json b/addons/s3compat/static/s3compatAnonymousLogActionList.json index 1e356acd6be..b9cd482999a 100644 --- a/addons/s3compat/static/s3compatAnonymousLogActionList.json +++ b/addons/s3compat/static/s3compatAnonymousLogActionList.json @@ -7,5 +7,7 @@ "s3compat_folder_created" : "A user created a folder in an S3 Compatible Storage in a project", "s3compat_node_authorized" : "A user authorized the S3 Compatible Storage addon for a project", "s3compat_node_deauthorized" : "A user deauthorized the S3 Compatible Storage addon for a project", - "s3compat_node_deauthorized_no_user" : "S3 Compatible Storage addon for a project deauthorized" + "s3compat_node_deauthorized_no_user" : "S3 Compatible Storage addon for a project deauthorized", + "s3compat_file_downloaded": "A user downloaded a file from an S3 Compatible Storage bucket in a project", + "s3compat_folder_downloaded_zip": "A user downloaded a folder as a zip from an S3 Compatible Storage bucket in a project" } diff --git a/addons/s3compat/static/s3compatLogActionList.json b/addons/s3compat/static/s3compatLogActionList.json index 4f2b2273278..b140cd174c9 100644 --- a/addons/s3compat/static/s3compatLogActionList.json +++ b/addons/s3compat/static/s3compatLogActionList.json @@ -7,5 +7,7 @@ "s3compat_folder_created" : "${user} created folder ${path} in S3 Compatible Storage bucket ${bucket} in ${node}", "s3compat_node_authorized" : "${user} authorized the S3 Compatible Storage addon for ${node}", "s3compat_node_deauthorized" : "${user} deauthorized the S3 Compatible Storage addon for ${node}", - "s3compat_node_deauthorized_no_user" : "S3 Compatible Storage addon for ${node} deauthorized" + "s3compat_node_deauthorized_no_user" : "S3 Compatible Storage addon for ${node} deauthorized", + "s3compat_file_downloaded": "${user} downloaded file ${path} from S3 Compatible Storage bucket ${bucket} in ${node}", + "s3compat_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from S3 Compatible Storage bucket ${bucket} in ${node}" } diff --git a/addons/s3compatb3/static/s3compatb3AnonymousLogActionList.json b/addons/s3compatb3/static/s3compatb3AnonymousLogActionList.json index 78d2be3ffde..ad7daff9b66 100644 --- a/addons/s3compatb3/static/s3compatb3AnonymousLogActionList.json +++ b/addons/s3compatb3/static/s3compatb3AnonymousLogActionList.json @@ -7,5 +7,7 @@ "s3compatb3_folder_created" : "A user created a folder in an Oracle Cloud Infrastructure Object Storage in a project", "s3compatb3_node_authorized" : "A user authorized the Oracle Cloud Infrastructure Object Storage addon for a project", "s3compatb3_node_deauthorized" : "A user deauthorized the Oracle Cloud Infrastructure Object Storage addon for a project", - "s3compatb3_node_deauthorized_no_user" : "Oracle Cloud Infrastructure Object Storage addon for a project deauthorized" + "s3compatb3_node_deauthorized_no_user" : "Oracle Cloud Infrastructure Object Storage addon for a project deauthorized", + "s3compatb3_file_downloaded": "A user downloaded a file from an Oracle Cloud Infrastructure Object Storage bucket in a project", + "s3compatb3_folder_downloaded_zip": "A user downloaded a folder as a zip from an Oracle Cloud Infrastructure Object Storage bucket in a project" } diff --git a/addons/s3compatb3/static/s3compatb3LogActionList.json b/addons/s3compatb3/static/s3compatb3LogActionList.json index d41b52cef1e..9e4fbae3c1a 100644 --- a/addons/s3compatb3/static/s3compatb3LogActionList.json +++ b/addons/s3compatb3/static/s3compatb3LogActionList.json @@ -7,5 +7,7 @@ "s3compatb3_folder_created" : "${user} created folder ${path} in Oracle Cloud Infrastructure Object Storage bucket ${bucket} in ${node}", "s3compatb3_node_authorized" : "${user} authorized the Oracle Cloud Infrastructure Object Storage addon for ${node}", "s3compatb3_node_deauthorized" : "${user} deauthorized the Oracle Cloud Infrastructure Object Storage addon for ${node}", - "s3compatb3_node_deauthorized_no_user" : "Oracle Cloud Infrastructure Object Storage addon for ${node} deauthorized" + "s3compatb3_node_deauthorized_no_user" : "Oracle Cloud Infrastructure Object Storage addon for ${node} deauthorized", + "s3compatb3_file_downloaded": "${user} downloaded file ${path} from Oracle Cloud Infrastructure Object Storage bucket ${bucket} in ${node}", + "s3compatb3_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from Oracle Cloud Infrastructure Object Storage bucket ${bucket} in ${node}" } diff --git a/addons/s3compatinstitutions/static/s3compatinstitutionsAnonymousLogActionList.json b/addons/s3compatinstitutions/static/s3compatinstitutionsAnonymousLogActionList.json index b9cc2cd184e..be35e5e8547 100644 --- a/addons/s3compatinstitutions/static/s3compatinstitutionsAnonymousLogActionList.json +++ b/addons/s3compatinstitutions/static/s3compatinstitutionsAnonymousLogActionList.json @@ -6,5 +6,7 @@ "s3compatinstitutions_folder_selected" : "A user linked a S3compat (for Institutions) folder in a project", "s3compatinstitutions_node_authorized" : "A user authorized the S3compat (for Institutions) addon for a project", "s3compatinstitutions_node_deauthorized" : "A user deauthorized the S3compat (for Institutions) addon for a project", - "s3compatinstitutions_node_deauthorized_no_user" : "S3compat (for Institutions) addon for a project deauthorized" + "s3compatinstitutions_node_deauthorized_no_user" : "S3compat (for Institutions) addon for a project deauthorized", + "s3compatinstitutions_file_downloaded": "A user downloaded a file from S3 Compatible Storage for Institutions in a project", + "s3compatinstitutions_folder_downloaded_zip": "A user downloaded a folder as a zip from S3 Compatible Storage for Institutions in a project" } diff --git a/addons/s3compatinstitutions/static/s3compatinstitutionsLogActionList.json b/addons/s3compatinstitutions/static/s3compatinstitutionsLogActionList.json index 77715dfb0e1..6944775b573 100644 --- a/addons/s3compatinstitutions/static/s3compatinstitutionsLogActionList.json +++ b/addons/s3compatinstitutions/static/s3compatinstitutionsLogActionList.json @@ -6,5 +6,7 @@ "s3compatinstitutions_folder_selected" : "${user} linked S3 Compatible Storage for Institutions folder ${folder} to ${node}", "s3compatinstitutions_node_authorized" : "${user} authorized the S3 Compatible Storage for Institutions addon for ${node}", "s3compatinstitutions_node_deauthorized" : "${user} deauthorized the S3 Compatible Storage for Institutions addon for ${node}", - "s3compatinstitutions_node_deauthorized_no_user" : "S3 Compatible Storage for Institutions addon for ${node} deauthorized" + "s3compatinstitutions_node_deauthorized_no_user" : "S3 Compatible Storage for Institutions addon for ${node} deauthorized", + "s3compatinstitutions_file_downloaded": "${user} downloaded file ${path} from S3 Compatible Storage for Institutions in ${node}", + "s3compatinstitutions_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from S3 Compatible Storage for Institutions in ${node}" } diff --git a/addons/s3compatsigv4/static/s3compatsigv4AnonymousLogActionList.json b/addons/s3compatsigv4/static/s3compatsigv4AnonymousLogActionList.json index d510c19eb0b..a487a225c09 100644 --- a/addons/s3compatsigv4/static/s3compatsigv4AnonymousLogActionList.json +++ b/addons/s3compatsigv4/static/s3compatsigv4AnonymousLogActionList.json @@ -7,5 +7,7 @@ "s3compatsigv4_folder_created" : "A user created a folder in an S3 Compatible Storage (SigV4) in a project", "s3compatsigv4_node_authorized" : "A user authorized the S3 Compatible Storage (SigV4) addon for a project", "s3compatsigv4_node_deauthorized" : "A user deauthorized the S3 Compatible Storage (SigV4) addon for a project", - "s3compatsigv4_node_deauthorized_no_user" : "S3 Compatible Storage (SigV4) addon for a project deauthorized" + "s3compatsigv4_node_deauthorized_no_user" : "S3 Compatible Storage (SigV4) addon for a project deauthorized", + "s3compatsigv4_file_downloaded": "A user downloaded a file from an S3 Compatible Storage (SigV4) bucket in a project", + "s3compatsigv4_folder_downloaded_zip": "A user downloaded a folder as a zip from an S3 Compatible Storage (SigV4) bucket in a project" } diff --git a/addons/s3compatsigv4/static/s3compatsigv4LogActionList.json b/addons/s3compatsigv4/static/s3compatsigv4LogActionList.json index ac955705ea2..bee41c1f03d 100644 --- a/addons/s3compatsigv4/static/s3compatsigv4LogActionList.json +++ b/addons/s3compatsigv4/static/s3compatsigv4LogActionList.json @@ -7,5 +7,7 @@ "s3compatsigv4_folder_created" : "${user} created folder ${path} in S3 Compatible Storage (SigV4) bucket ${bucket} in ${node}", "s3compatsigv4_node_authorized" : "${user} authorized the S3 Compatible Storage (SigV4) addon for ${node}", "s3compatsigv4_node_deauthorized" : "${user} deauthorized the S3 Compatible Storage (SigV4) addon for ${node}", - "s3compatsigv4_node_deauthorized_no_user" : "S3 Compatible Storage (SigV4) addon for ${node} deauthorized" + "s3compatsigv4_node_deauthorized_no_user" : "S3 Compatible Storage (SigV4) addon for ${node} deauthorized", + "s3compatsigv4_file_downloaded": "${user} downloaded file ${path} from S3 Compatible Storage (SigV4) bucket ${bucket} in ${node}", + "s3compatsigv4_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from S3 Compatible Storage (SigV4) bucket ${bucket} in ${node}" } diff --git a/addons/swift/static/swiftAnonymousLogActionList.json b/addons/swift/static/swiftAnonymousLogActionList.json index 3239bc3fa29..74dc6553616 100644 --- a/addons/swift/static/swiftAnonymousLogActionList.json +++ b/addons/swift/static/swiftAnonymousLogActionList.json @@ -7,5 +7,7 @@ "swift_folder_created" : "A user created a folder in an Swift in a project", "swift_node_authorized" : "A user authorized the Swift addon for a project", "swift_node_deauthorized" : "A user deauthorized the Swift addon for a project", - "swift_node_deauthorized_no_user" : "Swift addon for a project deauthorized" + "swift_node_deauthorized_no_user" : "Swift addon for a project deauthorized", + "swift_file_downloaded": "A user downloaded a file from a Swift container in a project", + "swift_folder_downloaded_zip": "A user downloaded a folder as a zip from a Swift container in a project" } diff --git a/addons/swift/static/swiftLogActionList.json b/addons/swift/static/swiftLogActionList.json index 05017ab63d9..81d4cb2062f 100644 --- a/addons/swift/static/swiftLogActionList.json +++ b/addons/swift/static/swiftLogActionList.json @@ -7,5 +7,7 @@ "swift_folder_created" : "${user} created folder ${path} in Swift container ${bucket} in ${node}", "swift_node_authorized" : "${user} authorized the Swift addon for ${node}", "swift_node_deauthorized" : "${user} deauthorized the Swift addon for ${node}", - "swift_node_deauthorized_no_user" : "Swift addon for ${node} deauthorized" + "swift_node_deauthorized_no_user" : "Swift addon for ${node} deauthorized", + "swift_file_downloaded": "${user} downloaded file ${path} from Swift container ${bucket} in ${node}", + "swift_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from Swift container ${bucket} in ${node}" } diff --git a/addons/weko/static/wekoAnonymousLogActionList.json b/addons/weko/static/wekoAnonymousLogActionList.json index 3c6c684cef1..6bd39f27582 100644 --- a/addons/weko/static/wekoAnonymousLogActionList.json +++ b/addons/weko/static/wekoAnonymousLogActionList.json @@ -6,5 +6,7 @@ "weko_item_deposited" : "A user deposit item in a JAIRO Cloud index in a project", "weko_node_authorized" : "A user authorized the JAIRO Cloud addon for a project", "weko_node_deauthorized" : "A user deauthorized the JAIRO Cloud addon for a project", - "weko_node_deauthorized_no_user" : "JAIRO Cloud addon for a project deauthorized" + "weko_node_deauthorized_no_user" : "JAIRO Cloud addon for a project deauthorized", + "weko_file_downloaded": "A user downloaded a draft file from a JAIRO Cloud index in a project", + "weko_folder_downloaded_zip": "A user downloaded a folder as a zip from a JAIRO Cloud index in a project" } diff --git a/addons/weko/static/wekoLogActionList.json b/addons/weko/static/wekoLogActionList.json index b8123d06e01..bdea2104f40 100644 --- a/addons/weko/static/wekoLogActionList.json +++ b/addons/weko/static/wekoLogActionList.json @@ -6,5 +6,7 @@ "weko_item_deposited" : "${user} deposit item ${path} in JAIRO Cloud index ${dataset} in ${node}", "weko_node_authorized" : "${user} authorized the JAIRO Cloud addon for ${node}", "weko_node_deauthorized" : "${user} deauthorized the JAIRO Cloud addon for ${node}", - "weko_node_deauthorized_no_user" : "JAIRO Cloud addon for ${node} deauthorized" + "weko_node_deauthorized_no_user" : "JAIRO Cloud addon for ${node} deauthorized", + "weko_file_downloaded": "${user} downloaded draft file ${filename} from JAIRO Cloud index ${dataset} in ${node}", + "weko_folder_downloaded_zip": "${user} downloaded folder ${filename} as a zip from JAIRO Cloud index ${dataset} in ${node}" } diff --git a/api/base/utils.py b/api/base/utils.py index 7950f4dbfdd..742d6a879c4 100644 --- a/api/base/utils.py +++ b/api/base/utils.py @@ -213,6 +213,13 @@ def waterbutler_api_url_for(node_id, provider, path='/', _internal=False, base_u # furl auto encoding, so urlquote is not necessary as `[urlquote(x) for x in segments]` # try to convert segment items to string for furl check url.path.segments.extend([str(x) for x in segments]) + + # For internal requests, automatically suppress callback logging unless explicitly set. + # This prevents internal system operations (metadata sync, exports, etc.) from being + # recorded as user downloads in the Recent Activity log. + if _internal and 'meta' not in kwargs and 'callback_log' not in kwargs: + kwargs['callback_log'] = 'false' + url.args.update(kwargs) return url.url diff --git a/api/logs/serializers.py b/api/logs/serializers.py index 3b7a77bcfab..3662f020ebf 100644 --- a/api/logs/serializers.py +++ b/api/logs/serializers.py @@ -338,16 +338,12 @@ def get_absolute_url(self, obj): def to_representation(self, obj, envelope='data'): """ Overriding to_representation to return different representation for download node logs function. """ - # Get params and embed's user based on JSONAPISerializer's to_representation + # Get params based on NodeLogDownloadParamsSerializer params = NodeLogDownloadParamsSerializer(obj.params, context=self.context, read_only=True).data - embeds = self.context.get('embed', {}) - embed_user = {} - if embeds and 'user' in embeds: - embed_user = embeds.get('user')(obj) params_node = params.get('params_node', {}) rep = { 'date': obj.date, - 'user': embed_user.get('data', {}).get('attributes', {}).get('full_name'), + 'user': obj.user.fullname if obj.user else None, 'project_id': params_node.get('id'), 'project_title': params_node.get('title'), 'action': obj.action, diff --git a/api_tests/base/test_utils.py b/api_tests/base/test_utils.py index 6b52f858a75..a155fd95b95 100644 --- a/api_tests/base/test_utils.py +++ b/api_tests/base/test_utils.py @@ -230,3 +230,91 @@ def test_user_no_matching_setting(self, user, institution): # can create project mock_filter.return_value.count.return_value = 4 assert api_utils.check_user_can_create_project(user) + + +class TestWaterbutlerApiUrlForCallbackLog: + """waterbutler_api_url_for() auto-suppresses callback_log for internal requests + (download history feature) unless the caller already set meta/callback_log.""" + + def test_internal_request_adds_callback_log_false(self): + url = api_utils.waterbutler_api_url_for( + 'abc12', 'osfstorage', path='/', _internal=True + ) + assert 'callback_log=false' in url + + def test_internal_request_with_meta_skips_callback_log(self): + url = api_utils.waterbutler_api_url_for( + 'abc12', 'osfstorage', path='/', _internal=True, meta='' + ) + assert 'callback_log=false' not in url + + def test_internal_request_respects_explicit_callback_log(self): + url = api_utils.waterbutler_api_url_for( + 'abc12', 'osfstorage', path='/', _internal=True, callback_log='true' + ) + assert 'callback_log=true' in url + + def test_external_request_does_not_add_callback_log(self): + url = api_utils.waterbutler_api_url_for( + 'abc12', 'osfstorage', path='/', _internal=False + ) + assert 'callback_log=false' not in url + + def test_url_path_construction_with_callback_log(self): + url = api_utils.waterbutler_api_url_for( + 'abc12', 'osfstorage', path='/folder/file.txt', _internal=True + ) + assert 'v1/resources/abc12/providers/osfstorage/folder/file.txt' in url + assert 'callback_log=false' in url + + def test_callback_log_query_param_format(self): + url = api_utils.waterbutler_api_url_for( + 'test_node', 'osfstorage', path='/', _internal=True + ) + assert '?' in url + assert 'callback_log=' in url + + def test_multiple_query_params_with_callback_log(self): + url = api_utils.waterbutler_api_url_for( + 'test_node', 'osfstorage', path='/', _internal=True, + version='2', revision='abc' + ) + assert 'callback_log=false' in url + assert 'version=2' in url + assert 'revision=abc' in url + + def test_callback_log_with_special_characters_in_path(self): + url = api_utils.waterbutler_api_url_for( + 'test_node', 'osfstorage', + path='/folder%20with%20spaces/file.txt', + _internal=True + ) + assert 'callback_log=false' in url + assert 'folder%20with%20spaces' in url + + def test_callback_log_with_empty_meta(self): + # When meta is present (even as ''), callback_log should not be auto-added + url = api_utils.waterbutler_api_url_for( + 'test_node', 'osfstorage', _internal=True, meta='' + ) + assert 'callback_log=false' not in url + + def test_callback_log_with_multiple_providers(self): + for provider in ['osfstorage', 'dropbox', 'box', 'googledrive']: + url = api_utils.waterbutler_api_url_for( + 'test_node', provider, _internal=True + ) + assert 'callback_log=false' in url + + def test_callback_log_with_kind_kwarg_produces_single_query_string(self): + # Regression test: addons/onlyoffice/views.py used to build this URL as + # generate_waterbutler_url(..., callback_log='true') + '?kind=file', which + # produced a double '?' once furl already appended '?direct&callback_log=true'. + # kind must now be passed as a kwarg alongside callback_log. + url = api_utils.waterbutler_api_url_for( + 'test_node', 'osfstorage', _internal=True, + callback_log='true', kind='file' + ) + assert url.count('?') == 1 + assert 'callback_log=true' in url + assert 'kind=file' in url diff --git a/api_tests/logs/serializers/test_serializers.py b/api_tests/logs/serializers/test_serializers.py index 1d8d5de2934..0147ce3bd00 100644 --- a/api_tests/logs/serializers/test_serializers.py +++ b/api_tests/logs/serializers/test_serializers.py @@ -87,17 +87,36 @@ def test_to_representation(self): serialized = NodeLogDownloadSerializer( log, - context={'request': request, 'embed': {'user': self.get_test_context_embed_user}} + context={'request': request} ).data assert_datetime_equal( serialized['date'], project.logs.first().date ) - assert serialized['user'] == 'test_user' + assert serialized['user'] == project.creator.fullname assert serialized['project_id'] == project._id assert serialized['project_title'] == project.title assert serialized['action'] == project.logs.first().action + def test_to_representation__user_is_none(self): + """api/logs/serializers.py: to_representation() reads `user` directly from + obj.user.fullname (no more embed-based lookup). NodeLog.user is nullable + (system-generated logs), so this branch's `else None` must be exercised - + previously only the "user exists" branch had coverage.""" + project = ProjectFactory() + request = make_drf_request_with_version() + log = project.add_log( + action='osf_storage_file_downloaded', + auth=None, + params={ + 'node': project._id, + 'path': 'test_file', + }, + ) + assert log.user is None + serialized = NodeLogDownloadSerializer(log, context={'request': request}).data + assert serialized['user'] is None + def test_to_representation__params_include_contributors(self): project = ProjectFactory() request = make_drf_request_with_version() diff --git a/api_tests/nodes/views/test_node_logs.py b/api_tests/nodes/views/test_node_logs.py index 5ef133d9deb..92dd238262a 100644 --- a/api_tests/nodes/views/test_node_logs.py +++ b/api_tests/nodes/views/test_node_logs.py @@ -509,6 +509,17 @@ def test_download__include_embed_user_query_param(self, app, user, public_projec assert res.json['data'][API_LATEST]['project_title'] == public_project.title assert res.json['data'][API_LATEST]['action'] == public_project.logs.first().action + def test_download__user_field_populated_without_embed_param(self, app, user, public_project, download_url): + """api/logs/serializers.py: NodeLogDownloadSerializer.to_representation() + now reads `user` directly from obj.user.fullname, not from + context['embed']['user'] like before - the old code required ?embed=user + to populate this field at all. Prove the field still resolves when embed + is omitted entirely, since nothing previously called this endpoint without + embed=user to confirm removing the embed-based lookup didn't regress it.""" + res = app.get(download_url, auth=user.auth) + assert res.status_code == 200 + assert res.json['data'][API_LATEST]['user'] == user.fullname + def test_download__limited_by_page_size(self, app, user, user_auth, private_project): private_project.add_tag('tag', auth=user_auth) download_url = '/{}nodes/{}/logs/?page[size]=1&action=download'.format( diff --git a/osf/management/commands/import_EGAP.py b/osf/management/commands/import_EGAP.py index 62c5dd62ea4..3e0458bb497 100644 --- a/osf/management/commands/import_EGAP.py +++ b/osf/management/commands/import_EGAP.py @@ -140,7 +140,8 @@ def get_egap_assets(guid, creator_auth): zip_file = node.files.first() temp_path = tempfile.mkdtemp() - url = '{}/v1/resources/{}/providers/osfstorage/{}'.format(WATERBUTLER_INTERNAL_URL, guid, zip_file._id) + # Suppress logging for EGAP import downloads (internal system operation) + url = '{}/v1/resources/{}/providers/osfstorage/{}?callback_log=false'.format(WATERBUTLER_INTERNAL_URL, guid, zip_file._id) zip_file = requests.get(url, headers=creator_auth).content egap_assets_path = os.path.join(temp_path, 'egap_assets.zip') diff --git a/osf/models/nodelog.py b/osf/models/nodelog.py index a3e4781fd50..750845f9a85 100644 --- a/osf/models/nodelog.py +++ b/osf/models/nodelog.py @@ -95,6 +95,8 @@ class NodeLog(ObjectIDMixin, BaseModel): FILE_UPDATED = 'file_updated' FILE_REMOVED = 'file_removed' FILE_RESTORED = 'file_restored' + FILE_DOWNLOADED = 'file_downloaded' + FOLDER_DOWNLOADED_ZIP = 'folder_downloaded_zip' CATEGORY_UPDATED = 'category_updated' ARTICLE_DOI_UPDATED = 'article_doi_updated' @@ -172,6 +174,7 @@ class NodeLog(ObjectIDMixin, BaseModel): PERMISSIONS_UPDATED, MADE_PRIVATE, MADE_PUBLIC, TAG_ADDED, TAG_REMOVED, EDITED_TITLE, EDITED_DESCRIPTION, UPDATED_FIELDS, FILE_MOVED, FILE_COPIED, FILE_METADATA_UPDATED, FOLDER_CREATED, FILE_ADDED, FILE_UPDATED, FILE_REMOVED, FILE_RESTORED, ADDON_ADDED, + FILE_DOWNLOADED, FOLDER_DOWNLOADED_ZIP, ADDON_REMOVED, COMMENT_ADDED, COMMENT_REMOVED, COMMENT_UPDATED, COMMENT_RESTORED, MADE_CONTRIBUTOR_VISIBLE, CONFIRM_HAM, FLAG_SPAM, CONFIRM_SPAM, MADE_CONTRIBUTOR_INVISIBLE, EXTERNAL_IDS_ADDED, EMBARGO_APPROVED, EMBARGO_TERMINATED, diff --git a/osf_tests/management_commands/test_EGAP_import.py b/osf_tests/management_commands/test_EGAP_import.py index f5f32ea97d6..2735c3878ec 100644 --- a/osf_tests/management_commands/test_EGAP_import.py +++ b/osf_tests/management_commands/test_EGAP_import.py @@ -214,4 +214,12 @@ def test_get_egap_assets(self, node_with_file, zip_data): # __MACOSX is a hidden file created by the os when zipping assert set(directory_list) == set(['20110307AA', '__MACOSX', '20110302AA', 'egap_assets.zip', '20120117AA']) + # Download history feature: EGAP import is an internal system operation and + # must suppress Recent Activity logging. `responses` matches URLs ignoring + # the query string by default, so this previously passed even without + # callback_log=false actually being on the requested URL - assert on the + # real request instead of relying on the mock match succeeding. + assert len(responses.calls) == 1 + assert 'callback_log=false' in responses.calls[0].request.url + shutil.rmtree(asset_path) diff --git a/osf_tests/test_node.py b/osf_tests/test_node.py index b28e36240ee..2f42a999268 100644 --- a/osf_tests/test_node.py +++ b/osf_tests/test_node.py @@ -4799,3 +4799,37 @@ def test_permissions_collection_project_views( assert len(collection_summary) == 3 urls_actual = {summary['url'] for summary in collection_summary} assert self._collection_url(bookmark_collection_public) not in urls_actual + + +@pytest.mark.django_db +class TestFileDownloadedNodeLog: + """osf/models/nodelog.py: FILE_DOWNLOADED/FOLDER_DOWNLOADED_ZIP constants, + their inclusion in the actions tuple, and that Node.add_log() accepts them.""" + + def test_constants_defined(self): + assert NodeLog.FILE_DOWNLOADED == 'file_downloaded' + assert NodeLog.FOLDER_DOWNLOADED_ZIP == 'folder_downloaded_zip' + + def test_constants_included_in_actions_tuple(self): + assert NodeLog.FILE_DOWNLOADED in NodeLog.actions + assert NodeLog.FOLDER_DOWNLOADED_ZIP in NodeLog.actions + + def test_add_log_persists_file_downloaded(self): + user = AuthUserFactory() + node = ProjectFactory(creator=user) + auth = Auth(user) + nlogs = node.logs.count() + node.add_log(NodeLog.FILE_DOWNLOADED, auth=auth, params={'node': node._id, 'path': '/testfile'}) + node.reload() + assert node.logs.count() == nlogs + 1 + assert node.logs.latest().action == NodeLog.FILE_DOWNLOADED + + def test_add_log_persists_folder_downloaded_zip(self): + user = AuthUserFactory() + node = ProjectFactory(creator=user) + auth = Auth(user) + nlogs = node.logs.count() + node.add_log(NodeLog.FOLDER_DOWNLOADED_ZIP, auth=auth, params={'node': node._id, 'path': '/testfolder/'}) + node.reload() + assert node.logs.count() == nlogs + 1 + assert node.logs.latest().action == NodeLog.FOLDER_DOWNLOADED_ZIP diff --git a/tests/test_addons.py b/tests/test_addons.py index e6f18031b56..c221a7213ba 100644 --- a/tests/test_addons.py +++ b/tests/test_addons.py @@ -306,6 +306,67 @@ def test_auth__user_is_None(self): assert_equal(res.status_code, 401) +class TestDownloadMfrCallbackLog(TestAddonAuth): + """Tests for the download-history MFR suppression: the download_is_from_mfr() + helper itself (pre-existing since 2018, never tested before this feature), and + its new integration point inside get_auth() (addons/base/views.py).""" + + def test_auth_download_from_mfr_suppresses_callback_url(self): + """A download action whose metrics.uri carries mode=render (i.e. MFR + fetching content for its own renderer) should get an empty callback_url, + so waterbutler never calls back to create_waterbutler_log.""" + url = self.build_url(metrics={'uri': settings.MFR_SERVER_URL + '?mode=render'}) + res = self.app.get(url, auth=self.user.auth) + assert_equal(res.status_code, 200) + data = jwt.decode( + jwe.decrypt(res.json['payload'].encode('utf-8'), self.JWE_KEY), + settings.WATERBUTLER_JWT_SECRET, + algorithm=settings.WATERBUTLER_JWT_ALGORITHM + )['data'] + assert_equal(data['callback_url'], '') + + def test_auth_download_not_from_mfr_keeps_callback_url(self): + """A regular (non-MFR) download keeps its real callback_url, same as + test_auth_download - this is the control case for the test above.""" + url = self.build_url() + res = self.app.get(url, auth=self.user.auth) + assert_equal(res.status_code, 200) + data = jwt.decode( + jwe.decrypt(res.json['payload'].encode('utf-8'), self.JWE_KEY), + settings.WATERBUTLER_JWT_SECRET, + algorithm=settings.WATERBUTLER_JWT_ALGORITHM + )['data'] + assert_not_equal(data['callback_url'], '') + + def test_download_is_from_mfr_with_render_header(self): + """MFR downloads should be detected by the X-Cos-Mfr-Render-Request header.""" + request = mock.MagicMock() + request.headers = {'X-Cos-Mfr-Render-Request': 'true'} + payload = {'metrics': {'uri': ''}} + assert views.download_is_from_mfr(request, payload) + + def test_download_is_from_mfr_with_render_uri(self): + """MFR downloads should be detected by mode=render in the request URI.""" + request = mock.MagicMock() + request.headers = {} + payload = {'metrics': {'uri': 'http://example.com/file?mode=render'}} + assert views.download_is_from_mfr(request, payload) is True + + def test_download_is_not_from_mfr_regular_download(self): + """Regular browser downloads should not be detected as MFR.""" + request = mock.MagicMock() + request.headers = {} + payload = {'metrics': {'uri': 'http://example.com/file'}} + assert views.download_is_from_mfr(request, payload) is False + + def test_download_is_not_from_mfr_no_uri(self): + """An empty uri should not be mistaken for an MFR render request.""" + request = mock.MagicMock() + request.headers = {} + payload = {'metrics': {'uri': ''}} + assert views.download_is_from_mfr(request, payload) is False + + class TestAddonLogs(OsfTestCase): def setUp(self): @@ -411,12 +472,14 @@ def test_add_log_timestamptoken(self, mock_perform, mock_downloadfile, mock_node ## tearDown rdmuserkey_pvt_key = RdmUserKey.objects.get(guid=self.user.id, key_kind=api_settings.PRIVATE_KEY_VALUE) pvt_key_path = os.path.join(api_settings.KEY_SAVE_PATH, rdmuserkey_pvt_key.key_name) - os.remove(pvt_key_path) + if os.path.exists(pvt_key_path): + os.remove(pvt_key_path) rdmuserkey_pvt_key.delete() rdmuserkey_pub_key = RdmUserKey.objects.get(guid=self.user.id, key_kind=api_settings.PUBLIC_KEY_VALUE) pub_key_path = os.path.join(api_settings.KEY_SAVE_PATH, rdmuserkey_pub_key.key_name) - os.remove(pub_key_path) + if os.path.exists(pub_key_path): + os.remove(pub_key_path) rdmuserkey_pub_key.delete() @mock.patch('addons.base.views.timestamp') @@ -960,11 +1023,16 @@ def test_disconnect_provider_timestamp(self, mock_perform, mock_downloadfile, mo assert_equal(api_settings.TIME_STAMP_STORAGE_DISCONNECTED, removed_file.inspection_result_status) def test_action_downloads_contrib(self): + """User-initiated downloads should create a Recent Activity log, prefixed + with the addon's short_name like every other file action (github_*).""" url = self.node.api_url_for('create_waterbutler_log') - download_actions=('download_file', 'download_zip') + download_action_to_expected_log = { + 'download_file': 'github_file_downloaded', + 'download_zip': 'github_folder_downloaded_zip', + } base_url = self.node.osfstorage_region.waterbutler_url wb_url = base_url + '?version=1' - for action in download_actions: + for action, expected_log_action in download_action_to_expected_log.items(): payload = self.build_payload(metadata={'path': '/testfile', 'nid': self.node._id}, action_meta={'is_mfr_render': False}, @@ -979,8 +1047,63 @@ def test_action_downloads_contrib(self): ) assert_equal(res.status_code, 200) + self.node.reload() + assert_equal(self.node.logs.count(), nlogs + 1) + assert_equal(self.node.logs.latest().action, expected_log_action) + + def test_log_action_map_download_mappings(self): + """download_file/download_zip map to two distinct NodeLog actions so + Recent Activity text can differ for file vs folder downloads.""" + assert_equal(views.LOG_ACTION_MAP.get('download_file'), NodeLog.FILE_DOWNLOADED) + assert_equal(views.LOG_ACTION_MAP.get('download_zip'), NodeLog.FOLDER_DOWNLOADED_ZIP) + + def test_action_download_file_osfstorage_log_params(self): + """Single-file downloads go through the same per-addon log path as + file_added/updated, producing a view/download url in params.""" + self.configure_osf_addon() + url = self.node.api_url_for('create_waterbutler_log') + payload = self.build_payload( + metadata={ + 'provider': 'osfstorage', + 'materialized': '/testfile', + 'path': '/testfile', + 'kind': 'file', + }, + action='download_file', + ) + nlogs = self.node.logs.count() + res = self.app.put_json(url, payload, headers={'Content-Type': 'application/json'}) + assert_equal(res.status_code, 200) self.node.reload() - assert_equal(self.node.logs.count(), nlogs) + assert_equal(self.node.logs.count(), nlogs + 1) + log = self.node.logs.latest() + assert_equal(log.action, 'osf_storage_file_downloaded') + assert_equal(log.params['path'], '/testfile') + assert('urls' in log.params) + + def test_action_download_zip_osfstorage_log_params(self): + """Folder zip downloads create a distinct folder_downloaded_zip log, + with no view/download url (folders never get urls, same as folder_created).""" + self.configure_osf_addon() + url = self.node.api_url_for('create_waterbutler_log') + payload = self.build_payload( + metadata={ + 'provider': 'osfstorage', + 'materialized': '/testfolder/', + 'path': '/testfolder/', + 'kind': 'folder', + }, + action='download_zip', + ) + nlogs = self.node.logs.count() + res = self.app.put_json(url, payload, headers={'Content-Type': 'application/json'}) + assert_equal(res.status_code, 200) + self.node.reload() + assert_equal(self.node.logs.count(), nlogs + 1) + log = self.node.logs.latest() + assert_equal(log.action, 'osf_storage_folder_downloaded_zip') + assert_equal(log.params['path'], '/testfolder/') + assert('urls' not in log.params) @mock.patch('addons.base.views.BaseFileNode') @mock.patch('addons.base.views.timestamp') diff --git a/tests/test_preprints.py b/tests/test_preprints.py index 45a8a64ff68..995490b0765 100644 --- a/tests/test_preprints.py +++ b/tests/test_preprints.py @@ -2302,7 +2302,12 @@ def build_url(self, **kwargs): options = {'payload': jwe.encrypt(jwt.encode({'data': dict(dict( action='download', nid=self.preprint._id, - provider='osfstorage'), **kwargs), + provider='osfstorage', + # Real waterbutler requests always include metrics (see + # RDM-waterbutler's OsfAuthHandler.get()) - download_is_from_mfr() + # (called unconditionally by get_auth() for any download action) + # reads metrics['uri'], so it must be present here too. + metrics={'uri': ''}), **kwargs), 'exp': timezone.now() + datetime.timedelta(seconds=500), }, settings.WATERBUTLER_JWT_SECRET, algorithm=settings.WATERBUTLER_JWT_ALGORITHM), self.JWE_KEY)} return self.preprint.api_url_for('get_auth', **options) @@ -2509,15 +2514,30 @@ def test_action_file_rename(self): ) def test_action_downloads_contrib(self): + """Real downloads now create a Recent Activity log via Preprint.create_waterbutler_log, + the same as any other file action - previously download actions were skipped entirely, + so this test's payload/assertions predate that change. A real waterbutler download + callback always includes materialized/kind in metadata (see RDM-waterbutler's + LogPayload.serialize(), which documents these as always-present keys).""" url = self.preprint.api_url_for('create_waterbutler_log') - download_actions=('download_file', 'download_zip') wb_url = settings.WATERBUTLER_URL + '?version=1' - for action in download_actions: - payload = self.build_payload(metadata={'path': '/testfile', - 'nid': self.preprint._id}, - action_meta={'is_mfr_render': False}, - request_meta={'url': wb_url}, - action=action) + download_action_to_expected_log = { + 'download_file': ( + 'osf_storage_file_downloaded', + {'path': '/testfile', 'materialized': '/testfile', 'kind': 'file'}, + ), + 'download_zip': ( + 'osf_storage_folder_downloaded_zip', + {'path': '/testfolder/', 'materialized': '/testfolder/', 'kind': 'folder'}, + ), + } + for action, (expected_log_action, metadata) in download_action_to_expected_log.items(): + payload = self.build_payload( + metadata=dict(metadata, nid=self.preprint._id), + action_meta={'is_mfr_render': False}, + request_meta={'url': wb_url}, + action=action, + ) nlogs = self.preprint.logs.count() res = self.app.put_json( url, @@ -2527,8 +2547,9 @@ def test_action_downloads_contrib(self): ) assert_equal(res.status_code, 200) - self.preprint.reload() - assert_equal(self.preprint.logs.count(), nlogs) + self.preprint.reload() + assert_equal(self.preprint.logs.count(), nlogs + 1) + assert_equal(self.preprint.logs.latest().action, expected_log_action) def test_add_file_osfstorage_log(self): path = 'pizza' diff --git a/website/static/js/anonymousLogActionsList.json b/website/static/js/anonymousLogActionsList.json index 0010b552a3f..5ec77a4c015 100644 --- a/website/static/js/anonymousLogActionsList.json +++ b/website/static/js/anonymousLogActionsList.json @@ -54,6 +54,8 @@ "file_updated" : "A user updated a file in a project", "file_removed" : "A user removed a file from a project", "file_restored" : "A user restored a file from a project", + "file_downloaded": "A user downloaded a file from a project", + "folder_downloaded_zip": "A user downloaded a folder as a zip from a project", "file_metadata_updated": "A user updated the file metadata for a file", "checked_out": "A user checked out a file from a project", "checked_in": "A user checked in a file to a project", @@ -93,6 +95,8 @@ "osf_storage_folder_created" : "A user created a folder in NII Storage in a project", "osf_storage_file_updated" : "A user updated a file in NII Storage in a project", "osf_storage_file_removed" : "A user removed a file from NII Storage in a project", + "osf_storage_file_downloaded": "A user downloaded a file from NII Storage in a project", + "osf_storage_folder_downloaded_zip": "A user downloaded a folder as a zip from NII Storage in a project", "affiliated_institution_added": "A user added an affiliation to a project", "affiliated_institution_removed": "A user removed an affiliation from a project", "preprint_initiated": "A user made a project a preprint", diff --git a/website/static/js/logActionsList.json b/website/static/js/logActionsList.json index 365019fff5f..ad4f525425c 100644 --- a/website/static/js/logActionsList.json +++ b/website/static/js/logActionsList.json @@ -55,6 +55,8 @@ "file_updated": "${user} updated file in ${node}", "file_removed": "${user} removed ${path_type} ${path} from ${node}", "file_restored": "${user} restored file ${path} from ${node}", + "file_downloaded": "${user} downloaded file ${path} from ${node}", + "folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from ${node}", "file_metadata_updated": "${user} updated the file metadata for ${path}", "checked_out": "${user} checked out ${kind} ${path} from ${node}", "checked_in": "${user} checked in ${kind} ${path} to ${node}", @@ -94,6 +96,8 @@ "osf_storage_folder_created" : "${user} created folder ${path} in ${storage_name} in ${node}", "osf_storage_file_updated" : "${user} updated file ${path} in ${storage_name} in ${node}", "osf_storage_file_removed" : "${user} removed ${path_type} ${path} from ${storage_name} in ${node}", + "osf_storage_file_downloaded": "${user} downloaded file ${path} from ${storage_name} in ${node}", + "osf_storage_folder_downloaded_zip": "${user} downloaded folder ${path} as a zip from ${storage_name} in ${node}", "affiliated_institution_added": "${user} added ${institution} affiliation to ${node}", "affiliated_institution_removed": "${user} removed ${institution} affiliation from ${node}", "preprint_initiated": "${user} made ${node} a ${preprint} on ${preprint_provider} Preprints", diff --git a/website/static/js/logActionsList_extract.js b/website/static/js/logActionsList_extract.js index 560cf0a6c38..bb82f199e97 100644 --- a/website/static/js/logActionsList_extract.js +++ b/website/static/js/logActionsList_extract.js @@ -55,6 +55,8 @@ var file_added = _('${user} added file ${path} to ${node}'); var file_updated = _('${user} updated file in ${node}'); var file_removed = _('${user} removed ${path_type} ${path} from ${node}'); var file_restored = _('${user} restored file ${path} from ${node}'); +var file_downloaded = _('${user} downloaded file ${path} from ${node}'); +var folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from ${node}'); var file_metadata_updated = _('${user} updated the file metadata for ${path}'); var checked_out = _('${user} checked out ${kind} ${path} from ${node}'); var checked_in = _('${user} checked in ${kind} ${path} to ${node}'); @@ -94,6 +96,8 @@ var osf_storage_file_added = _('${user} added file ${path} to ${storage_name} in var osf_storage_folder_created = _('${user} created folder ${path} in ${storage_name} in ${node}'); var osf_storage_file_updated = _('${user} updated file ${path} in ${storage_name} in ${node}'); var osf_storage_file_removed = _('${user} removed ${path_type} ${path} from ${storage_name} in ${node}'); +var osf_storage_file_downloaded = _('${user} downloaded file ${path} from ${storage_name} in ${node}'); +var osf_storage_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from ${storage_name} in ${node}'); var affiliated_institution_added = _('${user} added ${institution} affiliation to ${node}'); var affiliated_institution_removed = _('${user} removed ${institution} affiliation from ${node}'); var preprint_initiated = _('${user} made ${node} a ${preprint} on ${preprint_provider} Preprints'); @@ -127,6 +131,8 @@ var box_folder_selected = _('${user} linked Box folder ${box_folder} to ${node}' var box_node_authorized = _('${user} authorized the Box addon in ${node}'); var box_node_deauthorized = _('${user} deauthorized the Box addon for ${node}'); var box_node_deauthorized_no_user = _('Box addon for ${node} deauthorized'); +var box_file_downloaded = _('${user} downloaded file ${path} from Box in ${node}'); +var box_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from Box in ${node}'); var dataverse_file_added = _('${user} added file ${filename} to Dataverse dataset ${dataset} in ${node}'); var dataverse_file_removed = _('${user} removed file ${filename} from Dataverse dataset ${dataset} in ${node}'); var dataverse_dataset_linked = _('${user} linked Dataverse dataset ${dataset} to ${node}'); @@ -136,6 +142,8 @@ var dataverse_study_released = _('${user} published a new version of Dataverse d var dataverse_node_authorized = _('${user} authorized the Dataverse addon for ${node}'); var dataverse_node_deauthorized = _('${user} deauthorized the Dataverse addon for ${node}'); var dataverse_node_deauthorized_no_user = _('Dataverse addon for ${node} deauthorized'); +var dataverse_file_downloaded = _('${user} downloaded file ${filename} from Dataverse dataset ${dataset} in ${node}'); +var dataverse_folder_downloaded_zip = _('${user} downloaded folder ${filename} as a zip from Dataverse dataset ${dataset} in ${node}'); var dropbox_file_added = _('${user} added file ${path} to Dropbox in ${node}'); var dropbox_file_removed = _('${user} removed ${path_type} ${path} from Dropbox in ${node}'); var dropbox_file_updated = _('${user} updated file ${path} in Dropbox in ${node}'); @@ -144,6 +152,8 @@ var dropbox_folder_selected = _('${user} linked Dropbox folder ${dropbox_folder} var dropbox_node_authorized = _('${user} authorized the Dropbox addon for ${node}'); var dropbox_node_deauthorized = _('${user} deauthorized the Dropbox addon for ${node}'); var dropbox_node_deauthorized_no_user = _('Dropbox addon for ${node} deauthorized'); +var dropbox_file_downloaded = _('${user} downloaded file ${path} from Dropbox in ${node}'); +var dropbox_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from Dropbox in ${node}'); var figshare_folder_selected = _('${user} linked figshare ${folder} ${folder_name} to ${node}'); var figshare_content_unlinked = _('${user} unlinked content from figshare in ${node}'); var figshare_file_added = _('${user} added file ${path} to figshare in ${node}'); @@ -152,6 +162,8 @@ var figshare_folder_created = _('${user} created folder ${path} in figshare in $ var figshare_node_authorized = _('${user} authorized the figshare addon for ${node}'); var figshare_node_deauthorized = _('${user} deauthorized the figshare addon for ${node}'); var figshare_node_deauthorized_no_user = _('figshare addon for ${node} deauthorized'); +var figshare_file_downloaded = _('${user} downloaded file ${path} from figshare in ${node}'); +var figshare_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from figshare in ${node}'); var forward_url_changed = _('${user} changed external link to ${forward_url} in ${node}'); var github_file_added = _('${user} added file ${path} to GitHub repo ${repo} in ${node}'); var github_file_removed = _('${user} removed ${path_type} ${path} in GitHub repo ${repo} in ${node}'); @@ -161,6 +173,8 @@ var github_node_authorized = _('${user} authorized the GitHub addon for ${node}' var github_node_deauthorized = _('${user} deauthorized the GitHub addon for ${node}'); var github_node_deauthorized_no_user = _('GitHub addon for ${node} deauthorized'); var github_repo_linked = _('${user} linked GitHub repo ${repo} to ${node}'); +var github_file_downloaded = _('${user} downloaded file ${path} from GitHub repo ${repo} in ${node}'); +var github_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from GitHub repo ${repo} in ${node}'); var gitlab_file_added = _('${user} added file ${path} to GitLab repo ${gitlab_repo} in ${node}'); var gitlab_file_removed = _('${user} removed file ${path} in GitLab repo ${gitlab_repo} in ${node}'); var gitlab_file_updated = _('${user} updated file ${path} in GitLab repo ${gitlab_repo} in ${node}'); @@ -169,6 +183,8 @@ var gitlab_node_authorized = _('${user} authorized the GitLab addon for ${node}' var gitlab_node_deauthorized = _('${user} deauthorized the GitLab addon for ${node}'); var gitlab_node_deauthorized_no_user = _('GitLab addon for ${node} deauthorized'); var gitlab_repo_linked = _('${user} linked GitLab repo ${gitlab_repo} to ${node}'); +var gitlab_file_downloaded = _('${user} downloaded file ${path} from GitLab repo ${gitlab_repo} in ${node}'); +var gitlab_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from GitLab repo ${gitlab_repo} in ${node}'); var mendeley_folder_selected = _('${user} linked Mendeley folder ${folder_name} to ${node}'); var mendeley_node_authorized = _('${user} authorized the Mendeley addon for ${node}'); var mendeley_node_deauthorized = _('${user} deauthorized the Mendeley addon for ${node}'); @@ -184,6 +200,8 @@ var owncloud_folder_selected = _('${user} linked ownCloud folder ${folder} to ${ var owncloud_node_authorized = _('${user} authorized the ownCloud addon for ${node}'); var owncloud_node_deauthorized = _('${user} deauthorized the ownCloud addon for ${node}'); var owncloud_node_deauthorized_no_user = _('ownCloud addon for ${node} deauthorized'); +var owncloud_file_downloaded = _('${user} downloaded file ${path} from ownCloud in ${node}'); +var owncloud_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from ownCloud in ${node}'); var onedrive_file_added = _('${user} added file ${path} to Microsoft OneDrive in ${node}'); var onedrive_file_removed = _('${user} removed ${path_type} ${path} from Microsoft OneDrive in ${node}'); var onedrive_file_updated = _('${user} updated file ${path} in Microsoft OneDrive in ${node}'); @@ -192,6 +210,8 @@ var onedrive_folder_selected = _('${user} linked Microsoft OneDrive folder ${one var onedrive_node_authorized = _('${user} authorized the Microsoft OneDrive addon for ${node}'); var onedrive_node_deauthorized = _('${user} deauthorized the Microsoft OneDrive addon for ${node}'); var onedrive_node_deauthorized_no_user = _('Microsoft OneDrive addon for ${node} deauthorized'); +var onedrive_file_downloaded = _('${user} downloaded file ${path} from Microsoft OneDrive in ${node}'); +var onedrive_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from Microsoft OneDrive in ${node}'); var s3_bucket_linked = _('${user} linked the Amazon S3 bucket ${bucket} to ${node}'); var s3_bucket_unlinked = _('${user} unselected the Amazon S3 bucket ${bucket} in ${node}'); var s3_file_added = _('${user} added file ${path} to Amazon S3 bucket ${bucket} in ${node}'); @@ -201,6 +221,8 @@ var s3_folder_created = _('${user} created folder ${path} in Amazon S3 bucket ${ var s3_node_authorized = _('${user} authorized the Amazon S3 addon for ${node}'); var s3_node_deauthorized = _('${user} deauthorized the Amazon S3 addon for ${node}'); var s3_node_deauthorized_no_user = _('Amazon S3 addon for ${node} deauthorized'); +var s3_file_downloaded = _('${user} downloaded file ${path} from Amazon S3 bucket ${bucket} in ${node}'); +var s3_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from Amazon S3 bucket ${bucket} in ${node}'); var googledrive_file_added = _('${user} added file ${googledrive_path} to Google Drive in ${node}'); var googledrive_file_removed = _('${user} removed ${path_type} ${googledrive_path} from Google Drive in ${node}'); var googledrive_file_updated = _('${user} updated file ${googledrive_path} in Google Drive in ${node}'); @@ -209,6 +231,8 @@ var googledrive_folder_selected = _('${user} linked Google Drive folder ${google var googledrive_node_authorized = _('${user} authorized the Google Drive addon for ${node}'); var googledrive_node_deauthorized = _('${user} deauthorized the Google Drive addon for ${node}'); var googledrive_node_deauthorized_no_user = _('Google Drive addon for ${node} deauthorized'); +var googledrive_file_downloaded = _('${user} downloaded file ${googledrive_path} from Google Drive in ${node}'); +var googledrive_folder_downloaded_zip = _('${user} downloaded folder ${googledrive_path} as a zip from Google Drive in ${node}'); var bitbucket_file_added = _('${user} added file ${path} to Bitbucket repo ${bitbucket_repo} in ${node}'); var bitbucket_file_removed = _('${user} removed ${path_type} ${path} in Bitbucket repo ${bitbucket_repo} in ${node}'); var bitbucket_file_updated = _('${user} updated file ${path} in Bitbucket repo ${bitbucket_repo} in ${node}'); @@ -217,6 +241,8 @@ var bitbucket_node_authorized = _('${user} authorized the Bitbucket addon for ${ var bitbucket_node_deauthorized = _('${user} deauthorized the Bitbucket addon for ${node}'); var bitbucket_node_deauthorized_no_user = _('Bitbucket addon for ${node} deauthorized'); var bitbucket_repo_linked = _('${user} linked Bitbucket repo ${bitbucket_repo} to ${node}'); +var bitbucket_file_downloaded = _('${user} downloaded file ${path} from Bitbucket repo ${bitbucket_repo} in ${node}'); +var bitbucket_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from Bitbucket repo ${bitbucket_repo} in ${node}'); var swift_bucket_linked = _('${user} linked the Swift container ${bucket} to ${node}'); var swift_bucket_unlinked = _('${user} unselected the Swift container ${bucket} in ${node}'); var swift_file_added = _('${user} added file ${path} to Swift container ${bucket} in ${node}'); @@ -226,6 +252,8 @@ var swift_folder_created = _('${user} created folder ${path} in Swift container var swift_node_authorized = _('${user} authorized the Swift addon for ${node}'); var swift_node_deauthorized = _('${user} deauthorized the Swift addon for ${node}'); var swift_node_deauthorized_no_user = _('Swift addon for ${node} deauthorized'); +var swift_file_downloaded = _('${user} downloaded file ${path} from Swift container ${bucket} in ${node}'); +var swift_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from Swift container ${bucket} in ${node}'); var azureblobstorage_bucket_linked = _('${user} linked the Azure Blob Storage container ${bucket} to ${node}'); var azureblobstorage_bucket_unlinked = _('${user} unselected the Azure Blob Storage container ${bucket} in ${node}'); var azureblobstorage_file_added = _('${user} added file ${path} to Azure Blob Storage container ${bucket} in ${node}'); @@ -235,6 +263,8 @@ var azureblobstorage_folder_created = _('${user} created folder ${path} in Azure var azureblobstorage_node_authorized = _('${user} authorized the Azure Blob Storage addon for ${node}'); var azureblobstorage_node_deauthorized = _('${user} deauthorized the Azure Blob Storage addon for ${node}'); var azureblobstorage_node_deauthorized_no_user = _('Azure Blob Storage addon for ${node} deauthorized'); +var azureblobstorage_file_downloaded = _('${user} downloaded file ${path} from Azure Blob Storage container ${bucket} in ${node}'); +var azureblobstorage_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from Azure Blob Storage container ${bucket} in ${node}'); var weko_file_added = _('${user} added draft file ${filename} to JAIRO Cloud index ${dataset} in ${node}'); var weko_file_removed = _('${user} removed draft file ${filename} from JAIRO Cloud index ${dataset} in ${node}'); var weko_index_linked = _('${user} linked JAIRO Cloud index ${dataset} to ${node}'); @@ -243,6 +273,8 @@ var weko_item_deposited = _('${user} deposit item ${path} in JAIRO Cloud index $ var weko_node_authorized = _('${user} authorized the JAIRO Cloud addon for ${node}'); var weko_node_deauthorized = _('${user} deauthorized the JAIRO Cloud addon for ${node}'); var weko_node_deauthorized_no_user = _('JAIRO Cloud addon for ${node} deauthorized'); +var weko_file_downloaded = _('${user} downloaded draft file ${filename} from JAIRO Cloud index ${dataset} in ${node}'); +var weko_folder_downloaded_zip = _('${user} downloaded folder ${filename} as a zip from JAIRO Cloud index ${dataset} in ${node}'); var s3compat_bucket_linked = _('${user} linked the S3 Compatible Storage bucket ${bucket} to ${node}'); var s3compat_bucket_unlinked = _('${user} unselected the S3 Compatible Storage bucket ${bucket} in ${node}'); var s3compat_file_added = _('${user} added file ${path} to S3 Compatible Storage bucket ${bucket} in ${node}'); @@ -252,6 +284,8 @@ var s3compat_folder_created = _('${user} created folder ${path} in S3 Compatible var s3compat_node_authorized = _('${user} authorized the S3 Compatible Storage addon for ${node}'); var s3compat_node_deauthorized = _('${user} deauthorized the S3 Compatible Storage addon for ${node}'); var s3compat_node_deauthorized_no_user = _('S3 Compatible Storage addon for ${node} deauthorized'); +var s3compat_file_downloaded = _('${user} downloaded file ${path} from S3 Compatible Storage bucket ${bucket} in ${node}'); +var s3compat_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from S3 Compatible Storage bucket ${bucket} in ${node}'); var s3compatsigv4_bucket_linked = _('${user} linked the S3 Compatible Storage (SigV4) bucket ${bucket} to ${node}'); var s3compatsigv4_bucket_unlinked = _('${user} unselected the S3 Compatible Storage (SigV4) bucket ${bucket} in ${node}'); var s3compatsigv4_file_added = _('${user} added file ${path} to S3 Compatible Storage (SigV4) bucket ${bucket} in ${node}'); @@ -261,6 +295,8 @@ var s3compatsigv4_folder_created = _('${user} created folder ${path} in S3 Compa var s3compatsigv4_node_authorized = _('${user} authorized the S3 Compatible Storage (SigV4) addon for ${node}'); var s3compatsigv4_node_deauthorized = _('${user} deauthorized the S3 Compatible Storage (SigV4) addon for ${node}'); var s3compatsigv4_node_deauthorized_no_user = _('S3 Compatible Storage (SigV4) addon for ${node} deauthorized'); +var s3compatsigv4_file_downloaded = _('${user} downloaded file ${path} from S3 Compatible Storage (SigV4) bucket ${bucket} in ${node}'); +var s3compatsigv4_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from S3 Compatible Storage (SigV4) bucket ${bucket} in ${node}'); var s3compatinstitutions_file_added = _('${user} added file ${path} to S3 Compatible Storage for Institutions in ${node}'); var s3compatinstitutions_file_removed = _('${user} removed ${path_type} ${path} from S3 Compatible Storage for Institutions in ${node}'); var s3compatinstitutions_file_updated = _('${user} updated file ${path} in S3 Compatible Storage for Institutions in ${node}'); @@ -269,6 +305,8 @@ var s3compatinstitutions_folder_selected = _('${user} linked S3 Compatible Stora var s3compatinstitutions_node_authorized = _('${user} authorized the S3 Compatible Storage for Institutions addon for ${node}'); var s3compatinstitutions_node_deauthorized = _('${user} deauthorized the S3 Compatible Storage for Institutions addon for ${node}'); var s3compatinstitutions_node_deauthorized_no_user = _('S3 Compatible Storage for Institutions addon for ${node} deauthorized'); +var s3compatinstitutions_file_downloaded = _('${user} downloaded file ${path} from S3 Compatible Storage for Institutions in ${node}'); +var s3compatinstitutions_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from S3 Compatible Storage for Institutions in ${node}'); var s3compatb3_bucket_linked = _('${user} linked the Oracle Cloud Infrastructure Object Storage bucket ${bucket} to ${node}'); var s3compatb3_bucket_unlinked = _('${user} unselected the Oracle Cloud Infrastructure Object Storage bucket ${bucket} in ${node}'); var s3compatb3_file_added = _('${user} added file ${path} to Oracle Cloud Infrastructure Object Storage bucket ${bucket} in ${node}'); @@ -278,6 +316,8 @@ var s3compatb3_folder_created = _('${user} created folder ${path} in Oracle Clou var s3compatb3_node_authorized = _('${user} authorized the Oracle Cloud Infrastructure Object Storage addon for ${node}'); var s3compatb3_node_deauthorized = _('${user} deauthorized the Oracle Cloud Infrastructure Object Storage addon for ${node}'); var s3compatb3_node_deauthorized_no_user = _('Oracle Cloud Infrastructure Object Storage addon for ${node} deauthorized'); +var s3compatb3_file_downloaded = _('${user} downloaded file ${path} from Oracle Cloud Infrastructure Object Storage bucket ${bucket} in ${node}'); +var s3compatb3_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from Oracle Cloud Infrastructure Object Storage bucket ${bucket} in ${node}'); var ociinstitutions_file_added = _('${user} added file ${path} to Oracle Cloud Infrastructure for Institutions in ${node}'); var ociinstitutions_file_removed = _('${user} removed ${path_type} ${path} from Oracle Cloud Infrastructure for Institutions in ${node}'); var ociinstitutions_file_updated = _('${user} updated file ${path} in Oracle Cloud Infrastructure for Institutions in ${node}'); @@ -286,6 +326,8 @@ var ociinstitutions_folder_selected = _('${user} linked Oracle Cloud Infrastruct var ociinstitutions_node_authorized = _('${user} authorized the Oracle Cloud Infrastructure for Institutions addon for ${node}'); var ociinstitutions_node_deauthorized = _('${user} deauthorized the Oracle Cloud Infrastructure for Institutions addon for ${node}'); var ociinstitutions_node_deauthorized_no_user = _('Oracle Cloud Infrastructure for Institutions addon for ${node} deauthorized'); +var ociinstitutions_file_downloaded = _('${user} downloaded file ${path} from Oracle Cloud Infrastructure for Institutions in ${node}'); +var ociinstitutions_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from Oracle Cloud Infrastructure for Institutions in ${node}'); var nextcloud_file_added = _('${user} added file ${path} to Nextcloud in ${node}'); var nextcloud_file_removed = _('${user} removed ${path_type} ${path} from Nextcloud in ${node}'); var nextcloud_file_updated = _('${user} updated file ${path} in Nextcloud in ${node}'); @@ -294,6 +336,8 @@ var nextcloud_folder_selected = _('${user} linked Nextcloud folder ${folder} to var nextcloud_node_authorized = _('${user} authorized the Nextcloud addon for ${node}'); var nextcloud_node_deauthorized = _('${user} deauthorized the Nextcloud addon for ${node}'); var nextcloud_node_deauthorized_no_user = _('Nextcloud addon for ${node} deauthorized'); +var nextcloud_file_downloaded = _('${user} downloaded file ${path} from Nextcloud in ${node}'); +var nextcloud_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from Nextcloud in ${node}'); var nextcloudinstitutions_file_added = _('${user} added file ${path} to Nextcloud (for Institutions) in ${node}'); var nextcloudinstitutions_file_removed = _('${user} removed ${path_type} ${path} from Nextcloud (for Institutions) in ${node}'); var nextcloudinstitutions_file_updated = _('${user} updated file ${path} in Nextcloud (for Institutions) in ${node}'); @@ -302,6 +346,8 @@ var nextcloudinstitutions_folder_selected = _('${user} linked Nextcloud (for Ins var nextcloudinstitutions_node_authorized = _('${user} authorized the Nextcloud (for Institutions) addon for ${node}'); var nextcloudinstitutions_node_deauthorized = _('${user} deauthorized the Nextcloud (for Institutions) addon for ${node}'); var nextcloudinstitutions_node_deauthorized_no_user = _('Nextcloud (for Institutions) addon for ${node} deauthorized'); +var nextcloudinstitutions_file_downloaded = _('${user} downloaded file ${path} from Nextcloud (for Institutions) in ${node}'); +var nextcloudinstitutions_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from Nextcloud (for Institutions) in ${node}'); var iqbrims_file_added = _('${user} added file ${iqbrims_path} to IQB-RIMS in ${node}'); var iqbrims_file_removed = _('${user} removed ${path_type} ${iqbrims_path} from IQB-RIMS in ${node}'); var iqbrims_file_updated = _('${user} updated file ${iqbrims_path} in IQB-RIMS in ${node}'); @@ -314,6 +360,8 @@ var iqbrims_imagescan_workflow_start = _('${user} started IQB-RIMS workflow for var iqbrims_imagescan_workflow_finish = _('Finished workflow of ${user} for ${node}'); var iqbrims_paper_workflow_start = _('${user} started IQB-RIMS workflow for ${node}'); var iqbrims_paper_workflow_finish = _('Finished workflow of ${user} for ${node}'); +var iqbrims_file_downloaded = _('${user} downloaded file ${iqbrims_path} from IQB-RIMS in ${node}'); +var iqbrims_folder_downloaded_zip = _('${user} downloaded folder ${iqbrims_path} as a zip from IQB-RIMS in ${node}'); var dropboxbusiness_file_added = _('${user} added file ${path} to Dropbox Business in ${node}'); var dropboxbusiness_file_removed = _('${user} removed ${path_type} ${path} from Dropbox Business in ${node}'); var dropboxbusiness_file_updated = _('${user} updated file ${path} in Dropbox Business in ${node}'); @@ -322,6 +370,8 @@ var dropboxbusiness_folder_selected = _('${user} linked Dropbox Business folder var dropboxbusiness_node_authorized = _('${user} authorized the Dropbox Business addon for ${node}'); var dropboxbusiness_node_deauthorized = _('${user} deauthorized the Dropbox Business addon for ${node}'); var dropboxbusiness_node_deauthorized_no_user = _('Dropbox Business addon for ${node} deauthorized'); +var dropboxbusiness_file_downloaded = _('${user} downloaded file ${path} from Dropbox Business in ${node}'); +var dropboxbusiness_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from Dropbox Business in ${node}'); var onedrivebusiness_file_added = _('${user} added file ${path} to the OneDrive team folder ${folder} in ${node}'); var onedrivebusiness_file_removed = _('${user} removed ${path} in the OneDrive team folder ${folder} in ${node}'); var onedrivebusiness_file_updated = _('${user} updated file ${path} in the OneDrive team folder ${folder} in ${node}'); @@ -329,6 +379,8 @@ var onedrivebusiness_folder_created = _('${user} created folder ${path} in the O var onedrivebusiness_node_authorized = _('${user} authorized the OneDrive for Office365 addon for ${node}'); var onedrivebusiness_node_deauthorized = _('${user} deauthorized the OneDrive for Office365 addon for ${node}'); var onedrivebusiness_node_deauthorized_no_user = _('OneDrive for Office365 addon for ${node} deauthorized'); +var onedrivebusiness_file_downloaded = _('${user} downloaded file ${path} from the OneDrive team folder ${folder} in ${node}'); +var onedrivebusiness_folder_downloaded_zip = _('${user} downloaded folder ${path} as a zip from the OneDrive team folder ${folder} in ${node}'); var metadata_added = _('${user} added metadata in ${node}'); var metadata_file_added = _('${user} added metadata ${path} in ${node}'); var metadata_file_updated = _('${user} updated metadata ${path} in ${node}'); diff --git a/website/static/js/logTextParser.js b/website/static/js/logTextParser.js index 357bae7747b..d74f3c2ecd3 100644 --- a/website/static/js/logTextParser.js +++ b/website/static/js/logTextParser.js @@ -543,6 +543,32 @@ var LogPieces = { 'dropboxbusiness_file_added', 'dropboxbusiness_file_updated', 's3compatinstitutions_file_added', 's3compatinstitutions_file_updated', 'weko_file_added', 'weko_item_deposited', + 'file_downloaded', + 'osf_storage_file_downloaded', + 'box_file_downloaded', + 'dropbox_file_downloaded', + 'dropboxbusiness_file_downloaded', + 'googledrive_file_downloaded', + 'github_file_downloaded', + 'gitlab_file_downloaded', + 'bitbucket_file_downloaded', + 's3_file_downloaded', + 's3compat_file_downloaded', + 's3compatsigv4_file_downloaded', + 's3compatb3_file_downloaded', + 's3compatinstitutions_file_downloaded', + 'swift_file_downloaded', + 'onedrive_file_downloaded', + 'onedrivebusiness_file_downloaded', + 'owncloud_file_downloaded', + 'nextcloud_file_downloaded', + 'nextcloudinstitutions_file_downloaded', + 'azureblobstorage_file_downloaded', + 'weko_file_downloaded', + 'figshare_file_downloaded', + 'dataverse_file_downloaded', + 'iqbrims_file_downloaded', + 'ociinstitutions_file_downloaded', 'timestamp_added']; if (acceptableLinkedItems.indexOf(action) !== -1 && logObject.attributes.params.urls) { return logObject.attributes.params.urls.view; @@ -552,6 +578,15 @@ var LogPieces = { }; }, view: function (ctrl, logObject) { + var action = logObject.attributes.action; + if (action.indexOf('folder_downloaded_zip') !== -1) { + // Downloading the whole repo/storage as a zip has no real path + // providers report this root case as either '' or '/'. + var rawPath = logObject.attributes.params.path; + if (!rawPath || rawPath === '/') { + return m('span', ''); + } + } var url = ctrl.returnLinkForPath(logObject); return returnTextParams('path', 'a file', logObject, url); } diff --git a/website/translations/en/LC_MESSAGES/js_messages.po b/website/translations/en/LC_MESSAGES/js_messages.po index 3c26568387c..c2ef7d342a0 100644 --- a/website/translations/en/LC_MESSAGES/js_messages.po +++ b/website/translations/en/LC_MESSAGES/js_messages.po @@ -9472,3 +9472,411 @@ msgstr "" "GakuNin RDM is not responsible for the service or for your use " "thereof.\n" "\n" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from NII Storage in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from NII Storage in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Box in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Box in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from a Dataverse dataset in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a Dataverse dataset in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Dropbox in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Dropbox in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from figshare in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from figshare in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from a GitHub repo in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a GitHub repo in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from ownCloud in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from ownCloud in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Microsoft OneDrive in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Microsoft OneDrive in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an Amazon S3 bucket in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an Amazon S3 bucket in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Google Drive in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Google Drive in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from a Bitbucket repo in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a Bitbucket repo in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from a Swift container in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a Swift container in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an Azure Blob Storage container in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an Azure Blob Storage container in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a draft file from a JAIRO Cloud index in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a JAIRO Cloud index in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an S3 Compatible Storage bucket in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an S3 Compatible Storage bucket in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an S3 Compatible Storage (SigV4) bucket in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an S3 Compatible Storage (SigV4) bucket in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from S3 Compatible Storage for Institutions in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from S3 Compatible Storage for Institutions in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an Oracle Cloud Infrastructure Object Storage bucket in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an Oracle Cloud Infrastructure Object Storage bucket in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Oracle Cloud Infrastructure for Institutions in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Oracle Cloud Infrastructure for Institutions in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Nextcloud in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Nextcloud in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Nextcloud (for Institutions) in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Nextcloud (for Institutions) in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from IQB-RIMS in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from IQB-RIMS in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Dropbox Business in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Dropbox Business in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an OneDrive team folder in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an OneDrive team folder in a project" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from ${storage_name} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from ${storage_name} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Box in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Box in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${filename} from Dataverse dataset ${dataset} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${filename} as a zip from Dataverse dataset ${dataset} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Dropbox in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Dropbox in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from figshare in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from figshare in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from GitHub repo ${repo} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from GitHub repo ${repo} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from GitLab repo ${gitlab_repo} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from GitLab repo ${gitlab_repo} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from ownCloud in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from ownCloud in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Microsoft OneDrive in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Microsoft OneDrive in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Amazon S3 bucket ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Amazon S3 bucket ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${googledrive_path} from Google Drive in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${googledrive_path} as a zip from Google Drive in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Bitbucket repo ${bitbucket_repo} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Bitbucket repo ${bitbucket_repo} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Swift container ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Swift container ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Azure Blob Storage container ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Azure Blob Storage container ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded draft file ${filename} from JAIRO Cloud index ${dataset} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${filename} as a zip from JAIRO Cloud index ${dataset} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from S3 Compatible Storage bucket ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from S3 Compatible Storage bucket ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from S3 Compatible Storage (SigV4) bucket ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from S3 Compatible Storage (SigV4) bucket ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from S3 Compatible Storage for Institutions in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from S3 Compatible Storage for Institutions in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Oracle Cloud Infrastructure Object Storage bucket ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Oracle Cloud Infrastructure Object Storage bucket ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Oracle Cloud Infrastructure for Institutions in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Oracle Cloud Infrastructure for Institutions in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Nextcloud in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Nextcloud in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Nextcloud (for Institutions) in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Nextcloud (for Institutions) in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${iqbrims_path} from IQB-RIMS in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${iqbrims_path} as a zip from IQB-RIMS in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Dropbox Business in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Dropbox Business in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from the OneDrive team folder ${folder} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from the OneDrive team folder ${folder} in ${node}" +msgstr "" diff --git a/website/translations/ja/LC_MESSAGES/js_messages.po b/website/translations/ja/LC_MESSAGES/js_messages.po index 7bcb047a441..ea9208357f6 100644 --- a/website/translations/ja/LC_MESSAGES/js_messages.po +++ b/website/translations/ja/LC_MESSAGES/js_messages.po @@ -11018,3 +11018,411 @@ msgstr "" "RDMプロジェクトは外部サービスに接続されます。このサービスを利用することで、それら外部サービスの利用規約に拘束されます。GakuNin " "RDMは、それらサービスまたはユーザーによるその利用に対して責任を負いません。\n" "\n" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from a project" +msgstr "ユーザーがプロジェクトからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a project" +msgstr "ユーザーがプロジェクトからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from NII Storage in a project" +msgstr "ユーザーがプロジェクトのNII Storageからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from NII Storage in a project" +msgstr "ユーザーがプロジェクトのNII StorageからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Box in a project" +msgstr "ユーザーがプロジェクトのBoxからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Box in a project" +msgstr "ユーザーがプロジェクトのBoxからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from a Dataverse dataset in a project" +msgstr "ユーザーがプロジェクトのDataverseデータセットからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a Dataverse dataset in a project" +msgstr "ユーザーがプロジェクトのDataverseデータセットからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Dropbox in a project" +msgstr "ユーザーがプロジェクトのDropboxからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Dropbox in a project" +msgstr "ユーザーがプロジェクトのDropboxからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from figshare in a project" +msgstr "ユーザーがプロジェクトのfigshareからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from figshare in a project" +msgstr "ユーザーがプロジェクトのfigshareからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from a GitHub repo in a project" +msgstr "ユーザーがプロジェクトのGitHubリポジトリからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a GitHub repo in a project" +msgstr "ユーザーがプロジェクトのGitHubリポジトリからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from ownCloud in a project" +msgstr "ユーザーがプロジェクトのownCloudからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from ownCloud in a project" +msgstr "ユーザーがプロジェクトのownCloudからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Microsoft OneDrive in a project" +msgstr "ユーザーがプロジェクトのMicrosoft OneDriveからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Microsoft OneDrive in a project" +msgstr "ユーザーがプロジェクトのMicrosoft OneDriveからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an Amazon S3 bucket in a project" +msgstr "ユーザーがプロジェクトのAmazon S3バケットからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an Amazon S3 bucket in a project" +msgstr "ユーザーがプロジェクトのAmazon S3バケットからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Google Drive in a project" +msgstr "ユーザーがプロジェクトのGoogle Driveからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Google Drive in a project" +msgstr "ユーザーがプロジェクトのGoogle DriveからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from a Bitbucket repo in a project" +msgstr "ユーザーがプロジェクトのBitbucketリポジトリからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a Bitbucket repo in a project" +msgstr "ユーザーがプロジェクトのBitbucketリポジトリからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from a Swift container in a project" +msgstr "ユーザーがプロジェクトのSwiftコンテナからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a Swift container in a project" +msgstr "ユーザーがプロジェクトのSwiftコンテナからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an Azure Blob Storage container in a project" +msgstr "ユーザーがプロジェクトのAzure Blob Storageコンテナからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an Azure Blob Storage container in a project" +msgstr "ユーザーがプロジェクトのAzure Blob StorageコンテナからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a draft file from a JAIRO Cloud index in a project" +msgstr "ユーザーがプロジェクトのJAIRO Cloudインデックスからドラフトファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a JAIRO Cloud index in a project" +msgstr "ユーザーがプロジェクトのJAIRO CloudインデックスからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an S3 Compatible Storage bucket in a project" +msgstr "ユーザーがプロジェクトのS3互換ストレージバケットからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an S3 Compatible Storage bucket in a project" +msgstr "ユーザーがプロジェクトのS3互換ストレージバケットからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an S3 Compatible Storage (SigV4) bucket in a project" +msgstr "ユーザーがプロジェクトのS3互換ストレージ(SigV4)バケットからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an S3 Compatible Storage (SigV4) bucket in a project" +msgstr "ユーザーがプロジェクトのS3互換ストレージ(SigV4)バケットからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from S3 Compatible Storage for Institutions in a project" +msgstr "ユーザーがプロジェクトのS3互換ストレージ(機関向け)からファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from S3 Compatible Storage for Institutions in a project" +msgstr "ユーザーがプロジェクトのS3互換ストレージ(機関向け)からフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an Oracle Cloud Infrastructure Object Storage bucket in a project" +msgstr "ユーザーがプロジェクトのOracle Cloud Infrastructure Object Storageバケットからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an Oracle Cloud Infrastructure Object Storage bucket in a project" +msgstr "ユーザーがプロジェクトのOracle Cloud Infrastructure Object StorageバケットからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Oracle Cloud Infrastructure for Institutions in a project" +msgstr "ユーザーがプロジェクトのOracle Cloud Infrastructure(機関向け)からファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Oracle Cloud Infrastructure for Institutions in a project" +msgstr "ユーザーがプロジェクトのOracle Cloud Infrastructure(機関向け)からフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Nextcloud in a project" +msgstr "ユーザーがプロジェクトのNextcloudからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Nextcloud in a project" +msgstr "ユーザーがプロジェクトのNextcloudからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Nextcloud (for Institutions) in a project" +msgstr "ユーザーがプロジェクトのNextcloud (機関向け)からファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Nextcloud (for Institutions) in a project" +msgstr "ユーザーがプロジェクトのNextcloud(機関向け)からフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from IQB-RIMS in a project" +msgstr "ユーザーがプロジェクトのIQB-RIMSからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from IQB-RIMS in a project" +msgstr "ユーザーがプロジェクトのIQB-RIMSからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Dropbox Business in a project" +msgstr "ユーザーがプロジェクトのDropbox Businessからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Dropbox Business in a project" +msgstr "ユーザーがプロジェクトのDropbox BusinessからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an OneDrive team folder in a project" +msgstr "ユーザーがプロジェクトのOnedriveチームフォルダからファイルをダウンロードしました" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an OneDrive team folder in a project" +msgstr "ユーザーがプロジェクトのOnedriveチームフォルダからフォルダをZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from ${node}" +msgstr "${user}が${node}からファイル${path}をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from ${node}" +msgstr "${user}が${node}からフォルダ${path}をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from ${storage_name} in ${node}" +msgstr "${user}が${node}の${storage_name}からファイル${path}をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from ${storage_name} in ${node}" +msgstr "${user}が${node}の${storage_name}からフォルダ${path}をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Box in ${node}" +msgstr "${user}が${node}のBoxからファイル${path}をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Box in ${node}" +msgstr "${user}が${node}のBoxからフォルダ${path}をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${filename} from Dataverse dataset ${dataset} in ${node}" +msgstr "${user}が${node}のDataverseデータセット(${dataset})からファイル(${filename})をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${filename} as a zip from Dataverse dataset ${dataset} in ${node}" +msgstr "${user}が${node}のDataverseデータセット(${dataset})からフォルダ(${filename})をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Dropbox in ${node}" +msgstr "${user}が${node}のDropboxからファイル${path}をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Dropbox in ${node}" +msgstr "${user}が${node}のDropboxからフォルダ${path}をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from figshare in ${node}" +msgstr "${user}が${node}のfigshareからファイル${path}をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from figshare in ${node}" +msgstr "${user}が${node}のfigshareからフォルダ${path}をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from GitHub repo ${repo} in ${node}" +msgstr "${user}が${node}のGitHubリポジトリ${repo}からファイル${path}をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from GitHub repo ${repo} in ${node}" +msgstr "${user}が${node}のGitHubリポジトリ${repo}からフォルダ${path}をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from GitLab repo ${gitlab_repo} in ${node}" +msgstr "${user}が${node}のGitLabリポジトリ${gitlab_repo}からファイル${path}をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from GitLab repo ${gitlab_repo} in ${node}" +msgstr "${user}が${node}のGitLabリポジトリ${gitlab_repo}からフォルダ${path}をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from ownCloud in ${node}" +msgstr "${user}が${node}のownCloudからファイル${path}をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from ownCloud in ${node}" +msgstr "${user}が${node}のownCloudからフォルダ${path}をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Microsoft OneDrive in ${node}" +msgstr "${user}が${node}のMicrosoft OneDriveからファイル${path}をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Microsoft OneDrive in ${node}" +msgstr "${user}が${node}のMicrosoft OneDriveからフォルダ${path}をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Amazon S3 bucket ${bucket} in ${node}" +msgstr "${user}が${node}のAmazon S3バケット${bucket}からファイル${path}をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Amazon S3 bucket ${bucket} in ${node}" +msgstr "${user}が${node}のAmazon S3バケット${bucket}からフォルダ${path}をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${googledrive_path} from Google Drive in ${node}" +msgstr "${user}が${node}のGoogle Driveからファイル${googledrive_path}をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${googledrive_path} as a zip from Google Drive in ${node}" +msgstr "${user}が${node}のGoogle Driveからフォルダ${googledrive_path}をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Bitbucket repo ${bitbucket_repo} in ${node}" +msgstr "${user}が${node}のBitbucketリポジトリ(${bitbucket_repo})からファイル(${path})をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Bitbucket repo ${bitbucket_repo} in ${node}" +msgstr "${user}が${node}のBitbucketリポジトリ(${bitbucket_repo})からフォルダ(${path})をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Swift container ${bucket} in ${node}" +msgstr "${user}が${node}のSwiftコンテナ${bucket}からファイル${path}をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Swift container ${bucket} in ${node}" +msgstr "${user}が${node}のSwiftコンテナ${bucket}からフォルダ${path}をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Azure Blob Storage container ${bucket} in ${node}" +msgstr "${user}が${node}のAzure Blob Storageコンテナ${bucket}からファイル${path}をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Azure Blob Storage container ${bucket} in ${node}" +msgstr "${user}が${node}のAzure Blob Storageコンテナ${bucket}からフォルダ${path}をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded draft file ${filename} from JAIRO Cloud index ${dataset} in ${node}" +msgstr "${user}が${node}のJAIRO Cloudインデックス(${dataset})からドラフトファイル(${filename})をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${filename} as a zip from JAIRO Cloud index ${dataset} in ${node}" +msgstr "${user}が${node}のJAIRO Cloudインデックス(${dataset})からフォルダ(${filename})をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from S3 Compatible Storage bucket ${bucket} in ${node}" +msgstr "${user}が${node}のS3互換ストレージバケット(${bucket})からファイル(${path})をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from S3 Compatible Storage bucket ${bucket} in ${node}" +msgstr "${user}が${node}のS3互換ストレージバケット(${bucket})からフォルダ(${path})をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from S3 Compatible Storage (SigV4) bucket ${bucket} in ${node}" +msgstr "${user}が${node}のS3互換ストレージ(SigV4)バケット(${bucket})からファイル(${path})をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from S3 Compatible Storage (SigV4) bucket ${bucket} in ${node}" +msgstr "${user}が${node}のS3互換ストレージ(SigV4)バケット(${bucket})からフォルダ(${path})をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from S3 Compatible Storage for Institutions in ${node}" +msgstr "${user}が${node}のS3互換ストレージ(for Institutions)からファイル(${path})をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from S3 Compatible Storage for Institutions in ${node}" +msgstr "${user}が${node}のS3互換ストレージ(for Institutions)からフォルダ(${path})をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Oracle Cloud Infrastructure Object Storage bucket ${bucket} in ${node}" +msgstr "${user}が${node}のOCIオブジェクトストレージ(${bucket})からファイル(${path})をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Oracle Cloud Infrastructure Object Storage bucket ${bucket} in ${node}" +msgstr "${user}が${node}のOCIオブジェクトストレージ(${bucket})からフォルダ(${path})をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Oracle Cloud Infrastructure for Institutions in ${node}" +msgstr "${user}が${node}のOCIオブジェクトストレージ(for Institutions)からファイル(${path})をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Oracle Cloud Infrastructure for Institutions in ${node}" +msgstr "${user}が${node}のOCIオブジェクトストレージ(for Institutions)からフォルダ(${path})をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Nextcloud in ${node}" +msgstr "${user}が${node}のNextcloudからファイル${path}をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Nextcloud in ${node}" +msgstr "${user}が${node}のNextcloudからフォルダ${path}をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Nextcloud (for Institutions) in ${node}" +msgstr "${user}が${node}のNextcloud(for Institutions)からファイル(${path})をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Nextcloud (for Institutions) in ${node}" +msgstr "${user}が${node}のNextcloud(for Institutions)からフォルダ(${path})をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${iqbrims_path} from IQB-RIMS in ${node}" +msgstr "${user}が${node}のIQB-RIMSからファイル${iqbrims_path}をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${iqbrims_path} as a zip from IQB-RIMS in ${node}" +msgstr "${user}が${node}のIQB-RIMSからフォルダ${iqbrims_path}をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Dropbox Business in ${node}" +msgstr "${user}が${node}のDropbox Businessからファイル${path}をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Dropbox Business in ${node}" +msgstr "${user}が${node}のDropbox Businessからフォルダ${path}をZIPとしてダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from the OneDrive team folder ${folder} in ${node}" +msgstr "${user}が${node}のOneDriveチームフォルダ(${folder})からファイル(${path})をダウンロードしました" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from the OneDrive team folder ${folder} in ${node}" +msgstr "${user}が${node}のOneDriveチームフォルダ(${folder})からフォルダ(${path})をZIPとしてダウンロードしました" diff --git a/website/translations/js_messages.pot b/website/translations/js_messages.pot index ea04f71e49c..4c8e60a0368 100644 --- a/website/translations/js_messages.pot +++ b/website/translations/js_messages.pot @@ -9311,3 +9311,411 @@ msgid "" "${user} made bibliographic group ${mapcore_groups} a " "non-bibliographic group on ${node}" msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from NII Storage in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from NII Storage in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Box in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Box in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from a Dataverse dataset in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a Dataverse dataset in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Dropbox in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Dropbox in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from figshare in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from figshare in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from a GitHub repo in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a GitHub repo in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from ownCloud in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from ownCloud in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Microsoft OneDrive in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Microsoft OneDrive in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an Amazon S3 bucket in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an Amazon S3 bucket in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Google Drive in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Google Drive in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from a Bitbucket repo in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a Bitbucket repo in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from a Swift container in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a Swift container in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an Azure Blob Storage container in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an Azure Blob Storage container in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a draft file from a JAIRO Cloud index in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from a JAIRO Cloud index in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an S3 Compatible Storage bucket in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an S3 Compatible Storage bucket in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an S3 Compatible Storage (SigV4) bucket in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an S3 Compatible Storage (SigV4) bucket in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from S3 Compatible Storage for Institutions in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from S3 Compatible Storage for Institutions in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an Oracle Cloud Infrastructure Object Storage bucket in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an Oracle Cloud Infrastructure Object Storage bucket in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Oracle Cloud Infrastructure for Institutions in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Oracle Cloud Infrastructure for Institutions in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Nextcloud in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Nextcloud in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Nextcloud (for Institutions) in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Nextcloud (for Institutions) in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from IQB-RIMS in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from IQB-RIMS in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from Dropbox Business in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from Dropbox Business in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a file from an OneDrive team folder in a project" +msgstr "" + +#: website/static/js/anonymousLogActionsList_extract.js +msgid "A user downloaded a folder as a zip from an OneDrive team folder in a project" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from ${storage_name} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from ${storage_name} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Box in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Box in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${filename} from Dataverse dataset ${dataset} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${filename} as a zip from Dataverse dataset ${dataset} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Dropbox in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Dropbox in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from figshare in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from figshare in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from GitHub repo ${repo} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from GitHub repo ${repo} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from GitLab repo ${gitlab_repo} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from GitLab repo ${gitlab_repo} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from ownCloud in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from ownCloud in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Microsoft OneDrive in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Microsoft OneDrive in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Amazon S3 bucket ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Amazon S3 bucket ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${googledrive_path} from Google Drive in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${googledrive_path} as a zip from Google Drive in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Bitbucket repo ${bitbucket_repo} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Bitbucket repo ${bitbucket_repo} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Swift container ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Swift container ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Azure Blob Storage container ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Azure Blob Storage container ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded draft file ${filename} from JAIRO Cloud index ${dataset} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${filename} as a zip from JAIRO Cloud index ${dataset} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from S3 Compatible Storage bucket ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from S3 Compatible Storage bucket ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from S3 Compatible Storage (SigV4) bucket ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from S3 Compatible Storage (SigV4) bucket ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from S3 Compatible Storage for Institutions in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from S3 Compatible Storage for Institutions in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Oracle Cloud Infrastructure Object Storage bucket ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Oracle Cloud Infrastructure Object Storage bucket ${bucket} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Oracle Cloud Infrastructure for Institutions in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Oracle Cloud Infrastructure for Institutions in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Nextcloud in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Nextcloud in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Nextcloud (for Institutions) in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Nextcloud (for Institutions) in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${iqbrims_path} from IQB-RIMS in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${iqbrims_path} as a zip from IQB-RIMS in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from Dropbox Business in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from Dropbox Business in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded file ${path} from the OneDrive team folder ${folder} in ${node}" +msgstr "" + +#: website/static/js/logActionsList_extract.js +msgid "${user} downloaded folder ${path} as a zip from the OneDrive team folder ${folder} in ${node}" +msgstr ""