Update plugin generator to generate NP plugins (#55281)
* Generate NP plugin * Added tsconfig * tsconfig * Adjust sao test * Add server side to plugin gen * Added navigation * add empty element * eslint * platform team CR * design CR improvements * text updates * temp disable plugin gen tests * eslint * Code review fixes * Add scss support - requires #53976 to be merged to work * CR fixes * comment fixes * Don't generate eslint for internal plugins by default * Update tests * reenable jest test for sao * Fix regex * review comments * code review Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
4f4d3d753c
commit
479223b0a1
|
@ -29,6 +29,7 @@ exports.run = function run(argv) {
|
||||||
const options = getopts(argv, {
|
const options = getopts(argv, {
|
||||||
alias: {
|
alias: {
|
||||||
h: 'help',
|
h: 'help',
|
||||||
|
i: 'internal',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -40,17 +41,22 @@ exports.run = function run(argv) {
|
||||||
if (options.help) {
|
if (options.help) {
|
||||||
console.log(
|
console.log(
|
||||||
dedent(chalk`
|
dedent(chalk`
|
||||||
{dim usage:} node scripts/generate-plugin {bold [name]}
|
# {dim Usage:}
|
||||||
|
node scripts/generate-plugin {bold [name]}
|
||||||
generate a fresh Kibana plugin in the plugins/ directory
|
Generate a fresh Kibana plugin in the plugins/ directory
|
||||||
|
|
||||||
|
# {dim Core Kibana plugins:}
|
||||||
|
node scripts/generate-plugin {bold [name]} -i
|
||||||
|
To generate a core Kibana plugin inside the src/plugins/ directory, add the -i flag.
|
||||||
`) + '\n'
|
`) + '\n'
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = options._[0];
|
const name = options._[0];
|
||||||
|
const isKibanaPlugin = options.internal;
|
||||||
const template = resolve(__dirname, './sao_template');
|
const template = resolve(__dirname, './sao_template');
|
||||||
const kibanaPlugins = resolve(__dirname, '../../plugins');
|
const kibanaPlugins = resolve(__dirname, isKibanaPlugin ? '../../src/plugins' : '../../plugins');
|
||||||
const targetPath = resolve(kibanaPlugins, snakeCase(name));
|
const targetPath = resolve(kibanaPlugins, snakeCase(name));
|
||||||
|
|
||||||
sao({
|
sao({
|
||||||
|
@ -58,6 +64,8 @@ exports.run = function run(argv) {
|
||||||
targetPath: targetPath,
|
targetPath: targetPath,
|
||||||
configOptions: {
|
configOptions: {
|
||||||
name,
|
name,
|
||||||
|
isKibanaPlugin,
|
||||||
|
targetPath,
|
||||||
},
|
},
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error(chalk`{red fatal error}!`);
|
console.error(chalk`{red fatal error}!`);
|
||||||
|
|
24
packages/kbn-plugin-generator/index.js.d.ts
vendored
Normal file
24
packages/kbn-plugin-generator/index.js.d.ts
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||||
|
*/
|
||||||
|
interface PluginGenerator {
|
||||||
|
/**
|
||||||
|
* Run plugin generator.
|
||||||
|
*/
|
||||||
|
run: (...args: any[]) => any;
|
||||||
|
}
|
|
@ -61,7 +61,8 @@ describe(`running the plugin-generator via 'node scripts/generate_plugin.js plug
|
||||||
expect(stats.isDirectory()).toBe(true);
|
expect(stats.isDirectory()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should create an internationalization config file with a blank line appended to satisfy the parser`, async () => {
|
// skipped until internationalization is re-introduced
|
||||||
|
it.skip(`should create an internationalization config file with a blank line appended to satisfy the parser`, async () => {
|
||||||
// Link to the error that happens when the blank line is not there:
|
// Link to the error that happens when the blank line is not there:
|
||||||
// https://github.com/elastic/kibana/pull/45044#issuecomment-530092627
|
// https://github.com/elastic/kibana/pull/45044#issuecomment-530092627
|
||||||
const intlFile = `${generatedPath}/.i18nrc.json`;
|
const intlFile = `${generatedPath}/.i18nrc.json`;
|
||||||
|
@ -78,16 +79,7 @@ describe(`running the plugin-generator via 'node scripts/generate_plugin.js plug
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`'yarn test:server' should exit 0`, async () => {
|
it.skip(`'yarn build' should exit 0`, async () => {
|
||||||
await execa('yarn', ['test:server'], {
|
|
||||||
cwd: generatedPath,
|
|
||||||
env: {
|
|
||||||
DISABLE_JUNIT_REPORTER: '1',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`'yarn build' should exit 0`, async () => {
|
|
||||||
await execa('yarn', ['build'], { cwd: generatedPath });
|
await execa('yarn', ['build'], { cwd: generatedPath });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -109,7 +101,7 @@ describe(`running the plugin-generator via 'node scripts/generate_plugin.js plug
|
||||||
'--migrations.skip=true',
|
'--migrations.skip=true',
|
||||||
],
|
],
|
||||||
cwd: generatedPath,
|
cwd: generatedPath,
|
||||||
wait: /ispec_plugin.+Status changed from uninitialized to green - Ready/,
|
wait: new RegExp('\\[ispecPlugin\\]\\[plugins\\] Setting up plugin'),
|
||||||
});
|
});
|
||||||
await proc.stop('kibana');
|
await proc.stop('kibana');
|
||||||
});
|
});
|
||||||
|
@ -120,7 +112,7 @@ describe(`running the plugin-generator via 'node scripts/generate_plugin.js plug
|
||||||
await execa('yarn', ['preinstall'], { cwd: generatedPath });
|
await execa('yarn', ['preinstall'], { cwd: generatedPath });
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`'yarn lint' should exit 0`, async () => {
|
it.skip(`'yarn lint' should exit 0`, async () => {
|
||||||
await execa('yarn', ['lint'], { cwd: generatedPath });
|
await execa('yarn', ['lint'], { cwd: generatedPath });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -17,21 +17,19 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { resolve, relative, dirname } = require('path');
|
const { relative } = require('path');
|
||||||
|
|
||||||
const startCase = require('lodash.startcase');
|
const startCase = require('lodash.startcase');
|
||||||
const camelCase = require('lodash.camelcase');
|
const camelCase = require('lodash.camelcase');
|
||||||
const snakeCase = require('lodash.snakecase');
|
const snakeCase = require('lodash.snakecase');
|
||||||
const execa = require('execa');
|
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
|
const execa = require('execa');
|
||||||
|
|
||||||
const pkg = require('../package.json');
|
const pkg = require('../package.json');
|
||||||
const kibanaPkgPath = require.resolve('../../../package.json');
|
const kibanaPkgPath = require.resolve('../../../package.json');
|
||||||
const kibanaPkg = require(kibanaPkgPath); // eslint-disable-line import/no-dynamic-require
|
const kibanaPkg = require(kibanaPkgPath); // eslint-disable-line import/no-dynamic-require
|
||||||
|
|
||||||
const KBN_DIR = dirname(kibanaPkgPath);
|
module.exports = function({ name, targetPath, isKibanaPlugin }) {
|
||||||
|
|
||||||
module.exports = function({ name }) {
|
|
||||||
return {
|
return {
|
||||||
prompts: {
|
prompts: {
|
||||||
description: {
|
description: {
|
||||||
|
@ -47,41 +45,38 @@ module.exports = function({ name }) {
|
||||||
message: 'Should an app component be generated?',
|
message: 'Should an app component be generated?',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
generateTranslations: {
|
|
||||||
type: 'confirm',
|
|
||||||
message: 'Should translation files be generated?',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
generateHack: {
|
|
||||||
type: 'confirm',
|
|
||||||
message: 'Should a hack component be generated?',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
generateApi: {
|
generateApi: {
|
||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
message: 'Should a server API be generated?',
|
message: 'Should a server API be generated?',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
// generateTranslations: {
|
||||||
|
// type: 'confirm',
|
||||||
|
// message: 'Should translation files be generated?',
|
||||||
|
// default: true,
|
||||||
|
// },
|
||||||
generateScss: {
|
generateScss: {
|
||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
message: 'Should SCSS be used?',
|
message: 'Should SCSS be used?',
|
||||||
when: answers => answers.generateApp,
|
when: answers => answers.generateApp,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
generateEslint: {
|
||||||
|
type: 'confirm',
|
||||||
|
message: 'Would you like to use a custom eslint file?',
|
||||||
|
default: !isKibanaPlugin,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
|
'public/**/index.scss': 'generateScss',
|
||||||
'public/**/*': 'generateApp',
|
'public/**/*': 'generateApp',
|
||||||
'translations/**/*': 'generateTranslations',
|
|
||||||
'.i18nrc.json': 'generateTranslations',
|
|
||||||
'public/hack.js': 'generateHack',
|
|
||||||
'server/**/*': 'generateApi',
|
'server/**/*': 'generateApi',
|
||||||
'public/app.scss': 'generateScss',
|
// 'translations/**/*': 'generateTranslations',
|
||||||
'.kibana-plugin-helpers.json': 'generateScss',
|
// '.i18nrc.json': 'generateTranslations',
|
||||||
|
'eslintrc.js': 'generateEslint',
|
||||||
},
|
},
|
||||||
move: {
|
move: {
|
||||||
gitignore: '.gitignore',
|
|
||||||
'eslintrc.js': '.eslintrc.js',
|
'eslintrc.js': '.eslintrc.js',
|
||||||
'package_template.json': 'package.json',
|
|
||||||
},
|
},
|
||||||
data: answers =>
|
data: answers =>
|
||||||
Object.assign(
|
Object.assign(
|
||||||
|
@ -91,34 +86,36 @@ module.exports = function({ name }) {
|
||||||
camelCase,
|
camelCase,
|
||||||
snakeCase,
|
snakeCase,
|
||||||
name,
|
name,
|
||||||
|
isKibanaPlugin,
|
||||||
|
kbnVersion: answers.kbnVersion,
|
||||||
|
upperCamelCaseName: name.charAt(0).toUpperCase() + camelCase(name).slice(1),
|
||||||
|
hasUi: !!answers.generateApp,
|
||||||
|
hasServer: !!answers.generateApi,
|
||||||
|
hasScss: !!answers.generateScss,
|
||||||
|
relRoot: isKibanaPlugin ? '../../../..' : '../../..',
|
||||||
},
|
},
|
||||||
answers
|
answers
|
||||||
),
|
),
|
||||||
enforceNewFolder: true,
|
enforceNewFolder: true,
|
||||||
installDependencies: false,
|
installDependencies: false,
|
||||||
gitInit: true,
|
gitInit: !isKibanaPlugin,
|
||||||
async post({ log }) {
|
async post({ log }) {
|
||||||
await execa('yarn', ['kbn', 'bootstrap'], {
|
const dir = relative(process.cwd(), targetPath);
|
||||||
cwd: KBN_DIR,
|
|
||||||
stdio: 'inherit',
|
|
||||||
});
|
|
||||||
|
|
||||||
const dir = relative(process.cwd(), resolve(KBN_DIR, 'plugins', snakeCase(name)));
|
|
||||||
|
|
||||||
|
// Apply eslint to the generated plugin
|
||||||
try {
|
try {
|
||||||
await execa('yarn', ['lint', '--fix'], {
|
await execa('yarn', ['lint:es', `./${dir}/**/*.ts*`, '--no-ignore', '--fix']);
|
||||||
cwd: dir,
|
|
||||||
all: true,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Failure when running prettier on the generated output: ${error.all}`);
|
console.error(error);
|
||||||
|
throw new Error(
|
||||||
|
`Failure when running prettier on the generated output: ${error.all || error}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.success(chalk`🎉
|
log.success(chalk`🎉
|
||||||
|
|
||||||
Your plugin has been created in {bold ${dir}}. Move into that directory to run it:
|
Your plugin has been created in {bold ${dir}}.
|
||||||
|
|
||||||
{bold cd "${dir}"}
|
|
||||||
{bold yarn start}
|
{bold yarn start}
|
||||||
`);
|
`);
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,8 +19,6 @@
|
||||||
|
|
||||||
const sao = require('sao');
|
const sao = require('sao');
|
||||||
|
|
||||||
const templatePkg = require('../package.json');
|
|
||||||
|
|
||||||
const template = {
|
const template = {
|
||||||
fromPath: __dirname,
|
fromPath: __dirname,
|
||||||
configOptions: {
|
configOptions: {
|
||||||
|
@ -32,121 +30,57 @@ function getFileContents(file) {
|
||||||
return file.contents.toString();
|
return file.contents.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConfig(file) {
|
|
||||||
const contents = getFileContents(file).replace(/\r?\n/gm, '');
|
|
||||||
return contents.split('kibana.Plugin(')[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('plugin generator sao integration', () => {
|
describe('plugin generator sao integration', () => {
|
||||||
test('skips files when answering no', async () => {
|
test('skips files when answering no', async () => {
|
||||||
const res = await sao.mockPrompt(template, {
|
const res = await sao.mockPrompt(template, {
|
||||||
generateApp: false,
|
generateApp: false,
|
||||||
generateHack: false,
|
|
||||||
generateApi: false,
|
generateApi: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res.fileList).not.toContain('public/app.js');
|
expect(res.fileList).toContain('common/index.ts');
|
||||||
expect(res.fileList).not.toContain('public/__tests__/index.js');
|
expect(res.fileList).not.toContain('public/index.ts');
|
||||||
expect(res.fileList).not.toContain('public/hack.js');
|
expect(res.fileList).not.toContain('server/index.ts');
|
||||||
expect(res.fileList).not.toContain('server/routes/example.js');
|
|
||||||
expect(res.fileList).not.toContain('server/__tests__/index.js');
|
|
||||||
|
|
||||||
const uiExports = getConfig(res.files['index.js']);
|
|
||||||
expect(uiExports).not.toContain('app:');
|
|
||||||
expect(uiExports).not.toContain('hacks:');
|
|
||||||
expect(uiExports).not.toContain('init(server, options)');
|
|
||||||
expect(uiExports).not.toContain('registerFeature(');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('includes app when answering yes', async () => {
|
it('includes app when answering yes', async () => {
|
||||||
const res = await sao.mockPrompt(template, {
|
const res = await sao.mockPrompt(template, {
|
||||||
generateApp: true,
|
generateApp: true,
|
||||||
generateHack: false,
|
|
||||||
generateApi: false,
|
generateApi: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// check output files
|
// check output files
|
||||||
expect(res.fileList).toContain('public/app.js');
|
expect(res.fileList).toContain('common/index.ts');
|
||||||
expect(res.fileList).toContain('public/__tests__/index.js');
|
expect(res.fileList).toContain('public/index.ts');
|
||||||
expect(res.fileList).not.toContain('public/hack.js');
|
expect(res.fileList).toContain('public/plugin.ts');
|
||||||
expect(res.fileList).not.toContain('server/routes/example.js');
|
expect(res.fileList).toContain('public/types.ts');
|
||||||
expect(res.fileList).not.toContain('server/__tests__/index.js');
|
expect(res.fileList).toContain('public/components/app.tsx');
|
||||||
|
expect(res.fileList).not.toContain('server/index.ts');
|
||||||
const uiExports = getConfig(res.files['index.js']);
|
|
||||||
expect(uiExports).toContain('app:');
|
|
||||||
expect(uiExports).toContain('init(server, options)');
|
|
||||||
expect(uiExports).toContain('registerFeature(');
|
|
||||||
expect(uiExports).not.toContain('hacks:');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('includes hack when answering yes', async () => {
|
|
||||||
const res = await sao.mockPrompt(template, {
|
|
||||||
generateApp: true,
|
|
||||||
generateHack: true,
|
|
||||||
generateApi: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// check output files
|
|
||||||
expect(res.fileList).toContain('public/app.js');
|
|
||||||
expect(res.fileList).toContain('public/__tests__/index.js');
|
|
||||||
expect(res.fileList).toContain('public/hack.js');
|
|
||||||
expect(res.fileList).not.toContain('server/routes/example.js');
|
|
||||||
expect(res.fileList).not.toContain('server/__tests__/index.js');
|
|
||||||
|
|
||||||
const uiExports = getConfig(res.files['index.js']);
|
|
||||||
expect(uiExports).toContain('app:');
|
|
||||||
expect(uiExports).toContain('hacks:');
|
|
||||||
expect(uiExports).toContain('init(server, options)');
|
|
||||||
expect(uiExports).toContain('registerFeature(');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('includes server api when answering yes', async () => {
|
it('includes server api when answering yes', async () => {
|
||||||
const res = await sao.mockPrompt(template, {
|
const res = await sao.mockPrompt(template, {
|
||||||
generateApp: true,
|
generateApp: true,
|
||||||
generateHack: true,
|
|
||||||
generateApi: true,
|
generateApi: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// check output files
|
// check output files
|
||||||
expect(res.fileList).toContain('public/app.js');
|
expect(res.fileList).toContain('public/plugin.ts');
|
||||||
expect(res.fileList).toContain('public/__tests__/index.js');
|
expect(res.fileList).toContain('server/plugin.ts');
|
||||||
expect(res.fileList).toContain('public/hack.js');
|
expect(res.fileList).toContain('server/index.ts');
|
||||||
expect(res.fileList).toContain('server/routes/example.js');
|
expect(res.fileList).toContain('server/types.ts');
|
||||||
expect(res.fileList).toContain('server/__tests__/index.js');
|
expect(res.fileList).toContain('server/routes/index.ts');
|
||||||
|
|
||||||
const uiExports = getConfig(res.files['index.js']);
|
|
||||||
expect(uiExports).toContain('app:');
|
|
||||||
expect(uiExports).toContain('hacks:');
|
|
||||||
expect(uiExports).toContain('init(server, options)');
|
|
||||||
expect(uiExports).toContain('registerFeature(');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('plugin config has correct name and main path', async () => {
|
it('plugin package has correct title', async () => {
|
||||||
const res = await sao.mockPrompt(template, {
|
const res = await sao.mockPrompt(template, {
|
||||||
generateApp: true,
|
generateApp: true,
|
||||||
generateHack: true,
|
|
||||||
generateApi: true,
|
generateApi: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const indexContents = getFileContents(res.files['index.js']);
|
const contents = getFileContents(res.files['common/index.ts']);
|
||||||
const nameLine = indexContents.match('name: (.*)')[1];
|
const controllerLine = contents.match("PLUGIN_NAME = '(.*)'")[1];
|
||||||
const mainLine = indexContents.match('main: (.*)')[1];
|
|
||||||
|
|
||||||
expect(nameLine).toContain('some_fancy_plugin');
|
expect(controllerLine).toContain('Some fancy plugin');
|
||||||
expect(mainLine).toContain('plugins/some_fancy_plugin/app');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('plugin package has correct name', async () => {
|
|
||||||
const res = await sao.mockPrompt(template, {
|
|
||||||
generateApp: true,
|
|
||||||
generateHack: true,
|
|
||||||
generateApi: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const packageContents = getFileContents(res.files['package.json']);
|
|
||||||
const pkg = JSON.parse(packageContents);
|
|
||||||
|
|
||||||
expect(pkg.name).toBe('some_fancy_plugin');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('package has version "kibana" with master', async () => {
|
it('package has version "kibana" with master', async () => {
|
||||||
|
@ -154,10 +88,10 @@ describe('plugin generator sao integration', () => {
|
||||||
kbnVersion: 'master',
|
kbnVersion: 'master',
|
||||||
});
|
});
|
||||||
|
|
||||||
const packageContents = getFileContents(res.files['package.json']);
|
const packageContents = getFileContents(res.files['kibana.json']);
|
||||||
const pkg = JSON.parse(packageContents);
|
const pkg = JSON.parse(packageContents);
|
||||||
|
|
||||||
expect(pkg.kibana.version).toBe('kibana');
|
expect(pkg.version).toBe('master');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('package has correct version', async () => {
|
it('package has correct version', async () => {
|
||||||
|
@ -165,39 +99,26 @@ describe('plugin generator sao integration', () => {
|
||||||
kbnVersion: 'v6.0.0',
|
kbnVersion: 'v6.0.0',
|
||||||
});
|
});
|
||||||
|
|
||||||
const packageContents = getFileContents(res.files['package.json']);
|
const packageContents = getFileContents(res.files['kibana.json']);
|
||||||
const pkg = JSON.parse(packageContents);
|
const pkg = JSON.parse(packageContents);
|
||||||
|
|
||||||
expect(pkg.kibana.version).toBe('v6.0.0');
|
expect(pkg.version).toBe('v6.0.0');
|
||||||
});
|
|
||||||
|
|
||||||
it('package has correct templateVersion', async () => {
|
|
||||||
const res = await sao.mockPrompt(template, {
|
|
||||||
kbnVersion: 'master',
|
|
||||||
});
|
|
||||||
|
|
||||||
const packageContents = getFileContents(res.files['package.json']);
|
|
||||||
const pkg = JSON.parse(packageContents);
|
|
||||||
|
|
||||||
expect(pkg.kibana.templateVersion).toBe(templatePkg.version);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sample app has correct values', async () => {
|
it('sample app has correct values', async () => {
|
||||||
const res = await sao.mockPrompt(template, {
|
const res = await sao.mockPrompt(template, {
|
||||||
generateApp: true,
|
generateApp: true,
|
||||||
generateHack: true,
|
|
||||||
generateApi: true,
|
generateApi: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const contents = getFileContents(res.files['public/app.js']);
|
const contents = getFileContents(res.files['common/index.ts']);
|
||||||
const controllerLine = contents.match('setRootController(.*)')[1];
|
const controllerLine = contents.match("PLUGIN_ID = '(.*)'")[1];
|
||||||
|
|
||||||
expect(controllerLine).toContain('someFancyPlugin');
|
expect(controllerLine).toContain('someFancyPlugin');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('includes dotfiles', async () => {
|
it('includes dotfiles', async () => {
|
||||||
const res = await sao.mockPrompt(template);
|
const res = await sao.mockPrompt(template);
|
||||||
expect(res.files['.gitignore']).toBeTruthy();
|
|
||||||
expect(res.files['.eslintrc.js']).toBeTruthy();
|
expect(res.files['.eslintrc.js']).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"paths": {
|
|
||||||
"<%= camelCase(name) %>": "./"
|
|
||||||
},
|
|
||||||
"translations": [
|
|
||||||
"translations/zh-CN.json"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"styleSheetToCompile": "public/app.scss"
|
|
||||||
}
|
|
|
@ -6,34 +6,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## development
|
## Development
|
||||||
|
|
||||||
See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. Once you have completed that, use the following yarn scripts.
|
See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment.
|
||||||
|
|
||||||
- `yarn kbn bootstrap`
|
|
||||||
|
|
||||||
Install dependencies and crosslink Kibana and all projects/plugins.
|
|
||||||
|
|
||||||
> ***IMPORTANT:*** Use this script instead of `yarn` to install dependencies when switching branches, and re-run it whenever your dependencies change.
|
|
||||||
|
|
||||||
- `yarn start`
|
|
||||||
|
|
||||||
Start kibana and have it include this plugin. You can pass any arguments that you would normally send to `bin/kibana`
|
|
||||||
|
|
||||||
```
|
|
||||||
yarn start --elasticsearch.hosts http://localhost:9220
|
|
||||||
```
|
|
||||||
|
|
||||||
- `yarn build`
|
|
||||||
|
|
||||||
Build a distributable archive of your plugin.
|
|
||||||
|
|
||||||
- `yarn test:browser`
|
|
||||||
|
|
||||||
Run the browser tests in a real web browser.
|
|
||||||
|
|
||||||
- `yarn test:mocha`
|
|
||||||
|
|
||||||
Run the server tests using mocha.
|
|
||||||
|
|
||||||
For more information about any of these commands run `yarn ${task} --help`. For a full list of tasks checkout the `package.json` file, or run `yarn run`.
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
export const PLUGIN_ID = '<%= camelCase(name) %>';
|
||||||
|
export const PLUGIN_NAME = '<%= name %>';
|
31
packages/kbn-plugin-generator/sao_template/template/eslintrc.js
Executable file → Normal file
31
packages/kbn-plugin-generator/sao_template/template/eslintrc.js
Executable file → Normal file
|
@ -1,24 +1,9 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'],
|
extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'],
|
||||||
settings: {
|
<%_ if (!isKibanaPlugin) { -%>
|
||||||
'import/resolver': {
|
rules: {
|
||||||
'@kbn/eslint-import-resolver-kibana': {
|
"@kbn/eslint/require-license-header": "off"
|
||||||
rootPackageName: '<%= snakeCase(name) %>',
|
}
|
||||||
},
|
<%_ } -%>
|
||||||
},
|
};
|
||||||
},
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ['**/public/**/*'],
|
|
||||||
settings: {
|
|
||||||
'import/resolver': {
|
|
||||||
'@kbn/eslint-import-resolver-kibana': {
|
|
||||||
forceNode: false,
|
|
||||||
rootPackageName: '<%= snakeCase(name) %>',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
};
|
|
|
@ -1,6 +0,0 @@
|
||||||
npm-debug.log*
|
|
||||||
node_modules
|
|
||||||
/build/
|
|
||||||
<%_ if (generateScss) { -%>
|
|
||||||
/public/app.css
|
|
||||||
<%_ } -%>
|
|
|
@ -1,89 +0,0 @@
|
||||||
<% if (generateScss) { -%>
|
|
||||||
import { resolve } from 'path';
|
|
||||||
import { existsSync } from 'fs';
|
|
||||||
|
|
||||||
<% } -%>
|
|
||||||
|
|
||||||
<% if (generateApp) { -%>
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
<% } -%>
|
|
||||||
|
|
||||||
<% if (generateApi) { -%>
|
|
||||||
import exampleRoute from './server/routes/example';
|
|
||||||
|
|
||||||
<% } -%>
|
|
||||||
export default function (kibana) {
|
|
||||||
return new kibana.Plugin({
|
|
||||||
require: ['elasticsearch'],
|
|
||||||
name: '<%= snakeCase(name) %>',
|
|
||||||
uiExports: {
|
|
||||||
<%_ if (generateApp) { -%>
|
|
||||||
app: {
|
|
||||||
title: '<%= startCase(name) %>',
|
|
||||||
description: '<%= description %>',
|
|
||||||
main: 'plugins/<%= snakeCase(name) %>/app',
|
|
||||||
},
|
|
||||||
<%_ } -%>
|
|
||||||
<%_ if (generateHack) { -%>
|
|
||||||
hacks: [
|
|
||||||
'plugins/<%= snakeCase(name) %>/hack'
|
|
||||||
],
|
|
||||||
<%_ } -%>
|
|
||||||
<%_ if (generateScss) { -%>
|
|
||||||
styleSheetPaths: [resolve(__dirname, 'public/app.scss'), resolve(__dirname, 'public/app.css')].find(p => existsSync(p)),
|
|
||||||
<%_ } -%>
|
|
||||||
},
|
|
||||||
|
|
||||||
config(Joi) {
|
|
||||||
return Joi.object({
|
|
||||||
enabled: Joi.boolean().default(true),
|
|
||||||
}).default();
|
|
||||||
},
|
|
||||||
<%_ if (generateApi || generateApp) { -%>
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
init(server, options) {
|
|
||||||
<%_ if (generateApp) { -%>
|
|
||||||
const xpackMainPlugin = server.plugins.xpack_main;
|
|
||||||
if (xpackMainPlugin) {
|
|
||||||
const featureId = '<%= snakeCase(name) %>';
|
|
||||||
|
|
||||||
xpackMainPlugin.registerFeature({
|
|
||||||
id: featureId,
|
|
||||||
name: i18n.translate('<%= camelCase(name) %>.featureRegistry.featureName', {
|
|
||||||
defaultMessage: '<%= name %>',
|
|
||||||
}),
|
|
||||||
navLinkId: featureId,
|
|
||||||
icon: 'questionInCircle',
|
|
||||||
app: [featureId, 'kibana'],
|
|
||||||
catalogue: [],
|
|
||||||
privileges: {
|
|
||||||
all: {
|
|
||||||
api: [],
|
|
||||||
savedObject: {
|
|
||||||
all: [],
|
|
||||||
read: [],
|
|
||||||
},
|
|
||||||
ui: ['show'],
|
|
||||||
},
|
|
||||||
read: {
|
|
||||||
api: [],
|
|
||||||
savedObject: {
|
|
||||||
all: [],
|
|
||||||
read: [],
|
|
||||||
},
|
|
||||||
ui: ['show'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
<%_ } -%>
|
|
||||||
|
|
||||||
<%_ if (generateApi) { -%>
|
|
||||||
// Add server routes and initialize the plugin here
|
|
||||||
exampleRoute(server);
|
|
||||||
<%_ } -%>
|
|
||||||
}
|
|
||||||
<%_ } -%>
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"id": "<%= camelCase(name) %>",
|
||||||
|
"version": "<%= kbnVersion %>",
|
||||||
|
"server": <%= hasServer %>,
|
||||||
|
"ui": <%= hasUi %>,
|
||||||
|
"requiredPlugins": ["navigation"],
|
||||||
|
"optionalPlugins": []
|
||||||
|
}
|
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"name": "<%= snakeCase(name) %>",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"description": "<%= description %>",
|
|
||||||
"main": "index.js",
|
|
||||||
"kibana": {
|
|
||||||
"version": "<%= (kbnVersion === 'master') ? 'kibana' : kbnVersion %>",
|
|
||||||
"templateVersion": "<%= templateVersion %>"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"preinstall": "node ../../preinstall_check",
|
|
||||||
"kbn": "node ../../scripts/kbn",
|
|
||||||
"es": "node ../../scripts/es",
|
|
||||||
"lint": "eslint .",
|
|
||||||
"start": "plugin-helpers start",
|
|
||||||
"test:server": "plugin-helpers test:server",
|
|
||||||
"test:browser": "plugin-helpers test:browser",
|
|
||||||
"build": "plugin-helpers build"
|
|
||||||
},
|
|
||||||
<%_ if (generateTranslations) { _%>
|
|
||||||
"dependencies": {
|
|
||||||
"@kbn/i18n": "link:../../packages/kbn-i18n"
|
|
||||||
},
|
|
||||||
<%_ } _%>
|
|
||||||
"devDependencies": {
|
|
||||||
"@elastic/eslint-config-kibana": "link:../../packages/eslint-config-kibana",
|
|
||||||
"@elastic/eslint-import-resolver-kibana": "link:../../packages/kbn-eslint-import-resolver-kibana",
|
|
||||||
"@kbn/expect": "link:../../packages/kbn-expect",
|
|
||||||
"@kbn/plugin-helpers": "link:../../packages/kbn-plugin-helpers",
|
|
||||||
"babel-eslint": "^10.0.1",
|
|
||||||
"eslint": "^5.16.0",
|
|
||||||
"eslint-plugin-babel": "^5.3.0",
|
|
||||||
"eslint-plugin-import": "^2.16.0",
|
|
||||||
"eslint-plugin-jest": "^22.4.1",
|
|
||||||
"eslint-plugin-jsx-a11y": "^6.2.1",
|
|
||||||
"eslint-plugin-mocha": "^5.3.0",
|
|
||||||
"eslint-plugin-no-unsanitized": "^3.0.2",
|
|
||||||
"eslint-plugin-prefer-object-spread": "^1.2.1",
|
|
||||||
"eslint-plugin-react": "^7.12.4"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
import expect from '@kbn/expect';
|
|
||||||
|
|
||||||
describe('suite', () => {
|
|
||||||
it('is a test', () => {
|
|
||||||
expect(true).to.equal(true);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,45 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { uiModules } from 'ui/modules';
|
|
||||||
import chrome from 'ui/chrome';
|
|
||||||
import { render, unmountComponentAtNode } from 'react-dom';
|
|
||||||
<%_ if (generateTranslations) { _%>
|
|
||||||
import { I18nProvider } from '@kbn/i18n/react';
|
|
||||||
<%_ } _%>
|
|
||||||
|
|
||||||
import { Main } from './components/main';
|
|
||||||
|
|
||||||
const app = uiModules.get('apps/<%= camelCase(name) %>');
|
|
||||||
|
|
||||||
app.config($locationProvider => {
|
|
||||||
$locationProvider.html5Mode({
|
|
||||||
enabled: false,
|
|
||||||
requireBase: false,
|
|
||||||
rewriteLinks: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
app.config(stateManagementConfigProvider =>
|
|
||||||
stateManagementConfigProvider.disable()
|
|
||||||
);
|
|
||||||
|
|
||||||
function RootController($scope, $element, $http) {
|
|
||||||
const domNode = $element[0];
|
|
||||||
|
|
||||||
// render react to DOM
|
|
||||||
<%_ if (generateTranslations) { _%>
|
|
||||||
render(
|
|
||||||
<I18nProvider>
|
|
||||||
<Main title="<%= name %>" httpClient={$http} />
|
|
||||||
</I18nProvider>,
|
|
||||||
domNode
|
|
||||||
);
|
|
||||||
<%_ } else { _%>
|
|
||||||
render(<Main title="<%= name %>" httpClient={$http} />, domNode);
|
|
||||||
<%_ } _%>
|
|
||||||
|
|
||||||
// unmount react on controller destroy
|
|
||||||
$scope.$on('$destroy', () => {
|
|
||||||
unmountComponentAtNode(domNode);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
chrome.setRootController('<%= camelCase(name) %>', RootController);
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { AppMountParameters, CoreStart } from '<%= relRoot %>/src/core/public';
|
||||||
|
import { AppPluginStartDependencies } from './types';
|
||||||
|
import { <%= upperCamelCaseName %>App } from './components/app';
|
||||||
|
|
||||||
|
|
||||||
|
export const renderApp = (
|
||||||
|
{ notifications, http }: CoreStart,
|
||||||
|
{ navigation }: AppPluginStartDependencies,
|
||||||
|
{ appBasePath, element }: AppMountParameters
|
||||||
|
) => {
|
||||||
|
ReactDOM.render(
|
||||||
|
<<%= upperCamelCaseName %>App
|
||||||
|
basename={appBasePath}
|
||||||
|
notifications={notifications}
|
||||||
|
http={http}
|
||||||
|
navigation={navigation}
|
||||||
|
/>,
|
||||||
|
element
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => ReactDOM.unmountComponentAtNode(element);
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you 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, { useState } from 'react';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
||||||
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
|
|
||||||
|
import {
|
||||||
|
EuiButton,
|
||||||
|
EuiHorizontalRule,
|
||||||
|
EuiPage,
|
||||||
|
EuiPageBody,
|
||||||
|
EuiPageContent,
|
||||||
|
EuiPageContentBody,
|
||||||
|
EuiPageContentHeader,
|
||||||
|
EuiPageHeader,
|
||||||
|
EuiTitle,
|
||||||
|
EuiText,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
|
||||||
|
import { CoreStart } from '<%= relRoot %>/../src/core/public';
|
||||||
|
import { NavigationPublicPluginStart } from '<%= relRoot %>/../src/plugins/navigation/public';
|
||||||
|
|
||||||
|
import { PLUGIN_ID, PLUGIN_NAME } from '../../common';
|
||||||
|
|
||||||
|
interface <%= upperCamelCaseName %>AppDeps {
|
||||||
|
basename: string;
|
||||||
|
notifications: CoreStart['notifications'];
|
||||||
|
http: CoreStart['http'];
|
||||||
|
navigation: NavigationPublicPluginStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const <%= upperCamelCaseName %>App = ({ basename, notifications, http, navigation }: <%= upperCamelCaseName %>AppDeps) => {
|
||||||
|
// Use React hooks to manage state.
|
||||||
|
const [timestamp, setTimestamp] = useState<string | undefined>();
|
||||||
|
|
||||||
|
const onClickHandler = () => {
|
||||||
|
<%_ if (generateApi) { -%>
|
||||||
|
// Use the core http service to make a response to the server API.
|
||||||
|
http.get('/api/<%= snakeCase(name) %>/example').then(res => {
|
||||||
|
setTimestamp(res.time);
|
||||||
|
// Use the core notifications service to display a success message.
|
||||||
|
notifications.toasts.addSuccess(i18n.translate('<%= camelCase(name) %>.dataUpdated', {
|
||||||
|
defaultMessage: 'Data updated',
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
<%_ } else { -%>
|
||||||
|
setTimestamp(new Date().toISOString());
|
||||||
|
notifications.toasts.addSuccess(PLUGIN_NAME);
|
||||||
|
<%_ } -%>
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render the application DOM.
|
||||||
|
// Note that `navigation.ui.TopNavMenu` is a stateful component exported on the `navigation` plugin's start contract.
|
||||||
|
return (
|
||||||
|
<Router basename={basename}>
|
||||||
|
<I18nProvider>
|
||||||
|
<>
|
||||||
|
<navigation.ui.TopNavMenu appName={ PLUGIN_ID } showSearchBar={true} />
|
||||||
|
<EuiPage restrictWidth="1000px">
|
||||||
|
<EuiPageBody>
|
||||||
|
<EuiPageHeader>
|
||||||
|
<EuiTitle size="l">
|
||||||
|
<h1>
|
||||||
|
<FormattedMessage
|
||||||
|
id="<%= camelCase(name) %>.helloWorldText"
|
||||||
|
defaultMessage="{name}"
|
||||||
|
values={{ name: PLUGIN_NAME }}
|
||||||
|
/>
|
||||||
|
</h1>
|
||||||
|
</EuiTitle>
|
||||||
|
</EuiPageHeader>
|
||||||
|
<EuiPageContent>
|
||||||
|
<EuiPageContentHeader>
|
||||||
|
<EuiTitle>
|
||||||
|
<h2>
|
||||||
|
<FormattedMessage
|
||||||
|
id="<%= camelCase(name) %>.congratulationsTitle"
|
||||||
|
defaultMessage="Congratulations, you have successfully created a new Kibana Plugin!"
|
||||||
|
/>
|
||||||
|
</h2>
|
||||||
|
</EuiTitle>
|
||||||
|
</EuiPageContentHeader>
|
||||||
|
<EuiPageContentBody>
|
||||||
|
<EuiText>
|
||||||
|
<p>
|
||||||
|
<FormattedMessage
|
||||||
|
id="<%= camelCase(name) %>.content"
|
||||||
|
defaultMessage="Look through the generated code and check out the plugin development documentation."
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<EuiHorizontalRule/>
|
||||||
|
<p>
|
||||||
|
<FormattedMessage
|
||||||
|
id="<%= camelCase(name) %>.timestampText"
|
||||||
|
defaultMessage="Last timestamp: {time}"
|
||||||
|
values={{ time: timestamp ? timestamp : 'Unknown' }}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<EuiButton type="primary" size="s" onClick={onClickHandler}>
|
||||||
|
<FormattedMessage id="<%= camelCase(name) %>.buttonText" defaultMessage="<%_ if (generateApi) { -%>Get data<%_ } else { -%>Click me<%_ } -%>" />
|
||||||
|
</EuiButton>
|
||||||
|
</EuiText>
|
||||||
|
</EuiPageContentBody>
|
||||||
|
</EuiPageContent>
|
||||||
|
</EuiPageBody>
|
||||||
|
</EuiPage>
|
||||||
|
</>
|
||||||
|
</I18nProvider>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1 +0,0 @@
|
||||||
export { Main } from './main';
|
|
|
@ -1,97 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
EuiPage,
|
|
||||||
EuiPageHeader,
|
|
||||||
EuiTitle,
|
|
||||||
EuiPageBody,
|
|
||||||
EuiPageContent,
|
|
||||||
EuiPageContentHeader,
|
|
||||||
EuiPageContentBody,
|
|
||||||
EuiText
|
|
||||||
} from '@elastic/eui';
|
|
||||||
<%_ if (generateTranslations) { _%>
|
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
|
||||||
<%_ } _%>
|
|
||||||
|
|
||||||
export class Main extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
/*
|
|
||||||
FOR EXAMPLE PURPOSES ONLY. There are much better ways to
|
|
||||||
manage state and update your UI than this.
|
|
||||||
*/
|
|
||||||
const { httpClient } = this.props;
|
|
||||||
httpClient.get('../api/<%= name %>/example').then((resp) => {
|
|
||||||
this.setState({ time: resp.data.time });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
const { title } = this.props;
|
|
||||||
return (
|
|
||||||
<EuiPage>
|
|
||||||
<EuiPageBody>
|
|
||||||
<EuiPageHeader>
|
|
||||||
<EuiTitle size="l">
|
|
||||||
<h1>
|
|
||||||
<%_ if (generateTranslations) { _%>
|
|
||||||
<FormattedMessage
|
|
||||||
id="<%= camelCase(name) %>.helloWorldText"
|
|
||||||
defaultMessage="{title} Hello World!"
|
|
||||||
values={{ title }}
|
|
||||||
/>
|
|
||||||
<%_ } else { _%>
|
|
||||||
{title} Hello World!
|
|
||||||
<%_ } _%>
|
|
||||||
</h1>
|
|
||||||
</EuiTitle>
|
|
||||||
</EuiPageHeader>
|
|
||||||
<EuiPageContent>
|
|
||||||
<EuiPageContentHeader>
|
|
||||||
<EuiTitle>
|
|
||||||
<h2>
|
|
||||||
<%_ if (generateTranslations) { _%>
|
|
||||||
<FormattedMessage
|
|
||||||
id="<%= camelCase(name) %>.congratulationsTitle"
|
|
||||||
defaultMessage="Congratulations"
|
|
||||||
/>
|
|
||||||
<%_ } else { _%>
|
|
||||||
Congratulations
|
|
||||||
<%_ } _%>
|
|
||||||
</h2>
|
|
||||||
</EuiTitle>
|
|
||||||
</EuiPageContentHeader>
|
|
||||||
<EuiPageContentBody>
|
|
||||||
<EuiText>
|
|
||||||
<h3>
|
|
||||||
<%_ if (generateTranslations) { _%>
|
|
||||||
<FormattedMessage
|
|
||||||
id="<%= camelCase(name) %>.congratulationsText"
|
|
||||||
defaultMessage="You have successfully created your first Kibana Plugin!"
|
|
||||||
/>
|
|
||||||
<%_ } else { _%>
|
|
||||||
You have successfully created your first Kibana Plugin!
|
|
||||||
<%_ } _%>
|
|
||||||
</h3>
|
|
||||||
<p>
|
|
||||||
<%_ if (generateTranslations) { _%>
|
|
||||||
<FormattedMessage
|
|
||||||
id="<%= camelCase(name) %>.serverTimeText"
|
|
||||||
defaultMessage="The server time (via API call) is {time}"
|
|
||||||
values={{ time: this.state.time || 'NO API CALL YET' }}
|
|
||||||
/>
|
|
||||||
<%_ } else { _%>
|
|
||||||
The server time (via API call) is {this.state.time || 'NO API CALL YET'}
|
|
||||||
<%_ } _%>
|
|
||||||
</p>
|
|
||||||
</EuiText>
|
|
||||||
</EuiPageContentBody>
|
|
||||||
</EuiPageContent>
|
|
||||||
</EuiPageBody>
|
|
||||||
</EuiPage>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
import $ from 'jquery';
|
|
||||||
|
|
||||||
$(document.body).on('keypress', function (event) {
|
|
||||||
if (event.which === 58) {
|
|
||||||
alert('boo!');
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
<%_ if (hasScss) { -%>
|
||||||
|
import './index.scss';
|
||||||
|
<%_ } -%>
|
||||||
|
|
||||||
|
import { <%= upperCamelCaseName %>Plugin } from './plugin';
|
||||||
|
|
||||||
|
// This exports static code and TypeScript types,
|
||||||
|
// as well as, Kibana Platform `plugin()` initializer.
|
||||||
|
export function plugin() {
|
||||||
|
return new <%= upperCamelCaseName %>Plugin();
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
<%= upperCamelCaseName %>PluginSetup,
|
||||||
|
<%= upperCamelCaseName %>PluginStart,
|
||||||
|
} from './types';
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '<%= relRoot %>/src/core/public';
|
||||||
|
import { <%= upperCamelCaseName %>PluginSetup, <%= upperCamelCaseName %>PluginStart, AppPluginStartDependencies } from './types';
|
||||||
|
import { PLUGIN_NAME } from '../common';
|
||||||
|
|
||||||
|
export class <%= upperCamelCaseName %>Plugin
|
||||||
|
implements Plugin<<%= upperCamelCaseName %>PluginSetup, <%= upperCamelCaseName %>PluginStart> {
|
||||||
|
|
||||||
|
public setup(core: CoreSetup): <%= upperCamelCaseName %>PluginSetup {
|
||||||
|
// Register an application into the side navigation menu
|
||||||
|
core.application.register({
|
||||||
|
id: '<%= camelCase(name) %>',
|
||||||
|
title: PLUGIN_NAME,
|
||||||
|
async mount(params: AppMountParameters) {
|
||||||
|
// Load application bundle
|
||||||
|
const { renderApp } = await import('./application');
|
||||||
|
// Get start services as specified in kibana.json
|
||||||
|
const [coreStart, depsStart] = await core.getStartServices();
|
||||||
|
// Render the application
|
||||||
|
return renderApp(coreStart, depsStart as AppPluginStartDependencies, params);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return methods that should be available to other plugins
|
||||||
|
return {
|
||||||
|
getGreeting() {
|
||||||
|
return i18n.translate('<%= camelCase(name) %>.greetingText', {
|
||||||
|
defaultMessage: 'Hello from {name}!',
|
||||||
|
values: {
|
||||||
|
name: PLUGIN_NAME,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public start(core: CoreStart): <%= upperCamelCaseName %>PluginStart {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public stop() {}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { NavigationPublicPluginStart } from '<%= relRoot %>/src/plugins/navigation/public';
|
||||||
|
|
||||||
|
export interface <%= upperCamelCaseName %>PluginSetup {
|
||||||
|
getGreeting: () => string;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
|
export interface <%= upperCamelCaseName %>PluginStart {}
|
||||||
|
|
||||||
|
export interface AppPluginStartDependencies {
|
||||||
|
navigation: NavigationPublicPluginStart
|
||||||
|
};
|
|
@ -1,7 +0,0 @@
|
||||||
import expect from '@kbn/expect';
|
|
||||||
|
|
||||||
describe('suite', () => {
|
|
||||||
it('is a test', () => {
|
|
||||||
expect(true).to.equal(true);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { PluginInitializerContext } from '<%= relRoot %>/src/core/server';
|
||||||
|
import { <%= upperCamelCaseName %>Plugin } from './plugin';
|
||||||
|
|
||||||
|
|
||||||
|
// This exports static code and TypeScript types,
|
||||||
|
// as well as, Kibana Platform `plugin()` initializer.
|
||||||
|
|
||||||
|
export function plugin(initializerContext: PluginInitializerContext) {
|
||||||
|
return new <%= upperCamelCaseName %>Plugin(initializerContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
<%= upperCamelCaseName %>PluginSetup,
|
||||||
|
<%= upperCamelCaseName %>PluginStart,
|
||||||
|
} from './types';
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '<%= relRoot %>/src/core/server';
|
||||||
|
|
||||||
|
import { <%= upperCamelCaseName %>PluginSetup, <%= upperCamelCaseName %>PluginStart } from './types';
|
||||||
|
import { defineRoutes } from './routes';
|
||||||
|
|
||||||
|
export class <%= upperCamelCaseName %>Plugin
|
||||||
|
implements Plugin<<%= upperCamelCaseName %>PluginSetup, <%= upperCamelCaseName %>PluginStart> {
|
||||||
|
private readonly logger: Logger;
|
||||||
|
|
||||||
|
constructor(initializerContext: PluginInitializerContext) {
|
||||||
|
this.logger = initializerContext.logger.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setup(core: CoreSetup) {
|
||||||
|
this.logger.debug('<%= name %>: Setup');
|
||||||
|
const router = core.http.createRouter();
|
||||||
|
|
||||||
|
// Register server side APIs
|
||||||
|
defineRoutes(router);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public start(core: CoreStart) {
|
||||||
|
this.logger.debug('<%= name %>: Started');
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public stop() {}
|
||||||
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
export default function (server) {
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
path: '/api/<%= name %>/example',
|
|
||||||
method: 'GET',
|
|
||||||
handler() {
|
|
||||||
return { time: (new Date()).toISOString() };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { IRouter } from '<%= relRoot %>/../src/core/server';
|
||||||
|
|
||||||
|
export function defineRoutes(router: IRouter) {
|
||||||
|
router.get(
|
||||||
|
{
|
||||||
|
path: '/api/<%= snakeCase(name) %>/example',
|
||||||
|
validate: false,
|
||||||
|
},
|
||||||
|
async (context, request, response) => {
|
||||||
|
return response.ok({
|
||||||
|
body: {
|
||||||
|
time: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
|
export interface <%= upperCamelCaseName %>PluginSetup {}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
|
export interface <%= upperCamelCaseName %>PluginStart {}
|
|
@ -1,84 +0,0 @@
|
||||||
{
|
|
||||||
"formats": {
|
|
||||||
"number": {
|
|
||||||
"currency": {
|
|
||||||
"style": "currency"
|
|
||||||
},
|
|
||||||
"percent": {
|
|
||||||
"style": "percent"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"date": {
|
|
||||||
"short": {
|
|
||||||
"month": "numeric",
|
|
||||||
"day": "numeric",
|
|
||||||
"year": "2-digit"
|
|
||||||
},
|
|
||||||
"medium": {
|
|
||||||
"month": "short",
|
|
||||||
"day": "numeric",
|
|
||||||
"year": "numeric"
|
|
||||||
},
|
|
||||||
"long": {
|
|
||||||
"month": "long",
|
|
||||||
"day": "numeric",
|
|
||||||
"year": "numeric"
|
|
||||||
},
|
|
||||||
"full": {
|
|
||||||
"weekday": "long",
|
|
||||||
"month": "long",
|
|
||||||
"day": "numeric",
|
|
||||||
"year": "numeric"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"time": {
|
|
||||||
"short": {
|
|
||||||
"hour": "numeric",
|
|
||||||
"minute": "numeric"
|
|
||||||
},
|
|
||||||
"medium": {
|
|
||||||
"hour": "numeric",
|
|
||||||
"minute": "numeric",
|
|
||||||
"second": "numeric"
|
|
||||||
},
|
|
||||||
"long": {
|
|
||||||
"hour": "numeric",
|
|
||||||
"minute": "numeric",
|
|
||||||
"second": "numeric",
|
|
||||||
"timeZoneName": "short"
|
|
||||||
},
|
|
||||||
"full": {
|
|
||||||
"hour": "numeric",
|
|
||||||
"minute": "numeric",
|
|
||||||
"second": "numeric",
|
|
||||||
"timeZoneName": "short"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"relative": {
|
|
||||||
"years": {
|
|
||||||
"units": "year"
|
|
||||||
},
|
|
||||||
"months": {
|
|
||||||
"units": "month"
|
|
||||||
},
|
|
||||||
"days": {
|
|
||||||
"units": "day"
|
|
||||||
},
|
|
||||||
"hours": {
|
|
||||||
"units": "hour"
|
|
||||||
},
|
|
||||||
"minutes": {
|
|
||||||
"units": "minute"
|
|
||||||
},
|
|
||||||
"seconds": {
|
|
||||||
"units": "second"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"messages": {
|
|
||||||
"<%= camelCase(name) %>.congratulationsText": "您已经成功创建第一个 Kibana 插件。",
|
|
||||||
"<%= camelCase(name) %>.congratulationsTitle": "恭喜!",
|
|
||||||
"<%= camelCase(name) %>.helloWorldText": "{title} 您好,世界!",
|
|
||||||
"<%= camelCase(name) %>.serverTimeText": "服务器时间(通过 API 调用)为 {time}"
|
|
||||||
}
|
|
||||||
}
|
|
5
packages/kbn-plugin-generator/tsconfig.json
Normal file
5
packages/kbn-plugin-generator/tsconfig.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"include": ["**/*", "index.js.d.ts"],
|
||||||
|
"exclude": ["sao_template/template/*"]
|
||||||
|
}
|
Loading…
Reference in a new issue