Skip to content

Commit

Permalink
Merge pull request #1825 from jplag/report-viewer/display-base
Browse files Browse the repository at this point in the history
Display Basecode in report viewer
  • Loading branch information
tsaglam authored Jul 8, 2024
2 parents 9574536 + 21b728f commit 15058d4
Show file tree
Hide file tree
Showing 12 changed files with 283 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package de.jplag.reporting.jsonfactory;

import java.nio.file.Path;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

import de.jplag.JPlagComparison;
import de.jplag.JPlagResult;
import de.jplag.Match;
import de.jplag.Submission;
import de.jplag.Token;
import de.jplag.reporting.FilePathUtil;
import de.jplag.reporting.reportobject.model.BaseCodeMatch;
import de.jplag.reporting.reportobject.model.CodePosition;
import de.jplag.reporting.reportobject.writer.JPlagResultWriter;

/**
* Writes the comparisons of each Submission to the basecode in its own file
*/
public class BaseCodeReportWriter {

private final JPlagResultWriter resultWriter;
private final Function<Submission, String> submissionToIdFunction;
public static final String BASEPATH = "basecode";

/**
* Creates a new BaseCodeReportWriter
* @param submissionToIdFunction Function for translating a submission to a unique id
* @param resultWriter Writer used for writing the result
*/
public BaseCodeReportWriter(Function<Submission, String> submissionToIdFunction, JPlagResultWriter resultWriter) {
this.submissionToIdFunction = submissionToIdFunction;
this.resultWriter = resultWriter;
}

/**
* Writes the basecode of each submission in the result into its own file in the result writer
* @param jPlagResult The result containing the submissions
*/
public void writeBaseCodeReport(JPlagResult jPlagResult) {
Set<Submission> submissions = new HashSet<>();

int numberOfComparisons = jPlagResult.getOptions().maximumNumberOfComparisons();
List<JPlagComparison> comparisons = jPlagResult.getComparisons(numberOfComparisons);
for (JPlagComparison comparison : comparisons) {
submissions.add(comparison.firstSubmission());
submissions.add(comparison.secondSubmission());
}
submissions.forEach(this::writeBaseCodeFile);
}

private void writeBaseCodeFile(Submission submission) {
List<BaseCodeMatch> matches = List.of();
if (submission.getBaseCodeComparison() != null) {
JPlagComparison baseCodeComparison = submission.getBaseCodeComparison();
boolean takeLeft = baseCodeComparison.firstSubmission().equals(submission);
matches = baseCodeComparison.matches().stream().map(match -> convertToBaseCodeMatch(submission, match, takeLeft)).toList();
}
resultWriter.addJsonEntry(matches, Path.of(BASEPATH, submissionToIdFunction.apply(submission).concat(".json")));
}

private BaseCodeMatch convertToBaseCodeMatch(Submission submission, Match match, boolean takeLeft) {
List<Token> tokens = submission.getTokenList().subList(takeLeft ? match.startOfFirst() : match.startOfSecond(),
(takeLeft ? match.endOfFirst() : match.endOfSecond()) + 1);

Comparator<? super Token> lineComparator = Comparator.comparingInt(Token::getLine);
Token start = tokens.stream().min(lineComparator).orElseThrow();
Token end = tokens.stream().max(lineComparator).orElseThrow();

CodePosition startPosition = new CodePosition(start.getLine(), start.getColumn() - 1,
takeLeft ? match.startOfFirst() : match.startOfSecond());
CodePosition endPosition = new CodePosition(end.getLine(), end.getColumn() + end.getLength() - 1,
takeLeft ? match.endOfFirst() : match.endOfSecond());

return new BaseCodeMatch(FilePathUtil.getRelativeSubmissionPath(start.getFile(), submission, submissionToIdFunction).toString(),
startPosition, endPosition, match.length());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import de.jplag.Submission;
import de.jplag.options.JPlagOptions;
import de.jplag.reporting.FilePathUtil;
import de.jplag.reporting.jsonfactory.BaseCodeReportWriter;
import de.jplag.reporting.jsonfactory.ComparisonReportWriter;
import de.jplag.reporting.reportobject.mapper.ClusteringResultMapper;
import de.jplag.reporting.reportobject.mapper.MetricMapper;
Expand Down Expand Up @@ -92,6 +93,7 @@ public void createAndSaveReport(JPlagResult result) {
writeSubmissionIndexFile(result);
writeReadMeFile();
writeOptionsFiles(result.getOptions());
writeBaseCodeReport(result);

this.resultWriter.close();
}
Expand Down Expand Up @@ -126,6 +128,11 @@ private void writeComparisons(JPlagResult result) {
submissionNameToNameToComparisonFileName = comparisonReportWriter.writeComparisonReports(result);
}

private void writeBaseCodeReport(JPlagResult result) {
BaseCodeReportWriter baseCodeReportWriter = new BaseCodeReportWriter(submissionToIdFunction, this.resultWriter);
baseCodeReportWriter.writeBaseCodeReport(result);
}

private void writeOverview(JPlagResult result) {
List<File> folders = new ArrayList<>();
folders.addAll(result.getOptions().submissionDirectories());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package de.jplag.reporting.reportobject.model;

import com.fasterxml.jackson.annotation.JsonProperty;

public record BaseCodeMatch(@JsonProperty("file_name") String fileName, @JsonProperty("start") CodePosition start,
@JsonProperty("end") CodePosition end, @JsonProperty("tokens") int tokens) {
}
17 changes: 12 additions & 5 deletions report-viewer/src/components/fileDisplaying/CodePanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
:line="line.line"
:lineNumber="index + 1"
:matches="line.matches"
@matchSelected="(match) => matchSelected(match)"
@matchSelected="(match: Match) => matchSelected(match)"
/>
</div>

Expand All @@ -66,6 +66,7 @@ import type { Language } from '@/model/Language'
import ToolTipComponent from '../ToolTipComponent.vue'
import CodeLine from './CodeLine.vue'
import type { Match } from '@/model/Match'
import type { BaseCodeMatch } from '@/model/BaseCodeReport'

const props = defineProps({
/**
Expand All @@ -82,6 +83,10 @@ const props = defineProps({
type: Array<MatchInSingleFile>,
required: true
},
baseCodeMatches: {
type: Array<BaseCodeMatch>,
required: true
},
/**
* Language of the file.
*/
Expand All @@ -98,10 +103,12 @@ const lineRefs = ref<(typeof CodeLine)[]>([])

const codeLines: Ref<{ line: string; matches: MatchInSingleFile[] }[]> = computed(() =>
highlight(props.file.data, props.highlightLanguage).map((line, index) => {
return {
line,
matches: props.matches?.filter((m) => m.start <= index + 1 && index + 1 <= m.end) ?? []
}
const matches = props.matches.filter((m) => m.start <= index + 1 && index + 1 <= m.end)
const baseCodeMatches = props.baseCodeMatches.filter(
(m) => m.start <= index + 1 && index + 1 <= m.end
)
matches.push(...baseCodeMatches)
return { line, matches }
})
)

Expand Down
12 changes: 11 additions & 1 deletion report-viewer/src/components/fileDisplaying/FilesContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@
!matches.get(file.fileName) ? [] : (matches.get(file.fileName) as MatchInSingleFile[])
"
:highlight-language="highlightLanguage"
@match-selected="(match) => $emit('matchSelected', match)"
@match-selected="(match: Match) => $emit('matchSelected', match)"
class="mt-1 first:mt-0"
:base-code-matches="baseCodeMatches"
/>
</VueDraggableNext>
</ScrollableComponent>
Expand All @@ -47,6 +48,8 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faCompressAlt } from '@fortawesome/free-solid-svg-icons'
import { library } from '@fortawesome/fontawesome-svg-core'
import type { Language } from '@/model/Language'
import type { BaseCodeMatch } from '@/model/BaseCodeReport'
import type { Match } from '@/model/Match'

library.add(faCompressAlt)

Expand Down Expand Up @@ -78,6 +81,13 @@ const props = defineProps({
highlightLanguage: {
type: String as PropType<Language>,
required: true
},
/**
* Base code matches of the submission.
*/
baseCodeMatches: {
type: Array as PropType<BaseCodeMatch[]>,
required: true
}
})

Expand Down
64 changes: 60 additions & 4 deletions report-viewer/src/components/fileDisplaying/MatchList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,36 @@
<div
class="flex h-fit min-w-0 max-w-full flex-row space-x-1 overflow-x-hidden text-xs print:hidden"
>
<ToolTipComponent direction="right" v-if="hasBaseCode" class="pr-3">
<template #default>
<OptionComponent label="Base Code" :style="{ background: getMatchColor(0.3, 'base') }" />
</template>
<template #tooltip>
<div class="whitespace-pre text-sm">
Sections that are likely base code (thus ignored in similarity calculation). <br />
<p>
{{ store().getDisplayName(id1) }}:

<span v-if="basecodeInFirst.length > 0">
{{ basecodeInFirst.map((b) => b.match.tokens).reduce((a, b) => a + b, 0) }} Tokens,
Lines: {{ store().getDisplayName(id1 ?? '') }}: Lines
{{ basecodeInFirst.map((b) => `${b.start}-${b.end}`).join(',') }}
</span>
<span v-else>No Basecode in Submission</span>
</p>
<p>
{{ store().getDisplayName(id2) }}:
<span v-if="basecodeInSecond.length > 0">
{{ basecodeInSecond.map((b) => b.match.tokens).reduce((a, b) => a + b, 0) }} Tokens,
Lines: {{ store().getDisplayName(id2 ?? '') }}: Lines
{{ basecodeInSecond.map((b) => `${b.start}-${b.end}`).join(',') }}
</span>
<span v-else>No Basecode in Submission</span>
</p>
</div>
</template>
</ToolTipComponent>

<ToolTipComponent direction="right">
<template #default>
<OptionComponent label="Match Files: TokenCount" />
Expand Down Expand Up @@ -82,6 +112,13 @@
<td class="px-2">{{ match.startInSecond }} - {{ match.endInSecond }}</td>
<td class="px-2">{{ match.tokens }}</td>
</tr>
<tr
v-if="hasBaseCode"
:style="{ background: getMatchColor(0.3, 'base') }"
class="print-excact"
>
<td class="px-2" colspan="5">Basecode in submissions</td>
</tr>
</table>
</div>
</template>
Expand All @@ -92,27 +129,42 @@ import OptionComponent from '../optionsSelectors/OptionComponent.vue'
import ToolTipComponent from '@/components/ToolTipComponent.vue'
import { getMatchColor } from '@/utils/ColorUtils'
import type { ToolTipDirection } from '@/model/ui/ToolTip'
import { ref, type Ref } from 'vue'
import { ref, type Ref, computed } from 'vue'
import type { BaseCodeMatch } from '@/model/BaseCodeReport'
import { store } from '@/stores/store'

const props = defineProps({
/**
* Matches of the comparison.
* type: Array<Match>
*/
matches: {
type: Array<Match>
type: Array<Match>,
required: true
},
/**
* ID of first submission
*/
id1: {
type: String
type: String,
required: true
},
/**
* ID of second submission
*/
id2: {
type: String
type: String,
required: true
},
basecodeInFirst: {
type: Array<BaseCodeMatch>,
required: false,
default: () => []
},
basecodeInSecond: {
type: Array<BaseCodeMatch>,
required: false,
default: () => []
}
})

Expand Down Expand Up @@ -147,4 +199,8 @@ function updateScrollOffset() {
scrollOffsetX.value = scrollableList.value.scrollLeft
}
}

const hasBaseCode = computed(() => {
return props.basecodeInFirst.length > 0 || props.basecodeInSecond.length > 0
})
</script>
20 changes: 20 additions & 0 deletions report-viewer/src/model/BaseCodeReport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { CodePosition } from './Match'
import { MatchInSingleFile } from './MatchInSingleFile'

export class BaseCodeMatch extends MatchInSingleFile {
constructor(fileName: string, start: CodePosition, end: CodePosition, tokens: number) {
super(
{
firstFile: fileName,
secondFile: fileName,
startInFirst: start,
endInFirst: end,
startInSecond: start,
endInSecond: end,
colorIndex: 'base',
tokens
},
1
)
}
}
4 changes: 3 additions & 1 deletion report-viewer/src/model/Match.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { MatchColorIndex } from '@/utils/ColorUtils'

/**
* Match between two files of two submissions.
* @property firstFile - Path to the file of the first submission.
Expand All @@ -17,7 +19,7 @@ export interface Match {
startInSecond: CodePosition
endInSecond: CodePosition
tokens: number
colorIndex?: number
colorIndex: MatchColorIndex
}

export interface CodePosition {
Expand Down
26 changes: 26 additions & 0 deletions report-viewer/src/model/factories/BaseCodeReportFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import slash from 'slash'
import { BaseCodeMatch } from '../BaseCodeReport'
import { BaseFactory } from './BaseFactory'
import type { CodePosition } from '../Match'

export class BaseCodeReportFactory extends BaseFactory {
private static readonly basePath = 'basecode'

public static async getReport(submissionId: string): Promise<BaseCodeMatch[]> {
const a = await this.extractReport(
JSON.parse(await this.getFile(slash(`${this.basePath}/${submissionId}.json`)))
)
return a
}

private static async extractReport(json: Record<string, unknown>[]): Promise<BaseCodeMatch[]> {
return json.map((match: Record<string, unknown>) => {
return new BaseCodeMatch(
match['file_name'] as string,
match['start'] as CodePosition,
match['end'] as CodePosition,
match['tokens'] as number
)
})
}
}
13 changes: 11 additions & 2 deletions report-viewer/src/utils/ColorUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,22 @@ function getMatchColorCount() {
return matchColors.length
}

function getMatchColor(alpha: number, index?: number) {
type MatchColorIndex = number | undefined | 'base'

function getMatchColor(alpha: number, index: MatchColorIndex) {
if (index == undefined) {
return 'rgba(0,0,0,0)'
}
if (index == 'base') {
return getBaseCodeColor(alpha)
}
return `rgba(${matchColors[index].red}, ${matchColors[index].green}, ${matchColors[index].blue}, ${alpha})`
}

function getBaseCodeColor(opacity: number) {
return `hsla(0, 0%, 75%, ${opacity})`
}

const graphRGB = {
red: 190,
green: 22,
Expand All @@ -104,4 +113,4 @@ const graphColors = {
}
}

export { generateColors, graphColors, getMatchColorCount, getMatchColor }
export { generateColors, graphColors, getMatchColorCount, getMatchColor, type MatchColorIndex }
Loading

0 comments on commit 15058d4

Please sign in to comment.