Skip to content

Commit

Permalink
Merge pull request #56 from fraunhofer-iem/feature/FaultCorrectionCap…
Browse files Browse the repository at this point in the history
…ability

Fault correction capability KPI implemented
  • Loading branch information
janniclas authored Jan 20, 2022
2 parents 4267082 + ebd11ef commit ff29b0b
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 39 deletions.
10 changes: 5 additions & 5 deletions src/database/statistic.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,13 +193,13 @@ export class StatisticService {
return acc + curr;
}, 0) / numberOfElements;

const variance = numberOfFiles.reduce((acc, curr) => {
const constiance = numberOfFiles.reduce((acc, curr) => {
return acc + Math.pow(curr - avg, 2) / numberOfElements;
}, 0);

const standardDeviation = Math.sqrt(variance);
const standardDeviation = Math.sqrt(constiance);
this.logger.log(
`variance ${variance} standard deviation ${standardDeviation}`,
`constiance ${constiance} standard deviation ${standardDeviation}`,
);
this.logger.log(
`In average ${avg} files are changed with each pull request`,
Expand All @@ -208,7 +208,7 @@ export class StatisticService {
return {
numberOfFiles,
avg,
variance,
constiance,
standardDeviation,
};
}
Expand Down Expand Up @@ -658,7 +658,7 @@ export class StatisticService {
timeFrame?: number,
) {
const limit = userLimit ? userLimit : 100;
// This variable defines the fixed time set for the bugs to be resolved.
// This constiable defines the fixed time set for the bugs to be resolved.
// Since such an information cannot be derived from git (milestones can be looked at,
// however they are hardly properly utilized by most projects).
// Although information like this can be derived from Jira, but for now, it is manually defined.
Expand Down
55 changes: 52 additions & 3 deletions src/database/statistics/faultCorrection.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { Model } from 'mongoose';
import { Issue, Release } from 'src/github-api/model/PullRequest';
import { RepositoryNameDto } from 'src/github-api/model/Repository';
import { RepositoryDocument } from '../schemas/repository.schema';
import { calculateAvgRate, mapReleasesToIssues } from './issueUtil';
import {
calculateAvgClosedInTimeRate,
calculateAvgClosedOpenRate,
mapReleasesToIssues,
} from './issueUtil';
import { getIssueQuery } from './lib/issueQuery';
import { getReleaseQuery } from './lib/releaseQuery';
import { transformMapToObject } from './lib/transformMapToObject';
Expand Down Expand Up @@ -46,9 +50,54 @@ export class FaultCorrection {

const releaseIssueMap = mapReleasesToIssues(releases, issues);

const { avgRate, rateMap } = calculateAvgClosedOpenRate(releaseIssueMap);
return {
avgRate: calculateAvgRate(releaseIssueMap),
rawData: transformMapToObject(releaseIssueMap),
avgRate: avgRate,
rawData: transformMapToObject(rateMap),
};
}

/**
* The Fault Correction Capability describes the development team's capability to respond to bug reports.
* In more detail, it assesses the rate of faults corrected within the time frame the organization aims to adhere to for fault correction.
* For this qualitative indicator we take all issues labeled `bug` (or some other equivalent label) into consideration that have been resolved since the previous release.
*
* (release, issues[label = "bug", state = "closed"], T_bug) => {
* bugs = [ bug for bug in issues
* if bug.closed_at <= release.created_at and
* bug.closed_at >= release.previous().created_at ]
*
* bugs_corrected_in_time = [ bug for bug in bugs
* if bug.closed_at - bug.created_at <= T_bug ]
*
* return |bugs_corrected_in_time| / | bugs |
* }
*
* @param repoIdent
* @param userLimit
* @returns
*/
async faultCorrectionCapability(
repoIdent: RepositoryNameDto,
labelNames?: string[],
timeToCorrect = 1209600000,
) {
const queries = [
getReleaseQuery(this.repoModel, repoIdent).exec(),
getIssueQuery(this.repoModel, repoIdent, labelNames).exec(),
];
const promiseResults = await Promise.all(queries);

const releases = promiseResults[0] as Release[];
const issues = promiseResults[1] as Issue[];

const releaseIssueMap = mapReleasesToIssues(releases, issues);

const { inTime, avgRate } = calculateAvgClosedInTimeRate(
releaseIssueMap,
timeToCorrect,
);

return { avgRate: avgRate, rawData: transformMapToObject(inTime) };
}
}
8 changes: 4 additions & 4 deletions src/database/statistics/featureCompletion.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Model } from 'mongoose';
import { Issue, Release } from 'src/github-api/model/PullRequest';
import { RepositoryNameDto } from 'src/github-api/model/Repository';
import { RepositoryDocument } from '../schemas/repository.schema';
import { calculateAvgRate, mapReleasesToIssues } from './issueUtil';
import { calculateAvgClosedOpenRate, mapReleasesToIssues } from './issueUtil';
import { getIssueQuery } from './lib/issueQuery';
import { getReleaseQuery } from './lib/releaseQuery';
import { transformMapToObject } from './lib/transformMapToObject';
Expand Down Expand Up @@ -46,10 +46,10 @@ export class FeatureCompletion {
const issues = promiseResults[1] as Issue[];

const releaseIssueMap = mapReleasesToIssues(releases, issues);

const { avgRate, rateMap } = calculateAvgClosedOpenRate(releaseIssueMap);
return {
avgRate: calculateAvgRate(releaseIssueMap),
rawData: transformMapToObject(releaseIssueMap),
avgRate: avgRate,
rawData: transformMapToObject(rateMap),
};
}
}
77 changes: 62 additions & 15 deletions src/database/statistics/issueUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Issue, Release } from 'src/github-api/model/PullRequest';
export function mapReleasesToIssues(releases: Release[], issues: Issue[]) {
const issuesInTimespan = new Map<
number,
{ closed: Issue[]; open: Issue[]; rate: number; release: Release }
{ closed: Issue[]; open: Issue[]; release: Release }
>();
// we start at 1, because everything happening before the first release doesn't provide
// helpful information.
Expand All @@ -13,7 +13,6 @@ export function mapReleasesToIssues(releases: Release[], issues: Issue[]) {
issuesInTimespan.set(currRelease.id, {
open: [],
closed: [],
rate: 0,
release: currRelease,
});

Expand All @@ -40,38 +39,86 @@ export function mapReleasesToIssues(releases: Release[], issues: Issue[]) {
return issuesInTimespan;
}

export function calculateAvgRate(
export function calculateAvgClosedOpenRate(
releaseIssueMap: Map<
number,
{ closed: Issue[]; open: Issue[]; rate: number; release: Release }
{ closed: Issue[]; open: Issue[]; release: Release }
>,
) {
let sumOfRates = 0;
let noOfEmptyReleases = 0;
const rateMap = new Map<
number,
{
closed: Issue[];
open: Issue[];
release: Release;
rate: number;
}
>();
releaseIssueMap.forEach((currData) => {
const rate = calculateRate(currData)
if (rate === undefined) {
noOfEmptyReleases += 1
} else {
sumOfRates += rate
}
const rate = calculateRate(currData);
if (rate === undefined) {
noOfEmptyReleases += 1;
} else {
sumOfRates += rate;
}
rateMap.set(currData.release.id, { ...currData, rate: rate });
});

return sumOfRates / (releaseIssueMap.size - noOfEmptyReleases);
return {
rateMap: rateMap,
avgRate: sumOfRates / (releaseIssueMap.size - noOfEmptyReleases),
};
}

function calculateRate(data: {
closed: Issue[];
open: Issue[];
rate: number;
release: Release;
}) {
const noOfOpenIssues = data.open.length;
const noOfClosedIssues = data.closed.length;
if (noOfOpenIssues == 0 && noOfClosedIssues == 0) {
return undefined
return undefined;
} else {
data.rate = noOfClosedIssues / (noOfOpenIssues + noOfClosedIssues);
return noOfClosedIssues / (noOfOpenIssues + noOfClosedIssues);
}
return data.rate;
}

export function calculateAvgClosedInTimeRate(
releaseIssueMap: Map<
number,
{ closed: Issue[]; open: Issue[]; release: Release }
>,
timeToCorrect: number,
) {
const releaseInTime = new Map<
number,
{ closedInTime: Issue[]; rate: number; release: Release }
>();

let sumOfRates = 0;

releaseIssueMap.forEach((value, key) => {
const closedInTime = value.closed.filter((closedIssue) => {
const closedAt = new Date(closedIssue.closed_at).valueOf();
const createdAt = new Date(closedIssue.created_at).valueOf();

return closedAt - createdAt <= timeToCorrect;
});

const rate = closedInTime.length / value.closed.length;
releaseInTime.set(key, {
closedInTime,
rate: rate,
release: value.release,
});
sumOfRates += rate;
});

return {
inTime: releaseInTime,
avgRate: sumOfRates / releaseIssueMap.size,
};
}
16 changes: 7 additions & 9 deletions src/database/statistics/lib/transformMapToObject.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
export function transformMapToObject(
map: Map<any, Object>
) {
const json = {};
map.forEach((value, key) => {
json[key] = value;
});
return json;
}
export function transformMapToObject(map: Map<any, unknown>) {
const json = {};
map.forEach((value, key) => {
json[key] = value;
});
return json;
}
4 changes: 1 addition & 3 deletions src/github-api/github-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,7 @@ export class GithubApiService {
// 'support',
// 'awaiting response',
// ]);
return await this.featureCompletion.featureCompletionRate(repoIdent, [
'feature',
]);
return this.faultCorrection.faultCorrectionCapability(repoIdent);
//this.statisticService.faultCorrectionEfficiency(repoIdent);
// this.statisticService.workInProgress(repoIdent);
// this.devFocus.devSpreadTotal(
Expand Down

0 comments on commit ff29b0b

Please sign in to comment.