From fb852f11ca5442f6d294849fdb2b81c37568dc3e Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Thu, 13 Jul 2023 10:09:22 +0200 Subject: [PATCH 01/19] update java part --- .../reportobject/ReportObjectFactory.java | 16 +--- .../reportobject/mapper/MetricMapper.java | 52 ++++++------- .../reportobject/model/OverviewReport.java | 4 +- .../reportobject/model/TopComparison.java | 4 +- .../reportobject/mapper/MetricMapperTest.java | 73 +++++++------------ 5 files changed, 60 insertions(+), 89 deletions(-) diff --git a/core/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java b/core/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java index 1815c55e2..0f8f142da 100644 --- a/core/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java +++ b/core/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java @@ -33,7 +33,6 @@ import de.jplag.reporting.jsonfactory.ToDiskWriter; import de.jplag.reporting.reportobject.mapper.ClusteringResultMapper; import de.jplag.reporting.reportobject.mapper.MetricMapper; -import de.jplag.reporting.reportobject.model.Metric; import de.jplag.reporting.reportobject.model.OverviewReport; import de.jplag.reporting.reportobject.model.SubmissionFileIndex; import de.jplag.reporting.reportobject.model.Version; @@ -175,7 +174,7 @@ private void writeOverview(JPlagResult result, String path) { int totalComparisons = result.getAllComparisons().size(); int numberOfMaximumComparisons = result.getOptions().maximumNumberOfComparisons(); - int shownComparisons = totalComparisons > numberOfMaximumComparisons ? numberOfMaximumComparisons : totalComparisons; + int shownComparisons = Math.min(totalComparisons, numberOfMaximumComparisons); int missingComparisons = totalComparisons > numberOfMaximumComparisons ? (totalComparisons - numberOfMaximumComparisons) : 0; logger.info("Total Comparisons: {}. Comparisons in Report: {}. Omitted Comparisons: {}.", totalComparisons, shownComparisons, missingComparisons); @@ -190,7 +189,8 @@ private void writeOverview(JPlagResult result, String path) { result.getOptions().minimumTokenMatch(), // matchSensitivity getDate(),// dateOfExecution result.getDuration(), // executionTime - getMetrics(result),// metrics + MetricMapper.getDistributions(result), // distribution + new MetricMapper(submissionToIdFunction).getTopComparisons(result),// topComparisons clusteringResultMapper.map(result), // clusters totalComparisons); // totalComparisons @@ -220,16 +220,6 @@ private Set getSubmissions(List comparisons) { return submissions; } - /** - * Gets the used metrics in a JPlag comparison. As Max Metric is included in every JPlag run, this always include Max - * Metric. - * @return A list contains Metric DTOs. - */ - private List getMetrics(JPlagResult result) { - MetricMapper metricMapper = new MetricMapper(submissionToIdFunction); - return List.of(metricMapper.getAverageMetric(result), metricMapper.getMaxMetric(result)); - } - private String getDate() { SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yy"); Date date = new Date(); diff --git a/core/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java b/core/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java index 0b8a819d4..c508147ba 100644 --- a/core/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java +++ b/core/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java @@ -3,16 +3,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.function.Function; import de.jplag.JPlagComparison; import de.jplag.JPlagResult; -import de.jplag.Messages; import de.jplag.Submission; import de.jplag.options.SimilarityMetric; -import de.jplag.reporting.reportobject.model.Metric; import de.jplag.reporting.reportobject.model.TopComparison; /** @@ -25,40 +23,36 @@ public MetricMapper(Function submissionToIdFunction) { this.submissionToIdFunction = submissionToIdFunction; } - public Metric getAverageMetric(JPlagResult result) { - return new Metric(SimilarityMetric.AVG.name(), convertDistribution(result.getSimilarityDistribution()), - getTopComparisons(getComparisons(result)), Messages.getString("SimilarityMetric.Avg.Description")); + /** + * Generates a map of all distributions + * @param result Result containing distributions + * @return Map with key as name of metric and value as distribution + */ + public static Map> getDistributions(JPlagResult result) { + return Map.of(SimilarityMetric.AVG.name(), convertDistribution(result.getSimilarityDistribution()), + SimilarityMetric.MAX.name(), convertDistribution(result.getMaxSimilarityDistribution())); } - public Metric getMaxMetric(JPlagResult result) { - return new Metric(SimilarityMetric.MAX.name(), convertDistribution(result.getMaxSimilarityDistribution()), - getMaxSimilarityTopComparisons(getComparisons(result)), Messages.getString("SimilarityMetric.Max.Description")); + /** + * Generates a List of the top comparisons + * @param result Result containing comparisons + * @return List of top comparisons with similarities in all metrics + */ + public List getTopComparisons(JPlagResult result) { + return result.getComparisons(result.getOptions().maximumNumberOfComparisons()).stream() + .map(comparison -> new TopComparison(submissionToIdFunction.apply(comparison.firstSubmission()), + submissionToIdFunction.apply(comparison.secondSubmission()), getComparisonMetricMap(comparison))) + .toList(); } - private List getComparisons(JPlagResult result) { - int maxNumberOfComparisons = result.getOptions().maximumNumberOfComparisons(); - return result.getComparisons(maxNumberOfComparisons); + private Map getComparisonMetricMap(JPlagComparison comparison) { + return Map.of(SimilarityMetric.AVG.name(), comparison.similarity(), + SimilarityMetric.MAX.name(), comparison.maximalSimilarity()); } - private List convertDistribution(int[] array) { + private static List convertDistribution(int[] array) { List list = new ArrayList<>(Arrays.stream(array).boxed().toList()); Collections.reverse(list); return list; } - - private List getTopComparisons(List comparisons, Function similarityExtractor) { - return comparisons.stream().sorted(Comparator.comparing(similarityExtractor).reversed()) - .map(comparison -> new TopComparison(submissionToIdFunction.apply(comparison.firstSubmission()), - submissionToIdFunction.apply(comparison.secondSubmission()), similarityExtractor.apply(comparison))) - .toList(); - } - - private List getTopComparisons(List comparisons) { - return getTopComparisons(comparisons, JPlagComparison::similarity); - } - - private List getMaxSimilarityTopComparisons(List comparisons) { - return getTopComparisons(comparisons, JPlagComparison::maximalSimilarity); - } - } diff --git a/core/src/main/java/de/jplag/reporting/reportobject/model/OverviewReport.java b/core/src/main/java/de/jplag/reporting/reportobject/model/OverviewReport.java index b4651dbe9..fa7da7673 100644 --- a/core/src/main/java/de/jplag/reporting/reportobject/model/OverviewReport.java +++ b/core/src/main/java/de/jplag/reporting/reportobject/model/OverviewReport.java @@ -31,7 +31,9 @@ public record OverviewReport( @JsonProperty("execution_time") long executionTime, - @JsonProperty("metrics") List metrics, + @JsonProperty("distributions") Map> distributions, + + @JsonProperty("top_comparisons") List topComparisons, @JsonProperty("clusters") List clusters, diff --git a/core/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java b/core/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java index 7dfa1339f..ba4a54da8 100644 --- a/core/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java +++ b/core/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java @@ -1,7 +1,9 @@ package de.jplag.reporting.reportobject.model; +import java.util.Map; + import com.fasterxml.jackson.annotation.JsonProperty; public record TopComparison(@JsonProperty("first_submission") String firstSubmission, @JsonProperty("second_submission") String secondSubmission, - @JsonProperty("similarity") double similarity) { + @JsonProperty("similarities") Map similarities) { } diff --git a/core/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java b/core/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java index 1aedbefa8..3f4942d65 100644 --- a/core/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java +++ b/core/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -18,43 +19,37 @@ import de.jplag.reporting.reportobject.model.TopComparison; public class MetricMapperTest { - private static final List EXPECTED_DISTRIBUTION = List.of(29, 23, 19, 17, 13, 11, 7, 5, 3, 2); + private static final List EXPECTED_AVG_DISTRIBUTION = List.of(29, 23, 19, 17, 13, 11, 7, 5, 3, 2); + private static final List EXPECTED_MAX_DISTRIBUTION = List.of(50, 48, 20, 13, 10, 1, 3, 1, 0, 0); private final MetricMapper metricMapper = new MetricMapper(Submission::getName); @Test - public void test_getAverageMetric() { + public void test_getDistributions() { // given - JPlagResult jPlagResult = createJPlagResult(MockMetric.AVG, distribution(EXPECTED_DISTRIBUTION), - comparison(submission("1"), submission("2"), .7), comparison(submission("3"), submission("4"), .3)); + JPlagResult jPlagResult = createJPlagResult(distribution(EXPECTED_AVG_DISTRIBUTION), distribution(EXPECTED_MAX_DISTRIBUTION), + comparison(submission("1"), submission("2"), .7, .8), comparison(submission("3"), submission("4"), .3, .9)); + // when - var result = metricMapper.getAverageMetric(jPlagResult); + Map> result = MetricMapper.getDistributions(jPlagResult); - // then - Assertions.assertEquals("AVG", result.name()); - Assertions.assertIterableEquals(EXPECTED_DISTRIBUTION, result.distribution()); - Assertions.assertEquals(List.of(new TopComparison("1", "2", .7), new TopComparison("3", "4", .3)), result.topComparisons()); - Assertions.assertEquals( - "Average of both program coverages. This is the default similarity which" - + " works in most cases: Matches with a high average similarity indicate that the programs work " + "in a very similar way.", - result.description()); + //then + Assertions.assertEquals(Map.of("AVG", EXPECTED_AVG_DISTRIBUTION, "MAX", EXPECTED_MAX_DISTRIBUTION), result); } @Test - public void test_getMaxMetric() { + public void test_getTopComparisons() { // given - JPlagResult jPlagResult = createJPlagResult(MockMetric.MAX, distribution(EXPECTED_DISTRIBUTION), - comparison(submission("00"), submission("01"), .7), comparison(submission("10"), submission("11"), .3)); + JPlagResult jPlagResult = createJPlagResult(distribution(EXPECTED_AVG_DISTRIBUTION), distribution(EXPECTED_MAX_DISTRIBUTION), + comparison(submission("1"), submission("2"), .7, .8), comparison(submission("3"), submission("4"), .3, .9)); + // when - var result = metricMapper.getMaxMetric(jPlagResult); + List result = metricMapper.getTopComparisons(jPlagResult); // then - Assertions.assertEquals("MAX", result.name()); - Assertions.assertIterableEquals(EXPECTED_DISTRIBUTION, result.distribution()); - Assertions.assertEquals(List.of(new TopComparison("00", "01", .7), new TopComparison("10", "11", .3)), result.topComparisons()); - Assertions.assertEquals( - "Maximum of both program coverages. This ranking is especially useful if the programs are very " - + "different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.", - result.description()); + Assertions.assertEquals(List.of( + new TopComparison("1", "2", Map.of("AVG", .7, "MAX", .8)), + new TopComparison("3", "4", Map.of("AVG", .3, "MAX", .9)) + ), result); } private int[] distribution(List expectedDistribution) { @@ -67,19 +62,15 @@ private CreateSubmission submission(String name) { return new CreateSubmission(name); } - private Comparison comparison(CreateSubmission submission1, CreateSubmission submission2, double similarity) { - return new Comparison(submission1, submission2, similarity); + private Comparison comparison(CreateSubmission submission1, CreateSubmission submission2, double similarity, double maxSimilarity) { + return new Comparison(submission1, submission2, similarity, maxSimilarity); } - private JPlagResult createJPlagResult(MockMetric metricToMock, int[] distribution, Comparison... createComparisonsDto) { + private JPlagResult createJPlagResult(int[] avgDistribution, int[] maxDistribution, Comparison... createComparisonsDto) { JPlagResult jPlagResult = mock(JPlagResult.class); + doReturn(avgDistribution).when(jPlagResult).getSimilarityDistribution(); + doReturn(maxDistribution).when(jPlagResult).getMaxSimilarityDistribution(); - if (metricToMock.equals(MockMetric.AVG)) { - doReturn(distribution).when(jPlagResult).getSimilarityDistribution(); - } else if (metricToMock.equals(MockMetric.MAX)) { - doReturn(distribution).when(jPlagResult).getMaxSimilarityDistribution(); - - } JPlagOptions options = mock(JPlagOptions.class); doReturn(createComparisonsDto.length).when(options).maximumNumberOfComparisons(); @@ -95,11 +86,8 @@ private JPlagResult createJPlagResult(MockMetric metricToMock, int[] distributio JPlagComparison mockedComparison = mock(JPlagComparison.class); doReturn(submission1).when(mockedComparison).firstSubmission(); doReturn(submission2).when(mockedComparison).secondSubmission(); - if (metricToMock.equals(MockMetric.AVG)) { - doReturn(comparisonDto.similarity).when(mockedComparison).similarity(); - } else if (metricToMock.equals(MockMetric.MAX)) { - doReturn(comparisonDto.similarity).when(mockedComparison).maximalSimilarity(); - } + doReturn(comparisonDto.similarity).when(mockedComparison).similarity(); + doReturn(comparisonDto.maxSimilarity).when(mockedComparison).maximalSimilarity(); comparisonList.add(mockedComparison); } @@ -107,15 +95,10 @@ private JPlagResult createJPlagResult(MockMetric metricToMock, int[] distributio return jPlagResult; } - private enum MockMetric { - MAX, - AVG - } - - private record Comparison(CreateSubmission submission1, CreateSubmission submission2, double similarity) { + private record Comparison(CreateSubmission submission1, CreateSubmission submission2, double similarity, double maxSimilarity) { } private record CreateSubmission(String name) { } -} +} \ No newline at end of file From 3d403033ac39af836356001060d13d9db481eb99 Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Thu, 13 Jul 2023 14:34:47 +0200 Subject: [PATCH 02/19] Redo Overview --- .../src/components/ComparisonsTable.vue | 9 +- .../src/model/ComparisonListElement.ts | 5 +- report-viewer/src/model/Metric.ts | 17 -- report-viewer/src/model/MetricType.ts | 4 +- report-viewer/src/model/Overview.ts | 20 +- .../src/model/factories/BaseFactory.ts | 49 +++++ .../src/model/factories/OverviewFactory.ts | 204 ++++++++---------- report-viewer/src/views/ClusterView.vue | 6 +- report-viewer/src/views/OverviewView.vue | 32 +-- 9 files changed, 173 insertions(+), 173 deletions(-) delete mode 100644 report-viewer/src/model/Metric.ts create mode 100644 report-viewer/src/model/factories/BaseFactory.ts diff --git a/report-viewer/src/components/ComparisonsTable.vue b/report-viewer/src/components/ComparisonsTable.vue index 400b67943..9ef0ce566 100644 --- a/report-viewer/src/components/ComparisonsTable.vue +++ b/report-viewer/src/components/ComparisonsTable.vue @@ -79,8 +79,12 @@
-
{{ (item.averageSimilarity * 100).toFixed(2) }}%
-
{{ (item.maximumSimilarity * 100).toFixed(2) }}%
+
+ {{ (item.similarities[MetricType.AVERAGE] * 100).toFixed(2) }}% +
+
+ {{ (item.similarities[MetricType.MAXIMUM] * 100).toFixed(2) }}% +
@@ -133,6 +137,7 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import { library } from '@fortawesome/fontawesome-svg-core' import { faUserGroup } from '@fortawesome/free-solid-svg-icons' import { generateColors } from '@/utils/ColorUtils' +import MetricType from '@/model/MetricType' library.add(faUserGroup) diff --git a/report-viewer/src/model/ComparisonListElement.ts b/report-viewer/src/model/ComparisonListElement.ts index 815e6c9c1..60f4fb483 100644 --- a/report-viewer/src/model/ComparisonListElement.ts +++ b/report-viewer/src/model/ComparisonListElement.ts @@ -1,3 +1,5 @@ +import type MetricType from './MetricType' + /** * Comparison model used by the Comparison Table in Overview. Only the needed attributes to display are included. * For full comparison model see Comparison.ts @@ -13,6 +15,5 @@ export type ComparisonListElement = { id: number firstSubmissionId: string secondSubmissionId: string - averageSimilarity: number - maximumSimilarity: number + similarities: Record } diff --git a/report-viewer/src/model/Metric.ts b/report-viewer/src/model/Metric.ts deleted file mode 100644 index b6e7d6374..000000000 --- a/report-viewer/src/model/Metric.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { ComparisonListElement } from './ComparisonListElement' - -/** - * Metric used in the Jplag Comparison - * @property metricName - Name of the metric. - * @property description - Description of the metric. - * @property metricThreshold - Threshold of the metric. - * @property distribution - Distribution of the metric. - * @property comparisons - Comparisons of the metric. - */ -export type Metric = { - metricName: string - description: string - metricThreshold: number - distribution: Array - comparisons: Array -} diff --git a/report-viewer/src/model/MetricType.ts b/report-viewer/src/model/MetricType.ts index dfb93c9f0..16f5c7439 100644 --- a/report-viewer/src/model/MetricType.ts +++ b/report-viewer/src/model/MetricType.ts @@ -2,8 +2,8 @@ * This enum maps the metric type to the index they have in the generated JSON and respectivly in the store. */ enum MetricType { - AVERAGE = 0, - MAXIMUM = 1 + AVERAGE = 'AVG', + MAXIMUM = 'MAX' } export default MetricType diff --git a/report-viewer/src/model/Overview.ts b/report-viewer/src/model/Overview.ts index a59e2ccd0..2bc1f778f 100644 --- a/report-viewer/src/model/Overview.ts +++ b/report-viewer/src/model/Overview.ts @@ -1,6 +1,7 @@ import type Distribution from './Distribution' import type { Cluster } from '@/model/Cluster' import type { ComparisonListElement } from './ComparisonListElement' +import type MetricType from './MetricType' /** * Model of the Overview file generated by JPlag @@ -11,11 +12,10 @@ export class Overview { private readonly _language: string private readonly _fileExtensions: Array private readonly _matchSensitivity: number - private readonly _submissionIdsToComparisonFileName: Map> private readonly _dateOfExecution: string private readonly _durationOfExecution: number private readonly _topComparisons: Array - private readonly _distributions: Array + private readonly _distributions: Record private readonly _clusters: Array private readonly _totalComparisons: number @@ -28,10 +28,9 @@ export class Overview { dateOfExecution: string, durationOfExecution: number, topComparisons: Array, - distributions: Array, + distributions: Record, clusters: Array, - totalComparisons: number, - submissionIdsToComparisonFileName: Map> + totalComparisons: number ) { this._submissionFolderPath = submissionFolderPath this._baseCodeFolderPath = baseCodeFolderPath @@ -42,16 +41,7 @@ export class Overview { this._durationOfExecution = durationOfExecution this._topComparisons = topComparisons this._distributions = distributions - this._clusters = clusters - this._submissionIdsToComparisonFileName = submissionIdsToComparisonFileName - this._totalComparisons = totalComparisons - } - - /** - * @return Map of submission ids to the name of the comparison file and their content - */ - get submissionIdsToComparisonFileName() { - return this._submissionIdsToComparisonFileName + ;(this._clusters = clusters), (this._totalComparisons = totalComparisons) } /** diff --git a/report-viewer/src/model/factories/BaseFactory.ts b/report-viewer/src/model/factories/BaseFactory.ts new file mode 100644 index 000000000..bfb0b5211 --- /dev/null +++ b/report-viewer/src/model/factories/BaseFactory.ts @@ -0,0 +1,49 @@ +import store from '@/stores/store' + +/** + * This class provides some basic functionality for the factories. + */ +export class BaseFactory { + /** + * Returns the content of a file through the stored loading type. + * @param path - Path to the file + * @return Content of the file + * @throws Error if the file could not be found + */ + protected static getFile(path: string): string { + if (store().state.localModeUsed) { + return this.getLocalFile(path) + } else if (store().state.zipModeUsed) { + const index = Object.keys(store().state.files).find((name) => name.endsWith(path)) + if (index != undefined) { + const file = store().state.files[index] + if (file != undefined) { + return file + } + } + throw new Error(`Could not find ${path} in zip file.`) + } else if (store().state.singleModeUsed) { + return store().state.singleFillRawContent + } + + throw new Error('No loading type specified') + } + + /** + * Returns the content of a file from the local files. + * @param path - Path to the file + * @return Content of the file + * @throws Error if the file could not be found + */ + protected static getLocalFile(path: string): string { + const request = new XMLHttpRequest() + request.open('GET', `/files/${path}`, false) + request.send() + + if (request.status == 200) { + return request.response + } else { + throw new Error(`Could not find ${path} in local files.`) + } + } +} diff --git a/report-viewer/src/model/factories/OverviewFactory.ts b/report-viewer/src/model/factories/OverviewFactory.ts index 849aa4957..f332500fd 100644 --- a/report-viewer/src/model/factories/OverviewFactory.ts +++ b/report-viewer/src/model/factories/OverviewFactory.ts @@ -5,25 +5,28 @@ import store from '@/stores/store' import type { Version } from '../Version' import versionJson from '@/version.json' import Distribution from '../Distribution' +import MetricType from '../MetricType' +import { BaseFactory } from './BaseFactory' -export class OverviewFactory { +export class OverviewFactory extends BaseFactory { static reportViewerVersion: Version = versionJson['report_viewer_version'] !== undefined ? versionJson['report_viewer_version'] : { major: -1, minor: -1, patch: -1 } + /** + * Gets the overview file based on the used mode (zip, local, single). + */ + public static getOverview(): Overview { + return this.extractOverview(JSON.parse(this.getFile('overview.json'))) + } + /** * Creates an overview object from a json object created by by JPlag * @param json the json object */ private static extractOverview(json: Record): Overview { - const versionField = json.jplag_version as Record - const jplagVersion: Version = { - major: versionField.major, - minor: versionField.minor, - patch: versionField.patch - } - + const jplagVersion = this.extractVersion(json) OverviewFactory.compareVersions(jplagVersion, this.reportViewerVersion) const submissionFolder = json.submission_folder_path as Array @@ -31,123 +34,100 @@ export class OverviewFactory { const language = json.language as string const fileExtensions = json.file_extensions as Array const matchSensitivity = json.match_sensitivity as number - const jsonSubmissions = json.submission_id_to_display_name as Map - const map = new Map(Object.entries(jsonSubmissions)) const dateOfExecution = json.date_of_execution as string const duration = json.execution_time as number as number - const clusters = [] as Array const totalComparisons = json.total_comparisons as number - const distributions = [] as Array - const averageSimilarities: Map = new Map() - const comparisons = [] as Array + this.saveIdToDisplayNameMap(json) + this.saveComparisonFilesLookup(json) - const metrics = json.metrics as Array as Array> - // Average - distributions.push(new Distribution(metrics[0].distribution as Array)) - for (const comparison of metrics[0].topComparisons as Array>) { - averageSimilarities.set( - (comparison.first_submission as string) + '-' + (comparison.second_submission as string), - comparison.similarity as number - ) + return new Overview( + submissionFolder, + baseCodeFolder, + language, + fileExtensions, + matchSensitivity, + dateOfExecution, + duration, + this.extractTopComparisons(json), + this.extractDistributions(json), + this.extractClusters(json), + totalComparisons + ) + } + + private static extractVersion(json: Record): Version { + const versionField = json.jplag_version as Record + return { + major: versionField.major, + minor: versionField.minor, + patch: versionField.patch } + } - // Max - distributions.push(new Distribution(metrics[1].distribution as Array)) + private static extractDistributions( + json: Record + ): Record { + const distributionsMap = json.distributions as Record> + const distributions = {} as Record + for (const [key, value] of Object.entries(distributionsMap)) { + distributions[key as MetricType] = new Distribution(value as Array) + } + return distributions + } + + private static extractTopComparisons( + json: Record + ): Array { + const jsonComparisons = json.top_comparisons as Array> + const comparisons = [] as Array let counter = 0 - for (const comparison of metrics[1].topComparisons as Array>) { - const avg = averageSimilarities.get( - (comparison.first_submission as string) + '-' + (comparison.second_submission as string) - ) + for (const topComparison of jsonComparisons) { comparisons.push({ sortingPlace: counter++, id: counter, - firstSubmissionId: comparison.first_submission as string, - secondSubmissionId: comparison.second_submission as string, - averageSimilarity: avg as number, - maximumSimilarity: comparison.similarity as number + firstSubmissionId: topComparison.first_submission as string, + secondSubmissionId: topComparison.second_submission as string, + similarities: topComparison.similarities as Record }) } + return comparisons + } + private static extractClusters(json: Record): Array { + if (!json.clusters) { + return [] + } - store().saveSubmissionNames(map) - - if (json.clusters) { - ;(json.clusters as Array).forEach((jsonCluster) => { - const cluster = jsonCluster as Record - const newCluster: Cluster = { - averageSimilarity: cluster.average_similarity as number, - strength: cluster.strength as number, - members: cluster.members as Array - } - clusters.push(newCluster) + const clusters = [] as Array + for (const jsonCluster of json.clusters as Array>) { + clusters.push({ + averageSimilarity: jsonCluster.average_similarity as number, + strength: jsonCluster.strength as number, + members: jsonCluster.members as Array }) } + return clusters + } - OverviewFactory.saveSubmissionsToComparisonNameMap(json) - return new Overview( - submissionFolder, - baseCodeFolder, - language, - fileExtensions, - matchSensitivity, - dateOfExecution, - duration, - comparisons, - distributions, - clusters, - totalComparisons, - new Map() - ) + private static saveIdToDisplayNameMap(json: Record) { + const jsonSubmissions = json.submission_id_to_display_name as Map + const map = new Map(Object.entries(jsonSubmissions)) + + store().saveSubmissionNames(map) } - /** - * Gets the overview file based on the used mode (zip, local, single). - */ - public static getOverview(): Overview { - console.log('Generating overview...') - let temp!: Overview - //Gets the overview file based on the used mode (zip, local, single). - if (store().state.localModeUsed) { - const request = new XMLHttpRequest() - request.open('GET', '/files/overview.json', false) - request.send() - - if (request.status == 200) { - temp = OverviewFactory.extractOverview(JSON.parse(request.response)) - } else { - throw new Error('Could not find overview.json in folder.') - } - } else if (store().state.zipModeUsed) { - console.log('Start finding overview.json in state...') - const index = Object.keys(store().state.files).find((name) => name.endsWith('overview.json')) - const overviewFile = - index != undefined - ? store().state.files[index] - : console.log('Could not find overview.json') - - if (overviewFile === undefined) { - console.log('Could not find overview.json') - return new Overview( - [], - '', - '', - [], - 0, - '', - 0, - [], - [], - [], - 0, - new Map>() - ) - } - const overviewJson = JSON.parse(overviewFile) - temp = OverviewFactory.extractOverview(overviewJson) - } else if (store().state.singleModeUsed) { - temp = OverviewFactory.extractOverview(JSON.parse(store().state.singleFillRawContent)) + private static saveComparisonFilesLookup(json: Record) { + const submissionIdsToComparisonName = json.submission_ids_to_comparison_file_name as Map< + string, + Map + > + const test: Array> = Object.entries(submissionIdsToComparisonName) + const comparisonMap = new Map>() + for (const [key, value] of test) { + comparisonMap.set(key as string, new Map(Object.entries(value as object))) } - return temp + + store().saveComparisonFileLookup(comparisonMap) } /** @@ -204,18 +184,4 @@ export class OverviewFactory { sessionStorage.setItem('versionAlert', 'true') } } - - private static saveSubmissionsToComparisonNameMap(json: Record) { - const submissionIdsToComparisonName = json.submission_ids_to_comparison_file_name as Map< - string, - Map - > - const test: Array> = Object.entries(submissionIdsToComparisonName) - const comparisonMap = new Map>() - for (const [key, value] of test) { - comparisonMap.set(key as string, new Map(Object.entries(value as object))) - } - - store().saveComparisonFileLookup(comparisonMap) - } } diff --git a/report-viewer/src/views/ClusterView.vue b/report-viewer/src/views/ClusterView.vue index b0faca718..c47cdf038 100644 --- a/report-viewer/src/views/ClusterView.vue +++ b/report-viewer/src/views/ClusterView.vue @@ -30,6 +30,7 @@ import Container from '@/components/ContainerComponent.vue' import TextInformation from '@/components/TextInformation.vue' import type { ClusterListElement, ClusterListElementMember } from '@/model/ClusterListElement' import type { ComparisonListElement } from '@/model/ComparisonListElement' +import MetricType from '@/model/MetricType' import { OverviewFactory } from '@/model/factories/OverviewFactory' const props = defineProps({ @@ -43,6 +44,7 @@ const overview = OverviewFactory.getOverview() const cluster = overview.clusters[props.clusterIndex] const comparisons = [] as Array const clusterMemberList = new Map() as ClusterListElementMember +const usedMetric = MetricType.AVERAGE function getComparisonFor(id1: string, id2: string) { return overview.topComparisons.find( @@ -62,7 +64,7 @@ for (let i = 0; i < cluster.members.length; i++) { } let counter = 0 comparisons - .sort((a, b) => b.averageSimilarity - a.averageSimilarity) + .sort((a, b) => b.similarities[usedMetric] - a.similarities[usedMetric]) .forEach((c) => { c.sortingPlace = counter++ c.id = counter @@ -75,7 +77,7 @@ for (const member of cluster.members) { .forEach((c) => { membersComparisons.push({ matchedWith: c.firstSubmissionId === member ? c.secondSubmissionId : c.firstSubmissionId, - similarity: c.averageSimilarity + similarity: c.similarities[usedMetric] }) }) clusterMemberList.set(member, membersComparisons) diff --git a/report-viewer/src/views/OverviewView.vue b/report-viewer/src/views/OverviewView.vue index 5b0334fc8..4db66e6e7 100644 --- a/report-viewer/src/views/OverviewView.vue +++ b/report-viewer/src/views/OverviewView.vue @@ -35,7 +35,9 @@ @@ -60,7 +62,9 @@ b.maximumSimilarity - a.maximumSimilarity) - } else { - comparisons.sort((a, b) => b.averageSimilarity - a.averageSimilarity) - } + comparisons.sort( + (a, b) => + b.similarities[comparisonTableSortingMetric.value] - + a.similarities[comparisonTableSortingMetric.value] + ) let index = 0 comparisons.forEach((c) => { c.sortingPlace = index++ @@ -172,12 +176,12 @@ function changeAnnoymousForAll() { const selectedDistributionDiagramMetric = ref(MetricType.AVERAGE) -/** - * Switch between metrics - * @param metric Metric to switch to - */ -function selectDistributionDiagramMetric(metric: number) { - selectedDistributionDiagramMetric.value = metric +function getMetricFromNumber(metric: number) { + if (metric == 0) { + return MetricType.AVERAGE + } else { + return MetricType.MAXIMUM + } } const hasMoreSubmissionPaths = overview.submissionFolderPath.length > 1 From 333213f48bb9949ac97fc2a0e3525da239e96bab Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Thu, 13 Jul 2023 14:51:04 +0200 Subject: [PATCH 03/19] actually get base folder path --- report-viewer/src/model/factories/OverviewFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/report-viewer/src/model/factories/OverviewFactory.ts b/report-viewer/src/model/factories/OverviewFactory.ts index f332500fd..25f61dfcf 100644 --- a/report-viewer/src/model/factories/OverviewFactory.ts +++ b/report-viewer/src/model/factories/OverviewFactory.ts @@ -30,7 +30,7 @@ export class OverviewFactory extends BaseFactory { OverviewFactory.compareVersions(jplagVersion, this.reportViewerVersion) const submissionFolder = json.submission_folder_path as Array - const baseCodeFolder = '' + const baseCodeFolder = json.base_code_folder_path as string const language = json.language as string const fileExtensions = json.file_extensions as Array const matchSensitivity = json.match_sensitivity as number From 98332bb1318578f6607f3218e6f7f7a10686429b Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Thu, 13 Jul 2023 18:20:36 +0200 Subject: [PATCH 04/19] redo ComparisonFactory --- .../src/model/factories/ComparisonFactory.ts | 127 ++++++------------ 1 file changed, 44 insertions(+), 83 deletions(-) diff --git a/report-viewer/src/model/factories/ComparisonFactory.ts b/report-viewer/src/model/factories/ComparisonFactory.ts index a4eac9e5d..8a833b4f9 100644 --- a/report-viewer/src/model/factories/ComparisonFactory.ts +++ b/report-viewer/src/model/factories/ComparisonFactory.ts @@ -5,50 +5,19 @@ import type { MatchInSingleFile } from '../MatchInSingleFile' import store from '@/stores/store' import { generateColors } from '@/utils/ColorUtils' import slash from 'slash' +import { BaseFactory } from './BaseFactory' /** * Factory class for creating Comparison objects */ -export class ComparisonFactory { +export class ComparisonFactory extends BaseFactory { public static getComparison(id1: string, id2: string) { - console.log('Generating comparison {%s} - {%s}...', id1, id2) - let comparison = new Comparison('', '', 0) - - //getting the comparison file based on the used mode (zip, local, single) - if (store().state.localModeUsed) { - const request = new XMLHttpRequest() - request.open('GET', `/files/${store().getComparisonFileName(id1, id2)}`, false) - request.send() - - if (request.status == 200) { - ComparisonFactory.loadSubmissionFilesFromLocal(id1) - ComparisonFactory.loadSubmissionFilesFromLocal(id2) - try { - comparison = ComparisonFactory.extractComparison(JSON.parse(request.response)) - } catch (e) { - throw new Error('Comparison file not found') - } - } else { - throw new Error('Comparison file not found') - } - } else if (store().state.zipModeUsed) { - const comparisonFile = store().getComparisonFileForSubmissions(id1, id2) - if (comparisonFile) { - comparison = ComparisonFactory.extractComparison(JSON.parse(comparisonFile)) - } else { - throw new Error('Comparison file not found') - } - } else if (store().state.singleModeUsed) { - try { - comparison = ComparisonFactory.extractComparison( - JSON.parse(store().state.singleFillRawContent) - ) - } catch (e) { - store().clearStore() - throw new Error('No Comparison files given!') - } + const filePath = store().getComparisonFileName(id1, id2) + if (!filePath) { + throw new Error('Comparison file not specified') } - return comparison + + return this.extractComparison(JSON.parse(this.getFile(filePath))) } /** @@ -56,11 +25,17 @@ export class ComparisonFactory { * @param json the json object */ private static extractComparison(json: Record): Comparison { - const filesOfFirstSubmission = store().filesOfSubmission(json.id1 as string) - const filesOfSecondSubmission = store().filesOfSubmission(json.id2 as string) + const firstSubmissionId = json.id1 as string + const secondSubmissionId = json.id2 as string + if (store().state.localModeUsed) { + this.loadSubmissionFilesFromLocal(firstSubmissionId) + this.loadSubmissionFilesFromLocal(json.id2 as string) + } + const filesOfFirstSubmission = store().filesOfSubmission(firstSubmissionId) + const filesOfSecondSubmission = store().filesOfSubmission(secondSubmissionId) - const filesOfFirstConverted = this.convertToFilesByName(filesOfFirstSubmission) - const filesOfSecondConverted = this.convertToFilesByName(filesOfSecondSubmission) + const filesOfFirstConverted = this.convertToSubmissionFileList(filesOfFirstSubmission) + const filesOfSecondConverted = this.convertToSubmissionFileList(filesOfSecondSubmission) const matches = json.matches as Array> @@ -70,25 +45,22 @@ export class ComparisonFactory { const colors = generateColors(matches.length, matchSaturation, matchLightness, matchAlpha) const coloredMatches = matches.map((match, index) => this.mapMatch(match, colors[index])) - const matchesInFirst = this.groupMatchesByFileName(coloredMatches, 1) - const matchesInSecond = this.groupMatchesByFileName(coloredMatches, 2) - const comparison = new Comparison( - json.id1 as string, - json.id2 as string, + firstSubmissionId, + secondSubmissionId, json.similarity as number ) comparison.filesOfFirstSubmission = filesOfFirstConverted comparison.filesOfSecondSubmission = filesOfSecondConverted comparison.colors = colors comparison.allMatches = coloredMatches - comparison.matchesInFirstSubmission = matchesInFirst - comparison.matchesInSecondSubmissions = matchesInSecond + comparison.matchesInFirstSubmission = this.groupMatchesByFileName(coloredMatches, 1) + comparison.matchesInSecondSubmissions = this.groupMatchesByFileName(coloredMatches, 2) return comparison } - private static convertToFilesByName( + private static convertToSubmissionFileList( files: Array<{ name: string; value: string }> ): Map { const map = new Map() @@ -109,11 +81,11 @@ export class ComparisonFactory { private static groupMatchesByFileName( matches: Array, - index: number + index: 1 | 2 ): Map> { const acc = new Map>() matches.forEach((val) => { - const name = index === 1 ? (val.firstFile as string) : (val.secondFile as string) + const name = index === 1 ? val.firstFile : val.secondFile if (!acc.get(name)) { acc.set(name, []) @@ -121,22 +93,22 @@ export class ComparisonFactory { if (index === 1) { const newVal: MatchInSingleFile = { - start: val.startInFirst as number, - end: val.endInFirst as number, + start: val.startInFirst, + end: val.endInFirst, linked_panel: 2, - linked_file: val.secondFile as string, - linked_line: val.startInSecond as number, - color: val.color as string + linked_file: val.secondFile, + linked_line: val.startInSecond, + color: val.color } acc.get(name)?.push(newVal) } else { const newVal: MatchInSingleFile = { - start: val.startInSecond as number, - end: val.endInSecond as number, + start: val.startInSecond, + end: val.endInSecond, linked_panel: 1, - linked_file: val.firstFile as string, - linked_line: val.startInFirst as number, - color: val.color as string + linked_file: val.firstFile, + linked_line: val.startInFirst, + color: val.color } acc.get(name)?.push(newVal) } @@ -145,36 +117,25 @@ export class ComparisonFactory { } private static getSubmissionFileListFromLocal(submissionId: string): string[] { - const request = new XMLHttpRequest() - request.open('GET', `/files/submissionFileIndex.json`, false) - request.send() - if (request.status == 200) { - return JSON.parse(request.response).submission_file_indexes[submissionId].map( - (file: string) => slash(file) - ) - } else { - return [] - } + return JSON.parse(this.getLocalFile(`submissionFileIndex.json`)).submission_file_indexes[ + submissionId + ].map((file: string) => slash(file)) } private static loadSubmissionFilesFromLocal(submissionId: string) { - const request = new XMLHttpRequest() - const fileList = ComparisonFactory.getSubmissionFileListFromLocal(submissionId) - for (const file of fileList) { - request.open('GET', `/files/files/${file}`, false) - request.overrideMimeType('text/plain') - request.send() - if (request.status == 200) { + try { + const fileList = this.getSubmissionFileListFromLocal(submissionId) + for (const filePath of fileList) { store().saveSubmissionFile({ name: submissionId, file: { - fileName: slash(file), - data: request.response + fileName: slash(filePath), + data: this.getLocalFile(`files/${filePath}`) } }) - } else { - console.log('Error loading file: ' + file) } + } catch (e) { + console.log(e) } } From 2e1429794031fbfee2becd6d9b6173f0b41bb3f6 Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Thu, 13 Jul 2023 19:30:14 +0200 Subject: [PATCH 05/19] apply spotless --- .../reporting/reportobject/mapper/MetricMapper.java | 7 +++---- .../reportobject/mapper/MetricMapperTest.java | 10 ++++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java b/core/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java index c508147ba..bdad683a0 100644 --- a/core/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java +++ b/core/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java @@ -29,8 +29,8 @@ public MetricMapper(Function submissionToIdFunction) { * @return Map with key as name of metric and value as distribution */ public static Map> getDistributions(JPlagResult result) { - return Map.of(SimilarityMetric.AVG.name(), convertDistribution(result.getSimilarityDistribution()), - SimilarityMetric.MAX.name(), convertDistribution(result.getMaxSimilarityDistribution())); + return Map.of(SimilarityMetric.AVG.name(), convertDistribution(result.getSimilarityDistribution()), SimilarityMetric.MAX.name(), + convertDistribution(result.getMaxSimilarityDistribution())); } /** @@ -46,8 +46,7 @@ public List getTopComparisons(JPlagResult result) { } private Map getComparisonMetricMap(JPlagComparison comparison) { - return Map.of(SimilarityMetric.AVG.name(), comparison.similarity(), - SimilarityMetric.MAX.name(), comparison.maximalSimilarity()); + return Map.of(SimilarityMetric.AVG.name(), comparison.similarity(), SimilarityMetric.MAX.name(), comparison.maximalSimilarity()); } private static List convertDistribution(int[] array) { diff --git a/core/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java b/core/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java index 3f4942d65..4a3667896 100644 --- a/core/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java +++ b/core/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java @@ -32,7 +32,7 @@ public void test_getDistributions() { // when Map> result = MetricMapper.getDistributions(jPlagResult); - //then + // then Assertions.assertEquals(Map.of("AVG", EXPECTED_AVG_DISTRIBUTION, "MAX", EXPECTED_MAX_DISTRIBUTION), result); } @@ -46,10 +46,9 @@ public void test_getTopComparisons() { List result = metricMapper.getTopComparisons(jPlagResult); // then - Assertions.assertEquals(List.of( - new TopComparison("1", "2", Map.of("AVG", .7, "MAX", .8)), - new TopComparison("3", "4", Map.of("AVG", .3, "MAX", .9)) - ), result); + Assertions.assertEquals( + List.of(new TopComparison("1", "2", Map.of("AVG", .7, "MAX", .8)), new TopComparison("3", "4", Map.of("AVG", .3, "MAX", .9))), + result); } private int[] distribution(List expectedDistribution) { @@ -71,7 +70,6 @@ private JPlagResult createJPlagResult(int[] avgDistribution, int[] maxDistributi doReturn(avgDistribution).when(jPlagResult).getSimilarityDistribution(); doReturn(maxDistribution).when(jPlagResult).getMaxSimilarityDistribution(); - JPlagOptions options = mock(JPlagOptions.class); doReturn(createComparisonsDto.length).when(options).maximumNumberOfComparisons(); doReturn(options).when(jPlagResult).getOptions(); From 6b23104e59c362b679ed53787ec146759cda8e99 Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Sat, 22 Jul 2023 20:02:38 +0200 Subject: [PATCH 06/19] hundred value distribution and add old format again --- core/src/main/java/de/jplag/JPlagResult.java | 2 +- .../reportobject/mapper/MetricMapperTest.java | 10 ++- .../src/components/DistributionDiagram.vue | 8 +-- report-viewer/src/model/Distribution.ts | 11 +-- .../src/model/PercentileDistribution.ts | 18 +++++ .../src/model/TenthPercentileDistribution.ts | 11 +++ .../src/model/factories/OverviewFactory.ts | 71 ++++++++++++++++++- 7 files changed, 116 insertions(+), 15 deletions(-) create mode 100644 report-viewer/src/model/PercentileDistribution.ts create mode 100644 report-viewer/src/model/TenthPercentileDistribution.ts diff --git a/core/src/main/java/de/jplag/JPlagResult.java b/core/src/main/java/de/jplag/JPlagResult.java index 8b4d123d9..2b1aabd32 100644 --- a/core/src/main/java/de/jplag/JPlagResult.java +++ b/core/src/main/java/de/jplag/JPlagResult.java @@ -23,7 +23,7 @@ public class JPlagResult { private final int[] similarityDistribution; // 10-element array representing the similarity distribution of the detected matches. private List> clusteringResult; - private final int SIMILARITY_DISTRIBUTION_SIZE = 10; + private final int SIMILARITY_DISTRIBUTION_SIZE = 100; public JPlagResult(List comparisons, SubmissionSet submissions, long durationInMillis, JPlagOptions options) { // sort by similarity (descending) diff --git a/core/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java b/core/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java index 4a3667896..6419fda3b 100644 --- a/core/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java +++ b/core/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java @@ -19,8 +19,14 @@ import de.jplag.reporting.reportobject.model.TopComparison; public class MetricMapperTest { - private static final List EXPECTED_AVG_DISTRIBUTION = List.of(29, 23, 19, 17, 13, 11, 7, 5, 3, 2); - private static final List EXPECTED_MAX_DISTRIBUTION = List.of(50, 48, 20, 13, 10, 1, 3, 1, 0, 0); + private static final List EXPECTED_AVG_DISTRIBUTION = List.of(1, 0, 0, 2, 3, 15, 5, 2, 16, 5, 2, 18, 3, 21, 2, 1, 5, 0, 14, 32, 25, 4, 2, + 12, 3, 2, 5, 5, 0, 5, 1, 5, 2, 5, 4, 5, 3, 5, 18, 21, 30, 4, 3, 10, 2, 3, 17, 28, 4, 10, 2, 4, 3, 0, 2, 20, 4, 0, 19, 5, 25, 9, 4, 18, 1, + 1, 1, 0, 31, 15, 35, 38, 40, 43, 45, 49, 50, 50, 50, 53, 60, 71, 73, 74, 80, 83, 87, 93, 95, 99, 102, 105, 106, 110, 113, 113, 117, 117, + 122, 124); + private static final List EXPECTED_MAX_DISTRIBUTION = List.of(130, 129, 124, 116, 114, 110, 110, 108, 103, 101, 99, 97, 96, 92, 82, 81, + 70, 67, 64, 63, 59, 56, 52, 50, 50, 50, 49, 47, 43, 5, 6, 11, 4, 2, 3, 20, 37, 5, 0, 2, 33, 30, 19, 4, 5, 24, 40, 6, 3, 9, 2, 3, 18, 3, 5, + 1, 4, 1, 0, 0, 5, 5, 14, 5, 42, 4, 18, 0, 0, 10, 4, 3, 17, 33, 4, 4, 3, 4, 39, 0, 20, 2, 4, 9, 0, 5, 0, 8, 23, 4, 2, 39, 3, 4, 1, 0, 3, + 33, 2, 1); private final MetricMapper metricMapper = new MetricMapper(Submission::getName); @Test diff --git a/report-viewer/src/components/DistributionDiagram.vue b/report-viewer/src/components/DistributionDiagram.vue index e161b860a..55a0d82d4 100644 --- a/report-viewer/src/components/DistributionDiagram.vue +++ b/report-viewer/src/components/DistributionDiagram.vue @@ -23,7 +23,7 @@ const props = defineProps({ } }) -const maxVal = ref(Math.max(...props.distribution.distribution)) +const maxVal = ref(Math.max(...props.distribution.getTenthPercentileFormattedValues())) const labels = [ '91-100%', '81-90%', @@ -51,7 +51,7 @@ const chartData = ref({ datasets: [ { ...dataSetStyle.value, - data: props.distribution.distribution + data: props.distribution.getTenthPercentileFormattedValues() } ] }) @@ -109,12 +109,12 @@ watch( datasets: [ { ...dataSetStyle.value, - data: val.distribution + data: val.getTenthPercentileFormattedValues() } ] } - maxVal.value = Math.max(...val.distribution) + maxVal.value = Math.max(...val.getTenthPercentileFormattedValues()) options.value.scales.x.suggestedMax = maxVal.value + 5 } ) diff --git a/report-viewer/src/model/Distribution.ts b/report-viewer/src/model/Distribution.ts index d05544b53..fdfb87015 100644 --- a/report-viewer/src/model/Distribution.ts +++ b/report-viewer/src/model/Distribution.ts @@ -1,11 +1,12 @@ -export default class Distribution { - private readonly _distribution: Array +export default abstract class Distribution { + protected readonly _distribution: Array constructor(distribution: Array) { this._distribution = distribution } - get distribution(): Array { - return this._distribution - } + /** + * Returns the distribution summed at every tenth percentile + */ + public abstract getTenthPercentileFormattedValues(): Array } diff --git a/report-viewer/src/model/PercentileDistribution.ts b/report-viewer/src/model/PercentileDistribution.ts new file mode 100644 index 000000000..f28086802 --- /dev/null +++ b/report-viewer/src/model/PercentileDistribution.ts @@ -0,0 +1,18 @@ +import Distribution from './Distribution' + +export default class HundredValueDistribution extends Distribution { + constructor(distribution: number[]) { + super(distribution) + } + + /** + * Returns the distribution summed at every tenth percentile + */ + public getTenthPercentileFormattedValues(): number[] { + const tenValueArray = new Array(10).fill(0) + for (let i = 0; i < 100; i++) { + tenValueArray[Math.floor(i / 10)] += this._distribution[i] + } + return tenValueArray + } +} diff --git a/report-viewer/src/model/TenthPercentileDistribution.ts b/report-viewer/src/model/TenthPercentileDistribution.ts new file mode 100644 index 000000000..85282d247 --- /dev/null +++ b/report-viewer/src/model/TenthPercentileDistribution.ts @@ -0,0 +1,11 @@ +import Distribution from './Distribution' + +export default class TenValueDistribution extends Distribution { + constructor(distribution: number[]) { + super(distribution) + } + + public getTenthPercentileFormattedValues(): number[] { + return this._distribution + } +} diff --git a/report-viewer/src/model/factories/OverviewFactory.ts b/report-viewer/src/model/factories/OverviewFactory.ts index 25f61dfcf..cdf957e53 100644 --- a/report-viewer/src/model/factories/OverviewFactory.ts +++ b/report-viewer/src/model/factories/OverviewFactory.ts @@ -7,6 +7,8 @@ import versionJson from '@/version.json' import Distribution from '../Distribution' import MetricType from '../MetricType' import { BaseFactory } from './BaseFactory' +import PercentileDistribution from '../PercentileDistribution' +import TenValueDistribution from '../TenthPercentileDistribution' export class OverviewFactory extends BaseFactory { static reportViewerVersion: Version = @@ -68,18 +70,47 @@ export class OverviewFactory extends BaseFactory { private static extractDistributions( json: Record ): Record { - const distributionsMap = json.distributions as Record> + if (json.distributions) { + return this.extractDistributionsFromMap(json.distributions as Record>) + } else if (json.metrics) { + return this.extractDistributionsFromMetrics(json.metrics as Array>) + } + throw new Error('No distributions found') + } + + private static extractDistributionsFromMap( + distributionsMap: Record> + ): Record { const distributions = {} as Record for (const [key, value] of Object.entries(distributionsMap)) { - distributions[key as MetricType] = new Distribution(value as Array) + distributions[key as MetricType] = new PercentileDistribution(value as Array) } return distributions } + private static extractDistributionsFromMetrics( + metrics: Array> + ): Record { + return { + [MetricType.AVERAGE]: new TenValueDistribution(metrics[0].distribution as Array), + [MetricType.MAXIMUM]: new TenValueDistribution(metrics[1].distribution as Array) + } + } + private static extractTopComparisons( json: Record ): Array { - const jsonComparisons = json.top_comparisons as Array> + if (json.top_comparisons) { + return this.extractTopComparisonsFromMap( + json.top_comparisons as Array> + ) + } else if (json.metrics) { + return this.extractTopComparisonsFromMetrics(json.metrics as Array>) + } + throw new Error('No top comparisons found') + } + + private static extractTopComparisonsFromMap(jsonComparisons: Array>) { const comparisons = [] as Array let counter = 0 for (const topComparison of jsonComparisons) { @@ -93,6 +124,40 @@ export class OverviewFactory extends BaseFactory { } return comparisons } + + private static extractTopComparisonsFromMetrics(metrics: Array>) { + const averageSimilarities: Map = new Map() + const comparisons = [] as Array + + // Average + for (const comparison of metrics[0].topComparisons as Array>) { + averageSimilarities.set( + (comparison.first_submission as string) + '-' + (comparison.second_submission as string), + comparison.similarity as number + ) + } + + // Max + let counter = 0 + for (const comparison of metrics[1].topComparisons as Array>) { + const avg = averageSimilarities.get( + (comparison.first_submission as string) + '-' + (comparison.second_submission as string) + ) + comparisons.push({ + sortingPlace: counter++, + id: counter, + firstSubmissionId: comparison.first_submission as string, + secondSubmissionId: comparison.second_submission as string, + similarities: { + [MetricType.AVERAGE]: avg as number, + [MetricType.MAXIMUM]: comparison.similarity as number + } + }) + } + + return comparisons + } + private static extractClusters(json: Record): Array { if (!json.clusters) { return [] From 2bddcab56f3c95d7793a6f2f3170379eb8aa982f Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Sun, 23 Jul 2023 10:44:43 +0200 Subject: [PATCH 07/19] fix tests --- core/src/test/java/de/jplag/BaseCodeTest.java | 4 ++-- core/src/test/java/de/jplag/BasicFunctionalityTest.java | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/test/java/de/jplag/BaseCodeTest.java b/core/src/test/java/de/jplag/BaseCodeTest.java index bd307dc43..193482b8b 100644 --- a/core/src/test/java/de/jplag/BaseCodeTest.java +++ b/core/src/test/java/de/jplag/BaseCodeTest.java @@ -46,7 +46,7 @@ protected void verifyResults(JPlagResult result) { assertEquals(2, result.getNumberOfSubmissions()); assertEquals(1, result.getAllComparisons().size()); assertEquals(1, result.getAllComparisons().get(0).matches().size()); - assertEquals(1, result.getSimilarityDistribution()[8]); + assertEquals(1, result.getSimilarityDistribution()[81]); assertEquals(0.8125, result.getAllComparisons().get(0).similarity(), DELTA); } @@ -94,7 +94,7 @@ protected void verifySimpleSubdirectoryDuplicate(JPlagResult result, int submiss assertEquals(submissions, result.getNumberOfSubmissions()); assertEquals(comparisons, result.getAllComparisons().size()); assertEquals(1, result.getAllComparisons().get(0).matches().size()); - assertEquals(1, result.getSimilarityDistribution()[9]); + assertEquals(1, result.getSimilarityDistribution()[94]); assertEquals(0.9473, result.getAllComparisons().get(0).similarity(), DELTA); } diff --git a/core/src/test/java/de/jplag/BasicFunctionalityTest.java b/core/src/test/java/de/jplag/BasicFunctionalityTest.java index 4bf4bf347..3c993644d 100644 --- a/core/src/test/java/de/jplag/BasicFunctionalityTest.java +++ b/core/src/test/java/de/jplag/BasicFunctionalityTest.java @@ -22,14 +22,15 @@ void testSimpleDuplicate() throws ExitException { assertEquals(2, result.getNumberOfSubmissions()); assertEquals(1, result.getAllComparisons().size()); assertEquals(1, result.getAllComparisons().get(0).matches().size()); - assertEquals(1, result.getSimilarityDistribution()[6]); + assertEquals(1, result.getSimilarityDistribution()[66]); assertEquals(0.666, result.getAllComparisons().get(0).similarity(), DELTA); } @Test @DisplayName("test submissions with a custom minimum token match") void testWithMinTokenMatch() throws ExitException { - var expectedDistribution = new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + var expectedDistribution = new int[100]; + expectedDistribution[96] = 1; JPlagResult result = runJPlag("SimpleDuplicate", it -> it.withMinimumTokenMatch(4)); assertEquals(2, result.getNumberOfSubmissions()); @@ -90,7 +91,7 @@ void testSingleFileSubmisssions() throws ExitException { assertEquals(2, result.getNumberOfSubmissions()); assertEquals(1, result.getAllComparisons().size()); - assertEquals(1, result.getSimilarityDistribution()[6]); + assertEquals(1, result.getSimilarityDistribution()[66]); assertEquals(0.666, result.getAllComparisons().get(0).similarity(), DELTA); var matches = result.getAllComparisons().get(0).matches(); From a707bced32db07b4814d46903ee3d5ea73764498 Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Tue, 25 Jul 2023 17:22:58 +0200 Subject: [PATCH 08/19] rename --- report-viewer/src/components/DistributionDiagram.vue | 8 ++++---- report-viewer/src/model/Distribution.ts | 6 +++--- report-viewer/src/model/PercentileDistribution.ts | 2 +- report-viewer/src/model/TenthPercentileDistribution.ts | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/report-viewer/src/components/DistributionDiagram.vue b/report-viewer/src/components/DistributionDiagram.vue index 55a0d82d4..3cc12413c 100644 --- a/report-viewer/src/components/DistributionDiagram.vue +++ b/report-viewer/src/components/DistributionDiagram.vue @@ -23,7 +23,7 @@ const props = defineProps({ } }) -const maxVal = ref(Math.max(...props.distribution.getTenthPercentileFormattedValues())) +const maxVal = ref(Math.max(...props.distribution.splitIntoTenBuckets())) const labels = [ '91-100%', '81-90%', @@ -51,7 +51,7 @@ const chartData = ref({ datasets: [ { ...dataSetStyle.value, - data: props.distribution.getTenthPercentileFormattedValues() + data: props.distribution.splitIntoTenBuckets() } ] }) @@ -109,12 +109,12 @@ watch( datasets: [ { ...dataSetStyle.value, - data: val.getTenthPercentileFormattedValues() + data: val.splitIntoTenBuckets() } ] } - maxVal.value = Math.max(...val.getTenthPercentileFormattedValues()) + maxVal.value = Math.max(...val.splitIntoTenBuckets()) options.value.scales.x.suggestedMax = maxVal.value + 5 } ) diff --git a/report-viewer/src/model/Distribution.ts b/report-viewer/src/model/Distribution.ts index fdfb87015..622f334a5 100644 --- a/report-viewer/src/model/Distribution.ts +++ b/report-viewer/src/model/Distribution.ts @@ -1,12 +1,12 @@ export default abstract class Distribution { - protected readonly _distribution: Array + protected readonly _distribution: number[] - constructor(distribution: Array) { + constructor(distribution: number[]) { this._distribution = distribution } /** * Returns the distribution summed at every tenth percentile */ - public abstract getTenthPercentileFormattedValues(): Array + public abstract splitIntoTenBuckets(): number[] } diff --git a/report-viewer/src/model/PercentileDistribution.ts b/report-viewer/src/model/PercentileDistribution.ts index f28086802..bdfb70274 100644 --- a/report-viewer/src/model/PercentileDistribution.ts +++ b/report-viewer/src/model/PercentileDistribution.ts @@ -8,7 +8,7 @@ export default class HundredValueDistribution extends Distribution { /** * Returns the distribution summed at every tenth percentile */ - public getTenthPercentileFormattedValues(): number[] { + public splitIntoTenBuckets(): number[] { const tenValueArray = new Array(10).fill(0) for (let i = 0; i < 100; i++) { tenValueArray[Math.floor(i / 10)] += this._distribution[i] diff --git a/report-viewer/src/model/TenthPercentileDistribution.ts b/report-viewer/src/model/TenthPercentileDistribution.ts index 85282d247..c0305acc9 100644 --- a/report-viewer/src/model/TenthPercentileDistribution.ts +++ b/report-viewer/src/model/TenthPercentileDistribution.ts @@ -5,7 +5,7 @@ export default class TenValueDistribution extends Distribution { super(distribution) } - public getTenthPercentileFormattedValues(): number[] { + public splitIntoTenBuckets(): number[] { return this._distribution } } From 1435002ddde2eb657cb646ccee912f145d52abb2 Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Tue, 25 Jul 2023 19:00:59 +0200 Subject: [PATCH 09/19] rename --- .../{PercentileDistribution.ts => HundredValueDistribution.ts} | 0 .../{TenthPercentileDistribution.ts => TenValueDistribution.ts} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename report-viewer/src/model/{PercentileDistribution.ts => HundredValueDistribution.ts} (100%) rename report-viewer/src/model/{TenthPercentileDistribution.ts => TenValueDistribution.ts} (100%) diff --git a/report-viewer/src/model/PercentileDistribution.ts b/report-viewer/src/model/HundredValueDistribution.ts similarity index 100% rename from report-viewer/src/model/PercentileDistribution.ts rename to report-viewer/src/model/HundredValueDistribution.ts diff --git a/report-viewer/src/model/TenthPercentileDistribution.ts b/report-viewer/src/model/TenValueDistribution.ts similarity index 100% rename from report-viewer/src/model/TenthPercentileDistribution.ts rename to report-viewer/src/model/TenValueDistribution.ts From 153423d0ab333f6980166d5e948ce0d25658262f Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Tue, 25 Jul 2023 19:11:35 +0200 Subject: [PATCH 10/19] overview factory tests --- .../src/model/factories/OverviewFactory.ts | 4 +- .../tests/unit/model/OverviewFactory.test.ts | 178 ++++++++++++++++++ .../tests/unit/model/ValidNewOverview.json | 79 ++++++++ .../tests/unit/model/ValidOldOverview.json | 56 ++++++ report-viewer/vitest.config.ts | 5 + 5 files changed, 320 insertions(+), 2 deletions(-) create mode 100644 report-viewer/tests/unit/model/OverviewFactory.test.ts create mode 100644 report-viewer/tests/unit/model/ValidNewOverview.json create mode 100644 report-viewer/tests/unit/model/ValidOldOverview.json diff --git a/report-viewer/src/model/factories/OverviewFactory.ts b/report-viewer/src/model/factories/OverviewFactory.ts index cdf957e53..9b1801b46 100644 --- a/report-viewer/src/model/factories/OverviewFactory.ts +++ b/report-viewer/src/model/factories/OverviewFactory.ts @@ -7,8 +7,8 @@ import versionJson from '@/version.json' import Distribution from '../Distribution' import MetricType from '../MetricType' import { BaseFactory } from './BaseFactory' -import PercentileDistribution from '../PercentileDistribution' -import TenValueDistribution from '../TenthPercentileDistribution' +import PercentileDistribution from '../HundredValueDistribution' +import TenValueDistribution from '../TenValueDistribution' export class OverviewFactory extends BaseFactory { static reportViewerVersion: Version = diff --git a/report-viewer/tests/unit/model/OverviewFactory.test.ts b/report-viewer/tests/unit/model/OverviewFactory.test.ts new file mode 100644 index 000000000..803abe82f --- /dev/null +++ b/report-viewer/tests/unit/model/OverviewFactory.test.ts @@ -0,0 +1,178 @@ +import { beforeAll, describe, expect, it, vi } from 'vitest' +import { OverviewFactory } from '@/model/factories/OverviewFactory' +import MetricType from '@/model/MetricType' +import HundredValueDistribution from '@/model/HundredValueDistribution' +import TenValueDistribution from '@/model/TenValueDistribution' +import validNew from './ValidNewOverview.json' +import validOld from './ValidOldOverview.json' + +const store = { + state: { + localModeUsed: false, + zipModeUsed: true, + singleModeUsed: false, + files: {} + }, + saveSubmissionNames: (map) => { + expect(map.has('A')).toBeTruthy() + expect(map.has('B')).toBeTruthy() + expect(map.has('C')).toBeTruthy() + expect(map.has('D')).toBeTruthy() + }, + saveComparisonFileLookup: (map) => { + console.log(map) + expect(map.has('A')).toBeTruthy() + expect(map.has('B')).toBeTruthy() + } +} + +describe('Test JSON to Overview', () => { + beforeAll(() => { + vi.mock('@/stores/store', () => ({ + default: vi.fn(() => { + return store + }) + })) + + vi.spyOn(global.window, 'alert').mockImplementation(() => {}) + }) + + it('Post 5.0', () => { + store.state.files['overview.json'] = JSON.stringify(validNew) + + expect(OverviewFactory.getOverview()).toEqual({ + _submissionFolderPath: ['files'], + _baseCodeFolderPath: '', + _language: 'Javac based AST plugin', + _fileExtensions: ['.java', '.JAVA'], + _matchSensitivity: 9, + _dateOfExecution: '12/07/23', + _durationOfExecution: 12, + _topComparisons: [ + { + firstSubmissionId: 'A', + secondSubmissionId: 'C', + similarities: { + [MetricType.AVERAGE]: 0.9960435212660732, + [MetricType.MAXIMUM]: 0.9960435212660732 + }, + sortingPlace: 0, + id: 1 + }, + { + firstSubmissionId: 'D', + secondSubmissionId: 'A', + similarities: { + [MetricType.AVERAGE]: 0.751044776119403, + [MetricType.MAXIMUM]: 0.947289156626506 + }, + sortingPlace: 1, + id: 2 + }, + { + firstSubmissionId: 'D', + secondSubmissionId: 'C', + similarities: { + [MetricType.AVERAGE]: 0.751044776119403, + [MetricType.MAXIMUM]: 0.947289156626506 + }, + sortingPlace: 2, + id: 3 + }, + { + firstSubmissionId: 'B', + secondSubmissionId: 'D', + similarities: { + [MetricType.AVERAGE]: 0.28322981366459626, + [MetricType.MAXIMUM]: 0.8085106382978723 + }, + sortingPlace: 3, + id: 4 + }, + { + firstSubmissionId: 'B', + secondSubmissionId: 'A', + similarities: { + [MetricType.AVERAGE]: 0.2378472222222222, + [MetricType.MAXIMUM]: 0.9716312056737588 + }, + sortingPlace: 4, + id: 5 + }, + { + firstSubmissionId: 'B', + secondSubmissionId: 'C', + similarities: { + [MetricType.AVERAGE]: 0.2378472222222222, + [MetricType.MAXIMUM]: 0.9716312056737588 + }, + sortingPlace: 5, + id: 6 + } + ], + _distributions: { + [MetricType.MAXIMUM]: new HundredValueDistribution([ + 1, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]), + [MetricType.AVERAGE]: new HundredValueDistribution([ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]) + }, + _clusters: [ + { + averageSimilarity: 94.746956, + strength: 0.0, + members: ['C', 'A', 'B', 'D'] + } + ], + _totalComparisons: 6 + }) + }) + + it('Pre 5.0', () => { + store.state.files['overview.json'] = JSON.stringify(validOld) + expect(OverviewFactory.getOverview()).toEqual({ + _submissionFolderPath: ['test'], + _baseCodeFolderPath: '', + _language: 'Javac based AST plugin', + _fileExtensions: ['.java', '.JAVA'], + _matchSensitivity: 9, + _dateOfExecution: '12/07/23', + _durationOfExecution: 34, + _topComparisons: [ + { + firstSubmissionId: 'A', + secondSubmissionId: 'B', + similarities: { + [MetricType.AVERAGE]: 0.6900452488687783, + [MetricType.MAXIMUM]: 0.9457364341085271 + }, + sortingPlace: 0, + id: 1 + }, + { + firstSubmissionId: 'C', + secondSubmissionId: 'D', + similarities: { + [MetricType.AVERAGE]: 0.6954045248868778, + [MetricType.MAXIMUM]: 0.83500530023 + }, + sortingPlace: 1, + id: 2 + } + ], + _distributions: { + [MetricType.AVERAGE]: new TenValueDistribution([0, 0, 0, 1, 0, 0, 0, 0, 0, 0]), + [MetricType.MAXIMUM]: new TenValueDistribution([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + }, + _clusters: [], + _totalComparisons: 6 + }) + }) +}) diff --git a/report-viewer/tests/unit/model/ValidNewOverview.json b/report-viewer/tests/unit/model/ValidNewOverview.json new file mode 100644 index 000000000..8fb36cc51 --- /dev/null +++ b/report-viewer/tests/unit/model/ValidNewOverview.json @@ -0,0 +1,79 @@ +{ + "jplag_version": { "major": 0, "minor": 0, "patch": 0 }, + "submission_folder_path": ["files"], + "base_code_folder_path": "", + "language": "Javac based AST plugin", + "file_extensions": [".java", ".JAVA"], + "submission_id_to_display_name": { "A": "A", "B": "B", "C": "C", "D": "D" }, + "submission_ids_to_comparison_file_name": { + "A": { "B": "B-A.json", "C": "A-C.json", "D": "D-A.json" }, + "B": { "A": "B-A.json", "C": "B-C.json", "D": "B-D.json" }, + "C": { "A": "A-C.json", "B": "B-C.json", "D": "D-C.json" }, + "D": { "A": "D-A.json", "B": "B-D.json", "C": "D-C.json" } + }, + "failed_submission_names": [], + "excluded_files": [], + "match_sensitivity": 9, + "date_of_execution": "12/07/23", + "execution_time": 12, + "distributions": { + "MAX": [ + 1, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ], + "AVG": [ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "top_comparisons": [ + { + "first_submission": "A", + "second_submission": "C", + "similarities": { "AVG": 0.9960435212660732, "MAX": 0.9960435212660732 } + }, + { + "first_submission": "D", + "second_submission": "A", + "similarities": { "AVG": 0.751044776119403, "MAX": 0.947289156626506 } + }, + { + "first_submission": "D", + "second_submission": "C", + "similarities": { "AVG": 0.751044776119403, "MAX": 0.947289156626506 } + }, + { + "first_submission": "B", + "second_submission": "D", + "similarities": { "AVG": 0.28322981366459626, "MAX": 0.8085106382978723 } + }, + { + "first_submission": "B", + "second_submission": "A", + "similarities": { "AVG": 0.2378472222222222, "MAX": 0.9716312056737588 } + }, + { + "first_submission": "B", + "second_submission": "C", + "similarities": { "AVG": 0.2378472222222222, "MAX": 0.9716312056737588 } + } + ], + "clusters": [ + { + "average_similarity": 94.746956, + "strength": 0.0, + "members": [ + "C", + "A", + "B", + "D" + ] + } + ], + "total_comparisons": 6 + } + \ No newline at end of file diff --git a/report-viewer/tests/unit/model/ValidOldOverview.json b/report-viewer/tests/unit/model/ValidOldOverview.json new file mode 100644 index 000000000..981fca99f --- /dev/null +++ b/report-viewer/tests/unit/model/ValidOldOverview.json @@ -0,0 +1,56 @@ +{ + "jplag_version": { "major": 4, "minor": 2, "patch": 0 }, + "submission_folder_path": ["test"], + "base_code_folder_path": "", + "language": "Javac based AST plugin", + "file_extensions": [".java", ".JAVA"], + "submission_id_to_display_name": { "A": "A", "B": "B", "C": "C", "D": "D" }, + "submission_ids_to_comparison_file_name": { + "A": { "B": "B-A.json" }, + "B": { "A": "B-A.json" } + }, + "failed_submission_names": [], + "excluded_files": [], + "match_sensitivity": 9, + "date_of_execution": "12/07/23", + "execution_time": 34, + "metrics": [ + { + "name": "AVG", + "distribution": [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], + "topComparisons": [ + { + "first_submission": "A", + "second_submission": "B", + "similarity": 0.6900452488687783 + }, + { + "first_submission": "C", + "second_submission": "D", + "similarity": 0.6954045248868778 + } + ], + "description": "Average of both program coverages. This is the default similarity which works in most cases: Matches with a high average similarity indicate that the programs work in a very similar way." + }, + { + "name": "MAX", + "distribution": [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "topComparisons": [ + { + "first_submission": "A", + "second_submission": "B", + "similarity": 0.9457364341085271 + }, + { + "first_submission": "C", + "second_submission": "D", + "similarity": 0.83500530023 + } + ], + "description": "Maximum of both program coverages. This ranking is especially useful if the programs are very different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program." + } + ], + "clusters": [], + "total_comparisons": 6 + } + \ No newline at end of file diff --git a/report-viewer/vitest.config.ts b/report-viewer/vitest.config.ts index 23da12e5c..502cfa1a8 100644 --- a/report-viewer/vitest.config.ts +++ b/report-viewer/vitest.config.ts @@ -10,6 +10,11 @@ export default mergeConfig( environment: 'jsdom', exclude: [...configDefaults.exclude, 'tests/e2e/*'], root: fileURLToPath(new URL('./', import.meta.url)) + }, + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } } }) ) From 9a43fd1efed120124e7aa3c3d0dc6ecffac16744 Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Tue, 25 Jul 2023 21:43:06 +0200 Subject: [PATCH 11/19] fix distribution + test --- .../src/model/HundredValueDistribution.ts | 2 +- .../tests/unit/model/Distribution.test.ts | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 report-viewer/tests/unit/model/Distribution.test.ts diff --git a/report-viewer/src/model/HundredValueDistribution.ts b/report-viewer/src/model/HundredValueDistribution.ts index bdfb70274..7eee091f6 100644 --- a/report-viewer/src/model/HundredValueDistribution.ts +++ b/report-viewer/src/model/HundredValueDistribution.ts @@ -10,7 +10,7 @@ export default class HundredValueDistribution extends Distribution { */ public splitIntoTenBuckets(): number[] { const tenValueArray = new Array(10).fill(0) - for (let i = 0; i < 100; i++) { + for (let i = 99; i >= 0; i--) { tenValueArray[Math.floor(i / 10)] += this._distribution[i] } return tenValueArray diff --git a/report-viewer/tests/unit/model/Distribution.test.ts b/report-viewer/tests/unit/model/Distribution.test.ts new file mode 100644 index 000000000..1d8b6fa65 --- /dev/null +++ b/report-viewer/tests/unit/model/Distribution.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest' +import Distribution from '@/model/Distribution' +import TenValueDistribution from '@/model/TenValueDistribution' +import HundredValueDistribution from '@/model/HundredValueDistribution' + +describe('Distribution', () => { + it.each([ + new TenValueDistribution([0, 0, 0, 0, 0, 0, 26, 13209, 58955, 5231]), + new HundredValueDistribution([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 1, 0, 1, 2, 12, 8, 31, 61, 168, 273, 493, 923, 1544, 2244, 3163, 4309, 5373, 6343, 7177, + 7445, 7292, 7023, 6130, 5091, 4056, 3025, 2052, 1442, 869, 470, 225, 109, 42, 15, 7, 0 + ]) + ])('get in 10 Buckets', (distribution: Distribution) => { + expect(distribution.splitIntoTenBuckets()).toEqual([0, 0, 0, 0, 0, 0, 26, 13209, 58955, 5231]) + }) +}) From 7a443d1c17b3318b29f4cf666a121c6d1a07f535 Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Wed, 26 Jul 2023 15:42:36 +0200 Subject: [PATCH 12/19] move tests --- .../tests/unit/model/{ => factories}/OverviewFactory.test.ts | 1 - .../tests/unit/model/{ => factories}/ValidNewOverview.json | 0 .../tests/unit/model/{ => factories}/ValidOldOverview.json | 0 3 files changed, 1 deletion(-) rename report-viewer/tests/unit/model/{ => factories}/OverviewFactory.test.ts (99%) rename report-viewer/tests/unit/model/{ => factories}/ValidNewOverview.json (100%) rename report-viewer/tests/unit/model/{ => factories}/ValidOldOverview.json (100%) diff --git a/report-viewer/tests/unit/model/OverviewFactory.test.ts b/report-viewer/tests/unit/model/factories/OverviewFactory.test.ts similarity index 99% rename from report-viewer/tests/unit/model/OverviewFactory.test.ts rename to report-viewer/tests/unit/model/factories/OverviewFactory.test.ts index 803abe82f..5385d697a 100644 --- a/report-viewer/tests/unit/model/OverviewFactory.test.ts +++ b/report-viewer/tests/unit/model/factories/OverviewFactory.test.ts @@ -20,7 +20,6 @@ const store = { expect(map.has('D')).toBeTruthy() }, saveComparisonFileLookup: (map) => { - console.log(map) expect(map.has('A')).toBeTruthy() expect(map.has('B')).toBeTruthy() } diff --git a/report-viewer/tests/unit/model/ValidNewOverview.json b/report-viewer/tests/unit/model/factories/ValidNewOverview.json similarity index 100% rename from report-viewer/tests/unit/model/ValidNewOverview.json rename to report-viewer/tests/unit/model/factories/ValidNewOverview.json diff --git a/report-viewer/tests/unit/model/ValidOldOverview.json b/report-viewer/tests/unit/model/factories/ValidOldOverview.json similarity index 100% rename from report-viewer/tests/unit/model/ValidOldOverview.json rename to report-viewer/tests/unit/model/factories/ValidOldOverview.json From 96e153f07956ed652bb2c41b8524a81b1cf8cd1d Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Thu, 27 Jul 2023 18:56:32 +0200 Subject: [PATCH 13/19] overwork comparison --- .../jsonfactory/ComparisonReportWriter.java | 4 +- .../reportobject/model/ComparisonReport.java | 5 +- report-viewer/src/model/Comparison.ts | 103 +++++------------- .../src/model/factories/ComparisonFactory.ts | 46 ++++++-- report-viewer/src/views/ComparisonView.vue | 3 +- .../model/factories/ComparisonFactory.test.ts | 74 +++++++++++++ .../model/factories/ValidNewComparison.json | 48 ++++++++ .../model/factories/ValidOldComparison.json | 45 ++++++++ 8 files changed, 236 insertions(+), 92 deletions(-) create mode 100644 report-viewer/tests/unit/model/factories/ComparisonFactory.test.ts create mode 100644 report-viewer/tests/unit/model/factories/ValidNewComparison.json create mode 100644 report-viewer/tests/unit/model/factories/ValidOldComparison.json diff --git a/core/src/main/java/de/jplag/reporting/jsonfactory/ComparisonReportWriter.java b/core/src/main/java/de/jplag/reporting/jsonfactory/ComparisonReportWriter.java index 5b3594979..242d09138 100644 --- a/core/src/main/java/de/jplag/reporting/jsonfactory/ComparisonReportWriter.java +++ b/core/src/main/java/de/jplag/reporting/jsonfactory/ComparisonReportWriter.java @@ -12,6 +12,7 @@ import de.jplag.JPlagResult; import de.jplag.Submission; import de.jplag.Token; +import de.jplag.options.SimilarityMetric; import de.jplag.reporting.FilePathUtil; import de.jplag.reporting.reportobject.model.ComparisonReport; import de.jplag.reporting.reportobject.model.Match; @@ -55,7 +56,8 @@ private void writeComparisons(String path, List comparisons) { String secondSubmissionId = submissionToIdFunction.apply(comparison.secondSubmission()); String fileName = generateComparisonName(firstSubmissionId, secondSubmissionId); addToLookUp(firstSubmissionId, secondSubmissionId, fileName); - var comparisonReport = new ComparisonReport(firstSubmissionId, secondSubmissionId, comparison.similarity(), + var comparisonReport = new ComparisonReport(firstSubmissionId, secondSubmissionId, + Map.of(SimilarityMetric.AVG.name(), comparison.similarity(), SimilarityMetric.MAX.name(), comparison.maximalSimilarity()), convertMatchesToReportMatches(comparison)); fileWriter.saveAsJSON(comparisonReport, path, fileName); }); diff --git a/core/src/main/java/de/jplag/reporting/reportobject/model/ComparisonReport.java b/core/src/main/java/de/jplag/reporting/reportobject/model/ComparisonReport.java index 00b243fbd..5b8d2fc4b 100644 --- a/core/src/main/java/de/jplag/reporting/reportobject/model/ComparisonReport.java +++ b/core/src/main/java/de/jplag/reporting/reportobject/model/ComparisonReport.java @@ -1,6 +1,7 @@ package de.jplag.reporting.reportobject.model; import java.util.List; +import java.util.Map; import com.fasterxml.jackson.annotation.JsonProperty; @@ -8,10 +9,10 @@ * ReportViewer DTO for the comparison of two submissions. * @param firstSubmissionId id of the first submission * @param secondSubmissionId id of the second submission - * @param similarity average similarity. between 0.0 and 1.0. + * @param similarities map of metric names and corresponding similarities. between 0.0 and 1.0. * @param matches the list of matches found in the comparison of the two submissions */ public record ComparisonReport(@JsonProperty("id1") String firstSubmissionId, @JsonProperty("id2") String secondSubmissionId, - @JsonProperty("similarity") double similarity, @JsonProperty("matches") List matches) { + @JsonProperty("similarities") Map similarities, @JsonProperty("matches") List matches) { } diff --git a/report-viewer/src/model/Comparison.ts b/report-viewer/src/model/Comparison.ts index 53b9bc6ff..0cfb75cac 100644 --- a/report-viewer/src/model/Comparison.ts +++ b/report-viewer/src/model/Comparison.ts @@ -1,6 +1,7 @@ import type { Match } from './Match' import type { SubmissionFile } from './SubmissionFile' import type { MatchInSingleFile } from './MatchInSingleFile' +import type MetricType from './MetricType' /** * Comparison model used by the ComparisonView @@ -8,22 +9,33 @@ import type { MatchInSingleFile } from './MatchInSingleFile' export class Comparison { private readonly _firstSubmissionId: string private readonly _secondSubmissionId: string - private readonly _similarity: number + private readonly _similarities: Record + private _filesOfFirstSubmission: Map + private _filesOfSecondSubmission: Map + private _allMatches: Array + private _matchesInFirstSubmission: Map> + private _matchesInSecondSubmissions: Map> - constructor(firstSubmissionId: string, secondSubmissionId: string, similarity: number) { + constructor( + firstSubmissionId: string, + secondSubmissionId: string, + similarities: Record, + filesOfFirstSubmission: Map, + filesOfSecondSubmission: Map, + allMatches: Array, + matchesInFirstSubmission: Map>, + matchesInSecondSubmissions: Map> + ) { this._firstSubmissionId = firstSubmissionId this._secondSubmissionId = secondSubmissionId - this._similarity = similarity - this._filesOfFirstSubmission = new Map() - this._filesOfSecondSubmission = new Map() - this._colors = [] - this._allMatches = [] - this._matchesInFirstSubmission = new Map() - this._matchesInSecondSubmissions = new Map() + this._similarities = similarities + this._filesOfFirstSubmission = filesOfFirstSubmission + this._filesOfSecondSubmission = filesOfSecondSubmission + this._allMatches = allMatches + this._matchesInFirstSubmission = matchesInFirstSubmission + this._matchesInSecondSubmissions = matchesInSecondSubmissions } - private _filesOfFirstSubmission: Map - /** * @return Map of all files of the first submission */ @@ -31,16 +43,6 @@ export class Comparison { return this._filesOfFirstSubmission } - /** - * Set the files of the first submission - * @param value Map to set to - */ - set filesOfFirstSubmission(value: Map) { - this._filesOfFirstSubmission = value - } - - private _filesOfSecondSubmission: Map - /** * @return Map of all files of the second submission */ @@ -48,33 +50,6 @@ export class Comparison { return this._filesOfSecondSubmission } - /** - * Set the files of the second submission - * @param value Map to set to - */ - set filesOfSecondSubmission(value: Map) { - this._filesOfSecondSubmission = value - } - - private _colors: Array - - /** - * @return Array of all colors used to display the matches - */ - get colors(): Array { - return this._colors - } - - /** - * Set the colors used to display the matches - * @param value Colors to set to - */ - set colors(value: Array) { - this._colors = value - } - - private _allMatches: Array - /** * @return Array of all matches */ @@ -82,16 +57,6 @@ export class Comparison { return this._allMatches } - /** - * Set the array of all matches - * @param value Matches to set to - */ - set allMatches(value: Array) { - this._allMatches = value - } - - private _matchesInFirstSubmission: Map> - /** * @return Map of all matches in the first submission */ @@ -99,16 +64,6 @@ export class Comparison { return this._matchesInFirstSubmission } - /** - * Set the matches in the first submission - * @param value Matches in the first submission to set to - */ - set matchesInFirstSubmission(value: Map>) { - this._matchesInFirstSubmission = value - } - - private _matchesInSecondSubmissions: Map> - /** * @return Map of all matches in the second submission */ @@ -116,14 +71,6 @@ export class Comparison { return this._matchesInSecondSubmissions } - /** - * Set the matches in the second submission - * @param value Matches in the first submission to set to - */ - set matchesInSecondSubmissions(value: Map>) { - this._matchesInSecondSubmissions = value - } - /** * @return Id of the first submission */ @@ -141,7 +88,7 @@ export class Comparison { /** * @return Similarity of the two submissions */ - get similarity() { - return this._similarity + get similarities() { + return this._similarities } } diff --git a/report-viewer/src/model/factories/ComparisonFactory.ts b/report-viewer/src/model/factories/ComparisonFactory.ts index 8a833b4f9..a3ff4ca31 100644 --- a/report-viewer/src/model/factories/ComparisonFactory.ts +++ b/report-viewer/src/model/factories/ComparisonFactory.ts @@ -6,6 +6,7 @@ import store from '@/stores/store' import { generateColors } from '@/utils/ColorUtils' import slash from 'slash' import { BaseFactory } from './BaseFactory' +import MetricType from '../MetricType' /** * Factory class for creating Comparison objects @@ -45,19 +46,44 @@ export class ComparisonFactory extends BaseFactory { const colors = generateColors(matches.length, matchSaturation, matchLightness, matchAlpha) const coloredMatches = matches.map((match, index) => this.mapMatch(match, colors[index])) - const comparison = new Comparison( + return new Comparison( firstSubmissionId, secondSubmissionId, - json.similarity as number + this.extractSimilarities(json), + filesOfFirstConverted, + filesOfSecondConverted, + coloredMatches, + this.groupMatchesByFileName(coloredMatches, 1), + this.groupMatchesByFileName(coloredMatches, 2) ) - comparison.filesOfFirstSubmission = filesOfFirstConverted - comparison.filesOfSecondSubmission = filesOfSecondConverted - comparison.colors = colors - comparison.allMatches = coloredMatches - comparison.matchesInFirstSubmission = this.groupMatchesByFileName(coloredMatches, 1) - comparison.matchesInSecondSubmissions = this.groupMatchesByFileName(coloredMatches, 2) - - return comparison + } + + private static extractSimilarities(json: Record): Record { + if (json.similarities) { + return this.extractSimilaritiesFromMap(json.similarities as Record) + } else if (json.similarity) { + return this.extractSimilaritiesFromSingleValue(json.similarity as number) + } + throw new Error('No similarities found in comparison file') + } + + private static extractSimilaritiesFromSingleValue( + avgSimilarity: number + ): Record { + return { + [MetricType.AVERAGE]: avgSimilarity, + [MetricType.MAXIMUM]: Number.NaN + } + } + + private static extractSimilaritiesFromMap( + similarityMap: Record + ): Record { + const similarities = {} as Record + for (const [key, value] of Object.entries(similarityMap)) { + similarities[key as MetricType] = value + } + return similarities } private static convertToSubmissionFileList( diff --git a/report-viewer/src/views/ComparisonView.vue b/report-viewer/src/views/ComparisonView.vue index 2f046364a..5b51b8047 100644 --- a/report-viewer/src/views/ComparisonView.vue +++ b/report-viewer/src/views/ComparisonView.vue @@ -21,7 +21,7 @@
{{ (comparison.similarity * 100).toFixed(2) }}%{{ (comparison.similarities[MetricType.AVERAGE] * 100).toFixed(2) }}%
{ + return `${id1}-${id2}.json` + }, + filesOfSubmission: (name: string) => { + return [ + { + name: `${name}/Structure.java`, + value: '' + }, + { + name: `${name}/Submission.java`, + value: '' + } + ] + } +} + +describe('Test JSON to Comparison', () => { + beforeAll(() => { + vi.mock('@/stores/store', () => ({ + default: vi.fn(() => { + return store + }) + })) + }) + + it('Post 5.0', () => { + store.state.files['root1-root2.json'] = JSON.stringify(validNew) + + const result = ComparisonFactory.getComparison('root1', 'root2') + + expect(result).toBeDefined() + expect(result.firstSubmissionId).toBe('root1') + expect(result.secondSubmissionId).toBe('root2') + expect(result.similarities[MetricType.AVERAGE]).toBe(0.6900452488687783) + expect(result.similarities[MetricType.MAXIMUM]).toBe(0.9936000000000001) + expect(result.filesOfFirstSubmission).toBeDefined() + expect(result.filesOfSecondSubmission).toBeDefined() + expect(result.allMatches.length).toBe(4) + expect(result.matchesInFirstSubmission.size).toBe(2) + expect(result.matchesInSecondSubmissions.size).toBe(2) + }) + + it('Pre 5.0', () => { + store.state.files['root1-root2.json'] = JSON.stringify(validOld) + + const result = ComparisonFactory.getComparison('root1', 'root2') + + expect(result).toBeDefined() + expect(result.firstSubmissionId).toBe('root1') + expect(result.secondSubmissionId).toBe('root2') + expect(result.similarities[MetricType.AVERAGE]).toBe(0.6900452488687783) + expect(result.similarities[MetricType.MAXIMUM]).toBe(Number.NaN) + expect(result.filesOfFirstSubmission).toBeDefined() + expect(result.filesOfSecondSubmission).toBeDefined() + expect(result.allMatches.length).toBe(4) + expect(result.matchesInFirstSubmission.size).toBe(2) + expect(result.matchesInSecondSubmissions.size).toBe(2) + }) +}) diff --git a/report-viewer/tests/unit/model/factories/ValidNewComparison.json b/report-viewer/tests/unit/model/factories/ValidNewComparison.json new file mode 100644 index 000000000..fe8533d49 --- /dev/null +++ b/report-viewer/tests/unit/model/factories/ValidNewComparison.json @@ -0,0 +1,48 @@ +{ + "id1": "root1", + "id2": "root2", + "similarities": { + "AVG": 0.6900452488687783, + "MAX": 0.9936000000000001 + }, + "matches": [ + { + "file1": "root2\\Structure.java", + "file2": "root1\\Structure.java", + "start1": 3, + "end1": 120, + "start2": 1, + "end2": 118, + "tokens": 139 + }, + { + "file1": "root2\\Submission.java", + "file2": "root1\\Submission.java", + "start1": 129, + "end1": 155, + "start2": 134, + "end2": 160, + "tokens": 34 + }, + { + "file1": "root2\\Submission.java", + "file2": "root1\\Submission.java", + "start1": 165, + "end1": 194, + "start2": 173, + "end2": 203, + "tokens": 33 + }, + + { + "file1": "root2\\Submission.java", + "file2": "root1\\Submission.java", + "start1": 112, + "end1": 127, + "start2": 116, + "end2": 132, + "tokens": 23 + } + ] + } + \ No newline at end of file diff --git a/report-viewer/tests/unit/model/factories/ValidOldComparison.json b/report-viewer/tests/unit/model/factories/ValidOldComparison.json new file mode 100644 index 000000000..3f2394af2 --- /dev/null +++ b/report-viewer/tests/unit/model/factories/ValidOldComparison.json @@ -0,0 +1,45 @@ +{ + "id1": "root1", + "id2": "root2", + "similarity": 0.6900452488687783, + "matches": [ + { + "file1": "root2\\Structure.java", + "file2": "root1\\Structure.java", + "start1": 3, + "end1": 120, + "start2": 1, + "end2": 118, + "tokens": 139 + }, + { + "file1": "root2\\Submission.java", + "file2": "root1\\Submission.java", + "start1": 129, + "end1": 155, + "start2": 134, + "end2": 160, + "tokens": 34 + }, + { + "file1": "root2\\Submission.java", + "file2": "root1\\Submission.java", + "start1": 165, + "end1": 194, + "start2": 173, + "end2": 203, + "tokens": 33 + }, + + { + "file1": "root2\\Submission.java", + "file2": "root1\\Submission.java", + "start1": 112, + "end1": 127, + "start2": 116, + "end2": 132, + "tokens": 23 + } + ] + } + \ No newline at end of file From 007d777e74b38b2539e9ce27f09684d886045bd0 Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Thu, 27 Jul 2023 19:02:51 +0200 Subject: [PATCH 14/19] move file grouping --- report-viewer/src/model/Comparison.ts | 46 +++++++++++++++---- .../src/model/factories/ComparisonFactory.ts | 41 +---------------- 2 files changed, 38 insertions(+), 49 deletions(-) diff --git a/report-viewer/src/model/Comparison.ts b/report-viewer/src/model/Comparison.ts index 0cfb75cac..36f1b7dca 100644 --- a/report-viewer/src/model/Comparison.ts +++ b/report-viewer/src/model/Comparison.ts @@ -13,8 +13,6 @@ export class Comparison { private _filesOfFirstSubmission: Map private _filesOfSecondSubmission: Map private _allMatches: Array - private _matchesInFirstSubmission: Map> - private _matchesInSecondSubmissions: Map> constructor( firstSubmissionId: string, @@ -22,9 +20,7 @@ export class Comparison { similarities: Record, filesOfFirstSubmission: Map, filesOfSecondSubmission: Map, - allMatches: Array, - matchesInFirstSubmission: Map>, - matchesInSecondSubmissions: Map> + allMatches: Array ) { this._firstSubmissionId = firstSubmissionId this._secondSubmissionId = secondSubmissionId @@ -32,8 +28,6 @@ export class Comparison { this._filesOfFirstSubmission = filesOfFirstSubmission this._filesOfSecondSubmission = filesOfSecondSubmission this._allMatches = allMatches - this._matchesInFirstSubmission = matchesInFirstSubmission - this._matchesInSecondSubmissions = matchesInSecondSubmissions } /** @@ -61,14 +55,14 @@ export class Comparison { * @return Map of all matches in the first submission */ get matchesInFirstSubmission(): Map> { - return this._matchesInFirstSubmission + return this.groupMatchesByFileName(1) } /** * @return Map of all matches in the second submission */ get matchesInSecondSubmissions(): Map> { - return this._matchesInSecondSubmissions + return this.groupMatchesByFileName(2) } /** @@ -91,4 +85,38 @@ export class Comparison { get similarities() { return this._similarities } + + private groupMatchesByFileName(index: 1 | 2): Map> { + const acc = new Map>() + this.allMatches.forEach((val) => { + const name = index === 1 ? val.firstFile : val.secondFile + + if (!acc.get(name)) { + acc.set(name, []) + } + + if (index === 1) { + const newVal: MatchInSingleFile = { + start: val.startInFirst, + end: val.endInFirst, + linked_panel: 2, + linked_file: val.secondFile, + linked_line: val.startInSecond, + color: val.color + } + acc.get(name)?.push(newVal) + } else { + const newVal: MatchInSingleFile = { + start: val.startInSecond, + end: val.endInSecond, + linked_panel: 1, + linked_file: val.firstFile, + linked_line: val.startInFirst, + color: val.color + } + acc.get(name)?.push(newVal) + } + }) + return acc + } } diff --git a/report-viewer/src/model/factories/ComparisonFactory.ts b/report-viewer/src/model/factories/ComparisonFactory.ts index a3ff4ca31..9385e1f54 100644 --- a/report-viewer/src/model/factories/ComparisonFactory.ts +++ b/report-viewer/src/model/factories/ComparisonFactory.ts @@ -52,9 +52,7 @@ export class ComparisonFactory extends BaseFactory { this.extractSimilarities(json), filesOfFirstConverted, filesOfSecondConverted, - coloredMatches, - this.groupMatchesByFileName(coloredMatches, 1), - this.groupMatchesByFileName(coloredMatches, 2) + coloredMatches ) } @@ -105,43 +103,6 @@ export class ComparisonFactory extends BaseFactory { return map } - private static groupMatchesByFileName( - matches: Array, - index: 1 | 2 - ): Map> { - const acc = new Map>() - matches.forEach((val) => { - const name = index === 1 ? val.firstFile : val.secondFile - - if (!acc.get(name)) { - acc.set(name, []) - } - - if (index === 1) { - const newVal: MatchInSingleFile = { - start: val.startInFirst, - end: val.endInFirst, - linked_panel: 2, - linked_file: val.secondFile, - linked_line: val.startInSecond, - color: val.color - } - acc.get(name)?.push(newVal) - } else { - const newVal: MatchInSingleFile = { - start: val.startInSecond, - end: val.endInSecond, - linked_panel: 1, - linked_file: val.firstFile, - linked_line: val.startInFirst, - color: val.color - } - acc.get(name)?.push(newVal) - } - }) - return acc - } - private static getSubmissionFileListFromLocal(submissionId: string): string[] { return JSON.parse(this.getLocalFile(`submissionFileIndex.json`)).submission_file_indexes[ submissionId From 7e37aaa1d2b01227906e1a7b03b528e243030733 Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Wed, 9 Aug 2023 15:12:07 +0200 Subject: [PATCH 15/19] changes to the ts part --- report-viewer/src/model/HundredValueDistribution.ts | 4 ++++ report-viewer/src/model/Overview.ts | 3 ++- report-viewer/src/model/TenValueDistribution.ts | 4 ++++ report-viewer/src/model/factories/BaseFactory.ts | 13 +++++++------ .../src/model/factories/OverviewFactory.ts | 7 +++++-- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/report-viewer/src/model/HundredValueDistribution.ts b/report-viewer/src/model/HundredValueDistribution.ts index 7eee091f6..465cf88f9 100644 --- a/report-viewer/src/model/HundredValueDistribution.ts +++ b/report-viewer/src/model/HundredValueDistribution.ts @@ -1,5 +1,9 @@ import Distribution from './Distribution' +/** + * This class represents he JPlag Distribution of a metric. + * It is composed of 100 values, each representing the sum of the values of the metric in the corresponding percentile. + */ export default class HundredValueDistribution extends Distribution { constructor(distribution: number[]) { super(distribution) diff --git a/report-viewer/src/model/Overview.ts b/report-viewer/src/model/Overview.ts index 2bc1f778f..4ee8d9ba0 100644 --- a/report-viewer/src/model/Overview.ts +++ b/report-viewer/src/model/Overview.ts @@ -41,7 +41,8 @@ export class Overview { this._durationOfExecution = durationOfExecution this._topComparisons = topComparisons this._distributions = distributions - ;(this._clusters = clusters), (this._totalComparisons = totalComparisons) + this._clusters = clusters + this._totalComparisons = totalComparisons } /** diff --git a/report-viewer/src/model/TenValueDistribution.ts b/report-viewer/src/model/TenValueDistribution.ts index c0305acc9..52e1d45ae 100644 --- a/report-viewer/src/model/TenValueDistribution.ts +++ b/report-viewer/src/model/TenValueDistribution.ts @@ -1,5 +1,9 @@ import Distribution from './Distribution' +/** + * This class represents he JPlag Distribution of a metric. + * It is composed of 10 values, each representing the sum of the values of the metric in the corresponding percentiles. + */ export default class TenValueDistribution extends Distribution { constructor(distribution: number[]) { super(distribution) diff --git a/report-viewer/src/model/factories/BaseFactory.ts b/report-viewer/src/model/factories/BaseFactory.ts index bfb0b5211..2c31ccfc5 100644 --- a/report-viewer/src/model/factories/BaseFactory.ts +++ b/report-viewer/src/model/factories/BaseFactory.ts @@ -15,13 +15,14 @@ export class BaseFactory { return this.getLocalFile(path) } else if (store().state.zipModeUsed) { const index = Object.keys(store().state.files).find((name) => name.endsWith(path)) - if (index != undefined) { - const file = store().state.files[index] - if (file != undefined) { - return file - } + if (index == undefined) { + throw new Error(`Could not find ${path} in zip file.`) } - throw new Error(`Could not find ${path} in zip file.`) + const file = store().state.files[index] + if (file == undefined) { + throw new Error(`Could not load ${path}.`) + } + return file } else if (store().state.singleModeUsed) { return store().state.singleFillRawContent } diff --git a/report-viewer/src/model/factories/OverviewFactory.ts b/report-viewer/src/model/factories/OverviewFactory.ts index 9b1801b46..617a7ff35 100644 --- a/report-viewer/src/model/factories/OverviewFactory.ts +++ b/report-viewer/src/model/factories/OverviewFactory.ts @@ -10,6 +10,9 @@ import { BaseFactory } from './BaseFactory' import PercentileDistribution from '../HundredValueDistribution' import TenValueDistribution from '../TenValueDistribution' +/** + * Factory class for creating Overview objects + */ export class OverviewFactory extends BaseFactory { static reportViewerVersion: Version = versionJson['report_viewer_version'] !== undefined @@ -129,7 +132,7 @@ export class OverviewFactory extends BaseFactory { const averageSimilarities: Map = new Map() const comparisons = [] as Array - // Average + // Save the average similarities in a temporary map to combine them with the max similarities later for (const comparison of metrics[0].topComparisons as Array>) { averageSimilarities.set( (comparison.first_submission as string) + '-' + (comparison.second_submission as string), @@ -137,7 +140,7 @@ export class OverviewFactory extends BaseFactory { ) } - // Max + // Extract the max similarities and combine them with the average similarities let counter = 0 for (const comparison of metrics[1].topComparisons as Array>) { const avg = averageSimilarities.get( From a223017e8eb7330b6b52759888928e27de2d9933 Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Sun, 13 Aug 2023 19:08:51 +0200 Subject: [PATCH 16/19] add constant --- core/src/test/java/de/jplag/BasicFunctionalityTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/de/jplag/BasicFunctionalityTest.java b/core/src/test/java/de/jplag/BasicFunctionalityTest.java index 3c993644d..13ee3066e 100644 --- a/core/src/test/java/de/jplag/BasicFunctionalityTest.java +++ b/core/src/test/java/de/jplag/BasicFunctionalityTest.java @@ -14,6 +14,7 @@ */ class BasicFunctionalityTest extends TestBase { + private static int DISTRIBUTION_INDEX = 66; @Test @DisplayName("test submissions that contain obvious plagiarism") void testSimpleDuplicate() throws ExitException { @@ -22,7 +23,7 @@ void testSimpleDuplicate() throws ExitException { assertEquals(2, result.getNumberOfSubmissions()); assertEquals(1, result.getAllComparisons().size()); assertEquals(1, result.getAllComparisons().get(0).matches().size()); - assertEquals(1, result.getSimilarityDistribution()[66]); + assertEquals(1, result.getSimilarityDistribution()[DISTRIBUTION_INDEX]); assertEquals(0.666, result.getAllComparisons().get(0).similarity(), DELTA); } @@ -91,7 +92,7 @@ void testSingleFileSubmisssions() throws ExitException { assertEquals(2, result.getNumberOfSubmissions()); assertEquals(1, result.getAllComparisons().size()); - assertEquals(1, result.getSimilarityDistribution()[66]); + assertEquals(1, result.getSimilarityDistribution()[DISTRIBUTION_INDEX]); assertEquals(0.666, result.getAllComparisons().get(0).similarity(), DELTA); var matches = result.getAllComparisons().get(0).matches(); From 8325790b768da39ccb7fece3f8ffb7726013fe19 Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Sun, 13 Aug 2023 19:15:34 +0200 Subject: [PATCH 17/19] spottless --- core/src/test/java/de/jplag/BasicFunctionalityTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/test/java/de/jplag/BasicFunctionalityTest.java b/core/src/test/java/de/jplag/BasicFunctionalityTest.java index 13ee3066e..0c88ce7ea 100644 --- a/core/src/test/java/de/jplag/BasicFunctionalityTest.java +++ b/core/src/test/java/de/jplag/BasicFunctionalityTest.java @@ -15,6 +15,7 @@ class BasicFunctionalityTest extends TestBase { private static int DISTRIBUTION_INDEX = 66; + @Test @DisplayName("test submissions that contain obvious plagiarism") void testSimpleDuplicate() throws ExitException { From 9526fbdc5e06af591be4a21b2e45e60d0c452944 Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Mon, 14 Aug 2023 21:15:31 +0200 Subject: [PATCH 18/19] mark old format extraction as deprecated --- report-viewer/src/model/factories/ComparisonFactory.ts | 1 + report-viewer/src/model/factories/OverviewFactory.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/report-viewer/src/model/factories/ComparisonFactory.ts b/report-viewer/src/model/factories/ComparisonFactory.ts index 9385e1f54..afd0c4d5f 100644 --- a/report-viewer/src/model/factories/ComparisonFactory.ts +++ b/report-viewer/src/model/factories/ComparisonFactory.ts @@ -65,6 +65,7 @@ export class ComparisonFactory extends BaseFactory { throw new Error('No similarities found in comparison file') } + /** @deprecated since 5.0.0. Use the new format with {@link extractSimilaritiesFromMap} */ private static extractSimilaritiesFromSingleValue( avgSimilarity: number ): Record { diff --git a/report-viewer/src/model/factories/OverviewFactory.ts b/report-viewer/src/model/factories/OverviewFactory.ts index 617a7ff35..e97843930 100644 --- a/report-viewer/src/model/factories/OverviewFactory.ts +++ b/report-viewer/src/model/factories/OverviewFactory.ts @@ -91,6 +91,7 @@ export class OverviewFactory extends BaseFactory { return distributions } + /** @deprecated since 5.0.0. Use the new format with {@link extractDistributionsFromMap} */ private static extractDistributionsFromMetrics( metrics: Array> ): Record { @@ -128,6 +129,7 @@ export class OverviewFactory extends BaseFactory { return comparisons } + /** @deprecated since 5.0.0. Use the new format with {@link extractTopComparisonsFromMap} */ private static extractTopComparisonsFromMetrics(metrics: Array>) { const averageSimilarities: Map = new Map() const comparisons = [] as Array From 0ffb76ac237eb28f5e9a3fab49d01603a32b5227 Mon Sep 17 00:00:00 2001 From: Alexander Vogt Date: Thu, 17 Aug 2023 09:45:58 +0200 Subject: [PATCH 19/19] deprecate old distribution --- report-viewer/src/model/TenValueDistribution.ts | 1 + report-viewer/src/model/factories/OverviewFactory.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/report-viewer/src/model/TenValueDistribution.ts b/report-viewer/src/model/TenValueDistribution.ts index 52e1d45ae..3b8e244a1 100644 --- a/report-viewer/src/model/TenValueDistribution.ts +++ b/report-viewer/src/model/TenValueDistribution.ts @@ -4,6 +4,7 @@ import Distribution from './Distribution' * This class represents he JPlag Distribution of a metric. * It is composed of 10 values, each representing the sum of the values of the metric in the corresponding percentiles. */ +/** @deprecated since 5.0.0. Use the new format with {@link HundredValueDistribution} */ export default class TenValueDistribution extends Distribution { constructor(distribution: number[]) { super(distribution) diff --git a/report-viewer/src/model/factories/OverviewFactory.ts b/report-viewer/src/model/factories/OverviewFactory.ts index e97843930..5ea185584 100644 --- a/report-viewer/src/model/factories/OverviewFactory.ts +++ b/report-viewer/src/model/factories/OverviewFactory.ts @@ -7,7 +7,7 @@ import versionJson from '@/version.json' import Distribution from '../Distribution' import MetricType from '../MetricType' import { BaseFactory } from './BaseFactory' -import PercentileDistribution from '../HundredValueDistribution' +import HundredValueDistribution from '../HundredValueDistribution' import TenValueDistribution from '../TenValueDistribution' /** @@ -86,7 +86,7 @@ export class OverviewFactory extends BaseFactory { ): Record { const distributions = {} as Record for (const [key, value] of Object.entries(distributionsMap)) { - distributions[key as MetricType] = new PercentileDistribution(value as Array) + distributions[key as MetricType] = new HundredValueDistribution(value as Array) } return distributions }