feat: Save UI state in the database (#1635)

feat: Save UI state in the database
This commit is contained in:
Pranav Raj S 2021-01-10 19:25:33 +05:30 committed by GitHub
parent 37d8e1c9a0
commit 160a6fc6cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 129 additions and 15 deletions

View file

@ -23,7 +23,8 @@ class Api::V1::ProfilesController < Api::BaseController
:password, :password,
:password_confirmation, :password_confirmation,
:avatar, :avatar,
:availability :availability,
ui_settings: {}
) )
end end
end end

View file

@ -140,6 +140,12 @@ export default {
return axios.put(endPoints('profileUpdate').url, formData); return axios.put(endPoints('profileUpdate').url, formData);
}, },
updateUISettings({ uiSettings }) {
return axios.put(endPoints('profileUpdate').url, {
profile: { ui_settings: uiSettings },
});
},
updateAvailability({ availability }) { updateAvailability({ availability }) {
return axios.put(endPoints('profileUpdate').url, { return axios.put(endPoints('profileUpdate').url, {
profile: { availability }, profile: { availability },

View file

@ -63,26 +63,24 @@ export default {
data() { data() {
return { return {
panelToggleState: true,
showSearchModal: false, showSearchModal: false,
}; };
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
uiSettings: 'getUISettings',
chatList: 'getAllConversations', chatList: 'getAllConversations',
currentChat: 'getSelectedChat', currentChat: 'getSelectedChat',
}), }),
isContactPanelOpen: { isContactPanelOpen() {
get() {
if (this.currentChat.id) { if (this.currentChat.id) {
return this.panelToggleState; const {
is_contact_sidebar_open: isContactSidebarOpen,
} = this.uiSettings;
return isContactSidebarOpen;
} }
return false; return false;
}, },
set(val) {
this.panelToggleState = val;
},
},
}, },
mounted() { mounted() {
@ -132,7 +130,12 @@ export default {
} }
}, },
onToggleContactPanel() { onToggleContactPanel() {
this.isContactPanelOpen = !this.isContactPanelOpen; this.$store.dispatch('updateUISettings', {
uiSettings: {
...this.uiSettings,
is_contact_sidebar_open: !this.isContactPanelOpen,
},
});
}, },
onSearch() { onSearch() {
this.showSearchModal = true; this.showSearchModal = true;

View file

@ -34,6 +34,10 @@ export const getters = {
return _state.currentUser.id; return _state.currentUser.id;
}, },
getUISettings(_state) {
return _state.currentUser.ui_settings || {};
},
getCurrentUserAvailabilityStatus(_state) { getCurrentUserAvailabilityStatus(_state) {
return _state.currentUser.availability_status; return _state.currentUser.availability_status;
}, },
@ -95,6 +99,7 @@ export const actions = {
logout({ commit }) { logout({ commit }) {
commit(types.default.CLEAR_USER); commit(types.default.CLEAR_USER);
}, },
updateProfile: async ({ commit }, params) => { updateProfile: async ({ commit }, params) => {
try { try {
const response = await authAPI.profileUpdate(params); const response = await authAPI.profileUpdate(params);
@ -105,6 +110,17 @@ export const actions = {
} }
}, },
updateUISettings: async ({ commit }, params) => {
try {
commit(types.default.SET_CURRENT_USER_UI_SETTINGS, params);
const response = await authAPI.updateUISettings(params);
setUser(response.data, getHeaderExpiry(response));
commit(types.default.SET_CURRENT_USER);
} catch (error) {
// Ignore error
}
},
updateAvailability: ({ commit, dispatch }, { availability }) => { updateAvailability: ({ commit, dispatch }, { availability }) => {
authAPI.updateAvailability({ availability }).then(response => { authAPI.updateAvailability({ availability }).then(response => {
const userData = response.data; const userData = response.data;
@ -130,7 +146,7 @@ export const actions = {
}; };
// mutations // mutations
const mutations = { export const mutations = {
[types.default.SET_CURRENT_USER_AVAILABILITY](_state, status) { [types.default.SET_CURRENT_USER_AVAILABILITY](_state, status) {
Vue.set(_state.currentUser, 'availability_status', status); Vue.set(_state.currentUser, 'availability_status', status);
}, },
@ -145,6 +161,15 @@ const mutations = {
Vue.set(_state, 'currentUser', currentUser); Vue.set(_state, 'currentUser', currentUser);
}, },
[types.default.SET_CURRENT_USER_UI_SETTINGS](_state, { uiSettings }) {
Vue.set(_state, 'currentUser', {
..._state.currentUser,
ui_settings: {
..._state.currentUser.ui_settings,
...uiSettings,
},
});
},
[types.default.SET_CURRENT_ACCOUNT_ID](_state, accountId) { [types.default.SET_CURRENT_ACCOUNT_ID](_state, accountId) {
Vue.set(_state, 'currentAccountId', Number(accountId)); Vue.set(_state, 'currentAccountId', Number(accountId));
}, },

View file

@ -70,6 +70,32 @@ describe('#actions', () => {
}); });
}); });
describe('#updateUISettings', () => {
it('sends correct actions if API is success', async () => {
axios.put.mockResolvedValue({
data: {
id: 1,
name: 'John',
availability_status: 'offline',
ui_settings: { is_contact_sidebar_open: true },
},
headers: { expiry: 581842904 },
});
await actions.updateUISettings(
{ commit, dispatch },
{ uiSettings: { is_contact_sidebar_open: false } }
);
expect(setUser).toHaveBeenCalledTimes(1);
expect(commit.mock.calls).toEqual([
[
types.default.SET_CURRENT_USER_UI_SETTINGS,
{ uiSettings: { is_contact_sidebar_open: false } },
],
[types.default.SET_CURRENT_USER],
]);
});
});
describe('#setUser', () => { describe('#setUser', () => {
it('sends correct actions if user is logged in', async () => { it('sends correct actions if user is logged in', async () => {
Cookies.getJSON.mockImplementation(() => true); Cookies.getJSON.mockImplementation(() => true);

View file

@ -25,4 +25,12 @@ describe('#getters', () => {
}) })
).toEqual('busy'); ).toEqual('busy');
}); });
it('getUISettings', () => {
expect(
getters.getUISettings({
currentUser: { ui_settings: { is_contact_sidebar_open: true } },
})
).toEqual({ is_contact_sidebar_open: true });
});
}); });

View file

@ -0,0 +1,21 @@
import types from '../../../mutation-types';
import { mutations } from '../../auth';
describe('#mutations', () => {
describe('#SET_CURRENT_USER_UI_SETTINGS', () => {
it('set ui flags', () => {
const state = {
currentUser: {
ui_settings: { is_contact_sidebar_open: true, icon_type: 'emoji' },
},
};
mutations[types.SET_CURRENT_USER_UI_SETTINGS](state, {
uiSettings: { is_contact_sidebar_open: false },
});
expect(state.currentUser.ui_settings).toEqual({
is_contact_sidebar_open: false,
icon_type: 'emoji',
});
});
});
});

View file

@ -4,6 +4,7 @@ export default {
SET_CURRENT_USER: 'SET_CURRENT_USER', SET_CURRENT_USER: 'SET_CURRENT_USER',
SET_CURRENT_ACCOUNT_ID: 'SET_CURRENT_ACCOUNT_ID', SET_CURRENT_ACCOUNT_ID: 'SET_CURRENT_ACCOUNT_ID',
SET_CURRENT_USER_AVAILABILITY: 'SET_CURRENT_USER_AVAILABILITY', SET_CURRENT_USER_AVAILABILITY: 'SET_CURRENT_USER_AVAILABILITY',
SET_CURRENT_USER_UI_SETTINGS: 'SET_CURRENT_USER_UI_SETTINGS',
// Chat List // Chat List
RECEIVE_CHAT_LIST: 'RECEIVE_CHAT_LIST', RECEIVE_CHAT_LIST: 'RECEIVE_CHAT_LIST',

View file

@ -22,6 +22,7 @@
# reset_password_token :string # reset_password_token :string
# sign_in_count :integer default(0), not null # sign_in_count :integer default(0), not null
# tokens :json # tokens :json
# ui_settings :jsonb
# uid :string default(""), not null # uid :string default(""), not null
# unconfirmed_email :string # unconfirmed_email :string
# created_at :datetime not null # created_at :datetime not null

View file

@ -13,6 +13,7 @@ json.confirmed resource.confirmed?
json.avatar_url resource.avatar_url json.avatar_url resource.avatar_url
json.access_token resource.access_token.token json.access_token resource.access_token.token
json.availability_status resource.availability_status json.availability_status resource.availability_status
json.ui_settings resource.ui_settings
json.accounts do json.accounts do
json.array! resource.account_users do |account_user| json.array! resource.account_users do |account_user|
json.id account_user.account_id json.id account_user.account_id

View file

@ -0,0 +1,9 @@
class AddUiSettingsToUsers < ActiveRecord::Migration[6.0]
def up
add_column :users, :ui_settings, :jsonb, default: {}
end
def down
remove_column :users, :ui_settings, :jsonb
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_01_05_185632) do ActiveRecord::Schema.define(version: 2021_01_09_211805) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_stat_statements" enable_extension "pg_stat_statements"
@ -501,6 +501,7 @@ ActiveRecord::Schema.define(version: 2021_01_05_185632) do
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.string "pubsub_token" t.string "pubsub_token"
t.integer "availability", default: 0 t.integer "availability", default: 0
t.jsonb "ui_settings", default: {}
t.index ["email"], name: "index_users_on_email" t.index ["email"], name: "index_users_on_email"
t.index ["pubsub_token"], name: "index_users_on_pubsub_token", unique: true t.index ["pubsub_token"], name: "index_users_on_pubsub_token", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true

View file

@ -87,6 +87,17 @@ RSpec.describe 'Profile API', type: :request do
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
expect(::OnlineStatusTracker.get_status(account.id, agent.id)).to eq('offline') expect(::OnlineStatusTracker.get_status(account.id, agent.id)).to eq('offline')
end end
it 'updates the ui settings' do
put '/api/v1/profile',
params: { profile: { ui_settings: { is_contact_sidebar_open: false } } },
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
json_response = JSON.parse(response.body)
expect(json_response['ui_settings']['is_contact_sidebar_open']).to eq(false)
end
end end
end end
end end