@atomist/org-visualizer
Version:
Organization Visualizer using Atomist project scanning
251 lines • 13.5 kB
JavaScript
;
/*
* 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.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const automation_client_1 = require("@atomist/automation-client");
const sdm_pack_fingerprints_1 = require("@atomist/sdm-pack-fingerprints");
const Ideal_1 = require("@atomist/sdm-pack-fingerprints/lib/machine/Ideal");
const bodyParser = require("body-parser");
const _ = require("lodash");
const ReactDOMServer = require("react-dom/server");
const serveStatic = require("serve-static");
const org_1 = require("../../views/org");
const project_1 = require("../../views/project");
const projectList_1 = require("../../views/projectList");
const sunburstPage_1 = require("../../views/sunburstPage");
const topLevelPage_1 = require("../../views/topLevelPage");
const DefaultAspectRegistry_1 = require("../aspect/DefaultAspectRegistry");
function renderStaticReactNode(body, title, extraScripts) {
return ReactDOMServer.renderToStaticMarkup(topLevelPage_1.TopLevelPage({
bodyContent: body,
pageTitle: title,
extraScripts,
}));
}
/**
* Add the org page route to Atomist SDM Express server.
* @return {ExpressCustomizer}
*/
function orgPage(aspectRegistry, store, httpClientFactory) {
const orgRoute = "/org";
return {
routesToSuggestOnStartup: [{ title: "Org Visualizations", route: orgRoute }],
customizer: (express, ...handlers) => {
express.use(bodyParser.json()); // to support JSON-encoded bodies
express.use(bodyParser.urlencoded({
extended: true,
}));
express.use(serveStatic("public", { index: false }));
express.use(serveStatic("dist", { index: false }));
/* redirect / to the org page. This way we can go right here
* for now, and later make a higher-level page if we want.
*/
express.get("/", ...handlers, (req, res) => __awaiter(this, void 0, void 0, function* () {
res.redirect(orgRoute);
}));
/* the org page itself */
express.get(orgRoute, ...handlers, (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const repos = yield store.loadInWorkspace(req.query.workspace || req.params.workspace_id);
const fingerprintUsage = yield store.fingerprintUsageForType("*");
const ideals = yield aspectRegistry.idealStore.loadIdeals("*");
const aspectsEligibleForDisplay = aspectRegistry.aspects.filter(a => !!a.displayName)
.filter(a => fingerprintUsage.some(fu => fu.type === a.name));
const importantAspects = _.sortBy(aspectsEligibleForDisplay, a => a.displayName)
.map(aspect => {
const fingerprintsForThisAspect = fingerprintUsage.filter(fu => fu.type === aspect.name);
return {
aspect,
fingerprints: fingerprintsForThisAspect
.map(fp => formatFingerprintUsageForDisplay(aspect, ideals, fp)),
};
});
const unfoundAspects = aspectRegistry.aspects
.filter(f => !!f.displayName)
.filter(f => !fingerprintUsage.some(fu => fu.type === f.name));
res.send(renderStaticReactNode(org_1.OrgExplorer({
projectsAnalyzed: repos.length,
importantAspects,
unfoundAspects,
projects: repos.map(r => (Object.assign({}, r.repoRef, { id: r.id }))),
})));
}
catch (e) {
automation_client_1.logger.error(e.stack);
res.status(500).send("failure");
}
}));
/* Project list page */
express.get("/projects", ...handlers, (req, res) => __awaiter(this, void 0, void 0, function* () {
const allAnalysisResults = yield store.loadInWorkspace(req.query.workspace || req.params.workspace_id);
// optional query parameter: owner
const relevantAnalysisResults = allAnalysisResults.filter(ar => req.query.owner ? ar.analysis.id.owner === req.query.owner : true);
if (relevantAnalysisResults.length === 0) {
return res.send(`No matching repos for organization ${req.query.owner}`);
}
const projectsForDisplay = relevantAnalysisResults.map(ar => (Object.assign({ id: ar.id }, ar.analysis.id)));
return res.send(renderStaticReactNode(projectList_1.ProjectList({ projects: projectsForDisplay }), "Project list"));
}));
/* the project page */
express.get("/project", ...handlers, (req, res) => __awaiter(this, void 0, void 0, function* () {
const id = req.query.id;
const analysisResult = yield store.loadById(id);
if (!analysisResult) {
return res.send(`No project at ${JSON.stringify(id)}`);
}
const aspectsAndFingerprints = yield projectFingerprints(aspectRegistry, yield store.fingerprintsForProject(id));
// assign style based on ideal
const ffd = aspectsAndFingerprints.map(aspectAndFingerprints => (Object.assign({}, aspectAndFingerprints, { fingerprints: aspectAndFingerprints.fingerprints.map(fp => (Object.assign({}, fp, { idealDisplayString: displayIdeal(fp, aspectAndFingerprints.aspect), style: displayStyleAccordingToIdeal(fp) }))) })));
return res.send(renderStaticReactNode(project_1.ProjectExplorer({
analysisResult,
aspects: _.sortBy(ffd.filter(f => !!f.aspect.displayName), f => f.aspect.displayName),
})));
}));
/* the query page */
express.get("/query", ...handlers, (req, res) => __awaiter(this, void 0, void 0, function* () {
let dataUrl;
let currentIdealForDisplay;
const possibleIdealsForDisplay = [];
const workspaceId = req.query.workspaceId || "*";
const queryString = jsonToQueryString(req.query);
if (req.query.skew) {
dataUrl = `/api/v1/${workspaceId}/drift`;
}
else {
dataUrl = !!req.query.filter ?
`/api/v1/${workspaceId}/filter/${req.query.name}?${queryString}` :
`/api/v1/${workspaceId}/fingerprint/${encodeURIComponent(req.query.type)}/${encodeURIComponent(req.query.name)}?byOrg=${req.query.byOrg === "true"}&presence=${req.query.presence === "true"}&progress=${req.query.progress === "true"}&otherLabel=${req.query.otherLabel === "true"}&trim=${req.query.trim === "true"}`;
}
let tree;
const fullUrl = `http://${req.get("host")}${dataUrl}`;
try {
const result = yield httpClientFactory.create().exchange(fullUrl, {
retry: { retries: 0 },
});
tree = result.body;
automation_client_1.logger.info(`From ${fullUrl}, got: ` + JSON.stringify(tree.circles, undefined, 2));
}
catch (e) {
automation_client_1.logger.error(`Failure fetching sunburst data from ${fullUrl}: ` + e.message);
}
// tslint:disable-next-line
const aspect = aspectRegistry.aspectOf(req.query.type);
const fingerprintDisplayName = DefaultAspectRegistry_1.defaultedToDisplayableFingerprintName(aspect)(req.query.name);
function idealDisplayValue(ideal) {
if (!ideal) {
return undefined;
}
if (!sdm_pack_fingerprints_1.isConcreteIdeal(ideal)) {
return { displayValue: "eliminate" };
}
return { displayValue: DefaultAspectRegistry_1.defaultedToDisplayableFingerprint(aspect)(ideal.ideal) };
}
currentIdealForDisplay = idealDisplayValue(yield aspectRegistry.idealStore
.loadIdeal("local", req.query.type, req.query.name));
automation_client_1.logger.info("Data url=%s", dataUrl);
res.send(renderStaticReactNode(sunburstPage_1.SunburstPage({
workspaceId,
fingerprintDisplayName,
currentIdeal: currentIdealForDisplay,
possibleIdeals: possibleIdealsForDisplay,
query: req.params.query,
dataUrl,
tree,
}), "Atomist Aspect", [
"/sunburstScript-bundle.js",
]));
}));
},
};
}
exports.orgPage = orgPage;
function jsonToQueryString(json) {
return Object.keys(json).map(key => encodeURIComponent(key) + "=" + encodeURIComponent(json[key])).join("&");
}
exports.jsonToQueryString = jsonToQueryString;
function displayIdeal(fingerprint, aspect) {
if (idealIsDifferentFromActual(fingerprint)) {
return DefaultAspectRegistry_1.defaultedToDisplayableFingerprint(aspect)(fingerprint.ideal.ideal);
}
if (idealIsElimination(fingerprint)) {
return "eliminate";
}
return "";
}
function idealIsElimination(fingerprint) {
return fingerprint.ideal && !sdm_pack_fingerprints_1.isConcreteIdeal(fingerprint.ideal);
}
function idealIsDifferentFromActual(fingerprint) {
return fingerprint.ideal && sdm_pack_fingerprints_1.isConcreteIdeal(fingerprint.ideal) && fingerprint.ideal.ideal.sha !== fingerprint.sha;
}
function idealIsSameAsActual(fingerprint) {
return fingerprint.ideal && sdm_pack_fingerprints_1.isConcreteIdeal(fingerprint.ideal) && fingerprint.ideal.ideal.sha === fingerprint.sha;
}
function displayStyleAccordingToIdeal(fingerprint) {
const redStyle = { color: "red" };
const greenStyle = { color: "green" };
if (idealIsSameAsActual(fingerprint)) {
return greenStyle;
}
if (idealIsDifferentFromActual(fingerprint)) {
return redStyle;
}
if (idealIsElimination(fingerprint)) {
return redStyle;
}
return {};
}
function projectFingerprints(fm, allFingerprintsInOneProject) {
return __awaiter(this, void 0, void 0, function* () {
const result = [];
for (const aspect of fm.aspects) {
const originalFingerprints = _.sortBy(allFingerprintsInOneProject.filter(fp => aspect.name === (fp.type || fp.name)), fp => fp.name);
if (originalFingerprints.length > 0) {
const fingerprints = [];
for (const fp of originalFingerprints) {
fingerprints.push(Object.assign({}, fp, {
// ideal: await this.opts.idealResolver(fp.name),
displayValue: DefaultAspectRegistry_1.defaultedToDisplayableFingerprint(aspect)(fp), displayName: DefaultAspectRegistry_1.defaultedToDisplayableFingerprintName(aspect)(fp.name) }));
}
result.push({
aspect,
fingerprints,
});
}
}
return result;
});
}
function idealMatchesFingerprint(id, fp) {
const c = Ideal_1.idealCoordinates(id);
return c.type === fp.type && c.name === fp.name;
}
function formatFingerprintUsageForDisplay(aspect, ideals, fp) {
const foundIdeal = ideals.find(ide => idealMatchesFingerprint(ide, fp));
const ideal = foundIdeal && sdm_pack_fingerprints_1.isConcreteIdeal(foundIdeal) && aspect.toDisplayableFingerprint ?
{ displayValue: aspect.toDisplayableFingerprint(foundIdeal.ideal) }
: undefined;
return Object.assign({}, fp, { ideal, displayName: DefaultAspectRegistry_1.defaultedToDisplayableFingerprintName(aspect)(fp.name), entropy: sdm_pack_fingerprints_1.supportsEntropy(aspect) ? fp.entropy : undefined });
}
//# sourceMappingURL=orgPage.js.map