UNCLASSIFIED

Commit 1da8d9a0 authored by graham.smith's avatar graham.smith
Browse files

Merge branch 'BULL-797' into 'master'

BULL-797: Update unit test

See merge request !131
parents 02a57f2a 364dd6fe
This diff is collapsed.
......@@ -161,11 +161,13 @@
"minimist": "^1.2.5",
"node-notifier": "^9.0.0",
"normalize-url": "^4.5.1",
"path-parse": "^1.0.7",
"postcss": "^7.0.36",
"serialize-javascript": "^3.1.0",
"sockjs": "^0.3.21",
"ssri": "^8.0.1",
"trim-newlines": "^3.0.0",
"url-parse": "^1.5.3",
"y18n": "^5.0.5",
"yargs-parser": "^18.1.3",
"ws": "^7.4.6"
......
......@@ -8,5 +8,18 @@ export const createBaseHttp = () => {
};
export const HTTP = createBaseHttp();
HTTP.interceptors.response.use((response) => response?.data?.result);
HTTP.interceptors.response.use(
function (response) {
return response?.data?.result;
},
function (err) {
// If err.status does not exist a general networking error occurred.
// We'll assume that their keycloak session has expired, so refresh the
// page which will redirect them to login
if (!err.status) {
location.reload();
} else {
throw err;
}
}
);
import axios from "axios";
// TODO: the goal is to be able to delete this file - this is only temporary
// until the backend has the necessary REST endpoints and the frontend has
// been updated to call them
export const HTTP = axios.create({
baseURL: "/static/mock-data",
});
......@@ -2,6 +2,6 @@ import { HTTP } from "@/api/http-common";
export default {
async capacityMetrics(params) {
return await HTTP.get("/admin/capacity-metrics", { params });
return HTTP.get("/admin/capacity-metrics", { params });
},
};
......@@ -2,7 +2,7 @@ import { HTTP } from "@/api/http-common";
export default {
async getProjectsSummary() {
return await HTTP.get(
return HTTP.get(
"/gitlab/projects-with-jobs-and-pipelines-for-logged-in-user"
);
},
......
......@@ -5,7 +5,7 @@ const defaultCertificateFileName = "certificate.pdf";
export default {
defaultCertificateFileName,
async getScheduleForUser() {
return await HTTP.get("/courses/my-registrations");
return HTTP.get("/courses/my-registrations");
},
async downloadCertificate(courseId, userId) {
const certificateHTTP = createBaseHttp();
......
import get from "lodash/get";
import { HTTP } from "@/api/mock-http-common";
export default {
async getStatusSummary() {
const response = await HTTP.get(`status.json`);
return get(response, "data.result");
},
};
......@@ -9,13 +9,13 @@ const getTeamMemberBody = (users) => {
export default {
async getMyTeam() {
return await HTTP.get("/teams/my-teams");
return HTTP.get("/teams/my-teams");
},
async getTeams(params) {
return await HTTP.get("/teams", { params });
return HTTP.get("/teams", { params });
},
async getTeam(teamId) {
return await HTTP.get(`/teams/${teamId}`, {
return HTTP.get(`/teams/${teamId}`, {
params: { members: true },
});
},
......@@ -23,11 +23,11 @@ export default {
await HTTP.delete("/teams", { params: { teamIds: `[${teamIds.join()}]` } });
},
async addTeam(team) {
return await HTTP.post("/teams", team);
return HTTP.post("/teams", team);
},
async addTeams(newTeams) {
for (let i = 0; i < newTeams.length; i++) {
await this.addTeam(newTeams[i]);
for (const newTeam of newTeams) {
await this.addTeam(newTeam);
}
},
......
......@@ -68,22 +68,18 @@ export default {
window.open("/api/courses/export");
},
async getCourseRegistrationsById(courseId, params) {
const response = await HTTP.get(`/courses/${courseId}/registrations`, {
return HTTP.get(`/courses/${courseId}/registrations`, {
params,
});
return response;
},
async setUserAttendance(courseId, body) {
return await HTTP.put(
`/courses/${courseId}/registrations/attendance`,
body
);
return HTTP.put(`/courses/${courseId}/registrations/attendance`, body);
},
async setCourseRegistrationsByUserId(courseId, userId, body) {
return await HTTP.put(`/courses/${courseId}/registrations/${userId}`, body);
return HTTP.put(`/courses/${courseId}/registrations/${userId}`, body);
},
async removeCourseRegistrationsByUserId(courseId, userId) {
return await HTTP.delete(`/courses/${courseId}/registrations/${userId}`);
return HTTP.delete(`/courses/${courseId}/registrations/${userId}`);
},
async removeCourseRegistrationsByUsers(courseId, registrations) {
for (const registration of registrations) {
......
......@@ -2,10 +2,10 @@ import { HTTP } from "@/api/http-common";
export default {
async getUser() {
return await HTTP.get("/whoami");
return HTTP.get("/whoami");
},
async search(params) {
return await HTTP.get("/users", { params });
return HTTP.get("/users", { params });
},
async getUsersByPersonnelType(params) {
return this.search(params);
......
<template>
<v-container class="my-courses text-left px-2">
<v-skeleton-loader :loading="loading" type="paragraph@5" class="w-100">
<ErrorMessage v-if="error" v-bind:errorMessage="errorMessage" />
<v-card v-if="emptyString">
<v-card-title
>There currently are no current or upcoming courses.</v-card-title
>
<v-card-text
>Courses in progress or upcoming will appear here.</v-card-text
>
</v-card>
<v-container class="px-6" v-else>
<div
v-for="(item, index) in schedule"
:key="index"
:class="{ last: index == schedule.length - 1 }"
>
<div
v-if="inDateRange(item.startDate, item.endDate)"
class="dot dot-white"
></div>
<div v-else class="dot dot-green"></div>
<div class="d-flex col-12 py-0 class-container">
<div
v-if="inDateRange(item.startDate, item.endDate)"
class="dot-border dot-border-white col-1 d-inline-block"
></div>
<div
v-else
class="dot-border dot-border-green col-1 d-inline-block"
></div>
<div
class="col-sm col-3 d-inline-block d-flex align-start class-name"
>
{{ item.name }}
</div>
<div class="col-sm col-4 d-inline-block resText">
{{ item.startDate }} - {{ item.endDate }}
</div>
<div class="col-sm col-4 d-inline-block resText">
<div v-if="inDateRange(item.startDate, item.endDate)">
In Progress
</div>
<div v-else>
{{ seatsRemaining(item.capacity, item.registeredCount) }}
remaining seats
</div>
</div>
</div>
</div>
</v-container>
</v-skeleton-loader>
</v-container>
</template>
<script>
import TrainingService from "@/api/services/training";
import moment from "moment";
import { SET_ERROR_MESSAGE } from "@/store/mutation-types";
import ErrorMessage from "@/components/APIErrorCard";
export default {
components: {
ErrorMessage,
},
data: () => ({
loading: false,
schedule: [],
emptyString: false,
errorMessage: null,
error: false,
}),
async mounted() {
this.params = {
registeredCount: true,
dateFrom: `${moment().format("YYYY-MM-DD")}`,
dateTo: `${moment().add(6, "months").format("YYYY-MM-DD")}`,
sort: "startDate",
};
try {
this.loading = true;
const response = await TrainingService.getCourses(this.params);
this.schedule = response.courses;
if (this.schedule.length === 0) {
this.emptyString = true;
}
} catch (error) {
this.error = true;
this.$store.commit(SET_ERROR_MESSAGE, error);
this.errorMessage = error;
} finally {
this.loading = false;
}
},
methods: {
inDateRange(startDate, endDate) {
return moment().isBetween(startDate, endDate);
},
seatsRemaining(capacity, registered) {
return capacity - registered;
},
},
};
</script>
<template>
<div>
This is a test
<UserSelect
:force-error="areNoAvailableSeats"
:force-error-message="
......@@ -37,12 +38,10 @@ export default {
type: String,
default: "Select Course",
},
// needed in order for Add to Course button to become enabled
// parent components need to pass in their own updateCourseMembers
updateCourseMembers: {
type: Function,
default: () => {
// TODO: confirm this is even being triggered; looks like it's being passed as prop 'update-course-members'
return;
},
},
isCourseSelected: {
type: Boolean,
......
<template>
<v-container class="curriculum-schedule text-left px-2">
<v-skeleton-loader :loading="loading" type="paragraph@5" class="w-100">
<ErrorMessage v-if="error" v-bind:errorMessage="errorMessage" />
<v-card v-if="emptyString">
<v-card-title
>There currently are no current or upcoming courses.</v-card-title
>
<v-card-text
>Courses in progress or upcoming will appear here.</v-card-text
>
</v-card>
<v-container class="px-6" v-else>
<div
v-for="(item, index) in schedule"
:key="index"
:class="{ last: index == schedule.length - 1 }"
>
<div
v-if="inDateRange(item.startDate, item.endDate)"
class="dot dot-white"
></div>
<div v-else class="dot dot-green"></div>
<div class="d-flex col-12 py-0 class-container">
<div
v-if="inDateRange(item.startDate, item.endDate)"
class="dot-border dot-border-white col-1 d-inline-block"
></div>
<div
v-else
class="dot-border dot-border-green col-1 d-inline-block"
></div>
<div
class="col-sm col-3 d-inline-block d-flex align-start class-name"
>
{{ item.name }}
</div>
<div class="col-sm col-4 d-inline-block resText">
{{ item.startDate }} - {{ item.endDate }}
</div>
<div class="col-sm col-4 d-inline-block resText">
<div v-if="inDateRange(item.startDate, item.endDate)">
In Progress
</div>
<div v-else>
{{ seatsRemaining(item.capacity, item.registeredCount) }}
remaining seats
</div>
</div>
</div>
</div>
</v-container>
</v-skeleton-loader>
</v-container>
</template>
<script>
import TrainingService from "@/api/services/training";
import moment from "moment";
import { SET_ERROR_MESSAGE } from "@/store/mutation-types";
import ErrorMessage from "@/components/APIErrorCard";
export default {
components: {
ErrorMessage,
},
data: () => ({
loading: false,
schedule: [],
emptyString: false,
errorMessage: null,
error: false,
}),
async mounted() {
this.params = {
registeredCount: true,
instructorOf: true,
dateFrom: `${moment().format("YYYY-MM-DD")}`,
dateTo: `${moment().add(6, "months").format("YYYY-MM-DD")}`,
sort: "startDate",
};
try {
this.loading = true;
const response = await TrainingService.getCourses(this.params);
this.schedule = response.courses;
if (this.schedule.length === 0) {
this.emptyString = true;
}
} catch (error) {
this.error = true;
this.$store.commit(SET_ERROR_MESSAGE, error);
this.errorMessage = error;
} finally {
this.loading = false;
}
},
methods: {
inDateRange(startDate, endDate) {
return moment().isBetween(startDate, endDate);
},
seatsRemaining(capacity, registered) {
return capacity - registered;
},
},
};
</script>
......@@ -394,14 +394,13 @@ export default {
},
personnelList() {
const list = [...this.personnel.listItems];
const filteredList = list.filter(({ email, number, name }) => {
return list.filter(({ email, number, name }) => {
return (
includes(toLower(email), this.searchBy) ||
includes(toLower(name), this.searchBy) ||
includes(toLower(number), this.searchBy)
);
});
return filteredList;
},
permissionTypesDropdown() {
return map(this.permissionTypes, (label) => {
......
......@@ -230,9 +230,8 @@
import { mapState, mapMutations } from "vuex";
import ProjectService from "@/api/services/project";
import GitLabIcon from "@/components/icons/GitLabIcon";
import { SET_PROJECTS } from "@/store/mutation-types";
import { SET_PROJECTS, SET_ERROR_MESSAGE } from "@/store/mutation-types";
import ErrorMessage from "@/components/APIErrorCard";
import { SET_ERROR_MESSAGE } from "@/store/mutation-types";
export default {
components: {
......@@ -296,10 +295,9 @@ export default {
},
computed: {
filteredProjects() {
const projects = this.detailed
return this.detailed
? this.projects
: this.projects.filter((o) => o.favorite);
return projects;
},
...mapState({
projects: (state) => state.projects.list,
......
<template>
<v-container class="remaining-seats text-left px-2">
<v-skeleton-loader :loading="loading" type="paragraph@5" class="w-100">
<v-card v-if="emptyString">
<v-card-title>You currently don't have any classes! </v-card-title>
<v-card-text>Your scheduled classes will appear here.</v-card-text>
</v-card>
<v-container class="px-6" v-else>
<div
v-for="(item, index) in schedule"
:key="index"
class="flex-wrap text-left pb-3 row justify-content-center d-flex"
>
<div class="col-3 d-inline-block class-name">
{{ item.name }}
</div>
<div class="col-5 d-inline-block availability-bar">
<div class="pt-2">
<v-progress-linear
class="availability-bar"
rounded
background-color="white"
height="8px"
:value="(100 * item.registeredCount) / item.capacity"
></v-progress-linear>
</div>
</div>
<div class="col-2 d-inline-block">
<div class="white-space-nowrap ml-5 mt-1 small-count">
{{ item.registeredCount }} / {{ item.capacity }}
</div>
</div>
<div class="col-1 d-inline-block gutter" />
</div>
</v-container>
</v-skeleton-loader>
</v-container>
</template>
<script>
import TrainingService from "@/api/services/training";
import moment from "moment";
export default {
data: () => ({
loading: false,
schedule: [],
emptyString: false,
}),
async mounted() {
this.params = {
registeredCount: true,
dateFrom: `${moment().format("YYYY-MM-DD")}`,
dateTo: `${moment().add(6, "months").format("YYYY-MM-DD")}`,
};
try {
this.loading = true;
const response = await TrainingService.getCourses(this.params);
console.log(response);
this.schedule = response.courses;
if (this.schedule.length === 0) {
this.emptyString = true;
}
} catch (error) {
console.error(error);
} finally {
this.loading = false;
}
},
};
</script>
<style lang="scss">
.class-name {
color: #bdc931;
}
.small-count {
font-size: 10px;
}
</style>
<template>
<v-container class="status-summary text-center px-8">
<ErrorMessage v-if="error" />
<div v-else>
<div class="support-row d-flex flex-wrap flex-sm-nowrap text-left pb-8">
<div class="pr-0 pr-sm-10 text-container pb-3 pb-md-0">
<div class="subhead">View Status History</div>
Any previous status reports or connection issues
</div>
<v-spacer class="d-none d-sm-flex" />
<v-btn color="secondary" target="_blank" class="mx-auto">
View History
</v-btn>
</div>
<div class="d-flex flex-column text-left">
<v-skeleton-loader :loading="loading" type="list-item-avatar@8">
<v-expansion-panels accordion multiple hover>
<v-expansion-panel
v-for="(system, index) in systemStatus.systems"
:key="index"
>
<v-expansion-panel-header expand-icon="mdi-chevron-right">
<div class="d-flex align-items-center">
<v-icon class="icon mr-3">
{{ getIconName(system.name) }}
</v-icon>
{{ system.name }}
<v-spacer />
<div class="mx-6">
<v-tooltip top>
<template v-slot:activator="{ on, attrs }">
<span v-bind="attrs" v-on="on">
<GitLabIcon
class="status-icon mx-4"
:status="getStatusMapping(system.status)"
/>
</span>
</template>
<span> Latest Pipeline: {{ system.status }} </span>
</v-tooltip>
</div>
</div>
</v-expansion-panel-header>
<v-expansion-panel-content>
<div class="d-flex flex-wrap">
<p v-if="system.description" class="system-description">
{{ system.description }}
</p>
<v-spacer />
<ExternalLink
v-if="system.link"
:href="system.link"
class="mb-4"
>
{{ system.name }}
</ExternalLink>
</div>
<p class="subhead">Current status</p>
<div class="px-4">
<p>
<strong class="text-capitalize" :class="system.status">
{{ system.status }}
</strong>
since
<v-tooltip right>
<template v-slot:activator="{ on, attrs }">
<span v-bind="attrs" v-on="on" class="text-no-wrap">
{{ getIsoDateString(system.lastChange) }}
</span>
</template>
<span>{{ getLocalDateString(system.lastChange) }}</span>
</v-tooltip>
</p>
<p v-if="system.statusDetails">
{{ system.statusDetails }}
</p>
</div>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
</v-skeleton-loader>
</div>
</div>
</v-container>
</template>
<script>
import StatusService from "@/api/services/status";
import ExternalLink from "@/components/ExternalLink";
import GitLabIcon from "@/components/icons/GitLabIcon";
import { SET_ERROR_MESSAGE } from "@/store/mutation-types";
import ErrorMessage from "@/components/APIErrorCard";
export default {
components: {
ExternalLink,
GitLabIcon,
ErrorMessage,
},
data: () => ({
loading: false,
systemStatus: [],
error: false,
}),
async mounted() {
try {
this.loading = true;
this.systemStatus = await StatusService.getStatusSummary();
console.log("this.systemStatus", this.systemStatus);
} catch (error) {
this.error = true;
this.$store.commit(SET_ERROR_MESSAGE, error);
console.error(error);
} finally {
// TODO: remove fake wait
setTimeout(() => {
this.loading = false;
}, 2000);
}
},
methods: {
getIconName(systemName) {
const iconName = systemName.toLowerCase()?.replace(" ", "-");
if (this.$vuetify.icons.values[iconName]) {
return `$vuetify.icons.${iconName}`;
}
return null;
},
getLocalDateString(millis) {
return new Date(millis).toLocaleString();
},
getIsoDateString(millis) {
return new Date(millis).toISOString();
},
// map our status up/down/degraded to gitlab icon parlance
getStatusMapping(status) {
if (status === "down") {
return "failed";
} else if (status === "up") {
return "passed";
} else {
return "warning";
}
},
},
};
</script>
<style lang="scss">
.status-summary {
.support-row {
.v-btn {
max-width: 160px;
width: 160px;
}
& > * {
align-items: center;
}
}
.system-description,
.text-container {
min-width: 200px;
}
.degraded {
color: #fc9403;
}
.down {
color: #ff3738;
}
.up {
color: #56f000;
}
}
</style>
......@@ -2,11 +2,20 @@ import NotAuthorizedComponent from "@/components/NotAuthorizedComponent";
import NotFoundComponent from "@/components/NotFoundComponent";
import Permission from "@/config/user-permissions";
const importHelper = (importFn) => {
return () => {
return importFn().catch((error) => {
console.error(error);
location.reload();
});
};
};
export default [
{
path: "/",
name: "Launchboard",
component: () => import("@/views/Home.vue"),
component: importHelper(() => import("@/views/Home.vue")),
meta: {
title: "Platform One: Launchboard",
bodyClass: "launchboard-page",
......@@ -16,8 +25,9 @@ export default [
path: "/projects",
name: "Projects",
// route level code-splitting
component: () =>
import(/* webpackChunkName: "projects" */ "@/views/Projects.vue"),
component: importHelper(() =>
import(/* webpackChunkName: "projects" */ "@/views/Projects.vue")
),
meta: {
title: "Platform One: My Projects",
bodyClass: "projects-page",
......@@ -27,7 +37,9 @@ export default [
path: "/team",
name: "Team",
// route level code-splitting
component: () => import(/* webpackChunkName: "team" */ "@/views/Team.vue"),
component: importHelper(() =>
import(/* webpackChunkName: "team" */ "@/views/Team.vue")
),
meta: {
title: "Platform One: My Team",
bodyClass: "team-page",
......@@ -37,8 +49,9 @@ export default [
path: "/resources",
name: "Resources",
// route level code-splitting
component: () =>
import(/* webpackChunkName: "resources" */ "@/views/Resources.vue"),
component: importHelper(() =>
import(/* webpackChunkName: "resources" */ "@/views/Resources.vue")
),
meta: {
title: "Platform One: Resources",
bodyClass: "resources-page",
......@@ -48,8 +61,9 @@ export default [
path: "/help",
name: "Help Center",
// route level code-splitting
component: () =>
import(/* webpackChunkName: "help-center" */ "@/views/HelpCenter.vue"),
component: importHelper(() =>
import(/* webpackChunkName: "help-center" */ "@/views/HelpCenter.vue")
),
meta: {
title: "Platform One: Help Center",
bodyClass: "help-page",
......@@ -59,8 +73,9 @@ export default [
path: "/settings",
name: "Settings",
// route level code-splitting
component: () =>
import(/* webpackChunkName: "settings" */ "@/views/Settings.vue"),
component: importHelper(() =>
import(/* webpackChunkName: "settings" */ "@/views/Settings.vue")
),
meta: {
title: "Platform One: Settings",
bodyClass: "settings-page",
......@@ -70,8 +85,9 @@ export default [
{
path: "/teams",
name: "Teams",
component: () =>
import(/* webpackChunkName: "teams" */ "@/views/super-admin/Teams.vue"),
component: importHelper(() =>
import(/* webpackChunkName: "teams" */ "@/views/super-admin/Teams.vue")
),
meta: {
title: "Platform One: Teams",
bodyClass: "teams-page",
......@@ -81,7 +97,9 @@ export default [
{
path: "/teams/details/:teamId",
name: "TeamDetails",
component: () => import(/* webpackChunkName: "team" */ "@/views/Team.vue"),
component: importHelper(() =>
import(/* webpackChunkName: "team" */ "@/views/Team.vue")
),
meta: {
title: "Platform One: Team Details",
bodyClass: "team-page",
......@@ -90,10 +108,11 @@ export default [
{
path: "/training",
name: "Courses",
component: () =>
component: importHelper(() =>
import(
/* webpackChunkName: "training" */ "@/views/super-admin/Training.vue"
),
)
),
meta: {
title: "Platform One: Training",
bodyClass: "training-page",
......@@ -103,10 +122,11 @@ export default [
{
path: "/training/details/:trainingId",
name: "TrainingDetails",
component: () =>
component: importHelper(() =>
import(
/* webpackChunkName: "training-details" */ "@/views/TrainingDetails.vue"
),
)
),
meta: {
title: "Platform One: Training Details",
bodyClass: "training-details-page",
......@@ -116,10 +136,11 @@ export default [
{
path: "/personnel",
name: "Personnel",
component: () =>
component: importHelper(() =>
import(
/* webpackChunkName: "personnel" */ "@/views/super-admin/Personnel.vue"
),
)
),
meta: {
title: "Platform One: Personnel",
bodyClass: "personnel",
......
Markdown is supported
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