@atomist/sdm-pack-aspect
Version:
an Atomist SDM Extension Pack for visualizing drift across an organization
152 lines (137 loc) • 4.35 kB
text/typescript
/*
* Copyright © 2019 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
LocalProject,
} from "@atomist/automation-client";
import {
Aspect,
ExtractFingerprint,
fingerprintOf,
} from "@atomist/sdm-pack-fingerprint";
import * as child_process from "child_process";
import * as util from "util";
import {
bandFor,
Default,
} from "../../util/bands";
import {
AgeBands,
SizeBands,
} from "../../util/commonBands";
import { showTiming } from "../../util/showTiming";
import {
CountAspect,
CountData,
} from "../compose/commonTypes";
import { daysSince } from "./dateUtils";
const exec = util.promisify(child_process.exec);
const gitLastCommitCommand = "git log -1 --format=%cd --date=short";
export const GitRecencyType = "git-recency";
export interface GitRecencyData {
lastCommitTime: number;
}
const gitRecencyExtractor: ExtractFingerprint<GitRecencyData> =
async p => {
const r = await exec(gitLastCommitCommand, { cwd: (p as LocalProject).baseDir });
if (!r.stdout) {
return undefined;
}
const data = { lastCommitTime: new Date(r.stdout.trim()).getTime() };
return fingerprintOf({
type: GitRecencyType,
data,
});
};
/**
* Classify since last commit
*/
export const GitRecency: Aspect<GitRecencyData> = {
name: GitRecencyType,
displayName: "Recency of git activity",
baseOnly: true,
extract: gitRecencyExtractor,
toDisplayableFingerprintName: () => "Recency of git activity",
toDisplayableFingerprint: fp => {
const date = new Date(fp.data.lastCommitTime);
return lastDateToActivityBand(date);
},
stats: {
defaultStatStatus: {
entropy: false,
},
},
};
function committersCommands(commitDepth: number): string[] {
return [
`git fetch --depth=${commitDepth}`,
`git shortlog -s -n --all --max-count ${commitDepth}`,
];
}
export const GitActivesType = "git-actives";
function activeCommittersExtractor(commitDepth: number): ExtractFingerprint<CountData> {
return async p => {
const cwd = (p as LocalProject).baseDir;
const cmds = committersCommands(commitDepth);
const r = await showTiming(`commands ${cmds} in ${cwd}`, async () => {
exec(cmds[0], { cwd });
return exec(cmds[1], { cwd });
});
if (!r.stdout) {
return undefined;
}
const count = r.stdout.trim().split("\n").length;
const data = { count };
return fingerprintOf({
type: GitActivesType,
data,
});
};
}
/**
* Active committers. This is expensive as it requires cloning the
* last commitDepth commits
*/
export function gitActiveCommitters(opts: { commitDepth: number}): CountAspect {
return {
name: GitActivesType,
displayName: "Active git committers",
baseOnly: true,
extract: activeCommittersExtractor(opts.commitDepth),
toDisplayableFingerprintName: () => `Active git committers to ${opts.commitDepth} commits`,
toDisplayableFingerprint: fp => {
return bandFor<SizeBands>({
low: { upTo: 4 },
medium: { upTo: 12 },
high: Default,
}, fp.data.count, { includeNumber: true });
},
stats: {
defaultStatStatus: {
entropy: false,
},
basicStatsPath: "count",
},
};
}
function lastDateToActivityBand(date: Date): string {
const days = daysSince(date);
return bandFor<AgeBands>({
current: { upTo: 30 },
recent: { upTo: 200 },
ancient: { upTo: 500 },
prehistoric: Default,
}, days, { includeNumber: true });
}