Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
AURA
dashboard
Commits
0e4d90bb
Commit
0e4d90bb
authored
Aug 10, 2019
by
jackie / Andrea Ida Malkah Klaura
Browse files
Merge branch 'feature-emissionmanager' into develop
parents
e2d23fb2
4ce06afa
Changes
5
Hide whitespace changes
Inline
Side-by-side
src/components/EmissionManager.vue
View file @
0e4d90bb
...
...
@@ -34,21 +34,36 @@
variant=
"danger"
:show=
"conflictMode"
>
<b-row>
<b-col
cols=
"12"
>
<div
align=
"center"
>
<h4>
Conflict Resolution
</h4>
</div>
</b-col>
<b-col>
... coming soon ...
</b-col>
</b-row>
<div
v-if=
"conflictMode"
align=
"center"
>
<h4>
Conflict Resolution
</h4>
<p>
for new schedule
</p>
<p>
from
<b>
{{
resolveData
.
schedule
.
dstart
}}
,
{{
resolveData
.
schedule
.
tstart
}}
</b>
to
<b>
{{
resolveData
.
schedule
.
tend
}}
</b>
.
</p>
<p
v-if=
"resolveData.schedule.rrule !== 1"
>
This is a recurring event:
<b>
{{
rruleRender
(
resolveData
.
schedule
.
rrule
)
}}
</b>
,
until:
<b>
{{
resolveData
.
schedule
.
until
}}
</b>
.
</p>
<p
v-if=
"conflictCount > 0"
>
Conflicts to resolve:
{{
conflictCount
}}
</p>
<p
v-else
>
<b-button
variant=
"success"
@
click=
"resolveSubmit"
>
0 conflicts left! Submit this solution.
</b-button>
</p>
</div>
</b-alert>
<full-calendar
ref=
"calendar"
editable=
"false"
default-view=
"agendaWeek"
:events=
"calendarSlots"
:config=
"calendarConfig"
...
...
@@ -62,6 +77,12 @@
<app-modalEmissionManagerCreate
ref=
"appModalEmissionManagerCreate"
/>
<app-modalEmissionManagerResolve
ref=
"appModalEmissionManagerResolve"
/>
<app-modalEmissionManagerEdit
ref=
"appModalEmissionManagerEdit"
/>
</b-container>
</
template
>
...
...
@@ -70,13 +91,20 @@ import axios from 'axios'
import
{
FullCalendar
}
from
'
vue-full-calendar
'
import
'
fullcalendar/dist/fullcalendar.css
'
import
modalEmissionManagerCreate
from
'
./EmissionManagerModalCreate.vue
'
import
modalEmissionManagerResolve
from
'
./EmissionManagerModalResolve.vue
'
import
modalEmissionManagerEdit
from
'
./EmissionManagerModalEdit.vue
'
import
rrules
from
'
../mixins/rrules
'
export
default
{
components
:
{
FullCalendar
,
'
app-modalEmissionManagerCreate
'
:
modalEmissionManagerCreate
,
'
app-modalEmissionManagerResolve
'
:
modalEmissionManagerResolve
,
'
app-modalEmissionManagerEdit
'
:
modalEmissionManagerEdit
,
},
mixins
:
[
rrules
],
data
()
{
return
{
currentShow
:
0
,
...
...
@@ -93,6 +121,11 @@ export default {
// this flag signifies if we are in conflict resolution mode
conflictMode
:
false
,
// when conflict mode is activated, this should hold the steering response
// from schedule creation, with all the conflicts and solutions
resolveData
:
null
,
conflictCount
:
0
,
conflictSolutions
:
[],
// this is the whole configuration for our schedule calendar, including
// simple event handlers that do not need the whole components scope
...
...
@@ -110,6 +143,7 @@ export default {
timeFormat
:
'
k:mm
'
,
slotLabelFormat
:
'
k:mm
'
,
allDaySlot
:
false
,
editable
:
false
,
},
},
// here we add a simple tooltip to every event, so that the full title
...
...
@@ -145,18 +179,47 @@ export default {
}
},
eventSelected
(
event
,
jsEvent
,
view
)
{
this
.
$log
.
debug
(
'
eventSelected
'
,
event
,
jsEvent
,
view
)
// this handler will be called whenever the user clicks on one of the
// displayed timeslots
eventSelected
(
event
)
{
// in conflict resolution mode only the newly generated events are
// clickable. if there is no conflict for an event, only a modal
// with a short notice should be opend, otherwise the resolution modal
if
(
this
.
conflictMode
)
{
if
(
event
.
hash
===
undefined
)
{
return
}
else
if
(
this
.
conflictSolutions
[
event
.
hash
]
===
undefined
)
{
this
.
$refs
.
appModalEmissionManagerResolve
.
openNotNeeded
()
}
else
{
this
.
$refs
.
appModalEmissionManagerResolve
.
open
(
event
)
}
}
// standard mode only those events are clickable that belong to the
// currently selected show.
else
{
let
timeslot
=
this
.
timeslots
.
find
(
slot
=>
slot
.
id
===
event
.
id
)
if
(
timeslot
.
show
!==
this
.
shows
[
this
.
currentShow
].
id
)
{
return
}
this
.
$refs
.
appModalEmissionManagerEdit
.
open
(
timeslot
,
this
.
shows
[
this
.
currentShow
])
}
},
// currently this will not be called, as our events are not editable
// if editable is set to true in the calendar config, this handler will
// be called if a timeslot was dragged somewhere else
eventDrop
(
event
)
{
this
.
$log
.
debug
(
'
eventDrop
'
,
event
)
this
.
notYetImplemented
()
},
// currently this will not be called, as our events are not editable
// if editable is set to true in the calendar config, this handler will
// be called if a timeslot was resized
eventResize
(
event
)
{
this
.
$log
.
debug
(
'
eventResize
'
,
event
)
this
.
notYetImplemented
()
},
// this handler is called when the user creates a new timeslot
eventCreated
(
event
)
{
this
.
$refs
.
appModalEmissionManagerCreate
.
open
(
event
.
start
,
event
.
end
)
},
...
...
@@ -176,11 +239,8 @@ export default {
start
=
view
.
start
.
format
()
end
=
view
.
end
.
format
()
}
// if we are in conflict resolution mode we do not load all timeslots
// but only the conflicting ones
if
(
this
.
conflictMode
)
{
this
.
loadConflictSlots
(
start
,
end
)
}
else
{
// we only load new timeslots, if we are not in conflict mode
if
(
!
this
.
conflictMode
)
{
this
.
loadTimeslots
(
start
,
end
)
}
}
...
...
@@ -188,7 +248,108 @@ export default {
resolve
(
data
)
{
this
.
$log
.
debug
(
'
resolve
'
,
data
)
this
.
resolveData
=
data
this
.
conflictMode
=
true
this
.
conflictCount
=
0
this
.
conflictSolutions
=
data
.
solutions
this
.
calendarSlots
=
[]
try
{
for
(
let
i
in
data
.
projected
)
{
let
newSlot
=
{
// we need a numeric ID for the event for later selection by the user.
// with converting the hash to a number (in this case a float), we
// do not risk using a number that is already used by a timeslot id
// of a conflicting timeslot
id
:
Number
(
data
.
projected
[
i
].
hash
),
// the hash is needed to compare against solutions and conflicts
hash
:
data
.
projected
[
i
].
hash
,
start
:
data
.
projected
[
i
].
start
,
end
:
data
.
projected
[
i
].
end
,
title
:
'
new
'
,
collisions
:
[],
solutionChoices
:
[],
className
:
'
noconflict
'
,
editable
:
false
,
}
if
(
data
.
projected
[
i
].
collisions
.
length
>
0
)
{
newSlot
.
className
=
'
conflict
'
newSlot
.
solutionChoices
=
data
.
projected
[
i
].
solution_choices
for
(
let
col
of
data
.
projected
[
i
].
collisions
)
{
let
conflictingSlot
=
{
id
:
col
.
id
,
start
:
col
.
start
,
end
:
col
.
end
,
title
:
col
.
show_name
,
className
:
'
otherShow
'
,
editable
:
false
,
}
this
.
calendarSlots
.
push
(
conflictingSlot
)
this
.
conflictCount
++
newSlot
.
collisions
.
push
(
col
)
}
}
this
.
calendarSlots
.
push
(
newSlot
)
}
}
catch
(
err
)
{
this
.
$log
.
error
(
err
)
}
},
resolveEvent
(
toResolve
,
mode
)
{
this
.
conflictCount
-=
toResolve
.
collisions
.
length
let
slotIndex
=
this
.
calendarSlots
.
findIndex
(
s
=>
s
.
id
===
toResolve
.
id
)
switch
(
mode
)
{
case
'
theirs
'
:
this
.
conflictSolutions
[
toResolve
.
hash
]
=
'
theirs
'
this
.
calendarSlots
[
slotIndex
].
className
=
'
ours-discarded
'
this
.
renderView
(
null
)
break
default
:
this
.
$log
.
error
(
'
EmissionManager.resolveEvent
'
)
this
.
$log
.
error
(
'
toResolve:
'
,
toResolve
)
this
.
$log
.
error
(
'
mode:
'
,
mode
)
alert
(
'
Error: an undefined conflict resolution mode was chosen. See console for details
'
)
break
}
},
// submit a conflict-resolved schedule to steering
resolveSubmit
()
{
// TODO: check why steering retourns undefined and null values here
if
(
this
.
resolveData
.
schedule
.
add_business_days_only
===
undefined
)
{
this
.
resolveData
.
schedule
.
add_business_days_only
=
false
}
if
(
this
.
resolveData
.
schedule
.
add_days_no
===
null
)
{
this
.
resolveData
.
schedule
.
add_days_no
=
0
}
if
(
this
.
resolveData
.
schedule
.
is_repetition
===
undefined
)
{
this
.
resolveData
.
schedule
.
is_repetition
=
false
}
if
(
this
.
resolveData
.
schedule
.
fallback_id
===
null
)
{
this
.
resolveData
.
schedule
.
fallback_id
=
0
}
if
(
this
.
resolveData
.
schedule
.
automation_id
===
null
)
{
this
.
resolveData
.
schedule
.
automation_id
=
0
}
if
(
this
.
resolveData
.
schedule
.
byweekday
===
undefined
)
{
this
.
resolveData
.
schedule
.
byweekday
=
0
}
// create the resolved schedule object including solutions
let
resolvedSchedule
=
{
schedule
:
this
.
resolveData
.
schedule
,
solutions
:
this
.
resolveData
.
solutions
,
}
this
.
$log
.
debug
(
'
resolveSubmit: schedule:
'
,
resolvedSchedule
)
// now generate the URL and POST it to steering
let
uri
=
process
.
env
.
VUE_APP_API_STEERING_SHOWS
+
this
.
shows
[
this
.
currentShow
].
id
+
'
/schedules/
'
axios
.
post
(
uri
,
resolvedSchedule
,
{
withCredentials
:
true
,
headers
:
{
'
Authorization
'
:
'
Bearer
'
+
this
.
$parent
.
user
.
access_token
}
}).
then
(
response
=>
{
this
.
$log
.
debug
(
'
resolveSubmit: response:
'
,
response
)
// if for some reason a new conflict arose, e.g. because in the meantime
// someone else inserted a conflicting schedule, we have to resolve.
if
(
response
.
data
.
projected
===
undefined
)
{
this
.
conflictMode
=
false
this
.
renderView
(
null
)
}
else
{
this
.
resolve
(
response
.
data
)
}
}).
catch
(
error
=>
{
this
.
$log
.
error
(
error
.
response
.
status
+
'
'
+
error
.
response
.
statusText
)
this
.
$log
.
error
(
error
.
response
)
alert
(
'
Error: could not submit final schedule. See console for details.
'
)
// and we leave the modal open, so no call to its .hide function here
})
},
loadCalendarSlots
()
{
...
...
@@ -200,6 +361,7 @@ export default {
highlighting
=
'
currentShow
'
}
this
.
calendarSlots
.
push
({
id
:
this
.
timeslots
[
i
].
id
,
start
:
this
.
timeslots
[
i
].
start
,
end
:
this
.
timeslots
[
i
].
end
,
title
:
this
.
getShowTitleById
(
this
.
timeslots
[
i
].
show
),
...
...
@@ -246,9 +408,9 @@ export default {
})
},
updateSchedules
()
{
this
.
$log
.
debug
(
this
.
$refs
.
calendar
.
fireMethod
(
'
getView
'
).
start
.
format
()
)
}
notYetImplemented
:
function
()
{
alert
(
'
By the mighty witchcraftry of the mother of time!
\n\n
This feature is not implemented yet.
'
)
}
,
},
}
</
script
>
...
...
@@ -260,4 +422,15 @@ export default {
a
.currentShow
{
background-color
:
#17a2b8
;
}
.conflict
{
background-color
:
#b00
;
}
.noconflict
{
background-color
:
#17a2b8
;
}
.ours-discarded
{
background-color
:
#b00
;
opacity
:
0.5
;
text-decoration
:
line-through
!important
;
}
</
style
>
src/components/EmissionManagerModalCreate.vue
View file @
0e4d90bb
...
...
@@ -81,9 +81,10 @@
<
script
>
import
axios
from
'
axios
'
import
prettyDate
from
'
../mixins/prettyDate
'
import
rrules
from
'
../mixins/rrules
'
export
default
{
mixins
:
[
prettyDate
],
mixins
:
[
prettyDate
,
rrules
],
data
()
{
return
{
...
...
@@ -97,21 +98,6 @@ export default {
until
:
null
,
rrule
:
1
},
rruleOptions
:
[
{
value
:
1
,
text
:
'
einmalig
'
},
{
value
:
2
,
text
:
'
täglich
'
},
{
value
:
3
,
text
:
'
werktäglich
'
},
{
value
:
4
,
text
:
'
wöchentlich
'
},
{
value
:
5
,
text
:
'
zweiwöchentlich
'
},
{
value
:
6
,
text
:
'
vierwöchentlich
'
},
{
value
:
7
,
text
:
'
gerade Kalenderwoche
'
},
{
value
:
8
,
text
:
'
ungerade Kalenderwoche
'
},
{
value
:
9
,
text
:
'
Jede 1. Woche im Monat
'
},
{
value
:
10
,
text
:
'
Jede 2. Woche im Monat
'
},
{
value
:
11
,
text
:
'
Jede 3. Woche im Monat
'
},
{
value
:
12
,
text
:
'
Jede 4. Woche im Monat
'
},
{
value
:
13
,
text
:
'
Jede 5. Woche im Monat
'
},
],
}
},
...
...
src/components/EmissionManagerModalEdit.vue
0 → 100644
View file @
0e4d90bb
<
template
>
<div>
<b-modal
ref=
"modalEmissionManagerEdit"
title=
"Edit a schedule"
size=
"lg"
>
<p>
Editing a timeslot/schedule for show:
<b
v-if=
"loaded.modal"
>
<b>
{{
show
.
name
}}
</b>
</b>
</p>
<p
v-if=
"loaded.modal"
>
This timeslot starts at
<b-badge
variant=
"info"
>
{{
prettyDateTime
(
timeslot
.
start
)
}}
</b-badge>
and ends at
<b-badge
variant=
"info"
>
{{
prettyDateTime
(
timeslot
.
end
)
}}
</b-badge>
</p>
<div
v-if=
"loaded.schedule"
>
<div
v-if=
"schedule.rrule === 1"
>
<p>
This is a single emission. No other timeslots in this schedule.
</p>
</div>
<div
v-else
>
<p>
This is a recurring event:
<b>
{{
rruleRender
(
schedule
.
rrule
)
}}
</b>
, until:
{{
prettyDate
(
schedule
.
until
)
}}
</p>
<p>
All
<i>
upcoming
</i>
timeslots of this schedule:
</p>
<ul
v-if=
"loaded.scheduleTimeslots"
>
<li
v-for=
"slot in scheduleTimeslots"
:key=
"slot.id"
>
from
<b-badge
:variant=
"timeslot.id === slot.id ? 'info' : 'light'"
>
{{
prettyDateTime
(
slot
.
start
)
}}
</b-badge>
to
<b-badge
:variant=
"timeslot.id === slot.id ? 'info' : 'light'"
>
{{
prettyDateTime
(
slot
.
end
)
}}
</b-badge>
</li>
</ul>
</div>
<p>
What do you want to do with it?
</p>
<div
align=
"center"
>
<b-button-group>
<b-button
variant=
"danger"
size=
"sm"
@
click=
"deleteFullSchedule(schedule.id)"
>
<span
v-if=
"schedule.rrule === 1"
>
Delete
</span>
<span
v-else
>
Delete schedule + all timeslots
</span>
</b-button>
<b-button
v-if=
"schedule.rrule > 1"
variant=
"danger"
size=
"sm"
@
click=
"notYetImplemented()"
>
Delete only this timeslot
</b-button>
<b-button
v-if=
"schedule.rrule > 1"
variant=
"danger"
size=
"sm"
@
click=
"notYetImplemented()"
>
Delete this + all future timeslots
</b-button>
</b-button-group>
</div>
</div>
<div
v-else
>
<img
src=
"../assets/radio.gif"
alt=
"loading schedule data"
>
</div>
</b-modal>
</div>
</
template
>
<
script
>
import
axios
from
'
axios
'
import
prettyDate
from
'
../mixins/prettyDate
'
import
rrules
from
'
../mixins/rrules
'
export
default
{
mixins
:
[
prettyDate
,
rrules
],
data
()
{
return
{
timeslot
:
null
,
schedule
:
null
,
scheduleTimeslots
:
null
,
show
:
null
,
loaded
:
{
modal
:
false
,
schedule
:
false
,
scheduleTimeslots
:
false
,
}
}
},
methods
:
{
deleteFullSchedule
(
id
)
{
let
uri
=
process
.
env
.
VUE_APP_API_STEERING
+
'
shows/
'
+
this
.
show
.
id
+
'
/schedules/
'
+
id
+
'
/
'
axios
.
delete
(
uri
,
{
withCredentials
:
true
,
headers
:
{
'
Authorization
'
:
'
Bearer
'
+
this
.
$parent
.
$parent
.
user
.
access_token
}
}).
then
(()
=>
{
this
.
$refs
.
modalEmissionManagerEdit
.
hide
()
this
.
$parent
.
renderView
(
null
)
}).
catch
(
error
=>
{
this
.
$log
.
error
(
error
.
response
.
status
+
'
'
+
error
.
response
.
statusText
)
this
.
$log
.
error
(
error
.
response
)
alert
(
'
Error: could not delete full schedule. See console for details.
'
)
})
},
loadSchedule
(
id
)
{
this
.
loaded
.
schedule
=
false
let
uri
=
process
.
env
.
VUE_APP_API_STEERING
+
'
shows/
'
+
this
.
show
.
id
+
'
/schedules/
'
+
id
+
'
/
'
axios
.
get
(
uri
,
{
withCredentials
:
true
,
headers
:
{
'
Authorization
'
:
'
Bearer
'
+
this
.
$parent
.
$parent
.
user
.
access_token
}
}).
then
(
response
=>
{
this
.
schedule
=
response
.
data
this
.
loaded
.
schedule
=
true
}).
catch
(
error
=>
{
this
.
$log
.
error
(
error
.
response
.
status
+
'
'
+
error
.
response
.
statusText
)
this
.
$log
.
error
(
error
.
response
)
alert
(
'
Error: could not load schedule. See console for details.
'
)
})
},
loadScheduleTimeslots
(
id
)
{
this
.
loaded
.
scheduleTimeslots
=
false
let
uri
=
process
.
env
.
VUE_APP_API_STEERING
+
'
shows/
'
+
this
.
show
.
id
+
'
/schedules/
'
+
id
+
'
/timeslots/
'
axios
.
get
(
uri
,
{
withCredentials
:
true
,
headers
:
{
'
Authorization
'
:
'
Bearer
'
+
this
.
$parent
.
$parent
.
user
.
access_token
}
}).
then
(
response
=>
{
this
.
scheduleTimeslots
=
response
.
data
this
.
loaded
.
scheduleTimeslots
=
true
}).
catch
(
error
=>
{
this
.
$log
.
error
(
error
.
response
.
status
+
'
'
+
error
.
response
.
statusText
)
this
.
$log
.
error
(
error
.
response
)
alert
(
'
Error: could not load timeslots of this schedule. See console for details.
'
)
})
},
// initialise a new schedule and open the modal
open
(
timeslot
,
show
)
{
this
.
timeslot
=
timeslot
this
.
show
=
show
this
.
loaded
.
modal
=
true
this
.
$refs
.
modalEmissionManagerEdit
.
show
()
this
.
loadSchedule
(
timeslot
.
schedule
)
this
.
loadScheduleTimeslots
(
timeslot
.
schedule
)
},
notYetImplemented
:
function
()
{
alert
(
'
By the mighty witchcraftry of the mother of time!
\n\n
This feature is not implemented yet.
'
)
},
}
}
</
script
>
<
style
scoped
>
</
style
>
src/components/EmissionManagerModalResolve.vue
0 → 100644
View file @
0e4d90bb
<
template
>
<div>
<b-modal
ref=
"modalEmissionManagerResolve"
title=
"Resolve a timeslot conflict"
size=
"lg"
@
ok=
"resolve"
>
<p>
Resolving a conflict for a new schedule of the show:
<b
v-if=
"$parent.loaded.shows"
>
<b>
{{
$parent
.
shows
[
$parent
.
currentShow
].
name
}}
</b>
</b>
</p>
<p
v-if=
"loaded"
>
The new projected slot starts at
<b-badge
variant=
"danger"
>
{{
toResolve
.
start
.
format
(
'
YYYY-DD-MM HH:mm
'
)
}}
</b-badge>
and ends at
<b-badge
variant=
"danger"
>
{{
toResolve
.
end
.
format
(
'
YYYY-DD-MM HH:mm
'
)
}}
</b-badge>
.
</p>
<p>
It conflicts with the following timeslots:
</p>
<ul
v-if=
"loaded"
>
<li
v-for=
"col in toResolve.collisions"
:key=
"col.id"
>
<i>
{{
col
.
show_name
}}
</i>
from
<b-badge
variant=
"success"
>
{{
col
.
start
.
slice
(
0
,
16
)
}}
</b-badge>
to
<b-badge
variant=
"success"
>
{{
col
.
end
.
slice
(
0
,
16
)
}}
</b-badge>
</li>
</ul>
<p>
What should we do?
</p>
<div
align=
"center"
>
<b-button-group
v-if=
"loaded"
>
<b-button
v-if=
"toResolve.solutionChoices.indexOf('ours') >= 0"
variant=
"danger"
size=
"sm"
@
click=
"resolve('ours')"
>
Create new,
<br>
delete existing.
</b-button>
<b-button
v-if=
"toResolve.solutionChoices.indexOf('theirs') >= 0"
variant=
"success"
size=
"sm"
@
click=
"resolve('theirs')"
>
Discard new,
<br>
keep existing.
</b-button>
<b-button
v-if=
"toResolve.solutionChoices.indexOf('theirs-start') >= 0"
variant=
"info"
size=
"sm"
@
click=
"notYetImplemented"
>
theirs-start
<br>
TODO: describe
</b-button>
<b-button