MT#62916 Pbx Devices Bugfix

Listed here the changes:
- change properties accessed directly
  from state with computed properties
  that handles null cases
- Fix typos for subcriberListState
  and subcriberListError
- Improve route watcher in
  CscPagePbxDeviceDetails
- add a method to load only thumbnails
  instead of full set of data for
  the device profiles to improve
  performance.
- removed 1000 rows for loadProfiles()
  as it was too slow. We expect max 150
  devices so it was set to 300 rows.
Note, more refactoring will be needed.

Change-Id: Iefe0328052174f0bb93f8cdbae59f77257592592
mr13.4
Debora Crescenzo 4 months ago committed by Crescenzo Debora
parent 38c2f37c1a
commit d8c1f097cc

@ -87,8 +87,8 @@ export async function getList (options) {
const requestConfig = _.merge({
all: false,
params: {
page: LIST_DEFAULT_PAGE,
rows: LIST_DEFAULT_ROWS
page: options.page || LIST_DEFAULT_PAGE,
rows: options.rows || LIST_DEFAULT_ROWS
},
headers: GET_HEADERS
}, options)

@ -48,8 +48,10 @@ export function getProfiles (options) {
}
export function getAllProfiles () {
// Replace 1000 rows with 300 as we expect to have max 150 profiles.
return getProfiles({
all: true
page: 1,
rows: 300
})
}

@ -9,10 +9,21 @@
center
no-wrap
>
<img
class="csc-list-item-head-image"
:src="imageUrl"
<q-icon
v-if="!imageUrl"
name="fas fa-fax"
size="24px"
/>
<q-avatar
v-else
square
>
<q-img
class="csc-list-item-head-image"
:src="imageUrl"
/>
</q-avatar>
</q-item-section>
<q-item-section>
<csc-list-item-title>

@ -119,8 +119,8 @@ export default {
'deviceProfileMap',
'deviceProfileList',
'subscriberList',
'subcriberListState',
'subcriberListError'
'subscriberListState',
'subscriberListError'
]),
subscribersOptions () {
const options = []
@ -196,9 +196,9 @@ export default {
filterTypeModel () {
this.typedFilter = null
},
subcriberListState (state) {
subscriberListState (state) {
if (state === RequestState.failed) {
showGlobalError(this.subcriberListError)
showGlobalError(this.subscriberListError)
}
}
},

@ -49,10 +49,11 @@
>
<q-avatar
square
size="32px"
>
<img
<q-img
:src="deviceModelImageSmallMap[scope.opt.model].url"
>
/>
</q-avatar>
</q-item-section>
<q-item-section>
@ -118,15 +119,11 @@ export default {
return _.get(this.deviceModelImageSmallMap, `${deviceModelId}.url`, null)
},
options () {
const options = []
this.profiles.forEach((profile) => {
options.push({
label: profile.name,
value: profile.id,
model: profile.device_id
})
})
return options
return this.profiles.map((profile) => ({
label: profile.name,
value: profile.id,
model: profile.device_id
}))
}
},
watch: {

@ -154,8 +154,8 @@ export default {
'getCallQueueRemovalToastMessage'
]),
...mapState('pbx', [
'subcriberListState',
'subcriberListError'
'subscriberListState',
'subscriberListError'
])
},
watch: {
@ -187,9 +187,9 @@ export default {
showGlobalError(this.callQueueListError)
}
},
subcriberListState (state) {
subscriberListState (state) {
if (state === RequestState.failed) {
showGlobalError(this.subcriberListError)
showGlobalError(this.subscriberListError)
}
}
},

@ -175,10 +175,11 @@
v-if="!deviceSelected"
/>
<csc-pbx-device-config
v-if="deviceSelected && hasDeviceModelImage"
v-else
:id="deviceSelected?.id"
:device="deviceSelected"
:model="deviceModelMap[deviceProfileMap[deviceSelected.profile_id].device_id]"
:model-image="deviceModelImageMap[deviceProfileMap[deviceSelected.profile_id].device_id]"
:model="deviceModel"
:model-image="deviceModelImage"
:loading="isSubscribersRequesting"
:subscribers="subscriberList"
:subscriber-map="subscriberMap"
@ -244,22 +245,23 @@ export default {
'devicePreferencesUpdateState',
'devicePreferencesSelected',
'devicePreferencesListState',
'devicePreferencesError'
'devicePreferencesError',
'deviceMapById'
]),
...mapState('pbx', [
'deviceProfileList',
'deviceProfileMap',
'deviceProfileListState',
'deviceModelImageMap',
'deviceModelMap',
'subscriberList',
'subscriberMap',
'subcriberListState',
'subcriberListError'
'subscriberListState',
'subscriberListError'
]),
...mapGetters('pbx', [
'getSubscriberOptions',
'isSubscribersRequesting',
'isDeviceInModelMap'
'isSubscribersRequesting'
]),
...mapGetters('pbxDevices', [
'getDeviceUpdateToastMessage',
@ -269,6 +271,55 @@ export default {
'isDeviceInMapBy',
'isDeviceInPreferencesMap'
]),
deviceModel () {
if (!this.deviceSelected?.profile_id) {
return {}
}
const profileId = this.deviceSelected.profile_id
const deviceProfile = this.deviceProfileMap[profileId]
if (!deviceProfile) {
return {}
}
if (!deviceProfile.device_id) {
return {}
}
const deviceId = deviceProfile.device_id
const deviceModel = this.deviceModelMap[deviceId]
if (!deviceModel) {
return {}
}
return deviceModel
},
deviceModelImage () {
if (!this.deviceSelected?.profile_id) {
return null
}
const profileId = this.deviceSelected.profile_id
const deviceProfile = this.deviceProfileMap[profileId]
if (!deviceProfile) {
return null
}
if (!deviceProfile.device_id) {
return null
}
const deviceId = deviceProfile.device_id
const deviceModelImage = this.deviceModelImageMap[deviceId]
if (!deviceModelImage) {
return null
}
return deviceModelImage
},
tabs () {
return [
{
@ -279,7 +330,7 @@ export default {
]
},
isLoading () {
return this.isDeviceLoading(this.deviceSelected?.id)
return this.isDeviceLoading(this.id)
},
isLoadingPreferences () {
return this.isDevicePreferencesLoading(this.devicePreferencesSelected?.id)
@ -319,9 +370,11 @@ export default {
}
},
watch: {
async $route (to) {
this.id = to.params.id
await this.getData(this.id)
$route: {
async handler (to) {
this.id = to.params.id
await this.getData(this.id)
}
},
deviceSelected () {
this.changes = this.getDeviceData()
@ -345,15 +398,19 @@ export default {
showGlobalError(this.devicePreferencesError)
}
},
subcriberListState (state) {
subscriberListState (state) {
if (state === RequestState.failed) {
showGlobalError(this.subcriberListError)
showGlobalError(this.subscriberListError)
}
}
},
async created () {
const deviceId = this.deviceSelected?.id || this.id
await this.getData(deviceId)
if (this.deviceProfileList.length === 0) {
await this.loadProfiles()
await this.loadProfileThumbnails()
}
await this.getData(this.id)
},
methods: {
...mapMutations('pbxDevices', [
@ -375,7 +432,9 @@ export default {
]),
...mapActions('pbx', [
'loadSubscribers',
'loadDeviceModel'
'loadDeviceModel',
'loadProfiles',
'loadProfileThumbnails'
]),
async getData (deviceId) {
@ -383,10 +442,13 @@ export default {
await this.loadDevice(deviceId)
}
if (!this.isDeviceInModelMap(deviceId)) {
const deviceProfileId = this.deviceMapById[deviceId].profile_id
const deviceProfile = this.deviceProfileMap[deviceProfileId]
if (deviceProfile.device_id) {
await this.loadDeviceModel({
type: 'all',
deviceId
deviceId: deviceProfile.device_id
})
}
@ -461,6 +523,14 @@ export default {
this.setDeviceProfile({
deviceId: this.deviceSelected?.id,
profileId: this.changes.profile_id
}).then(() => {
const newProfile = this.deviceProfileMap[this.changes.profile_id]
if (newProfile && newProfile.device_id) {
this.loadDeviceModel({
type: 'all',
deviceId: newProfile.device_id
})
}
})
}
if (this.hasAdminNameChanged) {

@ -3,9 +3,7 @@
id="csc-page-pbx-devices"
class="q-pa-lg"
>
<csc-list-actions
class="row justify-center q-mb-xs"
>
<csc-list-actions class="row justify-center q-mb-xs">
<template
v-if="isDeviceAddFormDisabled"
#slot1
@ -18,9 +16,7 @@
@click="enableAddForm"
/>
</template>
<template
#slot2
>
<template #slot2>
<csc-list-action-button
v-if="!filtersEnabled"
icon="filter_alt"
@ -53,7 +49,7 @@
:model-image-map="deviceModelImageMap"
@cancel="disableDeviceAddForm"
@submit="createDevice"
@model-select-opened="loadDeviceModels('front_thumb')"
@model-select-opened="loadProfileThumbnails()"
/>
</div>
</q-slide-transition>
@ -63,7 +59,7 @@
:loading="isDeviceListRequesting"
class="q-pb-md"
@filter="applyFilter"
@model-select-opened="loadDeviceModels('front_thumb')"
@model-select-opened="loadProfileThumbnails()"
/>
</q-slide-transition>
<div
@ -76,11 +72,9 @@
@update:model-value="loadDeviceListFiltered"
/>
</div>
<csc-list-spinner
v-if="isDeviceListRequesting && !(isDeviceCreating || isDeviceRemoving || isDeviceUpdating)"
/>
<csc-list-spinner v-if="showSpinner" />
<q-list
v-if="!isDeviceListEmpty && deviceListVisibility === 'visible'"
v-if="!showSpinner && !isDeviceListEmpty && deviceListVisibility === 'visible'"
class="row justify-start items-start"
>
<csc-fade
@ -89,15 +83,15 @@
>
<csc-pbx-device
:key="device.id"
:loading="isDeviceLoading(device.id) || isDeviceListRequesting"
:loading="isItemLoading(device.id)"
:device="device"
:class="'col-xs-12 col-md-6 col-lg-4 csc-item-' + ((index % 2 === 0)?'odd':'even')"
:profile="deviceProfileMap[device.profile_id]"
:model="deviceModelMap[deviceProfileMap[device.profile_id].device_id]"
:model-image="deviceModelImageMap[deviceProfileMap[device.profile_id].device_id]"
:profile="getDeviceProfile(device.profile_id)"
:model="getDeviceModel(device.profile_id)"
:model-image="getDeviceModelImage(device.profile_id)"
@load-model="loadDeviceModel({
type: 'all',
deviceId: deviceProfileMap[device.profile_id].device_id
deviceId: device.profile_id ? getDeviceProfile(device.profile_id).device_id : null
})"
@remove="openDeviceRemovalDialog(device.id)"
/>
@ -169,17 +163,12 @@ export default {
'deviceModelList',
'deviceModelMap',
'deviceModelImageMap',
'subscriberList',
'subscriberMap',
'deviceModelListState',
'isDeviceModelListStateRequesting',
'deviceModelError',
'deviceProfileListState',
'deviceProfileListError'
]),
...mapGetters('pbx', [
'getSubscriberOptions',
'isSubscribersRequesting'
]),
...mapState('pbxDevices', [
'deviceRemoving',
'deviceList',
@ -189,12 +178,12 @@ export default {
'deviceCreationState',
'deviceRemovalState',
'deviceListState',
'deviceListError'
'deviceListError',
'deviceCreationError'
]),
...mapGetters('pbxDevices', [
'isDeviceListEmpty',
'isDeviceListRequesting',
'isDeviceExpanded',
'isDeviceListPaginationActive',
'isDeviceAddFormDisabled',
'isDeviceCreating',
@ -208,6 +197,75 @@ export default {
]),
hasFilters () {
return Object.keys(this.filters).length > 0
},
getDeviceIdFromProfile () {
return (profileId) => {
const profile = this.getDeviceProfile(profileId)
return profile && profile.device_id ? profile.device_id : null
}
},
getDeviceProfile () {
return (profileId) => {
if (!profileId) {
return {}
}
return this.deviceProfileMap[profileId] || {}
}
},
getDeviceModel () {
return (profileId) => {
if (!profileId) {
return {}
}
const deviceProfile = this.deviceProfileMap[profileId]
if (!deviceProfile) {
return {}
}
if (!deviceProfile.device_id) {
return {}
}
const deviceModel = this.deviceModelMap[deviceProfile.device_id]
if (!deviceModel) {
return {}
}
return deviceModel
}
},
getDeviceModelImage () {
return (profileId) => {
if (!profileId) {
return null
}
const deviceProfile = this.deviceProfileMap[profileId]
if (!deviceProfile) {
return null
}
if (!deviceProfile.device_id) {
return null
}
const deviceModelImage = this.deviceModelImageMap[deviceProfile.device_id]
if (!deviceModelImage) {
return null
}
return deviceModelImage
}
},
isItemLoading () {
return (deviceId) => this.isDeviceLoading(deviceId) ||
this.isDeviceListRequesting ||
this.isDeviceModelListStateRequesting
},
showSpinner () {
const deviceListDataIsNotReady = this.isDeviceListRequesting && !(this.isDeviceCreating || this.isDeviceRemoving || this.isDeviceUpdating)
return this.deviceProfileListState === RequestState.requesting || deviceListDataIsNotReady
}
},
watch: {
@ -243,6 +301,9 @@ export default {
}
}
},
async created () {
await this.loadProfiles()
},
mounted () {
this.$scrollTo(this.$parent.$el)
this.loadDeviceListFiltered()
@ -251,11 +312,10 @@ export default {
methods: {
...mapActions('pbx', [
'loadDeviceModel',
'loadDeviceModels'
'loadProfileThumbnails',
'loadProfiles'
]),
...mapMutations('pbxDevices', [
'expandDevice',
'collapseDevice',
'enableDeviceAddForm',
'disableDeviceAddForm',
'deviceRemovalRequesting',
@ -265,11 +325,7 @@ export default {
'loadDeviceList',
'loadDevicePreferencesList',
'createDevice',
'removeDevice',
'setDeviceStationName',
'setDeviceIdentifier',
'setDeviceProfile',
'setDeviceKeys'
'removeDevice'
]),
loadDeviceListFiltered (page) {
this.loadDeviceList({

@ -142,8 +142,8 @@ export default {
...mapState('pbx', [
'numberListState',
'numberListError',
'subcriberListState',
'subcriberListError'
'subscriberListState',
'subscriberListError'
]),
...mapGetters('pbxMsConfigs', [
@ -193,9 +193,9 @@ export default {
showGlobalError(this.numberListError)
}
},
subcriberListState (state) {
subscriberListState (state) {
if (state === RequestState.failed) {
showGlobalError(this.subcriberListError)
showGlobalError(this.subscriberListError)
}
}
},

@ -22,13 +22,12 @@ export default {
deviceCreationState: CreationState.initiated,
deviceListCurrentPage: 1,
deviceList: [],
deviceListError: null,
deviceListLastPage: null,
deviceListState: RequestState.initiated,
deviceListVisibility: 'visible',
deviceMapById: {},
devicePreferencesList: [],
deviceListError: null,
deviceMap: {},
devicePreferencesListState: RequestState.initiated,
devicePreferencesMap: {},
devicePreferencesRemovalState: RequestState.initiated,
@ -303,8 +302,6 @@ export default {
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) {
@ -315,7 +312,6 @@ export default {
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)

@ -10,6 +10,7 @@ import {
import { getSubscribers } from 'src/api/subscriber'
import { getNumbers } from 'src/api/user'
import numberFilter from 'src/filters/number'
import { showGlobalError } from 'src/helpers/ui'
import { RequestState } from 'src/store/common'
export default {
@ -26,7 +27,8 @@ export default {
seatMapById: {},
soundSetList: [],
soundSetMapByName: {},
deviceProfilesListState: RequestState.initiated,
deviceProfileListState: RequestState.initiated,
deviceProfileListError: null,
deviceProfileList: [],
deviceProfileMap: {},
deviceModelList: [],
@ -36,11 +38,9 @@ export default {
deviceModelImageSmallMap: {},
subscriberList: [],
subscriberListState: RequestState.initiated,
subcriberListError: null,
subscriberListError: null,
subscriberMap: {},
ncosMapByName: {},
deviceProfileListState: RequestState.initiated,
deviceProfileListError: null
ncosMapByName: {}
},
getters: {
pilot (state) {
@ -160,6 +160,9 @@ export default {
return state.deviceModelMap[deviceId] !== undefined
}
},
isDeviceModelListStateRequesting (state) {
return state.deviceModelListState === RequestState.requesting
},
isSubscribersRequesting (state) {
return state.subscriberListState === RequestState.requesting
},
@ -234,30 +237,39 @@ export default {
state.soundSetMapByName[soundSet.name] = soundSet
})
},
deviceProfilesListStateRequesting (state) {
state.deviceProfilesListState = RequestState.requesting
deviceProfileListStateRequesting (state) {
state.deviceProfileListState = RequestState.requesting
},
deviceProfilesListSucceeded (state, deviceProfileList) {
state.deviceProfilesListState = RequestState.succeeded
state.deviceProfileList = _.get(deviceProfileList, 'items', [])
deviceProfileListSucceeded (state, deviceProfileList) {
const newList = _.get(deviceProfileList, 'items', [])
// First remove existing items that we're about to replace with newer versions
// and keep only items whoseIds are not in the updated list
const existingIds = newList.map((item) => item.id)
const filteredExistingList = state.deviceProfileList.filter(
(existingItem) => !existingIds.includes(existingItem.id)
)
state.deviceProfileList = [...filteredExistingList, ...newList]
state.deviceProfileListState = RequestState.succeeded
state.deviceProfileMap = {}
state.deviceProfileList.forEach((deviceProfile) => {
state.deviceProfileMap[deviceProfile.id] = deviceProfile
})
},
deviceProfilesListFailed (state) {
state.deviceProfilesListState = RequestState.failed
deviceProfileListFailed (state) {
state.deviceProfileListState = RequestState.failed
},
deviceProfileRequesting (state) {
state.deviceProfilesListState = RequestState.requesting
state.deviceProfileListState = RequestState.requesting
},
deviceProfileSucceeded (state, deviceProfile) {
state.deviceProfilesListState = RequestState.succeeded
state.deviceProfileListState = RequestState.succeeded
state.deviceProfileList = [...state.deviceProfileList, deviceProfile]
state.deviceProfileMap[deviceProfile.id] = deviceProfile
},
deviceProfileFailed (state) {
state.deviceProfilesListState = RequestState.failed
state.deviceProfileListState = RequestState.failed
},
deviceModelSucceeded (state, deviceModel) {
state.deviceModelListState = RequestState.succeeded
@ -285,7 +297,7 @@ export default {
state.deviceModelError = options.error
},
subscribersRequesting (state) {
state.subcriberListState = RequestState.requesting
state.subscriberListState = RequestState.requesting
state.subscriberList = []
},
subscribersSucceeded (state, subscribers) {
@ -296,20 +308,18 @@ export default {
})
},
subscribersFailed (state, err) {
state.subcriberListState = RequestState.failed
state.subcriberListError = err
state.subscriberListState = RequestState.failed
state.subscriberListError = err
}
},
actions: {
async loadProfiles (context) {
if (context.state.deviceProfileList.length === 0) {
context.commit('deviceProfilesListStateRequesting')
try {
const profiles = await getAllProfiles()
context.commit('deviceProfilesListSucceeded', profiles)
} catch (err) {
context.commit('deviceProfilesListFailed', err.message)
}
context.commit('deviceProfileListStateRequesting')
try {
const profiles = await getAllProfiles()
context.commit('deviceProfileListSucceeded', profiles)
} catch (err) {
context.commit('deviceProfileListFailed', err.message)
}
},
async loadProfileById (context, deviceId) {
@ -321,6 +331,47 @@ export default {
context.commit('deviceProfileFailed')
}
},
async loadProfileThumbnails (context) {
const requests = []
context.state.deviceProfileList.forEach((deviceProfile) => {
const isFrontThumbnailCached = context.state.deviceModelImageSmallMap[deviceProfile.device_id] !== undefined
if (!isFrontThumbnailCached) {
requests.push(
getModelFrontThumbnailImage(deviceProfile.device_id)
.then((thumbnail) => ({
deviceId: deviceProfile.device_id,
thumbnail
}))
.catch((error) => ({
deviceId: deviceProfile.device_id,
error
}))
)
}
})
if (requests.length > 0) {
try {
const results = await Promise.all(requests)
results.forEach((result) => {
if (result.thumbnail) {
context.commit('deviceModelSucceeded', {
modelImageThumbnail: result.thumbnail
})
} else if (result.error) {
// Silent warning as it's not a critical error
// and we can still use the device profile without the thumbnail
// eslint-disable-next-line no-console
console.warn(`Failed to load thumbnail for device ${result.deviceId}:`, result.error)
}
})
} catch (error) {
showGlobalError('Failed to load profile thumbnails:', error)
}
}
},
async loadDeviceModel (context, payload) {
try {
const isFrontCached = context.state.deviceModelImageMap[payload.deviceId] !== undefined
@ -373,6 +424,10 @@ export default {
}
context.commit('deviceModelSucceeded', deviceModel)
} catch (err) {
// Note: the way it is implemented at the moment
// if any of the promises fails, the whole action fails
// and only the first error is reported. This needs refactoring
// to handle each request separately and report errors accordingly.
context.commit('deviceModelFailed', {
deviceModelId: payload.deviceId,
error: err.message

@ -3,10 +3,7 @@ import { i18n } from 'boot/i18n'
import _ from 'lodash'
import QRCode from 'qrcode'
import { date } from 'quasar'
import {
apiDownloadFile,
httpApi
} from 'src/api/common'
import { apiDownloadFile, httpApi } from 'src/api/common'
import { callInitialize } from 'src/api/ngcp-call'
import {
changePassword,

Loading…
Cancel
Save