Moves styleSheetPath to uiExports (#23007)

This was previously defined in uiExports.app, which limited plugins which are not an app of providing a stylesheet. This allows any plugin to define a stylesheet which will be available on page load.
This commit is contained in:
Tyler Smalley 2018-09-20 19:01:06 -07:00 committed by GitHub
parent 991e805669
commit 0e5fd324b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 257 additions and 166 deletions

View file

@ -12,9 +12,6 @@ export default function (kibana) {
title: '<%= startCase(name) %>',
description: '<%= description %>',
main: 'plugins/<%= snakeCase(name) %>/app',
<%_ if (generateScss) { -%>
styleSheetPath: require('path').resolve(__dirname, 'public/app.scss'),
<%_ } -%>
},
<%_ } -%>
<%_ if (generateHack) { -%>
@ -22,6 +19,9 @@ export default function (kibana) {
'plugins/<%= snakeCase(name) %>/hack'
]
<%_ } -%>
<%_ if (generateScss) { -%>
styleSheetPaths: require('path').resolve(__dirname, 'public/app.scss'),
<%_ } -%>
},
config(Joi) {

View file

@ -66,9 +66,8 @@ export default function (kibana) {
listed: false,
description: 'the kibana you know and love',
main: 'plugins/kibana/kibana',
styleSheetPath: `${__dirname}/public/index.scss`,
},
styleSheetPaths: `${__dirname}/public/index.scss`,
links: [
{
id: 'kibana:discover',

View file

@ -25,8 +25,8 @@ export default function (kibana) {
main: 'plugins/status_page/status_page',
hidden: true,
url: '/status',
styleSheetPath: `${__dirname}/public/index.scss`
}
},
styleSheetPaths: `${__dirname}/public/index.scss`,
}
});
}

View file

@ -1,6 +1,6 @@
@import 'ui/public/styles/styling_constants';
// SASSTODO: Remove when K7 applies background color to body
.stsPage {
#status_page-app .stsPage {
min-height: 100vh;
}

View file

