TT#28600 Implement Destinations Store

What has been done:
- TT#28055, CallForwarding: As a Customer, I want to see all call
  forwarding rules that apply "Always" (INCL all subtasks)
- TT#28059, CallForwarding: As a Customer, I want to remove Destinations

Change-Id: Id1e95dbaf1e55dba25f855ef9ad61352ededd5bb
changes/28/17928/12
raxelsen 7 years ago committed by Hans-Peter Herzog
parent 3eb81fa09f
commit 80c14dcb68

@ -82,3 +82,96 @@ export function getDestinationsets(id) {
});
});
}
export function loadAlwaysEverybodyDestinations(subscriberId) {
return new Promise((resolve, reject)=>{
Promise.resolve().then(()=>{
return getMappings(subscriberId);
}).then((mappings)=>{
let cfuPromises = [];
let cfnaPromises = [];
let cfbPromises = [];
if(_.has(mappings, 'cfu') && _.isArray(mappings.cfu) && mappings.cfu.length > 0) {
mappings.cfu.forEach((cfuMapping)=>{
if (cfuMapping.timeset_id === null && cfuMapping.sourceset_id === null) {
cfuPromises.push(getDestinationsetById(cfuMapping.destinationset_id));
}
});
}
if(_.has(mappings, 'cfna') && _.isArray(mappings.cfna) && mappings.cfna.length > 0) {
mappings.cfna.forEach((cfnaMapping)=>{
if (cfnaMapping.timeset_id === null && cfnaMapping.sourceset_id === null) {
cfnaPromises.push(getDestinationsetById(cfnaMapping.destinationset_id));
}
});
}
if(_.has(mappings, 'cfb') && _.isArray(mappings.cfb) && mappings.cfb.length > 0) {
mappings.cfb.forEach((cfbMapping)=>{
if (cfbMapping.timeset_id === null && cfbMapping.sourceset_id === null) {
cfbPromises.push(getDestinationsetById(cfbMapping.destinationset_id));
}
});
}
return Promise.all([
Promise.all(cfuPromises),
Promise.all(cfnaPromises),
Promise.all(cfbPromises)
]);
}).then((res)=>{
resolve({
online: res[0],
offline: res[1],
busy: res[2]
});
}).catch((err)=>{
reject(err);
});
});
}
export function getDestinationsetById(id) {
return new Promise((resolve, reject)=>{
Vue.http.get('/api/cfdestinationsets/' + id).then((res)=>{
let destinationset = getJsonBody(res.body);
delete destinationset['_links'];
resolve(destinationset);
}).catch((err)=>{
reject(err);
});
});
}
export function deleteDestinationFromDestinationset(options) {
let headers = {
'Content-Type': 'application/json-patch+json'
};
return new Promise((resolve, reject) => {
Vue.http.patch('/api/cfdestinationsets/' + options.id, [{
op: 'replace',
path: '/destinations',
value: options.data
}], { headers: headers }).then(result => {
if (options.deleteDestinationset) {
deleteDestinationsetById(options.id).then((res) => {
resolve(res);
}).catch((err) => {
console.log(err);
});
} else {
resolve(result);
};
}).catch(err => {
reject(err);
});
});
}
export function deleteDestinationsetById(id) {
return new Promise((resolve, reject) => {
Vue.http.delete('/api/cfdestinationsets/' + id).then(result => {
resolve(result);
}).catch(err => {
reject(err);
});
});
}

@ -1,5 +1,6 @@
<template>
<csc-page title="After Hours"></csc-page>
<csc-page :title="$t('pages.callForward.titles.afterHours')">
</csc-page>
</template>
<script>

@ -1,19 +1,48 @@
<template>
<csc-page title="Always"></csc-page>
<csc-page :title="$t('pages.callForward.titles.always')">
<q-card class="dest-card">
<csc-destinations :title="$t('pages.callForward.whenOnline')"
:group="destinations.online"
icon="signal_wifi_4_bar">
</csc-destinations>
<csc-destinations :title="$t('pages.callForward.whenBusy')"
:group="destinations.busy"
icon="record_voice_over">
</csc-destinations>
<csc-destinations :title="$t('pages.callForward.whenOffline')"
:group="destinations.offline"
icon="signal_wifi_off">
</csc-destinations>
</q-card>
</csc-page>
</template>
<script>
import CscPage from '../../CscPage'
import CscPage from '../../CscPage'
import CscDestinations from './CscDestinations'
import { QCard } from 'quasar-framework'
export default {
mounted() {
this.$store.dispatch('callForward/loadAlwaysEverybodyDestinations');
},
data () {
return {
}
},
components: {
CscPage
QCard,
CscPage,
CscDestinations
},
methods: {
},
computed: {
destinations() {
return this.$store.state.callForward.alwaysEverybodyDestinations;
}
}
}
</script>
<style>
<style lang="stylus">
</style>

