Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/app/doubtfire-angular.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {MatSnackBarModule} from '@angular/material/snack-bar';
import {MatPaginatorModule} from '@angular/material/paginator';
import {MatTooltipModule} from '@angular/material/tooltip';
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
import {MatChipListbox, MatChipsModule} from '@angular/material/chips';
import {MatChipsModule} from '@angular/material/chips';
import {MatGridListModule} from '@angular/material/grid-list';
import {PdfViewerModule} from 'ng2-pdf-viewer';
import {UIRouterUpgradeModule} from '@uirouter/angular-hybrid';
Expand Down Expand Up @@ -213,6 +213,8 @@ import {TaskAssessmentCardComponent} from './projects/states/dashboard/directive
import {TaskSubmissionCardComponent} from './projects/states/dashboard/directives/task-dashboard/directives/task-submission-card/task-submission-card.component';
import {TaskDashboardComponent} from './projects/states/dashboard/directives/task-dashboard/task-dashboard.component';
import {InboxComponent} from './units/states/tasks/inbox/inbox.component';
import {PortfoliosComponent} from './units/states/portfolios/portfolios.component';
import {AjsProjectProgressDashboardComponent} from './units/states/portfolios/ajs-project-progress-dashboard.component';
import {ProjectProgressBarComponent} from './common/project-progress-bar/project-progress-bar.component';
import {TeachingPeriodListComponent} from './admin/states/teaching-periods/teaching-period-list/teaching-period-list.component';
import {FChipComponent} from './common/f-chip/f-chip.component';
Expand Down Expand Up @@ -454,6 +456,8 @@ const GANTT_CHART_CONFIG = {
TaskSubmissionCardComponent,
TaskDashboardComponent,
InboxComponent,
PortfoliosComponent,
AjsProjectProgressDashboardComponent,
ProjectProgressBarComponent,
TeachingPeriodListComponent,
CreateNewUnitModal,
Expand Down Expand Up @@ -683,7 +687,6 @@ const GANTT_CHART_CONFIG = {
NgxGanttModule,
MatSidenavModule,
MonacoEditorModule.forRoot(),
MatChipListbox,
],
})