@ -20,6 +20,7 @@
import { toArray } from 'rxjs/operators';
import { buildAll } from '../../../server/sass/build_all';
import { findPluginSpecs } from '../../../plugin_discovery/find_plugin_specs';
import { collectUiExports } from '../../../ui/ui_exports/collect_ui_exports';
export const TranspileScssTask = {
description: 'Transpiling SCSS to CSS',
@ -30,9 +31,10 @@ export const TranspileScssTask = {
const { spec$ } = findPluginSpecs({ plugins: { scanDirs, paths } });
const enabledPlugins = await spec$.pipe(toArray()).toPromise();
const uiExports = collectUiExports(enabledPlugins);
try {
const bundles = await buildAll(enabledPlugins);
const bundles = await buildAll(uiExports.styleSheetPaths);
bundles.forEach(bundle => log.info(`Compiled SCSS: ${bundle.source}`));
} catch (error) {
const { message, line, file } = error;

View file

@ -18,16 +18,15 @@
*/
import { Build } from './build';
import { collectUiExports } from '../../ui/ui_exports';
export async function buildAll(enabledPluginSpecs) {
const { uiAppSpecs = [] } = collectUiExports(enabledPluginSpecs);
const bundles = await Promise.all(uiAppSpecs.map(async uiAppSpec => {
if (!uiAppSpec.styleSheetPath) {
export async function buildAll(styleSheets = []) {
const bundles = await Promise.all(styleSheets.map(async styleSheet => {
if (!styleSheet.localPath.endsWith('.scss')) {
return;
}
const bundle = new Build(uiAppSpec.styleSheetPath);
const bundle = new Build(styleSheet.localPath);
await bundle.build();
return bundle;

View file

@ -24,7 +24,6 @@ export async function sassMixin(kbnServer, server, config) {
return;
}
/**
* Build assets
*
@ -40,7 +39,7 @@ export async function sassMixin(kbnServer, server, config) {
let trackedFiles = new Set();
try {
scssBundles = await buildAll(kbnServer.pluginSpecs);
scssBundles = await buildAll(kbnServer.uiExports.styleSheetPaths);
scssBundles.forEach(bundle => {
bundle.includedFiles.forEach(file => trackedFiles.add(file));

View file

@ -83,6 +83,9 @@ const waitForBootstrap = new Promise(resolve => {
require('uiExports/chromeNavControls');
require('uiExports/hacks');
// sets attribute on body for stylesheet sandboxing
document.body.setAttribute('id', `${internals.app.id}-app`);
chrome.setupAngular();
targetDomElement.setAttribute('id', 'kibana-body');
targetDomElement.setAttribute('kbn-chrome', 'true');

View file

@ -90,10 +90,6 @@ describe('ui apps / UiApp', () => {
expect(app.getMainModuleId()).to.be(undefined);
});
it('has no styleSheetPath', () => {
expect(app.getStyleSheetUrlPath()).to.be(undefined);
});
it('has a mostly empty JSON representation', () => {
expect(JSON.parse(JSON.stringify(app))).to.eql({
id: spec.id,
@ -312,15 +308,4 @@ describe('ui apps / UiApp', () => {
expect(app.getMainModuleId()).to.be('bar');
});
});
describe('#getStyleSheetUrlPath', () => {
it('returns public path to styleSheetPath', () => {
const app = createUiApp(
createStubUiAppSpec({ pluginId: 'foo', id: 'foo', styleSheetPath: '/bar/public/baz/style.scss' }),
createStubKbnServer({ plugins: [{ id: 'foo', publicDir: '/bar/public' }] })
);
expect(app.getStyleSheetUrlPath()).to.eql('plugins/foo/baz/style.css');
});
});
});

View file

@ -17,7 +17,6 @@
* under the License.
*/
import path from 'path';
import { UiNavLink } from '../ui_nav_links';
export class UiApp {
@ -34,7 +33,6 @@ export class UiApp {
linkToLastSubUrl,
listed,
url = `/app/${id}`,
styleSheetPath,
} = spec;
if (!id) {
@ -53,7 +51,6 @@ export class UiApp {
this._url = url;
this._pluginId = pluginId;
this._kbnServer = kbnServer;
this._styleSheetPath = styleSheetPath;
if (this._pluginId && !this._getPlugin()) {
throw new Error(`Unknown plugin id "${this._pluginId}"`);
@ -105,25 +102,6 @@ export class UiApp {
return this._main;
}
getStyleSheetUrlPath() {
if (!this._styleSheetPath) {
return;
}
const plugin = this._getPlugin();
// get the path of the stylesheet relative to the public dir for the plugin
let relativePath = path.relative(plugin.publicDir, this._styleSheetPath);
// replace back slashes on windows
relativePath = relativePath.split('\\').join('/');
// replace the extension of relativePath to be .css
relativePath = relativePath.slice(0, -path.extname(relativePath).length) + '.css';
return `plugins/${plugin.id}/${relativePath}`;
}
_getPlugin() {
const pluginId = this._pluginId;
const { plugins } = this._kbnServer;
@ -142,7 +120,6 @@ export class UiApp {
main: this._main,
navLink: this._navLink,
linkToLastSubUrl: this._linkToLastSubUrl,
styleSheetPath: this._styleSheetPath,
};
}
}

View file

@ -37,6 +37,8 @@ export const UI_EXPORT_DEFAULTS = {
translationPaths: [],
styleSheetPaths: [],
appExtensions: {
fieldFormatEditors: [
'ui/field_editor/components/field_format_editor/register'

View file

@ -68,6 +68,10 @@ export {
links,
} from './ui_nav_links';
export {
styleSheetPaths
} from './style_sheet_paths';
export {
uiSettingDefaults,
} from './ui_settings';

View file

@ -0,0 +1,67 @@
/*
* 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 path from 'path';
import { flatConcatAtType } from './reduce';
import { mapSpec, wrap } from './modify_reduce';
const OK_EXTNAMES = ['.css', '.scss'];
function normalize(localPath, type, pluginSpec) {
const pluginId = pluginSpec.getId();
const publicDir = pluginSpec.getPublicDir();
const extname = path.extname(localPath);
if (!OK_EXTNAMES.includes(extname)) {
throw new Error(
`[plugin:${pluginId}] uiExports.styleSheetPaths supported extensions [${OK_EXTNAMES.join(', ')}], got "${extname}"`
);
}
if (!path.isAbsolute(localPath)) {
throw new Error(
`[plugin:${pluginId}] uiExports.styleSheetPaths must be an absolute path, got "${localPath}"`
);
}
if (!localPath.startsWith(publicDir)) {
throw new Error(
`[plugin:${pluginId}] uiExports.styleSheetPaths must be child of publicDir [${publicDir}]`
);
}
// get the path of the stylesheet relative to the public dir for the plugin
let relativePath = path.relative(publicDir, localPath);
// replace back slashes on windows
relativePath = relativePath.split('\\').join('/');
// replace the extension of relativePath to be .css
// publicPath will always point to the css file
relativePath = relativePath.slice(0, -extname.length) + '.css';
const publicPath = `plugins/${pluginSpec.getId()}/${relativePath}`;
return {
localPath,
publicPath
};
}
export const styleSheetPaths = wrap(mapSpec(normalize), flatConcatAtType);

View file

@ -0,0 +1,60 @@
/*
* 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 { styleSheetPaths } from './style_sheet_paths';
describe('uiExports.styleSheetPaths', () => {
const pluginSpec = {
getId: () => 'test',
getPublicDir: () => '/kibana/public'
};
it('does not support relative paths', () => {
expect(() => styleSheetPaths([], 'public/bar.css', 'styleSheetPaths', pluginSpec))
.toThrowError('[plugin:test] uiExports.styleSheetPaths must be an absolute path, got "public/bar.css"');
});
it('path must be child of public path', () => {
expect(() => styleSheetPaths([], '/another/public/bar.css', 'styleSheetPaths', pluginSpec))
.toThrowError('[plugin:test] uiExports.styleSheetPaths must be child of publicDir [/kibana/public]');
});
it('only supports css or scss extensions', () => {
expect(() => styleSheetPaths([], '/kibana/public/bar.bad', 'styleSheetPaths', pluginSpec))
.toThrowError('[plugin:test] uiExports.styleSheetPaths supported extensions [.css, .scss], got ".bad"');
});
it('provides publicPath for scss extensions', () => {
const localPath = '/kibana/public/bar.scss';
const uiExports = styleSheetPaths([], localPath, 'styleSheetPaths', pluginSpec);
expect(uiExports.styleSheetPaths).toHaveLength(1);
expect(uiExports.styleSheetPaths[0].localPath).toEqual(localPath);
expect(uiExports.styleSheetPaths[0].publicPath).toEqual('plugins/test/bar.css');
});
it('provides publicPath for css extensions', () => {
const localPath = '/kibana/public/bar.css';
const uiExports = styleSheetPaths([], localPath, 'styleSheetPaths', pluginSpec);
expect(uiExports.styleSheetPaths).toHaveLength(1);
expect(uiExports.styleSheetPaths[0].localPath).toEqual(localPath);
expect(uiExports.styleSheetPaths[0].publicPath).toEqual('plugins/test/bar.css');
});
});

View file

@ -17,7 +17,6 @@
* under the License.
*/
import { isAbsolute, normalize } from 'path';
import { flatConcatAtType } from './reduce';
import { alias, mapSpec, wrap } from './modify_reduce';
@ -48,14 +47,6 @@ function applySpecDefaults(spec, type, pluginSpec) {
);
}
const styleSheetPath = spec.styleSheetPath ? normalize(spec.styleSheetPath) : undefined;
if (styleSheetPath && (!isAbsolute(styleSheetPath) || !styleSheetPath.startsWith(pluginSpec.getPublicDir()))) {
throw new Error(
`[plugin:${pluginId}] uiExports.app.styleSheetPath must be an absolute path within the public directory`
);
}
return {
pluginId,
id,
@ -68,7 +59,6 @@ function applySpecDefaults(spec, type, pluginSpec) {
linkToLastSubUrl,
listed,
url,
styleSheetPath,
};
}

View file

@ -1,57 +1,58 @@
window.onload = function () {
function createAnchor(href) {
var anchor = document.createElement('a');
anchor.setAttribute('href', href);
return anchor.href;
}
var files = [
createAnchor('{{bundlePath}}/vendors.style.css'),
createAnchor('{{bundlePath}}/commons.style.css'),
createAnchor('{{bundlePath}}/{{appId}}.style.css'),
createAnchor('{{bundlePath}}/vendors.bundle.js'),
createAnchor('{{bundlePath}}/commons.bundle.js'),
createAnchor('{{bundlePath}}/{{appId}}.bundle.js')
'{{bundlePath}}/vendors.bundle.js',
'{{bundlePath}}/commons.bundle.js',
'{{bundlePath}}/{{appId}}.bundle.js'
];
if ('{{styleSheetPath}}' !== '') {
files.push(createAnchor('{{styleSheetPath}}'));
var failure = function () {
// make subsequent calls to failure() noop
failure = function () {};
var err = document.createElement('h1');
err.style['color'] = 'white';
err.style['font-family'] = 'monospace';
err.style['text-align'] = 'center';
err.style['background'] = '#F44336';
err.style['padding'] = '25px';
err.innerText = '{{i18n 'common.ui.welcomeError' '{"defaultMessage": "Kibana did not load properly. Check the server output for more information."}'}}';
document.body.innerHTML = err.outerHTML;
}
function loadStyleSheet(path) {
var dom = document.createElement('link');
dom.addEventListener('error', failure);
dom.setAttribute('rel', 'stylesheet');
dom.setAttribute('href', path);
document.head.appendChild(dom);
}
function createJavascriptElement(path) {
var dom = document.createElement('script');
dom.setAttribute('async', '');
dom.addEventListener('error', failure);
dom.setAttribute('src', file);
dom.addEventListener('load', next);
document.head.appendChild(dom);
}
{{#each styleSheetPaths}}
loadStyleSheet('{{this}}');
{{/each}}
(function next() {
var file = files.shift();
if (!file) return;
var failure = function () {
// make subsequent calls to failure() noop
failure = function () {};
var dom = document.createElement('script');
var err = document.createElement('h1');
err.style['color'] = 'white';
err.style['font-family'] = 'monospace';
err.style['text-align'] = 'center';
err.style['background'] = '#F44336';
err.style['padding'] = '25px';
err.innerText = '{{i18n 'common.ui.welcomeError' '{"defaultMessage": "Kibana did not load properly. Check the server output for more information."}'}}';
document.body.innerHTML = err.outerHTML;
}
var type = /\.js(\?.+)?$/.test(file) ? 'script' : 'link';
var dom = document.createElement(type);
dom.setAttribute('async', '');
dom.addEventListener('error', failure);
if (type === 'script') {
dom.setAttribute('src', file);
dom.addEventListener('load', next);
document.head.appendChild(dom);
} else {
dom.setAttribute('rel', 'stylesheet');
dom.setAttribute('href', file);
document.head.appendChild(dom);
next();
}
dom.setAttribute('src', file);
dom.addEventListener('load', next);
document.head.appendChild(dom);
}());
};

View file

@ -63,11 +63,18 @@ export function uiRenderMixin(kbnServer, server, config) {
}
const basePath = config.get('server.basePath');
const bundlePath = `${basePath}/bundles`;
const styleSheetPaths = [
`${bundlePath}/vendors.style.css`,
`${bundlePath}/commons.style.css`,
`${bundlePath}/${app.getId()}.style.css`,
].concat(kbnServer.uiExports.styleSheetPaths.map(path => `${basePath}/${path.publicPath}`).reverse());
const bootstrap = new AppBootstrap({
templateData: {
appId: app.getId(),
bundlePath: `${basePath}/bundles`,
styleSheetPath: app.getStyleSheetUrlPath() ? `${basePath}/${app.getStyleSheetUrlPath()}` : null,
bundlePath,
styleSheetPaths,
},
translations: await server.getUiTranslations()
});

View file

@ -21,8 +21,8 @@ export function canvas(kibana) {
description: 'Data driven workpads',
icon: 'plugins/canvas/icon.svg',
main: 'plugins/canvas/app',
styleSheetPath: `${__dirname}/public/style/index.scss`,
},
styleSheetPaths: `${__dirname}/public/style/index.scss`,
hacks: [
// window.onerror override
'plugins/canvas/lib/window_error_handler.js',

View file

@ -3,28 +3,30 @@
when the UI framework implements everything we need
*/
// Give buttons some room to the right
.euiAccordion__childWrapper {
overflow-x: hidden;
}
.clickable {
cursor: pointer;
}
// Temp EUI issue.
.canvasPalettePicker__swatchesPopover {
display: block;
.euiPopover__anchor {
display: block;
#canvas-app {
// Give buttons some room to the right
.euiAccordion__childWrapper {
overflow-x: hidden;
}
}
// TODO: remove if fix is provided for https://github.com/elastic/eui/issues/833
// temp fix for SVGs not appearing in the EuiImage fullscreen view
.euiImageFullScreen {
min-width: 100px;
.clickable {
cursor: pointer;
}
// Temp EUI issue.
.canvasPalettePicker__swatchesPopover {
display: block;
.euiPopover__anchor {
display: block;
}
}
// TODO: remove if fix is provided for https://github.com/elastic/eui/issues/833
// temp fix for SVGs not appearing in the EuiImage fullscreen view
.euiImageFullScreen {
min-width: 100px;
}
}
// Canvas has a few modals that necessitate a fixed height due to robust content
@ -36,4 +38,4 @@
width: 75vw;
height: 75vh;
max-height: 680px; // limit for large screen displays
}
}

View file

@ -53,4 +53,4 @@
@import '../components/toolbar/tray/tray';
@import '../components/workpad/workpad';
@import '../components/workpad_loader/workpad_dropzone/workpad_dropzone';
@import '../components/workpad_page/workpad_page';
@import '../components/workpad_page/workpad_page';

View file

@ -29,7 +29,7 @@
max-width: 100%;
}
body {
#canvas-app body {
overflow-y: hidden;
// Todo: replace this with EuiToast

View file

@ -40,7 +40,6 @@ export function dashboardMode(kibana) {
hidden: true,
description: 'view dashboards',
main: 'plugins/dashboard_mode/dashboard_viewer',
styleSheetPath: `${__dirname}/public/index.scss`,
links: [
{
id: 'kibana:dashboard',
@ -52,7 +51,7 @@ export function dashboardMode(kibana) {
icon: 'plugins/kibana/assets/dashboard.svg',
}
],
}
},
},
config(Joi) {

View file

@ -1,3 +0,0 @@
@import 'ui/public/styles/styling_constants';
@import 'core_plugins/kibana/public/dashboard/index';

View file

@ -23,8 +23,8 @@ export function graph(kibana) {
icon: 'plugins/graph/icon.png',
description: 'Graph exploration',
main: 'plugins/graph/app',
styleSheetPath: `${__dirname}/public/index.scss`,
},
styleSheetPaths: `${__dirname}/public/index.scss`,
hacks: ['plugins/graph/hacks/toggle_app_link_in_nav'],
home: ['plugins/graph/register_feature'],
mappings

View file

@ -38,7 +38,6 @@ export const ml = (kibana) => {
description: 'Machine Learning for the Elastic Stack',
icon: 'plugins/ml/ml.svg',
main: 'plugins/ml/app',
styleSheetPath: `${__dirname}/public/index.scss`,
},
hacks: ['plugins/ml/hacks/toggle_app_link_in_nav'],
home: ['plugins/ml/register_feature']

View file

@ -1,3 +0,0 @@
@import 'ui/public/styles/styling_constants';
@import 'core_plugins/kibana/public/visualize/index';

View file

@ -1,26 +1,28 @@
// SASSTODO: We need background colors till more of Kibana is converted
.tab-no-data, .tab-overview, .tab-license {
background: $euiColorLightestShade;
}
#monitoring-app {
// SASSTODO: We need background colors till more of Kibana is converted
.tab-no-data, .tab-overview, .tab-license {
background: $euiColorLightestShade;
}
// SASSTODO: PUI tooltips can be replaced with EuiToolTip
.pui-tooltip-inner {
font-size: $euiFontSizeXS;
}
// SASSTODO: PUI tooltips can be replaced with EuiToolTip
.pui-tooltip-inner {
font-size: $euiFontSizeXS;
}
.monitoring-tooltip__trigger,
.monitoring-tooltip__trigger:hover {
color: $euiTextColor;
}
.monitoring-tooltip__trigger,
.monitoring-tooltip__trigger:hover {
color: $euiTextColor;
}
// SASSTODO: Remove font awesome
.betaIcon {
color: $euiColorDarkShade;
}
// SASSTODO: Remove font awesome
.betaIcon {
color: $euiColorDarkShade;
}
// SASSTOD: Remove this, it comes from xpack_main/styles/main.less and was imported into monitoring
.xpack-breadcrumbs {
min-height: 37px;
padding: 8px 10px;
margin: 0;
// SASSTOD: Remove this, it comes from xpack_main/styles/main.less and was imported into monitoring
.xpack-breadcrumbs {
min-height: 37px;
padding: 8px 10px;
margin: 0;
}
}

View file

@ -18,7 +18,6 @@ export const uiExports = {
icon: 'plugins/monitoring/icons/monitoring.svg',
linkToLastSubUrl: false,
main: 'plugins/monitoring/monitoring',
styleSheetPath: `${__dirname}/public/index.scss`,
},
injectDefaultVars(server) {
const config = server.config();
@ -27,5 +26,6 @@ export const uiExports = {
};
},
hacks: [ 'plugins/monitoring/hacks/toggle_app_link_in_nav' ],
home: [ 'plugins/monitoring/register_feature' ]
home: [ 'plugins/monitoring/register_feature' ],
styleSheetPaths: `${__dirname}/public/index.scss`,
};