Toolkit: moved front end cli scripts to separate package and introduced very early version of plugin tools

* Move cli to grafana-toolkit

* Moving packages, fixing ts

* Add basics of plugin build task

* Add toolkit build task

* Circle - use node 10 for test-frontend

* Prettier fix

* First attempt for having shared tsconfig for plugins

* Add enzyme as peer depencency

* Do not expose internal commands when using toolkit from npm package

* Introduce plugin linting

* Fix missing file

* Fix shim extenstion

* Remove rollup typings

* Add tslint as dependency

* Toolkit - use the same versions of enzyme and tslint as core does

* Remove include property from plugin tsconfig

* Take failed suites into consideration when tests failed

* Set ts-jest preset for jest

* Cleanup tsconfig.plugins

* Add plugin:test task

* Rename file causing build failute

* Fixing those missed renames

* Add ts as peer dependency

* Remove enzyme dependency and tweak test plugin task

* Allow jest options overrides via package.json config

* Improvements

* Remove rollup node packages

* TMP : Fix ts errors when linked

* use local tslint if it exists

* support coverage commands

* Fix merge

* fix build

* Some minors

* Make jest pass when no tests discovered
This commit is contained in:
Dominik Prokop
2019-06-28 14:11:12 +02:00
committed by GitHub
parent ead4b1f5c7
commit 742e0d56eb
40 changed files with 1666 additions and 386 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
import fs from 'fs';
import * as fs from 'fs';
import darkTheme from '@grafana/ui/src/themes/dark';
import lightTheme from '@grafana/ui/src/themes/light';
import defaultTheme from '@grafana/ui/src/themes/default';
-1
View File
@@ -1 +0,0 @@
export type Task<T> = (options: T) => Promise<void>;
-108
View File
@@ -1,108 +0,0 @@
import program from 'commander';
import { execTask } from './utils/execTask';
import chalk from 'chalk';
import { startTask } from './tasks/core.start';
import { buildTask } from './tasks/grafanaui.build';
import { releaseTask } from './tasks/grafanaui.release';
import { changelogTask } from './tasks/changelog';
import { cherryPickTask } from './tasks/cherrypick';
import { closeMilestoneTask } from './tasks/closeMilestone';
import { precommitTask } from './tasks/precommit';
import { searchTestDataSetupTask } from './tasks/searchTestDataSetup';
program.option('-d, --depreciate <scripts>', 'Inform about npm script deprecation', v => v.split(','));
program
.command('core:start')
.option('-h, --hot', 'Run front-end with HRM enabled')
.option('-t, --watchTheme', 'Watch for theme changes and regenerate variables.scss files')
.description('Starts Grafana front-end in development mode with watch enabled')
.action(async cmd => {
await execTask(startTask)({
watchThemes: cmd.watchTheme,
hot: cmd.hot,
});
});
program
.command('gui:build')
.description('Builds @grafana/ui package to packages/grafana-ui/dist')
.action(async cmd => {
await execTask(buildTask)();
});
program
.command('gui:release')
.description('Prepares @grafana/ui release (and publishes to npm on demand)')
.option('-p, --publish', 'Publish @grafana/ui to npm registry')
.option('-u, --usePackageJsonVersion', 'Use version specified in package.json')
.option('--createVersionCommit', 'Create and push version commit')
.action(async cmd => {
await execTask(releaseTask)({
publishToNpm: !!cmd.publish,
usePackageJsonVersion: !!cmd.usePackageJsonVersion,
createVersionCommit: !!cmd.createVersionCommit,
});
});
program
.command('changelog')
.option('-m, --milestone <milestone>', 'Specify milestone')
.description('Builds changelog markdown')
.action(async cmd => {
if (!cmd.milestone) {
console.log('Please specify milestone, example: -m <milestone id from github milestone URL>');
return;
}
await execTask(changelogTask)({
milestone: cmd.milestone,
});
});
program
.command('cherrypick')
.description('Helps find commits to cherry pick')
.action(async cmd => {
await execTask(cherryPickTask)({});
});
program
.command('close-milestone')
.option('-m, --milestone <milestone>', 'Specify milestone')
.description('Helps ends a milestone by removing the cherry-pick label and closing it')
.action(async cmd => {
if (!cmd.milestone) {
console.log('Please specify milestone, example: -m <milestone id from github milestone URL>');
return;
}
await execTask(closeMilestoneTask)({
milestone: cmd.milestone,
});
});
program
.command('precommit')
.description('Executes checks')
.action(async cmd => {
await execTask(precommitTask)({});
});
program
.command('searchTestData')
.option('-c, --count <number_of_dashboards>', 'Specify number of dashboards')
.description('Setup test data for search')
.action(async cmd => {
await execTask(searchTestDataSetupTask)({ count: cmd.count });
});
program.parse(process.argv);
if (program.depreciate && program.depreciate.length === 2) {
console.log(
chalk.yellow.bold(
`[NPM script depreciation] ${program.depreciate[0]} is deprecated! Use ${program.depreciate[1]} instead!`
)
);
}
-83
View File
@@ -1,83 +0,0 @@
import _ from 'lodash';
import { Task, TaskRunner } from './task';
import GithubClient from '../utils/githubClient';
interface ChangelogOptions {
milestone: string;
}
const changelogTaskRunner: TaskRunner<ChangelogOptions> = async ({ milestone }) => {
const githubClient = new GithubClient();
const client = githubClient.client;
if (!/^\d+$/.test(milestone)) {
console.log('Use milestone number not title, find number in milestone url');
return;
}
const res = await client.get('/issues', {
params: {
state: 'closed',
per_page: 100,
labels: 'add to changelog',
milestone: milestone,
},
});
const issues = res.data;
const bugs = _.sortBy(
issues.filter(item => {
if (item.title.match(/fix|fixes/i)) {
return true;
}
if (item.labels.find(label => label.name === 'type/bug')) {
return true;
}
return false;
}),
'title'
);
const notBugs = _.sortBy(issues.filter(item => !bugs.find(bug => bug === item)), 'title');
let markdown = '';
if (notBugs.length > 0) {
markdown = '### Features / Enhancements\n';
}
for (const item of notBugs) {
markdown += getMarkdownLineForIssue(item);
}
if (bugs.length > 0) {
markdown += '\n### Bug Fixes\n';
}
for (const item of bugs) {
markdown += getMarkdownLineForIssue(item);
}
console.log(markdown);
};
function getMarkdownLineForIssue(item: any) {
const githubGrafanaUrl = 'https://github.com/grafana/grafana';
let markdown = '';
const title = item.title.replace(/^([^:]*)/, (match, g1) => {
return `**${g1}**`;
});
markdown += '* ' + title + '.';
markdown += ` [#${item.number}](${githubGrafanaUrl}/pull/${item.number})`;
markdown += `, [@${item.user.login}](${item.user.html_url})`;
markdown += '\n';
return markdown;
}
export const changelogTask = new Task<ChangelogOptions>();
changelogTask.setName('Changelog generator task');
changelogTask.setRunner(changelogTaskRunner);
-47
View File
@@ -1,47 +0,0 @@
import { Task, TaskRunner } from './task';
import GithubClient from '../utils/githubClient';
interface CherryPickOptions {}
const cherryPickRunner: TaskRunner<CherryPickOptions> = async () => {
const githubClient = new GithubClient();
const client = githubClient.client;
const res = await client.get('/issues', {
params: {
state: 'closed',
labels: 'cherry-pick needed',
},
});
// sort by closed date ASC
res.data.sort(function(a, b) {
return new Date(a.closed_at).getTime() - new Date(b.closed_at).getTime();
});
let commands = '';
console.log('--------------------------------------------------------------------');
console.log('Printing PRs with cherry-pick-needed, in ASC merge date order');
console.log('--------------------------------------------------------------------');
for (const item of res.data) {
if (!item.milestone) {
console.log(item.number + ' missing milestone!');
continue;
}
const issueDetails = await client.get(item.pull_request.url);
console.log(`* ${item.title}, (#${item.number}), merge-sha: ${issueDetails.data.merge_commit_sha}`);
commands += `git cherry-pick -x ${issueDetails.data.merge_commit_sha}\n`;
}
console.log('--------------------------------------------------------------------');
console.log('Commands (in order of how they should be executed)');
console.log('--------------------------------------------------------------------');
console.log(commands);
};
export const cherryPickTask = new Task<CherryPickOptions>();
cherryPickTask.setName('Cherry pick task');
cherryPickTask.setRunner(cherryPickRunner);
-75
View File
@@ -1,75 +0,0 @@
import { Task, TaskRunner } from './task';
import GithubClient from '../utils/githubClient';
interface CloseMilestoneOptions {
milestone: string;
}
const closeMilestoneTaskRunner: TaskRunner<CloseMilestoneOptions> = async ({ milestone }) => {
const githubClient = new GithubClient(true);
const cherryPickLabel = 'cherry-pick needed';
const client = githubClient.client;
if (!/^\d+$/.test(milestone)) {
console.log('Use milestone number not title, find number in milestone url');
return;
}
const milestoneRes = await client.get(`/milestones/${milestone}`, {});
const milestoneState = milestoneRes.data.state;
if (milestoneState === 'closed') {
console.log('milestone already closed. ✅');
return;
}
console.log('fetching issues/PRs of the milestone ⏬');
// Get all the issues/PRs with the label cherry-pick
// Every pull request is actually an issue
const issuesRes = await client.get('/issues', {
params: {
state: 'closed',
labels: cherryPickLabel,
per_page: 100,
milestone: milestone,
},
});
if (issuesRes.data.length < 1) {
console.log('no issues to remove label from');
} else {
console.log(`found ${issuesRes.data.length} issues to remove the cherry-pick label from 🔎`);
}
for (const issue of issuesRes.data) {
// the reason for using stdout.write is for achieving 'action -> result' on
// the same line
process.stdout.write(`🔧removing label from issue #${issue.number} 🗑...`);
const resDelete = await client.delete(`/issues/${issue.number}/labels/${cherryPickLabel}`, {});
if (resDelete.status === 200) {
process.stdout.write('done ✅\n');
} else {
console.log('failed ❌');
}
}
console.log(`cleaned up ${issuesRes.data.length} issues/prs ⚡️`);
const resClose = await client.patch(`/milestones/${milestone}`, {
state: 'closed',
});
if (resClose.status === 200) {
console.log('milestone closed 🙌');
} else {
console.log('failed to close the milestone, response:');
console.log(resClose);
}
};
export const closeMilestoneTask = new Task<CloseMilestoneOptions>();
closeMilestoneTask.setName('Close Milestone generator task');
closeMilestoneTask.setRunner(closeMilestoneTaskRunner);
-38
View File
@@ -1,38 +0,0 @@
import concurrently from 'concurrently';
import { Task, TaskRunner } from './task';
interface StartTaskOptions {
watchThemes: boolean;
hot: boolean;
}
const startTaskRunner: TaskRunner<StartTaskOptions> = async ({ watchThemes, hot }) => {
const jobs = [
watchThemes && {
command: 'nodemon -e ts -w ./packages/grafana-ui/src/themes -x yarn run themes:generate',
name: 'SASS variables generator',
},
hot
? {
command: 'webpack-dev-server --progress --colors --mode development --config scripts/webpack/webpack.hot.js',
name: 'Dev server',
}
: {
command: 'webpack --progress --colors --watch --mode development --config scripts/webpack/webpack.dev.js',
name: 'Webpack',
},
];
try {
await concurrently(jobs.filter(job => !!job), {
killOthers: ['failure', 'failure'],
});
} catch (e) {
console.error(e);
process.exit(1);
}
};
export const startTask = new Task<StartTaskOptions>();
startTask.setName('Core startTask');
startTask.setRunner(startTaskRunner);
-76
View File
@@ -1,76 +0,0 @@
import execa from 'execa';
import fs from 'fs';
import { changeCwdToGrafanaUi, restoreCwd } from '../utils/cwd';
import chalk from 'chalk';
import { useSpinner } from '../utils/useSpinner';
import { Task, TaskRunner } from './task';
let distDir, cwd;
export const clean = useSpinner<void>('Cleaning', async () => await execa('npm', ['run', 'clean']));
const compile = useSpinner<void>('Compiling sources', () => execa('tsc', ['-p', './tsconfig.build.json']));
const rollup = useSpinner<void>('Bundling', () => execa('npm', ['run', 'build']));
export const savePackage = useSpinner<{
path: string;
pkg: {};
}>('Updating package.json', async ({ path, pkg }) => {
return new Promise((resolve, reject) => {
fs.writeFile(path, JSON.stringify(pkg, null, 2), err => {
if (err) {
reject(err);
return;
}
resolve();
});
});
});
const preparePackage = async pkg => {
pkg.main = 'index.js';
pkg.types = 'index.d.ts';
await savePackage({
path: `${cwd}/dist/package.json`,
pkg,
});
};
const moveFiles = () => {
const files = ['README.md', 'CHANGELOG.md', 'index.js'];
return useSpinner<void>(`Moving ${files.join(', ')} files`, async () => {
const promises = files.map(file => {
return new Promise((resolve, reject) => {
fs.copyFile(`${cwd}/${file}`, `${distDir}/${file}`, err => {
if (err) {
reject(err);
return;
}
resolve();
});
});
});
await Promise.all(promises);
})();
};
const buildTaskRunner: TaskRunner<void> = async () => {
cwd = changeCwdToGrafanaUi();
distDir = `${cwd}/dist`;
const pkg = require(`${cwd}/package.json`);
console.log(chalk.yellow(`Building ${pkg.name} (package.json version: ${pkg.version})`));
await clean();
await compile();
await rollup();
await preparePackage(pkg);
await moveFiles();
restoreCwd();
};
export const buildTask = new Task<void>();
buildTask.setName('@grafana/ui build');
buildTask.setRunner(buildTaskRunner);
-195
View File
@@ -1,195 +0,0 @@
import execa from 'execa';
import { execTask } from '../utils/execTask';
import { changeCwdToGrafanaUiDist, changeCwdToGrafanaUi, restoreCwd } from '../utils/cwd';
import semver from 'semver';
import inquirer from 'inquirer';
import chalk from 'chalk';
import { useSpinner } from '../utils/useSpinner';
import { savePackage, buildTask, clean } from './grafanaui.build';
import { TaskRunner, Task } from './task';
type VersionBumpType = 'prerelease' | 'patch' | 'minor' | 'major';
interface ReleaseTaskOptions {
publishToNpm: boolean;
usePackageJsonVersion: boolean;
createVersionCommit: boolean;
}
const promptBumpType = async () => {
return inquirer.prompt<{ type: VersionBumpType }>([
{
type: 'list',
message: 'Select version bump',
name: 'type',
choices: ['prerelease', 'patch', 'minor', 'major'],
validate: answer => {
if (answer.length < 1) {
return 'You must choose something';
}
return true;
},
},
]);
};
const promptPrereleaseId = async (message = 'Is this a prerelease?', allowNo = true) => {
return inquirer.prompt<{ id: string }>([
{
type: 'list',
message: message,
name: 'id',
choices: allowNo ? ['no', 'alpha', 'beta'] : ['alpha', 'beta'],
validate: answer => {
if (answer.length < 1) {
return 'You must choose something';
}
return true;
},
},
]);
};
const promptConfirm = async (message?: string) => {
return inquirer.prompt<{ confirmed: boolean }>([
{
type: 'confirm',
message: message || 'Is that correct?',
name: 'confirmed',
default: false,
},
]);
};
// Since Grafana core depends on @grafana/ui highly, we run full check before release
const runChecksAndTests = async () =>
useSpinner<void>(`Running checks and tests`, async () => {
await execa('npm', ['run', 'test']);
})();
const bumpVersion = (version: string) =>
useSpinner<void>(`Saving version ${version} to package.json`, async () => {
changeCwdToGrafanaUi();
await execa('npm', ['version', version]);
changeCwdToGrafanaUiDist();
const pkg = require(`${process.cwd()}/package.json`);
pkg.version = version;
await savePackage({ path: `${process.cwd()}/package.json`, pkg });
})();
const publishPackage = (name: string, version: string) =>
useSpinner<void>(`Publishing ${name} @ ${version} to npm registry...`, async () => {
changeCwdToGrafanaUiDist();
await execa('npm', ['publish', '--access', 'public']);
})();
const ensureMasterBranch = async () => {
const currentBranch = await execa.stdout('git', ['symbolic-ref', '--short', 'HEAD']);
const status = await execa.stdout('git', ['status', '--porcelain']);
if (currentBranch !== 'master' && status !== '') {
console.error(chalk.red.bold('You need to be on clean master branch to release @grafana/ui'));
process.exit(1);
}
};
const prepareVersionCommitAndPush = async (version: string) =>
useSpinner<void>('Commiting and pushing @grafana/ui version update', async () => {
await execa.stdout('git', ['commit', '-a', '-m', `Upgrade @grafana/ui version to v${version}`]);
await execa.stdout('git', ['push']);
})();
const releaseTaskRunner: TaskRunner<ReleaseTaskOptions> = async ({
publishToNpm,
usePackageJsonVersion,
createVersionCommit,
}) => {
changeCwdToGrafanaUi();
await clean(); // Clean previous build if exists
restoreCwd();
if (publishToNpm) {
// TODO: Ensure release branch
// When need to update this when we star keeping @grafana/ui releases in sync with core
await ensureMasterBranch();
}
await runChecksAndTests();
await execTask(buildTask)();
let releaseConfirmed = false;
let nextVersion;
changeCwdToGrafanaUiDist();
const pkg = require(`${process.cwd()}/package.json`);
console.log(`Current version: ${pkg.version}`);
do {
if (!usePackageJsonVersion) {
const { type } = await promptBumpType();
console.log(type);
if (type === 'prerelease') {
const { id } = await promptPrereleaseId('What kind of prerelease?', false);
nextVersion = semver.inc(pkg.version, type, id);
} else {
const { id } = await promptPrereleaseId();
if (id !== 'no') {
nextVersion = semver.inc(pkg.version, `pre${type}`, id);
} else {
nextVersion = semver.inc(pkg.version, type);
}
}
} else {
nextVersion = pkg.version;
}
console.log(chalk.yellowBright.bold(`You are going to release a new version of ${pkg.name}`));
if (usePackageJsonVersion) {
console.log(chalk.green(`Version based on package.json: `), chalk.bold.yellowBright(`${nextVersion}`));
} else {
console.log(chalk.green(`Version bump: ${pkg.version} ->`), chalk.bold.yellowBright(`${nextVersion}`));
}
const { confirmed } = await promptConfirm();
releaseConfirmed = confirmed;
} while (!releaseConfirmed);
if (!usePackageJsonVersion) {
await bumpVersion(nextVersion);
}
if (createVersionCommit) {
await prepareVersionCommitAndPush(nextVersion);
}
if (publishToNpm) {
console.log(chalk.yellowBright.bold(`\nReview dist package.json before proceeding!\n`));
const { confirmed } = await promptConfirm('Are you ready to publish to npm?');
if (!confirmed) {
process.exit();
}
await publishPackage(pkg.name, nextVersion);
console.log(chalk.green(`\nVersion ${nextVersion} of ${pkg.name} succesfully released!`));
console.log(chalk.yellow(`\nUpdated @grafana/ui/package.json with version bump created.`));
process.exit();
} else {
console.log(
chalk.green(
`\nVersion ${nextVersion} of ${pkg.name} succesfully prepared for release. See packages/grafana-ui/dist`
)
);
console.log(chalk.green(`\nTo publish to npm registry run`), chalk.bold.blue(`npm run gui:publish`));
}
};
export const releaseTask = new Task<ReleaseTaskOptions>();
releaseTask.setName('@grafana/ui release');
releaseTask.setRunner(releaseTaskRunner);
-81
View File
@@ -1,81 +0,0 @@
import { Task, TaskRunner } from './task';
import chalk from 'chalk';
import get from 'lodash/get';
import flatten from 'lodash/flatten';
import execa = require('execa');
const simpleGit = require('simple-git/promise')(process.cwd());
interface PrecommitOptions {}
const tasks = {
lint: {
sass: ['newer:sasslint'],
core: ['newer:exec:tslintRoot'],
gui: ['newer:exec:tslintPackages'],
},
typecheck: {
core: ['newer:exec:typecheckRoot'],
gui: ['newer:exec:typecheckPackages'],
},
test: {
lint: {
ts: ['no-only-tests'],
go: ['no-focus-convey-tests'],
},
},
};
const precommitRunner: TaskRunner<PrecommitOptions> = async () => {
const status = await simpleGit.status();
const sassFiles = status.files.filter(
file => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\.scss)$/g) || file.path.indexOf('.sass-lint.yml') > -1
);
const tsFiles = status.files.filter(file => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\.(ts|tsx))$/g));
const testFiles = status.files.filter(file => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\.test.(ts|tsx))$/g));
const goTestFiles = status.files.filter(file => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\_test.go)$/g));
const grafanaUiFiles = tsFiles.filter(file => (file.path as string).indexOf('grafana-ui') > -1);
const grafanaUIFilesChangedOnly = tsFiles.length > 0 && tsFiles.length - grafanaUiFiles.length === 0;
const coreFilesChangedOnly = tsFiles.length > 0 && grafanaUiFiles.length === 0;
const taskPaths = [];
if (sassFiles.length > 0) {
taskPaths.push('lint.sass');
}
if (testFiles.length) {
taskPaths.push('test.lint.ts');
}
if (goTestFiles.length) {
taskPaths.push('test.lint.go');
}
if (tsFiles.length > 0) {
if (grafanaUIFilesChangedOnly) {
taskPaths.push('lint.gui', 'typecheck.core', 'typecheck.gui');
} else if (coreFilesChangedOnly) {
taskPaths.push('lint.core', 'typecheck.core');
} else {
taskPaths.push('lint.core', 'lint.gui', 'typecheck.core', 'typecheck.gui');
}
}
const gruntTasks = flatten(taskPaths.map(path => get(tasks, path)));
if (gruntTasks.length > 0) {
console.log(chalk.yellow(`Precommit checks: ${taskPaths.join(', ')}`));
const task = execa('grunt', gruntTasks);
// @ts-ignore
const stream = task.stdout;
stream.pipe(process.stdout);
return task;
}
console.log(chalk.yellow('Skipping precommit checks, not front-end changes detected'));
return;
};
export const precommitTask = new Task<PrecommitOptions>();
precommitTask.setName('Precommit task');
precommitTask.setRunner(precommitRunner);
-117
View File
@@ -1,117 +0,0 @@
import axios from 'axios';
import _ from 'lodash';
import { Task, TaskRunner } from './task';
interface SearchTestDataSetupOptions {
count: number;
}
const client = axios.create({
baseURL: 'http://localhost:3000/api',
auth: {
username: 'admin',
password: 'admin2',
},
});
export async function getUser(user): Promise<any> {
console.log('Creating user ' + user.name);
const search = await client.get('/users/search', {
params: { query: user.login },
});
if (search.data.totalCount === 1) {
user.id = search.data.users[0].id;
return user;
}
const rsp = await client.post('/admin/users', user);
user.id = rsp.data.id;
return user;
}
export async function getTeam(team: any): Promise<any> {
// delete if exists
const teams = await client.get('/teams/search');
for (const existing of teams.data.teams) {
if (existing.name === team.name) {
console.log('Team exists, deleting');
await client.delete('/teams/' + existing.id);
}
}
console.log('Creating team ' + team.name);
const teamRsp = await client.post(`/teams`, team);
team.id = teamRsp.data.teamId;
return team;
}
export async function addToTeam(team: any, user: any): Promise<any> {
const members = await client.get(`/teams/${team.id}/members`);
console.log(`Adding user ${user.name} to team ${team.name}`);
await client.post(`/teams/${team.id}/members`, { userId: user.id });
}
export async function setDashboardAcl(dashboardId: any, aclList: any) {
console.log('Setting Dashboard ACL ' + dashboardId);
await client.post(`/dashboards/id/${dashboardId}/permissions`, { items: aclList });
}
const searchTestDataSetupRunnner: TaskRunner<SearchTestDataSetupOptions> = async ({ count }) => {
const user1 = await getUser({
name: 'searchTestUser1',
email: 'searchTestUser@team.com',
login: 'searchTestUser1',
password: '12345',
});
const team1 = await getTeam({ name: 'searchTestTeam1', email: 'searchtestdata@team.com' });
addToTeam(team1, user1);
// create or update folder
const folder: any = {
uid: 'search-test-data',
title: 'Search test data folder',
version: 1,
};
try {
await client.delete(`/folders/${folder.uid}`);
} catch (err) {}
console.log('Creating folder');
const rsp = await client.post(`/folders`, folder);
folder.id = rsp.data.id;
folder.url = rsp.data.url;
await setDashboardAcl(folder.id, []);
console.log('Creating dashboards');
const dashboards: any = [];
for (let i = 0; i < count; i++) {
const dashboard: any = {
uid: 'search-test-dash-' + i.toString().padStart(5, '0'),
title: 'Search test dash ' + i.toString().padStart(5, '0'),
};
const rsp = await client.post(`/dashboards/db`, {
dashboard: dashboard,
folderId: folder.id,
overwrite: true,
});
dashboard.id = rsp.data.id;
dashboard.url = rsp.data.url;
console.log('Created dashboard ' + dashboard.title);
dashboards.push(dashboard);
await setDashboardAcl(dashboard.id, [{ userId: 0, teamId: team1.id, permission: 4 }]);
}
};
export const searchTestDataSetupTask = new Task<SearchTestDataSetupOptions>();
searchTestDataSetupTask.setName('Search test data setup');
searchTestDataSetupTask.setRunner(searchTestDataSetupRunnner);
-23
View File
@@ -1,23 +0,0 @@
export type TaskRunner<T> = (options: T) => Promise<any>;
export class Task<TOptions> {
name: string;
runner: (options: TOptions) => Promise<any>;
options: TOptions;
setName = name => {
this.name = name;
};
setRunner = (runner: TaskRunner<TOptions>) => {
this.runner = runner;
};
setOptions = options => {
this.options = options;
};
exec = () => {
return this.runner(this.options);
};
}
-14
View File
@@ -1,14 +0,0 @@
const cwd = process.cwd();
export const changeCwdToGrafanaUi = () => {
process.chdir(`${cwd}/packages/grafana-ui`);
return process.cwd();
};
export const changeCwdToGrafanaUiDist = () => {
process.chdir(`${cwd}/packages/grafana-ui/dist`);
};
export const restoreCwd = () => {
process.chdir(cwd);
};
-15
View File
@@ -1,15 +0,0 @@
import { Task } from '../tasks/task';
import chalk from 'chalk';
export const execTask = <TOptions>(task: Task<TOptions>) => async (options: TOptions) => {
console.log(chalk.yellow(`Running ${chalk.bold(task.name)} task`));
task.setOptions(options);
try {
console.group();
await task.exec();
console.groupEnd();
} catch (e) {
console.log(e);
process.exit(1);
}
};
-66
View File
@@ -1,66 +0,0 @@
import GithubClient from './githubClient';
const fakeClient = jest.fn();
beforeEach(() => {
delete process.env.GITHUB_USERNAME;
delete process.env.GITHUB_ACCESS_TOKEN;
});
afterEach(() => {
delete process.env.GITHUB_USERNAME;
delete process.env.GITHUB_ACCESS_TOKEN;
});
describe('GithubClient', () => {
it('should initialise a GithubClient', () => {
const github = new GithubClient();
expect(github).toBeInstanceOf(GithubClient);
});
describe('#client', () => {
it('it should contain a client', () => {
const spy = jest.spyOn(GithubClient.prototype, 'createClient').mockImplementation(() => fakeClient);
const github = new GithubClient();
const client = github.client;
expect(spy).toHaveBeenCalledWith({
baseURL: 'https://api.github.com/repos/grafana/grafana',
timeout: 10000,
});
expect(client).toEqual(fakeClient);
});
describe('when the credentials are required', () => {
it('should create the client when the credentials are defined', () => {
const username = 'grafana';
const token = 'averysecureaccesstoken';
process.env.GITHUB_USERNAME = username;
process.env.GITHUB_ACCESS_TOKEN = token;
const spy = jest.spyOn(GithubClient.prototype, 'createClient').mockImplementation(() => fakeClient);
const github = new GithubClient(true);
const client = github.client;
expect(spy).toHaveBeenCalledWith({
baseURL: 'https://api.github.com/repos/grafana/grafana',
timeout: 10000,
auth: { username, password: token },
});
expect(client).toEqual(fakeClient);
});
describe('when the credentials are not defined', () => {
it('should throw an error', () => {
expect(() => {
new GithubClient(true);
}).toThrow(/operation needs a GITHUB_USERNAME and GITHUB_ACCESS_TOKEN environment variables/);
});
});
});
});
});
-41
View File
@@ -1,41 +0,0 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
const baseURL = 'https://api.github.com/repos/grafana/grafana';
// Encapsulates the creation of a client for the Github API
//
// Two key things:
// 1. You can specify whenever you want the credentials to be required or not when imported.
// 2. If the the credentials are available as part of the environment, even if
// they're not required - the library will use them. This allows us to overcome
// any API rate limiting imposed without authentication.
class GithubClient {
client: AxiosInstance;
constructor(required = false) {
const username = process.env.GITHUB_USERNAME;
const token = process.env.GITHUB_ACCESS_TOKEN;
const clientConfig: AxiosRequestConfig = {
baseURL: baseURL,
timeout: 10000,
};
if (required && !username && !token) {
throw new Error('operation needs a GITHUB_USERNAME and GITHUB_ACCESS_TOKEN environment variables');
}
if (username && token) {
clientConfig.auth = { username: username, password: token };
}
this.client = this.createClient(clientConfig);
}
private createClient(clientConfig: AxiosRequestConfig) {
return axios.create(clientConfig);
}
}
export default GithubClient;
-19
View File
@@ -1,19 +0,0 @@
import ora from 'ora';
type FnToSpin<T> = (options: T) => Promise<void>;
export const useSpinner = <T>(spinnerLabel: string, fn: FnToSpin<T>, killProcess = true) => {
return async (options: T) => {
const spinner = ora(spinnerLabel);
spinner.start();
try {
await fn(options);
spinner.succeed();
} catch (e) {
spinner.fail(e);
if (killProcess) {
process.exit(1);
}
}
};
};