MT#64569 - Destination priority improvements

* Implemented priority flow as sort -> normalize to 0..n-1
    across fetch/create/edit/remove
    * On creation handles priority incrementally
    * On edit, arranges and normalizes (if needed) priority values
  * Enforced the same move rules in store action (moveDestination)
    to block invalid reorders.
  * Removes references to LocalSubscriber
  * Fixes unit tests

Change-Id: Ib64cc30ea75141692085ec38df5c528565389469
(cherry picked from commit 38d6e2089c)
(cherry picked from commit f0d78c695f)
mr12.5.1
Giancarlo Errigo Mattos 3 months ago committed by Giancarlo Mattos
parent 5a2497196b
commit 59b6d1734c

@ -0,0 +1,135 @@
import sipUriParse from 'src/sip-uri-parse'
export const DestinationType = {
VoiceBox: 'VoiceBox',
Conference: 'Conference',
Fax2Mail: 'Fax2Mail',
CallingCard: 'CallingCard',
CallThrough: 'CallThrough',
AutoAttendant: 'AutoAttendant',
OfficeHoursAnnouncement: 'OfficeHoursAnnouncement',
CustomAnnouncement: 'CustomAnnouncement',
LocalSubscriber: 'LocalSubscriber',
ManagerSecretary: 'ManagerSecretary',
Application: 'Application',
Number: 'Number'
}
export function parseSipUri (sipUri) {
const parsedUri = sipUriParse(sipUri)
const host = parsedUri.host
const username = parsedUri.username
let destinationType
if (host.endsWith('voicebox.local')) {
destinationType = DestinationType.VoiceBox
} else if (host.endsWith('conference.local')) {
destinationType = DestinationType.Conference
} else if (host.endsWith('fax2mail.local')) {
destinationType = DestinationType.Fax2Mail
} else if (username === 'callingcard' && host.endsWith('app.local')) {
destinationType = DestinationType.CallingCard
} else if (username === 'callthrough' && host.endsWith('app.local')) {
destinationType = DestinationType.CallThrough
} else if (username === 'auto-attendant' && host.endsWith('app.local')) {
destinationType = DestinationType.AutoAttendant
} else if (username === 'office-hours' && host.endsWith('app.local')) {
destinationType = DestinationType.OfficeHoursAnnouncement
} else if (username === 'custom-hours' && host.endsWith('app.local')) {
destinationType = DestinationType.CustomAnnouncement
} else if (username === 'localuser' && host.endsWith('local')) {
destinationType = DestinationType.LocalSubscriber
} else if (host.endsWith('managersecretary.local')) {
destinationType = DestinationType.ManagerSecretary
} else if (host.endsWith('app.local')) {
destinationType = DestinationType.Application
} else {
destinationType = DestinationType.Number
}
return {
destinationType,
parsedUri
}
}
function toNumericPriority (destination) {
const parsed = Number(destination?.priority)
return Number.isFinite(parsed) ? parsed : 0
}
function isTerminalDestination (destination) {
const destinationType = parseSipUri(destination?.destination || '').destinationType
return destinationType !== DestinationType.Number
}
function allDestinationsSharePriority (destinations) {
if (!Array.isArray(destinations) || destinations.length === 0) {
return true
}
const firstPriority = toNumericPriority(destinations[0])
return destinations.every((destination) => toNumericPriority(destination) === firstPriority)
}
function arePrioritiesSequential (destinations) {
return destinations.every((destination, index) => toNumericPriority(destination) === index)
}
function getPrioritySortedIndexEntries (destinations) {
const entries = destinations.map((destination, index) => ({
destination,
index,
priority: toNumericPriority(destination)
}))
const nonTerminalEntries = []
const terminalEntries = []
entries.forEach((entry) => {
if (isTerminalDestination(entry.destination)) {
terminalEntries.push(entry)
return
}
nonTerminalEntries.push(entry)
})
const sortByPriorityThenIndex = (left, right) => {
if (left.priority !== right.priority) {
return left.priority - right.priority
}
return left.index - right.index
}
nonTerminalEntries.sort(sortByPriorityThenIndex)
terminalEntries.sort(sortByPriorityThenIndex)
return [
...nonTerminalEntries,
...terminalEntries
]
}
export function sortDestinationsByPriority (destinations) {
if (!Array.isArray(destinations) || destinations.length <= 1) {
return Array.isArray(destinations) ? [...destinations] : []
}
if (allDestinationsSharePriority(destinations)) {
return [...destinations]
}
return getPrioritySortedIndexEntries(destinations).map((entry) => entry.destination)
}
export function normalizePriorities (destinations) {
const sortedDestinations = sortDestinationsByPriority(destinations)
let normalizedDestinations
if (!Array.isArray(destinations) || destinations.length === 0) {
normalizedDestinations = []
} else if (arePrioritiesSequential(sortedDestinations)) {
normalizedDestinations = [...sortedDestinations]
} else if (allDestinationsSharePriority(sortedDestinations)) {
normalizedDestinations = sortedDestinations.map((destination, index) => ({
...destination,
priority: index
}))
} else {
normalizedDestinations = sortedDestinations.map((destination, normalizedPriority) => ({
...destination,
priority: normalizedPriority
}))
}
return normalizedDestinations
}

@ -1,56 +1,5 @@
import _ from 'lodash'
import sipUriParse from 'src/sip-uri-parse'
const DestinationType = {
VoiceBox: 'VoiceBox',
Conference: 'Conference',
Fax2Mail: 'Fax2Mail',
CallingCard: 'CallingCard',
CallThrough: 'CallThrough',
AutoAttendant: 'AutoAttendant',
OfficeHoursAnnouncement: 'OfficeHoursAnnouncement',
CustomAnnouncement: 'CustomAnnouncement',
LocalSubscriber: 'LocalSubscriber',
ManagerSecretary: 'ManagerSecretary',
Application: 'Application',
Number: 'Number'
}
function parseSipUri (sipUri) {
const parsedUri = sipUriParse(sipUri)
const host = parsedUri.host
const username = parsedUri.username
let destinationType
if (host.endsWith('voicebox.local')) {
destinationType = DestinationType.VoiceBox
} else if (host.endsWith('conference.local')) {
destinationType = DestinationType.Conference
} else if (host.endsWith('fax2mail.local')) {
destinationType = DestinationType.Fax2Mail
} else if (username === 'callingcard' && host.endsWith('app.local')) {
destinationType = DestinationType.CallingCard
} else if (username === 'callthrough' && host.endsWith('app.local')) {
destinationType = DestinationType.CallThrough
} else if (username === 'auto-attendant' && host.endsWith('app.local')) {
destinationType = DestinationType.AutoAttendant
} else if (username === 'office-hours' && host.endsWith('app.local')) {
destinationType = DestinationType.OfficeHoursAnnouncement
} else if (username === 'custom-hours' && host.endsWith('app.local')) {
destinationType = DestinationType.CustomAnnouncement
} else if (username === 'localuser' && host.endsWith('local')) {
destinationType = DestinationType.LocalSubscriber
} else if (host.endsWith('managersecretary.local')) {
destinationType = DestinationType.ManagerSecretary
} else if (host.endsWith('app.local')) {
destinationType = DestinationType.Application
} else {
destinationType = DestinationType.Number
}
return {
destinationType,
parsedUri
}
}
import { DestinationType, parseSipUri } from 'src/helpers/call-forwarding-destinations'
export default {
methods: {

@ -24,6 +24,7 @@ import {
post, put
} from 'src/api/common'
import { i18n } from 'src/boot/i18n'
import { normalizePriorities } from 'src/helpers/call-forwarding-destinations'
import { showGlobalError, showGlobalWarning } from 'src/helpers/ui'
import { v4 } from 'uuid'
@ -31,10 +32,10 @@ const DEFAULT_RING_TIMEOUT = 60
const DEFAULT_PRIORITY = 0
const WAIT_IDENTIFIER = 'csc-cf-mappings-full'
function createDefaultDestination (destination, defaultAnnouncementId) {
function createDefaultDestination (destination, defaultAnnouncementId, priority = DEFAULT_PRIORITY) {
const payload = {
destination: destination || ' ',
priority: DEFAULT_PRIORITY,
priority,
timeout: DEFAULT_RING_TIMEOUT
}
if (destination === 'customhours') {
@ -47,10 +48,12 @@ export async function loadMappingsFull ({ dispatch, commit, rootGetters }, id) {
dispatch('wait/start', WAIT_IDENTIFIER, { root: true })
const subscriberId = id || rootGetters['user/getSubscriberId']
const mappingData = await cfLoadMappingsFull(subscriberId)
const mappings = mappingData[0]
const destinationSets = mappingData[1].items
commit('dataSucceeded', {
mappings: mappingData[0],
destinationSets: mappingData[1].items,
mappings,
destinationSets,
sourceSets: mappingData[2].items,
timeSets: mappingData[3].items
})
@ -154,11 +157,12 @@ export async function updateDestination ({ dispatch, commit, state }, payload) {
dispatch('wait/start', 'csc-cf-destination-set-update', { root: true })
const destinations = _.cloneDeep(state.destinationSetMap[payload.destinationSetId].destinations)
destinations[payload.destinationIndex].destination = payload.destination
const normalizedDestinations = normalizePriorities(destinations)
await patchReplace({
resource: 'cfdestinationsets',
resourceId: payload.destinationSetId,
fieldPath: 'destinations',
value: destinations
value: normalizedDestinations
})
const destinationSets = await cfLoadDestinationSets()
commit('dataSucceeded', {
@ -170,12 +174,17 @@ export async function updateDestination ({ dispatch, commit, state }, payload) {
export async function addDestination ({ dispatch, commit, state, rootGetters }, payload) {
dispatch('wait/start', WAIT_IDENTIFIER, { root: true })
const destinations = _.cloneDeep(state.destinationSetMap[payload.destinationSetId].destinations)
destinations.push(createDefaultDestination(payload.destination, payload.defaultAnnouncementId))
const normalizedDestinations = normalizePriorities(destinations)
normalizedDestinations.push(createDefaultDestination(
payload.destination,
payload.defaultAnnouncementId,
normalizedDestinations.length
))
await patchReplace({
resource: 'cfdestinationsets',
resourceId: payload.destinationSetId,
fieldPath: 'destinations',
value: destinations
value: normalizedDestinations
})
const destinationSets = await cfLoadDestinationSets()
commit('dataSucceeded', {
@ -209,11 +218,12 @@ export async function removeDestination ({ dispatch, commit, state }, payload) {
}
return $updatedDestinations
}, [])
const normalizedDestinations = normalizePriorities(updatedDestinations)
await patchReplace({
resource: 'cfdestinationsets',
resourceId: payload.destinationSetId,
fieldPath: 'destinations',
value: updatedDestinations
value: normalizedDestinations
})
const destinationSets = await cfLoadDestinationSets()
commit('dataSucceeded', {
@ -226,12 +236,13 @@ export async function updateDestinationTimeout ({ dispatch, commit, state }, pay
dispatch('wait/start', WAIT_IDENTIFIER, { root: true })
const destinations = _.cloneDeep(state.destinationSetMap[payload.destinationSetId].destinations)
destinations[payload.destinationIndex].timeout = payload.destinationTimeout
const normalizedDestinations = normalizePriorities(destinations)
try {
await patchReplace({
resource: 'cfdestinationsets',
resourceId: payload.destinationSetId,
fieldPath: 'destinations',
value: destinations
value: normalizedDestinations
})
} catch (e) {
showGlobalError(e.message)
@ -628,11 +639,12 @@ export async function updateAnnouncement ({ dispatch, commit, state }, payload)
try {
const destinations = _.cloneDeep(state.destinationSetMap[payload.destinationSetId].destinations)
destinations[payload.destinationIndex].announcement_id = payload.announcementId
const normalizedDestinations = normalizePriorities(destinations)
await patchReplace({
resource: 'cfdestinationsets',
resourceId: payload.destinationSetId,
fieldPath: 'destinations',
value: destinations
value: normalizedDestinations
})
} catch (e) {
showGlobalError(e.message)

@ -1,11 +1,19 @@
import { sortDestinationsByPriority } from 'src/helpers/call-forwarding-destinations'
export function dataSucceeded (state, res) {
if (res.destinationSets) {
const destinationSetMap = {}
res.destinationSets.forEach((destinationSet) => {
const orderedDestinationSets = res.destinationSets.map((destinationSet) => {
return {
...destinationSet,
destinations: sortDestinationsByPriority(destinationSet.destinations)
}
})
orderedDestinationSets.forEach((destinationSet) => {
destinationSetMap[destinationSet.id] = destinationSet
})
state.destinationSetMap = destinationSetMap
state.destinationSets = res.destinationSets
state.destinationSets = orderedDestinationSets
}
if (res.sourceSets) {
const sourceSetMap = {}

Loading…
Cancel
Save