diff --git a/components/dashboard/src/components/CheckBox.tsx b/components/dashboard/src/components/CheckBox.tsx index c426371870ffef..77c97b9e9e78b3 100644 --- a/components/dashboard/src/components/CheckBox.tsx +++ b/components/dashboard/src/components/CheckBox.tsx @@ -11,7 +11,7 @@ function CheckBox(props: { desc: string, checked: boolean, disabled?: boolean, - onChange: (e: React.ChangeEvent) => void + onChange?: (e: React.ChangeEvent) => void }) { const inputProps: React.InputHTMLAttributes = { checked: props.checked, diff --git a/components/dashboard/src/index.css b/components/dashboard/src/index.css index 454f44720017f5..a1f3c0bf7169f9 100644 --- a/components/dashboard/src/index.css +++ b/components/dashboard/src/index.css @@ -80,6 +80,9 @@ input[type=search] { @apply border-0 dark:bg-transparent; } + input[type=checkbox] { + @apply disabled:opacity-50 + } progress { @apply h-2 rounded; diff --git a/components/dashboard/src/service/service-mock.ts b/components/dashboard/src/service/service-mock.ts index de325131e1da52..7779f5fad35c50 100644 --- a/components/dashboard/src/service/service-mock.ts +++ b/components/dashboard/src/service/service-mock.ts @@ -14,7 +14,6 @@ const u1: User = { "avatarUrl": "https://avatars.githubusercontent.com/u/10137?v=4", "name": "gp-test", "fullName": "Alex", - "allowsMarketingCommunication": true, "identities": [ { "authProviderId": "Public-GitHub", @@ -28,6 +27,10 @@ const u1: User = { whatsNewSeen: { "April-2021": "true", "June-2021": "true", + }, + emailNotificationSettings: { + allowsChangelogMail: true, + allowsDevXMail: true } } } diff --git a/components/dashboard/src/settings/Notifications.tsx b/components/dashboard/src/settings/Notifications.tsx index a425d6f463066b..2db1994e39c754 100644 --- a/components/dashboard/src/settings/Notifications.tsx +++ b/components/dashboard/src/settings/Notifications.tsx @@ -13,51 +13,73 @@ import settingsMenu from "./settings-menu"; export default function Notifications() { const { user, setUser } = useContext(UserContext); + const [isChangelogMail, setChangelogMail] = useState(!!user?.additionalData?.emailNotificationSettings?.allowsChangelogMail); + const [isDevXMail, setDevXMail] = useState(!!user?.additionalData?.emailNotificationSettings?.allowsDevXMail); - const [isTransactionalMail, setTransactionMail] = useState(!user?.additionalData?.emailNotificationSettings?.disallowTransactionalEmails); - const toggleTransactionalMail = async () => { - if (user) { - user.additionalData = { - ...{ + const toggleChangelogMail = async () => { + if (user && user.additionalData && user.additionalData.emailNotificationSettings) { + const newIsChangelogMail = !isChangelogMail; + user.additionalData.emailNotificationSettings.allowsChangelogMail = newIsChangelogMail; + await getGitpodService().server.updateLoggedInUser({ + additionalData: { ...user.additionalData, emailNotificationSettings: { - ...user.additionalData?.emailNotificationSettings, - disallowTransactionalEmails: isTransactionalMail + ...user.additionalData.emailNotificationSettings, + allowsChangelogMail: newIsChangelogMail } } - } - await getGitpodService().server.updateLoggedInUser({ - additionalData: user.additionalData }); + await getGitpodService().server.trackEvent({ + event: "notification_change", + properties: { "unsubscribed_changelog": !newIsChangelogMail } + }) setUser(user); - setTransactionMail(!isTransactionalMail); + setChangelogMail(newIsChangelogMail); } - }; + } - const [isMarketingMail, setMarketingMail] = useState(!!user?.allowsMarketingCommunication); - const toggleMarketingMail = async () => { - if (user) { - user.allowsMarketingCommunication = !isMarketingMail; + const toggleDevXMail = async () => { + if (user && user.additionalData && user.additionalData.emailNotificationSettings) { + const newIsDevXMail = !isDevXMail + user.additionalData.emailNotificationSettings.allowsDevXMail = newIsDevXMail; await getGitpodService().server.updateLoggedInUser({ - allowsMarketingCommunication: user.allowsMarketingCommunication + additionalData: { + ...user.additionalData, + emailNotificationSettings: { + ...user.additionalData.emailNotificationSettings, + allowsDevXMail: newIsDevXMail + } + } }); + await getGitpodService().server.trackEvent({ + event: "notification_change", + properties: { "unsubscribed_devx": !newIsDevXMail } + }) setUser(user); - setMarketingMail(!isMarketingMail); + setDevXMail(newIsDevXMail); } } - return
+ + return ( +

Email Notification Preferences

+ title="Account Notifications [required]" + desc="Receive essential emails about changes to your account" + checked={true} + disabled={true} /> + + title="Developer Experience & Product Tips" + desc="Bring back joy and speed to your workflows" + checked={isDevXMail} + onChange={toggleDevXMail} />
-
; -} +
+ ) +} \ No newline at end of file diff --git a/components/ee/payment-endpoint/src/accounting/account-service.spec.db.ts b/components/ee/payment-endpoint/src/accounting/account-service.spec.db.ts index b3b71e1191e023..c603a6ec6d5c42 100644 --- a/components/ee/payment-endpoint/src/accounting/account-service.spec.db.ts +++ b/components/ee/payment-endpoint/src/accounting/account-service.spec.db.ts @@ -67,13 +67,18 @@ const end = new Date(Date.UTC(2000, 2, 1)).toISOString(); id: 'Sven', creationDate: start, fullName: 'Sven', - allowsMarketingCommunication: false, identities: [{ authProviderId: 'github.com', authId: 'Sven', authName: 'Sven', tokens: [] - }] + }], + additionalData: { + emailNotificationSettings: { + allowsChangelogMail: true, + allowsDevXMail: true + } + } }); await this.workspaceDb.store({ id: '1', diff --git a/components/gitpod-db/src/typeorm/entity/db-user.ts b/components/gitpod-db/src/typeorm/entity/db-user.ts index 96921c37dd51a3..956e73282d7911 100644 --- a/components/gitpod-db/src/typeorm/entity/db-user.ts +++ b/components/gitpod-db/src/typeorm/entity/db-user.ts @@ -50,11 +50,6 @@ export class DBUser implements User { @JoinColumn() identities: DBIdentity[]; - @Column({ - default: false - }) - allowsMarketingCommunication: boolean; - @Column({ default: false }) diff --git a/components/gitpod-db/src/typeorm/migration/1628674695873-DeprecateAllowsMarketingCommunication.ts b/components/gitpod-db/src/typeorm/migration/1628674695873-DeprecateAllowsMarketingCommunication.ts new file mode 100644 index 00000000000000..14200d16c2fce4 --- /dev/null +++ b/components/gitpod-db/src/typeorm/migration/1628674695873-DeprecateAllowsMarketingCommunication.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2021 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import {MigrationInterface, QueryRunner} from "typeorm"; +import { columnExists } from "./helper/helper"; + +export class DeprecateAllowsMarketingCommunication1628674695873 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + if (await columnExists(queryRunner, "d_b_user", "allowsMarketingCommunication")) { + await queryRunner.query("UPDATE d_b_user set additionalData = JSON_MERGE_PATCH(IFNULL(additionalData, '{}'), JSON_SET('{\"emailNotificationSettings\":{\"allowsChangelogMail\":true}}', '$.emailNotificationSettings.allowsChangelogMail', IF(allowsMarketingCommunication, 'true', 'false')))"); + await queryRunner.query("ALTER TABLE d_b_user DROP COLUMN allowsMarketingCommunication"); + } + } + + public async down(queryRunner: QueryRunner): Promise { + if (!(await columnExists(queryRunner, "d_b_user", "allowsMarketingCommunication"))) { + await queryRunner.query("ALTER TABLE d_b_user ADD COLUMN allowsMarketingCommunication tinyint(4) NOT NULL DEFAULT '0'"); + await queryRunner.query("UPDATE d_b_user set allowsMarketingCommunication = IF(additionalData->>'$.emailNotificationSettings.allowsChangelogMail'='true', 1, 0)"); + } + } +} diff --git a/components/gitpod-db/src/typeorm/user-db-impl.ts b/components/gitpod-db/src/typeorm/user-db-impl.ts index 357ea2f76eeb8a..5fbe7357f78e04 100644 --- a/components/gitpod-db/src/typeorm/user-db-impl.ts +++ b/components/gitpod-db/src/typeorm/user-db-impl.ts @@ -77,8 +77,10 @@ export class TypeORMUserDBImpl implements UserDB { id: uuidv4(), creationDate: new Date().toISOString(), identities: [], - allowsMarketingCommunication: true, - additionalData: { ideSettings: { defaultIde: 'code' } }, + additionalData: { + ideSettings: { defaultIde: 'code' }, + emailNotificationSettings: { allowsChangelogMail: true, allowsDevXMail: true } + }, }; await this.storeUser(user); return user; diff --git a/components/gitpod-db/src/user-db.ts b/components/gitpod-db/src/user-db.ts index f82fbaa3739417..91a59dfc81b2a4 100644 --- a/components/gitpod-db/src/user-db.ts +++ b/components/gitpod-db/src/user-db.ts @@ -4,7 +4,7 @@ * See License-AGPL.txt in the project root for license information. */ -import { GitpodToken, GitpodTokenType, Identity, IdentityLookup, Token, TokenEntry, User, UserEnvVar } from "@gitpod/gitpod-protocol"; +import { AdditionalUserData, GitpodToken, GitpodTokenType, Identity, IdentityLookup, Token, TokenEntry, User, UserEnvVar } from "@gitpod/gitpod-protocol"; import { OAuthTokenRepository, OAuthUserRepository } from "@jmondi/oauth2-server"; import { Repository } from "typeorm"; import { DBUser } from "./typeorm/entity/db-user"; @@ -125,6 +125,6 @@ export interface OwnerAndRepo { repo: string } -export type UserEmailContact = Pick & { - primaryEmail: string -} +export type UserEmailContact = Pick + & { primaryEmail: string } + & { additionalData?: Pick } diff --git a/components/gitpod-protocol/src/protocol.ts b/components/gitpod-protocol/src/protocol.ts index 830626fe40e1e0..ee9c3c2f03294c 100644 --- a/components/gitpod-protocol/src/protocol.ts +++ b/components/gitpod-protocol/src/protocol.ts @@ -28,8 +28,6 @@ export interface User { identities: Identity[] - allowsMarketingCommunication: boolean; - /** * Whether the user has been blocked to use our service, because of TOS violation for example. * Optional for backwards compatibility. @@ -109,7 +107,8 @@ export interface AdditionalUserData { } export interface EmailNotificationSettings { - disallowTransactionalEmails?: boolean; + allowsChangelogMail?: boolean; + allowsDevXMail?: boolean; } export type IDESettings = { diff --git a/components/server/src/dev/dev-data.ts b/components/server/src/dev/dev-data.ts index 3dc44c7ce77f99..52ee4a267f2133 100644 --- a/components/server/src/dev/dev-data.ts +++ b/components/server/src/dev/dev-data.ts @@ -30,7 +30,12 @@ export namespace DevData { primaryEmail: "somefox@gitpod.io" } ], - allowsMarketingCommunication: true + additionalData: { + emailNotificationSettings: { + allowsChangelogMail: true, + allowsDevXMail: true + } + } } } diff --git a/components/server/src/user/user-controller.ts b/components/server/src/user/user-controller.ts index 2e7668a1979fbc..12a8491cd0efe2 100644 --- a/components/server/src/user/user-controller.ts +++ b/components/server/src/user/user-controller.ts @@ -512,7 +512,8 @@ export class UserController { "name": user.identities[0].authName, "full_name": user.fullName, "created_at": user.creationDate, - "unsubscribed": !user.allowsMarketingCommunication, + "unsubscribed_changelog": !user.additionalData?.emailNotificationSettings?.allowsChangelogMail, + "unsubscribed_devx": !user.additionalData?.emailNotificationSettings?.allowsDevXMail, "blocked": user.blocked } }); diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index 82007c8fc3108b..d5d9e40b634f5c 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -270,22 +270,13 @@ export class GitpodServerImpl