MT#62500 Refactor logic of PBx Devices pages

App was crashing when user was refreshing
page on page /pbx-configuration/device/${id}
This happens because the logic to load all
necessary resources is a bit tangled. This
commit attempts to tidy up things without
inserting breaking changes.

Change-Id: I868678fcc6ed181697a5f3825d41940429785375
master
Debora Crescenzo 1 week ago committed by Crescenzo Debora
parent 1bff29a08f
commit e314c96c3c

@ -53,17 +53,15 @@ export function getAllProfiles () {
})
}
export function getProfile (id) {
return get({
path: `api/pbxdeviceprofiles/${id}`
})
}
export function getModel (id) {
return new Promise((resolve, reject) => {
Promise.resolve().then(() => {
return get({
path: `api/pbxdevicemodels/${id}`
})
}).then((model) => {
resolve(model)
}).catch((err) => {
reject(err)
})
return get({
path: `api/pbxdevicemodels/${id}`
})
}

@ -1,5 +1,6 @@
import _ from 'lodash'
import {
get,
getList,
httpApi,
patchAdd,
@ -7,12 +8,7 @@ import {
patchReplace,
patchReplaceFull
} from 'src/api/common'
import {
PBX_CONFIG_ORDER_BY,
PBX_CONFIG_ORDER_DIRECTION,
getModel,
getModelFrontImage
} from 'src/api/pbx-config'
import { PBX_CONFIG_ORDER_BY, PBX_CONFIG_ORDER_DIRECTION } from 'src/api/pbx-config'
export function getDevices (options) {
return new Promise((resolve, reject) => {
@ -27,6 +23,12 @@ export function getDevices (options) {
})
})
}
export function getDevice (id) {
return get({
path: `api/pbxdevices/${id}`
})
}
export function getDevicesPreferences (options) {
return new Promise((resolve, reject) => {
let requestOptions = options || {}
@ -182,22 +184,6 @@ export function setDeviceKeys (deviceId, keys) {
})
}
export async function loadDeviceModel (modelId) {
return new Promise((resolve, reject) => {
Promise.all([
getModel(modelId),
getModelFrontImage(modelId)
]).then((res) => {
resolve({
model: res[0],
modelImage: res[1]
})
}).catch((err) => {
reject(err)
})
})
}
export function setPreferenceDevice (deviceId, deviceValue, fieldName) {
return new Promise((resolve, reject) => {
Promise.resolve().then(() => {

@ -36,7 +36,7 @@
<csc-pbx-device-config-key-form
:selected-line="selectedLine"
:selected-key="selectedKey"
:subsriber-map="subscriberMap"
:subscriber-map="subscriberMap"
:loading="isDeviceLoading(device.id)"
@closeKeyOverlay="keyOverlayActive = false"
@onSave="onSave"

@ -171,8 +171,11 @@
</div>
<div class="col-12 col-md-6 q-pa-lg">
<csc-list-spinner
v-if="!deviceSelected"
/>
<csc-pbx-device-config
v-if="deviceSelected && deviceModelImageMap[deviceProfileMap[deviceSelected?.profile_id].device_id]"
v-if="deviceSelected && hasDeviceModelImage"
:device="deviceSelected"
:model="deviceModelMap[deviceProfileMap[deviceSelected.profile_id].device_id]"
:model-image="deviceModelImageMap[deviceProfileMap[deviceSelected.profile_id].device_id]"
@ -189,6 +192,7 @@
<script>
import useValidate from '@vuelidate/core'
import CscListSpinner from 'components/CscListSpinner'
import CscPageStickyTabs from 'components/CscPageStickyTabs'
import CscInputButtonReset from 'components/form/CscInputButtonReset'
import CscInputButtonSave from 'components/form/CscInputButtonSave'
@ -209,7 +213,9 @@ export default {
CscInputButtonSave,
CscInputButtonReset,
CscPbxModelSelect,
CscPbxDeviceConfig
CscPbxDeviceConfig,
CscListSpinner
},
props: {
initialTab: {
@ -248,13 +254,16 @@ export default {
]),
...mapGetters('pbx', [
'getSubscriberOptions',
'isSubscribersRequesting'
'isSubscribersRequesting',
'isDeviceInModelMap'
]),
...mapGetters('pbxDevices', [
'getDeviceUpdateToastMessage',
'getDevicePreferencesUpdateToastMessage',
'isDeviceLoading',
'isDevicePreferencesLoading'
'isDevicePreferencesLoading',
'isDeviceInMapBy',
'isDeviceInPreferencesMap'
]),
tabs () {
return [
@ -280,6 +289,18 @@ export default {
hasProfileChanged () {
return this.changes.profile_id !== this.deviceSelected?.profile_id
},
hasDeviceModelImage () {
if (!this.deviceSelected || !this.deviceSelected.profile_id) {
return false
}
const profileMap = this.deviceProfileMap[this.deviceSelected.profile_id]
if (!profileMap || !profileMap.device_id) {
return false
}
return !!this.deviceModelImageMap[profileMap.device_id]
},
imageUrl () {
if (this.modelImage && this.modelImage.url) {
return this.modelImage.url
@ -295,10 +316,8 @@ export default {
},
watch: {
async $route (to) {
if (this.id !== to.params.id) {
this.id = to.params.id
this.expandDevice(this.id)
}
this.id = to.params.id
await this.getData(this.id)
},
deviceSelected () {
this.changes = this.getDeviceData()
@ -319,13 +338,8 @@ export default {
}
},
async created () {
await this.loadDeviceListItems()
if (this.isDeviceMapByIdEmpty) {
await this.loadDeviceListItems()
}
this.expandDevice(this.id)
this.expandDevicePreferences(this.id)
await this.loadSubscribers()
const deviceId = this.deviceSelected?.id || this.id
await this.getData(deviceId)
},
methods: {
...mapMutations('pbxDevices', [
@ -333,7 +347,6 @@ export default {
'expandDevicePreferences'
]),
...mapActions('pbxDevices', [
'loadDeviceListItems',
'setDeviceKeys',
'setDeviceStationName',
'setDeviceIdentifier',
@ -342,11 +355,35 @@ export default {
'setAdminPassword',
'setGui',
'setUserConfig',
'setFW'
'setFW',
'loadDevicePreferencesList',
'loadDevice'
]),
...mapActions('pbx', [
'loadSubscribers'
'loadSubscribers',
'loadDeviceModel'
]),
async getData (deviceId) {
if (!this.isDeviceInMapBy(deviceId)) {
await this.loadDevice(deviceId)
}
if (!this.isDeviceInModelMap(deviceId)) {
await this.loadDeviceModel({
type: 'all',
deviceId
})
}
if (!this.isDeviceInPreferencesMap(deviceId)) {
await this.loadDevicePreferencesList()
}
this.expandDevice(deviceId)
this.expandDevicePreferences(deviceId)
this.loadSubscribers()
},
getDeviceData () {
return (this.deviceSelected && this.devicePreferencesSelected)
? {

@ -73,7 +73,7 @@
<q-pagination
:model-value="deviceListCurrentPage"
:max="deviceListLastPage"
@update:model-value="loadDeviceListItemsFiltered"
@update:model-value="loadDeviceListFiltered"
/>
</div>
<csc-list-spinner
@ -84,12 +84,12 @@
class="row justify-start items-start"
>
<csc-fade
v-for="(device, index) in deviceListItems"
v-for="(device, index) in deviceList"
:key="'csc-fade-' + device.id"
>
<csc-pbx-device
:key="device.id"
:loading="isDeviceLoading(device.id)"
:loading="isDeviceLoading(device.id) || isDeviceListRequesting"
:device="device"
:class="'col-xs-12 col-md-6 col-lg-4 csc-item-' + ((index % 2 === 0)?'odd':'even')"
:profile="deviceProfileMap[device.profile_id]"
@ -135,14 +135,8 @@ import CscPbxDevice from 'components/pages/PbxConfiguration/CscPbxDevice'
import CscPbxDeviceAddForm from 'components/pages/PbxConfiguration/CscPbxDeviceAddForm'
import CscPbxDeviceFilters from 'components/pages/PbxConfiguration/CscPbxDeviceFilters'
import CscFade from 'components/transitions/CscFade'
import {
showGlobalError,
showToast
} from 'src/helpers/ui'
import {
CreationState,
RequestState
} from 'src/store/common'
import { showGlobalError, showToast } from 'src/helpers/ui'
import { CreationState, RequestState } from 'src/store/common'
import {
mapActions,
mapGetters,
@ -184,7 +178,7 @@ export default {
]),
...mapState('pbxDevices', [
'deviceRemoving',
'deviceListItems',
'deviceList',
'deviceListCurrentPage',
'deviceListLastPage',
'deviceListVisibility',
@ -230,8 +224,8 @@ export default {
},
mounted () {
this.$scrollTo(this.$parent.$el)
this.loadDeviceListItemsFiltered()
this.loadDevicePreferencesListItems()
this.loadDeviceListFiltered()
this.loadDevicePreferencesList()
},
methods: {
...mapActions('pbx', [
@ -247,8 +241,8 @@ export default {
'deviceRemovalCanceled'
]),
...mapActions('pbxDevices', [
'loadDeviceListItems',
'loadDevicePreferencesListItems',
'loadDeviceList',
'loadDevicePreferencesList',
'createDevice',
'removeDevice',
'setDeviceStationName',
@ -256,8 +250,8 @@ export default {
'setDeviceProfile',
'setDeviceKeys'
]),
loadDeviceListItemsFiltered (page) {
this.loadDeviceListItems({
loadDeviceListFiltered (page) {
this.loadDeviceList({
page: page || 1,
filters: this.filters
})
@ -272,7 +266,7 @@ export default {
},
applyFilter (filterData) {
this.filters = filterData
this.loadDeviceListItemsFiltered()
this.loadDeviceListFiltered()
},
closeFilters () {
this.filtersEnabled = false
@ -281,7 +275,7 @@ export default {
resetFilters () {
if (this.hasFilters) {
this.filters = {}
this.loadDeviceListItemsFiltered()
this.loadDeviceListFiltered()
}
},
openDeviceRemovalDialog (deviceId) {

@ -2,6 +2,7 @@ import { i18n } from 'boot/i18n'
import _ from 'lodash'
import {
createDevice,
getDevice,
getDeviceList,
getDevicesPreferences,
removeDevice,
@ -20,12 +21,12 @@ export default {
deviceCreationError: null,
deviceCreationState: CreationState.initiated,
deviceListCurrentPage: 1,
deviceListItems: [],
deviceList: [],
deviceListLastPage: null,
deviceListState: RequestState.initiated,
deviceListVisibility: 'visible',
deviceMapById: {},
devicePreferencesListItems: [],
devicePreferencesList: [],
devicePreferencesListState: RequestState.initiated,
devicePreferencesMap: {},
devicePreferencesRemovalState: RequestState.initiated,
@ -108,10 +109,17 @@ export default {
}
},
isDeviceListEmpty (state) {
return Array.isArray(state.deviceListItems) && state.deviceListItems.length === 0
return Array.isArray(state.deviceList) && state.deviceList.length === 0
},
isDeviceMapByIdEmpty (state) {
return Object.keys(state.deviceMapById).length === 0
isDeviceInMapBy (state) {
return (deviceId) => {
return state.deviceMapById[deviceId] !== undefined
}
},
isDeviceInPreferencesMap (state) {
return (deviceId) => {
return state.devicePreferencesMap[deviceId] !== undefined
}
},
isDeviceListPaginationActive (state, getters) {
const requesting = !getters.isDeviceListRequesting || getters.isDeviceCreating ||
@ -147,41 +155,49 @@ export default {
}
},
mutations: {
deviceListItemsRequesting (state, options) {
deviceListRequesting (state, options) {
const clearList = _.get(options, 'clearList', true)
state.deviceListState = RequestState.requesting
state.deviceListLastPage = null
if (clearList) {
state.deviceListVisibility = 'hidden'
state.deviceListItems = []
state.deviceList = []
state.deviceMapById = {}
} else {
state.deviceListVisibility = 'visible'
}
},
deviceListItemsSucceeded (state, options) {
deviceListSucceeded (state, options) {
state.deviceListState = RequestState.succeeded
state.deviceListCurrentPage = _.get(options, 'page', 1)
state.deviceListItems = _.get(options, 'devices.items', [])
state.deviceList = _.get(options, 'devices.items', [])
state.deviceListLastPage = _.get(options, 'devices.lastPage', 1)
state.deviceMapById = {}
state.deviceListItems.forEach((device) => {
state.deviceList.forEach((device) => {
state.deviceMapById[device.id] = device
})
state.deviceListVisibility = 'visible'
},
devicePreferencesListItemsSucceeded (state, options) {
deviceSucceeded (state, device) {
state.deviceListState = RequestState.succeeded
state.deviceList = [...state.deviceList, device]
state.deviceMapById[device.id] = device
},
devicePreferencesListRequesting (state) {
state.devicePreferencesListState = RequestState.requesting
},
devicePreferencesListSucceeded (state, options) {
state.devicePreferencesListState = RequestState.succeeded
state.devicePreferencesListItems = _.get(options, 'devicesPreferences', [])
state.devicePreferencesList = _.get(options, 'devicesPreferences', [])
state.devicePreferencesMap = {}
state.devicePreferencesListItems.forEach((devicePreferences) => {
state.devicePreferencesList.forEach((devicePreferences) => {
state.devicePreferencesMap[devicePreferences.id] = devicePreferences
})
},
deviceListItemsFailed (state) {
deviceListFailed (state) {
state.deviceListState = RequestState.failed
},
devicePreferencesListItemsFailed (state) {
devicePreferencesListFailed (state) {
state.devicePreferencesListState = RequestState.failed
},
deviceCreationRequesting (state, device) {
@ -209,11 +225,11 @@ export default {
state.deviceUpdateState = RequestState.succeeded
delete state.deviceMapById[device.id]
state.deviceMapById[device.id] = device
for (let i = 0; i < state.deviceListItems.length; i++) {
if (state.deviceListItems[i].id === device.id) {
state.deviceListItems[i] = device
state.deviceList.forEach((item, index) => {
if (item.id === device.id) {
state.deviceList[index] = device
}
}
})
if (state.deviceSelected !== null && state.deviceSelected.id === device.id) {
state.deviceSelected = device
}
@ -222,11 +238,11 @@ export default {
state.devicePreferencesUpdateState = RequestState.succeeded
delete state.devicePreferencesMap[device.id]
state.devicePreferencesMap[device.id] = device
for (let i = 0; i < state.devicePreferencesListItems.length; i++) {
if (state.devicePreferencesListItems[i].id === device.id) {
state.devicePreferencesListItems[i] = device
state.devicePreferencesList.forEach((item, index) => {
if (item.id === device.id) {
state.devicePreferencesList[index] = device
}
}
})
if (state.devicePreferencesSelected !== null && state.devicePreferencesSelected.id === device.id) {
state.devicePreferencesSelected = device
}
@ -274,56 +290,54 @@ export default {
}
},
actions: {
loadDeviceListItems (context, options) {
return new Promise((resolve, reject) => {
const page = _.get(options, 'page', context.state.deviceListCurrentPage)
const clearList = _.get(options, 'clearList', true)
const filters = _.get(options, 'filters', {})
context.commit('deviceListItemsRequesting', {
clearList
})
Promise.resolve().then(() => {
return context.dispatch('pbx/loadProfiles', null, { root: true })
}).then(() => {
return getDeviceList({
page,
filters
})
}).then((devices) => {
context.commit('deviceListItemsSucceeded', {
devices,
page
})
resolve()
}).catch((err) => {
context.commit('deviceListItemsFailed', err.message)
reject(err)
})
})
async loadDeviceList (context, options) {
const page = _.get(options, 'page', context.state.deviceListCurrentPage)
const clearList = _.get(options, 'clearList', true)
const filters = _.get(options, 'filters', {})
context.commit('deviceListRequesting', { clearList })
try {
// Ensure profiles are loaded before fetching devices
await context.dispatch('pbx/loadProfiles', null, { root: true })
const devices = await getDeviceList({ page, filters })
context.commit('deviceListSucceeded', { devices, page })
} catch (err) {
context.commit('deviceListFailed', err.message)
throw err
}
},
async loadDevice (context, deviceId) {
context.commit('deviceListRequesting', { clearList: false })
try {
// Ensure profiles are loaded before fetching devices
await context.dispatch('pbx/loadProfileById', deviceId, { root: true })
const device = await getDevice(deviceId)
context.commit('deviceSucceeded', device)
} catch (err) {
context.commit('deviceListFailed', err.message)
throw err
}
},
loadDevicePreferencesListItems (context) {
return new Promise((resolve, reject) => {
Promise.resolve().then(() => {
return getDevicesPreferences()
}).then((devicesPreferences) => {
context.commit('devicePreferencesListItemsSucceeded', {
devicesPreferences: devicesPreferences.items
})
resolve()
}).catch((err) => {
context.commit('devicePreferencesListItemsFailed', err.message)
reject(err)
async loadDevicePreferencesList (context) {
context.commit('devicePreferencesListRequesting')
try {
const devicesPreferences = await getDevicesPreferences()
context.commit('devicePreferencesListSucceeded', {
devicesPreferences: devicesPreferences.items
})
})
} catch (err) {
context.commit('devicePreferencesListFailed', err.message)
}
},
createDevice (context, deviceData) {
context.commit('deviceCreationRequesting', deviceData)
createDevice(deviceData).then(() => {
context.dispatch('loadDeviceListItems', {
context.dispatch('loadDeviceList', {
page: 1,
clearList: false
})
context.dispatch('loadDevicePreferencesListItems')
context.dispatch('loadDevicePreferencesList')
}).then(() => {
context.commit('deviceCreationSucceeded')
}).catch((err) => {
@ -333,7 +347,7 @@ export default {
removeDevice (context, deviceId) {
context.commit('deviceRemovalRequesting', deviceId)
removeDevice(deviceId).then(() => {
return context.dispatch('loadDeviceListItems', {
return context.dispatch('loadDeviceList', {
page: context.state.deviceListCurrentPage,
clearList: false
})

@ -4,7 +4,8 @@ import {
getAllProfiles,
getModel,
getModelFrontImage,
getModelFrontThumbnailImage
getModelFrontThumbnailImage,
getProfile
} from 'src/api/pbx-config'
import { getSubscribers } from 'src/api/subscriber'
import { getNumbers } from 'src/api/user'
@ -24,6 +25,7 @@ export default {
seatMapById: {},
soundSetList: [],
soundSetMapByName: {},
deviceProfilesListState: RequestState.initiated,
deviceProfileList: [],
deviceProfileMap: {},
deviceModelList: [],
@ -148,6 +150,11 @@ export default {
})
return options
},
isDeviceInModelMap (state) {
return (deviceId) => {
return state.deviceModelMap[deviceId] !== undefined
}
},
isSubscribersRequesting (state) {
return state.subscriberListState === RequestState.requesting
},
@ -218,16 +225,30 @@ export default {
state.soundSetMapByName[soundSet.name] = soundSet
})
},
deviceProfilesSucceeded (state, deviceProfileList) {
deviceProfilesListStateRequesting (state) {
state.deviceProfilesListState = RequestState.requesting
},
deviceProfilesListSucceeded (state, deviceProfileList) {
state.deviceProfilesListState = RequestState.succeeded
state.deviceProfileList = _.get(deviceProfileList, 'items', [])
state.deviceProfileMap = {}
state.deviceProfileList.forEach((deviceProfile) => {
state.deviceProfileMap[deviceProfile.id] = deviceProfile
})
},
deviceProfilesFailed (state) {
state.deviceProfileList = []
state.deviceProfileMap = {}
deviceProfilesListFailed (state) {
state.deviceProfilesListState = RequestState.failed
},
deviceProfileRequesting (state) {
state.deviceProfilesListState = RequestState.requesting
},
deviceProfileSucceeded (state, deviceProfile) {
state.deviceProfilesListState = RequestState.succeeded
state.deviceProfileList = [...state.deviceProfileList, deviceProfile]
state.deviceProfileMap[deviceProfile.id] = deviceProfile
},
deviceProfileFailed (state) {
state.deviceProfilesListState = RequestState.failed
},
deviceModelSucceeded (state, deviceModel) {
const model = _.get(deviceModel, 'model', null)
@ -263,12 +284,13 @@ export default {
actions: {
loadProfiles (context) {
return new Promise((resolve, reject) => {
context.commit('deviceProfilesListStateRequesting')
if (context.state.deviceProfileList.length === 0) {
getAllProfiles().then((profiles) => {
context.commit('deviceProfilesSucceeded', profiles)
context.commit('deviceProfilesListSucceeded', profiles)
resolve(profiles)
}).catch((err) => {
context.commit('deviceProfilesFailed')
context.commit('deviceProfilesListFailed')
reject(err)
})
} else {
@ -276,6 +298,15 @@ export default {
}
})
},
async loadProfileById (context, deviceId) {
context.commit('deviceProfileRequesting')
try {
const profile = await getProfile(deviceId)
context.commit('deviceProfileSucceeded', profile)
} catch (err) {
context.commit('deviceProfileFailed')
}
},
async loadDeviceModel (context, payload) {
try {
const isFrontCached = context.state.deviceModelImageMap[payload.deviceId] !== undefined
@ -331,13 +362,13 @@ export default {
}
},
async loadDeviceModels (context, imageType) {
const requests = []
for (let i = 0; i < context.state.deviceProfileList.length; i++) {
requests.push(context.dispatch('loadDeviceModel', {
deviceId: context.state.deviceProfileList[i].device_id,
const requests = context.state.deviceProfileList.map((deviceProfile) => {
return context.dispatch('loadDeviceModel', {
deviceId: deviceProfile.device_id,
type: imageType
}))
}
})
})
await Promise.all(requests)
},
loadSubscribers (context) {

Loading…
Cancel
Save