@atomist/sdm-pack-aspect
Version:
an Atomist SDM Extension Pack for visualizing drift across an organization
114 lines (103 loc) • 4.95 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 { logger } from "@atomist/automation-client";
import {
Aspect,
Ideal,
idealCoordinates,
isConcreteIdeal,
supportsEntropy,
} from "@atomist/sdm-pack-fingerprint";
import * as _ from "lodash";
import {
AspectFingerprintsForDisplay,
FingerprintForDisplay,
Overview,
} from "../../../views/overview";
import { renderStaticReactNode } from "../../../views/topLevelPage";
import { ConnectionErrorHeading } from "../../analysis/offline/persist/pgClientFactory";
import { FingerprintUsage } from "../../analysis/offline/persist/ProjectAnalysisResultStore";
import { defaultedToDisplayableFingerprintName } from "../../aspect/DefaultAspectRegistry";
import { WebAppConfig } from "./webAppConfig";
export function exposeOverviewPage(conf: WebAppConfig,
topLevelRoute: string): void {
conf.express.get(topLevelRoute, ...conf.handlers, async (req, res, next) => {
try {
const repos = await conf.store.loadInWorkspace(req.query.workspace || req.params.workspace_id, false);
const workspaceId = "*";
const fingerprintUsage = await conf.store.fingerprintUsageForType(workspaceId);
const ideals = await conf.aspectRegistry.idealStore.loadIdeals(workspaceId);
const aspectsEligibleForDisplay = conf.aspectRegistry.aspects
.filter(a => !!a.displayName)
.filter(a => fingerprintUsage.some(fu => fu.type === a.name));
const foundAspects: AspectFingerprintsForDisplay[] = _.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: Aspect[] = conf.aspectRegistry.aspects
.filter(f => !!f.displayName)
.filter(f => !fingerprintUsage.some(fu => fu.type === f.name));
const virtualProjectCount = await conf.store.virtualProjectCount(workspaceId);
res.send(renderStaticReactNode(
Overview({
projectsAnalyzed: repos.length,
foundAspects,
unfoundAspects,
repos: repos.map(r => ({
id: r.id,
repo: r.repoRef.repo,
owner: r.repoRef.owner,
url: r.repoRef.url,
})),
virtualProjectCount,
}), `Atomist Visualizer (${repos.length} repositories)`,
conf.instanceMetadata));
} catch (e) {
logger.error(e.stack);
res.status(500).send(cleverlyExplainError(conf, e));
}
});
}
const ReadmeLink = "https://github.com/atomist/org-visualizer/#database-setup";
function cleverlyExplainError(conf: WebAppConfig, e: Error): string {
if (e.message.includes(ConnectionErrorHeading) || e.message.includes("ENOCONNECT")) {
return `This page cannot load without a database connection.<br>
Please check <a href="${ReadmeLink}">the org-visualizer README</a> for how to set up a database.<p>
Error:<br>${e.message}`;
}
return `Failed to load page. Please check the log output of ${conf.instanceMetadata.name}`;
}
function idealMatchesFingerprint(id: Ideal, fp: FingerprintUsage): boolean {
const c = idealCoordinates(id);
return c.type === fp.type && c.name === fp.name;
}
function formatFingerprintUsageForDisplay(aspect: Aspect, ideals: Ideal[], fp: FingerprintUsage): FingerprintForDisplay {
const foundIdeal = ideals.find(ide => idealMatchesFingerprint(ide, fp));
const ideal = foundIdeal && isConcreteIdeal(foundIdeal) && aspect.toDisplayableFingerprint ?
{ displayValue: aspect.toDisplayableFingerprint(foundIdeal.ideal) }
: undefined;
return {
...fp,
ideal,
displayName: defaultedToDisplayableFingerprintName(aspect)(fp.name),
entropy: supportsEntropy(aspect) ? fp.entropy : undefined,
};
}