TT#44664 Upload a custom "unavail" greeting sound

What has been done:
- TT#44664, Voicebox: As a Customer, I want to upload a custom
  "unavailable" greeting sound
- TT#44665, Voicebox: As a Customer, I want to delete/reset the custom
  "unavailable" greeting sound

Change-Id: Ifd266e0af78a6844135a8bd024998b70789efe5e
changes/38/24238/9
raxelsen 7 years ago
parent f872d4de20
commit 399de962c5

@ -92,11 +92,12 @@ export function deleteVoiceboxGreetingById(id) {
});
}
export function createNewGreeting(formData, onProgress) {
export function createNewGreeting(formData, onProgress, type) {
return new Promise((resolve, reject) => {
let requestKey = `previous${_.capitalize(type)}Request`;
Vue.http.post('api/voicemailgreetings/', formData, {
before(request) {
Vue.previousRequest = request;
Vue[requestKey] = request;
},
progress(e) {
if (e.lengthComputable) {
@ -124,13 +125,13 @@ export function uploadGreeting(options) {
Promise.resolve().then(() => {
return getVoiceboxGreetingByType({
id: options.data.subscriber_id,
type: options.data.type
type: options.data.dir
});
}).then((greetings) => {
if (_.some(greetings.items, { dir: options.data.dir })) {
deleteVoiceboxGreetingById(greetings.items[0].id);
}
return createNewGreeting(formData, options.onProgress);
return createNewGreeting(formData, options.onProgress, options.data.dir);
}).then(() => {
resolve();
}).catch((err) => {
@ -139,6 +140,7 @@ export function uploadGreeting(options) {
});
}
export function abortPreviousRequest() {
Vue.previousRequest.abort();
export function abortPreviousRequest(name) {
let requestKey = `previous${_.capitalize(name)}Request`;
Vue[requestKey].abort();
}

@ -11,6 +11,7 @@
/>
<input
v-show="false"
:accept="fileTypes"
ref="fileUpload"
type="file"
@change="inputChange"
@ -130,7 +131,7 @@
cancel() {
this.selectedFile = null;
this.$refs.fileUpload.value = null;
if(this.uploading) {
if (this.uploading) {
this.abort();
}
},
@ -152,11 +153,16 @@
<style lang="stylus" rel="stylesheet/stylus">
@import '../../themes/quasar.variables';
.csc-file-upload-actions
padding-top $flex-gutter-xs
.csc-upload-field
margin-bottom 40px
.q-field-icon
color $primary
.csc-upload-progress-field
margin 10px 0 5px 0
@ -169,4 +175,5 @@
.upload-progress
height 20px
</style>

@ -1,202 +0,0 @@
<template>
<q-field
class="csc-form-field upload-field"
icon="music_note"
>
<label
for="voicemail-file-upload"
class="upload-label"
>
{{ $t('voicebox.label.busyGreeting') }}
</label>
<span
v-show="selectedFile.length === 0"
>
<slot name="status-label" />
</span>
<div
v-show="requesting"
class="row no-wrap progress-field"
>
<q-chip
square
color="primary"
class="upload-chip"
>
{{ `${progress}%` }}
</q-chip>
<q-progress
stripe
animate
color="primary"
:percentage="progress"
class="upload-progress"
/>
</div>
<div class="upload-filename">
{{ selectedFile }}
</div>
<span
v-show="selectedFile.length === 0 && id"
>
<slot name="extra-buttons" />
</span>
<q-btn
v-show="selectedFile.length > 0"
flat
color="negative"
icon="cancel"
@click="cancel"
>
{{ $t('buttons.cancel') }}
</q-btn>
<q-btn
v-show="!requesting"
flat
color="primary"
@click="$refs.upload.click()"
icon="folder"
>
{{ selectLabel }}
</q-btn>
<q-btn
flat
v-show="selectedFile.length > 0 && !requesting"
color="primary"
icon="cloud_upload"
@click="upload"
>
{{ $t('buttons.upload') }}
</q-btn>
<input
ref="upload"
id="voicemail-file-upload"
type="file"
:accept="fileTypes"
@change="processFile($event)"
/>
</q-field>
</template>
<script>
import {
QField,
QInput,
QBtn,
QProgress,
QChip
} from 'quasar-framework'
export default {
name: 'csc-upload-file',
props: [
'progress',
'requesting',
'fileTypes',
'id'
],
data () {
return {
selectedFile: '',
file: null
}
},
components: {
QField,
QInput,
QBtn,
QProgress,
QChip
},
computed: {
selectLabel() {
return this.id ? this.$t('buttons.selectNew') :
this.$t('buttons.select');
}
},
methods: {
cancel() {
if (this.requesting) {
this.abort();
}
else if (this.selectedFile.length > 0) {
this.$emit('reset');
}
},
reset() {
this.file = null;
this.selectedFile = '';
this.$refs.upload.value = '';
},
processFile(event) {
let file = event.target.files[0];
let fileName = file ? file.name : '';
let fileNameSplit = fileName.split('.');
let extension = fileNameSplit[1] ? fileNameSplit[1] : null;
if (fileName.length > 22 && extension) {
fileName = `${fileName.substring(0, 14)}...${extension}`;
}
else if (fileName.length > 22 && !extension) {
fileName = `${fileName.substring(0, 17)}...`;
}
this.file = file;
this.selectedFile = fileName;
},
upload() {
this.$emit('upload', this.file);
},
abort() {
this.$emit('abort');
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../../themes/quasar.variables.styl'
#voicemail-file-upload
display none
.upload-field
margin-bottom 10px
.upload-label
display block
color $csc-label
font-size 16px
margin-bottom 5px
.reset-button
padding 0
.q-icon
margin 0
.upload-filename
color $black
margin-bottom 10px
.q-field-icon
margin-top 20px
.inactive-label
color $secondary
.active-label
color $primary
.progress-field
margin 10px 0 5px 0
.upload-chip
min-height 20px
height 20px
width 50px
border-top-right-radius 0
border-bottom-right-radius 0
.upload-progress
height 20px
</style>

@ -19,63 +19,32 @@
:deleteLabel="deleteLabel"
:attachLabel="attachLabel"
/>
<csc-sound-file-upload
ref="uploadBusyGreeting"
icon="music_note"
file-types=".wav,.mp3"
:label="$t('voicebox.label.busyGreeting')"
:value="busyGreetingLabel"
:progress="uploadProgress"
:progress="uploadBusyProgress"
:uploading="uploadBusyGreetingRequesting"
:uploaded="busyGreetingId !== null"
@upload="uploadBusyGreeting"
@abort="abort"
@abort="abortBusy"
@reset="deleteBusy"
/>
<!--<q-field-->
<!--icon="music_note"-->
<!--&gt;-->
<!--<q-input-->
<!--readonly-->
<!--:float-label="$t('voicebox.label.busyGreeting')"-->
<!--:value="busyGreetingLabel"-->
<!--:after="busyGreetingButtons"-->
<!--/>-->
<!--<input-->
<!--v-show="false"-->
<!--ref="busyGreetingUpload"-->
<!--type="file"-->
<!--@change="busyGreetingFileChange"-->
<!--/>-->
<!--</q-field>-->
<!--<csc-upload-file-->
<!--ref="uploadBusyGreeting"-->
<!--:progress="uploadProgress"-->
<!--:requesting="uploadBusyGreetingRequesting"-->
<!--:id="busyGreetingId"-->
<!--file-types=".wav,.mp3"-->
<!--@reset="resetBusyFile"-->
<!--@upload="uploadBusyGreeting"-->
<!--@abort="abort"-->
<!--&gt;-->
<!--<div-->
<!--slot="status-label"-->
<!--:class="busyGreetingLabelClasses"-->
<!--&gt;-->
<!--{{ busyGreetingLabel }}-->
<!--</div>-->
<!--<q-btn-->
<!--slot="extra-buttons"-->
<!--flat-->
<!--color="negative"-->
<!--icon="delete"-->
<!--@click="deleteBusy"-->
<!--&gt;-->
<!--{{ $t('buttons.remove') }}-->
<!--</q-btn>-->
<!--</csc-upload-file>-->
<csc-sound-file-upload
ref="uploadUnavailGreeting"
icon="music_note"
file-types=".wav,.mp3"
:label="$t('voicebox.label.unavailGreeting')"
:value="unavailGreetingLabel"
:progress="uploadUnavailProgress"
:uploading="uploadUnavailGreetingRequesting"
:uploaded="unavailGreetingId !== null"
@upload="uploadUnavailGreeting"
@abort="abortUnavail"
@reset="deleteUnavail"
/>
</div>
</div>
</csc-page>
@ -84,8 +53,6 @@
<script>
import {
QBtn,
QField,
QInput,
Dialog
} from 'quasar-framework'
import { mapGetters } from 'vuex'
@ -107,13 +74,12 @@
CscSoundFileUpload,
CscPage,
CscVoiceboxSettings,
QBtn,
QField,
QInput
QBtn
},
mounted() {
this.$store.dispatch('voicebox/getVoiceboxSettings');
this.loadBusyGreeting();
this.loadUnavailGreeting();
},
computed: {
...mapGetters('voicebox', [
@ -138,58 +104,46 @@
'updatePinError',
'updateEmailState',
'updateEmailError',
'uploadProgress',
'uploadBusyProgress',
'uploadUnavailProgress',
'uploadBusyGreetingState',
'uploadBusyGreetingError',
'uploadBusyGreetingRequesting',
'uploadUnavailGreetingState',
'uploadUnavailGreetingError',
'uploadUnavailGreetingRequesting',
'busyGreetingId',
'unavailGreetingId',
'deleteGreetingState',
'deleteGreetingError',
'isBusyGreetingLoaded',
'busyGreetingLabel'
]),
busyGreetingLabelClasses() {
let classes = ['csc-upload-file-label'];
if(this.busyGreetingId) {
classes.push('active-label');
}
else {
classes.push('inactive-label');
}
return classes;
},
busyGreetingButtons() {
let buttons = [];
let self = this;
buttons.push({
icon: 'folder',
error: false,
handler (event) {
event.stopPropagation();
self.$refs.busyGreetingUpload.click();
}
}
);
return buttons;
}
'busyGreetingLabel',
'unavailGreetingLabel'
])
},
methods: {
busyGreetingFileChange() {
},
resetBusyFile() {
this.$refs.uploadBusyGreeting.reset();
this.$store.commit('voicebox/resetProgress');
this.$store.commit('voicebox/resetBusyProgress');
},
resetUnavailFile() {
this.$refs.uploadUnavailGreeting.reset();
this.$store.commit('voicebox/resetUnavailProgress');
},
uploadBusyGreeting(file) {
this.$store.dispatch('voicebox/uploadGreeting', {
dir: 'busy',
this.$store.dispatch('voicebox/uploadBusyGreeting', {
file: file
});
},
abort() {
this.$store.dispatch('voicebox/abortPreviousRequest');
uploadUnavailGreeting(file) {
this.$store.dispatch('voicebox/uploadUnavailGreeting', {
file: file
});
},
abortBusy() {
this.$store.dispatch('voicebox/abortPreviousRequest', 'busy');
},
abortUnavail() {
this.$store.dispatch('voicebox/abortPreviousRequest', 'unavail');
},
deleteBusy() {
let self = this;
@ -200,12 +154,46 @@
type: 'busy'
}),
buttons: [
self.$t('buttons.cancel'),
// NOTE: Button object does not have an 'icon' option key,
// so trying to add icon: 'clear' did not help
{
label: self.$t('buttons.cancel'),
color: 'dark'
},
{
label: self.$t('buttons.reset'),
color: 'negative',
color: 'primary',
handler () {
store.dispatch('voicebox/deleteGreeting', self.busyGreetingId)
store.dispatch('voicebox/deleteGreeting', {
id: self.busyGreetingId,
type: 'busy'
})
}
}
]
});
},
deleteUnavail() {
let self = this;
let store = this.$store;
Dialog.create({
title: self.$t('voicebox.deleteCustomDialogTitle'),
message: self.$t('voicebox.deleteCustomDialogText', {
type: 'unavailable'
}),
buttons: [
{
label: self.$t('buttons.cancel'),
color: 'dark'
},
{
label: self.$t('buttons.reset'),
color: 'primary',
handler () {
store.dispatch('voicebox/deleteGreeting', {
id: self.unavailGreetingId,
type: 'unavail'
})
}
}
]
@ -213,6 +201,9 @@
},
loadBusyGreeting() {
this.$store.dispatch('voicebox/loadBusyGreeting');
},
loadUnavailGreeting() {
this.$store.dispatch('voicebox/loadUnavailGreeting');
}
},
watch: {
@ -260,11 +251,23 @@
}
else if (state === 'failed') {
showGlobalError(this.uploadBusyGreetingError);
if (this.uploadProgress > 0) {
if (this.uploadBusyProgress > 0) {
this.resetBusyFile();
}
}
},
uploadUnavailGreetingState(state) {
if (state === 'succeeded') {
showToast(this.$t('voicebox.uploadGreetingSuccessMessage'));
this.resetUnavailFile();
}
else if (state === 'failed') {
showGlobalError(this.uploadUnavailGreetingError);
if (this.uploadUnavailProgress > 0) {
this.resetUnavailFile();
}
}
},
deleteGreetingState(state) {
if (state === 'requesting') {
startLoading();

@ -422,11 +422,12 @@
"label": {
"changeEmail": "Change Email",
"changePin": "Change PIN",
"deletionEnabled": "Deleted voicemail after email notification is delivered",
"deletionDisabled": "Deleted voicemail after email notification is delivered",
"deletionEnabled": "Delete voicemail after email notification is delivered",
"deletionDisabled": "Delete voicemail after email notification is delivered",
"attachmentEnabled": "Attach voicemail to email notification",
"attachmentDisabled": "Attach voicemail to email notification",
"busyGreeting": "Busy Greeting",
"unavailGreeting": "Unavailable Greeting",
"customSoundActive": "Custom sound",
"defaultSoundActive": "Default sound"
},

@ -34,11 +34,16 @@ export default {
updateEmailError: null,
uploadBusyGreetingState: RequestState.initial,
uploadBusyGreetingError: null,
uploadProgress: 0,
uploadUnavailGreetingState: RequestState.initial,
uploadUnavailGreetingError: null,
uploadBusyProgress: 0,
uploadUnavailProgress: 0,
busyGreetingId: null,
unavailGreetingId: null,
loadBusyGreetingState: RequestState.initial,
loadBusyGreetingError: null,
loadUnavailGreetingState: RequestState.initial,
loadUnavailGreetingError: null,
deleteGreetingState: RequestState.initial,
deleteGreetingError: null
},
@ -132,12 +137,25 @@ export default {
return state.uploadBusyGreetingError ||
i18n.t('voicebox.uploadGreetingErrorMessage');
},
uploadProgress(state) {
return state.uploadProgress;
uploadUnavailGreetingState(state) {
return state.uploadUnavailGreetingState;
},
uploadUnavailGreetingError(state) {
return state.uploadUnavailGreetingError ||
i18n.t('voicebox.uploadGreetingErrorMessage');
},
uploadBusyProgress(state) {
return state.uploadBusyProgress;
},
uploadUnavailProgress(state) {
return state.uploadUnavailProgress;
},
uploadBusyGreetingRequesting(state) {
return state.uploadBusyGreetingState === 'requesting';
},
uploadUnavailGreetingRequesting(state) {
return state.uploadUnavailGreetingState === 'requesting';
},
busyGreetingId(state) {
return state.busyGreetingId;
},
@ -151,12 +169,13 @@ export default {
return state.deleteGreetingError ||
i18n.t('voicebox.deleteGreetingErrorMessage');
},
isBusyGreetingLoaded(state) {
return state.loadBusyGreetingState === 'succeeded';
},
busyGreetingLabel(state) {
return state.busyGreetingId ? i18n.t('voicebox.label.customSoundActive') :
i18n.t('voicebox.label.defaultSoundActive');
},
unavailGreetingLabel(state) {
return state.unavailGreetingId ? i18n.t('voicebox.label.customSoundActive') :
i18n.t('voicebox.label.defaultSoundActive')
}
},
mutations: {
@ -231,6 +250,18 @@ export default {
state.updateEmailState = RequestState.failed;
state.updateEmailError = error;
},
uploadUnavailGreetingRequesting(state) {
state.uploadUnavailGreetingState = RequestState.requesting;
state.uploadUnavailGreetingError = null;
},
uploadUnavailGreetingSucceeded(state) {
state.uploadUnavailGreetingState = RequestState.succeeded;
state.uploadUnavailGreetingError = null;
},
uploadUnavailGreetingFailed(state, error) {
state.uploadUnavailGreetingState = RequestState.failed;
state.uploadUnavailGreetingError = error;
},
uploadBusyGreetingRequesting(state) {
state.uploadBusyGreetingState = RequestState.requesting;
state.uploadBusyGreetingError = null;
@ -243,11 +274,17 @@ export default {
state.uploadBusyGreetingState = RequestState.failed;
state.uploadBusyGreetingError = error;
},
uploadProgress(state, progress) {
state.uploadProgress = progress;
uploadBusyProgress(state, progress) {
state.uploadBusyProgress = progress;
},
resetProgress(state) {
state.uploadProgress = 0;
uploadUnavailProgress(state, progress) {
state.uploadUnavailProgress = progress;
},
resetBusyProgress(state) {
state.uploadBusyProgress = 0;
},
resetUnavailProgress(state) {
state.uploadUnavailProgress = 0;
},
loadBusyGreetingRequesting(state) {
state.busyGreetingId = null,
@ -265,6 +302,22 @@ export default {
state.loadBusyGreetingState = RequestState.failed;
state.loadBusyGreetingError = error;
},
loadUnavailGreetingRequesting(state) {
state.unavailGreetingId = null,
state.loadUnavailGreetingState = RequestState.requesting;
state.loadUnavailGreetingError = null;
},
loadUnavailGreetingSucceeded(state, greetings) {
if (greetings.length > 0) {
state.unavailGreetingId = greetings[0].id;
}
state.loadUnavailGreetingState = RequestState.succeeded;
state.loadUnavailGreetingError = null;
},
loadUnavailGreetingFailed(state, error) {
state.loadUnavailGreetingState = RequestState.failed;
state.loadUnavailGreetingError = error;
},
deleteGreetingRequesting(state) {
state.deleteGreetingState = RequestState.requesting;
state.deleteGreetingError = null;
@ -345,30 +398,52 @@ export default {
context.commit('updateEmailFailed', err.message);
});
},
uploadGreeting(context, $options) {
uploadBusyGreeting(context, $options) {
let options = Object.assign($options, {
subscriber_id: context.getters.subscriberId,
type: $options.dir
dir: 'busy'
});
context.commit('uploadBusyGreetingRequesting');
uploadGreeting({
data: options,
onProgress: (progress) => { context.commit('uploadProgress', progress) }
onProgress: (progress) => { context.commit('uploadBusyProgress', progress) }
}).then(() => {
context.commit('loadBusyGreetingRequesting');
return getVoiceboxGreetingByType({
id: context.getters.subscriberId,
type: 'busy'
})
}).then((greetings)=>{
}).then((greetings) => {
context.commit('loadBusyGreetingSucceeded', greetings.items);
context.commit('uploadBusyGreetingSucceeded');
}).catch((err) => {
context.commit('uploadBusyGreetingFailed', err.message);
});
},
abortPreviousRequest() {
abortPreviousRequest();
uploadUnavailGreeting(context, $options) {
let options = Object.assign($options, {
subscriber_id: context.getters.subscriberId,
dir: 'unavail'
});
context.commit('uploadUnavailGreetingRequesting');
uploadGreeting({
data: options,
onProgress: (progress) => { context.commit('uploadUnavailProgress', progress) }
}).then(() => {
context.commit('loadUnavailGreetingRequesting');
return getVoiceboxGreetingByType({
id: context.getters.subscriberId,
type: 'unavail'
})
}).then((greetings) => {
context.commit('loadUnavailGreetingSucceeded', greetings.items);
context.commit('uploadUnavailGreetingSucceeded');
}).catch((err) => {
context.commit('uploadUnavailGreetingFailed', err.message);
});
},
abortPreviousRequest(context, name) {
abortPreviousRequest(name);
},
loadBusyGreeting(context) {
context.commit('loadBusyGreetingRequesting');
@ -381,11 +456,27 @@ export default {
context.commit('loadBusyGreetingFailed', err.message);
});
},
deleteGreeting(context, id) {
loadUnavailGreeting(context) {
context.commit('loadUnavailGreetingRequesting');
getVoiceboxGreetingByType({
id: context.getters.subscriberId,
type: 'unavail'
}).then((greetings) => {
context.commit('loadUnavailGreetingSucceeded', greetings.items);
}).catch((err) => {
context.commit('loadUnavailGreetingFailed', err.message);
});
},
deleteGreeting(context, options) {
context.commit('deleteGreetingRequesting');
deleteVoiceboxGreetingById(id).then(() => {
deleteVoiceboxGreetingById(options.id).then(() => {
context.commit('deleteGreetingSucceeded');
context.dispatch('loadBusyGreeting');
if (options.type === 'busy') {
context.dispatch('loadBusyGreeting');
}
else if (options.type === 'unavail') {
context.dispatch('loadUnavailGreeting');
}
}).catch((err) => {
context.commit('deleteGreetingFailed', err.message);
});

@ -114,4 +114,39 @@ describe('Voicebox', function() {
});
});
it('should get subscriber\'s unavailable greeting', function(done) {
let data = {
"_embedded" : {
"ngcp:voicemailgreetings" : [
{
"dir" : "unavail",
"id" : 1,
"subscriber_id" : 123
}
]
},
"total_count" : 1
};
let greeting = {
"dir" : "unavail",
"id" : 1,
"subscriber_id" : 123
};
Vue.http.interceptors = [];
Vue.http.interceptors.unshift((request, next) => {
next(request.respondWith(JSON.stringify(data), {
status: 200
}));
});
getVoiceboxGreetingByType({id: subscriberId, type: 'unavail'}).then((result) => {
assert.deepEqual(result.items[0], greeting);
done();
}).catch((err) => {
done(err);
});
});
});

@ -2,6 +2,8 @@
'use strict';
import VoiceboxModule from '../../src/store/voicebox';
import localeEn from '../../src/locales/en'
import { i18n } from '../../src/i18n';
import { assert } from 'chai';
describe('Voicebox', function(){
@ -42,4 +44,46 @@ describe('Voicebox', function(){
assert.deepEqual(state.busyGreetingId, greetings[0].id);
});
it('should load busy greeting id into store', function(){
let state = {
busyGreetingId: null
};
let greetings = [
{
id: 1
}
];
VoiceboxModule.mutations.loadBusyGreetingSucceeded(state, greetings);
assert.deepEqual(state.busyGreetingId, greetings[0].id);
});
it('should load unavailable greeting id into store', function(){
let state = {
unavailGreetingId: null
};
let greetings = [
{
id: 1
}
];
VoiceboxModule.mutations.loadUnavailGreetingSucceeded(state, greetings);
assert.deepEqual(state.unavailGreetingId, greetings[0].id);
});
it('should get right label for busy greeting to indicate if it\'s custom or default', function(){
let state = {
busyGreetingId: null
};
let getterObject = VoiceboxModule.getters.busyGreetingLabel(state);
assert.equal(getterObject, 'Default sound');
});
it('should get right label for unavailable greeting to indicate if it\'s custom or default', function(){
let state = {
unavailGreetingId: 1
};
let getterObject = VoiceboxModule.getters.unavailGreetingLabel(state);
assert.equal(getterObject, 'Custom sound');
});
});

Loading…
Cancel
Save