@ -1,5 +1,6 @@
<template>
<csc-page title="Company Hours"></csc-page>
<csc-page :title="$t('pages.callForward.titles.companyHours')">
</csc-page>
</template>
<script>

@ -0,0 +1,95 @@
<template>
<div>
<q-item highlight v-for="(destination, index) in destinations">
<q-item-main>
<div class="dest-row">
<span v-if="index == 0"> {{ $t('pages.callForward.firstRing') }} </span>
<span v-else-if="index > 0"> {{ $t('pages.callForward.thenRing') }} </span>
<span class="dest-values"> {{ destination.destination | numberFormat }} </span>
<span v-if="isNumber(destination.destination)">
<span> {{ $t('pages.callForward.for') }} </span>
<span class="dest-values">{{ destination.timeout }}</span>
<span> {{ $t('pages.callForward.secs') }} </span>
</span>
</div>
</q-item-main>
<q-item-side right>
<q-btn color="negative" flat icon="delete" @click="deleteDestination(index)">{{ $t('buttons.remove') }}</q-btn>
</q-item-side>
</q-item>
</div>
</template>
<script>
import numberFormat from '../../../filters/number-format'
import _ from 'lodash'
import { showToast } from '../../../helpers/ui'
import { QItem, QItemSide, Dialog, Toast, QBtn, QItemMain } from 'quasar-framework'
export default {
name: 'csc-destination',
props: [
'destinations',
'destinationsetId'
],
components: {
QItem,
QItemMain,
QItemSide,
Dialog,
Toast,
QBtn
},
computed: {
},
methods: {
isNumber(destination) {
let dest = destination.split(/:|@/);
return !isNaN(dest[1]);
},
deleteDestination(index) {
let clonedDestinations = _.cloneDeep(this.destinations);
let indexInt = parseInt(index);
let store = this.$store;
let removeDestination = numberFormat(this.destinations[index].destination);
let self = this;
let isLastDestination = this.destinations.length === 1;
clonedDestinations.splice(indexInt, 1);
Dialog.create({
title: self.$t('pages.callForward.removeDialogTitle'),
message: self.$t('pages.callForward.removeDialogText', {
destination: removeDestination
}),
buttons: [
self.$t('buttons.cancel'),
{
label: self.$t('buttons.remove'),
color: 'negative',
handler () {
store.dispatch('callForward/deleteDestinationFromDestinationset', {
id: self.destinationsetId,
data: clonedDestinations,
deleteDestinationset: isLastDestination }).then((result) => {
store.dispatch('callForward/loadAlwaysEverybodyDestinations');
showToast(self.$t('pages.callForward.removeSuccessMessage', {
destination: removeDestination
}));
}).catch((err) => {
showToast(self.$t('pages.callForward.removeErrorMessage'));
});
}
}
]
});
}
}
}
</script>
<style lang="stylus">
@import '~variables'
.dest-row
display inline-block
width 90%
.dest-values
font-weight 500
</style>

@ -0,0 +1,55 @@
<template>
<div>
<q-card-title class="dest-title">
<q-icon :name="icon" class="dest-icon" />
{{ title }}
</q-card-title>
<q-card-main>
<q-list no-border>
<div v-if="group.length === 0">
<q-item>
<div class="dest-row">
<span> {{ $t('pages.callForward.forwardToNowhere') }} </span>
</div>
</q-item>
</div>
<div v-else v-for="destinationset in group">
<csc-destination :destinations="destinationset.destinations" :destinationset-id="destinationset.id">
</csc-destination>
</div>
</q-list>
</q-card-main>
</div>
</template>
<script>
import CscDestination from './CscDestination'
import { QCardTitle, QCardMain, QList,
QItem, QIcon } from 'quasar-framework'
export default {
name: 'csc-destinations',
props: [
'title',
'icon',
'group'
],
components: {
QCardTitle,
QCardMain,
QList,
QItem,
QIcon,
CscDestination
}
}
</script>
<style lang="stylus">
@import '~variables'
.dest-row
inline-block
.dest-title
color $primary
.dest-icon
margin-right 5px
</style>

