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

Disable notification emails #8911

Merged
merged 20 commits into from
Apr 27, 2023
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
7 changes: 7 additions & 0 deletions changelog/unreleased/enhancement-add-notifications-setting
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Enhancement: Add notification setting to account page

We've added notification setting to the account page,
where the user can turn on or off receiving emails for notifications.

https://github.com/owncloud/web/pull/8911
https://github.com/owncloud/web/issues/8904
257 changes: 183 additions & 74 deletions packages/web-runtime/src/pages/account.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<main id="account" class="oc-height-1-1 oc-m">
<app-loading-spinner v-if="isLoading" />
<main v-else id="account" class="oc-height-1-1 oc-m">
<div class="oc-flex oc-flex-between oc-flex-bottom oc-width-1-1 oc-border-b oc-py">
<h1 id="account-page-title" class="oc-page-title oc-m-rm">{{ pageTitle }}</h1>
<div>
Expand Down Expand Up @@ -68,12 +69,15 @@
/>
</dd>
</div>
<div v-if="isLanguageSupported" class="account-page-info-language oc-mb oc-width-1-2@s">
<div
v-if="isSettingsServiceSupported"
class="account-page-info-language oc-mb oc-width-1-2@s"
>
<dt class="oc-text-normal oc-text-muted" v-text="$gettext('Language')" />
<dd data-testid="language">
<oc-select
v-if="languageOptions"
:model-value="selectedLanguageOption"
:model-value="selectedLanguageValue"
:clearable="false"
:options="languageOptions"
@update:model-value="updateSelectedLanguage"
Expand All @@ -100,45 +104,60 @@
<gdpr-export />
</dd>
</div>
<div v-if="isSettingsServiceSupported" class="account-page-notification oc-mb oc-width-1-2@s">
<dt class="oc-text-normal oc-text-muted" v-text="$gettext('Notifications')" />
<dd data-testid="notification-mails">
<oc-checkbox
:model-value="disableEmailNotificationsValue"
size="large"
:label="$gettext('Receive notification mails')"
data-testid="account-page-notification-mails-checkbox"
@update:model-value="updateDisableEmailNotifications"
/>
</dd>
</div>
</dl>
</main>
</template>

<script lang="ts">
import { mapActions } from 'vuex'
import EditPasswordModal from '../components/EditPasswordModal.vue'
import { computed, defineComponent, onMounted, unref } from 'vue'
import { computed, defineComponent, onMounted, unref, ref } from 'vue'
import {
useAccessToken,
useCapabilityGraphPersonalDataExport,
useCapabilitySpacesEnabled,
useClientService,
useStore
} from 'web-pkg/src/composables'
import { useTask } from 'vue-concurrency'
import axios from 'axios'
import { v4 as uuidV4 } from 'uuid'
import { useGettext } from 'vue3-gettext'
import { setCurrentLanguage } from 'web-runtime/src/helpers/language'
import GdprExport from 'web-runtime/src/components/Account/GdprExport.vue'
import { useConfigurationManager } from 'web-pkg/src/composables/configuration'
import { isPersonalSpaceResource } from 'web-client/src/helpers'
import AppLoadingSpinner from 'web-pkg/src/components/AppLoadingSpinner.vue'

