[7.x] [Discover] Remove Angular from doc viewer (#109368) (#109786)

* [Discover] Remove Angular from doc viewer (#109368)

* Remove angular from doc viewer

* Remove types

* Remove plugin_functional for angular doc_view, since testing angular is no longer necessary

* Update doc_views.ts

* Delete test/plugin_functional/test_suites/doc_views directory

* Update config.ts

Co-authored-by: Matthias Wilhelm <matthias.wilhelm@elastic.co>
Co-authored-by: Matthias Wilhelm <ankertal@gmail.com>
This commit is contained in:
Kibana Machine 2021-08-26 15:44:38 -04:00 committed by GitHub
parent 4642866c85
commit 9e06a9afcc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 4 additions and 287 deletions

View file

@ -1052,8 +1052,7 @@
"description": [],
"signature": [
"{ addDocView(docViewRaw: ComponentDocViewInput | ",
"RenderDocViewInput",
" | DirectiveDocViewInput | ",
"RenderDocViewInput |",
"DocViewInputFn",
"): void; }"
],
@ -1391,4 +1390,4 @@
],
"objects": []
}
}
}

View file

@ -1,85 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { auto, IController } from 'angular';
import React from 'react';
import { render } from 'react-dom';
import angular, { ICompileService } from 'angular';
import { DocViewRenderProps, AngularScope, AngularDirective } from './doc_views_types';
import { DocViewerError } from '../components/doc_viewer/doc_viewer_render_error';
/**
* Compiles and injects the give angular template into the given dom node
* returns a function to cleanup the injected angular element
*/
export async function injectAngularElement(
domNode: Element,
template: string,
scopeProps: DocViewRenderProps,
Controller: IController,
getInjector: () => Promise<auto.IInjectorService>
): Promise<() => void> {
const $injector = await getInjector();
const rootScope: AngularScope = $injector.get('$rootScope');
const $compile: ICompileService = $injector.get('$compile');
const newScope = Object.assign(rootScope.$new(), scopeProps);
if (typeof Controller === 'function') {
// when a controller is defined, expose the value it produces to the view as `$ctrl`
// see: https://docs.angularjs.org/api/ng/provider/$compileProvider#component
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(newScope as any).$ctrl = $injector.instantiate(Controller, {
$scope: newScope,
});
}
const $target = angular.element(domNode);
const $element = angular.element(template);
newScope.$apply(() => {
const linkFn = $compile($element);
$target.empty().append($element);
linkFn(newScope);
});
return () => {
newScope.$destroy();
};
}
/**
* Converts a given legacy angular directive to a render function
* for usage in a react component. Note that the rendering is async
*/
export function convertDirectiveToRenderFn(
directive: AngularDirective,
getInjector: () => Promise<auto.IInjectorService>
) {
return (domNode: Element, props: DocViewRenderProps) => {
let rejected = false;
const cleanupFnPromise = injectAngularElement(
domNode,
directive.template,
props,
directive.controller,
getInjector
);
cleanupFnPromise.catch((e) => {
rejected = true;
render(<DocViewerError error={e} />, domNode);
});
return () => {
if (!rejected) {
// for cleanup
// http://roubenmeschian.com/rubo/?p=51
cleanupFnPromise.then((cleanup) => cleanup());
}
};
};
}

View file

