UNCLASSIFIED
Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Open sidebar
Platform One
P
Party Bus
Launchboard
launchboard-fe
Commits
02f0931c
Commit
02f0931c
authored
Sep 03, 2021
by
Michael Winberry
Browse files
Merge branch 'master' into Valkyrie
parents
72508796
233c47f1
Changes
17
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
3394 additions
and
2425 deletions
+3394
-2425
CONTRIBUTING.md
CONTRIBUTING.md
+13
-13
package-lock.json
package-lock.json
+2359
-2383
package.json
package.json
+23
-23
src/api/services/training.js
src/api/services/training.js
+19
-2
src/api/services/user.js
src/api/services/user.js
+6
-0
src/assets/images/logos/Vector.svg
src/assets/images/logos/Vector.svg
+3
-0
src/components/DenialNotification.vue
src/components/DenialNotification.vue
+140
-0
src/components/Dialogs/SelectAddStudentsToCourseDialog.vue
src/components/Dialogs/SelectAddStudentsToCourseDialog.vue
+2
-1
src/components/Notification.vue
src/components/Notification.vue
+97
-0
src/components/PersonnelTable.vue
src/components/PersonnelTable.vue
+0
-1
src/components/Training/PendingRequests.vue
src/components/Training/PendingRequests.vue
+289
-0
src/views/TrainingDetails.vue
src/views/TrainingDetails.vue
+11
-2
src/views/super-admin/LaunchboardSuperAdmin.vue
src/views/super-admin/LaunchboardSuperAdmin.vue
+6
-0
tests/unit/components/DenialNotification.spec.js
tests/unit/components/DenialNotification.spec.js
+81
-0
tests/unit/components/Dialogs/SelectAddStudentsToCourseDialog.spec.js
...omponents/Dialogs/SelectAddStudentsToCourseDialog.spec.js
+17
-0
tests/unit/components/Notification.spec.js
tests/unit/components/Notification.spec.js
+107
-0
tests/unit/components/Training/PendingRequests.spec.js
tests/unit/components/Training/PendingRequests.spec.js
+221
-0
No files found.
CONTRIBUTING.md
View file @
02f0931c
...
...
@@ -34,22 +34,22 @@ appearance, race, religion, or sexual identity and orientation.
Examples of behavior that contributes to creating a positive environment
include:
*
Using welcoming and inclusive language
*
Being respectful of differing viewpoints and experiences
*
Gracefully accepting constructive criticism
*
Focusing on what is best for the community
*
Showing empathy towards other community members
-
Using welcoming and inclusive language
-
Being respectful of differing viewpoints and experiences
-
Gracefully accepting constructive criticism
-
Focusing on what is best for the community
-
Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
*
The use of sexualized language or imagery and unwelcome sexual attention or
advances
*
Trolling, insulting/derogatory comments, and personal or political attacks
*
Public or private harassment
*
Publishing others' private information, such as a physical or electronic
address, without explicit permission
*
Other conduct which could reasonably be considered inappropriate in a
professional setting
-
The use of sexualized language or imagery and unwelcome sexual attention or
advances
-
Trolling, insulting/derogatory comments, and personal or political attacks
-
Public or private harassment
-
Publishing others' private information, such as a physical or electronic
address, without explicit permission
-
Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
...
...
package-lock.json
View file @
02f0931c
This diff is collapsed.
Click to expand it.
package.json
View file @
02f0931c
...
...
@@ -17,51 +17,51 @@
"prettify:pretty-quick"
],
"dependencies"
:
{
"
apexcharts
"
:
"
^3.2
6.0
"
,
"
apexcharts
"
:
"
^3.2
8.1
"
,
"
axios
"
:
"
^0.21.1
"
,
"
core-js
"
:
"
^3.6.
4
"
,
"
core-js
"
:
"
^3.
1
6.
3
"
,
"
downloadjs
"
:
"
^1.4.7
"
,
"
lodash
"
:
"
4.17.21
"
,
"
moment
"
:
"
^2.29.1
"
,
"
vue
"
:
"
^2.6.1
1
"
,
"
vue-apexcharts
"
:
"
^1.6.
0
"
,
"
vue
"
:
"
^2.6.1
4
"
,
"
vue-apexcharts
"
:
"
^1.6.
2
"
,
"
vue-body-class
"
:
"
^3.0.2
"
,
"
vue-router
"
:
"
^3.
1.6
"
,
"
vuetify
"
:
"
^2.
4.2
"
,
"
vue-router
"
:
"
^3.
5.2
"
,
"
vuetify
"
:
"
^2.
5.8
"
,
"
vuex
"
:
"
^3.5.1
"
},
"devDependencies"
:
{
"
@babel/plugin-transform-strict-mode
"
:
"
^7.1
2.13
"
,
"
@babel/plugin-transform-strict-mode
"
:
"
^7.1
4.5
"
,
"
@mdi/font
"
:
"
^5.9.55
"
,
"
@vue/cli-plugin-babel
"
:
"
^4.5.1
1
"
,
"
@vue/cli-plugin-babel
"
:
"
^4.5.1
3
"
,
"
@vue/cli-plugin-e2e-cypress
"
:
"
^4.5.13
"
,
"
@vue/cli-plugin-eslint
"
:
"
^4.5.1
1
"
,
"
@vue/cli-plugin-router
"
:
"
^4.5.1
1
"
,
"
@vue/cli-plugin-unit-jest
"
:
"
^4.5.1
1
"
,
"
@vue/cli-service
"
:
"
^4.5.1
1
"
,
"
@vue/cli-plugin-eslint
"
:
"
^4.5.1
3
"
,
"
@vue/cli-plugin-router
"
:
"
^4.5.1
3
"
,
"
@vue/cli-plugin-unit-jest
"
:
"
^4.5.1
3
"
,
"
@vue/cli-service
"
:
"
^4.5.1
3
"
,
"
@vue/eslint-config-prettier
"
:
"
^6.0.0
"
,
"
@vue/test-utils
"
:
"
^1.
1.3
"
,
"
@vue/test-utils
"
:
"
^1.
2.2
"
,
"
babel-eslint
"
:
"
^10.1.0
"
,
"
cypress
"
:
"
^7.
3
.0
"
,
"
cypress
"
:
"
^7.
7
.0
"
,
"
eslint
"
:
"
^6.8.0
"
,
"
eslint-loader
"
:
"
^4.0.2
"
,
"
eslint-plugin-prettier
"
:
"
^3.
3
.1
"
,
"
eslint-plugin-prettier
"
:
"
^3.
4
.1
"
,
"
eslint-plugin-vue
"
:
"
^6.2.2
"
,
"
flush-promises
"
:
"
^1.0.2
"
,
"
husky
"
:
"
^4.3.8
"
,
"
pre-commit
"
:
"
^1.2.2
"
,
"
prettier
"
:
"
^2.
2.1
"
,
"
pretty-quick
"
:
"
^3.1.
0
"
,
"
sass
"
:
"
^
1.3
2.8
"
,
"
prettier
"
:
"
^2.
3.2
"
,
"
pretty-quick
"
:
"
^3.1.
1
"
,
"
sass
"
:
"
~
1.3
8.1
"
,
"
sass-loader
"
:
"
^8.0.2
"
,
"
style-resources-loader
"
:
"
^1.3.2
"
,
"
stylelint
"
:
"
^13.1
1.0
"
,
"
stylelint-webpack-plugin
"
:
"
^2.
1.1
"
,
"
vue-cli-plugin-style-resources-loader
"
:
"
~
0.1.
4
"
,
"
vue-cli-plugin-vuetify
"
:
"
^2.
0.9
"
,
"
stylelint
"
:
"
^13.1
3.1
"
,
"
stylelint-webpack-plugin
"
:
"
^2.
2.2
"
,
"
vue-cli-plugin-style-resources-loader
"
:
"
^
0.1.
5
"
,
"
vue-cli-plugin-vuetify
"
:
"
^2.
4.2
"
,
"
vue-loader-v16
"
:
"
^16.0.0-beta.5.4
"
,
"
vue-svg-loader
"
:
"
^0.16.0
"
,
"
vue-template-compiler
"
:
"
^2.6.1
2
"
,
"
vue-template-compiler
"
:
"
^2.6.1
4
"
,
"
vuetify-loader
"
:
"
^1.7.2
"
},
"eslintConfig"
:
{
...
...
src/api/services/training.js
View file @
02f0931c
...
...
@@ -59,9 +59,9 @@ export default {
await
this
.
addStudent
(
courseId
,
{
userId
:
student
.
id
});
}
},
async
addStudentsToCourses
(
courses
,
students
)
{
async
addStudentsToCourses
(
courses
,
students
,
pmId
)
{
for
(
const
{
id
:
courseId
}
of
courses
)
{
await
this
.
addStudents
(
courseId
,
students
);
await
this
.
add
Pending
Students
(
courseId
,
students
,
pmId
);
}
},
async
exportCourses
()
{
...
...
@@ -89,4 +89,21 @@ export default {
);
}
},
async
getCoursePendingRegistrationsById
(
courseId
)
{
return
HTTP
.
get
(
`/courses/
${
courseId
}
/pending-registrations`
);
},
async
putCoursePendingRegistrationsById
(
courseId
,
params
)
{
return
HTTP
.
put
(
`/courses/
${
courseId
}
/pending-registrations/acceptance`
,
params
);
},
async
addPendingStudent
(
courseId
,
student
)
{
return
HTTP
.
post
(
`/courses/
${
courseId
}
/pending-registrations`
,
student
);
},
async
addPendingStudents
(
courseId
,
students
,
pmId
)
{
for
(
const
student
of
students
)
{
await
this
.
addPendingStudent
(
courseId
,
{
userId
:
student
.
id
,
pmId
:
pmId
});
}
},
};
src/api/services/user.js
View file @
02f0931c
...
...
@@ -4,6 +4,12 @@ export default {
async
getUser
()
{
return
HTTP
.
get
(
"
/whoami
"
);
},
async
getNotifications
()
{
return
HTTP
.
get
(
"
/users/notifications
"
);
},
async
deleteNotifications
(
notificationId
)
{
return
HTTP
.
delete
(
`/users/notification/
${
notificationId
}
`
);
},
async
search
(
params
)
{
return
HTTP
.
get
(
"
/users
"
,
{
params
});
},
...
...
src/assets/images/logos/Vector.svg
0 → 100644
View file @
02f0931c
<svg
width=
"24"
height=
"21"
viewBox=
"0 0 24 21"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<path
d=
"M0 20.7273H24L12 0L0 20.7273ZM13.0909 17.4545H10.9091V15.2727H13.0909V17.4545ZM13.0909 13.0909H10.9091V8.72727H13.0909V13.0909Z"
fill=
"#BD0707"
/>
</svg>
src/components/DenialNotification.vue
0 → 100644
View file @
02f0931c
<
template
>
<v-dialog
v-model=
"denyDialog"
width=
"700"
>
<template
v-slot:activator=
"
{ on, attrs }">
<v-btn
color=
"secondary"
width=
"112px"
v-bind=
"attrs"
v-on=
"on"
>
DENY
</v-btn>
</
template
>
<v-card
class=
"py-12"
>
<v-img
contain
src=
"@/assets/images/logos/Logo-Platform-One-HEAD.png"
class=
"logo mx-auto"
height=
"100px"
width=
"100px"
/>
<v-card-title
class=
"text-h4 mb-8 justify-center font-weight-bold"
>
NOTIFY THE PM ON WHY THEIR
<br
/>
REQUEST WAS DENIED
</v-card-title>
<v-select
v-model=
"select"
:items=
"items"
item-text=
"title"
item-value=
"key"
label=
"Templated Responses"
return-object
single-line
light
outlined
hide-details
class=
"select mx-auto"
@
change=
"updateTextArea()"
></v-select>
<v-textarea
ref=
"inputRef"
clearable
v-model=
"selectedMessage"
class=
"text-area mx-auto mt-10"
placeholder=
"Edit your response in here or choose from the templated responses provided in the dropdown..."
outlined
auto-grow
>
</v-textarea>
<v-card-actions
class=
"justify-center"
>
<v-btn
color=
"secondary"
@
click=
"denyDialog = false"
>
CANCEL
</v-btn>
<v-btn
:disabled=
"!selectedMessage"
color=
"primary"
v-on:click=
"denyPendingRegistration(), (denyDialog = false)"
>
SEND NOTIFICATION
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<
script
>
import
TrainingService
from
"
@/api/services/training
"
;
import
{
SET_ERROR_MESSAGE
,
SET_ERROR_DIALOG
}
from
"
@/store/mutation-types
"
;
export
default
{
props
:
[
"
courseId
"
,
"
id
"
,
"
pm
"
,
"
mainMessage
"
],
data
()
{
return
{
selectedMessage
:
""
,
messageSubText
:
""
,
select
:
""
,
items
:
[
{
key
:
0
,
title
:
"
Payment has not been verified
"
,
mainText
:
""
,
subText
:
"
According to our records, your payment for another seat has not been validated. Please contact the Customer Success team.
"
,
},
{
key
:
1
,
title
:
"
Name has not been verified
"
,
mainText
:
""
,
subText
:
"
According to our records, the team member you have requested does not match our submission list. Please contact the Customer Success Team.
"
,
},
{
key
:
2
,
title
:
"
Course capacity is at maximum
"
,
mainText
:
""
,
subText
:
"
We are unable to add another seat to the course as we have reached maximum capacity. Please email the course instructor for further advice.
"
,
},
],
denyDialog
:
false
,
};
},
methods
:
{
updateTextArea
()
{
this
.
selectedMessage
=
this
.
select
.
subText
;
this
.
subText
=
this
.
select
.
subText
;
},
async
denyPendingRegistration
()
{
try
{
const
response
=
await
TrainingService
.
putCoursePendingRegistrationsById
(
this
.
$props
.
courseId
,
{
userId
:
this
.
$props
.
id
,
pmId
:
this
.
$props
.
pm
,
mainText
:
this
.
$props
.
mainMessage
,
subText
:
this
.
subText
,
add
:
false
,
}
);
this
.
total
=
response
.
meta
?.
total
||
0
;
}
catch
(
error
)
{
const
errorMessage
=
error
;
this
.
$store
.
commit
(
SET_ERROR_MESSAGE
,
errorMessage
);
this
.
$store
.
commit
(
SET_ERROR_DIALOG
,
true
);
}
finally
{
this
.
$emit
(
"
change
"
,
true
);
}
},
},
};
</
script
>
<
style
lang=
"scss"
scoped
>
.select
{
background-color
:
white
;
width
:
60%
;
height
:
56px
;
font-size
:
14px
;
}
.text-area
{
width
:
80%
;
font-size
:
14px
;
}
</
style
>
src/components/Dialogs/SelectAddStudentsToCourseDialog.vue
View file @
02f0931c
...
...
@@ -114,7 +114,8 @@ export default {
try
{
await
CourseService
.
addStudentsToCourses
(
[
this
.
course
],
this
.
selectedMembers
this
.
selectedMembers
,
this
.
$store
.
state
.
user
.
user
.
id
);
this
.
$emit
(
"
update-students
"
);
}
catch
(
error
)
{
...
...
src/components/Notification.vue
0 → 100644
View file @
02f0931c
<
template
>
<v-container
class=
"py-0"
>
<v-list
color=
"tertiary"
max-height=
"300px"
class=
"v-list pa-0"
>
<v-list-item
v-if=
"this.listItems.count === 0"
>
<v-list-item-content
class=
"pa-0"
>
<v-list-item-title
class=
"text-wrap text-left"
>
No notifications to display.
</v-list-item-title
>
</v-list-item-content>
</v-list-item>
<v-list-item
v-else
v-for=
"(item, i) in listItems.rows"
:key=
"i"
>
<v-col
class=
"text-left pa-0"
>
<v-list-item-icon>
<NotificationLogo
/>
</v-list-item-icon>
<v-list-item-content
class=
"pa-0"
>
<v-list-item-title
class=
"text-wrap text-left"
v-text=
"item.mainText"
></v-list-item-title>
<v-list-item-subtitle
v-text=
"item.subText"
class=
"text-wrap mt-3"
></v-list-item-subtitle>
<v-col
class=
"v-list-item-col text-right"
>
<v-btn
color=
"primary"
outlined
type=
"button"
@
click=
"clearNotification(item.id)"
>
Clear Notification
</v-btn
>
</v-col>
</v-list-item-content>
</v-col>
</v-list-item>
</v-list>
</v-container>
</
template
>
<
script
>
import
UserService
from
"
@/api/services/user
"
;
import
NotificationLogo
from
"
@/assets/images/logos/Vector.svg
"
;
import
{
SET_ERROR_MESSAGE
,
SET_ERROR_DIALOG
}
from
"
@/store/mutation-types
"
;
export
default
{
components
:
{
NotificationLogo
,
},
props
:
[
"
courseId
"
,
"
id
"
,
"
pm
"
],
data
()
{
return
{
listItems
:
[],
createDialog
:
false
,
editDialog
:
false
,
sentDialog
:
false
,
editNotif
:
false
,
};
},
async
mounted
()
{
this
.
fetchData
();
},
methods
:
{
async
fetchData
()
{
this
.
loading
=
true
;
try
{
this
.
listItems
=
await
UserService
.
getNotifications
();
}
catch
(
error
)
{
const
errorMessage
=
error
;
this
.
$store
.
commit
(
SET_ERROR_MESSAGE
,
errorMessage
);
this
.
$store
.
commit
(
SET_ERROR_DIALOG
,
true
);
}
this
.
loading
=
false
;
this
.
fetchingData
=
false
;
},
async
clearNotification
(
id
)
{
try
{
await
UserService
.
deleteNotifications
(
id
);
}
catch
(
error
)
{
const
errorMessage
=
error
;
this
.
$store
.
commit
(
SET_ERROR_MESSAGE
,
errorMessage
);
this
.
$store
.
commit
(
SET_ERROR_DIALOG
,
true
);
}
finally
{
this
.
fetchData
();
}
},
},
};
</
script
>
<
style
lang=
"scss"
scoped
>
.v-list
{
overflow-y
:
auto
;
}
.v-list-item-col
{
width
:
90%
;
}
</
style
>
src/components/PersonnelTable.vue
View file @
02f0931c
...
...
@@ -122,7 +122,6 @@
}}
</span>
</v-tooltip>
<v-tooltip
top
>
<
template
v-slot:activator=
"{ on, attrs }"
>
<v-btn
...
...
src/components/Training/PendingRequests.vue
0 → 100644
View file @
02f0931c
<
template
>
<v-container>
<v-row>
<v-col>
<h2
class=
"text-left px-0 my-3"
>
Pending Requests
</h2>
</v-col>
</v-row>
<v-data-table
id=
"pending-table"
v-if=
"headers"
:headers=
"header"
:items=
"listItems"
:search=
"search"
:hide-default-header=
"onMobile"
mobile-breakpoint=
"800"
:options.sync=
"options"
:footer-props=
"footerProps"
:server-items-length=
"total"
:hide-default-footer=
"listItems.length === 0"
:loading=
"loading"
>
<template
v-slot:
[`
item.user.name
`
]=
"
{ item }" class="px-0 my-0">
<div
class=
"my-0 py-0"
>
<p
class=
"py-0 mb-0"
>
{{
item
.
user
.
name
}}
</p>
</div>
</
template
>
<
template
v-slot:
[`
item.user.role
`
]=
"{ item }"
class=
"px-0 my-0"
>
<div
class=
"my-0 py-0"
>
<p
class=
"py-0 my-0"
>
{{
item
.
user
.
role
}}
</p>
</div>
</
template
>
<
template
v-slot:
[`
item.user.email
`
]=
"{ item }"
class=
"py-0 my-0"
>
<div
class=
"my-0 py-0"
>
<p
class=
"py-0 my-0"
>
{{
item
.
user
.
email
}}
</p>
</div>
</
template
>
<
template
v-slot:
[`
item.pm.name
`
]=
"{ item }"
class=
"px-0 my-0"
>
<div
class=
"my-0 py-0"
>
<p
class=
"py-0 mb-0"
>
{{
item
.
pm
.
name
}}
</p>
</div>
</
template
>
<
template
v-slot:
[`
item.approval
`
]=
"{ item }"
class=
"py-0"
>
<div
class=
"d-flex align-center justify-center"
>
<DenialNotification
v-bind:courseId=
"currentCourseId"
v-bind:id=
"item.user.id"
v-bind:pm=
"item.pm.id"
v-bind:mainMessage=
"mainMessage"
class=
"mx-2"
@
change=
"fetchReload"
/>
<v-btn
color=
"primary"
class=
"mx-2 black--text align-center justify-center"
max-width=
"50"
v-on:click=
"acceptPendingRegistration(item.user.id, item.pm.id)"
>
Approve
</v-btn>
</div>
</
template
>
</v-data-table>
</v-container>
</template>
<
script
>
import
TrainingService
from
"
@/api/services/training
"
;
import
DenialNotification
from
"
@/components/DenialNotification
"
;
import
{
debounceTimeout
}
from
"
@/config/config
"
;
import
{
SET_ERROR_MESSAGE
,
SET_ERROR_DIALOG
}
from
"
@/store/mutation-types
"
;
import
{
DEFAULT_FOOTER_PROPS
,
DEFAULT_PAGINATION_PARAMS
,
DEFAULT_TABLE_OPTIONS
,
}
from
"
@/config/table-constants
"
;
export
default
{
name
:
"
PendingRequests
"
,
components
:
{
DenialNotification
},
props
:
{
trainingCourse
:
{
type
:
Object
,
required
:
true
,
},
reload
:
{
type
:
Number
,
},
},
data
()
{
return
{
search
:
""
,
loading
:
false
,
total
:
0
,
footerProps
:
DEFAULT_FOOTER_PROPS
,
params
:
{
...
DEFAULT_PAGINATION_PARAMS
,
},
options
:
DEFAULT_TABLE_OPTIONS
,
state
:
{
isAddingStudent
:
false
,
isAddingDuplicate
:
false
,
isAddingBusy
:
false
,
},
listItems
:
[],
mobileBreakpoint
:
800
,
headers
:
[
{
text
:
"
Name
"
,
value
:
"
user.name
"
,
width
:
"
175px
"
,
sortable
:
false
},
{
text
:
"
Role
"
,
value
:
"
user.role
"
,
width
:
"
175px
"
,
sortable
:
false
},
{
text
:
"
Email
"
,
value
:
"
user.email
"
,
width
:
"
175px
"
,
sortable
:
false
,
},
{
text
:
"
Team PM
"
,
value
:
"
pm.name
"
,
width
:
"
175px
"
,
sortable
:
false
,
},
{
text
:
"
Approval
"
,
value
:
"
approval
"
,
width
:
"
250px
"
,
sortable
:
false
,
},
],
mobileHeader
:
[
{
text
:
"
Name
"
,
value
:
"
user.name
"
,
width
:
"
100%
"
,
align
:
"
center
"
,
sortable
:
false
,
},
{
text
:
"
Email
"
,
value
:
"
user.email
"
,
width
:
"
100%
"
,
align
:
"
center
"
,
sortable
:
false
,
},
{
text
:
"
Approval
"
,
value
:
"
approval
"
,
width
:
"
!00%
"
,
sortable
:
false
,
},
],
};
},
async
mounted
()
{
await
this
.
fetchData
();
},
methods
:
{
fetchDebounced
()
{
// cancel pending call
clearTimeout
(
this
.
_timerId
);
this
.
fetchingData
=
true
;
// delay new call
this
.
_timerId
=
setTimeout
(()
=>
{
this
.
fetchData
();
},
debounceTimeout
);
},
async
fetchData
()
{
this
.
loading
=
true
;
try
{
const
response
=
await
TrainingService
.
getCoursePendingRegistrationsById
(
this
.
currentCourseId
);
this
.
listItems
=
response
.
registrations
;
this
.
total
=
response
.
meta
?.
total
||
0
;
}
catch
(
error
)
{
const
errorMessage
=
error
;
console
.
error
(
"
ERROR
"
);
this
.
$store
.
commit
(
SET_ERROR_MESSAGE
,
errorMessage
);
this
.
$store
.
commit
(
SET_ERROR_DIALOG
,
true
);
}
this
.
loading
=
false
;
this
.
fetchingData
=
false
;
},
async
acceptPendingRegistration
(
userId
,
pmId
)
{
try
{
await
TrainingService
.
putCoursePendingRegistrationsById
(
this
.
currentCourseId
,
{
userId
:
userId
,
pmId
:
pmId
,
mainText
:
"
Your request to add a team member into
"
+
this
.
$props
.
trainingCourse
.
name
+
"
-
"
+
this
.
$props
.
trainingCourse
.
startDate
+
"
-
"
+
this
.
$props
.
trainingCourse
.
endDate
+
"
has been approved
"
,
subText
:
""
,
add
:
true
,
}
);
}
catch
(
error
)
{
const
errorMessage
=
error
;
this
.
$store
.
commit
(
SET_ERROR_MESSAGE
,
errorMessage
);
}
finally
{
this
.
$emit
(
"
change
"
,
true
);
this
.
fetchDebounced
();
}
},
fetchReload
(
value
)
{
if
(
value
)
{
this
.
fetchDebounced
();
}
},
},
computed
:
{
onMobile
()
{
return
this
.
$vuetify
.
breakpoint
.
width
<
this
.
mobileBreakpoint
;
},
header
()
{
return
this
.
onMobile
?
this
.
mobileHeader
:
this
.
headers
;
},
currentCourseId
()
{
return
this
.
trainingCourse
.
id
;
},
mainMessage
()
{
return
(
"
Your request to add a team member into
"
+
this
.
$props
.
trainingCourse
.
name
+
"
-
"
+
this
.
$props
.
trainingCourse
.
startDate
+
"
-
"
+
this
.
$props
.
trainingCourse
.
endDate
+
"
has been denied due to the following reasons:
"
);
},
},
};
</
script
>
<
style
lang=
"scss"
scoped
>
.search
{
width
:
250px
;
}
#pending-table
{
::v-deep
.v-data-table__wrapper
{
table
>
tbody
>
tr
>
td
:nth-child
(
5
)
>
div
>
button
.v-btn
{
min-width
:
112px
;
}
.v-data-table__mobile-table-row
{
>
.v-data-table__mobile-row
{
margin
:
0px
0px
-10px
0px
;
padding
:
0px
0px
0px
0px
;
&
:nth-child
(
n
)
{
width
:
100%
;
}
&
:nth-child
(
2
)
{
color
:
#bdc931
;
.v-data-table__mobile-row__cell
{
margin
:
-30px
0px
0px
0px
;
}
}
.v-data-table__mobile-row__cell
{
width
:
100%
!
important
;
align-self
:
center
;
text-align
:
center
;
justify-content
:
center
;
}
&
:nth-child
(
3
)
{
white-space
:
nowrap
;
div
>
button
.v-btn
{
min-width
:
112px
;
}
.v-data-table__mobile-row__cell
{
padding-top
:
6px
;
padding-bottom
:
32px
;
}
}
}
}
}
}
</
style
>
src/views/TrainingDetails.vue
View file @
02f0931c
...
...
@@ -20,6 +20,7 @@
color=
"primary"
@
click=
"state.isAddingStudent = true"
class=
"mx-auto ml-4 black--text"
v-if=
"!onMobile"
>
Add Student
</v-btn>
...
...
@@ -315,6 +316,7 @@
background-color=
"white"
height=
"8px"
:value=
"courseProgress"
v-if=
"loading"
></v-progress-linear>
</div>
</
template
>
...
...
@@ -370,7 +372,12 @@
Edit Course
</v-btn>
</div>
<PendingRequests
ref=
"pendingRequests"
class=
"px-0"
v-if=
"trainingCourse.id"
:trainingCourse=
"trainingCourse"
/>
<TrainingAttendance
ref=
"trainingAttendance"
class=
"px-0"
...
...
@@ -389,6 +396,7 @@ import TrainingService from "@/api/services/training";
import
inputRules
from
"
@/utils/inputRules
"
;
import
NotFoundComponent
from
"
@/components/NotFoundComponent
"
;
import
TrainingAttendance
from
"
@/components/Training/TrainingAttendance
"
;
import
PendingRequests
from
"
@/components/Training/PendingRequests
"
;
import
{
SET_ERROR_MESSAGE
,
SET_ERROR_DIALOG
}
from
"
@/store/mutation-types
"
;
import
UserSelect
from
"
@/components/UserSelect
"
;
import
{
DEFAULT_PAGINATION_PARAMS
}
from
"
@/config/table-constants
"
;
...
...
@@ -399,6 +407,7 @@ export default {
AddStudentsToCourseDialog
,
TrainingAttendance
,
UserSelect
,
PendingRequests
,
NotFoundComponent
,
},
data
:
()
=>
({
...
...
@@ -583,7 +592,7 @@ export default {
// delay new call
this
.
fetchData
();
this
.
$refs
.
trainingAttendance
.
fetchDebounced
();
this
.
$refs
.
addStudentsToCourseDialog
.
fetchDebounced
();
this
.
$refs
.
trainingAttendance
.
fetchDebounced
();
},
async
updateCourse
()
{
this
.
loading
=
true
;
...
...
src/views/super-admin/LaunchboardSuperAdmin.vue
View file @
02f0931c
...
...
@@ -17,6 +17,10 @@
<WelcomeSummary
slot=
"content"
/>
</Section>
<Section
class=
"mt-6"
headerTitle=
"Notifications"
>
<Notification
slot=
"content"
/>
</Section>
<Section
class=
"mt-6"
sync
...
...
@@ -113,6 +117,7 @@ import UserBanner from "@/components/UserBanner";
import
Section
from
"
@/components/Section
"
;
import
AdminCourses
from
"
@/components/AdminCourses
"
;
import
HelpDeskSummary
from
"
@/components/HelpDeskSummary
"
;
import
Notification
from
"
@/components/Notification
"
;
import
{
SET_PAST_METRICS_STATE
,
SET_PRESENT_METRICS_STATE
,
...
...
@@ -132,6 +137,7 @@ export default {
Section
,
AdminCourses
,
HelpDeskSummary
,
Notification
,
WelcomeSummary
,
SeatMetrics
,
},
...
...
tests/unit/components/DenialNotification.spec.js
0 → 100644
View file @
02f0931c
import
{
shallowMount
}
from
"
@vue/test-utils
"
;
import
TrainingService
from
"
@/api/services/training
"
;
import
DenialNotification
from
"
@/components/DenialNotification
"
;
import
{
SET_ERROR_MESSAGE
,
SET_ERROR_DIALOG
}
from
"
@/store/mutation-types
"
;
describe
(
"
DenialNotification
"
,
()
=>
{
const
mockError
=
"
mock error
"
;
it
(
"
should send back a notification that denies the user
"
,
async
()
=>
{
const
wrapper
=
shallowMount
(
DenialNotification
,
{
mocks
:
{
$store
:
{
state
:
{
error
:
{},
},
commit
:
jest
.
fn
(),
},
},
data
()
{
return
{
select
:
{
subText
:
"
mockOne
"
},
selectedMessage
:
""
,
mainText
:
""
,
subText
:
""
,
};
},
});
wrapper
.
vm
.
updateTextArea
();
expect
(
wrapper
.
vm
.
selectedMessage
).
toEqual
(
"
mockOne
"
);
expect
(
wrapper
.
vm
.
subText
).
toEqual
(
"
mockOne
"
);
});
it
(
"
should send a notification of denial to pm
"
,
async
()
=>
{
TrainingService
.
putCoursePendingRegistrationsById
=
jest
.
fn
()
.
mockReturnValue
({
status
:
"
success
"
});
const
wrapper
=
shallowMount
(
DenialNotification
,
{
mocks
:
{
$store
:
{
state
:
{
error
:
{},
},
commit
:
jest
.
fn
(),
},
},
});
await
wrapper
.
vm
.
denyPendingRegistration
();
expect
(
TrainingService
.
putCoursePendingRegistrationsById
).
toBeCalledTimes
(
1
);
});
it
(
"
should set error message if unable to send denial notification
"
,
async
()
=>
{
TrainingService
.
putCoursePendingRegistrationsById
=
jest
.
fn
()
.
mockRejectedValue
(
mockError
);
const
wrapper
=
shallowMount
(
DenialNotification
,
{
mocks
:
{
$store
:
{
state
:
{
error
:
{},
},
commit
:
jest
.
fn
(),
},
},
});
await
wrapper
.
vm
.
denyPendingRegistration
();
expect
(
wrapper
.
vm
.
$store
.
commit
).
toHaveBeenCalledWith
(
SET_ERROR_MESSAGE
,
"
mock error
"
);
expect
(
wrapper
.
vm
.
$store
.
commit
).
toHaveBeenCalledWith
(
SET_ERROR_DIALOG
,
true
);
});
});
tests/unit/components/Dialogs/SelectAddStudentsToCourseDialog.spec.js
View file @
02f0931c
...
...
@@ -112,6 +112,15 @@ describe("SelectAddStudentsToCourseDialog", () => {
});
it
(
"
addSelectedUsersToSelectedCourses should call addStudentsToCourses
"
,
async
()
=>
{
const
wrapper
=
shallowMount
(
SelectAddStudentsToCourseDialog
,
{
mocks
:
{
$store
:
{
state
:
{
error
:
{},
user
:
{
user
:
{
id
:
1
}
},
},
commit
:
jest
.
fn
(),
},
},
propsData
:
{
value
:
[],
selectedMembers
:
[],
...
...
@@ -139,6 +148,10 @@ describe("SelectAddStudentsToCourseDialog", () => {
},
mocks
:
{
$store
:
{
state
:
{
error
:
{},
user
:
{
user
:
{
id
:
1
}
},
},
commit
:
jest
.
fn
(),
},
},
...
...
@@ -168,6 +181,10 @@ describe("SelectAddStudentsToCourseDialog", () => {
},
mocks
:
{
$store
:
{
state
:
{
error
:
{},
user
:
{
user
:
{
id
:
1
}
},
},
commit
:
jest
.
fn
(),
},
},
...
...
tests/unit/components/Notification.spec.js
0 → 100644
View file @
02f0931c
import
{
shallowMount
}
from
"
@vue/test-utils
"
;
import
UserService
from
"
@/api/services/user
"
;
import
Notification
from
"
@/components/Notification
"
;
import
{
SET_ERROR_MESSAGE
,
SET_ERROR_DIALOG
}
from
"
@/store/mutation-types
"
;
describe
(
"
Notification
"
,
()
=>
{
const
mockError
=
"
mock error
"
;
it
(
"
should grab all notifications sent to current user
"
,
async
()
=>
{
const
wrapper
=
shallowMount
(
Notification
,
{
mocks
:
{
$store
:
{
state
:
{
error
:
{},
},
commit
:
jest
.
fn
(),
},
},
data
()
{
return
{
listItems
:
[],
createDialog
:
false
,
editDialog
:
false
,
sentDialog
:
false
,
editNotif
:
false
,
};
},
});
wrapper
.
vm
.
fetchData
();
expect
(
wrapper
.
vm
.
listItems
).
toEqual
([]);
});
it
(
"
should set error message if unable to receive notifications
"
,
async
()
=>
{
UserService
.
getNotifications
=
jest
.
fn
().
mockRejectedValue
(
mockError
);
const
wrapper
=
shallowMount
(
Notification
,
{
mocks
:
{
$store
:
{
state
:
{
error
:
{},
},
commit
:
jest
.
fn
(),
},
},
});
await
wrapper
.
vm
.
fetchData
();
expect
(
wrapper
.
vm
.
$store
.
commit
).
toHaveBeenCalledWith
(
SET_ERROR_MESSAGE
,
"
mock error
"
);
expect
(
wrapper
.
vm
.
$store
.
commit
).
toHaveBeenCalledWith
(
SET_ERROR_DIALOG
,
true
);
});
it
(
"
should delete the desired notification from the user
"
,
async
()
=>
{
UserService
.
deleteNotifications
=
jest
.
fn
()
.
mockResolvedValue
({
result
:
"
true
"
});
const
wrapper
=
shallowMount
(
Notification
,
{
mocks
:
{
$store
:
{
state
:
{
error
:
{},
},
commit
:
jest
.
fn
(),
},
},
data
()
{
return
{
listItems
:
[],
};
},
});
await
wrapper
.
vm
.
clearNotification
();
expect
(
UserService
.
deleteNotifications
).
toHaveBeenCalledTimes
(
1
);
});
it
(
"
should set error message if unable to delete notification
"
,
async
()
=>
{
UserService
.
deleteNotifications
=
jest
.
fn
().
mockRejectedValue
(
mockError
);
const
wrapper
=
shallowMount
(
Notification
,
{
mocks
:
{
$store
:
{
state
:
{
error
:
{},
},
commit
:
jest
.
fn
(),
},
},
});
await
wrapper
.
vm
.
clearNotification
();
expect
(
wrapper
.
vm
.
$store
.
commit
).
toHaveBeenCalledWith
(
SET_ERROR_MESSAGE
,
"
mock error
"
);
expect
(
wrapper
.
vm
.
$store
.
commit
).
toHaveBeenCalledWith
(
SET_ERROR_DIALOG
,
true
);
});
});
tests/unit/components/Training/PendingRequests.spec.js
0 → 100644
View file @
02f0931c
import
Vuex
from
"
vuex
"
;
import
Vuetify
from
"
vuetify
"
;
import
{
shallowMount
,
createLocalVue
}
from
"
@vue/test-utils
"
;
import
PendingRequests
from
"
@/components/Training/PendingRequests
"
;
import
TrainingService
from
"
@/api/services/training
"
;
import
{
SET_ERROR_MESSAGE
}
from
"
@/store/mutation-types
"
;
import
flushPromises
from
"
flush-promises
"
;
const
vuetify
=
new
Vuetify
();
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
describe
(
"
PendingRequests
"
,
()
=>
{
const
propsData
=
{
trainingCourse
:
{
id
:
1
,
startDate
:
"
2021-01-01
"
,
endDate
:
"
2021-01-03
"
,
name
:
"
Test
"
,
},
};
afterEach
(()
=>
{
jest
.
resetAllMocks
();
});
it
(
"
should load component
"
,
async
()
=>
{
TrainingService
.
getCoursePendingRegistrationsById
=
jest
.
fn
()
.
mockResolvedValue
({
registrations
:
[{
user
:
[{
name
:
"
user4
"
}]
},
{}],
});
// render the component
const
wrapper
=
shallowMount
(
PendingRequests
,
{
mocks
:
{
$store
:
{
state
:
{
error
:
{},
},
commit
:
jest
.
fn
(),
},
},
data
()
{
return
{
loading
:
true
,
initialLoad
:
true
,
listItems
:
[{
userId
:
1
},
{
userId
:
2
},
{
userId
:
3
}],
};
},
propsData
,
localVue
,
vuetify
,
});
await
flushPromises
();
// assert the component loads without error
expect
(
wrapper
.
find
(
"
.error
"
).
exists
()).
toBe
(
false
);
});
it
(
"
should catch error with fetchData
"
,
async
()
=>
{
TrainingService
.
getCoursePendingRegistrationsById
=
jest
.
fn
()
.
mockRejectedValue
(
"
mock-error
"
);
// render the component
const
wrapper
=
shallowMount
(
PendingRequests
,
{
mocks
:
{
$store
:
{
state
:
{
error
:
{},
},
commit
:
jest
.
fn
(),
},
},
data
()
{
return
{
loading
:
true
,
initialLoad
:
true
,
listItems
:
[{
userId
:
1
},
{
userId
:
2
},
{
userId
:
3
}],
};
},
propsData
,
localVue
,
vuetify
,
});
await
flushPromises
();
expect
(
wrapper
.
vm
.
$store
.
commit
).
toHaveBeenCalledWith
(
SET_ERROR_MESSAGE
,
"
mock-error
"
);
});
it
(
"
should fetch debounced
"
,
async
()
=>
{
TrainingService
.
getCourseRegistrationsById
=
jest
.
fn
()
.
mockResolvedValue
({
users
:
[{
name
:
"
user1
"
}]
});
PendingRequests
.
fetchData
=
jest
.
fn
();
const
wrapper
=
shallowMount
(
PendingRequests
,
{
mocks
:
{
$route
:
{
params
:
{
trainingId
:
1
,
},
},
$store
:
{
state
:
{
error
:
{},
},
commit
:
jest
.
fn
(),
},
},
data
()
{
return
{
fetchingData
:
false
,
listItems
:
[{
userId
:
1
},
{
userId
:
2
},
{
userId
:
3
}],
};
},
propsData
,
localVue
,
vuetify
,
});
expect
(
wrapper
.
vm
.
fetchingData
).
toBe
(
false
);
await
flushPromises
();
await
wrapper
.
vm
.
fetchDebounced
();
expect
(
wrapper
.
vm
.
fetchingData
).
toBe
(
true
);
});
it
(
"
should call fetchReload
"
,
async
()
=>
{
TrainingService
.
getCourseRegistrationsById
=
jest
.
fn
()
.
mockResolvedValue
({
users
:
[{
name
:
"
user1
"
}]
});
const
wrapper
=
shallowMount
(
PendingRequests
,
{
mocks
:
{
$store
:
{
state
:
{
error
:
{},
},
commit
:
jest
.
fn
(),
},
},
data
()
{
return
{
selectedUsers
:
[{
userId
:
1
},
{
userId
:
2
}],
listItems
:
[{
userId
:
1
},
{
userId
:
2
},
{
userId
:
3
}],
};
},
propsData
,
localVue
,
vuetify
,
});
await
flushPromises
();
wrapper
.
vm
.
fetchDebounced
=
jest
.
fn
();
await
wrapper
.
vm
.
$nextTick
();
await
wrapper
.
vm
.
fetchReload
(
true
);
expect
(
wrapper
.
vm
.
fetchDebounced
).
toHaveBeenCalledTimes
(
1
);
});
it
(
"
should call acceptPendingRegistration
"
,
async
()
=>
{
TrainingService
.
putCoursePendingRegistrationsById
=
jest
.
fn
()
.
mockResolvedValue
({
result
:
"
true
"
});
// render the component
const
wrapper
=
shallowMount
(
PendingRequests
,
{
mocks
:
{
$store
:
{
state
:
{
error
:
{},
},
commit
:
jest
.
fn
(),
},
},
data
()
{
return
{
loading
:
true
,
initialLoad
:
true
,
selectedUsers
:
[{
userId
:
1
},
{
userId
:
2
}],
listItems
:
[{
userId
:
1
},
{
userId
:
2
},
{
userId
:
3
}],
};
},
propsData
,
localVue
,
vuetify
,
});
await
wrapper
.
vm
.
acceptPendingRegistration
();
await
flushPromises
();
expect
(
TrainingService
.
putCoursePendingRegistrationsById
).
toHaveBeenCalledTimes
(
1
);
});
it
(
"
should catch acceptPendingRegistration errors
"
,
async
()
=>
{
TrainingService
.
putCoursePendingRegistrationsById
=
jest
.
fn
()
.
mockRejectedValue
(
"
mock-error
"
);
// render the component
const
wrapper
=
shallowMount
(
PendingRequests
,
{
mocks
:
{
$store
:
{
state
:
{
error
:
{},
},
commit
:
jest
.
fn
(),
},
},
data
()
{
return
{
loading
:
true
,
initialLoad
:
true
,
listItems
:
[{
userId
:
1
},
{
userId
:
2
},
{
userId
:
3
}],
};
},
propsData
,
localVue
,
vuetify
,
});
await
wrapper
.
vm
.
acceptPendingRegistration
();
await
flushPromises
();
expect
(
wrapper
.
vm
.
$store
.
commit
).
toHaveBeenCalledWith
(
SET_ERROR_MESSAGE
,
"
mock-error
"
);
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment