feat: define rings and quadrants via config file

closes #96
This commit is contained in:
Danny Koppenhagen
2021-10-03 20:18:22 +02:00
committed by Bastian
parent 8d28e4c3a3
commit 70ea8d5bcd
21 changed files with 1087 additions and 948 deletions

View File

@@ -110,6 +110,22 @@ To change the logo, create a public folder in your application and put your `log
For reference have a look at [public/logo.svg](./public/logo.svg). For reference have a look at [public/logo.svg](./public/logo.svg).
### Change the rings and quadrants config
To change the default rings and quadrants of the radar, you can place a custom `config.json` file within the `public` folder.
The content should look as follows:
```json
{
"quadrants": {
"languages-and-frameworks": "Languages & Frameworks",
"methods-and-patterns": "Methods & Patterns",
"platforms-and-aoe-services": "Platforms & Operations",
"tools": "Tools"
},
"rings":["all", "adopt", "trial", "assess", "hold"]
}
```
### Change the index.html ### Change the index.html
To change the index.html, create a public folder in your application and put your `index.html` in it. To change the index.html, create a public folder in your application and put your `index.html` in it.

9
config_example.json Normal file
View File

@@ -0,0 +1,9 @@
{
"quadrants": {
"languages-and-frameworks": "Languages & Frameworks",
"methods-and-patterns": "Methods & Patterns",
"platforms-and-aoe-services": "Platforms & Operations",
"tools": "Tools"
},
"rings":["all", "adopt", "trial", "assess", "hold"]
}

View File

@@ -39,7 +39,6 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
var fs_1 = require("fs"); var fs_1 = require("fs");
var radar_1 = require("./generateJson/radar"); var radar_1 = require("./generateJson/radar");
var config_1 = require("../src/config");
// Do this as the first thing so that any code reading it knows the right env. // Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = "production"; process.env.BABEL_ENV = "production";
process.env.NODE_ENV = "production"; process.env.NODE_ENV = "production";
@@ -50,7 +49,7 @@ process.on("unhandledRejection", function (err) {
throw err; throw err;
}); });
(function () { return __awaiter(void 0, void 0, void 0, function () { (function () { return __awaiter(void 0, void 0, void 0, function () {
var radar, e_1; var radar, rawConf, config, e_1;
return __generator(this, function (_a) { return __generator(this, function (_a) {
switch (_a.label) { switch (_a.label) {
case 0: case 0:
@@ -61,7 +60,9 @@ process.on("unhandledRejection", function (err) {
radar = _a.sent(); radar = _a.sent();
fs_1.copyFileSync("build/index.html", "build/overview.html"); fs_1.copyFileSync("build/index.html", "build/overview.html");
fs_1.copyFileSync("build/index.html", "build/help-and-about-tech-radar.html"); fs_1.copyFileSync("build/index.html", "build/help-and-about-tech-radar.html");
config_1.quadrants.forEach(function (quadrant) { rawConf = fs_1.readFileSync("build/config.json", "utf-8");
config = JSON.parse(rawConf);
Object.keys(config.quadrants).forEach(function (quadrant) {
var destFolder = "build/" + quadrant; var destFolder = "build/" + quadrant;
fs_1.copyFileSync("build/index.html", destFolder + ".html"); fs_1.copyFileSync("build/index.html", destFolder + ".html");
if (!fs_1.existsSync(destFolder)) { if (!fs_1.existsSync(destFolder)) {

View File

@@ -76,13 +76,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.createRadar = void 0; exports.createRadar = void 0;
var fs_extra_1 = require("fs-extra"); var fs_extra_1 = require("fs-extra");
var fs_1 = require("fs");
var path = __importStar(require("path")); var path = __importStar(require("path"));
var front_matter_1 = __importDefault(require("front-matter")); var front_matter_1 = __importDefault(require("front-matter"));
// @ts-ignore esModuleInterop is activated in tsconfig.scripts.json, but IDE typescript uses default typescript config // @ts-ignore esModuleInterop is activated in tsconfig.scripts.json, but IDE typescript uses default typescript config
var marked_1 = __importDefault(require("marked")); var marked_1 = __importDefault(require("marked"));
var highlight_js_1 = __importDefault(require("highlight.js")); var highlight_js_1 = __importDefault(require("highlight.js"));
var file_1 = require("./file"); var file_1 = require("./file");
var config_1 = require("../../src/config"); var paths_1 = require("../paths");
marked_1.default.setOptions({ marked_1.default.setOptions({
highlight: function (code) { return highlight_js_1.default.highlightAuto(code).value; }, highlight: function (code) { return highlight_js_1.default.highlightAuto(code).value; },
}); });
@@ -108,11 +109,14 @@ var createRadar = function () { return __awaiter(void 0, void 0, void 0, functio
}); }; }); };
exports.createRadar = createRadar; exports.createRadar = createRadar;
var checkAttributes = function (fileName, attributes) { var checkAttributes = function (fileName, attributes) {
if (attributes.ring && !config_1.rings.includes(attributes.ring)) { var rawConf = fs_1.readFileSync(path.resolve(paths_1.appBuild, 'config.json'), 'utf-8');
throw new Error("Error: " + fileName + " has an illegal value for 'ring' - must be one of " + config_1.rings); var config = JSON.parse(rawConf);
if (attributes.ring && !config.rings.includes(attributes.ring)) {
throw new Error("Error: " + fileName + " has an illegal value for 'ring' - must be one of " + config.rings);
} }
if (attributes.quadrant && !config_1.quadrants.includes(attributes.quadrant)) { var quadrants = Object.keys(config.quadrants);
throw new Error("Error: " + fileName + " has an illegal value for 'quadrant' - must be one of " + config_1.quadrants); if (attributes.quadrant && !quadrants.includes(attributes.quadrant)) {
throw new Error("Error: " + fileName + " has an illegal value for 'quadrant' - must be one of " + quadrants);
} }
return attributes; return attributes;
}; };
@@ -120,19 +124,21 @@ var createRevisionsFromFiles = function (fileNames) {
var publicUrl = process.env.PUBLIC_URL; var publicUrl = process.env.PUBLIC_URL;
return Promise.all(fileNames.map(function (fileName) { return Promise.all(fileNames.map(function (fileName) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
fs_extra_1.readFile(fileName, "utf8", function (err, data) { fs_extra_1.readFile(fileName, "utf8", function (err, data) { return __awaiter(void 0, void 0, void 0, function () {
if (err) { var fm, html;
reject(err); return __generator(this, function (_a) {
} if (err) {
else { reject(err);
var fm = front_matter_1.default(data); }
// add target attribute to external links else {
// todo: check path fm = front_matter_1.default(data);
var html = marked_1.default(fm.body.replace(/\]\(\//g, "](" + publicUrl + "/")); html = marked_1.default(fm.body.replace(/\]\(\//g, "](" + publicUrl + "/"));
html = html.replace(/a href="http/g, 'a target="_blank" rel="noopener noreferrer" href="http'); html = html.replace(/a href="http/g, 'a target="_blank" rel="noopener noreferrer" href="http');
resolve(__assign(__assign(__assign({}, itemInfoFromFilename(fileName)), checkAttributes(fileName, fm.attributes)), { fileName: fileName, body: html })); resolve(__assign(__assign(__assign({}, itemInfoFromFilename(fileName)), checkAttributes(fileName, fm.attributes)), { fileName: fileName, body: html }));
} }
}); return [2 /*return*/];
});
}); });
}); });
})); }));
}; };

9
public/config.json Normal file
View File

@@ -0,0 +1,9 @@
{
"quadrants": {
"languages-and-frameworks": "Languages & Frameworks",
"methods-and-patterns": "Methods & Patterns",
"platforms-and-aoe-services": "Platforms & Operations",
"tools": "Tools"
},
"rings":["all", "adopt", "trial", "assess", "hold"]
}

View File

@@ -1,8 +1,7 @@
#!/usr/bin/env node #!/usr/bin/env node
import { copyFileSync, mkdirSync, existsSync } from "fs"; import { copyFileSync, mkdirSync, existsSync, readFileSync } from "fs";
import { createRadar } from "./generateJson/radar"; import { createRadar } from "./generateJson/radar";
import { quadrants } from "../src/config";
// Do this as the first thing so that any code reading it knows the right env. // Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = "production"; process.env.BABEL_ENV = "production";
@@ -22,8 +21,9 @@ process.on("unhandledRejection", (err) => {
copyFileSync("build/index.html", "build/overview.html"); copyFileSync("build/index.html", "build/overview.html");
copyFileSync("build/index.html", "build/help-and-about-tech-radar.html"); copyFileSync("build/index.html", "build/help-and-about-tech-radar.html");
const rawConf = readFileSync("build/config.json", "utf-8");
quadrants.forEach((quadrant) => { const config = JSON.parse(rawConf);
Object.keys(config.quadrants).forEach((quadrant) => {
const destFolder = `build/${quadrant}`; const destFolder = `build/${quadrant}`;
copyFileSync("build/index.html", `${destFolder}.html`); copyFileSync("build/index.html", `${destFolder}.html`);
if (!existsSync(destFolder)) { if (!existsSync(destFolder)) {

View File

@@ -1,4 +1,5 @@
import { readFile } from "fs-extra"; import { readFile } from "fs-extra";
import { readFileSync } from "fs";
import * as path from "path"; import * as path from "path";
import frontMatter from "front-matter"; import frontMatter from "front-matter";
// @ts-ignore esModuleInterop is activated in tsconfig.scripts.json, but IDE typescript uses default typescript config // @ts-ignore esModuleInterop is activated in tsconfig.scripts.json, but IDE typescript uses default typescript config
@@ -6,8 +7,8 @@ import marked from "marked";
import highlight from "highlight.js"; import highlight from "highlight.js";
import { radarPath, getAllMarkdownFiles } from "./file"; import { radarPath, getAllMarkdownFiles } from "./file";
import { quadrants, rings } from "../../src/config";
import { Item, Revision, ItemAttributes, Radar } from "../../src/model"; import { Item, Revision, ItemAttributes, Radar } from "../../src/model";
import { appBuild } from "../paths";
type FMAttributes = ItemAttributes; type FMAttributes = ItemAttributes;
@@ -29,12 +30,16 @@ export const createRadar = async (): Promise<Radar> => {
}; };
const checkAttributes = (fileName: string, attributes: FMAttributes) => { const checkAttributes = (fileName: string, attributes: FMAttributes) => {
if (attributes.ring && !rings.includes(attributes.ring)) { const rawConf = readFileSync(path.resolve(appBuild, 'config.json'), 'utf-8');
const config = JSON.parse(rawConf);
if (attributes.ring && !config.rings.includes(attributes.ring)) {
throw new Error( throw new Error(
`Error: ${fileName} has an illegal value for 'ring' - must be one of ${rings}` `Error: ${fileName} has an illegal value for 'ring' - must be one of ${config.rings}`
); );
} }
const quadrants = Object.keys(config.quadrants);
if (attributes.quadrant && !quadrants.includes(attributes.quadrant)) { if (attributes.quadrant && !quadrants.includes(attributes.quadrant)) {
throw new Error( throw new Error(
`Error: ${fileName} has an illegal value for 'quadrant' - must be one of ${quadrants}` `Error: ${fileName} has an illegal value for 'quadrant' - must be one of ${quadrants}`
@@ -50,7 +55,7 @@ const createRevisionsFromFiles = (fileNames: string[]) => {
fileNames.map( fileNames.map(
(fileName) => (fileName) =>
new Promise<Revision>((resolve, reject) => { new Promise<Revision>((resolve, reject) => {
readFile(fileName, "utf8", (err, data) => { readFile(fileName, "utf8", async (err, data) => {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {

View File

@@ -13,6 +13,7 @@ import {
} from "react-router-dom"; } from "react-router-dom";
import { Item } from "../model"; import { Item } from "../model";
import { Messages, MessagesProvider } from "../context/MessagesContext"; import { Messages, MessagesProvider } from "../context/MessagesContext";
import { ConfigData } from "../config";
interface Params { interface Params {
page: string; page: string;
@@ -40,9 +41,11 @@ const useQuery = () => new URLSearchParams(useLocation().search);
const RouterWithPageParam = ({ const RouterWithPageParam = ({
items, items,
releases, releases,
config,
}: { }: {
items: Item[]; items: Item[];
releases: string[]; releases: string[];
config: ConfigData;
}) => { }) => {
const { page } = useParams<Params>(); const { page } = useParams<Params>();
const query = useQuery(); const query = useQuery();
@@ -53,6 +56,7 @@ const RouterWithPageParam = ({
search={query.get("search") || ""} search={query.get("search") || ""}
items={items} items={items}
releases={releases} releases={releases}
config={config}
/> />
); );
}; };
@@ -79,8 +83,11 @@ export default function App() {
const messages = useFetch<Messages>( const messages = useFetch<Messages>(
`${process.env.PUBLIC_URL}/messages.json` `${process.env.PUBLIC_URL}/messages.json`
); );
const config = useFetch<ConfigData>(
`${process.env.PUBLIC_URL}/config.json`
);
if (data) { if (data && config) {
const { items, releases } = data; const { items, releases } = data;
return ( return (
<MessagesProvider messages={messages}> <MessagesProvider messages={messages}>
@@ -93,7 +100,7 @@ export default function App() {
<HeaderWithPageParam /> <HeaderWithPageParam />
</div> </div>
<div className={classNames("page__content")}> <div className={classNames("page__content")}>
<RouterWithPageParam items={items} releases={releases} /> <RouterWithPageParam config={config} items={items} releases={releases} />
</div> </div>
<div className="page__footer"> <div className="page__footer">
<FooterWithPageParam items={items} /> <FooterWithPageParam items={items} />

View File

@@ -1,11 +1,10 @@
import React, { MouseEventHandler } from "react"; import React, { MouseEventHandler } from "react";
import classNames from "classnames"; import classNames from "classnames";
import "./badge.scss"; import "./badge.scss";
import { Ring } from "../../config";
type BadgeProps = { type BadgeProps = {
onClick?: MouseEventHandler; onClick?: MouseEventHandler;
big?: boolean; big?: boolean;
type: "big" | "all" | "empty" | Ring; type: "big" | "all" | "empty" | string;
}; };
export default function Badge({ export default function Badge({

View File

@@ -1,11 +1,10 @@
import React from "react";
import { formatRelease } from "../../date"; import { formatRelease } from "../../date";
import { featuredOnly, Item } from "../../model"; import { featuredOnly, Item } from "../../model";
import HeroHeadline from "../HeroHeadline/HeroHeadline"; import HeroHeadline from "../HeroHeadline/HeroHeadline";
import QuadrantGrid from "../QuadrantGrid/QuadrantGrid"; import QuadrantGrid from "../QuadrantGrid/QuadrantGrid";
import Fadeable from "../Fadeable/Fadeable"; import Fadeable from "../Fadeable/Fadeable";
import SetTitle from "../SetTitle"; import SetTitle from "../SetTitle";
import { radarName, radarNameShort } from "../../config"; import { ConfigData, radarName, radarNameShort } from "../../config";
import { MomentInput } from "moment"; import { MomentInput } from "moment";
import { useMessages } from "../../context/MessagesContext"; import { useMessages } from "../../context/MessagesContext";
@@ -13,6 +12,7 @@ type PageIndexProps = {
leaving: boolean; leaving: boolean;
onLeave: () => void; onLeave: () => void;
items: Item[]; items: Item[];
config: ConfigData;
releases: MomentInput[]; releases: MomentInput[];
}; };
@@ -20,6 +20,7 @@ export default function PageIndex({
leaving, leaving,
onLeave, onLeave,
items, items,
config,
releases, releases,
}: PageIndexProps) { }: PageIndexProps) {
const { pageIndex } = useMessages(); const { pageIndex } = useMessages();
@@ -35,7 +36,7 @@ export default function PageIndex({
{radarName} {radarName}
</HeroHeadline> </HeroHeadline>
</div> </div>
<QuadrantGrid items={featuredOnly(items)} /> <QuadrantGrid items={featuredOnly(items)} config={config} />
<div className="publish-date"> <div className="publish-date">
{publishedLabel} {formatRelease(newestRelease)} {publishedLabel} {formatRelease(newestRelease)}
</div> </div>

View File

@@ -7,7 +7,7 @@ import SetTitle from "../SetTitle";
import ItemRevisions from "../ItemRevisions/ItemRevisions"; import ItemRevisions from "../ItemRevisions/ItemRevisions";
import { useAnimations } from "./useAnimations"; import { useAnimations } from "./useAnimations";
import "./item-page.scss"; import "./item-page.scss";
import { translate } from "../../config"; import { ConfigData, translate } from "../../config";
import { import {
groupByQuadrants, groupByQuadrants,
Item, Item,
@@ -29,11 +29,12 @@ const getItemsInRing = (pageName: string, items: Item[]) => {
type Props = { type Props = {
pageName: string; pageName: string;
items: Item[]; items: Item[];
config: ConfigData;
leaving: boolean; leaving: boolean;
onLeave: () => void; onLeave: () => void;
}; };
const PageItem: React.FC<Props> = ({ pageName, items, leaving, onLeave }) => { const PageItem: React.FC<Props> = ({ pageName, items, config, leaving, onLeave }) => {
const { pageItem } = useMessages(); const { pageItem } = useMessages();
const quadrantOverview = pageItem?.quadrantOverview || 'Quadrant Overview'; const quadrantOverview = pageItem?.quadrantOverview || 'Quadrant Overview';
@@ -57,7 +58,7 @@ const PageItem: React.FC<Props> = ({ pageName, items, leaving, onLeave }) => {
className="item-page__header" className="item-page__header"
style={getAnimationState("navHeader")} style={getAnimationState("navHeader")}
> >
<h3 className="headline">{translate(item.quadrant)}</h3> <h3 className="headline">{translate(config, item.quadrant)}</h3>
</div> </div>
<ItemList <ItemList
items={itemsInRing} items={itemsInRing}

View File

@@ -6,12 +6,13 @@ import Fadeable from "../Fadeable/Fadeable";
import SetTitle from "../SetTitle"; import SetTitle from "../SetTitle";
import ItemRevisions from "../ItemRevisions/ItemRevisions"; import ItemRevisions from "../ItemRevisions/ItemRevisions";
import { translate } from "../../config"; import { ConfigData, translate } from "../../config";
import { groupByQuadrants, Item } from "../../model"; import { groupByQuadrants, Item } from "../../model";
type PageItemMobileProps = { type PageItemMobileProps = {
pageName: string; pageName: string;
items: Item[]; items: Item[];
config: ConfigData;
leaving: boolean; leaving: boolean;
onLeave: () => void; onLeave: () => void;
}; };
@@ -19,6 +20,7 @@ type PageItemMobileProps = {
export default function PageItemMobile({ export default function PageItemMobile({
pageName, pageName,
items, items,
config,
leaving, leaving,
onLeave, onLeave,
}: PageItemMobileProps) { }: PageItemMobileProps) {
@@ -47,7 +49,7 @@ export default function PageItemMobile({
<div className="mobile-item-page__header"> <div className="mobile-item-page__header">
<div className="split"> <div className="split">
<div className="split__left"> <div className="split__left">
<h3 className="headline">{translate(item.quadrant)}</h3> <h3 className="headline">{translate(config, item.quadrant)}</h3>
<h1 className="hero-headline hero-headline--inverse"> <h1 className="hero-headline hero-headline--inverse">
{item.title} {item.title}
</h1> </h1>
@@ -73,7 +75,7 @@ export default function PageItemMobile({
<ItemList items={itemsInRing} activeItem={item}> <ItemList items={itemsInRing} activeItem={item}>
<div className="split"> <div className="split">
<div className="split__left"> <div className="split__left">
<h3 className="headline">{translate(item.quadrant)}</h3> <h3 className="headline">{translate(config, item.quadrant)}</h3>
</div> </div>
<div className="split__right"> <div className="split__right">
<Link className="icon-link" pageName={item.quadrant}> <Link className="icon-link" pageName={item.quadrant}>

View File

@@ -8,8 +8,8 @@ import Fadeable from "../Fadeable/Fadeable";
import SetTitle from "../SetTitle"; import SetTitle from "../SetTitle";
import Flag from "../Flag/Flag"; import Flag from "../Flag/Flag";
import { groupByFirstLetter, Item } from "../../model"; import { groupByFirstLetter, Item } from "../../model";
import { ConfigData, translate } from "../../config";
import { useMessages } from "../../context/MessagesContext"; import { useMessages } from "../../context/MessagesContext";
import { translate, Ring } from "../../config";
const containsSearchTerm = (text = "", term = "") => { const containsSearchTerm = (text = "", term = "") => {
// TODO search refinement // TODO search refinement
@@ -20,9 +20,10 @@ const containsSearchTerm = (text = "", term = "") => {
}; };
type PageOverviewProps = { type PageOverviewProps = {
rings: readonly ("all" | Ring)[]; rings: readonly ("all" | string)[];
search: string; search: string;
items: Item[]; items: Item[];
config: ConfigData;
leaving: boolean; leaving: boolean;
onLeave: () => void; onLeave: () => void;
}; };
@@ -31,10 +32,11 @@ export default function PageOverview({
rings, rings,
search: searchProp, search: searchProp,
items, items,
config,
leaving, leaving,
onLeave, onLeave,
}: PageOverviewProps) { }: PageOverviewProps) {
const [ring, setRing] = useState<Ring | "all">("all"); const [ring, setRing] = useState<string | "all">("all");
const [search, setSearch] = useState(searchProp); const [search, setSearch] = useState(searchProp);
const { pageOverview } = useMessages(); const { pageOverview } = useMessages();
const title = pageOverview?.title || 'Technologies Overview'; const title = pageOverview?.title || 'Technologies Overview';
@@ -46,7 +48,7 @@ export default function PageOverview({
setSearch(searchProp); setSearch(searchProp);
}, [rings, searchProp]); }, [rings, searchProp]);
const handleRingClick = (ring: Ring) => () => { const handleRingClick = (ring: string) => () => {
setRing(ring); setRing(ring);
}; };
@@ -134,7 +136,7 @@ export default function PageOverview({
<div className="split__right"> <div className="split__right">
<div className="nav nav--relations"> <div className="nav nav--relations">
<div className="nav__item"> <div className="nav__item">
{translate(item.quadrant)} {translate(config, item.quadrant)}
</div> </div>
<div className="nav__item"> <div className="nav__item">
<Badge type={item.ring}>{item.ring}</Badge> <Badge type={item.ring}>{item.ring}</Badge>

View File

@@ -5,7 +5,7 @@ import QuadrantSection from "../QuadrantSection/QuadrantSection";
import Fadeable from "../Fadeable/Fadeable"; import Fadeable from "../Fadeable/Fadeable";
import SetTitle from "../SetTitle"; import SetTitle from "../SetTitle";
import { translate } from "../../config"; import { ConfigData, translate } from "../../config";
import { featuredOnly, groupByQuadrants, Item } from "../../model"; import { featuredOnly, groupByQuadrants, Item } from "../../model";
type PageQuadrantProps = { type PageQuadrantProps = {
@@ -13,6 +13,7 @@ type PageQuadrantProps = {
onLeave: () => void; onLeave: () => void;
pageName: string; pageName: string;
items: Item[]; items: Item[];
config: ConfigData;
}; };
export default function PageQuadrant({ export default function PageQuadrant({
@@ -20,15 +21,16 @@ export default function PageQuadrant({
onLeave, onLeave,
pageName, pageName,
items, items,
config,
}: PageQuadrantProps) { }: PageQuadrantProps) {
const groups = groupByQuadrants(featuredOnly(items)); const groups = groupByQuadrants(featuredOnly(items));
return ( return (
<Fadeable leaving={leaving} onLeave={onLeave}> <Fadeable leaving={leaving} onLeave={onLeave}>
<SetTitle title={translate(pageName)} /> <SetTitle title={translate(config, pageName)} />
<HeadlineGroup> <HeadlineGroup>
<HeroHeadline>{translate(pageName)}</HeroHeadline> <HeroHeadline>{translate(config, pageName)}</HeroHeadline>
</HeadlineGroup> </HeadlineGroup>
<QuadrantSection groups={groups} quadrantName={pageName} big /> <QuadrantSection groups={groups} quadrantName={pageName} config={config} big />
</Fadeable> </Fadeable>
); );
} }

View File

@@ -1,21 +1,21 @@
import React from "react"; import React from "react";
import { groupByQuadrants, Item, Group } from "../../model"; import { groupByQuadrants, Item, Group } from "../../model";
import { quadrants } from "../../config"; import { ConfigData } from "../../config";
import QuadrantSection from "../QuadrantSection/QuadrantSection"; import QuadrantSection from "../QuadrantSection/QuadrantSection";
import "./quadrant-grid.scss"; import "./quadrant-grid.scss";
const renderQuadrant = (quadrantName: string, groups: Group) => { const renderQuadrant = (quadrantName: string, groups: Group, config: ConfigData) => {
return ( return (
<div key={quadrantName} className="quadrant-grid__quadrant"> <div key={quadrantName} className="quadrant-grid__quadrant">
<QuadrantSection quadrantName={quadrantName} groups={groups} /> <QuadrantSection quadrantName={quadrantName} groups={groups} config={config} />
</div> </div>
); );
}; };
export default function QuadrantGrid({ items }: { items: Item[] }) { export default function QuadrantGrid({ items, config }: { items: Item[], config: ConfigData }) {
const groups = groupByQuadrants(items); const groups = groupByQuadrants(items);
return ( return (
<div className="quadrant-grid"> <div className="quadrant-grid">
{quadrants.map((quadrantName) => renderQuadrant(quadrantName, groups))} {Object.keys(config.quadrants).map((quadrantName: string) => renderQuadrant(quadrantName, groups, config))}
</div> </div>
); );
} }

View File

@@ -1,5 +1,4 @@
import React from "react"; import { translate, showEmptyRings, ConfigData } from "../../config";
import { translate, rings, Ring, showEmptyRings } from "../../config";
import Badge from "../Badge/Badge"; import Badge from "../Badge/Badge";
import Link from "../Link/Link"; import Link from "../Link/Link";
import ItemList from "../ItemList/ItemList"; import ItemList from "../ItemList/ItemList";
@@ -7,7 +6,7 @@ import Flag from "../Flag/Flag";
import { Group } from "../../model"; import { Group } from "../../model";
import "./quadrant-section.scss"; import "./quadrant-section.scss";
const renderList = ( const renderList = (
ringName: Ring, ringName: string,
quadrantName: string, quadrantName: string,
groups: Group, groups: Group,
big: boolean big: boolean
@@ -42,7 +41,7 @@ const renderList = (
}; };
const renderRing = ( const renderRing = (
ringName: Ring, ringName: string,
quadrantName: string, quadrantName: string,
groups: Group, groups: Group,
big: boolean big: boolean
@@ -65,10 +64,12 @@ const renderRing = (
export default function QuadrantSection({ export default function QuadrantSection({
quadrantName, quadrantName,
groups, groups,
config,
big = false, big = false,
}: { }: {
quadrantName: string; quadrantName: string;
groups: Group; groups: Group;
config: ConfigData;
big?: boolean; big?: boolean;
}) { }) {
return ( return (
@@ -76,7 +77,7 @@ export default function QuadrantSection({
<div className="quadrant-section__header"> <div className="quadrant-section__header">
<div className="split"> <div className="split">
<div className="split__left"> <div className="split__left">
<h4 className="headline">{translate(quadrantName)}</h4> <h4 className="headline">{translate(config, quadrantName)}</h4>
</div> </div>
{!big && ( {!big && (
<div className="split__right"> <div className="split__right">
@@ -89,7 +90,7 @@ export default function QuadrantSection({
</div> </div>
</div> </div>
<div className="quadrant-section__rings"> <div className="quadrant-section__rings">
{rings.map((ringName) => {config.rings.map((ringName: string) =>
renderRing(ringName, quadrantName, groups, big) renderRing(ringName, quadrantName, groups, big)
)} )}
</div> </div>

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import PageIndex from "./PageIndex/PageIndex"; import PageIndex from "./PageIndex/PageIndex";
import PageOverview from "./PageOverview/PageOverview"; import PageOverview from "./PageOverview/PageOverview";
import PageHelp from "./PageHelp/PageHelp"; import PageHelp from "./PageHelp/PageHelp";
@@ -6,10 +6,9 @@ import PageQuadrant from "./PageQuadrant/PageQuadrant";
import PageItem from "./PageItem/PageItem"; import PageItem from "./PageItem/PageItem";
import PageItemMobile from "./PageItemMobile/PageItemMobile"; import PageItemMobile from "./PageItemMobile/PageItemMobile";
import { import {
quadrants, ConfigData,
getItemPageNames, getItemPageNames,
isMobileViewport, isMobileViewport,
rings,
} from "../config"; } from "../config";
import { Item } from "../model"; import { Item } from "../model";
@@ -18,6 +17,7 @@ type RouterProps = {
items: Item[]; items: Item[];
releases: string[]; releases: string[];
search: string; search: string;
config: ConfigData;
}; };
enum page { enum page {
@@ -30,7 +30,7 @@ enum page {
notFound, notFound,
} }
const getPageByName = (items: Item[], pageName: string): page => { const getPageByName = (items: Item[], pageName: string, config: ConfigData): page => {
if (pageName === "index") { if (pageName === "index") {
return page.index; return page.index;
} }
@@ -40,7 +40,7 @@ const getPageByName = (items: Item[], pageName: string): page => {
if (pageName === "help-and-about-tech-radar") { if (pageName === "help-and-about-tech-radar") {
return page.help; return page.help;
} }
if (quadrants.includes(pageName)) { if (Object.keys(config.quadrants).includes(pageName)) {
return page.quadrant; return page.quadrant;
} }
if (getItemPageNames(items).includes(pageName)) { if (getItemPageNames(items).includes(pageName)) {
@@ -55,6 +55,7 @@ export default function Router({
items, items,
releases, releases,
search, search,
config
}: RouterProps) { }: RouterProps) {
const [statePageName, setStatePageName] = useState(pageName); const [statePageName, setStatePageName] = useState(pageName);
const [leaving, setLeaving] = useState(false); const [leaving, setLeaving] = useState(false);
@@ -62,14 +63,14 @@ export default function Router({
useEffect(() => { useEffect(() => {
const nowLeaving = const nowLeaving =
getPageByName(items, pageName) !== getPageByName(items, statePageName); getPageByName(items, pageName, config) !== getPageByName(items, statePageName, config);
if (nowLeaving) { if (nowLeaving) {
setLeaving(true); setLeaving(true);
setNextPageName(pageName); setNextPageName(pageName);
} else { } else {
setStatePageName(pageName); setStatePageName(pageName);
} }
}, [pageName, items, statePageName]); }, [pageName, items, config, statePageName]);
const handlePageLeave = () => { const handlePageLeave = () => {
setStatePageName(nextPageName); setStatePageName(nextPageName);
@@ -82,12 +83,13 @@ export default function Router({
}, 0); }, 0);
}; };
switch (getPageByName(items, statePageName)) { switch (getPageByName(items, statePageName, config)) {
case page.index: case page.index:
return ( return (
<PageIndex <PageIndex
leaving={leaving} leaving={leaving}
items={items} items={items}
config={config}
onLeave={handlePageLeave} onLeave={handlePageLeave}
releases={releases} releases={releases}
/> />
@@ -96,7 +98,8 @@ export default function Router({
return ( return (
<PageOverview <PageOverview
items={items} items={items}
rings={rings} config={config}
rings={config.rings}
search={search} search={search}
leaving={leaving} leaving={leaving}
onLeave={handlePageLeave} onLeave={handlePageLeave}
@@ -110,6 +113,7 @@ export default function Router({
leaving={leaving} leaving={leaving}
onLeave={handlePageLeave} onLeave={handlePageLeave}
items={items} items={items}
config={config}
pageName={statePageName} pageName={statePageName}
/> />
); );
@@ -117,6 +121,7 @@ export default function Router({
return ( return (
<PageItemMobile <PageItemMobile
items={items} items={items}
config={config}
pageName={statePageName} pageName={statePageName}
leaving={leaving} leaving={leaving}
onLeave={handlePageLeave} onLeave={handlePageLeave}
@@ -126,6 +131,7 @@ export default function Router({
return ( return (
<PageItem <PageItem
items={items} items={items}
config={config}
pageName={statePageName} pageName={statePageName}
leaving={leaving} leaving={leaving}
onLeave={handlePageLeave} onLeave={handlePageLeave}

View File

@@ -1,34 +1,19 @@
import { Item } from "./model"; import { Item } from "./model";
export interface ConfigData {
quadrants: { [key: string]: string };
rings: string[];
}
export const radarName = export const radarName =
process.env.REACT_APP_RADAR_NAME || "AOE Technology Radar"; process.env.REACT_APP_RADAR_NAME || "AOE Technology Radar";
export const radarNameShort = radarName; export const radarNameShort = radarName;
export const quadrants = [
"languages-and-frameworks",
"methods-and-patterns",
"platforms-and-aoe-services",
"tools",
];
export const rings = ["all", "adopt", "trial", "assess", "hold"] as const;
export type Ring = typeof rings[number];
export const getItemPageNames = (items: Item[]) => export const getItemPageNames = (items: Item[]) =>
items.map((item) => `${item.quadrant}/${item.name}`); items.map((item) => `${item.quadrant}/${item.name}`);
export const showEmptyRings = false; export const showEmptyRings = false;
const messages: { [k: string]: string } = {
"languages-and-frameworks": "Languages & Frameworks",
"methods-and-patterns": "Methods & Patterns",
"platforms-and-aoe-services": "Platforms & Operations",
tools: "Tools",
};
export const translate = (key: string) => messages[key] || "-";
export function isMobileViewport() { export function isMobileViewport() {
// return false for server side rendering // return false for server side rendering
if (typeof window == "undefined") return false; if (typeof window == "undefined") return false;
@@ -43,3 +28,7 @@ export function isMobileViewport() {
export function assetUrl(file: string) { export function assetUrl(file: string) {
return process.env.PUBLIC_URL + "/" + file; return process.env.PUBLIC_URL + "/" + file;
} }
export function translate(config: ConfigData, key: string) {
return config.quadrants[key] || "-";
}

View File

@@ -6,11 +6,6 @@ interface Quadrant {
description: string; description: string;
} }
interface Ring {
name: string;
description: string;
}
interface Paragraph { interface Paragraph {
headline: string; headline: string;
values: string[]; values: string[];
@@ -20,7 +15,7 @@ interface PageHelp {
paragraphs: Paragraph[]; paragraphs: Paragraph[];
quadrantsPreDescription?: string; quadrantsPreDescription?: string;
quadrants: Quadrant[]; quadrants: Quadrant[];
rings: Ring[]; rings: {name: string, description: string }[];
ringsPreDescription?: string; ringsPreDescription?: string;
sourcecodeLink?: { sourcecodeLink?: {
href: string; href: string;

View File

@@ -1,8 +1,6 @@
import { Ring } from "./config";
export type ItemAttributes = { export type ItemAttributes = {
name: string; name: string;
ring: Ring; ring: string;
quadrant: string; quadrant: string;
title: string; title: string;
featured?: boolean; featured?: boolean;

1786
yarn.lock

File diff suppressed because it is too large Load Diff