@ -6,33 +6,16 @@
* Side Public License, v 1.
*/
import { auto } from 'angular';
import { convertDirectiveToRenderFn } from './doc_views_helpers';
import { DocView, DocViewInput, ElasticSearchHit, DocViewInputFn } from './doc_views_types';
export class DocViewsRegistry {
private docViews: DocView[] = [];
private angularInjectorGetter: (() => Promise<auto.IInjectorService>) | null = null;
setAngularInjectorGetter = (injectorGetter: () => Promise<auto.IInjectorService>) => {
this.angularInjectorGetter = injectorGetter;
};
/**
* Extends and adds the given doc view to the registry array
*/
addDocView(docViewRaw: DocViewInput | DocViewInputFn) {
const docView = typeof docViewRaw === 'function' ? docViewRaw() : docViewRaw;
if (docView.directive) {
// convert angular directive to render function for backwards compatibility
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(docView.render as any) = convertDirectiveToRenderFn(docView.directive as any, () => {
if (!this.angularInjectorGetter) {
throw new Error('Angular was not initialized');
}
return this.angularInjectorGetter();
});
}
if (typeof docView.shouldShow !== 'function') {
docView.shouldShow = () => true;
}

View file

@ -7,17 +7,10 @@
*/
import { ComponentType } from 'react';
import { IScope } from 'angular';
import type { estypes } from '@elastic/elasticsearch';
import { IndexPattern } from '../../../../data/public';
export interface AngularDirective {
controller: (...injectedServices: unknown[]) => void;
template: string;
}
export type AngularScope = IScope;
export type ElasticSearchHit<T = unknown> = estypes.SearchHit<T>;
export interface FieldMapping {
@ -67,13 +60,7 @@ interface ComponentDocViewInput extends BaseDocViewInput {
directive?: undefined;
}
interface DirectiveDocViewInput extends BaseDocViewInput {
component?: undefined;
render?: undefined;
directive: ng.IDirective;
}
export type DocViewInput = ComponentDocViewInput | RenderDocViewInput | DirectiveDocViewInput;
export type DocViewInput = ComponentDocViewInput | RenderDocViewInput;
export type DocView = DocViewInput & {
shouldShow: NonNullable<DocViewInput['shouldShow']>;

View file

@ -317,7 +317,6 @@ export class DiscoverPlugin
stopUrlTracker();
};
this.docViewsRegistry.setAngularInjectorGetter(this.getEmbeddableInjector);
core.application.register({
id: 'discover',
title: 'Discover',

View file

@ -29,7 +29,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
require.resolve('./test_suites/panel_actions'),
require.resolve('./test_suites/core_plugins'),
require.resolve('./test_suites/management'),
require.resolve('./test_suites/doc_views'),
require.resolve('./test_suites/application_links'),
require.resolve('./test_suites/data_plugin'),
require.resolve('./test_suites/saved_objects_management'),

View file

@ -1,8 +0,0 @@
{
"id": "docViewPlugin",
"version": "0.0.1",
"kibanaVersion": "kibana",
"server": false,
"ui": true,
"requiredPlugins": ["discover"]
}

View file

@ -1,14 +0,0 @@
{
"name": "docViewPlugin",
"version": "1.0.0",
"main": "target/test/plugin_functional/plugins/doc_views_plugin",
"kibana": {
"version": "kibana",
"templateVersion": "1.0.0"
},
"license": "SSPL-1.0 OR Elastic License 2.0",
"scripts": {
"kbn": "node ../../../../scripts/kbn.js",
"build": "rm -rf './target' && ../../../../node_modules/.bin/tsc"
}
}

View file

@ -1,11 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { DocViewsPlugin } from './plugin';
export const plugin = () => new DocViewsPlugin();

View file

@ -1,49 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import angular from 'angular';
import React from 'react';
import { Plugin, CoreSetup } from 'kibana/public';
import { DiscoverSetup } from '../../../../../src/plugins/discover/public';
angular.module('myDocView', []).directive('myHit', () => ({
restrict: 'E',
scope: {
hit: '=hit',
},
template: '<h1 data-test-subj="angular-docview">{{hit._index}}</h1>',
}));
function MyHit(props: { index: string }) {
return <h1 data-test-subj="react-docview">{props.index}</h1>;
}
export class DocViewsPlugin implements Plugin<void, void> {
public setup(core: CoreSetup, { discover }: { discover: DiscoverSetup }) {
discover.docViews.addDocView({
directive: {
controller: function MyController($injector: any) {
$injector.loadNewModules(['myDocView']);
},
template: `<my-hit hit="hit"></my-hit>`,
},
order: 1,
title: 'Angular doc view',
});
discover.docViews.addDocView({
component: (props) => {
return <MyHit index={props.hit._index as string} />;
},
order: 2,
title: 'React doc view',
});
}
public start() {}
}

View file

@ -1,17 +0,0 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./target/types"
},
"include": [
"index.ts",
"public/**/*.ts",
"public/**/*.tsx",
"../../../../typings/**/*"
],
"exclude": [],
"references": [
{ "path": "../../../../src/core/tsconfig.json" },
{ "path": "../../../../src/plugins/discover/tsconfig.json" },
]
}

View file

@ -1,45 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../services';
export default function ({ getService, getPageObjects }: PluginFunctionalProviderContext) {
const testSubjects = getService('testSubjects');
const find = getService('find');
const PageObjects = getPageObjects(['common', 'discover', 'timePicker']);
describe('custom doc views', function () {
before(async () => {
await PageObjects.common.navigateToApp('discover');
await PageObjects.timePicker.setDefaultAbsoluteRange();
});
it('should show custom doc views', async () => {
await testSubjects.click('docTableExpandToggleColumn');
const angularTab = await find.byButtonText('Angular doc view');
const reactTab = await find.byButtonText('React doc view');
expect(await angularTab.isDisplayed()).to.be(true);
expect(await reactTab.isDisplayed()).to.be(true);
});
it('should render angular doc view', async () => {
const angularTab = await find.byButtonText('Angular doc view');
await angularTab.click();
const angularContent = await testSubjects.find('angular-docview');
expect(await angularContent.getVisibleText()).to.be('logstash-2015.09.22');
});
it('should render react doc view', async () => {
const reactTab = await find.byButtonText('React doc view');
await reactTab.click();
const reactContent = await testSubjects.find('react-docview');
expect(await reactContent.getVisibleText()).to.be('logstash-2015.09.22');
});
});
}

View file

@ -1,21 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { PluginFunctionalProviderContext } from '../../services';
export default function ({ getService, loadTestFile }: PluginFunctionalProviderContext) {
const esArchiver = getService('esArchiver');
describe('doc views', function () {
before(async () => {
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/discover');
});
loadTestFile(require.resolve('./doc_views'));
});
}