Commit e01a3692 authored by Konrad Mohrfeldt's avatar Konrad Mohrfeldt
Browse files

fix: properly handle steering scheduling API errors

The steering API returns HTTP 400 errors for general payload and 409
errors for scheduling conflicts that we didn’t handle yet.

fixes #93
parent 4a07ad9e
Pipeline #1849 passed with stage
in 1 minute and 33 seconds
......@@ -82,6 +82,8 @@
</div>
</b-alert>
<server-errors :errors="serverErrors" />
<div class="tw-flex">
<div :class="{ 'tw-hidden': view !== 'week'}">
<full-calendar
......@@ -239,9 +241,11 @@ import AuthWall from '@/components/AuthWall.vue'
import rrules from '@/mixins/rrules'
import prettyDate from '@/mixins/prettyDate'
import playlist from '@/mixins/playlist'
import ServerErrors from '@/components/ServerErrors'
export default {
components: {
ServerErrors,
FullCalendar,
AuthWall,
'show-selector': showSelector,
......@@ -270,6 +274,7 @@ export default {
resolveData: null,
conflictCount: 0,
conflictSolutions: [],
serverErrors: [],
}
},
......@@ -696,7 +701,7 @@ export default {
},
// submit a conflict-resolved schedule to steering
resolveSubmit() {
async resolveSubmit() {
// TODO: check why steering returns undefined and null values here
if (this.resolveData.schedule.add_business_days_only === undefined) {
this.resolveData.schedule.add_business_days_only = false
......@@ -725,22 +730,23 @@ export default {
this.$log.debug('resolveSubmit: schedule:', resolvedSchedule)
this.submitting = true
this.$store.dispatch('shows/submitSchedule', {
showId: this.selectedShow.id,
schedule: resolvedSchedule,
callback: (response) => {
this.submitting = false
if (response.data.projected === undefined) {
this.conflictMode = false
this.renderView(null)
this.serverErrors = []
try {
await this.$store.dispatch('shows/submitSchedule', {
showId: this.selectedShow.id,
schedule: resolvedSchedule,
})
this.conflictMode = false
this.renderView(null)
} catch (e) {
if (e.response?.status === 409) {
this.resolve(e.response.data)
} else {
this.resolve(response.data)
this.serverErrors = e.errors
}
},
callbackCancel: () => {
} finally {
this.submitting = false
}
})
}
},
loadCalendarSlots() {
......
......@@ -14,6 +14,8 @@
<span v-html="$t('scheduleEditor.pastEventWarning')" />
</b-alert>
<server-errors :errors="serverErrors" />
<div v-if="loaded.modal && !submitting">
<b-row>
<b-col cols="12">
......@@ -108,8 +110,10 @@
import {mapGetters} from 'vuex'
import prettyDate from '../../mixins/prettyDate'
import rrules from '../../mixins/rrules'
import ServerErrors from '@/components/ServerErrors'
export default {
components: {ServerErrors},
mixins: [prettyDate, rrules],
data() {
......@@ -118,6 +122,7 @@
loadedModal: false,
submitting: false,
pastEventWarning: false,
serverErrors: [],
initialLastDate: null,
valuePick: {
firstDate: null,
......@@ -156,7 +161,7 @@
},
methods: {
create(event) {
async create(event) {
// prevent the modal from closing automatically on click
event.preventDefault()
// check for past dates; as past dates will be ignored by steering we
......@@ -181,58 +186,25 @@
// ok then, let's submit and see if any conflicts arise
this.submitting = true
this.$store.dispatch('shows/submitSchedule', {
showId: this.selectedShow.id,
schedule: this.newSchedule,
callback: (response) => {
// we have to check if there are any conflicts with existing timeslots
let conflicts = false
for (let i in response.data.projected) {
if (response.data.projected[i].collisions.length > 0) {
conflicts = true
break
}
}
// if there are no conflicts we can set an empty solutions object
// in our newSchedule and submit it.
if (!conflicts) {
this.newSchedule.solutions = {}
this.submit()
// otherwise we have to resolve the conflict first.
} else {
this.submitting = false
this.$parent.resolve(response.data)
this.$refs.modalEmissionManagerCreate.hide()
}
},
callbackCancel: () => {
this.submitting = false
}
})
},
submit() {
this.$store.dispatch('shows/submitSchedule', {
showId: this.selectedShow.id,
schedule: this.newSchedule,
callback: (response) => {
this.submitting = false
// if for some reason a new conflict arose, e.g. because in the meantime
// someone else inserted a conflicting schedule, we have to resolve.
// TODO: based on single event schedule
// TODO: check for complex schedules with resolved conflicts
if (response.data.projected === undefined) {
this.$parent.renderView(null)
} else {
this.$log.debug('Timeslot conflict. Switching to resolve mode.')
this.$parent.resolve(response.data)
}
this.serverErrors = []
try {
await this.$store.dispatch('shows/submitSchedule', {
showId: this.selectedShow.id,
schedule: this.newSchedule,
})
this.$parent.renderView(null)
this.$refs.modalEmissionManagerCreate.hide()
} catch (e) {
const responseData = e.response?.data
if (e.response?.status === 409) {
this.$parent.resolve(responseData)
this.$refs.modalEmissionManagerCreate.hide()
},
callbackCancel: () => {
this.submitting = false
} else {
this.serverErrors = e.errors ?? []
}
})
} finally {
this.submitting = false
}
},
// initialise a new schedule and open the modal
......
......@@ -5,6 +5,8 @@
:title="$t('scheduleEditor.titleEdit', { show: selectedShow.name })"
size="lg"
>
<server-errors :errors="serverErrors" />
<div v-if="timeslot && loaded.schedule">
<p
class="tw-mb-0"
......@@ -240,8 +242,10 @@
import {mapGetters} from 'vuex'
import prettyDate from '../../mixins/prettyDate'
import rrules from '../../mixins/rrules'
import ServerErrors from '@/components/ServerErrors'
export default {
components: {ServerErrors},
mixins: [prettyDate, rrules],
data() {
......@@ -258,6 +262,7 @@
addNoOfDays: 1,
repetitionRule: 1,
repetitionTime: '',
serverErrors: [],
}
},
......@@ -304,7 +309,7 @@
return start > now
},
createRepetitionSchedule() {
async createRepetitionSchedule() {
const {onlyBusinessDays, addNoOfDays} = this.getRepetitionParameters()
let {first_date, time_start, time_end, rrule, last_date, default_playlist_id, automation_id, by_weekday} = this.schedule
......@@ -334,20 +339,23 @@
}
}
this.$store.dispatch('shows/submitSchedule', {
showId: this.selectedShow.id,
schedule: newSchedule,
callback: (response) => {
console.log(this.$parent)
if (response.data.projected === undefined) {
this.$parent.renderView(null)
} else {
this.$log.debug('Timeslot conflict. Switching to resolve mode.')
this.$parent.resolve(response.data)
}
this.$refs.modalEmissionManagerEdit.hide()
},
})
this.serverErrors = []
try {
await this.$store.dispatch('shows/submitSchedule', {
showId: this.selectedShow.id,
schedule: newSchedule,
})
this.$parent.renderView(null)
} catch (e) {
if (e.response?.status === 409) {
this.$log.debug('Timeslot conflict. Switching to resolve mode.')
this.$parent.resolve(e.response.data)
} else {
this.serverErrors = e.errors ?? []
}
} finally {
this.$refs.modalEmissionManagerEdit.hide()
}
},
deleteFullSchedule(id) {
......
......@@ -460,4 +460,10 @@ export default {
'13': 'Jede 5. Woche im Monat',
}
},
'steeringErrorCodes': {
'no-start-after-end': 'Das Startdatum darf nicht hinter dem Enddatum liegen.',
'no-same-day-start-and-end': 'Start- und Enddatum dürfen nicht auf den selben Tag fallen.',
'one-solution-per-conflict': 'Es wird genau eine Lösung für jeden Konflikt benötigt. Es wurde entweder keine oder zuviele Lösungen für einen Konflikt übermittelt.'
}
}
......@@ -461,4 +461,10 @@ export default {
'13': 'Every fifth week of the month',
}
},
'steeringErrorCodes': {
'no-start-after-end': 'The start date cannot be after the end date.',
'no-same-day-start-and-end': 'The start and end date cannot be on the same day.',
'one-solution-per-conflict': 'Exactly one solution is required per conflict. You either submitted no or more than one solution.'
}
}
import axios from 'axios'
import { handleApiError } from '../api-helper'
import { APIError, callOrReturn, handleApiError } from '../api-helper'
const cloneMinimalShowObject = function (show) {
/* returns a new minimal object from the current show object with all
......@@ -363,19 +363,14 @@ const actions = {
submitSchedule(ctx, data) {
ctx.commit('loading', 'schedules')
let uri = process.env.VUE_APP_API_STEERING_SHOWS + data.showId + '/schedules/'
axios.post(uri, data.schedule, {
return axios.post(uri, data.schedule, {
withCredentials: true,
headers: {'Authorization': 'Bearer ' + ctx.rootState.auth.user.access_token}
}).then(response => {
ctx.commit('finishLoading', 'schedules')
if (data && typeof (data.callback) === 'function') {
data.callback(response)
}
return callOrReturn(response, data?.callback)
}).catch(error => {
handleApiError(this, error, 'could not load timeslots')
if (data && typeof (data.callbackCancel) === 'function') {
data.callbackCancel()
}
APIError.handle(error, 'Unable to submit schedule', data, this)
})
},
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment