Merge branch 'master' into remove_plugin_wip
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: true,
|
||||
optionalDependencies: true,
|
||||
peerDependencies: true,
|
||||
bundledDependencies: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const os = require('os');
|
||||
const fs = require('fs-extra');
|
||||
const { resolve: resolvePath } = require('path');
|
||||
const Browser = require('zombie');
|
||||
|
||||
const {
|
||||
spawnPiped,
|
||||
handleError,
|
||||
waitForPageWithText,
|
||||
waitForExit,
|
||||
print,
|
||||
} = require('./helpers');
|
||||
|
||||
const createTestApp = require('./createTestApp');
|
||||
const createTestPlugin = require('./createTestPlugin');
|
||||
|
||||
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 workDir = process.env.CI ? process.cwd() : await createTempDir();
|
||||
|
||||
process.stdout.write(`Initial directory: ${process.cwd()}\n`);
|
||||
process.chdir(workDir);
|
||||
process.stdout.write(`Working directory: ${process.cwd()}\n`);
|
||||
|
||||
await createTestApp();
|
||||
|
||||
const appDir = resolvePath(workDir, 'test-app');
|
||||
process.chdir(appDir);
|
||||
process.stdout.write(`App directory: ${appDir}\n`);
|
||||
|
||||
await createTestPlugin();
|
||||
|
||||
print('Starting the app');
|
||||
const startApp = spawnPiped(['yarn', 'start']);
|
||||
|
||||
try {
|
||||
const browser = new Browser();
|
||||
|
||||
await waitForPageWithText(browser, '/', 'Welcome to Backstage');
|
||||
await waitForPageWithText(
|
||||
browser,
|
||||
'/test-plugin',
|
||||
'Welcome to test-plugin!',
|
||||
);
|
||||
|
||||
print('Both App and Plugin loaded correctly');
|
||||
} finally {
|
||||
startApp.kill();
|
||||
}
|
||||
|
||||
await waitForExit(startApp);
|
||||
|
||||
print('All tests done');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', handleError);
|
||||
main(process.argv.slice(2)).catch(handleError);
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { resolve: resolvePath } = require('path');
|
||||
const { spawnPiped, waitFor, waitForExit, print } = require('./helpers');
|
||||
|
||||
async function createTestApp() {
|
||||
const cliPath = resolvePath(__dirname, '../bin/backstage-cli');
|
||||
|
||||
print('Creating a Backstage App');
|
||||
const createApp = spawnPiped(['node', cliPath, 'create-app']);
|
||||
|
||||
try {
|
||||
let stdout = '';
|
||||
createApp.stdout.on('data', data => {
|
||||
stdout = stdout + data.toString('utf8');
|
||||
});
|
||||
|
||||
await waitFor(() => stdout.includes('Enter a name for the app'));
|
||||
createApp.stdin.write('test-app\n');
|
||||
|
||||
print('Waiting for app create script to be done');
|
||||
await waitForExit(createApp);
|
||||
|
||||
print('Test app created');
|
||||
} finally {
|
||||
createApp.kill();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = createTestApp;
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { spawnPiped, waitFor, waitForExit, print } = require('./helpers');
|
||||
|
||||
async function createTestPlugin() {
|
||||
print('Creating a Backstage Plugin');
|
||||
const createPlugin = spawnPiped(['yarn', 'create-plugin']);
|
||||
|
||||
try {
|
||||
let stdout = '';
|
||||
createPlugin.stdout.on('data', data => {
|
||||
stdout = stdout + data.toString('utf8');
|
||||
});
|
||||
|
||||
await waitFor(() => stdout.includes('Enter an ID for the plugin'));
|
||||
createPlugin.stdin.write('test-plugin\n');
|
||||
|
||||
// await waitFor(() => stdout.includes('Enter the owner(s) of the plugin'));
|
||||
// createPlugin.stdin.write('@someuser\n');
|
||||
|
||||
print('Waiting for plugin create script to be done');
|
||||
await waitForExit(createPlugin);
|
||||
|
||||
print('Test plugin created');
|
||||
} finally {
|
||||
createPlugin.kill();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = createTestPlugin;
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const childProcess = require('child_process');
|
||||
const { spawn } = childProcess;
|
||||
|
||||
const EXPECTED_LOAD_ERRORS = /ECONNREFUSED|ECONNRESET|did not get to load all resources/;
|
||||
|
||||
function spawnPiped(cmd, options) {
|
||||
function pipeWithPrefix(stream, prefix = '') {
|
||||
return data => {
|
||||
const prefixedMsg = data
|
||||
.toString('utf8')
|
||||
.trimRight()
|
||||
.replace(/^/gm, prefix);
|
||||
stream.write(`${prefixedMsg}\n`, 'utf8');
|
||||
};
|
||||
}
|
||||
|
||||
const child = spawn(cmd[0], cmd.slice(1), {
|
||||
stdio: 'pipe',
|
||||
shell: true,
|
||||
...options,
|
||||
});
|
||||
child.on('error', handleError);
|
||||
child.on('exit', code => {
|
||||
if (code) {
|
||||
print(`Child '${cmd.join(' ')}' exited with code ${code}`);
|
||||
process.exit(code);
|
||||
}
|
||||
});
|
||||
child.stdout.on(
|
||||
'data',
|
||||
pipeWithPrefix(process.stdout, `[${cmd.join(' ')}].out: `),
|
||||
);
|
||||
child.stderr.on(
|
||||
'data',
|
||||
pipeWithPrefix(process.stderr, `[${cmd.join(' ')}].err: `),
|
||||
);
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
function handleError(err) {
|
||||
process.stdout.write(`${err.name}: ${err.stack || err.message}\n`);
|
||||
if (typeof err.code === 'number') {
|
||||
process.exit(err.code);
|
||||
} else {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function waitFor(fn) {
|
||||
return new Promise(resolve => {
|
||||
const handle = setInterval(() => {
|
||||
if (fn()) {
|
||||
clearInterval(handle);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForExit(child) {
|
||||
if (child.exitCode !== null) {
|
||||
throw new Error(`Child already exited with code ${child.exitCode}`);
|
||||
}
|
||||
await new Promise((resolve, reject) =>
|
||||
child.once('exit', code => {
|
||||
if (code) {
|
||||
reject(new Error(`Child exited with code ${code}`));
|
||||
} else {
|
||||
print('Child finished');
|
||||
resolve();
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async function waitForPageWithText(
|
||||
browser,
|
||||
path,
|
||||
text,
|
||||
{ intervalMs = 1000, maxAttempts = 240 } = {},
|
||||
) {
|
||||
let attempts = 0;
|
||||
for (;;) {
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
||||
await browser.visit(path);
|
||||
break;
|
||||
} catch (error) {
|
||||
if (error.message.match(EXPECTED_LOAD_ERRORS)) {
|
||||
attempts++;
|
||||
if (attempts > maxAttempts) {
|
||||
throw new Error(
|
||||
`Failed to load page '${path}', max number of attempts reached`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const escapedText = text.replace(/"/g, '\\"');
|
||||
browser.assert.evaluate(
|
||||
`Array.from(document.querySelectorAll("*")).some(el => el.textContent === "${escapedText}")`,
|
||||
true,
|
||||
`expected to find text ${text}`,
|
||||
);
|
||||
}
|
||||
|
||||
function print(msg) {
|
||||
return process.stdout.write(`${msg}\n`);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
spawnPiped,
|
||||
handleError,
|
||||
waitFor,
|
||||
waitForExit,
|
||||
waitForPageWithText,
|
||||
print,
|
||||
};
|
||||
@@ -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');
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable jest/no-disabled-tests */
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInThemedTestApp } from '@backstage/test-utils';
|
||||
|
||||
import TrendLine from '.';
|
||||
|
||||
describe('TrendLine', () => {
|
||||
describe('when no data is present', () => {
|
||||
it('renders null without throwing', () => {
|
||||
const rendered = render(
|
||||
wrapInThemedTestApp(<TrendLine data={[]} title="sparkline" />),
|
||||
);
|
||||
expect(rendered.queryByTitle('sparkline')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when one datapoint is present', () => {
|
||||
it('renders as a straight line', () => {
|
||||
const rendered = render(
|
||||
wrapInThemedTestApp(<TrendLine data={[0.5]} title="sparkline" />),
|
||||
);
|
||||
expect(rendered.getByTitle('sparkline')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('when the data finishes above the success threshold', () => {
|
||||
it('renders with the correct color', () => {
|
||||
const rendered = render(
|
||||
wrapInThemedTestApp(<TrendLine data={[0.5, 0.95]} title="sparkline" />),
|
||||
);
|
||||
expect(rendered.getByTitle('sparkline')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('when the data finishes within the the warning threshold', () => {
|
||||
it('renders with the correct color', () => {
|
||||
const rendered = render(
|
||||
wrapInThemedTestApp(<TrendLine data={[0.5, 0.65]} title="sparkline" />),
|
||||
);
|
||||
expect(rendered.getByTitle('sparkline')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('when the data finishes within the the error threshold', () => {
|
||||
it('renders with the correct color', () => {
|
||||
const rendered = render(
|
||||
wrapInThemedTestApp(<TrendLine data={[0.5, 0.4]} title="sparkline" />),
|
||||
);
|
||||
expect(rendered.getByTitle('sparkline')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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, { FC } from 'react';
|
||||
import { Sparklines, SparklinesLine, SparklinesProps } from 'react-sparklines';
|
||||
import { useTheme } from '@material-ui/core';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
|
||||
function color(data: number[], theme: BackstageTheme): string | undefined {
|
||||
const lastNum = data[data.length - 1];
|
||||
if (!lastNum) return undefined;
|
||||
if (lastNum >= 0.9) return theme.palette.status.ok;
|
||||
if (lastNum >= 0.5) return theme.palette.status.warning;
|
||||
return theme.palette.status.error;
|
||||
}
|
||||
|
||||
const Trendline: FC<SparklinesProps & { title?: string }> = props => {
|
||||
const theme = useTheme<BackstageTheme>();
|
||||
|
||||
if (!props.data) return null;
|
||||
return (
|
||||
<Sparklines width={120} height={30} min={0} max={1} {...props}>
|
||||
{props.title && <title>{props.title}</title>}
|
||||
<SparklinesLine color={color(props.data, theme)} />
|
||||
</Sparklines>
|
||||
);
|
||||
};
|
||||
|
||||
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';
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user