Skip to content

Commit

Permalink
Merge pull request #15933 from CartoDB/email-settings
Browse files Browse the repository at this point in the history
Email settings section in Account page to manage notifications
  • Loading branch information
VictorVelarde authored Nov 19, 2020
2 parents ffa1d77 + 8244d4f commit 06fe379
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 11 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Development

### Features
* Email notifications toggle API endpoint [#15930](https://github.com/CartoDB/cartodb/pull/15930)
* New Email settings section in Account page to manage notifications [#15933](https://github.com/CartoDB/cartodb/pull/15933)

### Bug fixes / enhancements
* Fix BigQuery connector not importing 0-bytes-processed datasets [#15916](https://github.com/CartoDB/cartodb/pull/15916)
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/carto/api/email_notifications_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ def load_notifications

def decorate_notifications
payload = {}
Carto::UserEmailNotification::VALID_NOTIFICATIONS.map { |n| payload[n] = true }

@notifications.each do |notification|
payload[notification.notification] = notification.enabled
end
Expand Down
10 changes: 10 additions & 0 deletions assets/stylesheets/common/account_forms.scss
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ $sLabel-width: 140px;
margin-bottom: 16px;
}

.FormAccount-row--mediumMarginBottom {
margin-bottom: 24px;
}

.FormAccount-row--wideMarginBottom {
margin-bottom: 100px;
}
Expand Down Expand Up @@ -215,6 +219,12 @@ $sLabel-width: 140px;
align-items: flex-start;
}

.FormAccount-rowData--listItemWithAction {
justify-content: space-between;
background: $cStructure-grayBkg;
padding: 11px 11px 10px 12px;
}

.FormAccount-planTag {
padding: 5px 10px;
border-radius: 4px;
Expand Down
20 changes: 20 additions & 0 deletions lib/assets/javascripts/carto-node/lib/clients/authenticated.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,26 @@ class AuthenticatedClient extends PublicClient {
}
};
}

/**
* API to enable/disable email notifications, such as DO notifications
*/
emailNotifications () {
const notificationsURLParts = ['api/v3/email_notifications'];
return {
get: (callback) => {
return this.get(notificationsURLParts, callback);
},

set: (notifications, callback) => {
const opts = {
data: JSON.stringify({ notifications }),
dataType: 'json'
};
return this.put(notificationsURLParts, opts, callback);
}
};
}
}

