Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development: Improve performance of programming exercise details view #9785

Open
wants to merge 19 commits into
base: develop
Choose a base branch
from
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@if (headlines?.length && headlines.length > 1) {
<jhi-detail-overview-navigation-bar [sectionHeadlines]="headlines" />
}
@for (section of sections; track section) {
@for (section of sections(); track section) {
<h3 class="section-headline" [id]="headlinesRecord[section.headline]">{{ section.headline | artemisTranslate }}</h3>
<dl class="section-detail-list">
@for (detail of section.details; track $index) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Component, OnDestroy, OnInit, ViewEncapsulation, inject, input } from '@angular/core';
import { isEmpty } from 'lodash-es';
import { FeatureToggle } from 'app/shared/feature-toggle/feature-toggle.service';
import { ButtonSize, TooltipPlacement } from 'app/shared/components/button.component';
import { ButtonSize } from 'app/shared/components/button.component';
import { IrisSubSettingsType } from 'app/entities/iris/settings/iris-sub-settings.model';
import { ModelingExerciseService } from 'app/exercises/modeling/manage/modeling-exercise.service';
import { AlertService } from 'app/core/util/alert.service';
Expand Down Expand Up @@ -50,11 +50,13 @@ export class DetailOverviewListComponent implements OnInit, OnDestroy {
protected readonly FeatureToggle = FeatureToggle;
protected readonly ButtonSize = ButtonSize;
protected readonly ProgrammingExerciseParticipationType = ProgrammingExerciseParticipationType;
protected readonly CHAT = IrisSubSettingsType.CHAT;

readonly CHAT = IrisSubSettingsType.CHAT;
private readonly modelingExerciseService = inject(ModelingExerciseService);
private readonly alertService = inject(AlertService);
private readonly profileService = inject(ProfileService);

@Input()
sections: DetailOverviewSection[];
sections = input.required<DetailOverviewSection[]>();

// headline list for navigation bar
headlines: { id: string; translationKey: string }[];
Expand All @@ -64,14 +66,8 @@ export class DetailOverviewListComponent implements OnInit, OnDestroy {
profileSubscription: Subscription;
isLocalVC = false;

constructor(
private modelingExerciseService: ModelingExerciseService,
private alertService: AlertService,
private profileService: ProfileService,
) {}

ngOnInit() {
this.headlines = this.sections.map((section) => {
this.headlines = this.sections().map((section) => {
return {
id: section.headline.replaceAll('.', '-'),
translationKey: section.headline,
Expand All @@ -98,6 +94,4 @@ export class DetailOverviewListComponent implements OnInit, OnDestroy {
ngOnDestroy() {
this.profileSubscription?.unsubscribe();
}

protected readonly TooltipPlacement = TooltipPlacement;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { SafeHtml } from '@angular/platform-browser';
import { ProgrammingExerciseBuildConfig } from 'app/entities/programming/programming-exercise-build.config';
import { Subject, Subscription } from 'rxjs';
import { Subject, Subscription, of } from 'rxjs';
import { ProgrammingExercise, ProgrammingLanguage } from 'app/entities/programming/programming-exercise.model';
import { ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service';
import { AlertService, AlertType } from 'app/core/util/alert.service';
Expand Down Expand Up @@ -57,6 +57,9 @@ import { IrisSubSettingsType } from 'app/entities/iris/settings/iris-sub-setting
import { Detail } from 'app/detail-overview-list/detail.model';
import { Competency } from 'app/entities/competency.model';
import { AeolusService } from 'app/exercises/programming/shared/service/aeolus.service';
import { switchMap, tap } from 'rxjs/operators';
import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model';
import { BuildLogStatisticsDTO } from 'app/entities/programming/build-log-statistics-dto';

@Component({
selector: 'jhi-programming-exercise-detail',
Expand Down Expand Up @@ -184,13 +187,15 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy {

this.templateAndSolutionParticipationSubscription = this.programmingExerciseService
.findWithTemplateAndSolutionParticipationAndLatestResults(programmingExercise.id!)
.subscribe((updatedProgrammingExercise) => {
this.programmingExercise = updatedProgrammingExercise.body!;

this.setLatestCoveredLineRatio();
this.loadingTemplateParticipationResults = false;
this.loadingSolutionParticipationResults = false;
this.profileInfoSubscription = this.profileService.getProfileInfo().subscribe(async (profileInfo) => {
.pipe(
tap((updatedProgrammingExercise) => {
this.programmingExercise = updatedProgrammingExercise.body!;
this.setLatestCoveredLineRatio();
this.loadingTemplateParticipationResults = false;
this.loadingSolutionParticipationResults = false;
}),
switchMap(() => this.profileService.getProfileInfo()),
tap(async (profileInfo) => {
if (profileInfo) {
if (this.programmingExercise.projectKey && this.programmingExercise.templateParticipation?.buildPlanId) {
this.programmingExercise.templateParticipation.buildPlanUrl = createBuildPlanUrl(
Expand All @@ -215,38 +220,39 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy {
if (this.irisEnabled) {
this.irisSettingsSubscription = this.irisSettingsService.getCombinedCourseSettings(this.courseId).subscribe((settings) => {
this.irisChatEnabled = settings?.irisChatSettings?.enabled ?? false;
this.exerciseDetailSections = this.getExerciseDetails();
});
}
}
}),
switchMap(() => this.programmingExerciseSubmissionPolicyService.getSubmissionPolicyOfProgrammingExercise(exerciseId!)),
tap((submissionPolicy) => {
this.programmingExercise.submissionPolicy = submissionPolicy;
}),
switchMap(() => this.programmingExerciseService.getDiffReport(this.programmingExercise.id!)),
tap((gitDiffReport) => {
this.processGitDiffReport(gitDiffReport);
}),
switchMap(() =>
this.programmingExercise.isAtLeastEditor ? this.programmingExerciseService.getBuildLogStatistics(exerciseId!) : of([] as BuildLogStatisticsDTO),
),
tap((buildLogStatistics) => {
if (this.programmingExercise.isAtLeastEditor) {
this.programmingExercise.buildLogStatistics = buildLogStatistics;
}
}),
)
.subscribe({
next: () => {
this.setLatestCoveredLineRatio();
this.checkAndAlertInconsistencies();
this.plagiarismCheckSupported = this.programmingLanguageFeatureService.getProgrammingLanguageFeature(
programmingExercise.programmingLanguage,
).plagiarismCheckSupported;
this.exerciseDetailSections = this.getExerciseDetails();
});

this.submissionPolicySubscription = this.programmingExerciseSubmissionPolicyService
.getSubmissionPolicyOfProgrammingExercise(exerciseId!)
.subscribe((submissionPolicy) => {
this.programmingExercise.submissionPolicy = submissionPolicy;
this.exerciseDetailSections = this.getExerciseDetails();
});

this.loadGitDiffReport();

// the build logs endpoint requires at least editor privileges
if (this.programmingExercise.isAtLeastEditor) {
this.buildLogsSubscription = this.programmingExerciseService
.getBuildLogStatistics(exerciseId!)
.subscribe((buildLogStatistics) => (this.programmingExercise.buildLogStatistics = buildLogStatistics));
this.exerciseDetailSections = this.getExerciseDetails();
}

this.setLatestCoveredLineRatio();

this.checkAndAlertInconsistencies();

this.plagiarismCheckSupported = this.programmingLanguageFeatureService.getProgrammingLanguageFeature(
programmingExercise.programmingLanguage,
).plagiarismCheckSupported;
this.exerciseDetailSections = this.getExerciseDetails();
},
error: (error) => {
this.alertService.error(error.message);
},
});

this.exerciseStatisticsSubscription = this.statisticsService.getExerciseStatistics(exerciseId!).subscribe((statistics: ExerciseManagementStatisticsDto) => {
Expand Down Expand Up @@ -780,29 +786,27 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy {
return link;
}

private processGitDiffReport(gitDiffReport: ProgrammingExerciseGitDiffReport | undefined): void {
if (
gitDiffReport &&
(this.programmingExercise.gitDiffReport?.templateRepositoryCommitHash !== gitDiffReport.templateRepositoryCommitHash ||
this.programmingExercise.gitDiffReport?.solutionRepositoryCommitHash !== gitDiffReport.solutionRepositoryCommitHash)
) {
this.programmingExercise.gitDiffReport = gitDiffReport;
gitDiffReport.programmingExercise = this.programmingExercise;

const calculateLineCount = (entries: { lineCount?: number; previousLineCount?: number }[] = [], key: 'lineCount' | 'previousLineCount') =>
entries.map((entry) => entry[key] ?? 0).reduce((sum, count) => sum + count, 0);

this.addedLineCount = calculateLineCount(gitDiffReport.entries, 'lineCount');
this.removedLineCount = calculateLineCount(gitDiffReport.entries, 'previousLineCount');
}
}

loadGitDiffReport() {
this.programmingExerciseService.getDiffReport(this.programmingExercise.id!).subscribe((gitDiffReport) => {
if (
gitDiffReport &&
(this.programmingExercise.gitDiffReport?.templateRepositoryCommitHash !== gitDiffReport.templateRepositoryCommitHash ||
this.programmingExercise.gitDiffReport?.solutionRepositoryCommitHash !== gitDiffReport.solutionRepositoryCommitHash)
) {
this.programmingExercise.gitDiffReport = gitDiffReport;
gitDiffReport.programmingExercise = this.programmingExercise;
this.addedLineCount =
gitDiffReport.entries
?.map((entry) => entry.lineCount)
.filter((lineCount) => lineCount)
.map((lineCount) => lineCount!)
.reduce((lineCount1, lineCount2) => lineCount1 + lineCount2, 0) ?? 0;
this.removedLineCount =
gitDiffReport.entries
?.map((entry) => entry.previousLineCount)
.filter((lineCount) => lineCount)
.map((lineCount) => lineCount!)
.reduce((lineCount1, lineCount2) => lineCount1 + lineCount2, 0) ?? 0;
this.exerciseDetailSections = this.getExerciseDetails();
}
this.processGitDiffReport(gitDiffReport);
this.exerciseDetailSections = this.getExerciseDetails();
florian-glombik marked this conversation as resolved.
Show resolved Hide resolved
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe('DetailOverviewList', () => {
});

it('should initialize and destroy', () => {
component.sections = sections;
fixture.componentRef.setInput('sections', sections);
fixture.detectChanges();
expect(component.headlines).toStrictEqual([{ id: 'headline-1', translationKey: 'headline.1' }]);
expect(component.headlinesRecord).toStrictEqual({ 'headline.1': 'headline-1' });
Expand All @@ -67,7 +67,7 @@ describe('DetailOverviewList', () => {
});

it('should escape all falsy values', () => {
component.sections = [
fixture.componentRef.setInput('sections', [
{
headline: 'some-section',
details: [
Expand All @@ -81,7 +81,7 @@ describe('DetailOverviewList', () => {
},
],
},
];
]);
fixture.detectChanges();
const detailListTitleDOMElements = fixture.nativeElement.querySelectorAll('dt[id^=detail-title]');
expect(detailListTitleDOMElements).toHaveLength(1);
Expand Down
Loading
Loading