Skip to content

Commit

Permalink
New notifications system
Browse files Browse the repository at this point in the history
This enables users to set notifications on shares, in order to know when someone edits a file. It's missing the UI integration for displaying, for now it only generates emails.
  • Loading branch information
javfg authored and diocas committed Jun 1, 2023
1 parent a1f92ac commit 9ffff2d
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 12 deletions.
3 changes: 2 additions & 1 deletion packages/web-app-files/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"description": "ownCloud web files",
"license": "AGPL-3.0",
"dependencies": {
"mark.js": "^8.11.1"
"mark.js": "^8.11.1",
"email-validator": "^2.0.4"
},
"devDependencies": {
"@jest/globals": "29.3.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export default defineComponent({
required: true
}
},
emits: ['expirationDateChanged', 'removeShare', 'showAccessDetails'],
emits: ['expirationDateChanged', 'notifyShare', 'removeShare', 'showAccessDetails'],
data: function () {
return {
enteredExpirationDate: null
Expand All @@ -104,6 +104,13 @@ export default defineComponent({
}
return [
...result,
{
title: this.$gettext('Notify via mail'),
method: this.notifyShare,
enabled: true,
icon: 'mail',
class: 'notify-via-mail'
},
{
title: this.$gettext('Remove share'),
method: this.removeShare,
Expand Down Expand Up @@ -248,6 +255,9 @@ export default defineComponent({
},
showAccessDetails() {
this.$emit('showAccessDetails')
},
notifyShare() {
this.$emit('notifyShare')
}
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
:share-types="selectedCollaborators.map((c) => c.value.shareType)"
@option-change="collaboratorExpiryChanged"
/>
<oc-checkbox v-model="notifyEnabled" :value="false" label="Notify via mail" />
<oc-button v-if="saving" key="new-collaborator-saving-button" :disabled="true">
<oc-spinner :aria-label="$gettext('Creating share')" size="small" />
<span :aria-hidden="true" v-text="$gettext(saveButtonLabel)" />
Expand Down Expand Up @@ -143,7 +144,9 @@ export default defineComponent({
customPermissions: null,
saving: false,
expirationDate: null,
searchQuery: ''
searchQuery: '',
notifyEnabled: false,
formData: new URLSearchParams()
}
},
computed: {
Expand Down Expand Up @@ -298,9 +301,11 @@ export default defineComponent({
const saveQueue = new PQueue({ concurrency: 4 })
const savePromises = []
this.selectedCollaborators.forEach((collaborator) => {
this.selectedCollaborators.forEach((collaborator, i) => {
savePromises.push(
saveQueue.add(() => {
const collaborators = this.selectedCollaborators
const bitmask = this.selectedRole.hasCustomPermissions
? SharePermissions.permissionsToBitmask(this.customPermissions)
: SharePermissions.permissionsToBitmask(
Expand All @@ -327,7 +332,8 @@ export default defineComponent({
permissions: bitmask,
role: this.selectedRole,
expirationDate: this.expirationDate,
storageId: this.resource.fileId || this.resource.id
storageId: this.resource.fileId || this.resource.id,
notify: this.notifyEnabled
})
})
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
@expiration-date-changed="shareExpirationChanged"
@remove-share="removeShare"
@show-access-details="showAccessDetails"
@notify-share="$_ocCollaborators_notifyShare_trigger"
/>
<oc-info-drop
ref="accessDetailsDrop"
Expand Down Expand Up @@ -137,7 +138,6 @@ export default defineComponent({
default: null
}
},
emits: ['onDelete'],
setup() {
return {
hasResharing: useCapabilityFilesSharingResharing(),
Expand Down Expand Up @@ -318,14 +318,54 @@ export default defineComponent({
}
},
methods: {
...mapActions(['showMessage']),
...mapActions('Files', ['changeShare']),
...mapActions(['createModal', 'hideModal', 'showMessage']),
...mapActions('Files', ['changeShare', 'notifyShare']),
...mapActions('runtime/spaces', ['changeSpaceMember']),
removeShare() {
this.$emit('onDelete', this.share)
},
$_ocCollaborators_notifyShare_trigger() {
const modal = {
variation: 'warning',
icon: 'mail-send',
title: this.$gettext('Send a reminder'),
cancelText: this.$gettext('Cancel'),
confirmText: this.$gettext('Send'),
message: this.$gettext('Are you sure you want to send a reminder about this share?'),
hasInput: false,
onCancel: this.hideModal,
onConfirm: () => this.$_ocCollaborators_notifyShare()
}
this.createModal(modal)
},
async $_ocCollaborators_notifyShare() {
try {
const response = await this.notifyShare({
client: this.$client,
share: this.share,
})
this.showMessage({
title: this.$gettext('Success'),
desc: `${this.$gettext('Email reminder sent to')} ${response[0]}`,
status: 'success'
})
} catch (error) {
console.error(error)
this.showMessage({
title: this.$gettext('An error occurred'),
desc: this.$gettext('Email notification could not be sent'),
status: 'danger'
})
} finally {
this.hideModal()
}
},
showAccessDetails() {
this.$refs.accessDetailsDrop.$refs.drop.show()
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,8 @@ export default defineComponent({
permissions: link.permissions.toString(),
quicklink: link.quicklink,
name: link.name,
notifyUploads: link.notifyUploads,
notifyUploadsExtraRecipients: link.notifyUploadsExtraRecipients,
spaceRef: this.resource.fileId,
...(this.currentStorageId && {
storageId: this.currentStorageId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@
v-text="$gettext(currentLinkRoleLabel)"
/>
</p>
<oc-checkbox
v-if="hasNotifications && isCurrentLinkRoleUploader()"
:model-value="currentLinkNotifyUploads"
:label="notifyUploadsLabel"
@input="toggleNotifyUploads()"
/>
<div :class="{ 'oc-pr-s': !isModifiable }" class="details-buttons">
<oc-button
v-if="link.indirect"
Expand Down Expand Up @@ -97,6 +103,13 @@
name="calendar-event"
fill-type="line"
/>
<oc-icon
v-if="currentLinkNotifyUploadsExtraRecipients"
v-oc-tooltip="notifyUploadsExtraRecipientsTooltip"
:aria-label="notifyUploadsExtraRecipientsTooltip"
name="mail-add"
fill-type="line"
/>
<div v-if="isModifiable">
<oc-button
:id="`edit-public-link-dropdown-toggl-${link.id}`"
Expand Down Expand Up @@ -173,19 +186,22 @@
</template>

<script lang="ts">
import * as EmailValidator from 'email-validator'
import { basename } from 'path'
import { DateTime } from 'luxon'
import { mapActions, mapGetters } from 'vuex'
import { createLocationSpaces } from '../../../../router'
import {
linkRoleInternalFile,
linkRoleInternalFolder,
linkRoleUploaderFolder,
LinkShareRoles,
ShareRole
} from 'web-client/src/helpers/share'
import { defineComponent, inject, PropType } from 'vue'
import { formatDateFromDateTime, formatRelativeDateFromDateTime } from 'web-pkg/src/helpers'
import { Resource, SpaceResource } from 'web-client/src/helpers'
import { useCapabilityGroupBasedCapabilities } from 'web-pkg/src/composables'
import { createFileRouteOptions } from 'web-pkg/src/helpers/router'
export default defineComponent({
Expand Down Expand Up @@ -223,7 +239,11 @@ export default defineComponent({
},
emits: ['removePublicLink', 'updateLink'],
setup() {
return { space: inject<Resource>('space'), resource: inject<Resource>('resource') }
return {
space: inject<Resource>('space'),
resource: inject<Resource>('resource'),
hasNotifications: useCapabilityGroupBasedCapabilities().value.includes('notifications')
}
},
data() {
return {
Expand Down Expand Up @@ -319,6 +339,30 @@ export default defineComponent({
})
}
if (this.isCurrentLinkRoleUploader && this.currentLinkNotifyUploads) {
result.push({
id: 'add-notify-uploads-extra-recipients',
title: this.notifyUploadsExtraRecipientsMenuEntry,
icon: 'mail-add',
method: this.showNotifyUploadsExtraRecipientsModal
})
}
if (this.currentLinkNotifyUploadsExtraRecipients) {
result.push({
id: 'remove-notify-uploads-extra-recipients',
title: this.$gettext('Remove third party notification'),
icon: 'mail-close',
method: () =>
this.updateLink({
link: {
...this.link,
notifyUploadsExtraRecipients: ''
}
})
})
}
return result
},
Expand Down Expand Up @@ -387,6 +431,25 @@ export default defineComponent({
isAliasLink() {
return [linkRoleInternalFolder, linkRoleInternalFile].includes(this.currentLinkRole)
},
currentLinkNotifyUploads() {
return this.link.notifyUploads
},
currentLinkNotifyUploadsExtraRecipients() {
return this.link.notifyUploadsExtraRecipients
},
notifyUploadsLabel() {
return this.$gettext('Notify me about uploads')
},
notifyUploadsExtraRecipientsTooltip() {
return this.$gettext('Uploads will be notified to a third party')
},
notifyUploadsExtraRecipientsMenuEntry() {
if (this.currentLinkNotifyUploadsExtraRecipients) {
return this.$gettext('Edit third party notification')
}
return this.$gettext('Notify a third party about uploads')
}
},
watch: {
Expand All @@ -410,6 +473,22 @@ export default defineComponent({
this.$emit('updateLink', { link, onSuccess })
dropRef.hide()
},
toggleNotifyUploads() {
if (this.currentLinkNotifyUploads) {
this.$emit('updateLink', {
link: { ...this.link, notifyUploads: false, notifyUploadsExtraRecipients: '' }
})
} else {
this.$emit('updateLink', { link: { ...this.link, notifyUploads: true } })
}
},
isCurrentLinkRoleUploader() {
return (
LinkShareRoles.getByBitmask(parseInt(this.link.permissions), this.isFolderShare).bitmask(
false
) === linkRoleUploaderFolder.bitmask(false)
)
},
deleteLink() {
this.$emit('removePublicLink', { link: this.link })
this.$refs.editPublicLinkDropdown.hide()
Expand Down Expand Up @@ -472,6 +551,50 @@ export default defineComponent({
}
this.createModal(modal)
},
showNotifyUploadsExtraRecipientsModal() {
const modal = {
variation: 'passive',
icon: 'mail-add',
title: this.$gettext('Notify a third party about uploads'),
cancelText: this.$gettext('Cancel'),
confirmText: this.$gettext('Apply'),
hasInput: true,
inputDescription: this.$gettext(
'When a file is uploaded, this address will be notified as well.'
),
inputValue: this.currentLinkNotifyUploadsExtraRecipients,
inputLabel: this.$gettext('Email address'),
inputType: 'email',
onCancel: this.hideModal,
onInput: (value) => this.checkEmailValid(value),
onConfirm: (value) => {
this.updateLink({
link: {
...this.link,
notifyUploadsExtraRecipients: value
},
onSuccess: () => {
this.hideModal()
}
})
}
}
this.createModal(modal)
},
checkEmailValid(email) {
if (!EmailValidator.validate(email)) {
return this.setModalInputErrorMessage(this.$gettext('Email is invalid'))
}
if (email === '') {
return this.setModalInputErrorMessage(this.$gettext("Email can't be empty"))
}
return this.setModalInputErrorMessage(null)
}
}
})
Expand Down
7 changes: 6 additions & 1 deletion packages/web-app-files/src/helpers/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,12 @@ function _buildLink(link): Share {
parent: link.file_parent,
source: link.file_source,
target: link.file_target
}
},
notifyUploads: link.notify_uploads === 'true',
notifyUploadsExtraRecipients:
typeof link.notify_uploads_extra_recipients === 'string'
? link.notify_uploads_extra_recipients
: null
}
}

Expand Down
Loading

0 comments on commit 9ffff2d

Please sign in to comment.