Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add notifications settings #12010

Merged
merged 1 commit into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
38 changes: 28 additions & 10 deletions packages/web-runtime/src/components/Account/AccountTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@
<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 +28,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,15 +42,16 @@ export default defineComponent({
required: true
},
fields: {
type: Array<string>,
type: Array<string | AccountTableCell>,
required: true
}
},
showHead: { type: Boolean, required: false, default: false }
}
})
</script>

<style lang="scss">
@media (max-width: 800px) {
@media (max-width: $oc-breakpoint-small-max) {
.account-table {
tr {
display: block;
Expand Down Expand Up @@ -85,11 +101,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: $oc-breakpoint-medium-default) {
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