export default defineComponent({
name: 'Personal',
components: {
AppLoadingSpinner,
EditPasswordModal,
GdprExport
},
setup() {
const store = useStore()
const accessToken = useAccessToken({ store })
const language = useGettext()
const { $gettext } = language
const clientService = useClientService()
const configurationManager = useConfigurationManager()
const valuesList = ref()
const bundlesList = ref()
const selectedLanguageValue = ref()
const disableEmailNotificationsValue = ref()

// FIXME: Use graph capability when we have it
const isLanguageSupported = useCapabilitySpacesEnabled()
const isSettingsServiceSupported = useCapabilitySpacesEnabled()
const isChangePasswordEnabled = useCapabilitySpacesEnabled()
const isPersonalDataExportEnabled = useCapabilityGraphPersonalDataExport()
const user = computed(() => {
Expand All @@ -152,113 +171,203 @@ export default defineComponent({
)
})

const loadAccountBundleTask = useTask(function* () {
const loadValuesListTask = useTask(function* () {
try {
const {
data: { values }
} = yield clientService.httpAuthenticated.post('/api/v0/settings/values-list', {
account_uuid: 'me'
})
valuesList.value = values || []
} catch (e) {
console.error(e)
store.dispatch('showMessage', {
title: $gettext('Unable to load account data…'),
status: 'danger'
})
valuesList.value = []
}
}).restartable()

const loadBundlesListTask = useTask(function* () {
try {
const {
data: { bundles }
} = yield axios.post(
'/api/v0/settings/bundles-list',
{},
{
headers: {
authorization: `Bearer ${unref(accessToken)}`,
'X-Request-ID': uuidV4()
}
}
)
return bundles.find((b) => b.extension === 'ocis-accounts')
} = yield clientService.httpAuthenticated.post('/api/v0/settings/bundles-list', {})
bundlesList.value = bundles?.find((b) => b.extension === 'ocis-accounts')
} catch (e) {
console.error(e)
return []
store.dispatch('showMessage', {
title: $gettext('Unable to load account data…'),
status: 'danger'
})
bundlesList.value = []
}
}).restartable()

const accountSettingIdentifier = {
extension: 'ocis-accounts',
bundle: 'profile',
setting: 'language'
}
const languageSetting = computed(() => {
return store.getters.getSettingsValue(accountSettingIdentifier)
const isLoading = computed(() => {
if (!unref(isSettingsServiceSupported)) {
return false
}
return (
loadValuesListTask.isRunning ||
!loadValuesListTask.last ||
loadBundlesListTask.isRunning ||
!loadBundlesListTask.last
)
})

const languageOptions = computed(() => {
const languageOptions = loadAccountBundleTask.last?.value?.settings.find(
(s) => s.name === 'language'
)?.singleChoiceValue.options
const languageOptions = unref(bundlesList)?.settings?.find((s) => s.name === 'language')
?.singleChoiceValue.options
return languageOptions?.map((l) => ({
label: l.displayValue,
value: l.value.stringValue,
default: l.default
}))
})
const selectedLanguageOption = computed(() => {
const current = unref(languageSetting)?.listValue.values[0].stringValue
if (!current) {
return unref(languageOptions).find((o) => o.default)

const groupNames = computed(() => {
if (unref(useCapabilitySpacesEnabled())) {
return unref(user)
.groups.map((group) => group.displayName)
.join(', ')
}
return unref(languageOptions).find((o) => o.value === current)

return unref(user).groups.join(', ')
})
const updateSelectedLanguage = (option) => {
const bundle = loadAccountBundleTask.last?.value

const saveValue = async ({
identifier,
valueOptions
}: {
identifier: string
valueOptions: Record<string, any>
}) => {
const valueId = unref(valuesList)?.find((cV) => cV.identifier.setting === identifier)?.value
?.id

const value = {
bundleId: bundle?.id,
settingId: bundle?.settings.find((s) => s.name === 'language')?.id,
bundleId: unref(bundlesList)?.id,
settingId: unref(bundlesList)?.settings?.find((s) => s.name === identifier)?.id,
resource: { type: 'TYPE_USER' },
listValue: { values: [{ stringValue: option.value }] },
...(unref(languageSetting) && { id: unref(languageSetting).id })
accountUuid: 'me',
...valueOptions,
...(valueId && { id: valueId })
}

axios.post(
'/api/v0/settings/values-save',
{ value: { ...value, accountUuid: 'me' } },
{
headers: {
authorization: `Bearer ${unref(accessToken)}`,
'X-Request-ID': uuidV4()
try {
await clientService.httpAuthenticated.post('/api/v0/settings/values-save', {
value: {
accountUuid: 'me',
...value
}
})

/**
* Edge case: we need to reload the values list to retrieve the valueId if not set,
* otherwise the backend saves multiple entries
*/
if (!valueId) {
loadValuesListTask.perform()
}
)

const newSetting = { identifier: accountSettingIdentifier, value }
store.commit('SET_SETTINGS_VALUE', newSetting)
setCurrentLanguage({ language, languageSetting: newSetting })
return value
} catch (e) {
throw e
}
}
const accountEditLink = computed(() => {
return store.getters.configuration?.options?.accountEditLink
})

const groupNames = computed(() => {
if (unref(useCapabilitySpacesEnabled())) {
return unref(user)
.groups.map((group) => group.displayName)
.join(', ')
const updateSelectedLanguage = async (option) => {
try {
const value = await saveValue({
identifier: 'language',
valueOptions: { listValue: { values: [{ stringValue: option.value }] } }
})
selectedLanguageValue.value = option
setCurrentLanguage({
language,
languageSetting: {
identifier: {
extension: 'ocis-accounts',
bundle: 'profile',
setting: 'language'
},
value
}
})
store.dispatch('showMessage', {
title: $gettext('Language was saved successfully.')
})
} catch (e) {
console.error(e)
store.dispatch('showMessage', {
title: $gettext('Saving language failed…'),
status: 'danger'
})
}
}

return unref(user).groups.join(', ')
})
const updateDisableEmailNotifications = async (option) => {
try {
await saveValue({
identifier: 'disable-email-notifications',
valueOptions: { boolValue: !option }
})
disableEmailNotificationsValue.value = option
store.dispatch('showMessage', {
title: $gettext('Email notifications preference saved successfully.')
})
} catch (e) {
console.error(e)
store.dispatch('showMessage', {
title: $gettext('Unable to save email notifications preference…'),
status: 'danger'
})
}
}

const logoutUrl = computed(() => {
return configurationManager.logoutUrl
})
onMounted(async () => {
if (unref(isSettingsServiceSupported)) {
await loadBundlesListTask.perform()
await loadValuesListTask.perform()

const languageConfiguration = unref(valuesList)?.find(
(cV) => cV.identifier.setting === 'language'
)
selectedLanguageValue.value = languageConfiguration
? unref(languageOptions)?.find(
(lO) => lO.value === languageConfiguration.value?.listValue?.values?.[0]?.stringValue
)
: unref(languageOptions)?.find((o) => o.default)

const disableEmailNotificationsConfiguration = unref(valuesList)?.find(
(cV) => cV.identifier.setting === 'disable-email-notifications'
)

onMounted(() => {
if (unref(isLanguageSupported)) {
loadAccountBundleTask.perform()
disableEmailNotificationsValue.value = disableEmailNotificationsConfiguration
? !disableEmailNotificationsConfiguration.value?.boolValue
: true
}
})

return {
clientService,
languageOptions,
selectedLanguageOption,
selectedLanguageValue,
updateSelectedLanguage,
accountEditLink,
updateDisableEmailNotifications,
accountEditLink: store.getters.configuration?.options?.accountEditLink,
isChangePasswordEnabled,
showGdprExport,
isLanguageSupported,
isSettingsServiceSupported,
groupNames,
user,
logoutUrl
logoutUrl: configurationManager.logoutUrl,
isLoading,
disableEmailNotificationsValue,
loadBundlesListTask,
loadValuesListTask
}
},
data() {
Expand Down
1 change: 0 additions & 1 deletion packages/web-runtime/src/services/auth/userManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,6 @@ export class UserManager extends OidcUserManager {
let roles
;[graphUser, roles] = await Promise.all([graphClient.users.getMe(), this.fetchRoles()])
this.store.commit('SET_ROLES', roles)
this.store.commit('SET_SETTINGS_VALUES', settings)

role = await this.fetchRole({ graphUser, roles })
} else {
Expand Down
2 changes: 0 additions & 2 deletions packages/web-runtime/src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import apps from './apps'
import auth from './auth'
import config from './config'
import user from './user'
import settings from './settings'
import modal from './modal'
import navigation from './navigation'
import spaces from './spaces'
Expand All @@ -24,7 +23,6 @@ export default {
apps,
user,
config,
settings,
modal,
navigation,
runtime
Expand Down
Loading