module.exports = AuthenticatedClient;
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ module.exports = CoreView.extend({
events: {
'click .js-save': '_onClickSave',
'submit form': '_onClickSave',
'change .js-toggle-mfa': '_onToggleMfa'
'change .js-toggle-mfa': '_onToggleMfa',
'change .js-toggle-notification': '_onToggleNotification'
},

initialize: function (options) {
Expand All @@ -40,6 +41,7 @@ module.exports = CoreView.extend({
_initModels: function () {
this._errors = this.options.errors || {};
this.add_related_model(this._renderModel);
this._getNotifications();
},

_initBinds: function () {
Expand Down Expand Up @@ -75,6 +77,7 @@ module.exports = CoreView.extend({
services: this._getField('services') || [],
mfaEnabled: this._getField('mfa_configured'),
licenseExpiration: this._formatLicenseExpiration(),
notifications: this._notifications || {},
view: this
};

Expand Down Expand Up @@ -133,22 +136,30 @@ module.exports = CoreView.extend({
_onClickSave: function (event) {
this.killEvent(event);

// updated user info
const origin = this._getUserFields();
const destination = this._getDestinationValues();
const destinationKeys = _.keys(destination);
const differenceKeys = _.filter(destinationKeys, key =>
origin[key] !== destination[key]
);

const user = _.pick(destination, differenceKeys);

// updated notifications info
const notifications = this._getNotificationValuesFromUI();

if (!this._userModel.get('needs_password_confirmation')) {
return this._updateUser(user);
this._updateUser(user);
this._updateNotifications(notifications);
return;
}

PasswordValidatedForm.showPasswordModal({
modalService: this._modals,
onPasswordTyped: (password) => this._updateUser(user, password),
onPasswordTyped: (password) => {
this._updateUser(user, password);
this._updateNotifications(notifications);
},
updatePassword: destination.new_password !== '' && destination.confirm_password !== ''
});
},
Expand All @@ -161,6 +172,18 @@ module.exports = CoreView.extend({
this._mfaLabel().html(newLabel);
},

_onToggleNotification: function (event) {
this.killEvent(event);

const id = event.target.id;
const newLabel = (this._notificationStatus(id)
? _t('account.views.form.email_section.notifications.enabled')
: _t('account.views.form.email_section.notifications.disabled')
);

this._notificationLabel(id).html(newLabel);
},

_updateUser: function (user, password) {
this._setLoading('Saving changes');

Expand Down Expand Up @@ -193,6 +216,31 @@ module.exports = CoreView.extend({
};
},

_getNotifications: function () {
this._client.emailNotifications().get((errors, response, data) => {
if (errors) {
this.options.onError(data, response, errors);
} else {
this._notifications = data.notifications;
}

this.render();
});
},

_updateNotifications: function (notifications) {
this._setLoading('Saving email notifications');

this._client.emailNotifications().set(notifications, (errors, response, data) => {
if (errors) {
this.options.onError(data, errors);
this.render();
} else {
this._getNotifications();
}
});
},

_getDestinationValues: function () {
return {
username: this._username(),
Expand All @@ -202,6 +250,13 @@ module.exports = CoreView.extend({
};
},

_getNotificationValuesFromUI: function () {
return _.reduce(this._notifications, function (params, value, key) {
params[key] = this._notificationStatus(key);
return params;
}, {}, this);
},

_username: function () {
return this.$('#user_username').val();
},
Expand All @@ -225,6 +280,18 @@ module.exports = CoreView.extend({
return this.$('.js-mfa-label');
},

_notificationStatus: function (id) {
const selector = `.js-toggle-notification-${id}`;
if (this.$(selector).length === 0) {
return false;
}
return this.$(selector)[0].checked;
},

_notificationLabel: function (id) {
return this.$(`.js-notification-label-${id}`);
},

_formatLicenseExpiration: function () {
if (!this._getField('license_expiration')) { return null; }

Expand Down
48 changes: 47 additions & 1 deletion lib/assets/javascripts/dashboard/views/account/account-form.tpl
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<form accept-charset="UTF-8">

<!-- Username -->
<div class="FormAccount-row">
<div class="FormAccount-rowLabel">
<label class="CDB-Text CDB-Size-medium is-semibold u-mainTextColor"><%= _t('account.views.form.username') %></label>
Expand All @@ -12,6 +14,7 @@
</div>
</div>

<!-- Change password -->
<% if (!hidePasswordFields) { %>
<div class="VerticalAligned--FormRow">
<div class="FormAccount-row">
Expand Down Expand Up @@ -41,6 +44,7 @@

<%= accountFormExtension %>

<!-- Multifactor authentication -->
<div class="FormAccount-row">
<div class="FormAccount-rowLabel">
<label class="CDB-Text CDB-Size-medium is-semibold u-mainTextColor">
Expand All @@ -59,11 +63,12 @@
</p>
</div>
</div>
<div class="FormAccount-rowData u-tspace-xs">
<div class="FormAccount-rowData u-tspace-xs u-vspace-s">
<p class="CDB-Text CDB-Size-small u-altTextColor"><%= _t('account.views.form.mfa_description') %></p>
</div>
</div>

<!-- Account type -->
<% if (isCartoDBHosted) { %>
<% if ((isOrgAdmin || isOrgOwner) && licenseExpiration) { %>
<div class="FormAccount-title">
Expand Down Expand Up @@ -111,6 +116,46 @@
<% } %>
<% } %>

<!-- Email settings -->
<% if (Object.keys(notifications).length > 0) { %>
<div class="FormAccount-title">
<p class="FormAccount-titleText"><%= _t('account.views.form.email_section.title') %></p>
</div>
<span class="FormAccount-separator"></span>
<div class="FormAccount-row FormAccount-row--mediumMarginBottom">
<p class="CDB-Text CDB-Size-medium"><%= _t('account.views.form.email_section.description') %></p>
</div>
<div class="FormAccount-row">
<!-- One row per notification (eg: do_subscriptions) -->
<% Object.keys(notifications).forEach(function (notificationKey) { %>
<div class="FormAccount-rowData FormAccount-rowData--listItemWithAction">
<label class="CDB-Text CDB-Size-medium is-semibold u-mainTextColor">
<!-- extract to a 'description' property -->
<%= _t('account.views.form.email_section.notifications.' + notificationKey) %>
</label>
<div class="FormAccount-rowData">
<div class="Toggler">
<input name="<%='notifications[' + notificationKey + ']'%>" type="hidden" value="0">
<input class="js-toggle-notification <%='js-toggle-notification-' + notificationKey%>" id="<%=notificationKey%>" name="<%='notifications[' + notificationKey + ']'%>" type="checkbox" value="1" <% if (notifications[notificationKey]) { %>checked="checked"<% } %>>
<label for="<%=notificationKey%>"></label>
</div>

<div class="FormAccount-rowInfo u-lSpace--xl">
<p class="CDB-Text CDB-Size-medium <%='js-notification-label-' + notificationKey%>">
<%= notifications[notificationKey] ? _t('account.views.form.email_section.notifications.enabled') : _t('account.views.form.email_section.notifications.disabled') %>
</p>
</div>
</div>
</div>
<% }); %>
</div>
<% } %>

<!-- External datasources -->
<% if (services.length > 0) { %>
<div class="FormAccount-title">
<p class="FormAccount-titleText"><%= _t('account.views.form.connect_external_datasources') %></p>
Expand All @@ -121,6 +166,7 @@
<div class="js-datasourcesContent"></div>
<% } %>

<!-- Delete account -->
<div class="FormAccount-footer <% if (cantBeDeletedReason) { %>FormAccount-footer--noMarginBottom<% } %>">
<% if (cantBeDeletedReason) { %>
<p class="FormAccount-footerText">
Expand Down
9 changes: 9 additions & 0 deletions lib/assets/javascripts/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@
"email_google": "Your account is linked to your Google account.",
"errors": {
"change_email": "You can change the email if you set a password."
},
"email_section": {
"title": "Email settings",
"description": "Choose what type of emails you'd like to receive.",
"notifications": {
"enabled": "On",
"disabled": "Off",
"do_subscriptions":"Emails from Data Observatory"
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cartodb-ui",
"version": "1.0.0-assets.207",
"version": "1.0.0-assets.208",
"description": "CARTO UI frontend",
"repository": {
"type": "git",
Expand Down
15 changes: 11 additions & 4 deletions spec/requests/carto/api/email_notifications_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,30 @@

before(:all) do
@carto_user = FactoryGirl.create(:carto_user)
@carto_user.email_notifications = {
do_subscriptions: true
}
end

let(:auth_params) do
{ user_domain: @carto_user.username, api_key: @carto_user.api_key }
end

describe '#show' do
it 'list the current notifications' do
it 'always list available notifications with default value if missing in database' do
get_json(api_v3_email_notifications_show_url(auth_params)) do |response|
response.status.should eq 200
response.body.should eq({ notifications: { do_subscriptions: true } })
end
end

it 'list the current notifications' do
@carto_user.email_notifications = {
do_subscriptions: false
}
get_json(api_v3_email_notifications_show_url(auth_params)) do |response|
response.status.should eq 200
response.body.should eq({ notifications: { do_subscriptions: false } })
end
end

it 'return error if unauthenticated' do
get_json(api_v3_email_notifications_show_url({})) do |response|
response.status.should eq 401
Expand Down

0 comments on commit 06fe379

Please sign in to comment.