UNPKG

@atomist/org-visualizer

Version:

Organization Visualizer using Atomist project scanning

251 lines 13.5 kB
"use strict"; /* * 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