Merge branch 'master' into remove_plugin_wip

This commit is contained in:
Patrik Oldsberg
2020-04-17 10:25:28 +02:00
committed by GitHub
21 changed files with 162 additions and 94 deletions
+12 -16
View File
@@ -41,31 +41,27 @@ jobs:
- name: yarn install
run: yarn install --frozen-lockfile
- run: yarn build
# generate temp directory
- name: generate tempdir
id: generate_tempdir
run: echo "::set-output name=tempdir::$(node scripts/generateTempDir.js)"
# This creates a new app and plugin which pollutes the workspace, so it should be run last.
- name: verify app and plugin creation on Windows
working-directory: ${{ steps.generate_tempdir.outputs.tempdir }}
working-directory: ${{ runner.temp }}
if: runner.os == 'Windows'
run: node ${{ github.workspace }}/scripts/cli-e2e-test.js
run: node ${{ github.workspace }}/packages/cli/e2e-test/cli-e2e-test.js
env:
BACKSTAGE_E2E_CLI_TEST: true
- name: verify app and plugin creation on Linux
working-directory: ${{ steps.generate_tempdir.outputs.tempdir }}
working-directory: ${{ runner.temp }}
if: runner.os == 'Linux'
run: |
sudo sysctl fs.inotify.max_user_watches=524288
node ${{ github.workspace }}/scripts/cli-e2e-test.js
node ${{ github.workspace }}/packages/cli/e2e-test/cli-e2e-test.js
env:
BACKSTAGE_E2E_CLI_TEST: true
# This should lint and test both an app and a plugin
- name: yarn lint, test after creation
working-directory: ${{ steps.generate_tempdir.outputs.tempdir }}
run: |
cd test-app
yarn lint:all
yarn test:all
- name: lint newly created app and plugin
run: yarn lint:all
working-directory: ${{ runner.temp }}/test-app
env:
BACKSTAGE_E2E_CLI_TEST: true
- name: test newly created app and plugin
run: yarn test:all
working-directory: ${{ runner.temp }}/test-app
env:
BACKSTAGE_E2E_CLI_TEST: true
+1 -2
View File
@@ -34,8 +34,7 @@
"lerna": "^3.20.2",
"lint-staged": "^10.1.0",
"prettier": "^1.19.1",
"typescript": "^3.7.5",
"zombie": "^6.1.4"
"typescript": "^3.7.5"
},
"dependencies": {
"@types/classnames": "^2.2.9",
@@ -14,17 +14,16 @@
* limitations under the License.
*/
const { handleError } = require('./helpers');
async function generateTempDir() {
const tempDir = await require('fs-extra').mkdtemp(
require('path').join(require('os').tmpdir(), 'backstage-e2e-'),
);
process.stdout.write(tempDir);
return tempDir;
}
module.exports = generateTempDir;
process.on('unhandledRejection', handleError);
generateTempDir().catch(handleError);
module.exports = {
rules: {
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
optionalDependencies: true,
peerDependencies: true,
bundledDependencies: true,
},
],
},
};
@@ -14,6 +14,8 @@
* limitations under the License.
*/
const os = require('os');
const fs = require('fs-extra');
const { resolve: resolvePath } = require('path');
const Browser = require('zombie');
@@ -27,35 +29,25 @@ const {
const createTestApp = require('./createTestApp');
const createTestPlugin = require('./createTestPlugin');
const generateTempDir = require('./generateTempDir.js');
Browser.localhost('localhost', 3000);
async function createTempDir() {
return fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-e2e-'));
}
async function main() {
process.env.BACKSTAGE_E2E_CLI_TEST = 'true';
const rootDir = process.env.CI
? resolvePath(process.env.GITHUB_WORKSPACE)
: resolvePath(__dirname, '..');
const tempDir = process.env.CI ? process.cwd() : await generateTempDir();
const workDir = process.env.CI ? process.cwd() : await createTempDir();
process.stdout.write(`Initial directory: ${process.cwd()}\n`);
process.chdir(tempDir);
process.stdout.write(`Temp directory: ${process.cwd()}\n`);
process.chdir(workDir);
process.stdout.write(`Working directory: ${process.cwd()}\n`);
await waitForExit(spawnPiped(['yarn', 'init --yes']));
await createTestApp();
const createCmdPath = require('path').join(
rootDir,
'packages',
'cli',
'bin',
'backstage-cli',
);
await createTestApp(`${createCmdPath} create-app`);
const appDir = resolvePath(tempDir, 'test-app');
const appDir = resolvePath(workDir, 'test-app');
process.chdir(appDir);
process.stdout.write(`App directory: ${appDir}\n`);
@@ -14,11 +14,14 @@
* limitations under the License.
*/
const { resolve: resolvePath } = require('path');
const { spawnPiped, waitFor, waitForExit, print } = require('./helpers');
async function createTestApp(cmd) {
async function createTestApp() {
const cliPath = resolvePath(__dirname, '../bin/backstage-cli');
print('Creating a Backstage App');
const createApp = spawnPiped(['node', cmd]);
const createApp = spawnPiped(['node', cliPath, 'create-app']);
try {
let stdout = '';
+3 -1
View File
@@ -22,6 +22,7 @@
"build": "backstage-cli build-cache -- tsc",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"test:e2e": "node e2e-test/cli-e2e-test.js",
"clean": "backstage-cli clean",
"start": "nodemon ."
},
@@ -41,7 +42,8 @@
"del": "^5.1.0",
"nodemon": "^2.0.2",
"ts-node": "^8.6.2",
"tsconfig-paths": "^3.9.0"
"tsconfig-paths": "^3.9.0",
"zombie": "^6.1.4"
},
"bin": {
"backstage-cli": "bin/backstage-cli"
@@ -30,7 +30,7 @@ export async function withCache(
buildFunc: () => Promise<void>,
): Promise<void> {
const key = await Cache.readInputKey(options.inputs);
if (!key || process.env.BACKSTAGE_E2E_CLI_TEST) {
if (!key) {
print('input directory is dirty, skipping cache');
await fs.remove(options.output);
await buildFunc();
@@ -86,26 +86,22 @@ export async function moveApp(
});
}
async function addPackageResolutions(rootDir: string, appDir: string) {
process.chdir(appDir);
const packageFileContent = await fs.readFile('package.json', 'utf-8');
async function addPackageResolutions(appDir: string) {
const pkgJsonPath = resolvePath(appDir, 'package.json');
const packageFileContent = await fs.readFile(pkgJsonPath, 'utf-8');
const packageFileJson = JSON.parse(packageFileContent);
if (packageFileJson.resolutions) {
throw new Error('package.json already contains resolutions');
}
packageFileJson.resolutions = {};
packageFileJson.resolutions = packageFileJson.resolutions || {};
const packages = ['cli', 'core', 'test-utils', 'test-utils-core', 'theme'];
for (const pkg of packages) {
await Task.forItem('adding', `${pkg} link to package.json`, async () => {
const pkgPath = require('path').join(rootDir, 'packages', pkg);
const pkgPath = paths.resolveOwnRoot('packages', pkg);
packageFileJson.resolutions[`@backstage/${pkg}`] = `file:${pkgPath}`;
const newContents = `${JSON.stringify(packageFileJson, null, 2)}\n`;
await fs.writeFile('package.json', 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}`,
);
@@ -157,10 +153,7 @@ export default async () => {
// e2e testing needs special treatment
if (process.env.BACKSTAGE_E2E_CLI_TEST) {
Task.section('Linking packages locally for e2e tests');
const rootDir = process.env.CI
? resolvePath(process.env.GITHUB_WORKSPACE!)
: resolvePath(__dirname, '..', '..', '..');
await addPackageResolutions(rootDir, appDir);
await addPackageResolutions(appDir);
}
Task.section('Building the app');
+31
View File
@@ -25,6 +25,9 @@ export type Paths = {
// Root dir of the cli itself, containing package.json
ownDir: string;
// Monorepo root dir of the cli itself. Only accessible when running inside Backstage repo.
ownRoot: string;
// The location of the app that the cli is being executed in
targetDir: string;
@@ -34,6 +37,9 @@ export type Paths = {
// Resolve a path relative to own repo
resolveOwn: ResolveFunc;
// Resolve a path relative to own monorepo root. Only accessible when running inside Backstage repo.
resolveOwnRoot: ResolveFunc;
// Resolve a path relative to the app
resolveTarget: ResolveFunc;
@@ -91,10 +97,31 @@ export function findOwnDir() {
return resolvePath(__dirname, path);
}
// Finds the root of the monorepo that the cli exists in. Only accessible when running inside Backstage repo.
export function findOwnRootPath(ownDir: string) {
const isLocal = fs.pathExistsSync(resolvePath(ownDir, 'src'));
if (!isLocal) {
throw new Error(
'Tried to access monorepo package root dir outside of Backstage repository',
);
}
return resolvePath(ownDir, '../..');
}
export function findPaths(): Paths {
const ownDir = findOwnDir();
const targetDir = fs.realpathSync(process.cwd());
// Lazy load this as it will throw an error if we're not inside the Backstage repo.
let ownRoot = '';
const getOwnRoot = () => {
if (!ownRoot) {
ownRoot = findOwnRootPath(ownDir);
}
return ownRoot;
};
// We're not always running in a monorepo, so we lazy init this to only crash commands
// that require a monorepo when we're not in one.
let targetRoot = '';
@@ -107,11 +134,15 @@ export function findPaths(): Paths {
return {
ownDir,
get ownRoot() {
return getOwnRoot();
},
targetDir,
get targetRoot() {
return getTargetRoot();
},
resolveOwn: (...paths) => resolvePath(ownDir, ...paths),
resolveOwnRoot: (...paths) => resolvePath(getOwnRoot(), ...paths),
resolveTarget: (...paths) => resolvePath(targetDir, ...paths),
resolveTargetRoot: (...paths) => resolvePath(getTargetRoot(), ...paths),
};
+2
View File
@@ -39,6 +39,7 @@
"react-dom": "^16.12.0",
"react-helmet": "5.2.1",
"react-router-dom": "^5.1.2",
"react-sparklines": "^1.7.0",
"recompose": "0.30.0"
},
"devDependencies": {
@@ -49,6 +50,7 @@
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/react-sparklines": "^1.7.0",
"react-router": "^5.1.2"
},
"peerDependencies": {
@@ -17,7 +17,7 @@ import React from 'react';
import { AlphaLabel } from './Lifecycle';
export default {
title: 'Alpha Lifecycle',
title: 'Lifecycle - Alpha',
component: AlphaLabel,
};
@@ -17,7 +17,7 @@ import React from 'react';
import { BetaLabel } from './Lifecycle';
export default {
title: 'Beta Lifecycle',
title: 'Lifecycle - Beta',
component: BetaLabel,
};
@@ -0,0 +1,43 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import TrendLine from '.';
export default {
title: 'TrendLine',
component: TrendLine,
};
const width = 140;
export const Default = () => (
<div style={{ width }}>
<TrendLine data={[0.1, 0.7, 0.5, 0.8]} title="Trend over time" />
</div>
);
export const TrendingUp = () => (
<div style={{ width }}>
<TrendLine data={[0.1, 0.5, 0.9, 1.0]} title="Trend over time" />
</div>
);
export const TrendingDown = () => (
<div style={{ width }}>
<TrendLine data={[0.8, 0.7, 0.5, 0.1]} title="Trend over time" />
</div>
);
@@ -15,18 +15,17 @@
*/
/* eslint-disable jest/no-disabled-tests */
import React from 'react';
import { render } from '@testing-library/react';
import { wrapInThemedTestApp } from '@backstage/test-utils';
import CategoryTrendline from '.';
import TrendLine from '.';
describe('CategoryTrendline', () => {
describe('TrendLine', () => {
describe('when no data is present', () => {
it('renders null without throwing', () => {
const rendered = render(
wrapInThemedTestApp(<CategoryTrendline data={[]} title="sparkline" />),
wrapInThemedTestApp(<TrendLine data={[]} title="sparkline" />),
);
expect(rendered.queryByTitle('sparkline')).not.toBeInTheDocument();
});
@@ -35,9 +34,7 @@ describe('CategoryTrendline', () => {
describe('when one datapoint is present', () => {
it('renders as a straight line', () => {
const rendered = render(
wrapInThemedTestApp(
<CategoryTrendline data={[0.5]} title="sparkline" />,
),
wrapInThemedTestApp(<TrendLine data={[0.5]} title="sparkline" />),
);
expect(rendered.getByTitle('sparkline')).toBeInTheDocument();
});
@@ -46,9 +43,7 @@ describe('CategoryTrendline', () => {
describe.skip('when the data finishes above the success threshold', () => {
it('renders with the correct color', () => {
const rendered = render(
wrapInThemedTestApp(
<CategoryTrendline data={[0.5, 0.95]} title="sparkline" />,
),
wrapInThemedTestApp(<TrendLine data={[0.5, 0.95]} title="sparkline" />),
);
expect(rendered.getByTitle('sparkline')).toBeInTheDocument();
});
@@ -57,9 +52,7 @@ describe('CategoryTrendline', () => {
describe.skip('when the data finishes within the the warning threshold', () => {
it('renders with the correct color', () => {
const rendered = render(
wrapInThemedTestApp(
<CategoryTrendline data={[0.5, 0.65]} title="sparkline" />,
),
wrapInThemedTestApp(<TrendLine data={[0.5, 0.65]} title="sparkline" />),
);
expect(rendered.getByTitle('sparkline')).toBeInTheDocument();
});
@@ -68,9 +61,7 @@ describe('CategoryTrendline', () => {
describe.skip('when the data finishes within the the error threshold', () => {
it('renders with the correct color', () => {
const rendered = render(
wrapInThemedTestApp(
<CategoryTrendline data={[0.5, 0.4]} title="sparkline" />,
),
wrapInThemedTestApp(<TrendLine data={[0.5, 0.4]} title="sparkline" />),
);
expect(rendered.getByTitle('sparkline')).toBeInTheDocument();
});
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { FC } from 'react';
import { Sparklines, SparklinesLine, SparklinesProps } from 'react-sparklines';
import { useTheme } from '@material-ui/core';
@@ -26,7 +27,7 @@ function color(data: number[], theme: BackstageTheme): string | undefined {
return theme.palette.status.error;
}
const CategoryTrendline: FC<SparklinesProps & { title?: string }> = props => {
const Trendline: FC<SparklinesProps & { title?: string }> = props => {
const theme = useTheme<BackstageTheme>();
if (!props.data) return null;
@@ -38,4 +39,4 @@ const CategoryTrendline: FC<SparklinesProps & { title?: string }> = props => {
);
};
export default CategoryTrendline;
export default Trendline;
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { default } from './TrendLine';
+1
View File
@@ -31,6 +31,7 @@ export { default as Progress } from './components/Progress';
export { AlphaLabel, BetaLabel } from './components/Lifecycle';
export { default as SupportButton } from './components/SupportButton';
export { default as SortableTable } from './components/SortableTable';
export { default as TrendLine } from './components/TrendLine';
export { FeatureCalloutCircular } from './components/FeatureDiscovery/FeatureCalloutCircular';
export * from './components/Status';
export { default as WarningPanel } from './components/WarningPanel';
+1 -3
View File
@@ -13,8 +13,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"react-markdown": "^4.3.1",
"react-sparklines": "^1.7.0"
"react-markdown": "^4.3.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
@@ -29,7 +28,6 @@
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/react-sparklines": "^1.7.0",
"@types/testing-library__jest-dom": "5.0.2",
"jest-fetch-mock": "^3.0.3",
"react": "^16.13.1",
@@ -24,6 +24,7 @@ import {
TableRow,
} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import { TrendLine } from '@backstage/core';
import {
Audit,
@@ -32,7 +33,6 @@ import {
Website,
} from '../../api';
import { formatTime } from '../../utils';
import CategoryTrendline from '../CategoryTrendline';
import AuditStatusIcon from '../AuditStatusIcon';
export const CATEGORIES: LighthouseCategoryId[] = [
@@ -132,7 +132,7 @@ export const AuditListTable: FC<{ items: Website[] }> = ({ items }) => {
key={`${website.url}|${category}`}
className={classes.sparklinesCell}
>
<CategoryTrendline
<TrendLine
title={`trendline for ${CATEGORY_LABELS[category]} category of ${website.url}`}
data={categorySparklines[website.url][category] || []}
/>