Skip to content

Commit 29b9f5f

Browse files
authored
[#944] Implement authorship analysis (#2140)
A line is credited to the author who last modified it. Another author might have written the line initially and the current author only modified it slightly. In such a case, the current author gets credited for work that is not entirely done by him/her. Let's analyze how similar a line is as compared to its ancestor lines (previous versions of the line) and give full or partial credit to the last author based on the analysis.
1 parent bf78bf2 commit 29b9f5f

File tree

66 files changed

+1355
-134
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1355
-134
lines changed

build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ def serveTestReportInBackground = tasks.register('serveTestReportInBackground',
196196
workingDir = 'build/serveTestReport'
197197
main = mainClassName
198198
classpath = sourceSets.main.runtimeClasspath
199-
args = ['--config', './exampleconfig', '--since', 'd1', '--view']
199+
args = ['--config', './exampleconfig', '--since', 'd1', '--view', '-A']
200200
String versionJvmArgs = '-Dversion=' + getRepoSenseVersion()
201201
jvmArgs = [ versionJvmArgs ]
202202
waitForPort = 9000

docs/ug/cli.md

+42-2
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,36 @@ The command `java -jar RepoSense.jar` takes several flags.
1616
**Examples**:
1717

1818
An example of a command using most parameters:<br>
19-
`java -jar RepoSense.jar --repos https://github.com/reposense/RepoSense.git --output ./report_folder --since 31/1/2017 --until 31/12/2018 --formats java adoc xml --view --ignore-standalone-config --last-modified-date --timezone UTC+08 --find-previous-authors`
19+
`java -jar RepoSense.jar --repos https://github.com/reposense/RepoSense.git --output ./report_folder --since 31/1/2017 --until 31/12/2018 --formats java adoc xml --view --ignore-standalone-config --last-modified-date --timezone UTC+08 --find-previous-authors --analyze-authorship --originality-threshold 0.66`
2020

2121
Same command as above but using most parameters in alias format:<br>
22-
`java -jar RepoSense.jar -r https://github.com/reposense/RepoSense.git -o ./report_folder -s 31/1/2017 -u 31/12/2018 -f java adoc xml -v -i -l -t UTC+08 -F`
22+
`java -jar RepoSense.jar -r https://github.com/reposense/RepoSense.git -o ./report_folder -s 31/1/2017 -u 31/12/2018 -f java adoc xml -v -i -l -t UTC+08 -F -A -ot 0.66`
2323
</box>
2424

2525
The section below provides explanations for each of the flags.
2626

2727
<!-- --------------------------◘---------------------------------------------------------------------------- -->
2828

29+
### `--analyze-authorship`, `-A`
30+
31+
**`--analyze-authorship`**: Performs further analysis to distinguish between partial and full credit attribution for
32+
lines of code assigned to the author.
33+
34+
* Default: this feature is turned ***off*** by default and the author will receive partial credits for all lines of
35+
code, as the code lines are at least partial credit but may not qualify for full credit.
36+
* Alias: `-A` (upper case)
37+
* Example: `--analyze-authorship` or `-A`
38+
39+
<box type="info" seamless>
40+
41+
A darker background colour represents full credit, while a lighter background colour represents partial credit.
42+
43+
If the code is attributed to a different author by the user via `@@author` tag, then the new author will be given
44+
partial credit.
45+
</box>
46+
47+
<!-- ------------------------------------------------------------------------------------------------------ -->
48+
2949
### `--assets`, `-a`
3050

3151
<div id="section-assets">
@@ -147,6 +167,26 @@ This flag overrides the `Ignore file size limit` field in the CSV config file.
147167

148168
<!-- ------------------------------------------------------------------------------------------------------ -->
149169

170+
### `--originality-threshold`, `-ot`
171+
172+
**`--originality-threshold [VALUE]`**: Specifies the cut-off point for partial and full credit
173+
in `--analyze-authorship`. Author will be given full credit if their contribution exceeds this threshold, else partial
174+
credit is given.
175+
176+
* Parameter: `VALUE` Optional. Acceptable range: [0.0, 1.0].<br>
177+
Default: `0.51`
178+
* Alias: `-ot`
179+
* Example: `--originality-threshold 0.66` or `-ot 0.66`
180+
181+
<box type="info" seamless>
182+
183+
* Requires `--analyze-authorship` flag.
184+
* An author's contribution, or `originality score`, is calculated using Levenshtein Distance (Edit Distance) algorithm.
185+
We compare the difference between current code line and its previous versions.
186+
</box>
187+
188+
<!-- ------------------------------------------------------------------------------------------------------ -->
189+
150190
### `--output`, `-o`
151191

152192
**`--output OUTPUT_DIRECTORY`**: Indicates where to save the report generated.

frontend/cypress/tests/codeView/codeView_codeHighlighting.cy.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ describe('code highlighting works properly', () => {
3838
cy.get('.hljs-comment').contains('* Represents a Git Author.')
3939
.parent() // .line-content
4040
.parent() // .code
41-
.should('have.css', 'background-color', 'rgb(230, 255, 237)'); // #e6ffed
41+
.should('have.css', 'background-color', 'rgb(191, 246, 207)'); // #BFF6CF
4242
});
4343

4444
it('should highlight code when multiple authors are merged in a repo group', () => {
@@ -62,13 +62,13 @@ describe('code highlighting works properly', () => {
6262
cy.get('.hljs-comment').contains('* MUI Colors module') // eugenepeh
6363
.parent() // .line-content
6464
.parent() // .code
65-
.should('have.css', 'background-color', 'rgba(30, 144, 255, 0.19)') // #1e90ff, transparencyValue 30
65+
.should('have.css', 'background-color', 'rgba(30, 144, 255, 0.314)') // #1e90ff, transparencyValue 50
6666
.then((firstAuthorColor) => {
6767
// eslint-disable-next-line quotes
6868
cy.get('.line-content').contains("'red': (") // jamessspanggg
6969
.parent() // .code
70-
// #f08080, transparencyValue 30
71-
.should('have.css', 'background-color', 'rgba(240, 128, 128, 0.19)')
70+
// #f08080, transparencyValue 50
71+
.should('have.css', 'background-color', 'rgba(240, 128, 128, 0.314)')
7272
.and('not.eq', firstAuthorColor);
7373
});
7474
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
describe('credit background colour', () => {
2+
it('check if background colour match the credit information for Eugene', () => {
3+
// open the code panel
4+
cy.get('.icon-button.fa-code')
5+
.should('exist')
6+
.first()
7+
.click();
8+
9+
// src/main/java/reposense/model/Author.java
10+
// line 9: full credit - #BFF6CF
11+
cy.get(':nth-child(1) > .file-content > .segment-collection > :nth-child(2) > .java > .code')
12+
.should('have.css', 'background-color')
13+
.and('eq', 'rgb(191, 246, 207)');
14+
15+
// src/main/java/reposense/model/Author.java
16+
// line 15: partial credit - #E6FFED
17+
cy.get(':nth-child(1) > .file-content > .segment-collection > :nth-child(4) > .java > .code')
18+
.should('have.css', 'background-color')
19+
.and('eq', 'rgb(230, 255, 237)');
20+
});
21+
22+
it('check if background colour match the credit information when group is merged', () => {
23+
// check merge group checkbox
24+
cy.get('#summary label.merge-group > input')
25+
.should('be.visible')
26+
.check()
27+
.should('be.checked');
28+
29+
// open the code panel
30+
cy.get('.icon-button.fa-code')
31+
.should('exist')
32+
.first()
33+
.click();
34+
35+
// frontend/src/styles/_colors.scss
36+
// line 35: full credit - #F0808050
37+
cy.get(':nth-child(7) > .scss > :nth-child(1)')
38+
.should('have.css', 'background-color')
39+
.and('eq', 'rgba(240, 128, 128, 0.314)');
40+
41+
// FileInfoExtractor.java is too far away to be loaded, use filter to go to it directly
42+
cy.get('#search')
43+
.click()
44+
.type('FileInfoExtractor.java');
45+
46+
cy.get('#submit-button')
47+
.click();
48+
49+
// src/main/java/reposense/authorship/FileInfoExtractor.java
50+
// line 23: partial credit - #1E90FF20
51+
cy.get(':nth-child(10) > .java > :nth-child(1)')
52+
.should('have.css', 'background-color')
53+
.and('eq', 'rgba(30, 144, 255, 0.125)');
54+
});
55+
});

frontend/src/components/c-segment.vue

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<template lang="pug">
22
.segment(
3-
v-bind:class="{ untouched: !segment.knownAuthor, active: isOpen }",
3+
v-bind:class="{ untouched: !segment.knownAuthor, active: isOpen, isNotFullCredit: !segment.isFullCredit }",
44
v-bind:style="{ 'border-left': `0.25rem solid ${authorColors[segment.knownAuthor]}` }",
5-
v-bind:title="`Author: ${segment.knownAuthor || \"Unknown\"}`"
5+
v-bind:title="`${segment.isFullCredit ? 'Author' : 'Co-author'}: ${segment.knownAuthor || \"Unknown\"}`"
66
)
77
.closer(v-if="canOpen",
88
v-on:click="toggleCode", ref="topButton")
@@ -17,6 +17,7 @@
1717
v-bind:title="'Click to hide code'"
1818
)
1919
div(v-if="isOpen", v-hljs="path")
20+
//- author color is applied only when the author color exists, else it takes the default mui color value
2021
.code(
2122
v-for="(line, index) in segment.lines", v-bind:key="index",
2223
v-bind:style="{ 'background-color': `${authorColors[segment.knownAuthor]}${transparencyValue}` }"
@@ -57,7 +58,7 @@ export default defineComponent({
5758
return {
5859
isOpen: (this.segment.knownAuthor !== null) || this.segment.lines.length < 5 as boolean,
5960
canOpen: (this.segment.knownAuthor === null) && this.segment.lines.length > 4 as boolean,
60-
transparencyValue: '30' as string,
61+
transparencyValue: (this.segment.isFullCredit ? '50' : '20') as string,
6162
};
6263
},
6364
computed: {
@@ -81,7 +82,7 @@ export default defineComponent({
8182
border-left: .25rem solid mui-color('green');
8283
8384
.code {
84-
background-color: mui-color('github', 'authored-code-background');
85+
background-color: mui-color('github', 'full-authored-code-background');
8586
padding-left: 1rem;
8687
}
8788
@@ -114,6 +115,12 @@ export default defineComponent({
114115
word-break: break-word;
115116
}
116117
118+
&.isNotFullCredit {
119+
.code {
120+
background-color: mui-color('github', 'partial-authored-code-background');
121+
}
122+
}
123+
117124
&.untouched {
118125
$grey: mui-color('grey', '400');
119126
border-left: .25rem solid $grey;

frontend/src/styles/_colors.scss

+2-1
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,8 @@ $mui-colors: (
303303
'github': (
304304
'title-background': #FAFBFC,
305305
'border': #E1E4E8,
306-
'authored-code-background': #E6FFED,
306+
'full-authored-code-background': #BFF6CF,
307+
'partial-authored-code-background': #E6FFED,
307308
),
308309
'grey': (
309310
'50': #FAFAFA,

frontend/src/styles/hightlight-js-style.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
.hljs-section,
3636
.hljs-name {
37-
color: #63a35c;
37+
color: #468C5A;
3838
}
3939

4040
.hljs-tag {

frontend/src/types/types.ts

+7
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export interface Repo extends RepoRaw {
6161

6262
export interface AuthorshipFileSegment {
6363
knownAuthor: string | null;
64+
isFullCredit: boolean;
6465
lineNumbers: number[];
6566
lines: string[];
6667
}
@@ -83,3 +84,9 @@ export interface Bar {
8384
color?: string;
8485
tooltipText?: string;
8586
}
87+
88+
export interface SegmentState {
89+
id: number;
90+
author: string | null;
91+
isFullCredit: boolean;
92+
}

frontend/src/types/window.ts

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ declare global {
6868
repoSenseVersion: string;
6969
isSinceDateProvided: boolean;
7070
isUntilDateProvided: boolean;
71+
isAuthorshipAnalyzed: boolean;
7172
DOMAIN_URL_MAP: DomainUrlMap;
7273
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7374
app: any;

frontend/src/types/zod/authorship-type.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const lineSchema = z.object({
44
lineNumber: z.number(),
55
author: z.object({ gitId: z.string() }),
66
content: z.string(),
7+
isFullCredit: z.boolean().default(false), // for backwards compatability
78
});
89

910
const fileResult = z.object({

frontend/src/types/zod/summary-type.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ const urlSchema = z.object({
3131
const supportedDomainUrlMapSchema = z.record(urlSchema);
3232

3333
// Contains the zod validation schema for the summary.json file
34-
3534
export const summarySchema = z.object({
3635
repoSenseVersion: z.string(),
3736
reportGeneratedTime: z.string(),
@@ -44,6 +43,7 @@ export const summarySchema = z.object({
4443
untilDate: z.string(),
4544
isSinceDateProvided: z.boolean(),
4645
isUntilDateProvided: z.boolean(),
46+
isAuthorshipAnalyzed: z.boolean().default(false), // for backwards compatability
4747
supportedDomainUrlMap: supportedDomainUrlMapSchema,
4848
});
4949

frontend/src/utils/api.ts

+1
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ window.api = {
217217
window.repoSenseVersion = data.repoSenseVersion;
218218
window.isSinceDateProvided = data.isSinceDateProvided;
219219
window.isUntilDateProvided = data.isUntilDateProvided;
220+
window.isAuthorshipAnalyzed = data.isAuthorshipAnalyzed;
220221
document.title = data.reportTitle || document.title;
221222

222223
const errorMessages: { [key: string]: ErrorMessage } = {};

0 commit comments

Comments
 (0)