diff --git a/src/GitLabHealth-Model-Analysis-Tests/MergeRequestMergedByUserMetricTest.class.st b/src/GitLabHealth-Model-Analysis-Tests/MergeRequestMergedByUserMetricTest.class.st new file mode 100644 index 00000000..44afbd81 --- /dev/null +++ b/src/GitLabHealth-Model-Analysis-Tests/MergeRequestMergedByUserMetricTest.class.st @@ -0,0 +1,286 @@ +" +A MergedMergeRequestMetricTest is a test class for testing the behavior of MergedMergeRequestMetric +" +Class { + #name : #MergeRequestMergedByUserMetricTest, + #superclass : #UserMetricTest, + #category : #'GitLabHealth-Model-Analysis-Tests' +} + +{ #category : #tests } +MergeRequestMergedByUserMetricTest >> testCalculate [ + + | result glhImporter mergedMergeRequest | + "Given" + + + glhImporter := GLPHImporterMock new. + glhImporter mergeRequests: { + (GLHMergeRequest new + author: user; + merge_user: user; + project: project1; + created_at: createdAt ; + merged_at: mergedAt ). + + (GLHMergeRequest new + author: user; + merge_user: user; + project: project1; + created_at: createdAt - 20 days; + merged_at: nil) }. + + + mergedMergeRequest := MergeRequestMergedByUserMetric new + user: user; + glhImporter: glhImporter; + setPeriodSince: since + until: until; + over: Week. + + "When" + result := mergedMergeRequest calculate. + + "Then" + self assert: result equals: 1 +] + +{ #category : #tests } +MergeRequestMergedByUserMetricTest >> testCalculate1MRInPeriod1Outside [ + + | result glhImporter mergedMergeRequest | + "Given" + glhImporter := GLPHImporterMock new. + glhImporter mergeRequests: { + (GLHMergeRequest new + author: user; + project: project1; + merge_user: user; + created_at: createdAt; + merged_at: mergedAt). + + "open but merged anotherWeek" + (GLHMergeRequest new + author: user; + merge_user: user; + project: project1; + created_at: createdAt; + merged_at: createdAt + 10 days) }. + "open and merge during the same week" + + + mergedMergeRequest := MergeRequestMergedByUserMetric new + user: user; + glhImporter: glhImporter; + setPeriodSince: since until: until; + over: Week. + + "When" + result := mergedMergeRequest calculate. + + "Then" + self assert: result equals: 1 +] + +{ #category : #tests } +MergeRequestMergedByUserMetricTest >> testCalculate1MROpenedButNotMerged [ + + | result glhImporter mergedMergeRequest | + "Given" + glhImporter := GLPHImporterMock new. + glhImporter mergeRequests: { + (GLHMergeRequest new + author: user; + merge_user: user; + project: project1; + created_at: createdAt; + merged_at: mergedAt; + state: #merged). + + (GLHMergeRequest new + author: user; + project: project1; + created_at: createdAt; + state: #opened) }. + + + mergedMergeRequest := MergeRequestMergedByUserMetric new + user: user; + glhImporter: glhImporter; + setPeriodSince: since until: until; + over: Week. + + "When" + result := mergedMergeRequest calculate. + + "Then" + self assert: result equals: 1 +] + +{ #category : #tests } +MergeRequestMergedByUserMetricTest >> testCalculate2MROneForEachUser [ + + | result glhImporter mergedMergeRequest user2 | + "Given" + + + user2 := GLHUser new + name: 'user2'; + id: 2; + contributedProjects: { project1 }. + + glhImporter := GLPHImporterMock new. + glhImporter mergeRequests: { + (GLHMergeRequest new + author: user; + title: 'MR1'; + project: project1; + created_at: createdAt; + merge_user: user; + merged_at: mergedAt). + (GLHMergeRequest new + author: user2; + title: 'MR2'; + project: project1; + created_at: createdAt; + merge_user: user2; + merged_at: mergedAt) }. + + + mergedMergeRequest := MergeRequestMergedByUserMetric new + user: user; + glhImporter: glhImporter; + setPeriodSince: since until: until; + over: Week. + + "When" + result := mergedMergeRequest calculate. + + "Then" + self assert: result equals: 1 +] + +{ #category : #tests } +MergeRequestMergedByUserMetricTest >> testCalculate3MROpenButMergedByOthers [ + + | result glhImporter mergedMergeRequest user2 | + "Given" + + + user2 := GLHUser new + name: 'user2'; + id: 2; + contributedProjects: + { project1 }. + + glhImporter := GLPHImporterMock new. + glhImporter mergeRequests: { + (GLHMergeRequest new + author: user; + title: 'MR1'; + created_at: createdAt; + merge_user: user2; + merged_at: mergedAt ). + (GLHMergeRequest new + author: user; + title: 'MR2'; + created_at: createdAt ; + merge_user: user2; + merged_at: mergedAt ). + (GLHMergeRequest new + author: user; + title: 'MR3'; + created_at: createdAt ; + merge_user: user2; + merged_at: mergedAt ) }. + + + mergedMergeRequest := MergeRequestMergedByUserMetric new + user: user; + glhImporter: glhImporter; + setPeriodSince: since + until: until; + over: Week. + + "When" + result := mergedMergeRequest calculate. + + "Then" + self assert: result equals: 0 +] + +{ #category : #tests } +MergeRequestMergedByUserMetricTest >> testCalculate3MROpenByOther [ + + | result glhImporter mergedMergeRequest user2 | + "Given" + + user2 := GLHUser new + name: 'user2'; + id: 2; + contributedProjects: + { project1 }. + + glhImporter := GLPHImporterMock new. + glhImporter mergeRequests: { + (GLHMergeRequest new + author: user2; + title: 'MR1'; + created_at: createdAt ; + merge_user: user; + project: project1 ; + state: #merged; + merged_at: mergedAt ). + (GLHMergeRequest new + author: user2; + state: #merged; + title: 'MR2'; + created_at: createdAt ; + project: project1 ; + merge_user: user; + merged_at: mergedAt ). + (GLHMergeRequest new + author: user2; + state: #merged; + title: 'MR3'; + created_at: createdAt ; + project: project1 ; + merge_user: user; + merged_at: mergedAt ) }. + + + mergedMergeRequest := MergeRequestMergedByUserMetric new + user: user; + glhImporter: glhImporter; + setPeriodSince: since + until: until; + over: Week. + + "When" + result := mergedMergeRequest calculate. + + "Then" + self assert: result equals: 3 +] + +{ #category : #tests } +MergeRequestMergedByUserMetricTest >> testCalculateNoMergeRequests [ + + | result glhImporter mergedMergeRequest | + "Given" + glhImporter := GLPHImporterMock new. + + + mergedMergeRequest := MergeRequestMergedByUserMetric new + user: user; + glhImporter: glhImporter; + setPeriodSince: since + until: until; + over: Week. + + "When" + result := mergedMergeRequest calculate. + + "Then" + self assert: result equals: 0 +] diff --git a/src/GitLabHealth-Model-Analysis-Tests/MergedMergeRequestMetricTest.class.st b/src/GitLabHealth-Model-Analysis-Tests/MergedMergeRequestMetricTest.class.st index b52ee8e0..88d40560 100644 --- a/src/GitLabHealth-Model-Analysis-Tests/MergedMergeRequestMetricTest.class.st +++ b/src/GitLabHealth-Model-Analysis-Tests/MergedMergeRequestMetricTest.class.st @@ -45,6 +45,80 @@ MergedMergeRequestMetricTest >> testCalculate [ self assert: result equals: 1 ] +{ #category : #tests } +MergedMergeRequestMetricTest >> testCalculate1MRCreatedBeforeAndMergeAfterPeriod [ + + | result glhImporter mergedMergeRequest | + "Given" + glhImporter := GLPHImporterMock new. + glhImporter mergeRequests: { + (GLHMergeRequest new + author: user; + project: project1; + merge_user: user; + created_at: createdAt; + merged_at: mergedAt). + + "opened anotherWeek" + (GLHMergeRequest new + author: user; + merge_user: user; + project: project1; + created_at: createdAt - 10 days; + merged_at: createdAt + 10 days) }. + "open and merge during the same week" + + + mergedMergeRequest := MergedMergeRequestMetric new + user: user; + glhImporter: glhImporter; + setPeriodSince: since until: until; + over: Week. + + "When" + result := mergedMergeRequest calculate. + + "Then" + self assert: result equals: 1 +] + +{ #category : #tests } +MergedMergeRequestMetricTest >> testCalculate1MRCreatedBeforePeriod [ + + | result glhImporter mergedMergeRequest | + "Given" + glhImporter := GLPHImporterMock new. + glhImporter mergeRequests: { + (GLHMergeRequest new + author: user; + project: project1; + merge_user: user; + created_at: createdAt; + merged_at: mergedAt). + + "opened anotherWeek" + (GLHMergeRequest new + author: user; + merge_user: user; + project: project1; + created_at: createdAt - 10 days; + merged_at: createdAt) }. + "open and merge during the same week" + + + mergedMergeRequest := MergedMergeRequestMetric new + user: user; + glhImporter: glhImporter; + setPeriodSince: since until: until; + over: Week. + + "When" + result := mergedMergeRequest calculate. + + "Then" + self assert: result equals: 2 +] + { #category : #tests } MergedMergeRequestMetricTest >> testCalculate1MRInPeriod1Outside [ @@ -120,7 +194,7 @@ MergedMergeRequestMetricTest >> testCalculate1MROpenedButNotMerged [ { #category : #tests } MergedMergeRequestMetricTest >> testCalculate2MROneForEachUser [ - | result glhImporter mergedMergeRequest user2 | + | result glhImporter mergedMergeRequest user2 | "Given" @@ -212,55 +286,52 @@ MergedMergeRequestMetricTest >> testCalculate3MROpenButMergedByOthers [ { #category : #tests } MergedMergeRequestMetricTest >> testCalculate3MROpenByOther [ - | result glhImporter mergedMergeRequest user2 | + | result glhImporter mergedMergeRequest user2 | "Given" - user2 := GLHUser new name: 'user2'; id: 2; - contributedProjects: - { project1 }. + contributedProjects: { project1 }. glhImporter := GLPHImporterMock new. glhImporter mergeRequests: { (GLHMergeRequest new author: user2; title: 'MR1'; - created_at: createdAt ; + created_at: createdAt; merge_user: user; - project: project1 ; - state: #merged; - merged_at: mergedAt ). + project: project1; + state: #merged; + merged_at: mergedAt). (GLHMergeRequest new author: user2; - state: #merged; + state: #merged; title: 'MR2'; - created_at: createdAt ; - project: project1 ; + created_at: createdAt; + project: project1; merge_user: user; - merged_at: mergedAt ). + merged_at: mergedAt). (GLHMergeRequest new author: user2; - state: #merged; + state: #merged; title: 'MR3'; - created_at: createdAt ; - project: project1 ; + created_at: createdAt; + project: project1; merge_user: user; - merged_at: mergedAt ) }. + merged_at: mergedAt) }. mergedMergeRequest := MergedMergeRequestMetric new user: user; glhImporter: glhImporter; - setPeriodSince: since - until: until; + setPeriodSince: since until: until; over: Week. "When" result := mergedMergeRequest calculate. "Then" - self assert: result equals: 3 + self assert: result equals: 0 ] { #category : #tests } diff --git a/src/GitLabHealth-Model-Analysis/MergeRequestMergedByUserMetric.class.st b/src/GitLabHealth-Model-Analysis/MergeRequestMergedByUserMetric.class.st new file mode 100644 index 00000000..fe35e940 --- /dev/null +++ b/src/GitLabHealth-Model-Analysis/MergeRequestMergedByUserMetric.class.st @@ -0,0 +1,57 @@ +Class { + #name : #MergeRequestMergedByUserMetric, + #superclass : #UserMergeRequestMetric, + #instVars : [ + 'allMergeRequestDuringPeriod' + ], + #category : #'GitLabHealth-Model-Analysis' +} + +{ #category : #calculating } +MergeRequestMergedByUserMetric >> calculate [ + + | groupedByDate userMergedMergeRequests dateOver | + allMergeRequestDuringPeriod ifNil: [ self load ]. + + groupedByDate := self setupGroupedDate. + + "filter to see the MR associated to one user" + userMergedMergeRequests := allMergeRequestDuringPeriod select: [ + :userMergeRequest | + userMergeRequest merge_user isNotNil + and: [ + userMergeRequest merge_user id = user id ] ]. + + "filter to reject MR that are not merge" + userMergedMergeRequests := userMergedMergeRequests reject: [ :mr | + mr merged_at isNil ]. + + userMergedMergeRequests do: [ :userMergeRequest | "here we look at the specific date of its merged" + dateOver := self transformDate: userMergeRequest merged_at to: over. + groupedByDate + at: dateOver printString + ifPresent: [ :value | value add: userMergeRequest ] ]. + groupedByDate := groupedByDate collect: [ :group | group size ]. + + ^ groupedByDate average asFloat +] + +{ #category : #accessing } +MergeRequestMergedByUserMetric >> description [ + + ^ 'number of merge request merged by this user' +] + +{ #category : #loading } +MergeRequestMergedByUserMetric >> load [ + + allMergeRequestDuringPeriod := self + loadMergeRequestsSince: (period at: #since) + until: (period at: #until) +] + +{ #category : #accessing } +MergeRequestMergedByUserMetric >> name [ + + ^ 'mergeRequestMergedByThisUser' +] diff --git a/src/GitLabHealth-Model-Analysis/MergedMergeRequestMetric.class.st b/src/GitLabHealth-Model-Analysis/MergedMergeRequestMetric.class.st index 5c94dcf5..5357c35f 100644 --- a/src/GitLabHealth-Model-Analysis/MergedMergeRequestMetric.class.st +++ b/src/GitLabHealth-Model-Analysis/MergedMergeRequestMetric.class.st @@ -1,3 +1,9 @@ +" +I compute the case where an MR has for author the _user_, and has been merged + +If the merge is merged by someone else, it counts since my _user_ created the MR +If the merge is created by someone else, and my _user_ merge it, it does not cound (the important is that the _user_ created it) +" Class { #name : #MergedMergeRequestMetric, #superclass : #UserMergeRequestMetric, @@ -7,22 +13,17 @@ Class { { #category : #calculating } MergedMergeRequestMetric >> calculate [ - | groupedByDate userMergedMergeRequests dateOver | - userMergeRequests ifNil: [ self load ]. + | groupedByDate dateOver | + userMergeRequests ifNil: [ self load ]. groupedByDate := self setupGroupedDate. - "filter to see the MR associated to one user" - userMergedMergeRequests := userMergeRequests select: [ - :userMergeRequest | - userMergeRequest merge_user isNotNil - and: [ userMergeRequest merge_user id = user id ] ]. - + "filter to reject MR that are not merge" - userMergedMergeRequests := userMergedMergeRequests reject: [ :mr | mr merged_at isNil ]. - - userMergedMergeRequests do: [ :userMergeRequest | - "here we look at the specific date of its merged" + userMergeRequests := userMergeRequests reject: [ :mr | + mr merged_at isNil ]. + + userMergeRequests do: [ :userMergeRequest | "here we look at the specific date of its merged" dateOver := self transformDate: userMergeRequest merged_at to: over. groupedByDate at: dateOver printString @@ -35,15 +36,7 @@ MergedMergeRequestMetric >> calculate [ { #category : #accessing } MergedMergeRequestMetric >> description [ - ^ 'number of merge request merged by this user' -] - -{ #category : #loading } -MergedMergeRequestMetric >> load [ - - userMergeRequests := self - loadMergeRequestsSince: (period at: #since) - until: (period at: #until) + ^ 'number of merge request created by this user that is merged' ] { #category : #accessing } diff --git a/src/GitLabHealth-Model-Generator/GLHMetamodelGenerator.class.st b/src/GitLabHealth-Model-Generator/GLHMetamodelGenerator.class.st index 64d64f73..479ecc31 100644 --- a/src/GitLabHealth-Model-Generator/GLHMetamodelGenerator.class.st +++ b/src/GitLabHealth-Model-Generator/GLHMetamodelGenerator.class.st @@ -22,6 +22,8 @@ Class { 'deletion', 'lineOfCode', 'note', + 'notePosition', + 'noteSuggestion', 'entity', 'release', 'tag', @@ -176,6 +178,13 @@ GLHMetamodelGenerator >> defineClasses [ comment: 'a note (a diff) proposed in a Merge Request; can be accepted, modified or deleted'. + notePosition := builder + newClassNamed: #NotePosition + comment: 'indicate the position of a note'. + + noteSuggestion := builder + newClassNamed: #NoteSuggestion + comment: 'suggestion of code made in a note'. tag := builder newClassNamed: #Tag @@ -187,16 +196,16 @@ GLHMetamodelGenerator >> defineClasses [ newClassNamed: #Release comment: 'a Release is typically associated with a tag and provide additional metadata and assets that can be distributed to users'. - + issue := builder newClassNamed: #Issue comment: 'an Issues help collaboration within a team to plan, track, and deliver work'. - + milestone := builder - newClassNamed: #Milestone - comment: - 'a Milestone is use to track progress on groups of issues or pull requests in a repository'. + newClassNamed: #Milestone + comment: + 'a Milestone is use to track progress on groups of issues or pull requests in a repository' ] { #category : #definition } @@ -214,11 +223,13 @@ GLHMetamodelGenerator >> defineHierarchy [ diff --|> #TNamedEntity. job --|> #TNamedEntity. note --|> #TNamedEntity. - + notePosition --|> #TNamedEntity. + noteSuggestion --|> #TNamedEntity. + tRef --|> #TNamedEntity. - tRef <|-- commit . - tRef <|-- branch . - tRef <|-- tag . + tRef <|-- commit. + tRef <|-- branch. + tRef <|-- tag. change --|> #TNamedEntity. change <|-- addition. @@ -227,7 +238,7 @@ GLHMetamodelGenerator >> defineHierarchy [ mergeRequest --|> #TNamedEntity. issue --|> #TNamedEntity. - milestone --|> #TNamedEntity. + milestone --|> #TNamedEntity ] { #category : #definition } @@ -248,12 +259,14 @@ GLHMetamodelGenerator >> defineProperties [ self mergeRequestProperties. self diffRangeProperties. self noteProperties. + self notePositionProperties. + self noteSuggestionProperties. self tagProperties. self releaseProperties. self issueProperties. self milestoneProperties. - - self tRefProperties. + + self tRefProperties ] { #category : #definition } @@ -289,7 +302,10 @@ GLHMetamodelGenerator >> defineRelations [ self issueRelations. self mergeRequestsRelations. self noteRelations. - self milestoneRelations. + self notePositionRelations. + self noteSuggestionRelations. + self milestoneRelations. + self notePositionRelations ] { #category : #definition } @@ -504,6 +520,23 @@ GLHMetamodelGenerator >> milestoneRelations [ (milestone property: #mergeRequest) <>-* (mergeRequest property: #milestone). ] +{ #category : #notes } +GLHMetamodelGenerator >> notePositionProperties [ + + notePosition property: #file_path type: #String. + notePosition property: #original_file_path type: #String. + notePosition property: #start_line type: #Number. + notePosition property: #start_line_type type: #String. + notePosition property: #end_line type: #Number. + notePosition property: #end_line_type type: #String. +] + +{ #category : #notes } +GLHMetamodelGenerator >> notePositionRelations [ + + (notePosition property: #note) - (note property: #position) +] + { #category : #notes } GLHMetamodelGenerator >> noteProperties [ note property: #id type: #Number. @@ -534,10 +567,26 @@ GLHMetamodelGenerator >> noteProperties [ ] -{ #category : #'merge requests' } +{ #category : #notes } GLHMetamodelGenerator >> noteRelations [ - (note property: #mergeRequest) *- (mergeRequest property: #note) + (note property: #mergeRequest) *- (mergeRequest property: #note). + (note property: #position) - (notePosition property: #note) +] + +{ #category : #notes } +GLHMetamodelGenerator >> noteSuggestionProperties [ + + noteSuggestion property: #from_line type: #Number. + noteSuggestion property: #to_line type: #Number. + noteSuggestion property: #from_content type: #String. + noteSuggestion property: #to_content type: #String. +] + +{ #category : #notes } +GLHMetamodelGenerator >> noteSuggestionRelations [ + + (noteSuggestion property: #note) *- (note property: #suggestions) ] { #category : #pipelines } diff --git a/src/GitLabHealth-Model-Importer-Tests/GitlabMergeRequestsMock.class.st b/src/GitLabHealth-Model-Importer-Tests/GitlabMergeRequestsMock.class.st index 5158ec7b..ddafca61 100644 --- a/src/GitLabHealth-Model-Importer-Tests/GitlabMergeRequestsMock.class.st +++ b/src/GitLabHealth-Model-Importer-Tests/GitlabMergeRequestsMock.class.st @@ -180,6 +180,11 @@ GitlabMergeRequestsMock >> getAllOfProject: anUndefinedObject [ ]' } ] +{ #category : #api } +GitlabMergeRequestsMock >> getAllOfProject: project withParams: params [ + ^self getAllOfProject: project +] + { #category : #api } GitlabMergeRequestsMock >> getByPage: anInteger perPage: anInteger2 inProject: anUndefinedObject [ ^ '[ diff --git a/src/GitLabHealth-Model-Importer-Tests/GitlabModelImporterTest.class.st b/src/GitLabHealth-Model-Importer-Tests/GitlabModelImporterTest.class.st index 1aaa0ef7..0a1514e7 100644 --- a/src/GitLabHealth-Model-Importer-Tests/GitlabModelImporterTest.class.st +++ b/src/GitLabHealth-Model-Importer-Tests/GitlabModelImporterTest.class.st @@ -192,6 +192,7 @@ GitlabModelImporterTest >> setUp [ model := GLHModel new. importer glhModel: model. defaultProject := GLHProject new + name: 'default project'; repository: GLHRepository new; yourself. @@ -343,6 +344,27 @@ GitlabModelImporterTest >> testImportLatestReleaseOfProject [ self assert: element project isNotNil ] +{ #category : #'tests - merge-requests' } +GitlabModelImporterTest >> testImportMergeRequestsOfProjectSinceUntil [ + + | collection element collection2 | + collection := importer importMergeRequestsOfProject: + self defaultProject since: '04-28-2017' asDate until: '04-30-2017' asDate. + + collection2 := importer importMergeRequestsOfProject: + self defaultProject since: '04-28-2017' asDate until: '04-30-2017' asDate. + + self assert: collection isCollection. + element := collection first. + self assert: element class equals: GLHMergeRequest. + self assert: element project class equals: GLHProject. + self assert: element diffs isCollection. + self assert: element diffs first class equals: GLHDiff. + self assert: element mergeRequestCommit class equals: GLHCommit. + + self assertCollection: collection equals: collection2. +] + { #category : #'tests - notes' } GitlabModelImporterTest >> testImportNotesOfMergeRequest [ @@ -386,6 +408,156 @@ GitlabModelImporterTest >> testImportProjects [ self assert: element repository isNil. ] +{ #category : #tests } +GitlabModelImporterTest >> testImportSuggestionsFromNote [ + + | body position glhNote result suggestion | + body := ' + test + ```suggestion:-0+5 + content +```'. + + position := GLHNotePosition new + start_line: 2; + end_line: 2. + glhNote := GLHNote new + body: body; + position: position. + + + result := importer importSuggestionsFromNote: glhNote. + + self assert: result size equals: 1. + + suggestion := result first. + self assert: suggestion from_line equals: 2. + self assert: suggestion to_line equals: 7. + self assert: suggestion to_content trim equals: 'content' +] + +{ #category : #tests } +GitlabModelImporterTest >> testImportSuggestionsFromNoteWithDifferentStartAndEndLine [ + + | body position glhNote result suggestion | + body := ' + test + ```suggestion:-2+5 + content +```'. + + position := GLHNotePosition new + start_line: 2; + end_line: 5. + glhNote := GLHNote new + body: body; + position: position. + + + result := importer importSuggestionsFromNote: glhNote. + + self assert: result size equals: 1. + + suggestion := result first. + self assert: suggestion from_line equals: 3. + self assert: suggestion to_line equals: 10. + self assert: suggestion to_content trim equals: 'content' +] + +{ #category : #tests } +GitlabModelImporterTest >> testImportSuggestionsFromNoteWithMultipleSuggestions [ + + | body position glhNote result suggestion suggestion2 | + body := ' + test + ```suggestion:-2+5 + content +``` + +```suggestion:-0+0 +```'. + + position := GLHNotePosition new + start_line: 2; + end_line: 5. + glhNote := GLHNote new + body: body; + position: position. + + + result := importer importSuggestionsFromNote: glhNote. + + self assert: result size equals: 2. + + suggestion := result first. + self assert: suggestion from_line equals: 3. + self assert: suggestion to_line equals: 10. + self assert: suggestion to_content trim equals: 'content'. + + suggestion2 := result at: 2. + self assert: suggestion2 from_line equals: 5. + self assert: suggestion2 to_line equals: 5. + self assert: suggestion2 to_content trim equals: '' +] + +{ #category : #tests } +GitlabModelImporterTest >> testImportSuggestionsInfoFromString [ + + | string result suggestion | + string := ' + some content + ```suggestion:-0+2 + test +```'. + + result := importer importSuggestionsInfoFromString: string. + + self assert: result size equals: 1. + suggestion := result first. + self assert: (suggestion at: #minus) equals: 0. + self assert: (suggestion at: #plus) equals: 2. + self assert: (suggestion at: #content) trim equals: 'test' +] + +{ #category : #tests } +GitlabModelImporterTest >> testImportSuggestionsInfoFromStringWithMultipleSuggestion [ + + | string result suggestion suggestion2 | + string := '```suggestion:-0+2 + test +``` + +```suggestion:-2+4 + +oui + +```'. + + result := importer importSuggestionsInfoFromString: string. + + self assert: result size equals: 2. + suggestion := result first. + self assert: (suggestion at: #minus) equals: 0. + self assert: (suggestion at: #plus) equals: 2. + self assert: (suggestion at: #content) trim equals: 'test'. + + suggestion2 := result at: 2. + self assert: (suggestion2 at: #minus) equals: 2. + self assert: (suggestion2 at: #plus) equals: 4. + self assert: (suggestion2 at: #content) trim equals: 'oui' +] + +{ #category : #tests } +GitlabModelImporterTest >> testImportSuggestionsInfoFromStringWithNoSuggestions [ + + | string result | + string := 'a normal comment'. + + result := importer importSuggestionsInfoFromString: string. + + self assert: result size equals: 0. +] + { #category : #'tests - tags' } GitlabModelImporterTest >> testImportTagsForProject [ @@ -497,7 +669,7 @@ GitlabModelImporterTest >> testParseNote [ "confidential": false, "internal": false }]'. - notesArray := importer parseNoteJson: jsonNote. + notesArray := importer parseNotesResult: jsonNote. note := notesArray first. self assert: notesArray size equals: 1. self assert: note body equals: 'Comment for MR' diff --git a/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st b/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st index a0006833..cf78cb76 100644 --- a/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st +++ b/src/GitLabHealth-Model-Importer/GitlabModelImporter.class.st @@ -353,15 +353,76 @@ GitlabModelImporter >> configureReaderForMergeRequest: reader [ object cacheAt: #mergeUserID put: (value at: #id) ] ] ] ] -{ #category : #'private - parsing' } +{ #category : #'private - configure reader' } GitlabModelImporter >> configureReaderForNote: reader [ super configureReaderForNote: reader. reader for: GLHNote do: [ :mapping | + mapping + mapProperty: #author + getter: [ ] + setter: [ :note :rawUser | + note cacheAt: #userID put: (rawUser at: #id) ]. + (mapping mapInstVar: #created_at) valueSchema: DateAndTime. - (mapping mapInstVar: #updated_at) valueSchema: DateAndTime ] + (mapping mapInstVar: #updated_at) valueSchema: DateAndTime. + + (mapping mapInstVar: #position) valueSchema: GLHNotePosition ] +] + +{ #category : #'private - configure reader' } +GitlabModelImporter >> configureReaderForNotePosition: reader [ + + super configureReaderForNotePosition: reader. + + reader for: GLHNotePosition do: [ :mapping | + mapping mapInstVar: #file_path to: #new_path. + mapping mapInstVar: #original_file_path to: #old_path. + + mapping + mapProperty: #new_line + getter: [ ] + setter: [ :notePosition :newLine | + notePosition start_line: newLine. + notePosition start_line_type: 'new'. + notePosition end_line: newLine. + notePosition end_line_type: 'new'. ]. + + mapping + mapProperty: #old_line + getter: [ ] + setter: [ :notePosition :oldLine | + notePosition start_line: oldLine. + notePosition start_line_type: 'old'. + notePosition end_line: oldLine. + notePosition end_line_type: 'old'. + ]. + + mapping + mapProperty: #line_range + getter: [ ] + setter: [ :notePosition :rawLineRange | + rawLineRange + ifNotNil: [ + | start startType startLine end endType endLine | + start := rawLineRange at: #start. + startType := start at: #type. + startLine := (start at: #type) = 'new' + ifTrue: [ start at: #new_line ] + ifFalse: [ start at: #old_line ]. + notePosition start_line_type: (start at: #type). + notePosition start_line: startLine. + + + end := rawLineRange at: #end. + endType := end at: #type. + endLine := (end at: #type) = 'new' + ifTrue: [ end at: #new_line ] + ifFalse: [ end at: #old_line ]. + notePosition end_line_type: (end at: #type). + notePosition end_line: endLine ] ] ] ] { #category : #'private - configure reader' } @@ -1333,7 +1394,7 @@ GitlabModelImporter >> importMergeRequestsOfProject: aGLHProject since: fromDate aGLHProject mergeRequests do: [ :mr | self importDiffOfMergeRequest: mr ] ]. - self glhModel + mergeRequests := self glhModel addAll: mergeRequests unless: (self blockEqualityOn: #iid). @@ -1399,21 +1460,40 @@ GitlabModelImporter >> importMergeResquestMerger: aGLPHEMergeRequest [ { #category : #'import - notes' } GitlabModelImporter >> importNotesOfMergeRequest: mergeRequest [ + | results notes | - - results := self repoApi notes allInMergeRequest: mergeRequest iid ofProject: mergeRequest project id. - - notes := results collect: [ :note | - self parseNoteJson: note ]. - "notes := self parseNoteJson: results." - notes do: [ :tabNotes | tabNotes do: [ :note | - note author: (self importUser: (note author at: #id)). - note name: note id asString]. ]. - notes := notes flattened. - notes := self glhModel addAll: notes unless: self blockOnIdEquality. - notes := mergeRequest note addAll: notes unless: self blockOnIdEquality. - ^notes - + results := self repoApi notes + allInMergeRequest: mergeRequest iid + ofProject: mergeRequest project id. + + notes := (results collect: [ :rawNotes | + | items | + items := self parseNotesResult: rawNotes. + (items select: [ :item | item position isNotNil ]) do: [ + :item | + | position | + position := self glhModel + add: item position + unless: [ :existing :new | + new note isNotNil and: [ + existing note id = new note id ] ]. + position note: item ]. + items ]) flattened. + + notes := mergeRequest note + addAll: notes + unless: self blockOnIdEquality. + notes := self glhModel addAll: notes unless: self blockOnIdEquality. + + notes do: [ :note | " self haltIf: [ note id = 553666 ]." + note + cacheAt: #userID + ifPresent: [ :id | note author: (self importUser: id) ]. + note name: note id asString. + + note suggestions: (self importSuggestionsFromNote: note) ]. + + ^ notes ] { #category : #'import - commits' } @@ -1617,6 +1697,66 @@ GitlabModelImporter >> importSZZFromCommit: aCommit [ ^ szzCommits ] +{ #category : #'import - notes' } +GitlabModelImporter >> importSuggestionsFromNote: glhNote [ + + | suggestions suggestion fromLine toLine suggestionsInfo | + glhNote position ifNil: [ ^ { } ]. + suggestions := MooseGroup new. + + + suggestionsInfo := self importSuggestionsInfoFromString: glhNote body. + + suggestions := suggestionsInfo collect: [ :suggestionInfo | + suggestion := GLHNoteSuggestion new. + fromLine := glhNote position end_line + - (suggestionInfo at: #minus). + toLine := glhNote position end_line + + (suggestionInfo at: #plus). + suggestion from_line: fromLine. + suggestion to_line: toLine. + suggestion to_content: (suggestionInfo at: #content). + suggestion ]. + + ^ suggestions +] + +{ #category : #'import - notes' } +GitlabModelImporter >> importSuggestionsInfoFromString: string [ + + | suggestionsInfo regex result regex2 suggestionInfo | + suggestionsInfo := OrderedCollection new. + regex := '```suggestion' asRegex. + result := regex matchesIn: string. + + result isEmpty ifTrue: [ ^ suggestionsInfo ]. + + result size > 1 ifTrue: [ + | start end firstPart secondPart | + start := string findString: '```suggestion'. + end := (string findString: '```' startingAt: start + 2) + 2. + + firstPart := string copyFrom: 1 to: end. + secondPart := string copyFrom: end to: string size. + + suggestionsInfo addAll: + (self importSuggestionsInfoFromString: firstPart). + suggestionsInfo addAll: + (self importSuggestionsInfoFromString: secondPart). + ^ suggestionsInfo ]. + + regex2 := '```suggestion\:-(\d+)\+(\d+)(.*)```' asRegex. + regex2 search: string. + + suggestionInfo := { + (#minus -> (regex2 subexpression: 2) asNumber). + (#plus -> (regex2 subexpression: 3) asNumber). + (#content -> (regex2 subexpression: 4)) } + asDictionary. + + ^ { suggestionInfo } +] + { #category : #'import - tags' } GitlabModelImporter >> importTagsForProject: aProject [ |results tags | @@ -1922,10 +2062,11 @@ GitlabModelImporter >> parseMergeRequestsResult: result [ ] { #category : #'private - parsing' } -GitlabModelImporter >> parseNoteJson: results [ +GitlabModelImporter >> parseNotesResult: results [ | reader | "Créer un lecteur JSON" + reader := generalReader on: results readStream. "Corriger la conversion des dates" @@ -1933,7 +2074,7 @@ GitlabModelImporter >> parseNoteJson: results [ mapping decoder: [ :string | DateAndTime readFrom: string readStream ] ]." - + ^ reader nextAs: #ArrayOfNote ] diff --git a/src/GitLabHealth-Model/GLHIssue.class.st b/src/GitLabHealth-Model/GLHIssue.class.st index 2d6fc7bb..80320c00 100644 --- a/src/GitLabHealth-Model/GLHIssue.class.st +++ b/src/GitLabHealth-Model/GLHIssue.class.st @@ -27,8 +27,8 @@ an Issues help collaboration within a team to plan, track, and deliver work | `description` | `String` | nil | | | `due_date` | `Object` | nil | | | `id` | `Number` | nil | | -| `name` | `String` | nil | Basic name of the entity, not full reference.| | `name` | `String` | nil | | +| `name` | `String` | nil | Basic name of the entity, not full reference.| | `state` | `String` | nil | | | `updated_at` | `Object` | nil | | diff --git a/src/GitLabHealth-Model/GLHNote.class.st b/src/GitLabHealth-Model/GLHNote.class.st index 032e80fa..89c1a2bf 100644 --- a/src/GitLabHealth-Model/GLHNote.class.st +++ b/src/GitLabHealth-Model/GLHNote.class.st @@ -8,6 +8,10 @@ a note (a diff) proposed in a Merge Request; can be accepted, modified or delete | Relation | Origin | Opposite | Type | Comment | |---| | `mergeRequest` | `GLHNote` | `note` | `GLHMergeRequest` | | +| `position` | `GLHNote` | `note` | `GLHNotePosition` | | +| `position` | `GLHNote` | `note` | `GLHNotePosition` | | +| `position` | `GLHNote` | `note` | `GLHNotePosition` | | +| `suggestions` | `GLHNote` | `note` | `GLHNoteSuggestion` | | ## Properties @@ -56,7 +60,9 @@ Class { '#internal => FMProperty', '#imported => FMProperty', '#imported_from => FMProperty', - '#mergeRequest => FMOne type: #GLHMergeRequest opposite: #note' + '#mergeRequest => FMOne type: #GLHMergeRequest opposite: #note', + '#position => FMOne type: #GLHNotePosition opposite: #note', + '#suggestions => FMMany type: #GLHNoteSuggestion opposite: #note' ], #category : #'GitLabHealth-Model-Entities' } @@ -70,6 +76,12 @@ GLHNote class >> annotation [ ^ self ] +{ #category : #adding } +GLHNote >> addSuggestion: anObject [ + + ^ self suggestions add: anObject +] + { #category : #accessing } GLHNote >> attachment [ @@ -253,6 +265,21 @@ GLHNote >> noteable_type: anObject [ noteable_type := anObject ] +{ #category : #accessing } +GLHNote >> position [ + "Relation named: #position type: #GLHNotePosition opposite: #note" + + + ^ position +] + +{ #category : #accessing } +GLHNote >> position: anObject [ + + + position := anObject +] + { #category : #accessing } GLHNote >> project_id [ @@ -281,6 +308,22 @@ GLHNote >> resolvable: anObject [ resolvable := anObject ] +{ #category : #accessing } +GLHNote >> suggestions [ + "Relation named: #suggestions type: #GLHNoteSuggestion opposite: #note" + + + + ^ suggestions +] + +{ #category : #accessing } +GLHNote >> suggestions: anObject [ + + + suggestions value: anObject +] + { #category : #accessing } GLHNote >> system [ diff --git a/src/GitLabHealth-Model/GLHNotePosition.class.st b/src/GitLabHealth-Model/GLHNotePosition.class.st new file mode 100644 index 00000000..ee76ee38 --- /dev/null +++ b/src/GitLabHealth-Model/GLHNotePosition.class.st @@ -0,0 +1,153 @@ +" +indicate the position of a note + +## Relations +====================== + +### Other +| Relation | Origin | Opposite | Type | Comment | +|---| +| `note` | `GLHNotePosition` | `position` | `GLHNote` | | +| `note` | `GLHNotePosition` | `position` | `GLHNote` | | +| `note` | `GLHNotePosition` | `position` | `GLHNote` | | + + +## Properties +====================== + +| Name | Type | Default value | Comment | +|---| +| `end_line` | `Number` | nil | | +| `end_line_type` | `String` | nil | | +| `file_path` | `String` | nil | | +| `name` | `String` | nil | Basic name of the entity, not full reference.| +| `original_file_path` | `String` | nil | | +| `start_line` | `Number` | nil | | +| `start_line_type` | `String` | nil | | + +" +Class { + #name : #GLHNotePosition, + #superclass : #GLHEntity, + #traits : 'FamixTNamedEntity', + #classTraits : 'FamixTNamedEntity classTrait', + #instVars : [ + '#file_path => FMProperty', + '#original_file_path => FMProperty', + '#start_line => FMProperty', + '#start_line_type => FMProperty', + '#end_line => FMProperty', + '#end_line_type => FMProperty', + '#note => FMOne type: #GLHNote opposite: #position' + ], + #category : #'GitLabHealth-Model-Entities' +} + +{ #category : #meta } +GLHNotePosition class >> annotation [ + + + + + ^ self +] + +{ #category : #accessing } +GLHNotePosition >> end_line [ + + + + ^ end_line +] + +{ #category : #accessing } +GLHNotePosition >> end_line: anObject [ + + end_line := anObject +] + +{ #category : #accessing } +GLHNotePosition >> end_line_type [ + + + + ^ end_line_type +] + +{ #category : #accessing } +GLHNotePosition >> end_line_type: anObject [ + + end_line_type := anObject +] + +{ #category : #accessing } +GLHNotePosition >> file_path [ + + + + ^ file_path +] + +{ #category : #accessing } +GLHNotePosition >> file_path: anObject [ + + file_path := anObject +] + +{ #category : #accessing } +GLHNotePosition >> note [ + "Relation named: #note type: #GLHNote opposite: #position" + + + + ^ note +] + +{ #category : #accessing } +GLHNotePosition >> note: anObject [ + + + note := anObject +] + +{ #category : #accessing } +GLHNotePosition >> original_file_path [ + + + + ^ original_file_path +] + +{ #category : #accessing } +GLHNotePosition >> original_file_path: anObject [ + + original_file_path := anObject +] + +{ #category : #accessing } +GLHNotePosition >> start_line [ + + + + ^ start_line +] + +{ #category : #accessing } +GLHNotePosition >> start_line: anObject [ + + start_line := anObject +] + +{ #category : #accessing } +GLHNotePosition >> start_line_type [ + + + + ^ start_line_type +] + +{ #category : #accessing } +GLHNotePosition >> start_line_type: anObject [ + + start_line_type := anObject +] diff --git a/src/GitLabHealth-Model/GLHNoteSuggestion.class.st b/src/GitLabHealth-Model/GLHNoteSuggestion.class.st new file mode 100644 index 00000000..5f6edc58 --- /dev/null +++ b/src/GitLabHealth-Model/GLHNoteSuggestion.class.st @@ -0,0 +1,118 @@ +" +suggestion of code made in a note + +## Relations +====================== + +### Other +| Relation | Origin | Opposite | Type | Comment | +|---| +| `note` | `GLHNoteSuggestion` | `suggestions` | `GLHNote` | | + + +## Properties +====================== + +| Name | Type | Default value | Comment | +|---| +| `from_content` | `String` | nil | | +| `from_line` | `Number` | nil | | +| `name` | `String` | nil | Basic name of the entity, not full reference.| +| `to_content` | `String` | nil | | +| `to_line` | `Number` | nil | | + +" +Class { + #name : #GLHNoteSuggestion, + #superclass : #GLHEntity, + #traits : 'FamixTNamedEntity', + #classTraits : 'FamixTNamedEntity classTrait', + #instVars : [ + '#from_line => FMProperty', + '#to_line => FMProperty', + '#from_content => FMProperty', + '#to_content => FMProperty', + '#note => FMOne type: #GLHNote opposite: #suggestions' + ], + #category : #'GitLabHealth-Model-Entities' +} + +{ #category : #meta } +GLHNoteSuggestion class >> annotation [ + + + + + ^ self +] + +{ #category : #accessing } +GLHNoteSuggestion >> from_content [ + + + + ^ from_content +] + +{ #category : #accessing } +GLHNoteSuggestion >> from_content: anObject [ + + from_content := anObject +] + +{ #category : #accessing } +GLHNoteSuggestion >> from_line [ + + + + ^ from_line +] + +{ #category : #accessing } +GLHNoteSuggestion >> from_line: anObject [ + + from_line := anObject +] + +{ #category : #accessing } +GLHNoteSuggestion >> note [ + "Relation named: #note type: #GLHNote opposite: #suggestions" + + + ^ note +] + +{ #category : #accessing } +GLHNoteSuggestion >> note: anObject [ + + + note := anObject +] + +{ #category : #accessing } +GLHNoteSuggestion >> to_content [ + + + + ^ to_content +] + +{ #category : #accessing } +GLHNoteSuggestion >> to_content: anObject [ + + to_content := anObject +] + +{ #category : #accessing } +GLHNoteSuggestion >> to_line [ + + + + ^ to_line +] + +{ #category : #accessing } +GLHNoteSuggestion >> to_line: anObject [ + + to_line := anObject +] diff --git a/src/GitLabHealth-Model/GLHTEntityCreator.trait.st b/src/GitLabHealth-Model/GLHTEntityCreator.trait.st index f50e3745..fda34086 100644 --- a/src/GitLabHealth-Model/GLHTEntityCreator.trait.st +++ b/src/GitLabHealth-Model/GLHTEntityCreator.trait.st @@ -207,6 +207,34 @@ GLHTEntityCreator >> newNoteNamed: aName [ ^ self add: (GLHNote named: aName) ] +{ #category : #'entity creation' } +GLHTEntityCreator >> newNotePosition [ + + + ^ self add: GLHNotePosition new +] + +{ #category : #'entity creation' } +GLHTEntityCreator >> newNotePositionNamed: aName [ + + + ^ self add: (GLHNotePosition named: aName) +] + +{ #category : #'entity creation' } +GLHTEntityCreator >> newNoteSuggestion [ + + + ^ self add: GLHNoteSuggestion new +] + +{ #category : #'entity creation' } +GLHTEntityCreator >> newNoteSuggestionNamed: aName [ + + + ^ self add: (GLHNoteSuggestion named: aName) +] + { #category : #'entity creation' } GLHTEntityCreator >> newPipeline [ diff --git a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st index 165980f4..84d5e01f 100644 --- a/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st +++ b/src/GitProjectHealth-Model-Importer/GitModelImporter.class.st @@ -145,9 +145,15 @@ GitModelImporter >> blockForBranchEquality [ { #category : #equality } GitModelImporter >> blockForDiffEquality [ - ^ [ :existing :new | - existing diffString size = new diffString size and: [ - existing diffString = new diffString ] ] + +^ [ :existing :new | + | sameSize sameDiff sameFile | + sameSize := existing diffString size = new diffString size. + sameDiff := existing diffString = new diffString. + sameFile := existing new_path = new new_path. + + sameSize and: [ sameDiff and: [ sameFile ] ]. + ] ] { #category : #equality } @@ -159,6 +165,20 @@ GitModelImporter >> blockForDiffRangeEquality [ existing newLineRange = new newLineRange ] ] ] ] +{ #category : #equality } +GitModelImporter >> blockForNoteSuggestionEquality [ + + +^ [ :existing :new | + | sameLine sameNote sameContent | + sameLine := existing to_line = new to_line. + sameNote := existing note id = new note id. + sameContent := existing to_content = new to_content. + sameLine and: (sameNote and: sameContent) + + ] +] + { #category : #equality } GitModelImporter >> blockOnIdEquality [ @@ -309,6 +329,15 @@ GitModelImporter >> configureReaderForNote: reader [ customMappting listOfElementSchema: GLHNote ] ] +{ #category : #'private - configure reader' } +GitModelImporter >> configureReaderForNotePosition: reader [ + + reader mapInstVarsFor: GLHNotePosition. + + reader for: #ArrayOfNotePosition customDo: [ :customMapping | + customMapping listOfElementSchema: GLHNotePosition ] +] + { #category : #'private - configure reader' } GitModelImporter >> configureReaderForPipeline: reader [ @@ -660,7 +689,7 @@ GitModelImporter >> initReader [ string ifNil: [ nil ] ifNotNil: [ DateAndTime fromString: string ] ] ]. configurators := (self class allSelectors select: [ :m | - m beginsWith: #configureReaderFor ]) asSet. + m beginsWith: #configureReaderFor ]) asSet. configurators do: [ :configureReader | self perform: configureReader with: generalReader ]