Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into audit-backend
Browse files Browse the repository at this point in the history
  • Loading branch information
waxlamp committed Aug 8, 2024
2 parents 1a0faf7 + 83d0c44 commit cee70a5
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 9 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
# v0.3.92 (Tue Jul 30 2024)

#### 🐛 Bug Fix

- Add retries to sha256 checksum calculation task [#1937](https://github.com/dandi/dandi-archive/pull/1937) ([@jjnesbitt](https://github.com/jjnesbitt))
- Contact owner [#1840](https://github.com/dandi/dandi-archive/pull/1840) ([@marySalvi](https://github.com/marySalvi) [@mvandenburgh](https://github.com/mvandenburgh))

#### Authors: 3

- Jacob Nesbitt ([@jjnesbitt](https://github.com/jjnesbitt))
- Mary Salvi ([@marySalvi](https://github.com/marySalvi))
- Mike VanDenburgh ([@mvandenburgh](https://github.com/mvandenburgh))

---

# v0.3.91 (Wed Jul 24 2024)

#### 🐛 Bug Fix
Expand Down
18 changes: 15 additions & 3 deletions dandiapi/api/tasks/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from celery import shared_task
from celery.exceptions import SoftTimeLimitExceeded
from celery.utils.log import get_task_logger
from django.contrib.auth.models import User

Expand All @@ -16,6 +19,9 @@
from dandiapi.api.models import Asset, AssetBlob, Version
from dandiapi.api.models.dandiset import Dandiset

if TYPE_CHECKING:
from uuid import UUID

logger = get_task_logger(__name__)


Expand All @@ -27,10 +33,16 @@ def remove_asset_blob_embargoed_tag_task(blob_id: str) -> None:
remove_asset_blob_embargoed_tag(asset_blob)


@shared_task(queue='calculate_sha256', soft_time_limit=86_400)
def calculate_sha256(blob_id: str) -> None:
@shared_task(
queue='calculate_sha256',
soft_time_limit=86_400, # 24 hours
autoretry_for=(SoftTimeLimitExceeded,),
retry_backoff=True,
max_retries=3,
)
def calculate_sha256(blob_id: str | UUID) -> None:
asset_blob = AssetBlob.objects.get(blob_id=blob_id)
logger.info('Found AssetBlob %s', blob_id)
logger.info('Calculating sha256 checksum for asset blob %s', blob_id)
sha256 = asset_blob.blob.storage.sha256_checksum(asset_blob.blob.name)

# TODO: Run dandi-cli validation
Expand Down
13 changes: 12 additions & 1 deletion dandiapi/api/tests/test_dandiset.py
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,7 @@ def test_dandiset_rest_get_owners(api_client, dandiset, social_account):
{
'username': social_account.extra_data['login'],
'name': social_account.extra_data['name'],
'email': None,
}
]

Expand All @@ -836,7 +837,13 @@ def test_dandiset_rest_get_owners_no_social_account(api_client, dandiset, user):
resp = api_client.get(f'/api/dandisets/{dandiset.identifier}/users/')

assert resp.status_code == 200
assert resp.data == [{'username': user.username, 'name': f'{user.first_name} {user.last_name}'}]
assert resp.data == [
{
'username': user.username,
'name': f'{user.first_name} {user.last_name}',
'email': None,
}
]


@pytest.mark.parametrize(
Expand Down Expand Up @@ -871,6 +878,7 @@ def test_dandiset_rest_change_owner(
{
'username': social_account2.extra_data['login'],
'name': social_account2.extra_data['name'],
'email': social_account2.extra_data['email'],
}
]
assert list(dandiset.owners) == [user2]
Expand Down Expand Up @@ -943,10 +951,12 @@ def test_dandiset_rest_add_owner(
{
'username': social_account1.extra_data['login'],
'name': social_account1.extra_data['name'],
'email': social_account1.extra_data['email'],
},
{
'username': social_account2.extra_data['login'],
'name': social_account2.extra_data['name'],
'email': social_account2.extra_data['email'],
},
]
assert list(dandiset.owners) == [user1, user2]
Expand Down Expand Up @@ -983,6 +993,7 @@ def test_dandiset_rest_remove_owner(
{
'username': social_account1.extra_data['login'],
'name': social_account1.extra_data['name'],
'email': social_account1.extra_data['email'],
}
]
assert list(dandiset.owners) == [user1]
Expand Down
13 changes: 10 additions & 3 deletions dandiapi/api/views/dandiset.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ def unembargo(self, request, dandiset__pk):
)
# TODO: move these into a viewset
@action(methods=['GET', 'PUT'], detail=True)
def users(self, request, dandiset__pk): # noqa: C901
def users(self, request, dandiset__pk):
dandiset: Dandiset = self.get_object()
if request.method == 'PUT':
if dandiset.unembargo_in_progress:
Expand Down Expand Up @@ -434,15 +434,22 @@ def users(self, request, dandiset__pk): # noqa: C901
try:
owner_account = SocialAccount.objects.get(user=owner_user)
owner_dict = {'username': owner_account.extra_data['login']}
if 'name' in owner_account.extra_data:
owner_dict['name'] = owner_account.extra_data['name']
owner_dict['name'] = owner_account.extra_data.get('name', None)
owner_dict['email'] = (
owner_account.extra_data['email']
# Only logged-in users can see owners' email addresses
if request.user.is_authenticated and 'email' in owner_account.extra_data
else None
)
owners.append(owner_dict)
except SocialAccount.DoesNotExist:
# Just in case some users aren't using social accounts, have a fallback
owners.append(
{
'username': owner_user.username,
'name': f'{owner_user.first_name} {owner_user.last_name}',
'email': owner_user.email if request.user.is_authenticated else None,
}
)

return Response(owners, status=status.HTTP_200_OK)
1 change: 1 addition & 0 deletions web/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface User {
username: string,
name: string,
admin?: boolean,
email: string,
status: 'INCOMPLETE' | 'PENDING' | 'APPROVED' | 'REJECTED',
approved: boolean,
}
Expand Down
143 changes: 143 additions & 0 deletions web/src/views/DandisetLandingView/ContactDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<template>
<v-menu
offset-y
left
>
<template
#activator="{ on, attrs }"
>
<v-btn
id="contact"
outlined
block
v-bind="attrs"
v-on="on"
>
<v-icon
color="primary"
left
>
mdi-card-account-mail
</v-icon>
<span>Contact</span>
<v-spacer />
<v-icon right>
mdi-chevron-down
</v-icon>
</v-btn>
</template>
<v-card
>
<v-card-title class="pb-0" style="min-width: fit-content;">
Select an e-mail recipient:
</v-card-title>
<v-list>
<v-tooltip
:disabled="!disableDandisetOwnersButton"
open-on-hover
left
>
<template #activator="{ on }">
<div
v-on="on"
>
<v-list-item
:disabled="disableDandisetOwnersButton"
:href="makeTemplate(dandisetOwnerEmails)"
>
<v-icon
color="primary"
left
small
>
mdi-card-account-mail
</v-icon>
Dandiset Owners
</v-list-item>
</div>
</template>
<span v-if="!loggedIn()"> You must be logged in to contact the owner </span>
<span v-if="!dandisetOwnerEmails?.length"> No owner e-mail available </span>
</v-tooltip>
<v-divider />
<v-tooltip
:disabled="!disableContactPersonButton"
open-on-hover
left
>
<template #activator="{ on }">
<div v-on="on">
<v-list-item
:disabled="disableContactPersonButton"
:href="makeTemplate(dandisetContactPersonEmails)"
>
<v-icon
color="primary"
left
small
>
mdi-card-account-mail
</v-icon>
Dandiset Contact Person
</v-list-item>
</div>
</template>
<span> No contact e-mail available </span>
</v-tooltip>
</v-list>
</v-card>
</v-menu>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useDandisetStore } from '@/stores/dandiset';
import { loggedIn } from '@/rest';
import type { User, Person, Organization, Email} from '@/types';
const store = useDandisetStore();
const currentDandiset = computed(() => store.dandiset);
const dandisetOwnerEmails = computed(() => store.owners?.map((owner: User) => owner.email) || []);
const dandisetContactPersonEmails = computed(() =>
currentDandiset.value?.metadata?.contributor?.filter(
(contact: Person | Organization) =>
contact.roleName?.includes("dcite:ContactPerson")
)
.map((contact: Person | Organization) =>
contact.email as Email
)
// Exclude users missing an email
.filter((email?: Email) => email !== undefined)
// Exclude users with an empty email
.filter((email: Email) => email !== '')
|| []
);

const makeTemplate = (contacts: string[]) => {
if (currentDandiset.value === undefined) {
throw new Error('Dandiset is undefined.');
}
if (currentDandiset.value){
const subject = encodeURIComponent(`Regarding Dandiset ${currentDandiset.value.dandiset.identifier} ("${currentDandiset.value.name}")`);
const contact = contacts.join(',');
return `mailto:${contact}?subject=${subject}`;
}
};

const disableContactPersonButton = computed(() => !dandisetContactPersonEmails.value?.length)
const disableDandisetOwnersButton = computed(
// Only logged in users can access owners' emails
() => !loggedIn() || !dandisetOwnerEmails.value?.length
);

</script>
<style scoped>
.v-btn--outlined {
border: thin solid #E0E0E0;
color: #424242;
font-weight: 400;
}
</style>
12 changes: 10 additions & 2 deletions web/src/views/DandisetLandingView/DandisetActions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@
</template>
</DownloadDialog>
</v-row>
<v-row no-gutters>
<v-row
no-gutters
>
<CiteAsDialog>
<template
#activator="{ on }"
>
<v-btn
id="download"
id="cite_as"
outlined
block
v-on="on"
Expand All @@ -63,6 +65,11 @@
</template>
</CiteAsDialog>
</v-row>
<v-row
no-gutters
>
<ContactDialog />
</v-row>
</div>

<!-- Files and Metadata buttons -->
Expand Down Expand Up @@ -151,6 +158,7 @@ import { open as openMeditor } from '@/components/Meditor/state';
import DownloadDialog from './DownloadDialog.vue';
import CiteAsDialog from './CiteAsDialog.vue';
import ShareDialog from './ShareDialog.vue';
import ContactDialog from './ContactDialog.vue';
const store = useDandisetStore();
Expand Down

0 comments on commit cee70a5

Please sign in to comment.