Merge branch 'master' of github.com:spotify/backstage

This commit is contained in:
Ivan Shmidt
2020-05-08 09:31:07 +02:00
120 changed files with 866 additions and 911 deletions
+15 -14
View File
@@ -2,20 +2,6 @@
"name": "example-app",
"version": "0.1.1-alpha.4",
"private": true,
"devDependencies": {
"@testing-library/cypress": "^6.0.0",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/react-router-dom": "^5.1.3",
"@types/zen-observable": "^0.8.0",
"cross-env": "^7.0.0",
"cypress": "^4.2.0",
"eslint-plugin-cypress": "^2.10.3",
"start-server-and-test": "^1.10.11"
},
"dependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@backstage/core": "^0.1.1-alpha.4",
@@ -38,6 +24,21 @@
"react-use": "^13.24.0",
"zen-observable": "^0.8.15"
},
"devDependencies": {
"@testing-library/cypress": "^6.0.0",
"@types/jquery": "^3.3.34",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/react-router-dom": "^5.1.3",
"@types/zen-observable": "^0.8.0",
"cross-env": "^7.0.0",
"cypress": "^4.2.0",
"eslint-plugin-cypress": "^2.10.3",
"start-server-and-test": "^1.10.11"
},
"scripts": {
"start": "backstage-cli app:serve",
"bundle": "backstage-cli app:build",
+9 -6
View File
@@ -30,11 +30,7 @@ import {
LighthouseRestApi,
} from '@backstage/plugin-lighthouse';
import {
techRadarApiRef,
TechRadar,
loadSampleData,
} from '@backstage/plugin-tech-radar';
import { techRadarApiRef, TechRadar } from '@backstage/plugin-tech-radar';
import { CircleCIApi, circleCIApiRef } from '@backstage/plugin-circleci';
@@ -49,6 +45,13 @@ builder.add(circleCIApiRef, new CircleCIApi());
builder.add(featureFlagsApiRef, new FeatureFlags());
builder.add(lighthouseApiRef, new LighthouseRestApi('http://localhost:3003'));
builder.add(techRadarApiRef, new TechRadar(1800, 800, loadSampleData));
builder.add(
techRadarApiRef,
new TechRadar({
width: 1500,
height: 800,
}),
);
export default builder.build() as ApiHolder;
-1
View File
@@ -1,7 +1,6 @@
{
"include": ["src"],
"compilerOptions": {
"baseUrl": "src",
"outDir": "dist",
"incremental": true,
"sourceMap": true,
-1
View File
@@ -1,7 +1,6 @@
{
"include": ["src"],
"compilerOptions": {
"baseUrl": "src",
"outDir": "dist",
"incremental": true,
"sourceMap": true,
-56
View File
@@ -21,68 +21,12 @@ const path = require('path');
const isLocal = require('fs').existsSync(path.resolve(__dirname, '../src'));
if (!isLocal || process.env.BACKSTAGE_E2E_CLI_TEST) {
// src-relative imports are a pain to get to work with plain tsc compilation, as the
// transpiled code will maintain the imports as they are in the source. Which means an
// import for `helpers/paths` will start like that in the output, which won't work in NodeJS.
//
// This is a solution for getting src-relative imports to work with typescript/node in
// the published package. We're using `module: "amd"` to ship the entire cli implementation
// in one file, and it also happens to generate correct module definition and import statements.
// Minimal AMD implementation
const moduleFactories = {};
const moduleCache = {};
global.define = (name, deps, moduleFunc) => {
moduleFactories[name] = () => {
const exportsObj = {};
// require() that first searches for locally defined amd modules
const requireFunc = name => {
let factory = moduleFactories[name];
if (!factory) {
// Check /index as well, to mirror nodejs resolution
const index = `${name}/index`;
if (!moduleFactories[index]) {
return require(name);
}
name = index;
factory = moduleFactories[name];
}
if (!moduleCache[name]) {
moduleCache[name] = factory();
}
return moduleCache[name];
};
const impls = deps.slice(2).map(requireFunc);
moduleFunc(requireFunc, exportsObj, ...impls);
return exportsObj;
};
};
require('../dist');
// index module is the entrypoint
moduleFactories.index();
} else {
const tsConfigPath = path.resolve(__dirname, '../tsconfig.json');
const {
baseUrl: relativeBaseUrl,
paths = {},
} = require(tsConfigPath).compilerOptions;
const baseUrl = path.resolve(__dirname, '..', relativeBaseUrl);
// Use tsconfig-paths to resolve src-relative paths during development
const cleanupPaths = require('tsconfig-paths').register({ baseUrl, paths });
require('ts-node').register({
project: path.resolve(__dirname, '../tsconfig.build.json'),
compilerOptions: {
module: 'CommonJS',
},
transpileOnly: true,
});
require('../src');
cleanupPaths();
}
+19 -20
View File
@@ -28,26 +28,6 @@
"bin": {
"backstage-cli": "bin/backstage-cli"
},
"devDependencies": {
"@types/diff": "^4.0.2",
"@types/fs-extra": "^8.1.0",
"@types/html-webpack-plugin": "^3.2.2",
"@types/inquirer": "^6.5.0",
"@types/node": "^13.7.2",
"@types/ora": "^3.2.0",
"@types/react-dev-utils": "^9.0.4",
"@types/recursive-readdir": "^2.2.0",
"@types/rollup-plugin-peer-deps-external": "^2.2.0",
"@types/rollup-plugin-postcss": "^2.0.0",
"@types/tar": "^4.0.3",
"@types/webpack": "^4.41.7",
"@types/webpack-dev-server": "^3.10.0",
"del": "^5.1.0",
"nodemon": "^2.0.2",
"ts-node": "^8.6.2",
"tsconfig-paths": "^3.9.0",
"zombie": "^6.1.4"
},
"dependencies": {
"@lerna/package-graph": "^3.18.5",
"@lerna/project": "^3.18.0",
@@ -86,6 +66,25 @@
"webpack": "^4.41.6",
"webpack-dev-server": "^3.10.3"
},
"devDependencies": {
"@types/diff": "^4.0.2",
"@types/fs-extra": "^8.1.0",
"@types/html-webpack-plugin": "^3.2.2",
"@types/inquirer": "^6.5.0",
"@types/node": "^13.7.2",
"@types/ora": "^3.2.0",
"@types/react-dev-utils": "^9.0.4",
"@types/recursive-readdir": "^2.2.0",
"@types/rollup-plugin-peer-deps-external": "^2.2.0",
"@types/rollup-plugin-postcss": "^2.0.0",
"@types/tar": "^4.0.3",
"@types/webpack": "^4.41.7",
"@types/webpack-dev-server": "^3.10.0",
"del": "^5.1.0",
"nodemon": "^2.0.2",
"ts-node": "^8.6.2",
"zombie": "^6.1.4"
},
"files": [
"templates",
"config",
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { run } from 'lib/run';
import { run } from '../../lib/run';
export default async () => {
const args = ['build'];
+3 -3
View File
@@ -14,9 +14,9 @@
* limitations under the License.
*/
import { run } from 'lib/run';
import { createLogFunc } from 'lib/logging';
import { watchDeps } from 'lib/watchDeps';
import { run } from '../../lib/run';
import { createLogFunc } from '../../lib/logging';
import { watchDeps } from '../../lib/watchDeps';
export default async () => {
// Start dynamic watch and build of dependencies, then serve the app
@@ -15,8 +15,8 @@
*/
import { Command } from 'commander';
import { run } from 'lib/run';
import { withCache, parseOptions } from 'lib/buildCache';
import { run } from '../../lib/run';
import { withCache, parseOptions } from '../../lib/buildCache';
/*
* The build-cache command is used to make builds a no-op if there are no changes to the package.
+2 -2
View File
@@ -16,8 +16,8 @@
import fs from 'fs-extra';
import { resolve as resolvePath, relative as relativePath } from 'path';
import { paths } from 'lib/paths';
import { getDefaultCacheOptions } from 'lib/buildCache';
import { paths } from '../../lib/paths';
import { getDefaultCacheOptions } from '../../lib/buildCache';
export default async function clean() {
const cacheOptions = getDefaultCacheOptions();
@@ -21,9 +21,9 @@ import inquirer, { Answers, Question } from 'inquirer';
import { exec as execCb } from 'child_process';
import { resolve as resolvePath } from 'path';
import os from 'os';
import { Task, templatingTask } from 'lib/tasks';
import { paths } from 'lib/paths';
import { version } from 'lib/version';
import { Task, templatingTask } from '../../lib/tasks';
import { paths } from '../../lib/paths';
import { version } from '../../lib/version';
const exec = promisify(execCb);
async function checkExists(rootDir: string, name: string) {
@@ -63,7 +63,7 @@ async function buildApp(appFolder: string) {
await Task.forItem('executing', command, async () => {
process.chdir(appFolder);
await exec(command).catch(error => {
await exec(command).catch((error) => {
process.stdout.write(error.stderr);
process.stdout.write(error.stdout);
throw new Error(`Could not execute command ${chalk.cyan(command)}`);
@@ -78,7 +78,7 @@ export async function moveApp(
id: string,
) {
await Task.forItem('moving', id, async () => {
await fs.move(tempDir, destination).catch(error => {
await fs.move(tempDir, destination).catch((error) => {
throw new Error(
`Failed to move app from ${tempDir} to ${destination}: ${error.message}`,
);
@@ -101,7 +101,7 @@ async function addPackageResolutions(appDir: string) {
packageFileJson.resolutions[`@backstage/${pkg}`] = `file:${pkgPath}`;
const newContents = `${JSON.stringify(packageFileJson, null, 2)}\n`;
await fs.writeFile(pkgJsonPath, newContents, 'utf-8').catch(error => {
await fs.writeFile(pkgJsonPath, newContents, 'utf-8').catch((error) => {
throw new Error(
`Failed to add resolutions to package.json: ${error.message}`,
);
@@ -25,10 +25,10 @@ import {
parseOwnerIds,
addCodeownersEntry,
getCodeownersFilePath,
} from 'lib/codeowners';
import { paths } from 'lib/paths';
import { version } from 'lib/version';
import { Task, templatingTask } from 'lib/tasks';
} from '../../lib/codeowners';
import { paths } from '../../lib/paths';
import { version } from '../../lib/version';
import { Task, templatingTask } from '../../lib/tasks';
const exec = promisify(execCb);
async function checkExists(rootDir: string, id: string) {
@@ -107,7 +107,7 @@ export async function addPluginDependencyToApp(
packageFileJson.dependencies = sortObjectByKeys(dependencies);
const newContents = `${JSON.stringify(packageFileJson, null, 2)}\n`;
await fs.writeFile(packageFile, newContents, 'utf-8').catch(error => {
await fs.writeFile(packageFile, newContents, 'utf-8').catch((error) => {
throw new Error(
`Failed to add plugin as dependency to app: ${packageFile}: ${error.message}`,
);
@@ -119,14 +119,14 @@ export async function addPluginToApp(rootDir: string, pluginName: string) {
const pluginPackage = `@backstage/plugin-${pluginName}`;
const pluginNameCapitalized = pluginName
.split('-')
.map(name => capitalize(name))
.map((name) => capitalize(name))
.join('');
const pluginExport = `export { plugin as ${pluginNameCapitalized} } from '${pluginPackage}';`;
const pluginsFilePath = 'packages/app/src/plugins.ts';
const pluginsFile = resolvePath(rootDir, pluginsFilePath);
await Task.forItem('processing', pluginsFilePath, async () => {
await addExportStatement(pluginsFile, pluginExport).catch(error => {
await addExportStatement(pluginsFile, pluginExport).catch((error) => {
throw new Error(
`Failed to import plugin in app: ${pluginsFile}: ${error.message}`,
);
@@ -146,7 +146,7 @@ async function buildPlugin(pluginFolder: string) {
await Task.forItem('executing', command, async () => {
process.chdir(pluginFolder);
await exec(command).catch(error => {
await exec(command).catch((error) => {
process.stdout.write(error.stderr);
process.stdout.write(error.stdout);
throw new Error(`Could not execute command ${chalk.cyan(command)}`);
@@ -161,7 +161,7 @@ export async function movePlugin(
id: string,
) {
await Task.forItem('moving', id, async () => {
await fs.move(tempDir, destination).catch(error => {
await fs.move(tempDir, destination).catch((error) => {
throw new Error(
`Failed to move plugin from ${tempDir} to ${destination}: ${error.message}`,
);
+1 -1
View File
@@ -15,7 +15,7 @@
*/
import { Command } from 'commander';
import { run } from 'lib/run';
import { run } from '../lib/run';
export default async (cmd: Command) => {
const args = ['lint', '--max-warnings=0', '--format=codeframe'];
+3 -3
View File
@@ -17,8 +17,8 @@
import { rollup, watch, OutputOptions } from 'rollup';
import { Command } from 'commander';
import chalk from 'chalk';
import { withCache, getDefaultCacheOptions } from 'lib/buildCache';
import { paths } from 'lib/paths';
import { withCache, getDefaultCacheOptions } from '../../lib/buildCache';
import { paths } from '../../lib/paths';
import conf from './rollup.config';
function logError(error: any) {
@@ -62,7 +62,7 @@ export default async (cmd: Command) => {
// Instead we just wait until the user sends an interrupt signal.
await new Promise(() => {
const watcher = watch(conf);
watcher.on('event', event => {
watcher.on('event', (event) => {
// START — the watcher is (re)starting
// BUNDLE_START — building an individual bundle
// BUNDLE_END — finished building a bundle
@@ -18,7 +18,7 @@ import fs from 'fs-extra';
import chalk from 'chalk';
import { dirname } from 'path';
import { diffLines } from 'diff';
import { paths } from 'lib/paths';
import { paths } from '../../../lib/paths';
import { TemplateFile, PromptFunc, FileHandler } from './types';
export async function writeTargetFile(targetPath: string, contents: string) {
@@ -193,8 +193,8 @@ export async function handleAllFiles(
) {
for (const file of files) {
const { targetPath } = file;
const fileHandler = fileHandlers.find(handler =>
handler.patterns.some(pattern =>
const fileHandler = fileHandlers.find((handler) =>
handler.patterns.some((pattern) =>
typeof pattern === 'string'
? pattern === targetPath
: pattern.test(targetPath),
@@ -18,8 +18,8 @@ import fs from 'fs-extra';
import { relative as relativePath } from 'path';
import handlebars from 'handlebars';
import recursiveReadDir from 'recursive-readdir';
import { paths } from 'lib/paths';
import { version } from 'lib/version';
import { paths } from '../../../lib/paths';
import { version } from '../../../lib/version';
import { PluginInfo, TemplateFile } from './types';
// Reads info from the existing plugin
@@ -64,9 +64,11 @@ export async function readTemplate(
templateDir: string,
templateVars: any,
): Promise<TemplateFile[]> {
const templateFilePaths = await recursiveReadDir(templateDir).catch(error => {
throw new Error(`Failed to read template directory: ${error.message}`);
});
const templateFilePaths = await recursiveReadDir(templateDir).catch(
(error) => {
throw new Error(`Failed to read template directory: ${error.message}`);
},
);
const templateFiles = new Array<TemplateFile>();
for (const templateFile of templateFilePaths) {
@@ -22,7 +22,7 @@ import postcss from 'rollup-plugin-postcss';
import imageFiles from 'rollup-plugin-image-files';
import json from '@rollup/plugin-json';
import { RollupWatchOptions } from 'rollup';
import { paths } from 'lib/paths';
import { paths } from '../../lib/paths';
export default {
input: 'src/index.ts',
@@ -15,7 +15,7 @@
*/
import { startDevServer } from './server';
import { watchDeps } from 'lib/watchDeps';
import { watchDeps } from '../../../lib/watchDeps';
export default async () => {
await watchDeps({ build: true });
@@ -15,7 +15,7 @@
*/
import { existsSync } from 'fs';
import { paths } from 'lib/paths';
import { paths } from '../../../lib/paths';
export function getPaths() {
const resolveTargetModule = (path: string) => {
@@ -15,7 +15,7 @@
*/
import { Command } from 'commander';
import { run } from 'lib/run';
import { run } from '../../lib/run';
export default async (cmd: Command) => {
const args = ['test'];
@@ -17,13 +17,13 @@
import fse from 'fs-extra';
import path from 'path';
import os from 'os';
import { paths } from 'lib/paths';
import { paths } from '../../lib/paths';
import {
addExportStatement,
capitalize,
createTemporaryPluginFolder,
} from '../create-plugin/createPlugin';
import { addCodeownersEntry } from 'lib/codeowners';
import { addCodeownersEntry } from '../../lib/codeowners';
import {
removeReferencesFromAppPackage,
removeReferencesFromPluginsFile,
@@ -39,10 +39,7 @@ const testPluginPackage = `${BACKSTAGE}/plugin-${testPluginName}`;
const tempDir = path.join(os.tmpdir(), 'remove-plugin-test');
const removeEmptyLines = (file: string): string =>
file
.split('\n')
.filter(Boolean)
.join('\n');
file.split('\n').filter(Boolean).join('\n');
const createTestPackageFile = async (
testFilePath: string,
@@ -69,7 +66,7 @@ const createTestPluginFile = async (
fse.copyFileSync(pluginsFilePath, testFilePath);
const pluginNameCapitalized = testPluginName
.split('-')
.map(name => capitalize(name))
.map((name) => capitalize(name))
.join('');
const exportStatement = `export { default as ${pluginNameCapitalized}} from @backstage/plugin-${testPluginName}`;
addExportStatement(testFilePath, exportStatement);
@@ -17,9 +17,9 @@ import fse from 'fs-extra';
import path from 'path';
import chalk from 'chalk';
import inquirer, { Answers, Question } from 'inquirer';
import { getCodeownersFilePath } from 'lib/codeowners';
import { paths } from 'lib/paths';
import { Task } from 'lib/tasks';
import { getCodeownersFilePath } from '../../lib/codeowners';
import { paths } from '../../lib/paths';
import { Task } from '../../lib/tasks';
// import os from 'os';
const BACKSTAGE = '@backstage';
@@ -85,7 +85,7 @@ const removeAllStatementsContainingID = async (file: string, ID: string) => {
const contentAfterRemoval = originalContent
.split('\n')
.filter(Boolean) // get rid of empty lines
.filter(statement => {
.filter((statement) => {
return !statement.includes(`${ID}`);
}) // get rid of lines with pluginName
.concat(['']) // newline at end of line
@@ -105,7 +105,7 @@ export const removeReferencesFromPluginsFile = async (
) => {
const pluginNameCapitalized = pluginName
.split('-')
.map(name => capitalize(name))
.map((name) => capitalize(name))
.join('');
await Task.forItem('removing', 'export references', async () => {
+2 -2
View File
@@ -15,8 +15,8 @@
*/
import { Command } from 'commander';
import { paths } from 'lib/paths';
import { runCheck } from 'lib/run';
import { paths } from '../lib/paths';
import { runCheck } from '../lib/run';
function includesAnyOf(hayStack: string[], ...needles: string[]) {
for (const needle of needles) {
@@ -15,9 +15,9 @@
*/
import { Command } from 'commander';
import { run } from 'lib/run';
import { createLogPipe } from 'lib/logging';
import { watchDeps, Options } from 'lib/watchDeps';
import { run } from '../../lib/run';
import { createLogPipe } from '../../lib/logging';
import { watchDeps, Options } from '../../lib/watchDeps';
/*
* The watch-deps command is meant to improve iteration speed while working in a large monorepo
+16 -16
View File
@@ -16,8 +16,8 @@
import program from 'commander';
import chalk from 'chalk';
import { exitWithError } from 'lib/errors';
import { version } from 'lib/version';
import { exitWithError } from './lib/errors';
import { version } from './lib/version';
const main = (argv: string[]) => {
program.name('backstage-cli').version(version);
@@ -25,68 +25,68 @@ const main = (argv: string[]) => {
program
.command('create-app')
.description('Creates a new app in a new directory')
.action(actionHandler(() => require('commands/create-app/createApp')));
.action(actionHandler(() => require('./commands/create-app/createApp')));
program
.command('app:build')
.description('Build an app for a production release')
.action(actionHandler(() => require('commands/app/build')));
.action(actionHandler(() => require('./commands/app/build')));
program
.command('app:serve')
.description('Serve an app for local development')
.action(actionHandler(() => require('commands/app/serve')));
.action(actionHandler(() => require('./commands/app/serve')));
program
.command('create-plugin')
.description('Creates a new plugin in the current repository')
.action(
actionHandler(() => require('commands/create-plugin/createPlugin')),
actionHandler(() => require('./commands/create-plugin/createPlugin')),
);
program
.command('remove-plugin')
.description('Removes plugin in the current repository')
.action(
actionHandler(() => require('commands/remove-plugin/removePlugin')),
actionHandler(() => require('./commands/remove-plugin/removePlugin')),
);
program
.command('plugin:build')
.option('--watch', 'Enable watch mode')
.description('Build a plugin')
.action(actionHandler(() => require('commands/plugin/build')));
.action(actionHandler(() => require('./commands/plugin/build')));
program
.command('plugin:serve')
.description('Serves the dev/ folder of a plugin')
.action(actionHandler(() => require('commands/plugin/serve')));
.action(actionHandler(() => require('./commands/plugin/serve')));
program
.command('plugin:diff')
.option('--check', 'Fail if changes are required')
.option('--yes', 'Apply all changes')
.description('Diff an existing plugin with the creation template')
.action(actionHandler(() => require('commands/plugin/diff')));
.action(actionHandler(() => require('./commands/plugin/diff')));
program
.command('lint')
.option('--fix', 'Attempt to automatically fix violations')
.description('Lint a package')
.action(actionHandler(() => require('commands/lint')));
.action(actionHandler(() => require('./commands/lint')));
program
.command('test')
.allowUnknownOption(true) // Allows the command to run, but we still need to parse raw args
.helpOption(', --backstage-cli-help') // Let Jest handle help
.description('Run tests, forwarding args to Jest, defaulting to watch mode')
.action(actionHandler(() => require('commands/testCommand')));
.action(actionHandler(() => require('./commands/testCommand')));
program
.command('watch-deps')
.option('--build', 'Build all dependencies on startup')
.description('Watch all dependencies while running another command')
.action(actionHandler(() => require('commands/watch-deps')));
.action(actionHandler(() => require('./commands/watch-deps')));
program
.command('build-cache')
@@ -103,12 +103,12 @@ const main = (argv: string[]) => {
'Cache dir',
'<repoRoot>/node_modules/.cache/backstage-builds',
)
.action(actionHandler(() => require('commands/build-cache')));
.action(actionHandler(() => require('./commands/build-cache')));
program
.command('clean')
.description('Delete cache directories')
.action(actionHandler(() => require('commands/clean/clean')));
.action(actionHandler(() => require('./commands/clean/clean')));
program.on('command:*', () => {
console.log();
@@ -142,7 +142,7 @@ function actionHandler<T extends readonly any[]>(
};
}
process.on('unhandledRejection', rejection => {
process.on('unhandledRejection', (rejection) => {
if (rejection instanceof Error) {
exitWithError(rejection);
} else {
+6 -8
View File
@@ -16,11 +16,11 @@
import fs from 'fs-extra';
import { resolve as resolvePath, relative as relativePath } from 'path';
import { runPlain, runCheck } from 'lib/run';
import { runPlain, runCheck } from '../run';
import { Options } from './options';
import { extractArchive, createArchive } from './archive';
import { paths } from 'lib/paths';
import { version, isDev } from 'lib/version';
import { paths } from '../paths';
import { version, isDev } from '../version';
const INFO_FILE = '.backstage-build-cache';
@@ -127,9 +127,7 @@ export class Cache {
await writeCacheInfo(outputDir, { key });
const timestamp = new Date().toISOString().replace(/-|:|\..*/g, '');
const rand = Math.random()
.toString(36)
.slice(2, 6);
const rand = Math.random().toString(36).slice(2, 6);
const archiveName = `cache-${timestamp}-${rand}.tgz`;
const archivePath = resolvePath(location, archiveName);
@@ -137,7 +135,7 @@ export class Cache {
const { entries = [] } = (await readCacheInfo(location)) ?? {};
// Check if there's already aan entry for this key, in that case we just wanna bump it
const entryIndex = entries.findIndex(e => compareKeys(e.key, key));
const entryIndex = entries.findIndex((e) => compareKeys(e.key, key));
if (entryIndex !== -1) {
const [existingEntry] = entries.splice(entryIndex, 1);
entries.unshift(existingEntry);
@@ -167,7 +165,7 @@ export class Cache {
return { hit: true, archive };
}
const matchingEntry = this.entries.find(e => compareKeys(e.key, key));
const matchingEntry = this.entries.find((e) => compareKeys(e.key, key));
if (!matchingEntry) {
return { hit: false, archive };
}
+1 -1
View File
@@ -16,7 +16,7 @@
import { resolve as resolvePath } from 'path';
import { Command } from 'commander';
import { paths } from 'lib/paths';
import { paths } from '../paths';
const DEFAULT_CACHE_DIR = '<repoRoot>/node_modules/.cache/backstage-builds';
const DEFAULT_MAX_ENTRIES = 10;
+3 -3
View File
@@ -15,14 +15,14 @@
*/
import { spawn } from 'child_process';
import { LogPipe } from 'lib/logging';
import { LogPipe } from '../logging';
import chalk from 'chalk';
import { Package } from './packages';
export function startCompiler(pkg: Package, logPipe: LogPipe) {
// First we figure out which yarn script is a available, falling back to "build --watch"
const scriptName = ['build:watch', 'watch'].find(
script => script in pkg.scripts,
(script) => script in pkg.scripts,
);
const args = scriptName ? [scriptName] : ['build', '--watch'];
@@ -40,7 +40,7 @@ export function startCompiler(pkg: Package, logPipe: LogPipe) {
watch.stderr.on('data', logErr);
const promise = new Promise<void>((resolve, reject) => {
watch.on('error', error => {
watch.on('error', (error) => {
reject(error);
});
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { createLogPipe } from 'lib/logging';
import { createLogPipe } from '../logging';
export type ColorFunc = (msg: string) => string;
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { paths } from 'lib/paths';
import { paths } from '../paths';
const LernaProject = require('@lerna/project');
const PackageGraph = require('@lerna/package-graph');
+4 -4
View File
@@ -20,8 +20,8 @@ import { createLogPipeFactory } from './logger';
import { findAllDeps } from './packages';
import { startWatcher, startPackageWatcher } from './watcher';
import { startCompiler } from './compiler';
import { run } from 'lib/run';
import { paths } from 'lib/paths';
import { run } from '../run';
import { paths } from '../paths';
const PACKAGE_BLACKLIST = [
// We never want to watch for changes in the cli, but all packages will depend on it.
@@ -66,8 +66,8 @@ export async function watchDeps(options: Options = {}) {
}
// We lazily watch all our deps, as in we don't start the actual watch compiler until a change is detected
const watcher = await startWatcher(deps, WATCH_LOCATIONS, pkg => {
startCompiler(pkg, createLogPipe(pkg.name)).promise.catch(error => {
const watcher = await startWatcher(deps, WATCH_LOCATIONS, (pkg) => {
startCompiler(pkg, createLogPipe(pkg.name)).promise.catch((error) => {
process.stderr.write(`${error}\n`);
});
});
+2 -2
View File
@@ -18,7 +18,7 @@ import { resolve as resolvePath } from 'path';
import chalk from 'chalk';
import chokidar from 'chokidar';
import { Package } from './packages';
import { createLogFunc } from 'lib/logging';
import { createLogFunc } from '../logging';
export type Watcher = {
update(newPackages: Package[]): Promise<void>;
@@ -42,7 +42,7 @@ export async function startWatcher(
let signalled = false;
watchedPackageLocations.add(pkg.location);
const watchLocations = paths.map(path => resolvePath(pkg.location, path));
const watchLocations = paths.map((path) => resolvePath(pkg.location, path));
const watcher = chokidar
.watch(watchLocations, {
cwd: pkg.location,
@@ -9,12 +9,6 @@
"lint": "backstage-cli lint",
"test": "backstage-cli test"
},
"devDependencies": {
"@backstage/cli": "^{{version}}",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@types/testing-library__jest-dom": "5.0.2"
},
"dependencies": {
"@backstage/core": "^{{version}}",
"@backstage/theme": "^{{version}}",
@@ -24,6 +18,12 @@
"react-dom": "16.13.1",
"react-router-dom": "5.1.2"
},
"devDependencies": {
"@backstage/cli": "^{{version}}",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@types/testing-library__jest-dom": "5.0.2"
},
"files": [
"dist"
]
@@ -1,7 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "src"
}
"compilerOptions": {}
}
@@ -11,16 +11,6 @@
"test": "backstage-cli test",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^{{version}}",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.2",
"jest-fetch-mock": "^3.0.3"
},
"dependencies": {
"@backstage/core": "^{{version}}",
"@backstage/theme": "^{{version}}",
@@ -31,6 +21,16 @@
"react-dom": "16.13.1",
"react-use": "^13.0.0"
},
"devDependencies": {
"@backstage/cli": "^{{version}}",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.2",
"jest-fetch-mock": "^3.0.3"
},
"files": [
"dist"
]
@@ -1,7 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "src"
}
"compilerOptions": {}
}
+2 -2
View File
@@ -2,7 +2,7 @@
"extends": "./tsconfig.json",
"exclude": ["**/*.test.*"],
"compilerOptions": {
"outFile": "dist/index.js",
"module": "amd"
"outDir": "dist",
"module": "CommonJS"
}
}
+1 -3
View File
@@ -1,7 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "src"
}
"compilerOptions": {}
}
+14 -14
View File
@@ -24,20 +24,6 @@
"test": "backstage-cli test",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@backstage/test-utils": "0.1.1-alpha.4",
"@backstage/test-utils-core": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/classnames": "^2.2.9",
"@types/google-protobuf": "^3.7.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/react-helmet": "^5.0.15",
"@types/react-sparklines": "^1.7.0"
},
"dependencies": {
"@backstage/theme": "^0.1.1-alpha.4",
"@material-ui/core": "^4.9.1",
@@ -56,6 +42,20 @@
"react-router-dom": "^5.1.2",
"react-sparklines": "^1.7.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@backstage/test-utils": "0.1.1-alpha.4",
"@backstage/test-utils-core": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/classnames": "^2.2.9",
"@types/google-protobuf": "^3.7.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/react-helmet": "^5.0.15",
"@types/react-sparklines": "^1.7.0"
},
"files": [
"dist"
]
@@ -19,7 +19,7 @@ import {
UserFlags,
FeatureFlagsRegistry,
FeatureFlagsRegistryItem,
} from 'api/app/FeatureFlags';
} from '../../app/FeatureFlags';
/**
* The feature flags API is used to toggle functionality to users across plugins and Backstage.
+4 -4
View File
@@ -18,9 +18,9 @@ import React, { ComponentType } from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import { AppContextProvider } from './AppContext';
import { App } from './types';
import BackstagePlugin from 'api/plugin/Plugin';
import BackstagePlugin from '../plugin/Plugin';
import { FeatureFlagsRegistryItem } from './FeatureFlags';
import { featureFlagsApiRef } from 'api/apis/definitions/featureFlags';
import { featureFlagsApiRef } from '../apis/definitions/featureFlags';
import ErrorPage from '../../layout/ErrorPage';
import {
@@ -28,8 +28,8 @@ import {
SystemIcons,
SystemIconKey,
defaultSystemIcons,
} from 'icons';
import { ApiHolder, ApiProvider } from 'api/apis';
} from '../../icons';
import { ApiHolder, ApiProvider } from '../apis';
import LoginPage from './LoginPage';
class AppImpl implements App {
@@ -18,7 +18,7 @@ import { FeatureFlags as FeatureFlagsImpl } from './FeatureFlags';
import {
FeatureFlagState,
FeatureFlagsApi,
} from 'api/apis/definitions/featureFlags';
} from '../apis/definitions/featureFlags';
describe('FeatureFlags', () => {
beforeEach(() => {
@@ -150,7 +150,7 @@ describe('FeatureFlags', () => {
it('should get the correct values', () => {
const getByName = (name: string) =>
featureFlags.getRegisteredFlags().find(flag => flag.name === name);
featureFlags.getRegisteredFlags().find((flag) => flag.name === name);
expect(getByName('registered-flag-0')).toBeUndefined();
expect(getByName('registered-flag-1')).toEqual({
+5 -5
View File
@@ -14,11 +14,11 @@
* limitations under the License.
*/
import { FeatureFlagName } from 'api/plugin/types';
import { FeatureFlagName } from '../plugin/types';
import {
FeatureFlagState,
FeatureFlagsApi,
} from 'api/apis/definitions/featureFlags';
} from '../apis/definitions/featureFlags';
/**
* Helper method for validating compatibility and flag name.
@@ -130,12 +130,12 @@ export interface FeatureFlagsRegistryItem {
export class FeatureFlagsRegistry extends Array<FeatureFlagsRegistryItem> {
static from(entries: FeatureFlagsRegistryItem[]) {
Array.from(entries).forEach(entry => validateFlagName(entry.name));
Array.from(entries).forEach((entry) => validateFlagName(entry.name));
return new FeatureFlagsRegistry(...entries);
}
push(...entries: FeatureFlagsRegistryItem[]): number {
Array.from(entries).forEach(entry => validateFlagName(entry.name));
Array.from(entries).forEach((entry) => validateFlagName(entry.name));
return super.push(...entries);
}
@@ -146,7 +146,7 @@ export class FeatureFlagsRegistry extends Array<FeatureFlagsRegistryItem> {
)[]
): FeatureFlagsRegistryItem[] {
const _concat = super.concat(...entries);
Array.from(_concat).forEach(entry => validateFlagName(entry.name));
Array.from(_concat).forEach((entry) => validateFlagName(entry.name));
return _concat;
}
@@ -16,10 +16,10 @@
import React, { FC, useState } from 'react';
import { GitHub as GitHubIcon } from '@material-ui/icons';
import Page from 'layout/Page';
import Header from 'layout/Header';
import Content from 'layout/Content/Content';
import ContentHeader from 'layout/ContentHeader/ContentHeader';
import Page from '../../../layout/Page';
import Header from '../../../layout/Header';
import Content from '../../../layout/Content/Content';
import ContentHeader from '../../../layout/ContentHeader/ContentHeader';
import {
Grid,
Typography,
@@ -29,7 +29,7 @@ import {
ListItem,
Link,
} from '@material-ui/core';
import InfoCard from 'layout/InfoCard/InfoCard';
import InfoCard from '../../../layout/InfoCard/InfoCard';
enum AuthType {
GitHub,
@@ -72,11 +72,11 @@ const LoginPage: FC<{}> = () => {
'Content-Type': 'application/x-www-form-urlencoded',
}),
})
.then(response => {
.then((response) => {
if (response.status === 200) return response.json();
throw Error(`${response.status} ${response.statusText}`);
})
.then(data => {
.then((data) => {
const info = {
username: username,
token: token,
+1 -1
View File
@@ -21,7 +21,7 @@ import {
RouteOptions,
FeatureFlagName,
} from './types';
import { validateBrowserCompat, validateFlagName } from 'api/app/FeatureFlags';
import { validateBrowserCompat, validateFlagName } from '../app/FeatureFlags';
export type PluginConfig = {
id: string;
@@ -16,7 +16,7 @@
import React from 'react';
import CopyTextButton from '.';
import { ApiProvider, errorApiRef, ApiRegistry, ErrorApi } from 'api';
import { ApiProvider, errorApiRef, ApiRegistry, ErrorApi } from '../../api';
export default {
title: 'CopyTextButton',
@@ -18,7 +18,7 @@ import React from 'react';
import { render } from '@testing-library/react';
import { wrapInThemedTestApp } from '@backstage/test-utils';
import CopyTextButton from './CopyTextButton';
import { ApiRegistry, errorApiRef, ApiProvider, ErrorApi } from 'api';
import { ApiRegistry, errorApiRef, ApiProvider, ErrorApi } from '../../api';
jest.mock('popper.js', () => {
const PopperJS = jest.requireActual('popper.js');
@@ -19,9 +19,9 @@ import { IconButton, makeStyles, Tooltip } from '@material-ui/core';
import PropTypes from 'prop-types';
import CopyIcon from '@material-ui/icons/FileCopy';
import { BackstageTheme } from '@backstage/theme';
import { errorApiRef, useApi } from 'api';
import { errorApiRef, useApi } from '../../api';
const useStyles = makeStyles<BackstageTheme>(theme => ({
const useStyles = makeStyles<BackstageTheme>((theme) => ({
button: {
'&:hover': {
backgroundColor: theme.palette.highlight,
@@ -56,7 +56,7 @@ const defaultProps = {
tooltipText: 'Text copied to clipboard',
};
const CopyTextButton: FC<Props> = props => {
const CopyTextButton: FC<Props> = (props) => {
const { text, tooltipDelay, tooltipText } = {
...defaultProps,
...props,
@@ -66,7 +66,7 @@ const CopyTextButton: FC<Props> = props => {
const inputRef = useRef<HTMLInputElement>(null);
const [open, setOpen] = useState(false);
const handleCopyClick: MouseEventHandler = e => {
const handleCopyClick: MouseEventHandler = (e) => {
e.stopPropagation();
setOpen(true);
@@ -16,7 +16,7 @@
import React, { FC } from 'react';
import { makeStyles } from '@material-ui/core';
import InfoCard from 'layout/InfoCard';
import InfoCard from '../../layout/InfoCard';
import { Props as BottomLinkProps } from '../../layout/BottomLink';
import CircleProgress from './CircleProgress';
@@ -36,7 +36,7 @@ const useStyles = makeStyles({
},
});
const ProgressCard: FC<Props> = props => {
const ProgressCard: FC<Props> = (props) => {
const classes = useStyles(props);
const { title, subheader, progress, deepLink, variant } = props;
@@ -23,6 +23,33 @@ const getTextInSlide = (rendered: any, index: number) =>
within(rendered.getByTestId(`step${index}`)).getByText;
describe('Stepper', () => {
it('Maintains state history', async () => {
const rendered = render(
wrapInTestApp(
<Stepper>
<Step title="Step 0" data-testid="step0">
<div>step0</div>
</Step>
<Step title="Step 1" data-testid="step1">
<div>step1</div>
</Step>
<Step title="Step 2" data-testid="step2">
<div>step2</div>
</Step>
</Stepper>,
),
);
fireEvent.click(getTextInSlide(rendered, 0)('Next') as Node);
expect(rendered.getByText('step1')).toBeInTheDocument();
fireEvent.click(getTextInSlide(rendered, 1)('Back') as Node);
expect(rendered.getByText('step0')).toBeInTheDocument();
fireEvent.click(getTextInSlide(rendered, 0)('Next') as Node);
expect(rendered.getByText('step1')).toBeInTheDocument();
});
it('Handles nextStep property', async () => {
const rendered = render(
wrapInTestApp(
@@ -42,7 +42,7 @@ export interface StepperProps {
const Stepper: FC<StepperProps> = ({ children, elevated, onStepChange }) => {
const [stepIndex, setStepIndex] = useState<number>(0);
const [stepHistory, setStepHistory] = useState<number[]>([]);
const [stepHistory, setStepHistory] = useState<number[]>([0]);
const steps: React.ReactNode[] = [];
let endStep;
@@ -24,8 +24,8 @@ import {
StatusRunning,
StatusWarning,
} from './Status';
import Table from 'components/Table';
import InfoCard from 'layout/InfoCard';
import Table from '../Table';
import InfoCard from '../../layout/InfoCard';
export default {
title: 'Status',
@@ -22,7 +22,9 @@ export default {
component: Table,
};
const generateTestData: (number: number) => Array<{}> = (rows = 20) => {
const containerStyle = { width: 850 };
const generateTestData: (number: number) => Array<{}> = (rows = 10) => {
const data: Array<{}> = [];
while (data.length <= rows) {
data.push({
@@ -37,7 +39,7 @@ const generateTestData: (number: number) => Array<{}> = (rows = 20) => {
return data;
};
const testData100 = generateTestData(100);
const testData10 = generateTestData(10);
export const DefaultTable = () => {
const columns: TableColumn[] = [
@@ -63,12 +65,14 @@ export const DefaultTable = () => {
];
return (
<Table
options={{ paging: false }}
data={testData100}
columns={columns}
title="Backstage Table"
/>
<div style={containerStyle}>
<Table
options={{ paging: false }}
data={testData10}
columns={columns}
title="Backstage Table"
/>
</div>
);
};
@@ -96,11 +100,13 @@ export const HiddenSearchTable = () => {
];
return (
<Table
options={{ paging: false, search: false }}
data={testData100}
columns={columns}
/>
<div style={containerStyle}>
<Table
options={{ paging: false, search: false }}
data={testData10}
columns={columns}
/>
</div>
);
};
@@ -138,6 +144,8 @@ export const SubvalueTable = () => {
];
return (
<Table options={{ paging: false }} data={testData100} columns={columns} />
<div style={containerStyle}>
<Table options={{ paging: false }} data={testData10} columns={columns} />
</div>
);
};
@@ -16,8 +16,8 @@
import React from 'react';
import TrendLine from '.';
import Table from 'components/Table';
import InfoCard from 'layout/InfoCard';
import Table from '../Table';
import InfoCard from '../../layout/InfoCard';
export default {
title: 'TrendLine',
@@ -19,7 +19,7 @@ import { Typography, makeStyles } from '@material-ui/core';
import { BackstageTheme } from '@backstage/theme';
import ErrorOutline from '@material-ui/icons/ErrorOutline';
const useErrorOutlineStyles = makeStyles<BackstageTheme>(theme => ({
const useErrorOutlineStyles = makeStyles<BackstageTheme>((theme) => ({
root: {
marginRight: theme.spacing(1),
fill: theme.palette.warningText,
@@ -30,7 +30,7 @@ const ErrorOutlineStyled = () => {
return <ErrorOutline classes={classes} />;
};
const useStyles = makeStyles<BackstageTheme>(theme => ({
const useStyles = makeStyles<BackstageTheme>((theme) => ({
message: {
display: 'flex',
flexDirection: 'column',
@@ -62,7 +62,7 @@ type Props = {
title?: string;
};
const WarningPanel: FC<Props> = props => {
const WarningPanel: FC<Props> = (props) => {
const classes = useStyles(props);
const { title, message, children } = props;
return (
@@ -74,7 +74,9 @@ const WarningPanel: FC<Props> = props => {
</Typography>
</div>
{message && (
<Typography className={classes.messageText}>{message}</Typography>
<Typography className={classes.messageText} variant="body2">
{message}
</Typography>
)}
{children}
</div>
+2 -2
View File
@@ -18,7 +18,7 @@ import { SvgIconProps } from '@material-ui/core';
import PeopleIcon from '@material-ui/icons/People';
import PersonIcon from '@material-ui/icons/Person';
import React, { FC } from 'react';
import { useApp } from 'api/app/AppContext';
import { useApp } from '../api/app/AppContext';
import { IconComponent, SystemIconKey, SystemIcons } from './types';
export const defaultSystemIcons: SystemIcons = {
@@ -27,7 +27,7 @@ export const defaultSystemIcons: SystemIcons = {
};
const overridableSystemIcon = (key: SystemIconKey): IconComponent => {
const Component: FC<SvgIconProps> = props => {
const Component: FC<SvgIconProps> = (props) => {
const app = useApp();
const Icon = app.getSystemIcon(key);
return <Icon {...props} />;
+3 -3
View File
@@ -19,11 +19,11 @@ import Helmet from 'react-helmet';
import { Typography, Tooltip, makeStyles } from '@material-ui/core';
import { BackstageTheme } from '@backstage/theme';
import { Theme } from 'layout/Page/Page';
import { Theme } from '../Page/Page';
// import { Link } from 'shared/components';
import Waves from './Waves';
const useStyles = makeStyles<BackstageTheme>(theme => ({
const useStyles = makeStyles<BackstageTheme>((theme) => ({
header: {
gridArea: 'pageHeader',
padding: theme.spacing(3),
@@ -175,7 +175,7 @@ export const Header: FC<Props> = ({
<Fragment>
<Helmet titleTemplate={titleTemplate} defaultTitle={defaultTitle} />
<Theme.Consumer>
{theme => (
{(theme) => (
<header style={style} className={classes.header}>
<Waves theme={theme} />
<div className={classes.leftItemsBox}>
@@ -16,7 +16,7 @@
import React from 'react';
import { render } from '@testing-library/react';
import { pageTheme } from 'layout/Page/PageThemeProvider';
import { pageTheme } from '../Page/PageThemeProvider';
import Waves from './Waves';
describe('<Waves/>', () => {
+1 -1
View File
@@ -16,7 +16,7 @@
import React, { FC } from 'react';
import { makeStyles } from '@material-ui/core';
import { PageTheme } from 'layout/Page';
import { PageTheme } from '../Page';
const useStyles = makeStyles<PageTheme>({
wave: {
@@ -24,7 +24,7 @@ import {
withStyles,
makeStyles,
} from '@material-ui/core';
import ErrorBoundary from 'layout/ErrorBoundary/ErrorBoundary';
import ErrorBoundary from '../ErrorBoundary';
import BottomLink, { Props as BottomLinkProps } from '../BottomLink';
const useStyles = makeStyles((theme) => ({
+1 -3
View File
@@ -1,7 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "src"
}
"compilerOptions": {}
}
+1 -3
View File
@@ -1,7 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "src"
}
"compilerOptions": {}
}
+1 -2
View File
@@ -22,7 +22,6 @@ module.exports = {
// Point to dist version of theme and any other packages that might be needed in the future
'@backstage/theme': path.resolve(__dirname, '../../theme'),
};
config.resolve.modules.push(coreSrc);
// Remove the default babel-loader for js files, we're using ts-loader instead
const [jsLoader] = config.module.rules.splice(0, 1);
@@ -56,7 +55,7 @@ module.exports = {
// Fail storybook build on CI if there are webpack warnings.
if (process.env.CI) {
config.plugins.push(new WebpackPluginFailBuildOnWarning())
config.plugins.push(new WebpackPluginFailBuildOnWarning());
}
return config;
+3 -3
View File
@@ -13,6 +13,9 @@
"@storybook/addons/**"
]
},
"dependencies": {
"@backstage/theme": "0.1.1-alpha.4"
},
"devDependencies": {
"@storybook/addon-actions": "^5.3.17",
"@storybook/addon-links": "^5.3.17",
@@ -20,8 +23,5 @@
"@storybook/addons": "^5.3.17",
"@storybook/react": "^5.3.17",
"storybook-dark-mode": "^0.4.1"
},
"dependencies": {
"@backstage/theme": "0.1.1-alpha.4"
}
}
+1 -3
View File
@@ -1,7 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "src"
}
"compilerOptions": {}
}
+1 -3
View File
@@ -1,7 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "src"
}
"compilerOptions": {}
}
+3 -3
View File
@@ -23,12 +23,12 @@
"lint": "backstage-cli lint",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4"
},
"dependencies": {
"@material-ui/core": "^4.9.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4"
},
"files": [
"dist"
]
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { createTheme } from 'baseTheme';
import { createTheme } from './baseTheme';
import { blue, yellow } from '@material-ui/core/colors';
export const lightTheme = createTheme({
+1 -3
View File
@@ -1,7 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "src"
}
"compilerOptions": {}
}
+11 -11
View File
@@ -11,17 +11,6 @@
"test": "backstage-cli test",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@backstage/test-utils": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.2",
"jest-fetch-mock": "^3.0.3"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.4",
"@backstage/theme": "^0.1.1-alpha.4",
@@ -33,6 +22,17 @@
"react-dom": "16.13.1",
"react-use": "^13.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@backstage/test-utils": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.4",
"jest-fetch-mock": "^3.0.3"
},
"files": [
"dist"
]
+1 -3
View File
@@ -1,7 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "src"
}
"compilerOptions": {}
}
+13 -13
View File
@@ -25,19 +25,6 @@
"test": "backstage-cli test",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@backstage/dev-utils": "^0.1.1-alpha.4",
"@backstage/test-utils": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.2",
"jest-fetch-mock": "^3.0.3",
"react-router-dom": "^5.1.2"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.4",
"@backstage/theme": "^0.1.1-alpha.4",
@@ -50,6 +37,19 @@
"react-dom": "16.13.1",
"react-use": "^13.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@backstage/dev-utils": "^0.1.1-alpha.4",
"@backstage/test-utils": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.4",
"jest-fetch-mock": "^3.0.3",
"react-router-dom": "^5.1.2"
},
"files": [
"dist"
]
@@ -17,8 +17,8 @@
import React, { FC, useState, Suspense } from 'react';
import { Tabs, Tab, makeStyles, Typography, Divider } from '@material-ui/core';
import 'graphiql/graphiql.css';
import { StorageBucket } from 'lib/storage';
import { GraphQLEndpoint } from 'lib/api';
import { StorageBucket } from '../../lib/storage';
import { GraphQLEndpoint } from '../../lib/api';
import { Progress } from '@backstage/core';
import { BackstageTheme } from '@backstage/theme';
@@ -20,7 +20,7 @@ import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
import { ApiProvider, ApiRegistry } from '@backstage/core';
import { renderWithEffects } from '@backstage/test-utils';
import { GraphQLBrowseApi, graphQlBrowseApiRef } from 'lib/api';
import { GraphQLBrowseApi, graphQlBrowseApiRef } from '../../lib/api';
jest.mock('components/GraphiQLBrowser', () => ({
GraphiQLBrowser: () => '<GraphiQLBrowser />',
@@ -26,8 +26,8 @@ import {
} from '@backstage/core';
import { useAsync } from 'react-use';
import 'graphiql/graphiql.css';
import { graphQlBrowseApiRef } from 'lib/api';
import { GraphiQLBrowser } from 'components';
import { graphQlBrowseApiRef } from '../../lib/api';
import { GraphiQLBrowser } from '../GraphiQLBrowser';
import { Typography } from '@material-ui/core';
export const GraphiQLPage: FC<{}> = () => {
+1 -3
View File
@@ -1,7 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["src", "dev"],
"compilerOptions": {
"baseUrl": "src"
}
"compilerOptions": {}
}
+1 -3
View File
@@ -17,6 +17,4 @@
import { createDevApp } from '@backstage/dev-utils';
import { plugin } from '../src/plugin';
createDevApp()
.registerPlugin(plugin)
.render();
createDevApp().registerPlugin(plugin).render();
+11 -11
View File
@@ -12,17 +12,6 @@
"test": "backstage-cli test",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@backstage/dev-utils": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.2",
"react-router-dom": "^5.1.2"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.4",
"@backstage/theme": "^0.1.1-alpha.4",
@@ -32,6 +21,17 @@
"react": "16.13.1",
"react-dom": "16.13.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@backstage/dev-utils": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.4",
"react-router-dom": "^5.1.2"
},
"files": [
"dist"
]
@@ -16,7 +16,7 @@
import React, { FC } from 'react';
import { Typography, Link, Grid } from '@material-ui/core';
import HomePageTimer from 'components/HomepageTimer';
import HomePageTimer from '../HomepageTimer';
import { Content, InfoCard, Header, Page, pageTheme } from '@backstage/core';
import SquadTechHealth from './SquadTechHealth';
import Table from '@material-ui/core/Table';
@@ -68,7 +68,7 @@ const HomePage: FC<{}> = () => {
</TableRow>
</TableHead>
<TableBody>
{data.map(d => (
{data.map((d) => (
<TableRow key={d.id}>
<TableCell>{d.entity}</TableCell>
<TableCell>{d.kind}</TableCell>
+1 -1
View File
@@ -15,7 +15,7 @@
*/
import { createPlugin } from '@backstage/core';
import HomePage from 'components/HomePage';
import HomePage from './components/HomePage';
export const plugin = createPlugin({
id: 'home-page',
+1 -3
View File
@@ -1,7 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["src", "dev"],
"compilerOptions": {
"baseUrl": "src"
}
"compilerOptions": {}
}
+5 -5
View File
@@ -11,11 +11,6 @@
"test": "backstage-cli test",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"jest-fetch-mock": "^3.0.3",
"tsc-watch": "^4.2.3"
},
"dependencies": {
"@backstage/backend-common": "0.1.1-alpha.4",
"compression": "^1.7.4",
@@ -26,6 +21,11 @@
"morgan": "^1.10.0",
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"jest-fetch-mock": "^3.0.3",
"tsc-watch": "^4.2.3"
},
"files": [
"dist"
],
-1
View File
@@ -1,7 +1,6 @@
{
"include": ["src"],
"compilerOptions": {
"baseUrl": "src",
"outDir": "dist",
"incremental": true,
"sourceMap": true,
+10 -10
View File
@@ -12,16 +12,6 @@
"test": "backstage-cli test",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.2",
"jest-fetch-mock": "^3.0.3"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.4",
"@backstage/theme": "^0.1.1-alpha.4",
@@ -32,6 +22,16 @@
"react-dom": "16.13.1",
"react-use": "^13.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.4",
"jest-fetch-mock": "^3.0.3"
},
"files": [
"dist"
]
@@ -47,7 +47,7 @@ const InventoryPage: FC<{}> = () => {
</TableRow>
</TableHead>
<TableBody>
{STATIC_DATA.map(d => (
{STATIC_DATA.map((d) => (
<TableRow key={d.id}>
<TableCell>{d.id}</TableCell>
<TableCell>{d.kind}</TableCell>
+1 -3
View File
@@ -1,7 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "src"
}
"compilerOptions": {}
}
+11 -11
View File
@@ -12,17 +12,6 @@
"test": "backstage-cli test",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@backstage/test-utils": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.2",
"jest-fetch-mock": "^3.0.3"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.4",
"@backstage/theme": "^0.1.1-alpha.4",
@@ -35,6 +24,17 @@
"react-router-dom": "^5.1.2",
"react-use": "^13.24.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@backstage/test-utils": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.4",
"jest-fetch-mock": "^3.0.3"
},
"files": [
"dist"
]
@@ -46,7 +46,7 @@ describe('AuditView', () => {
apis = ApiRegistry.from([
[lighthouseApiRef, new LighthouseRestApi('https://lighthouse')],
]);
id = websiteResponse.audits.find(a => a.status === 'COMPLETED')
id = websiteResponse.audits.find((a) => a.status === 'COMPLETED')
?.id as string;
useParams.mockReturnValue({ id });
});
@@ -98,7 +98,7 @@ describe('AuditView', () => {
await rendered.findByTestId('audit-sidebar');
websiteResponse.audits.forEach(a => {
websiteResponse.audits.forEach((a) => {
expect(
rendered.queryByText(formatTime(a.timeCreated)),
).toBeInTheDocument();
@@ -116,14 +116,14 @@ describe('AuditView', () => {
await rendered.findByTestId('audit-sidebar');
const audit = websiteResponse.audits.find(a => a.id === id) as Audit;
const audit = websiteResponse.audits.find((a) => a.id === id) as Audit;
const auditElement = rendered.getByText(formatTime(audit.timeCreated));
expect(auditElement.parentElement?.parentElement?.className).toContain(
'selected',
);
const notSelectedAudit = websiteResponse.audits.find(
a => a.id !== id,
(a) => a.id !== id,
) as Audit;
const notSelectedAuditElement = rendered.getByText(
formatTime(notSelectedAudit.timeCreated),
@@ -144,7 +144,7 @@ describe('AuditView', () => {
await rendered.findByTestId('audit-sidebar');
websiteResponse.audits.forEach(a => {
websiteResponse.audits.forEach((a) => {
expect(
rendered.getByText(formatTime(a.timeCreated)).parentElement
?.parentElement,
@@ -183,7 +183,7 @@ describe('AuditView', () => {
describe.skip('when a loading audit is accessed', () => {
it('shows a loading view', async () => {
id = websiteResponse.audits.find(a => a.status === 'RUNNING')
id = websiteResponse.audits.find((a) => a.status === 'RUNNING')
?.id as string;
useParams.mockReturnValueOnce({ id });
@@ -203,7 +203,7 @@ describe('AuditView', () => {
describe.skip('when a failed audit is accessed', () => {
it('shows an error message', async () => {
id = websiteResponse.audits.find(a => a.status === 'FAILED')
id = websiteResponse.audits.find((a) => a.status === 'FAILED')
?.id as string;
useParams.mockReturnValueOnce({ id });
@@ -68,7 +68,7 @@ const AuditLinkList: FC<AuditLinkListProps> = ({
component="nav"
aria-label="lighthouse audit history"
>
{audits.map(audit => (
{audits.map((audit) => (
<ListItem
key={audit.id}
selected={audit.id === selectedId}
@@ -136,7 +136,7 @@ const ConnectedAuditView: FC<{}> = () => {
<AuditLinkList audits={value?.audits} selectedId={params.id} />
</Grid>
<Grid item xs={9}>
<AuditView audit={value?.audits.find(a => a.id === params.id)} />
<AuditView audit={value?.audits.find((a) => a.id === params.id)} />
</Grid>
</Grid>
);
@@ -164,7 +164,7 @@ describe('CreateAudit', () => {
fireEvent.click(rendered.getByText(/Create Audit/));
await wait(() => expect(rendered.getByLabelText(/URL/)).toBeEnabled());
await new Promise(r => setTimeout(r, 0));
await new Promise((r) => setTimeout(r, 0));
expect(errorApi.post).toHaveBeenCalledWith(expect.any(Error));
});
@@ -40,7 +40,7 @@ import { lighthouseApiRef } from '../../api';
import { useQuery } from '../../utils';
import LighthouseSupportButton from '../SupportButton';
const useStyles = makeStyles(theme => ({
const useStyles = makeStyles((theme) => ({
input: {
minWidth: 300,
},
@@ -113,7 +113,7 @@ const CreateAudit: FC<{}> = () => {
<Grid item xs={12} sm={6}>
<InfoCard>
<form
onSubmit={ev => {
onSubmit={(ev) => {
ev.preventDefault();
triggerAudit();
}}
@@ -128,7 +128,7 @@ const CreateAudit: FC<{}> = () => {
helperText="The target URL for Lighthouse to use."
required
disabled={submitting}
onChange={ev => setUrl(ev.target.value)}
onChange={(ev) => setUrl(ev.target.value)}
value={url}
inputProps={{ 'aria-label': 'URL' }}
/>
@@ -142,7 +142,7 @@ const CreateAudit: FC<{}> = () => {
select
required
disabled={submitting}
onChange={ev => setEmulatedFormFactor(ev.target.value)}
onChange={(ev) => setEmulatedFormFactor(ev.target.value)}
value={emulatedFormFactor}
inputProps={{ 'aria-label': 'Emulated form factor' }}
>
@@ -71,7 +71,7 @@ export default builder.build() as ApiHolder;
\`\`\`
`;
const useStyles = makeStyles(theme => ({
const useStyles = makeStyles((theme) => ({
tabs: { marginBottom: -18 },
tab: { minWidth: 72, paddingLeft: 1, paddingRight: 1 },
content: { marginBottom: theme.spacing(2) },
+2 -2
View File
@@ -53,13 +53,13 @@ export function buildSparklinesDataForItem(
(audit: Audit): audit is AuditCompleted => audit.status === 'COMPLETED',
)
.reduce((scores, audit) => {
Object.values(audit.categories).forEach(category => {
Object.values(audit.categories).forEach((category) => {
scores[category.id] = scores[category.id] || [];
scores[category.id].unshift(category.score);
});
// edge case: if only one audit exists, force a "flat" sparkline
Object.values(scores).forEach(arr => {
Object.values(scores).forEach((arr) => {
if (arr.length === 1) arr.push(arr[0]);
});
+6 -6
View File
@@ -10,12 +10,6 @@
"test": "backstage-cli test",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@types/fs-extra": "^8.1.0",
"@types/supertest": "^2.0.8",
"supertest": "^4.0.2"
},
"dependencies": {
"@backstage/backend-common": "0.1.1-alpha.4",
"dockerode": "^3.2.0",
@@ -28,5 +22,11 @@
"helmet": "^3.22.0",
"morgan": "^1.10.0",
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@types/fs-extra": "^8.1.0",
"@types/supertest": "^2.0.8",
"supertest": "^4.0.2"
}
}
+10 -10
View File
@@ -12,16 +12,6 @@
"test": "backstage-cli test",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.2",
"jest-fetch-mock": "^3.0.3"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.4",
"@backstage/theme": "^0.1.1-alpha.4",
@@ -32,6 +22,16 @@
"react-dom": "16.13.1",
"react-use": "^13.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.4",
"jest-fetch-mock": "^3.0.3"
},
"files": [
"dist"
]
+1 -3
View File
@@ -1,7 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "src"
}
"compilerOptions": {}
}
+95 -69
View File
@@ -1,89 +1,120 @@
# @backstage/plugin-tech-radar
<img src="docs/screenshot.png" width="700" alt="Screenshot of Tech Radar plugin" />
<img src="docs/screenshot.png" alt="Screenshot of Tech Radar plugin" />
The Backstage integration for the Tech Radar based on [Zalando's Tech Radar](https://opensource.zalando.com/tech-radar/) open sourced on [GitHub](https://github.com/zalando/tech-radar). This is used at [Spotify](https://spotify.github.io) for visualizing the official guidelines of different areas of software development such as languages, frameworks, infrastructure and processes.
## Purpose
Zalando explains it very well on their website:
Zalando has a fantastic description [on their website](https://opensource.zalando.com/tech-radar/):
> The Tech Radar is a tool to inspire and support engineering teams at Zalando to pick the best technologies for new projects; it provides a platform to share knowledge and experience in technologies, to reflect on technology decisions and continuously evolve our technology landscape. Based on the pioneering work of ThoughtWorks, our Tech Radar sets out the changes in technologies that are interesting in software development — changes that we think our engineering teams should pay attention to and consider using in their projects.
It serves well for teams and companies of all sizes that want to have alignment and wish to visualize it. It scales well for companies who have dozens of different technologies in place.
It serves and scales well for teams and companies of all sizes that want to have alignment across dozens of technologies and visualize it in a simple way.
## Getting Started
In your installation, add the dependency to your Backstage installation:
The Tech Radar can be used in two ways:
- **Simple (Recommended)** - This gives you an out-of-the-box Tech Radar experience. It lives on the `/tech-radar` URL of your Backstage installation, and you can set a variety of configuration directly in your `apis.ts`.
- **Advanced** - This gives you the React UI component directly. It enables you to insert the Radar on your own layout or page for a more customized feel.
### Install
For either simple or advanced installations, you'll need to add the dependency using Yarn:
```sh
yarn add @backstage/plugin-tech-radar
```
In your `apis.ts` set up the "out of the box" implementation for Tech Radar:
### Simple Configuration
In your `apis.ts` set up the simple "out of the box" implementation for Tech Radar:
```ts
import { ApiHolder, ApiRegistry } from '@backstage/core';
import {
techRadarApiRef,
TechRadar,
loadSampleData,
} from '@backstage/plugin-tech-radar';
const builder = ApiRegistry.builder();
builder.add(techRadarApiRef, new TechRadar(1400, 800, loadSampleData));
builder.add(techRadarApiRef, new TechRadar({
width: 1400,
height: 800
));
export default builder.build() as ApiHolder;
```
It will then be available on your Backstage installation over at <http://localhost:3000/tech-radar>
Congrats, you're done! We'll just load it with [example data](src/sampleData.ts) to get you started. Just go to <http://localhost:3000/tech-radar> to see it live in action.
## Configuration
The implementation for the TechRadar class is:
And if you'd like to configure it more, such as providing it with your own data, see the `TechRadarApi` TypeScript interface below for the options:
```ts
export interface TechRadarAdditionalOptions {
title?: string;
subtitle?: string;
export interface TechRadarComponentProps {
width: number;
height: number;
getData?: () => Promise<TechRadarLoaderResponse>;
svgProps?: object;
}
export interface TechRadarLoaderResponse {
quadrants: RadarQuadrant[];
rings: RadarRing[];
entries: RadarEntry[];
export interface TechRadarApi extends TechRadarComponentProps {
title?: string;
subtitle?: string;
}
export interface TechRadarApi {
width: number;
height: number;
load: () => Promise<TechRadarLoaderResponse>;
additionalOpts: TechRadarAdditionalOptions;
}
// Constructor signature for the `TechRadar` class
// constructor(
// public width: number,
// public height: number,
// public load: () => Promise<TechRadarLoaderResponse>,
// public additionalOpts: TechRadarAdditionalOptions = {},
// )
```
The source code is available in [api.ts](src/api.ts).
You can see the API directly over at [src/api.ts](./src/api.ts).
## Code Samples
### Advanced Configuration
### Set up Tech Radar with sample data
This way won't expose an `/tech-radar` path. Instead, you'll need to create your own Backstage plugin and use the Tech Radar as any other React UI component.
See example above.
In your Backstage app, run the following command:
### Set up Tech Radar with hard-coded values
```sh
yarn create-plugin
```
In your plugin, in any React component you'd like to import the Tech Radar, do the following:
```tsx
import { TechRadarComponent } from '@backstage/plugin-tech-radar';
function MyCustomRadar() {
return <TechRadarComponent width={1400} height={800} />;
}
```
If you'd like to configure it more, see the `TechRadarComponentProps` TypeScript interface for options:
```ts
const hardCodedData = () =>
export interface TechRadarComponentProps {
width: number;
height: number;
getData?: () => Promise<TechRadarLoaderResponse>;
svgProps?: object;
}
```
You can see the API directly over at [src/api.ts](./src/api.ts).
## Frequently Asked Questions
### Who created the Tech Radar?
[ThoughtWorks](https://thoughtworks.com/radar) created the Tech Radar concept, and [Zalando created the visualization](https://opensource.zalando.com/tech-radar/) that we use at Spotify and in this plugin.
### How do I load in my own data?
It's simple. In both the Simple (Backstage plugin) and Advanced (React component) configurations, you can pass through a `getData` prop which expects a `Promise<TechRadarLoaderResponse>` signature. See more in [src/api.ts](./src/api.ts).
Here's an example:
```tsx
const getHardCodedData = () =>
Promise.resolve({
quadrants: [{ id: 'infrastructure', name: 'Infrastructure' }],
rings: [{ id: 'use', name: 'USE', color: '#93c47d' }],
@@ -100,48 +131,43 @@ const hardCodedData = () =>
],
});
builder.add(techRadarApiRef, new TechRadar(1400, 800, hardCodedData));
// Simple
builder.add(techRadarApiRef, new TechRadar({
width: 1400,
height: 800,
getData: getHardCodedData
));
// Advanced
<TechRadarComponent width={1400} height={800} getData={getHardCodedData} />
```
### Set up Tech Radar with an API call
```ts
const apiRetrievedData = async () => {
const response = await fetch('http://example.com/tech-radar-values.json');
const json = await response.json();
return json as TechRadarLoaderResponse;
};
builder.add(techRadarApiRef, new TechRadar(1400, 800, apiRetrievedData));
```
### Use a custom title and subtitle
### How do I write tests?
You can use the `svgProps` option to pass custom React props to the `<svg>` element we create for the Tech Radar. This complements well with the `data-testid` attribute and the `@testing-library/react` library we use in Backstage.
```ts
// Simple
builder.add(
techRadarApiRef,
new TechRadar(1400, 800, loadSampleData, {
title: 'My Company Tech Radar',
subtitle: 'Learn about what technologies we use at My Company.',
}),
);
```
### Use custom props
Great for testing through adding a `data-testid` for being able to test with `@testing-library/react`
```ts
builder.add(
techRadarApiRef,
new TechRadar(1400, 800, loadSampleData, {
new TechRadar({
width: 1400,
height: 800,
svgProps: {
// for the main <svg> tag of the visualization
'data-testid': 'tech-radar-svg',
},
}),
);
// Advanced
<TechRadarComponent
width={1400}
height={800}
svgProps={{
'data-testid': 'tech-radar-svg',
}}
/>;
// Then, in your tests...
// const { getByTestId } = render(...);
// expect(getByTestId('tech-radar-svg')).toBeInTheDocument();
+12 -12
View File
@@ -11,18 +11,6 @@
"test": "backstage-cli test",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/color": "^3.0.1",
"@types/d3-force": "^1.2.1",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.2",
"jest-fetch-mock": "^3.0.3"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.4",
"@backstage/test-utils-core": "^0.1.1-alpha.4",
@@ -36,5 +24,17 @@
"react": "16.13.1",
"react-dom": "16.13.1",
"react-use": "^13.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/color": "^3.0.1",
"@types/d3-force": "^1.2.1",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.4",
"jest-fetch-mock": "^3.0.3"
}
}
+45 -20
View File
@@ -16,6 +16,10 @@
import { ApiRef } from '@backstage/core';
/**
* Types related to the Radar's visualization.
*/
export interface RadarRing {
id: string;
name: string;
@@ -37,42 +41,63 @@ export interface RadarEntry {
url: string;
}
/**
* Types related to data collection for the Radar.
*/
export interface TechRadarLoaderResponse {
quadrants: RadarQuadrant[];
rings: RadarRing[];
entries: RadarEntry[];
}
export interface TechRadarAdditionalOptions {
title?: string;
subtitle?: string;
/**
* Set up the Radar as a Backstage component.
*/
export interface TechRadarComponentProps {
width: number;
height: number;
getData?: () => Promise<TechRadarLoaderResponse>;
svgProps?: object;
}
export interface TechRadarApi {
width: number;
height: number;
load: () => Promise<TechRadarLoaderResponse>;
additionalOpts: TechRadarAdditionalOptions;
/**
* Set up the Radar as a Backstage plugin.
*/
export interface TechRadarApi extends TechRadarComponentProps {
title?: string;
subtitle?: string;
}
export const techRadarApiRef = new ApiRef<TechRadarApi>({
id: 'plugin.techradar',
description: 'Used by the Tech Radar to render the diagram',
description: 'Used by the Tech Radar to render the visualization',
});
export class TechRadar implements TechRadarApi {
private defaultAdditionalOpts: Partial<TechRadarAdditionalOptions> = {
title: 'Tech Radar',
subtitle: 'Welcome to the Tech Radar!',
};
// Default columns
public width: TechRadarApi['width'];
public height: TechRadarApi['height'];
public getData: TechRadarApi['getData'];
public svgProps: TechRadarApi['svgProps'];
public title: TechRadarApi['title'];
public subtitle: TechRadarApi['subtitle'];
constructor(
public width: number,
public height: number,
public load: () => Promise<TechRadarLoaderResponse>,
public additionalOpts: TechRadarAdditionalOptions = {},
) {
this.additionalOpts = { ...this.defaultAdditionalOpts, ...additionalOpts };
constructor(overrideOptions: TechRadarApi) {
const defaultOptions: Partial<TechRadarApi> = {
title: 'Tech Radar',
subtitle: 'Welcome to the Tech Radar!',
};
const options = { ...defaultOptions, ...overrideOptions };
this.width = options.width;
this.height = options.height;
this.getData = options.getData;
this.svgProps = options.svgProps;
this.title = options.title;
this.subtitle = options.subtitle;
}
}
@@ -105,14 +105,14 @@ export default class Radar extends React.Component {
static adjustEntries(entries, activeEntry, quadrants, rings, radius) {
let seed = 42;
entries.forEach((entry, idx) => {
const quadrant = quadrants.find(q => {
const quadrant = quadrants.find((q) => {
const match =
typeof entry.quadrant === 'object'
? entry.quadrant.id
: entry.quadrant;
return q.id === match;
});
const ring = rings.find(r => {
const ring = rings.find((r) => {
const match =
typeof entry.ring === 'object' ? entry.ring.id : entry.ring;
return r.id === match;
@@ -137,21 +137,13 @@ export default class Radar extends React.Component {
entry.active = activeEntry ? entry.id === activeEntry.id : false;
entry.color = entry.active
? entry.ring.color
: color(entry.ring.color)
.desaturate(0.5)
.lighten(0.1)
.string();
: color(entry.ring.color).desaturate(0.5).lighten(0.1).string();
});
const simulation = forceSimulation()
.nodes(entries)
.velocityDecay(0.19)
.force(
'collision',
forceCollide()
.radius(12)
.strength(0.85),
)
.force('collision', forceCollide().radius(12).strength(0.85))
.stop();
for (
@@ -198,7 +190,7 @@ export default class Radar extends React.Component {
return (
<svg
ref={node => {
ref={(node) => {
this.node = node;
}}
width={width}
@@ -213,7 +205,7 @@ export default class Radar extends React.Component {
quadrants={quadrants}
rings={rings}
activeEntry={activeEntry}
onEntryMouseEnter={entry => this._setActiveEntry(entry)}
onEntryMouseEnter={(entry) => this._setActiveEntry(entry)}
onEntryMouseLeave={() => this._clearActiveEntry()}
/>
</svg>

Some files were not shown because too many files have changed in this diff Show More