@ -1,12 +1,16 @@
import url from 'url';
import { PhoneNumberUtil, PhoneNumberFormat } from 'google-libphonenumber';
import { format } from 'quasar-framework'
const { capitalize } = format
var phoneUtil = PhoneNumberUtil.getInstance();
export default function(number) {
try {
let phoneNumber = url.parse(number, true).auth.split(':')[0];
if (isNaN(phoneNumber)) {
phoneNumber = normalizeDestination(url.parse(number, true));
}
return normalizeNumber(phoneNumber);
} catch(err1) {
return normalizeNumber(number);
@ -35,3 +39,15 @@ export function rawNumber(number) {
}
return '';
}
export function normalizeDestination(destination) {
let normalizedDestination;
if (destination.host == 'app.local') {
normalizedDestination = destination.auth;
} else if (destination.host == 'voicebox.local') {
normalizedDestination = 'Voicemail';
} else {
normalizedDestination = capitalize(destination.host.split('.')[0]);
}
return normalizedDestination;
}

@ -114,6 +114,26 @@
},
"timeUpdatedMsg": "Time updated!",
"recurrenceUpdatedMsg": "Recurrence updated!"
},
"callForward": {
"titles": {
"always": "Always",
"companyHours": "Company Hours",
"afterHours": "After Hours"
},
"whenOnline": "When I am online ...",
"whenBusy": "When I am busy ...",
"whenOffline": "When I am offline ...",
"forwardTo": "forward to",
"firstRing": "first ring",
"thenRing": "then ring",
"for": "for",
"secs": "secs",
"forwardToNowhere": "forward to nowhere",
"removeDialogTitle": "Remove call forward destination",
"removeDialogText": "You are about to remove the destination {destination}",
"removeSuccessMessage": "Removed destination {destination}",
"removeErrorMessage": "An error occured while trying to delete the destination. Please try again."
}
},
"call": {

@ -3,7 +3,9 @@
import _ from 'lodash';
import { getSourcesets, getDestinationsets, getTimesets,
getMappings } from '../api/call-forward';
getMappings, loadAlwaysEverybodyDestinations,
deleteDestinationFromDestinationset,
deleteDestinationsetById } from '../api/call-forward';
export default {
namespaced: true,
@ -11,7 +13,12 @@ export default {
mappings: null,
sourcesets: null,
timesets: null,
destinationsets: null
destinationsets: null,
alwaysEverybodyDestinations: {
online: [],
busy: [],
offline: []
}
},
mutations: {
loadMappings(state, result) {
@ -25,6 +32,9 @@ export default {
},
loadDestinationsets(state, result) {
state.destinationsets = result;
},
loadAlwaysEverybodyDestinations(state, result) {
state.alwaysEverybodyDestinations = result;
}
},
actions: {
@ -53,7 +63,7 @@ export default {
getTimesets(localStorage.getItem('subscriberId'))
.then(result => {
context.commit('loadTimesets', result);
}).catch(err => {
}).catch((err) => {
reject(err);
});
});
@ -67,6 +77,33 @@ export default {
reject(err);
});
});
},
loadAlwaysEverybodyDestinations(context) {
return new Promise((resolve, reject)=>{
loadAlwaysEverybodyDestinations(localStorage.getItem('subscriberId')).then((result)=>{
context.commit('loadAlwaysEverybodyDestinations', result);
})
});
},
deleteDestinationFromDestinationset(context, options) {
return new Promise((resolve, reject) => {
deleteDestinationFromDestinationset(options)
.then((result) => {
resolve(result);
}).catch((err) => {
reject(err);
});
});
},
deleteDestinationsetById(context, id) {
return new Promise((resolve, reject) => {
deleteDestinationsetById(id)
.then((result) => {
resolve(result);
}).catch((err) => {
reject(err);
});
});
}
}
};