Expand Down
2 changes: 2 additions & 0 deletions src/app/doubtfire-angularjs.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ import {FooterComponent} from './common/footer/footer.component';
import {TaskAssessmentCardComponent} from './projects/states/dashboard/directives/task-dashboard/directives/task-assessment-card/task-assessment-card.component';
import {TaskSubmissionCardComponent} from './projects/states/dashboard/directives/task-dashboard/directives/task-submission-card/task-submission-card.component';
import {InboxComponent} from './units/states/tasks/inbox/inbox.component';
import {PortfoliosComponent} from './units/states/portfolios/portfolios.component';
import {TaskDefinitionEditorComponent} from './units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-editor.component';
import {UnitAnalyticsComponent} from './units/states/analytics/unit-analytics-route.component';
import {UnitTaskEditorComponent} from './units/states/edit/directives/unit-tasks-editor/unit-task-editor.component';
Expand Down Expand Up @@ -417,6 +418,7 @@ DoubtfireAngularJSModule.directive(
downgradeComponent({component: TasksViewerComponent}),
);
DoubtfireAngularJSModule.directive('fInbox', downgradeComponent({component: InboxComponent}));
DoubtfireAngularJSModule.directive('fPortfolios', downgradeComponent({component: PortfoliosComponent}));
DoubtfireAngularJSModule.directive(
'fTaskDueCard',
downgradeComponent({component: TaskDueCardComponent}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,31 @@ angular.module('doubtfire.projects.project-progress-dashboard',[])
.directive('projectProgressDashboard', ->
restrict: 'E'
templateUrl: 'projects/project-progress-dashboard/project-progress-dashboard.tpl.html'
# Explicit bindings for use inside upgraded Angular hosts (f-portfolios). Link copies from
# $parent when attributes are omitted so legacy templates without project="..." still work.
scope:
project: '<'
unit: '<'
controller: ($scope, $state, $rootScope, $stateParams, newProjectService, alertService, gradeService, newTaskService, listenerService) ->
if $stateParams.projectId?
$scope.studentProjectId = $stateParams.projectId
else if $scope.project?
$scope.studentProjectId = $scope.project.id

$scope.grades = gradeService.grades

$scope.currentVisualisation = 'burndown'

$scope.taskStats = {}

updateTaskCompletionStats = ->
return unless $scope.project?
$scope.taskStats.numberOfTasksCompleted = $scope.project.tasksByStatus(newTaskService.completeStatus).length
$scope.taskStats.numberOfTasksRemaining = $scope.project.activeTasks().length - $scope.taskStats.numberOfTasksCompleted

syncStudentProjectId = ->
if $stateParams.projectId?
$scope.studentProjectId = $stateParams.projectId
else if $scope.project?
$scope.studentProjectId = $scope.project.id

$scope.chooseGrade = (idx) ->
return unless $scope.project?
$scope.project.targetGrade = idx
newProjectService.update($scope.project).subscribe(
(response) ->
Expand All @@ -29,18 +43,19 @@ angular.module('doubtfire.projects.project-progress-dashboard',[])
updateTaskCompletionStats()

$scope.taskCount = ->
$scope.unit.taskDefinitionCount

$scope.taskStats = {}

# Update move to task and project...
updateTaskCompletionStats = ->
$scope.taskStats.numberOfTasksCompleted = $scope.project.tasksByStatus(newTaskService.completeStatus).length
$scope.taskStats.numberOfTasksRemaining = $scope.project.activeTasks().length - $scope.taskStats.numberOfTasksCompleted
$scope.unit?.taskDefinitionCount

$scope.$on 'TaskStatusUpdated', ->
updateTaskCompletionStats()

$scope.$watch 'project', (project) ->
return unless project?
syncStudentProjectId()
updateTaskCompletionStats()

updateTaskCompletionStats()
link: (scope, _el, _attrs) ->
unless scope.project?
scope.project = scope.$parent.project if scope.$parent?
unless scope.unit?
scope.unit = scope.$parent.unit if scope.$parent?
)
31 changes: 18 additions & 13 deletions src/app/projects/states/index/global-state.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {Inject, Injectable, OnDestroy} from '@angular/core';
import {MediaObserver} from 'ng-flex-layout';
import {UIRouter} from '@uirouter/angular';
import {EntityCache} from 'ngx-entity-service';
import {BehaviorSubject, Observable, Subject, skip, take} from 'rxjs';
import {BehaviorSubject, Observable, Subject, catchError, of, skip, take} from 'rxjs';
import {
CampusService,
LearningOutcomeService,
Expand Down Expand Up @@ -122,9 +122,7 @@ export class GlobalStateService implements OnDestroy {
setTimeout(() => {
// Try to login using the refresh token
this.authenticationService.attemptLoginUsingRefreshToken((result: boolean) => {
if (result) {
this.loadGlobals();
} else {
if (!result) {
// Loading is finshed...
this.isLoadingSubject.next(false);

Expand All @@ -133,6 +131,7 @@ export class GlobalStateService implements OnDestroy {
this.router.stateService.go('sign_in');
}
}
// On success, loadGlobals() runs from AuthenticationService.setupUserFromResponse
});
}, 100);

Expand Down Expand Up @@ -238,29 +237,35 @@ export class GlobalStateService implements OnDestroy {
});

if (this.userService.currentUser.isStaff) {
// Global GLO / feedback-chip endpoints are optional: many API builds do not mount
// GET /api/global/outcomes or /api/global/feedback_chips yet. Failing here should not
// block login or spam error toasts; task-level data still loads from unit routes.
this.learningOutcomeService
.query({}, {endpointFormat: LearningOutcomeService.globalEndpoint})
.pipe(
catchError((err: unknown) => {
console.warn('Global learning outcomes (GLO) bootstrap skipped:', err);
return of([]);
}),
)
.subscribe({
next: (_response) => {
subscriber.next(true);
},
error: (_response) => {
this.alerts.error('Unable to access service. Failed loading GLOs.', 6000);
},
});

this.feedbackTemplateService
.query({}, {endpointFormat: FeedbackTemplateService.globalEndpoint})
.pipe(
catchError((err: unknown) => {
console.warn('Global GLO feedback templates bootstrap skipped:', err);
return of([]);
}),
)
.subscribe({
next: (_response) => {
subscriber.next(true);
},
error: (_response) => {
this.alerts.error(
'Unable to access service. Failed loading GLO feedback templates.',
6000,
);
},
});
}

Expand Down
3 changes: 1 addition & 2 deletions src/app/sessions/states/sign-in/sign-in.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ export class SignInComponent implements OnInit {
* Perform the actions needed when the user successfully signs in.
*/
private actionSignInSuccess(): void {
this.globalState.loadGlobals();
// loadGlobals() already runs inside AuthenticationService.setupUserFromResponse after sign-in
this.state.go('welcome');
}

Expand Down Expand Up @@ -246,7 +246,6 @@ export class SignInComponent implements OnInit {
this.authService.signIn(signInCredentials).subscribe({
next: () => {
if (this.isLtiLogin) {
this.globalState.loadGlobals();
const params = getUrlParams(document.location.href);
this.state.go('lti', {
ltik: params.ltik,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Component, ElementRef, Inject, Input, Injector, OnInit, Optional } from '@angular/core';
import { UpgradeComponent } from '@angular/upgrade/static';
import { Project } from 'src/app/api/models/project';
import { Unit } from 'src/app/api/models/unit';
import { visualisations } from 'src/app/ajs-upgraded-providers';

/**
* Hosts the AngularJS `projectProgressDashboard` directive inside Angular templates
* (same charts and target-grade UI as the legacy portfolios “View Progress” tab).
*/
@Component({
selector: 'f-ajs-project-progress-dashboard',
template: '',
})
export class AjsProjectProgressDashboardComponent extends UpgradeComponent implements OnInit {
@Input() project: Project;
@Input() unit: Unit;

constructor(
elementRef: ElementRef,
injector: Injector,
@Optional() @Inject(visualisations) private readonly visualisationApi: { refreshAll?: () => void } | null,
) {
super('projectProgressDashboard', elementRef, injector);
}

override ngOnInit(): void {
super.ngOnInit();
// Burndown / pie use legacy visualisation lifecycle; match UnitPortfoliosStateCtrl.refreshCharts.
setTimeout(() => this.visualisationApi?.refreshAll?.(), 0);
}
}
145 changes: 0 additions & 145 deletions src/app/units/states/portfolios/portfolios.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -7,154 +7,9 @@ angular.module('doubtfire.units.states.portfolios', [])
parent: 'units/index'
url: '/students/portfolios'
templateUrl: "units/states/portfolios/portfolios.tpl.html"
controller: "UnitPortfoliosStateCtrl"
data:
task: "Student Portfolios"
pageTitle: "_Home_"
roleWhitelist: ['Tutor', 'Convenor', 'Admin', 'Auditor']
}
)
.controller("UnitPortfoliosStateCtrl", ($scope, alertService, analyticsService, gradeService, newProjectService, Visualisation, newTaskService, fileDownloaderService, newUserService, D2lTransferModal, newUnitService, sidekiqProgressModalService) ->
# TODO: (@alexcu) Break this down into smaller directives/substates

$scope.unit.loadD2lMapping().subscribe()

$scope.downloadGrades = -> fileDownloaderService.downloadFile($scope.unit.gradesUrl, "#{$scope.unit.code}-grades.csv")

$scope.downloadPortfolios = ->
newUnitService.zipPortfolios($scope.unit).subscribe({
next: (newJob) ->
sidekiqProgressModalService.show("Downloading Portfolios: " + $scope.unit.code, newJob.id).subscribe({
next: (job) ->
fileDownloaderService.downloadFile($scope.unit.portfoliosUrl, "#{$scope.unit.code}-portfolios.zip")
error: (message) -> alertService.error(message, 6000)
})
error: (message) -> alertService.error(message, 6000)
})


$scope.studentFilter = 'allStudents'
$scope.portfolioFilter = 'withPortfolio'

$scope.statusClass = newTaskService.statusClass
$scope.statusText = newTaskService.statusText

refreshCharts = Visualisation.refreshAll

#
# Sets the active tab
#
$scope.setActiveTab = (tab) ->
# Do nothing if we're switching to the same tab
return if tab is $scope.activeTab
$scope.activeTab?.active = false
$scope.activeTab = tab
$scope.activeTab.active = true

if $scope.activeTab == $scope.tabs.viewProgress
refreshCharts()

#
# Active task tab group
#
$scope.tabs =
selectStudent:
title: "Select Student"
subtitle: "Select the student to assess"
seq: 0
viewProgress:
title: "View Progress"
subtitle: "See the progress of the student"
seq: 1
viewStaffNotes:
title: "View Staff Notes"
subtitle: "See notes of the student add by staff"
seq: 2
viewPortfolio:
title: "View Portfolio"
subtitle: "See the portfolio of the student"
seq: 3
assessPortfolio:
title: "Assess Portfolio"
subtitle: "Enter a grade for the student"
seq: 4

$scope.setActiveTab($scope.tabs.selectStudent)

$scope.tutor = newUserService.currentUser

$scope.search = ""

# Pagination details
$scope.currentPage = 1
$scope.maxSize = 5
$scope.pageSize = 10

$scope.filterOptions = {selectedGrade: -1}
$scope.gradeValues = gradeService.gradeValues
$scope.grades = gradeService.grades
$scope.gradeAcronyms = gradeService.gradeAcronyms

$scope.selectedStudent = null

$scope.gradeResults = [
{
name: 'Fail',
scores: [ 0, 10, 20, 30, 40, 44 ]
}
{
name: 'Pass',
scores: [ 50, 53, 55, 57 ]
}
{
name: 'Credit',
scores: [ 60, 63, 65, 67 ]
}
{
name: 'Distinction',
scores: [ 70, 73, 75, 77 ]
}
{
name: 'High Distinction',
scores: [ 80, 83, 85, 87 ]
}
{
name: 'High Distinction',
scores: [ 90, 93, 95, 97, 100 ]
}
]

$scope.editingRationale = false

$scope.toggleEditRationale = ->
$scope.editingRationale = !$scope.editingRationale


analyticsService.watchEvent $scope, 'studentFilter', 'Teacher View - Grading Tab'
analyticsService.watchEvent $scope, 'sortOrder', 'Teacher View - Grading Tab'
analyticsService.watchEvent $scope, 'currentPage', 'Teacher View - Grading Tab', 'Selected Page'

$scope.selectStudent = (student) ->
$scope.selectedStudent = student
$scope.project = null
newProjectService.loadProject(student, $scope.unit).subscribe({
next: (project) ->
$scope.project = project
$scope.project.preloadedUrl = $scope.project.portfolioUrl()
error: (message) -> alertService.error( message, 6000)
})

$scope.hasD2lMapping = ->
$scope.unit.hasD2lMapping()

$scope.transferToD2L = ->
D2lTransferModal.open($scope.unit)

$scope.openProject = ($event, project) ->
$event.stopPropagation()
# HACK: avoids using window.open() to prevent AngularJS error
link = document.createElement('a')
link.href = "/projects/#{project.id}/dashboard/?tutor=true"
link.target = '_blank'
link.click()
)
Loading