Skip to content

Commit

Permalink
feat: add notifications settings
Browse files Browse the repository at this point in the history
We've added a new notifications settings section into the account screen. This section allows users to configure what notifications they wish to receive either in-app or via email, when to receive email notifications, and drops the previous notifications toggle.
  • Loading branch information
LukasHirt committed Dec 12, 2024
1 parent e037256 commit 9c510b6
Show file tree
Hide file tree
Showing 9 changed files with 500 additions and 36 deletions.
6 changes: 6 additions & 0 deletions changelog/unreleased/enhancement-add-notifications-settings
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Enhancement: Add notifications settings

We've added a new notifications settings section into the account screen. This section allows users to configure what notifications they wish to receive either in-app or via email, when to receive email notifications, and drops the previous notifications toggle.

https://github.com/owncloud/web/pull/12010
https://github.com/owncloud/web/issues/9248
1 change: 1 addition & 0 deletions packages/web-client/src/ocs/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export interface Capabilities {
}
notifications?: {
'ocs-endpoints'?: string[]
configurable?: boolean
}
core: {
pollinterval?: number
Expand Down
35 changes: 26 additions & 9 deletions packages/web-runtime/src/components/Account/AccountTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@
<h2 class="account-table-title" v-text="title" />
</slot>
<oc-table-simple>
<oc-thead class="oc-invisible-sr">
<oc-thead :class="{ 'oc-invisible-sr': !showHead }">
<oc-tr>
<oc-th v-for="field in fields" :key="field">{{ field }}</oc-th>
<template v-for="field in fields" :key="typeof field === 'string' ? field : field.label">
<oc-th v-if="typeof field === 'string'">{{ field }}</oc-th>
<oc-th
v-else
:align-h="field.alignH || 'left'"
:class="{ 'oc-invisible-sr': field.hidden }"
>{{ field.label }}</oc-th
>
</template>
</oc-tr>
</oc-thead>
<oc-tbody>
Expand All @@ -19,6 +27,12 @@
<script lang="ts">
import { defineComponent } from 'vue'
type AccountTableCell = {
label: string
alignH?: string
hidden?: boolean
}
export default defineComponent({
name: 'AccountTable',
props: {
Expand All @@ -27,9 +41,10 @@ export default defineComponent({
required: true
},
fields: {
type: Array<string>,
type: Array<string | AccountTableCell>,
required: true
}
},
showHead: { type: Boolean, required: false, default: false }
}
})
</script>
Expand Down Expand Up @@ -85,11 +100,13 @@ export default defineComponent({
}
}
td:nth-child(3) {
display: flex;
justify-content: end;
align-items: center;
min-height: var(--oc-size-height-table-row);
@media (min-width: 801px) {
td > .checkbox-cell-wrapper {
display: flex;
justify-content: end;
align-items: center;
min-height: var(--oc-size-height-table-row);
}
}
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useNotificationsSettings'
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { computed, Ref, unref } from 'vue'
import {
getSettingsValue,
SETTINGS_EMAIL_NOTIFICATION_BUNDLE_IDS,
SETTINGS_NOTIFICATION_BUNDLE_IDS,
SettingsBundle,
SettingsValue
} from '../../helpers/settings'

export const useNotificationsSettings = (
valueList: Ref<SettingsValue[]>,
bundle: Ref<SettingsBundle>
) => {
const values = computed(() => {
if (!unref(bundle)) {
return {}
}

return unref(bundle).settings.reduce((acc, curr) => {
if (!SETTINGS_NOTIFICATION_BUNDLE_IDS.includes(curr.id)) {
return acc
}

acc[curr.id] = getSettingsValue(curr, unref(valueList))

return acc
}, {})
})

const options = computed<SettingsBundle['settings']>(() => {
if (!unref(bundle)) {
return []
}

return unref(bundle).settings.filter(({ id }) => SETTINGS_NOTIFICATION_BUNDLE_IDS.includes(id))
})

const emailOptions = computed<SettingsBundle['settings']>(() => {
if (!unref(bundle)) {
return []
}

return unref(bundle).settings.filter(({ id }) =>
SETTINGS_EMAIL_NOTIFICATION_BUNDLE_IDS.includes(id)
)
})

const emailValues = computed(() => {
if (!unref(bundle)) {
return {}
}

return unref(bundle).settings.reduce((acc, curr) => {
if (!SETTINGS_EMAIL_NOTIFICATION_BUNDLE_IDS.includes(curr.id)) {
return acc
}

acc[curr.id] = getSettingsValue(curr, unref(valueList))

return acc
}, {})
})

return { values, options, emailOptions, emailValues }
}
145 changes: 132 additions & 13 deletions packages/web-runtime/src/helpers/settings.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { captureException } from '@sentry/vue'

export interface SettingsValue {
identifier: {
bundle: string
Expand All @@ -18,7 +20,40 @@ export interface SettingsValue {
stringValue: string
}[]
}
collectionValue?: {
values: {
key: string
boolValue: boolean
}[]
}
stringValue?: string
}
}

interface SettingsBundleSetting {
description: string
displayName: string
id: string
name: string
resource: {
type: string
}
singleChoiceValue?: {
options: Record<string, any>[]
}
multiChoiceCollectionValue?: {
options: {
value: {
boolValue: {
default?: boolean
}
}
key: string
displayValue: string
attribute?: 'disabled'
}[]
}
boolValue?: Record<string, any>
}

export interface SettingsBundle {
Expand All @@ -29,19 +64,7 @@ export interface SettingsBundle {
resource: {
type: string
}
settings: {
description: string
displayName: string
id: string
name: string
resource: {
type: string
}
singleChoiceValue?: {
options: Record<string, any>[]
}
boolValue?: Record<string, any>
}[]
settings: SettingsBundleSetting[]
type: string
roleId?: string
}
Expand All @@ -50,3 +73,99 @@ export interface LanguageOption {
label: string
value: string
}

/** IDs of notifications setting bundles */
export enum SettingsNotificationBundle {
ShareCreated = '872d8ef6-6f2a-42ab-af7d-f53cc81d7046',
ShareRemoved = 'd7484394-8321-4c84-9677-741ba71e1f80',
ShareExpired = 'e1aa0b7c-1b0f-4072-9325-c643c89fee4e',
SpaceShared = '694d5ee1-a41c-448c-8d14-396b95d2a918',
SpaceUnshared = '26c20e0e-98df-4483-8a77-759b3a766af0',
SpaceMembershipExpired = '7275921e-b737-4074-ba91-3c2983be3edd',
SpaceDisabled = 'eb5c716e-03be-42c6-9ed1-1105d24e109f',
SpaceDeleted = '094ceca9-5a00-40ba-bb1a-bbc7bccd39ee',
PostprocessingStepFinished = 'fe0a3011-d886-49c8-b797-33d02fa426ef',
ScienceMeshInviteTokenGenerated = 'b441ffb1-f5ee-4733-a08f-48d03f6e7f22'
}

/** IDs of email notifications setting bundles */
export enum SettingsEmailNotificationBundle {
EmailSendingInterval = '08dec2fe-3f97-42a9-9d1b-500855e92f25'
}

// We need the type specified here because e.g. includes method would otherwise complain about it
export const SETTINGS_NOTIFICATION_BUNDLE_IDS: string[] = [
SettingsNotificationBundle.ShareCreated,
SettingsNotificationBundle.ShareRemoved,
SettingsNotificationBundle.ShareExpired,
SettingsNotificationBundle.SpaceShared,
SettingsNotificationBundle.SpaceUnshared,
SettingsNotificationBundle.SpaceMembershipExpired,
SettingsNotificationBundle.SpaceDisabled,
SettingsNotificationBundle.SpaceDeleted,
SettingsNotificationBundle.PostprocessingStepFinished,
SettingsNotificationBundle.ScienceMeshInviteTokenGenerated
]

export const SETTINGS_EMAIL_NOTIFICATION_BUNDLE_IDS: string[] = [
SettingsEmailNotificationBundle.EmailSendingInterval
]

function getSettingsDefaultValue(setting: SettingsBundleSetting) {
if (setting.singleChoiceValue) {
const [option] = setting.singleChoiceValue.options

return {
value: option.value.stringValue,
displayValue: option.displayValue
}
}

if (setting.multiChoiceCollectionValue) {
return setting.multiChoiceCollectionValue.options.reduce((acc, curr) => {
acc[curr.key] = curr.value.boolValue.default

return acc
}, {})
}

const error = new Error('Unsupported setting value')

console.error(error)
captureException(error)

return null
}

export function getSettingsValue(
setting: SettingsBundleSetting,
valueList: SettingsValue[]
): boolean | string | null | { [key: string]: boolean } | { value: string; displayValue: string } {
const { value } = valueList.find((v) => v.identifier.setting === setting.name) || {}

if (!value) {
return getSettingsDefaultValue(setting)
}

if (value.collectionValue) {
return setting.multiChoiceCollectionValue.options.reduce((acc, curr) => {
const val = value.collectionValue.values.find((v) => v.key === curr.key)

if (val) {
acc[curr.key] = val.boolValue
return acc
}

acc[curr.key] = curr.value.boolValue.default
return acc
}, {})
}

if (value.stringValue) {
const option = setting.singleChoiceValue.options.find(
(o) => o.value.stringValue === value.stringValue
)

return { value: value.stringValue, displayValue: option?.displayValue || value.stringValue }
}
}
Loading

0 comments on commit 9c510b6

Please sign in to comment.