From 6eed0487c7233e7b6e1fad37fd04dfcb73728215 Mon Sep 17 00:00:00 2001 From: Lukas Hirt Date: Thu, 5 Dec 2024 12:05:28 +0100 Subject: [PATCH] feat: 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. --- .../enhancement-add-notifications-settings | 6 + packages/web-client/src/ocs/capabilities.ts | 1 + .../src/components/Account/AccountTable.vue | 38 +++- .../notificationsSettings/index.ts | 1 + .../useNotificationsSettings.ts | 65 ++++++ packages/web-runtime/src/helpers/settings.ts | 145 ++++++++++-- packages/web-runtime/src/pages/account.vue | 212 +++++++++++++++++- .../pages/__snapshots__/account.spec.ts.snap | 1 + .../tests/unit/pages/account.spec.ts | 70 +++++- 9 files changed, 502 insertions(+), 37 deletions(-) create mode 100644 changelog/unreleased/enhancement-add-notifications-settings create mode 100644 packages/web-runtime/src/composables/notificationsSettings/index.ts create mode 100644 packages/web-runtime/src/composables/notificationsSettings/useNotificationsSettings.ts diff --git a/changelog/unreleased/enhancement-add-notifications-settings b/changelog/unreleased/enhancement-add-notifications-settings new file mode 100644 index 00000000000..9903640a8ad --- /dev/null +++ b/changelog/unreleased/enhancement-add-notifications-settings @@ -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 diff --git a/packages/web-client/src/ocs/capabilities.ts b/packages/web-client/src/ocs/capabilities.ts index 3719dd12a8a..57408cbef90 100644 --- a/packages/web-client/src/ocs/capabilities.ts +++ b/packages/web-client/src/ocs/capabilities.ts @@ -72,6 +72,7 @@ export interface Capabilities { } notifications?: { 'ocs-endpoints'?: string[] + configurable?: boolean } core: { pollinterval?: number diff --git a/packages/web-runtime/src/components/Account/AccountTable.vue b/packages/web-runtime/src/components/Account/AccountTable.vue index 27c1dfeb16a..d47bad4ae5a 100644 --- a/packages/web-runtime/src/components/Account/AccountTable.vue +++ b/packages/web-runtime/src/components/Account/AccountTable.vue @@ -4,9 +4,18 @@

- + - {{ field }} + @@ -19,6 +28,12 @@ diff --git a/packages/web-runtime/src/composables/notificationsSettings/index.ts b/packages/web-runtime/src/composables/notificationsSettings/index.ts new file mode 100644 index 00000000000..5c88d17cb8f --- /dev/null +++ b/packages/web-runtime/src/composables/notificationsSettings/index.ts @@ -0,0 +1 @@ +export * from './useNotificationsSettings' diff --git a/packages/web-runtime/src/composables/notificationsSettings/useNotificationsSettings.ts b/packages/web-runtime/src/composables/notificationsSettings/useNotificationsSettings.ts new file mode 100644 index 00000000000..7f0a46ccdcf --- /dev/null +++ b/packages/web-runtime/src/composables/notificationsSettings/useNotificationsSettings.ts @@ -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, + bundle: Ref +) => { + 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(() => { + if (!unref(bundle)) { + return [] + } + + return unref(bundle).settings.filter(({ id }) => SETTINGS_NOTIFICATION_BUNDLE_IDS.includes(id)) + }) + + const emailOptions = computed(() => { + 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 } +} diff --git a/packages/web-runtime/src/helpers/settings.ts b/packages/web-runtime/src/helpers/settings.ts index db944bea92c..bd098558b19 100644 --- a/packages/web-runtime/src/helpers/settings.ts +++ b/packages/web-runtime/src/helpers/settings.ts @@ -1,3 +1,5 @@ +import { captureException } from '@sentry/vue' + export interface SettingsValue { identifier: { bundle: string @@ -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[] + } + multiChoiceCollectionValue?: { + options: { + value: { + boolValue: { + default?: boolean + } + } + key: string + displayValue: string + attribute?: 'disabled' + }[] + } + boolValue?: Record } export interface SettingsBundle { @@ -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[] - } - boolValue?: Record - }[] + settings: SettingsBundleSetting[] type: string roleId?: string } @@ -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 } + } +} diff --git a/packages/web-runtime/src/pages/account.vue b/packages/web-runtime/src/pages/account.vue index 2ccb54c45b6..0d725178d96 100644 --- a/packages/web-runtime/src/pages/account.vue +++ b/packages/web-runtime/src/pages/account.vue @@ -128,7 +128,10 @@ -