Commit 2da3c32d authored by Cornee Traas's avatar Cornee Traas
Browse files

Make frontend use new platform aware backend apis.

parent ed01dcb9
......@@ -6,76 +6,104 @@
<template>
<div class="app">
<AppHeader fixed>
<SidebarToggler class="d-lg-none" display="md" mobile />
<SidebarToggler class="d-lg-none" display="md" mobile/>
<b-link class="nav-brand logo-link" to="/">
<!-- <h4>DASH-IT</h4> -->
<img src="../../public/img/logo.png" alt="Logo" class="logo-style">
</b-link>
<SidebarToggler class="d-md-down-none" display="lg" />
<b-form-select class="header-select" id="platform-select" v-model="selectedPlatform" @change="platformChange" :options="platformOptions"></b-form-select>
<b-form-select class="header-select" id="course-select" v-if="selectedPlatform !== 'platform-select'" @change="courseChange" v-model="selectedCourse" :options="courseOptions"></b-form-select>
<b-button id="filterButton" @click="showFilterModal=true"><i class="fa fa-filter mr-1" />Filters</b-button>
<b-button :disabled="level<2" id="actions-button" @click="showActionsModal=true"><i class="fa fa-exchange mr-1" />Changes</b-button>
<SidebarToggler class="d-md-down-none" display="lg"/>
<b-form-select
class="header-select"
id="platform-select"
v-model="selectedPlatform"
@change="platformChange"
:options="platformOptions"
></b-form-select>
<b-form-select
class="header-select"
id="course-select"
v-if="selectedPlatform !== 'platform-select'"
@change="courseChange"
v-model="selectedCourse"
:options="courseOptions"
></b-form-select>
<b-button id="filterButton" @click="showFilterModal=true">
<i class="fa fa-filter mr-1"/>Filters
</b-button>
<b-button :disabled="level<2" id="actions-button" @click="showActionsModal=true">
<i class="fa fa-exchange mr-1"/>Changes
</b-button>
<b-navbar-nav class="custom-nav ml-auto">
<DefaultHeaderDropdownAccnt />
<DefaultHeaderDropdownAccnt/>
<!-- <NotificationToggler :notificationCount=testCount class="d-none d-lg-block" /> -->
</b-navbar-nav>
<!--<AsideToggler class="d-lg-none" mobile />-->
</AppHeader>
<div class="app-body">
<AppSidebar fixed>
<SidebarHeader />
<SidebarForm />
<BackButton v-if="level > 0" :callback=goUp></BackButton>
<TopbarNav :navItems="top_nav[level]" :clickCallback=sideButtonClick></TopbarNav>
<SidebarHeader/>
<SidebarForm/>
<BackButton v-if="level > 0" :callback="goUp"></BackButton>
<TopbarNav :navItems="top_nav[level]" :clickCallback="sideButtonClick"></TopbarNav>
<BottombarNav class="bottom-nav" :navItems="bottom_nav"></BottombarNav>
<SidebarFooter />
<SidebarMinimizer />
<SidebarFooter/>
<SidebarMinimizer/>
</AppSidebar>
<main class="main">
<b-breadcrumb :items="list" />
<b-breadcrumb :items="list"/>
<div class="container-fluid">
<router-view></router-view>
</div>
</main>
</div>
<TheFooter v-bind:class="{ 'bg-success' : isPrimary, 'bg-danger' : !isPrimary }">
API Status: {{apiStatus}}
</TheFooter>
<TheFooter
v-bind:class="{ 'bg-success' : isPrimary, 'bg-danger' : !isPrimary }"
>API Status: {{apiStatus}}</TheFooter>
<!-- FILTER MODAL -->
<b-modal v-model="showFilterModal" id="filterModal" title="Filters" @ok="handleOk">
<h3 id="first-title">Timespan</h3>
<div class="timespan-area">
<b-alert v-if=showAlert variant="danger" show>{{ dangerMessage }}</b-alert>
<b-alert v-if="showAlert" variant="danger" show>{{ dangerMessage }}</b-alert>
<datepicker placeholder="Start date" v-model="fromDate"></datepicker>
<div id="spacer"></div>
<datepicker placeholder="End date" v-model="toDate"></datepicker>
<!-- <b-form-select disabled value-field="id" text-field="name" v-model="selectedCohort" :options=cohorts>
</b-form-select> -->
</b-form-select>-->
</div>
<!-- <h3>Geography filter</h3>
<multi-select optionsTitle="" selectedTitle="" v-model="selectedCountries" :options=countries tf="name" vf="id"></multi-select>
<h3>Payment status filter</h3>
<b-form-select v-model="selectedFilter" :options=filterOptions text-field="text" value-field="id">
</b-form-select> -->
</b-form-select>-->
<div slot="modal-footer" class="w-100">
<b-button @click="resetTimeFilter" class="float-left" id="filter-reset-button" variant="danger">Reset filters</b-button>
<b-btn @click="handleOk" class="float-right" id="filter-save-button" variant="primary">
Save
</b-btn>
<b-btn @click="showFilterModal=false" class="float-right" id="filter-cancel-button" variant="secondary">
Cancel
</b-btn>
<b-button
@click="resetTimeFilter"
class="float-left"
id="filter-reset-button"
variant="danger"
>Reset filters</b-button>
<b-btn @click="handleOk" class="float-right" id="filter-save-button" variant="primary">Save</b-btn>
<b-btn
@click="showFilterModal=false"
class="float-right"
id="filter-cancel-button"
variant="secondary"
>Cancel</b-btn>
</div>
</b-modal>
<!-- ACTIONS LIST MODAL -->
<b-modal v-model="showActionsModal" id="actionsModal" @ok="handleOk">
<div slot="modal-title" class="w-100">
Changes
<b-btn @click="showActionsModal=false; showAddActionModal=true" size="sm" class="float-right ml-3" id="add-action-button" variant="secondary">
<i class="fa fa-plus mr-1" />Add change
<div slot="modal-title" class="w-100">Changes
<b-btn
@click="showActionsModal=false; showAddActionModal=true"
size="sm"
class="float-right ml-3"
id="add-action-button"
variant="secondary"
>
<i class="fa fa-plus mr-1"/>Add change
</b-btn>
</div>
......@@ -83,7 +111,13 @@
<b-list-group>
<b-list-group-item v-for="action of actions" :key="action.name">
{{ action.title }}
<b-button id="action-delete-button" size="sm" @click="actionToDelete = action; showDeleteActionModal=true"><i class="fa fa-trash" /></b-button>
<b-button
id="action-delete-button"
size="sm"
@click="actionToDelete = action; showDeleteActionModal=true"
>
<i class="fa fa-trash"/>
</b-button>
<span class="action-date mr-2">{{ action.date }}</span>
</b-list-group-item>
</b-list-group>
......@@ -93,47 +127,75 @@
<p class="loading-text">{{ loadingActionsText }}</p>
</div>
<div slot="modal-footer" class="w-100">
<b-btn @click="showActionsModal=false" class="float-right" id="filter-cancel-button" variant="secondary">
Back
</b-btn>
<b-btn
@click="showActionsModal=false"
class="float-right"
id="filter-cancel-button"
variant="secondary"
>Back</b-btn>
</div>
</b-modal>
<!-- ADD ACTION MODAL -->
<b-modal v-model="showAddActionModal" id="addActionModal" @ok="handleOk">
<div slot="modal-title" class="w-100">
Add change
</div>
<div slot="modal-title" class="w-100">Add change</div>
<b-form>
<b-form-input id="action-title" class="mb-2" type="text" v-model="action.title" required placeholder="Title" />
<b-form-textarea id="action-description" :rows="3" :max-rows="3" no-resize class="mb-2" type="text" v-model="action.description" required placeholder="Description" />
<b-form-input
id="action-title"
class="mb-2"
type="text"
v-model="action.title"
required
placeholder="Title"
/>
<b-form-textarea
id="action-description"
:rows="3"
:max-rows="3"
no-resize
class="mb-2"
type="text"
v-model="action.description"
required
placeholder="Description"
/>
<datepicker placeholder="Date" v-model="action.date"></datepicker>
</b-form>
<b-alert v-if=showAddActionAlert class="mt-2" variant="danger" show>{{ addActionWarning }}</b-alert>
<b-alert v-if="showAddActionAlert" class="mt-2" variant="danger" show>{{ addActionWarning }}</b-alert>
<div slot="modal-footer" class="w-100">
<b-btn @click="saveAction" class="float-right" id="action-save-button" variant="primary">
Save
</b-btn>
<b-btn @click="showAddActionModal=false" class="float-right" id="filter-cancel-button" variant="secondary">
Cancel
</b-btn>
<b-btn
@click="saveAction"
class="float-right"
id="action-save-button"
variant="primary"
>Save</b-btn>
<b-btn
@click="showAddActionModal=false"
class="float-right"
id="filter-cancel-button"
variant="secondary"
>Cancel</b-btn>
</div>
</b-modal>
<!-- DELETE ACITON MODAL -->
<b-modal v-model="showDeleteActionModal" id="deleteActionModal" @ok="handleOk">
<div slot="modal-title" class="w-100">
Delete change
</div>
Are you sure you want to delete <b>{{actionToDelete.title }}</b>?
<div slot="modal-title" class="w-100">Delete change</div>Are you sure you want to delete
<b>{{actionToDelete.title }}</b>?
<div slot="modal-footer" class="w-100">
<b-btn @click="deleteAction" class="float-right" id="delete-save-button" variant="danger">
Yes
</b-btn>
<b-btn @click="showDeleteActionModal=false" class="float-right mr-3" id="delete-cancel-button" variant="secondary">
No
</b-btn>
<b-btn
@click="deleteAction"
class="float-right"
id="delete-save-button"
variant="danger"
>Yes</b-btn>
<b-btn
@click="showDeleteActionModal=false"
class="float-right mr-3"
id="delete-cancel-button"
variant="secondary"
>No</b-btn>
</div>
</b-modal>
</div>
......@@ -164,6 +226,7 @@ import BackButton from "../views/sidebars/sidebaritems/BackButton";
import MultiSelect from "@/components/MultiSelect";
import Datepicker from "vuejs-datepicker";
import { EventBus } from "@/event-bus";
import store from "@/store";
import util from "@/util";
......@@ -196,7 +259,7 @@ export default {
level: 0,
top_nav: [],
level_0: ["/home", "/settings", "/contact",],
platforms: settings.platforms,
platforms: store.state.platforms,
bottom_nav: nav.bottom_items,
testCount: 5,
platformOptions: [],
......
export default {
//Platform settings
platforms: [{
name: "Coursera",
slug: "coursera",
},],
course_pages: [{
name: "Videos",
slug: "videos",
icon: "fa fa-video-camera",
},
{
name: "Quizzes",
slug: "quizzes",
icon: "fa fa-check",
},
{
name: "Assignments",
slug: "assignments",
icon: "cui-calendar",
},
],
resources: [
name: "Videos",
slug: "videos",
icon: "fa fa-video-camera",
},
{
name: "Quizzes",
slug: "quizzes",
icon: "fa fa-check",
},
{
name: "Coursera for Educators",
link: "https://blog.coursera.org/for-educators/",
name: "Assignments",
slug: "assignments",
icon: "cui-calendar",
},
],
resources: [{
name: "Coursera for Educators",
link: "https://blog.coursera.org/for-educators/",
}, ],
// Constants
quiz_id: 5,
......@@ -42,5 +35,5 @@ export default {
}, {
name: "Admin",
id: "admin",
},],
}, ],
}
......@@ -2,6 +2,7 @@ import Vue from 'vue'
import Vuex from 'vuex'
import VuexPersistence from 'vuex-persist'
import axios from 'axios'
import util from "@/util"
Vue.use(Vuex)
......@@ -13,6 +14,7 @@ export default new Vuex.Store({
state: {
apiToken: "",
apiExpire: Date.now(),
platforms: [],
courses: [],
resetToken: "",
resetId: "",
......@@ -37,6 +39,9 @@ export default new Vuex.Store({
setExpire(state, expire) {
state.apiExpire = expire;
},
setPlatforms(state, platforms) {
state.platforms = platforms;
},
setCourses(state, courses) {
state.courses = courses
},
......@@ -69,18 +74,23 @@ export default new Vuex.Store({
}) {
return new Promise((resolve, reject) => {
axios.post(
process.env.VUE_APP_ROOT_API + "/o/token/",
new URLSearchParams({
client_id: process.env.VUE_APP_CLIENT_ID,
grant_type: "password",
username: username,
password: password,
}),
)
process.env.VUE_APP_ROOT_API + "/o/token/",
new URLSearchParams({
client_id: process.env.VUE_APP_CLIENT_ID,
grant_type: "password",
username: username,
password: password,
}),
)
.then(response => {
context.commit('setToken', response.data.access_token);
context.commit('setExpire', Date.now() + (response.data.expires_in * 1000));
resolve();
util.getPlatforms().then(response => {
context.commit('setPlatforms', response.data)
resolve();
}).catch(response => {
reject(response)
})
})
.catch(function (response) {
reject(response);
......@@ -100,5 +110,5 @@ export default new Vuex.Store({
context.commit('setExpire', 0);
},
},
plugins: [vuexLocal.plugin,],
plugins: [vuexLocal.plugin, ],
})
......@@ -11,7 +11,7 @@ export default {
for (let item of items) {
if (item.lesson !== lastLesson) {
lastLesson = item.lesson;
lessons.push([item]);
lessons.push([item, ]);
} else {
lessons[lessons.length - 1].push(item)
}
......@@ -47,6 +47,10 @@ export default {
return process.env.VUE_APP_ROOT_API;
},
platformUrl(platform = store.state.selectedPlatform) {
return this.apiUrl() + "/p/" + platform;
},
/**
* Sets the headers to include the API token
* which is used for API call authentication.
......@@ -105,6 +109,12 @@ export default {
})
},
getPlatforms() {
return axios.get(this.apiUrl() + "/p/", {
headers: this.authHeader(),
})
},
// User API calls
getUsers() {
return axios
......@@ -158,23 +168,23 @@ export default {
},
// Course API calls
getCourses() {
getCourses(platform) {
return axios
.get(this.apiUrl() + "/courses/", {
.get(this.platformUrl(platform) + "/courses/", {
headers: this.authHeader(),
})
},
getCoursesData(filters) {
getCoursesData(platform, filters) {
return axios
.get(this.apiUrl() + `/api/course-analytics/` + this.getQueryParams(filters), {
.get(this.platformUrl(platform) + `/course-analytics/` + this.getQueryParams(filters), {
headers: this.authHeader(),
})
},
getDetailedCourseData(courseId, filters) {
return axios
.get(this.apiUrl() + `/api/course-analytics/${courseId}/` + this.getQueryParams(filters), {
.get(this.platformUrl() + `/course-analytics/${courseId}/` + this.getQueryParams(filters), {
headers: this.authHeader(),
})
},
......@@ -182,14 +192,14 @@ export default {
// Video API calls
getVideos(courseId, filters) {
return axios
.get(this.apiUrl() + `/api/video-analytics/${courseId}/` + this.getQueryParams(filters), {
.get(this.platformUrl() + `/video-analytics/${courseId}/` + this.getQueryParams(filters), {
headers: this.authHeader(),
})
},
getVideoDetails(courseId, videoId, filters) {
return axios
.get(this.apiUrl() + `/api/video-analytics/${courseId}/${videoId}/` + this.getQueryParams(filters), {
.get(this.platformUrl() + `/video-analytics/${courseId}/${videoId}/` + this.getQueryParams(filters), {
headers: this.authHeader(),
})
},
......@@ -197,21 +207,21 @@ export default {
// Quiz API calls
getQuizzes(courseId, filters) {
return axios
.get(this.apiUrl() + `/api/quiz-analytics/${courseId}/` + this.getQueryParams(filters), {
.get(this.platformUrl() + `/quiz-analytics/${courseId}/` + this.getQueryParams(filters), {
headers: this.authHeader(),
})
},
getQuizVersions(courseId, baseId) {
return axios
.get(this.apiUrl() + `/api/quiz-analytics/${courseId}/${baseId}/`, {
.get(this.platformUrl() + `/quiz-analytics/${courseId}/${baseId}/`, {
headers: this.authHeader(),
})
},
getQuizDetails(courseId, baseId, version, filters) {
return axios
.get(this.apiUrl() + `/api/quiz-analytics/${courseId}/${baseId}/${version}/` + this.getQueryParams(filters), {
.get(this.platformUrl() + `/quiz-analytics/${courseId}/${baseId}/${version}/` + this.getQueryParams(filters), {
headers: this.authHeader(),
})
},
......@@ -219,14 +229,14 @@ export default {
// Assignment API calls
getAssignments(courseId, filters) {
return axios
.get(this.apiUrl() + `/api/assignment-analytics/${courseId}/` + this.getQueryParams(filters), {
.get(this.platformUrl() + `/assignment-analytics/${courseId}/` + this.getQueryParams(filters), {
headers: this.authHeader(),
})
},
getAssignmentDetails(courseId, itemId, filters) {
return axios
.get(this.apiUrl() + `/api/assignment-analytics/${courseId}/${itemId}/` + this.getQueryParams(filters), {
.get(this.platformUrl() + `/assignment-analytics/${courseId}/${itemId}/` + this.getQueryParams(filters), {
headers: this.authHeader(),
})
},
......@@ -234,21 +244,21 @@ export default {
// Actions API calls
getActions(platformId, courseId, filters) {
return axios
.get(this.apiUrl() + `/actions/${platformId}/${courseId}/` + this.getQueryParams(filters), {
.get(this.platformUrl() + `/actions/${courseId}/` + this.getQueryParams(filters), {
headers: this.authHeader(),
})
},
saveAction(action) {
return axios
.post(this.apiUrl() + `/actions/`, action, {
.post(this.platformUrl() + `/actions/`, action, {
headers: this.authHeader(),
})
},
deleteAction(pk) {
return axios
.delete(this.apiUrl() + `/actions/${pk}` + '/', {
.delete(this.platformUrl() + `/actions/${pk}` + '/', {
headers: this.authHeader(),
})
},
......
......@@ -15,7 +15,12 @@
</b-card-header>
<b-card-body class="pb-0">
<b-row>
<b-col v-for="statistic in statisticsCoursera" :key="statistic.name" :lg="6" :xl="4">
<b-col
v-for="statistic in statisticsCoursera[platform.id]"
:key="statistic.name"
:lg="6"
:xl="4"
>
<!-- statistics list -->
<b-card :no-body="true">
<b-card-body class="p-0 clearfix stats-card">
......@@ -43,7 +48,13 @@
</b-row>
<div class="divider"></div>
<b-row>
<b-col v-for="course in coursesCoursera" :key="course.name" :md="12" :lg="6" :xl="6">
<b-col
v-for="course in coursesCoursera[platform.id]"
:key="course.name"
:md="12"
:lg="6"
:xl="6"
>
<!-- list of courses + their statistics -->
<b-card no-body>
<b-card-header class="bg-primary">
......@@ -120,77 +131,82 @@ export default {
data: function() {
return {
// Settings
platforms: settings.platforms,
platforms: this.$store.state.platforms,
resources: settings.resources,
// Statistics variables.
courses: 0,
learners: 0,
completers: 0,
paidUsers: 0,
// General variables
coursesCoursera: []
coursesCoursera: {},
statisticsCoursera: {},
};
},
computed: {
// Get total numbers computed from single items.
statisticsCoursera() {
computed: {},
methods: {
arrayWeightedAverage: util.arrayWeightedAverage,
arrayColumn: util.arrayColumn,
getStatisticsCoursera: function(p) {
return {
// courses: {
// text: "courses",
// value: this.coursesCoursera.length,
// value: this.coursesCoursera[p].length,
// icon: "fa fa-book"
// },
learners: {
text: "learners",
value: this.coursesCoursera.reduce(
value: this.coursesCoursera[p].reduce(
(a, b) => a + b.enrolled_learners,
0
),
icon: "fa fa-user"
icon: "fa fa-user",
},
completers: {
text: "completers",
value: this.coursesCoursera.reduce(
value: this.coursesCoursera[p].reduce(
(a, b) => a + b.finished_learners,
0
),
icon: "fa fa-check"
icon: "fa fa-check",
},
activeLearners: {
text: "active leaners",
value:
this.coursesCoursera.reduce((a, b) => a + b.enrolled_learners, 0) -
this.coursesCoursera.reduce((a, b) => a + b.leaving_learners, 0),
icon: "fa fa-check"
}