@ -4,7 +4,8 @@
import Vue from 'vue';
import VueResource from 'vue-resource';
import { getMappings, getSourcesets, getTimesets,
getDestinationsets } from '../../src/api/call-forward';
getDestinationsets, getDestinationsetById,
deleteDestinationFromDestinationset } from '../../src/api/call-forward';
import { assert } from 'chai';
Vue.use(VueResource);
@ -229,4 +230,133 @@ describe('CallForward', function(){
});
});
it('should get all call forward destinationset by id', function(done){
let data = {
"_links": {
"collection": {
"href": "/api/cfdestinationsets/"
},
"curies": {
"href": "http://purl.org/sipwise/ngcp-api/#rel-{rel}",
"name": "ngcp",
"templated": true
},
"ngcp:subscribers": {
"href": "/api/subscribers/309"
},
"profile": {
"href": "http://purl.org/sipwise/ngcp-api/"
},
"self": {
"href": "/api/cfdestinationsets/3"
}
},
"destinations": [
{
"announcement_id": null,
"destination": "sip:3333@192.168.178.23",
"priority": 1,
"simple_destination": "3333",
"timeout": 60
},
{
"announcement_id": null,
"destination": "sip:3333@192.168.178.23",
"priority": 1,
"simple_destination": "3333",
"timeout": 90
},
{
"announcement_id": null,
"destination": "sip:vmu04ee2ae6-aa11-4cee-82fe-5ac57c11174e@voicebox.local",
"priority": 1,
"timeout": 300
},
{
"announcement_id": null,
"destination": "sip:04ee2ae6-aa11-4cee-82fe-5ac57c11174e@fax2mail.local",
"priority": 1,
"timeout": 300
}
],
"id": 3,
"name": "t2"
};
let responseData = {
"destinations": [
{
"announcement_id": null,
"destination": "sip:3333@192.168.178.23",
"priority": 1,
"simple_destination": "3333",
"timeout": 60
},
{
"announcement_id": null,
"destination": "sip:3333@192.168.178.23",
"priority": 1,
"simple_destination": "3333",
"timeout": 90
},
{
"announcement_id": null,
"destination": "sip:vmu04ee2ae6-aa11-4cee-82fe-5ac57c11174e@voicebox.local",
"priority": 1,
"timeout": 300
},
{
"announcement_id": null,
"destination": "sip:04ee2ae6-aa11-4cee-82fe-5ac57c11174e@fax2mail.local",
"priority": 1,
"timeout": 300
}
],
"id": 3,
"name": "t2"
};
Vue.http.interceptors = [];
Vue.http.interceptors.unshift((request, next)=>{
next(request.respondWith(JSON.stringify(data), {
status: 200
}));
});
getDestinationsetById('3').then((result)=>{
assert.deepEqual(result, responseData);
done();
}).catch((err)=>{
done(err);
});
});
it('should delete destination from call forward destinationset', function(done){
let options = {
id: 3,
data: {
"announcement_id": null,
"destination": "sip:3333@192.168.178.23",
"priority": 1,
"simple_destination": "3333",
"timeout": 60
},
deleteDestinationset: false
};
Vue.http.interceptors = [];
Vue.http.interceptors.unshift((request, next)=>{
next(request.respondWith(JSON.stringify({}), {
status: 204
}));
});
deleteDestinationFromDestinationset(options).then((result)=>{
assert.isOk(result);
done();
}).catch((err)=>{
done(err);
});
});
});

@ -0,0 +1,40 @@
'use strict';
import CallForwardModule from '../../src/store/call-forward';
import { assert } from 'chai';
describe('CallForward', function(){
it('should load always everybody destinations', function(){
let state = {
alwaysEverybodyDestinations: [
]
};
let data = {
busy: [],
offline: [{
destinations: [{
"announcement_id": null,
"destination": "sip:3333@192.168.178.23",
"priority": 1,
"simple_destination": "3333",
"timeout": 60
},
{
"announcement_id": null,
"destination": "sip:2222@192.168.178.23",
"priority": 1,
"simple_destination": "2222",
"timeout": 300
}],
id: 3,
name: "csc_destinationset_1"
}],
online: []
};
CallForwardModule.mutations.loadAlwaysEverybodyDestinations(state, data);
assert.deepEqual(state.alwaysEverybodyDestinations, data);
});
});
Loading…
Cancel
Save