mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Inspector 👉 New Platform (#42164)
* refactor: 💡 remove SASS and clean up InspectorView component * refactor: 💡 clean up inspector, convert .js -> .ts * feat: 🎸 add Inspector NP plugin boilerplate * feat: 🎸 move view registry to NP, move types, add registerView * docs: ✏️ move inspector README to NP plugin * refactor: 💡 move ui/inspector/ui to NP * refactor: 💡 move Inspector adapters to NP * refactor: 💡 move Inspector.isAvailable to New Platform * refactor: 💡 move Inspector.open to New Platform plugin * test: 💍 move Inspector tests to NP plugin * chore: 🤖 fix imports * feat: 🎸 update translations * test: 💍 fix failing translation snapshot * test: 💍 fix yarn test:browser tests * Update src/legacy/ui/public/inspector/build_tabular_inspector_data.ts Co-Authored-By: Stacey Gammon <gammon@elastic.co> * [ML] [Job wizards] switching to new kibana context provider * fix: 🐛 specify translation path directly to the plugin * docs: ✏️ add comment about Webpack config fix * Update src/legacy/ui/public/inspector/build_tabular_inspector_data.ts Co-Authored-By: Stacey Gammon <gammon@elastic.co> * Update src/legacy/ui/public/inspector/build_tabular_inspector_data.ts Co-Authored-By: Stacey Gammon <gammon@elastic.co> * feat: 🎸 improve types as per review * fix: 🐛 remove <InspectorView> comp and fix view layouts * test: 💍 improve mocks
This commit is contained in:
parent
b94e94ea94
commit
39b233a24d
57 changed files with 1144 additions and 906 deletions
|
@ -24,7 +24,8 @@
|
|||
"timelion": "src/legacy/core_plugins/timelion",
|
||||
"tagCloud": "src/legacy/core_plugins/tagcloud",
|
||||
"tsvb": "src/legacy/core_plugins/metrics",
|
||||
"kbnESQuery": "packages/kbn-es-query"
|
||||
"kbnESQuery": "packages/kbn-es-query",
|
||||
"inspector": "src/plugins/inspector"
|
||||
},
|
||||
"exclude": ["src/legacy/ui/ui_render/ui_render_mixin.js"],
|
||||
"translations": []
|
||||
|
|
|
@ -17,35 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { fatalErrorsServiceMock, notificationServiceMock } from '../../../../core/public/mocks';
|
||||
|
||||
let modalContents: React.Component;
|
||||
|
||||
export const getModalContents = () => modalContents;
|
||||
|
||||
jest.doMock('ui/new_platform', () => {
|
||||
return {
|
||||
npStart: {
|
||||
core: {
|
||||
overlays: {
|
||||
openFlyout: jest.fn(),
|
||||
openModal: (component: React.Component) => {
|
||||
modalContents = component;
|
||||
return {
|
||||
close: jest.fn(),
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
npSetup: {
|
||||
core: {
|
||||
fatalErrors: fatalErrorsServiceMock.createSetupContract(),
|
||||
notifications: notificationServiceMock.createSetupContract(),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
jest.doMock('ui/metadata', () => ({
|
||||
metadata: {
|
||||
|
|
|
@ -24,3 +24,5 @@ jest.doMock('ui/capabilities', () => ({
|
|||
},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
|
|
@ -119,93 +119,77 @@ exports[`Inspector Data View component should render empty state 1`] = `
|
|||
}
|
||||
title="Test Data"
|
||||
>
|
||||
<InspectorView
|
||||
useFlex={true}
|
||||
<EuiEmptyPrompt
|
||||
body={
|
||||
<React.Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="The element did not provide any data."
|
||||
id="inspectorViews.data.noDataAvailableDescription"
|
||||
values={Object {}}
|
||||
/>
|
||||
</p>
|
||||
</React.Fragment>
|
||||
}
|
||||
iconColor="subdued"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
defaultMessage="No data available"
|
||||
id="inspectorViews.data.noDataAvailableTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
>
|
||||
<EuiFlyoutBody
|
||||
className="kbnInspectorView--flex"
|
||||
<div
|
||||
className="euiEmptyPrompt"
|
||||
>
|
||||
<div
|
||||
className="euiFlyoutBody kbnInspectorView--flex"
|
||||
<EuiTextColor
|
||||
color="subdued"
|
||||
>
|
||||
<div
|
||||
className="euiFlyoutBody__overflow"
|
||||
<span
|
||||
className="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
<EuiEmptyPrompt
|
||||
body={
|
||||
<React.Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="The element did not provide any data."
|
||||
id="inspectorViews.data.noDataAvailableDescription"
|
||||
values={Object {}}
|
||||
/>
|
||||
</p>
|
||||
</React.Fragment>
|
||||
}
|
||||
iconColor="subdued"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
defaultMessage="No data available"
|
||||
id="inspectorViews.data.noDataAvailableTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
<EuiTitle>
|
||||
<h2
|
||||
className="euiTitle euiTitle--medium"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="No data available"
|
||||
id="inspectorViews.data.noDataAvailableTitle"
|
||||
values={Object {}}
|
||||
>
|
||||
No data available
|
||||
</FormattedMessage>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
>
|
||||
<div
|
||||
className="euiEmptyPrompt"
|
||||
className="euiSpacer euiSpacer--m"
|
||||
/>
|
||||
</EuiSpacer>
|
||||
<EuiText>
|
||||
<div
|
||||
className="euiText euiText--medium"
|
||||
>
|
||||
<EuiTextColor
|
||||
color="subdued"
|
||||
>
|
||||
<span
|
||||
className="euiTextColor euiTextColor--subdued"
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="The element did not provide any data."
|
||||
id="inspectorViews.data.noDataAvailableDescription"
|
||||
values={Object {}}
|
||||
>
|
||||
<EuiTitle>
|
||||
<h2
|
||||
className="euiTitle euiTitle--medium"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="No data available"
|
||||
id="inspectorViews.data.noDataAvailableTitle"
|
||||
values={Object {}}
|
||||
>
|
||||
No data available
|
||||
</FormattedMessage>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--m"
|
||||
/>
|
||||
</EuiSpacer>
|
||||
<EuiText>
|
||||
<div
|
||||
className="euiText euiText--medium"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="The element did not provide any data."
|
||||
id="inspectorViews.data.noDataAvailableDescription"
|
||||
values={Object {}}
|
||||
>
|
||||
The element did not provide any data.
|
||||
</FormattedMessage>
|
||||
</p>
|
||||
</div>
|
||||
</EuiText>
|
||||
</span>
|
||||
</EuiTextColor>
|
||||
The element did not provide any data.
|
||||
</FormattedMessage>
|
||||
</p>
|
||||
</div>
|
||||
</EuiEmptyPrompt>
|
||||
</div>
|
||||
</div>
|
||||
</EuiFlyoutBody>
|
||||
</InspectorView>
|
||||
</EuiText>
|
||||
</span>
|
||||
</EuiTextColor>
|
||||
</div>
|
||||
</EuiEmptyPrompt>
|
||||
</DataViewComponent>
|
||||
`;
|
||||
|
||||
|
@ -328,91 +312,85 @@ exports[`Inspector Data View component should render loading state 1`] = `
|
|||
}
|
||||
title="Test Data"
|
||||
>
|
||||
<InspectorView
|
||||
useFlex={true}
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
style={
|
||||
Object {
|
||||
"height": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiFlyoutBody
|
||||
className="kbnInspectorView--flex"
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
style={
|
||||
Object {
|
||||
"height": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlyoutBody kbnInspectorView--flex"
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<div
|
||||
className="euiFlyoutBody__overflow"
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
<EuiPanel
|
||||
className="eui-textCenter"
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="m"
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
className="euiPanel euiPanel--paddingMedium eui-textCenter"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
<EuiLoadingChart
|
||||
size="m"
|
||||
>
|
||||
<span
|
||||
className="euiLoadingChart euiLoadingChart--medium"
|
||||
>
|
||||
<span
|
||||
className="euiLoadingChart__bar"
|
||||
/>
|
||||
<span
|
||||
className="euiLoadingChart__bar"
|
||||
/>
|
||||
<span
|
||||
className="euiLoadingChart__bar"
|
||||
/>
|
||||
<span
|
||||
className="euiLoadingChart__bar"
|
||||
/>
|
||||
</span>
|
||||
</EuiLoadingChart>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
className="euiSpacer euiSpacer--s"
|
||||
/>
|
||||
</EuiSpacer>
|
||||
<EuiText>
|
||||
<div
|
||||
className="euiText euiText--medium"
|
||||
>
|
||||
<EuiPanel
|
||||
className="eui-textCenter"
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="m"
|
||||
>
|
||||
<div
|
||||
className="euiPanel euiPanel--paddingMedium eui-textCenter"
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Gathering data"
|
||||
id="inspectorViews.data.gatheringDataLabel"
|
||||
values={Object {}}
|
||||
>
|
||||
<EuiLoadingChart
|
||||
size="m"
|
||||
>
|
||||
<span
|
||||
className="euiLoadingChart euiLoadingChart--medium"
|
||||
>
|
||||
<span
|
||||
className="euiLoadingChart__bar"
|
||||
/>
|
||||
<span
|
||||
className="euiLoadingChart__bar"
|
||||
/>
|
||||
<span
|
||||
className="euiLoadingChart__bar"
|
||||
/>
|
||||
<span
|
||||
className="euiLoadingChart__bar"
|
||||
/>
|
||||
</span>
|
||||
</EuiLoadingChart>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--s"
|
||||
/>
|
||||
</EuiSpacer>
|
||||
<EuiText>
|
||||
<div
|
||||
className="euiText euiText--medium"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Gathering data"
|
||||
id="inspectorViews.data.gatheringDataLabel"
|
||||
values={Object {}}
|
||||
>
|
||||
Gathering data
|
||||
</FormattedMessage>
|
||||
</p>
|
||||
</div>
|
||||
</EuiText>
|
||||
</div>
|
||||
</EuiPanel>
|
||||
Gathering data
|
||||
</FormattedMessage>
|
||||
</p>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</EuiText>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</div>
|
||||
</div>
|
||||
</EuiFlyoutBody>
|
||||
</InspectorView>
|
||||
</EuiFlexItem>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
</DataViewComponent>
|
||||
`;
|
||||
|
|
|
@ -29,8 +29,6 @@ import {
|
|||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { InspectorView } from 'ui/inspector';
|
||||
|
||||
import {
|
||||
DataTableFormat,
|
||||
} from './data_table';
|
||||
|
@ -102,54 +100,51 @@ class DataViewComponent extends Component {
|
|||
|
||||
renderNoData() {
|
||||
return (
|
||||
<InspectorView useFlex={true}>
|
||||
<EuiEmptyPrompt
|
||||
title={
|
||||
<h2>
|
||||
<EuiEmptyPrompt
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="inspectorViews.data.noDataAvailableTitle"
|
||||
defaultMessage="No data available"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<React.Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="inspectorViews.data.noDataAvailableTitle"
|
||||
defaultMessage="No data available"
|
||||
id="inspectorViews.data.noDataAvailableDescription"
|
||||
defaultMessage="The element did not provide any data."
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<React.Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="inspectorViews.data.noDataAvailableDescription"
|
||||
defaultMessage="The element did not provide any data."
|
||||
/>
|
||||
</p>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
</InspectorView>
|
||||
</p>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderLoading() {
|
||||
return (
|
||||
<InspectorView useFlex={true}>
|
||||
<EuiFlexGroup
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPanel className="eui-textCenter">
|
||||
<EuiLoadingChart size="m" />
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="inspectorViews.data.gatheringDataLabel"
|
||||
defaultMessage="Gathering data"
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</InspectorView>
|
||||
<EuiFlexGroup
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
style={{ height: '100%' }}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPanel className="eui-textCenter">
|
||||
<EuiLoadingChart size="m" />
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="inspectorViews.data.gatheringDataLabel"
|
||||
defaultMessage="Gathering data"
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -161,13 +156,11 @@ class DataViewComponent extends Component {
|
|||
}
|
||||
|
||||
return (
|
||||
<InspectorView>
|
||||
<DataTableFormat
|
||||
data={this.state.tabularData}
|
||||
isFormatted={this.state.tabularOptions.returnsFormattedValues}
|
||||
exportTitle={this.props.title}
|
||||
/>
|
||||
</InspectorView>
|
||||
<DataTableFormat
|
||||
data={this.state.tabularData}
|
||||
isFormatted={this.state.tabularOptions.returnsFormattedValues}
|
||||
exportTitle={this.props.title}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import { DataView } from './data_view';
|
|||
import { DataAdapter } from 'ui/inspector/adapters';
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
jest.mock('./lib/export_csv', () => ({
|
||||
exportAsCsv: jest.fn(),
|
||||
}));
|
||||
|
|
|
@ -26,7 +26,6 @@ import {
|
|||
EuiTextColor,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { InspectorView } from 'ui/inspector';
|
||||
import { RequestStatus } from 'ui/inspector/adapters';
|
||||
|
||||
import { RequestSelector } from './request_selector';
|
||||
|
@ -68,36 +67,34 @@ class RequestsViewComponent extends Component {
|
|||
|
||||
renderEmptyRequests() {
|
||||
return (
|
||||
<InspectorView useFlex={true}>
|
||||
<EuiEmptyPrompt
|
||||
data-test-subj="inspectorNoRequestsMessage"
|
||||
title={
|
||||
<h2>
|
||||
<EuiEmptyPrompt
|
||||
data-test-subj="inspectorNoRequestsMessage"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="inspectorViews.requests.noRequestsLoggedTitle"
|
||||
defaultMessage="No requests logged"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<React.Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="inspectorViews.requests.noRequestsLoggedTitle"
|
||||
defaultMessage="No requests logged"
|
||||
id="inspectorViews.requests.noRequestsLoggedDescription.elementHasNotLoggedAnyRequestsText"
|
||||
defaultMessage="The element hasn't logged any requests (yet)."
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<React.Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="inspectorViews.requests.noRequestsLoggedDescription.elementHasNotLoggedAnyRequestsText"
|
||||
defaultMessage="The element hasn't logged any requests (yet)."
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="inspectorViews.requests.noRequestsLoggedDescription.whatDoesItUsuallyMeanText"
|
||||
defaultMessage="This usually means that there was no need to fetch any data or
|
||||
that the element has not yet started fetching data."
|
||||
/>
|
||||
</p>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
</InspectorView>
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="inspectorViews.requests.noRequestsLoggedDescription.whatDoesItUsuallyMeanText"
|
||||
defaultMessage="This usually means that there was no need to fetch any data or
|
||||
that the element has not yet started fetching data."
|
||||
/>
|
||||
</p>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -111,7 +108,7 @@ class RequestsViewComponent extends Component {
|
|||
).length;
|
||||
|
||||
return (
|
||||
<InspectorView>
|
||||
<>
|
||||
<EuiText size="xs">
|
||||
<p role="status" aria-live="polite" aria-atomic="true">
|
||||
<FormattedMessage
|
||||
|
@ -152,7 +149,7 @@ class RequestsViewComponent extends Component {
|
|||
request={this.state.request}
|
||||
/>
|
||||
}
|
||||
</InspectorView>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ jest.mock(
|
|||
{ virtual: true }
|
||||
);
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
import { migratePanelsTo730 } from './migrate_to_730_panels';
|
||||
import { SavedDashboardPanelTo60, SavedDashboardPanel730ToLatest } from '../types';
|
||||
import {
|
||||
|
|
|
@ -17,35 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { fatalErrorsServiceMock, notificationServiceMock } from '../../../../../core/public/mocks';
|
||||
|
||||
let modalContents: React.Component;
|
||||
|
||||
export const getModalContents = () => modalContents;
|
||||
|
||||
jest.doMock('ui/new_platform', () => {
|
||||
return {
|
||||
npStart: {
|
||||
core: {
|
||||
overlays: {
|
||||
openFlyout: jest.fn(),
|
||||
openModal: (component: React.Component) => {
|
||||
modalContents = component;
|
||||
return {
|
||||
close: jest.fn(),
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
npSetup: {
|
||||
core: {
|
||||
fatalErrors: fatalErrorsServiceMock.createSetupContract(),
|
||||
notifications: notificationServiceMock.createSetupContract(),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
jest.doMock('ui/metadata', () => ({
|
||||
metadata: {
|
||||
|
|
|
@ -104,11 +104,25 @@ export default (kibana) => {
|
|||
modules: [...modules],
|
||||
template: createTestEntryTemplate(uiSettingDefaults),
|
||||
extendConfig(webpackConfig) {
|
||||
return webpackMerge({
|
||||
const mergedConfig = webpackMerge({
|
||||
resolve: {
|
||||
extensions: ['.karma_mock.js', '.karma_mock.tsx', '.karma_mock.ts']
|
||||
}
|
||||
}, webpackConfig);
|
||||
|
||||
/**
|
||||
* [..] it removes the commons bundle creation from the webpack
|
||||
* config when we're building the bundle for the browser tests. It
|
||||
* shouldn't be created, and by default isn't, but something is
|
||||
* triggering it in webpack which breaks the tests so if we just
|
||||
* remove the optimization config it will never happen and the tests
|
||||
* will keep working [..]
|
||||
*
|
||||
* TLDR: If you have any questions about this line, ask Spencer.
|
||||
*/
|
||||
delete mergedConfig.optimization.splitChunks.cacheGroups.commons;
|
||||
|
||||
return mergedConfig;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
@import './error_url_overflow/index';
|
||||
@import './exit_full_screen/index';
|
||||
@import './field_editor/index';
|
||||
@import './inspector/index';
|
||||
@import './kbn_top_nav/index';
|
||||
@import './markdown/index';
|
||||
@import './notify/index';
|
||||
|
|
|
@ -1,127 +1,6 @@
|
|||
# Inspector
|
||||
|
||||
The inspector is a contextual tool to gain insights into different elements
|
||||
in Kibana, e.g. visualizations. It has the form of a flyout panel.
|
||||
|
||||
## Inspector Views
|
||||
|
||||
The "Inspector Panel" can have multiple so called "Inspector Views" inside of it.
|
||||
These views are used to gain different information into the element you are inspecting.
|
||||
There is a request inspector view to gain information in the requests done for this
|
||||
element or a data inspector view to inspect the underlying data. Whether or not
|
||||
a specific view is available depends on the used adapters.
|
||||
|
||||
## Inspector Adapters
|
||||
|
||||
Since the Inspector panel itself is not tied to a specific type of elements (visualizations,
|
||||
saved searches, etc.), everything you need to open the inspector is a collection
|
||||
of so called inspector adapters. A single adapter can be any type of JavaScript class.
|
||||
|
||||
Most likely an adapter offers some kind of logging capabilities for the element, that
|
||||
uses it e.g. the request adapter allows element (like visualizations) to log requests
|
||||
they make.
|
||||
|
||||
The corresponding inspector view will then use the information inside the adapter
|
||||
to present the data in the panel. That concept allows different types of elements
|
||||
to use the Inspector panel, while they can use completely or partial different adapters
|
||||
and inspector views than other elements.
|
||||
|
||||
For example a visualization could provide the request and data adapter while a saved
|
||||
search could only provide the request adapter and a Vega visualization could additionally
|
||||
provide a Vega adapter.
|
||||
|
||||
There is no 1 to 1 relationship between adapters and views. An adapter could be used
|
||||
by multiple views and a view can use data from multiple adapters. It's up to the
|
||||
view to decide whether or not it wants to be shown for a given adapters list.
|
||||
|
||||
## Develop custom inspectors
|
||||
|
||||
You can extend the inspector panel by adding custom inspector views and inspector
|
||||
adapters via a plugin.
|
||||
|
||||
### Develop inspector views
|
||||
|
||||
To develop custom inspector views you should first register your file via `uiExports`
|
||||
in your plugin config:
|
||||
|
||||
```js
|
||||
export default (kibana) => {
|
||||
return new kibana.Plugin({
|
||||
uiExports: {
|
||||
inspectorViews: [ 'plugins/your_plugin/custom_view' ],
|
||||
}
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
Within the `custom_view.js` file in your `public` folder, you can define your
|
||||
inspector view as follows:
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import { InspectorView, viewRegistry } from 'ui/inspector';
|
||||
|
||||
function MyInspectorComponent(props) {
|
||||
// props.adapters is the object of all adapters and may vary depending
|
||||
// on who and where this inspector was opened. You should check for all
|
||||
// adapters you need, in the below shouldShow method, before accessing
|
||||
// them here.
|
||||
return (
|
||||
<InspectorView>
|
||||
{ /* Always use InspectorView as the wrapping element! */ }
|
||||
</InspectorView>
|
||||
);
|
||||
}
|
||||
|
||||
const MyLittleInspectorView = {
|
||||
// Title shown to select this view
|
||||
title: 'Display Name',
|
||||
// An icon id from the EUI icon list
|
||||
icon: 'iconName',
|
||||
// An order to sort the views (lower means first)
|
||||
order: 10,
|
||||
// An additional helptext, that wil
|
||||
help: `And additional help text, that will be shown in the inspector help.`,
|
||||
shouldShow(adapters) {
|
||||
// Only show if `someAdapter` is available. Make sure to check for
|
||||
// all adapters that you want to access in your view later on and
|
||||
// any additional condition you want to be true to be shown.
|
||||
return adapters.someAdapter;
|
||||
},
|
||||
// A React component, that will be used for rendering
|
||||
component: MyInspectorComponent
|
||||
};
|
||||
|
||||
viewRegistry.register(MyLittleInspectorView);
|
||||
```
|
||||
|
||||
### Develop custom adapters
|
||||
|
||||
An inspector adapter is just a plain JavaScript class, that can e.g. be attached
|
||||
to custom visualization types, so an inspector view can show additional information for this
|
||||
visualization.
|
||||
|
||||
To add additional adapters to your visualization type, use the `inspectorAdapters.custom`
|
||||
object when defining the visualization type:
|
||||
|
||||
```js
|
||||
class MyCustomInspectorAdapter {
|
||||
// ....
|
||||
}
|
||||
|
||||
// inside your visualization type description (usually passed to VisFactory.create...Type)
|
||||
{
|
||||
// ...
|
||||
inspectorAdapters: {
|
||||
custom: {
|
||||
someAdapter: MyCustomInspectorAdapter
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
An instance of MyCustomInspectorAdapter will now be available on each visualization
|
||||
of that type and can be accessed via `vis.API.inspectorAdapters.someInspector`.
|
||||
|
||||
Custom inspector views can now check for the presence of `adapters.someAdapter`
|
||||
in their `shouldShow` method and use this adapter in their component.
|
||||
- Inspector has been moved to `inspector` New Platform plugin.
|
||||
- You can find its documentation in `src/plugins/inspector/README.md`.
|
||||
- This folder will be deleted soon, it is deprecated, do not use anything from here.
|
||||
- This folder is ready to be deleted, as soon as nothing imports from here anymore.
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
@import './ui/index';
|
|
@ -17,5 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { DataAdapter, FormattedData } from './data';
|
||||
export { RequestAdapter, RequestStatus } from './request';
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* Do not use this, use NP `inspector` plugin instead.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export * from '../../../../../plugins/inspector/public/adapters/index';
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* 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 { set } from 'lodash';
|
||||
import { createFilter } from '../vis/vis_filters';
|
||||
import { FormattedData } from './adapters/data';
|
||||
|
||||
/**
|
||||
* This function builds tabular data from the response and attaches it to the
|
||||
* inspector. It will only be called when the data view in the inspector is opened.
|
||||
*/
|
||||
export async function buildTabularInspectorData(table, queryFilter) {
|
||||
const aggConfigs = table.columns.map(column => column.aggConfig);
|
||||
const rows = table.rows.map(row => {
|
||||
return table.columns.reduce((prev, cur, colIndex) => {
|
||||
const value = row[cur.id];
|
||||
const fieldFormatter = cur.aggConfig.fieldFormatter('text');
|
||||
prev[`col-${colIndex}-${cur.aggConfig.id}`] = new FormattedData(value, fieldFormatter(value));
|
||||
return prev;
|
||||
}, {});
|
||||
});
|
||||
|
||||
const columns = table.columns.map((col, colIndex) => {
|
||||
const field = col.aggConfig.getField();
|
||||
const isCellContentFilterable =
|
||||
col.aggConfig.isFilterable()
|
||||
&& (!field || field.filterable);
|
||||
return ({
|
||||
name: col.name,
|
||||
field: `col-${colIndex}-${col.aggConfig.id}`,
|
||||
filter: isCellContentFilterable && (value => {
|
||||
const rowIndex = rows.findIndex(row => row[`col-${colIndex}-${col.aggConfig.id}`].raw === value.raw);
|
||||
const filter = createFilter(aggConfigs, table, colIndex, rowIndex, value.raw);
|
||||
queryFilter.addFilters(filter);
|
||||
}),
|
||||
filterOut: isCellContentFilterable && (value => {
|
||||
const rowIndex = rows.findIndex(row => row[`col-${colIndex}-${col.aggConfig.id}`].raw === value.raw);
|
||||
const filter = createFilter(aggConfigs, table, colIndex, rowIndex, value.raw);
|
||||
const notOther = value.raw !== '__other__';
|
||||
const notMissing = value.raw !== '__missing__';
|
||||
if (Array.isArray(filter)) {
|
||||
filter.forEach(f => set(f, 'meta.negate', (notOther && notMissing)));
|
||||
} else {
|
||||
set(filter, 'meta.negate', (notOther && notMissing));
|
||||
}
|
||||
queryFilter.addFilters(filter);
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
return { columns, rows };
|
||||
}
|
103
src/legacy/ui/public/inspector/build_tabular_inspector_data.ts
Normal file
103
src/legacy/ui/public/inspector/build_tabular_inspector_data.ts
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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 { set } from 'lodash';
|
||||
// @ts-ignore
|
||||
import { createFilter } from '../vis/vis_filters';
|
||||
import { FormattedData } from './adapters';
|
||||
|
||||
interface Column {
|
||||
id: string;
|
||||
name: string;
|
||||
aggConfig: any;
|
||||
}
|
||||
|
||||
interface Row {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface Table {
|
||||
columns: Column[];
|
||||
rows: Row[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Do not use this function.
|
||||
*
|
||||
* @todo This function is used only by Courier. Courier will
|
||||
* soon be removed, and this function will be deleted, too. If Courier is not removed,
|
||||
* move this function inside Courier.
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* This function builds tabular data from the response and attaches it to the
|
||||
* inspector. It will only be called when the data view in the inspector is opened.
|
||||
*/
|
||||
export async function buildTabularInspectorData(
|
||||
table: Table,
|
||||
queryFilter: { addFilters: (filter: any) => void }
|
||||
) {
|
||||
const aggConfigs = table.columns.map(column => column.aggConfig);
|
||||
const rows = table.rows.map(row => {
|
||||
return table.columns.reduce<Record<string, FormattedData>>((prev, cur, colIndex) => {
|
||||
const value = row[cur.id];
|
||||
const fieldFormatter = cur.aggConfig.fieldFormatter('text');
|
||||
prev[`col-${colIndex}-${cur.aggConfig.id}`] = new FormattedData(value, fieldFormatter(value));
|
||||
return prev;
|
||||
}, {});
|
||||
});
|
||||
|
||||
const columns = table.columns.map((col, colIndex) => {
|
||||
const field = col.aggConfig.getField();
|
||||
const isCellContentFilterable = col.aggConfig.isFilterable() && (!field || field.filterable);
|
||||
return {
|
||||
name: col.name,
|
||||
field: `col-${colIndex}-${col.aggConfig.id}`,
|
||||
filter:
|
||||
isCellContentFilterable &&
|
||||
((value: { raw: unknown }) => {
|
||||
const rowIndex = rows.findIndex(
|
||||
row => row[`col-${colIndex}-${col.aggConfig.id}`].raw === value.raw
|
||||
);
|
||||
const filter = createFilter(aggConfigs, table, colIndex, rowIndex, value.raw);
|
||||
queryFilter.addFilters(filter);
|
||||
}),
|
||||
filterOut:
|
||||
isCellContentFilterable &&
|
||||
((value: { raw: unknown }) => {
|
||||
const rowIndex = rows.findIndex(
|
||||
row => row[`col-${colIndex}-${col.aggConfig.id}`].raw === value.raw
|
||||
);
|
||||
const filter = createFilter(aggConfigs, table, colIndex, rowIndex, value.raw);
|
||||
const notOther = value.raw !== '__other__';
|
||||
const notMissing = value.raw !== '__missing__';
|
||||
if (Array.isArray(filter)) {
|
||||
filter.forEach(f => set(f, 'meta.negate', notOther && notMissing));
|
||||
} else {
|
||||
set(filter, 'meta.negate', notOther && notMissing);
|
||||
}
|
||||
queryFilter.addFilters(filter);
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
return { columns, rows };
|
||||
}
|
|
@ -17,10 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { InspectorView } from './ui';
|
||||
|
||||
export { Inspector } from './inspector';
|
||||
|
||||
export { viewRegistry } from './view_registry';
|
||||
|
||||
export { Adapters } from './types';
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* 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 { Inspector } from './inspector';
|
||||
jest.mock('./view_registry', () => ({
|
||||
viewRegistry: {
|
||||
getVisible: jest.fn(),
|
||||
},
|
||||
}));
|
||||
jest.mock('./ui/inspector_panel', () => ({
|
||||
InspectorPanel: () => 'InspectorPanel',
|
||||
}));
|
||||
jest.mock('ui/i18n', () => ({ I18nContext: ({ children }) => children }));
|
||||
|
||||
jest.mock('ui/new_platform', () => ({
|
||||
npStart: {
|
||||
core: {
|
||||
overlay: {
|
||||
openFlyout: jest.fn(),
|
||||
},
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
import { viewRegistry } from './view_registry';
|
||||
|
||||
function setViews(views) {
|
||||
viewRegistry.getVisible.mockImplementation(() => views);
|
||||
}
|
||||
|
||||
describe('Inspector', () => {
|
||||
describe('isAvailable()', () => {
|
||||
it('should return false if no view would be available', () => {
|
||||
setViews([]);
|
||||
expect(Inspector.isAvailable({})).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true if views would be available', () => {
|
||||
setViews([{}]);
|
||||
expect(Inspector.isAvailable({})).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('open()', () => {
|
||||
it('should throw an error if no views available', () => {
|
||||
setViews([]);
|
||||
expect(() => Inspector.open({})).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -16,75 +16,27 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { OverlayRef } from '../../../../core/public';
|
||||
import { npStart } from '../new_platform';
|
||||
import { Adapters } from './types';
|
||||
import { InspectorPanel } from './ui/inspector_panel';
|
||||
import { viewRegistry } from './view_registry';
|
||||
|
||||
const closeButtonLabel = i18n.translate('common.ui.inspector.closeButton', {
|
||||
defaultMessage: 'Close Inspector',
|
||||
});
|
||||
export { InspectorSession } from '../../../../plugins/inspector/public';
|
||||
|
||||
/**
|
||||
* Checks if a inspector panel could be shown based on the passed adapters.
|
||||
* @deprecated
|
||||
*
|
||||
* @param {object} adapters - An object of adapters. This should be the same
|
||||
* you would pass into `open`.
|
||||
* @returns {boolean} True, if a call to `open` with the same adapters
|
||||
* would have shown the inspector panel, false otherwise.
|
||||
* Do not use this, use New Platform `inspector` plugin instead.
|
||||
*/
|
||||
function isAvailable(adapters?: Adapters): boolean {
|
||||
return viewRegistry.getVisible(adapters).length > 0;
|
||||
}
|
||||
export const Inspector = {
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Do not use this, use New Platform `inspector` plugin instead.
|
||||
*/
|
||||
isAvailable: npStart.plugins.inspector.isAvailable,
|
||||
|
||||
/**
|
||||
* Options that can be specified when opening the inspector.
|
||||
* @property {string} title - An optional title, that will be shown in the header
|
||||
* of the inspector. Can be used to give more context about what is being inspected.
|
||||
*/
|
||||
interface InspectorOptions {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export type InspectorSession = OverlayRef;
|
||||
|
||||
/**
|
||||
* Opens the inspector panel for the given adapters and close any previously opened
|
||||
* inspector panel. The previously panel will be closed also if no new panel will be
|
||||
* opened (e.g. because of the passed adapters no view is available). You can use
|
||||
* {@link InspectorSession#close} on the return value to close that opened panel again.
|
||||
*
|
||||
* @param {object} adapters - An object of adapters for which you want to show
|
||||
* the inspector panel.
|
||||
* @param {InspectorOptions} options - Options that configure the inspector. See InspectorOptions type.
|
||||
* @return {InspectorSession} The session instance for the opened inspector.
|
||||
*/
|
||||
function open(adapters: Adapters, options: InspectorOptions = {}): InspectorSession {
|
||||
const views = viewRegistry.getVisible(adapters);
|
||||
|
||||
// Don't open inspector if there are no views available for the passed adapters
|
||||
if (!views || views.length === 0) {
|
||||
throw new Error(`Tried to open an inspector without views being available.
|
||||
Make sure to call Inspector.isAvailable() with the same adapters before to check
|
||||
if an inspector can be shown.`);
|
||||
}
|
||||
|
||||
return npStart.core.overlays.openFlyout(
|
||||
<InspectorPanel views={views} adapters={adapters} title={options.title} />,
|
||||
{
|
||||
'data-test-subj': 'inspectorPanel',
|
||||
closeButtonAriaLabel: closeButtonLabel,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const Inspector = {
|
||||
isAvailable,
|
||||
open,
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Do not use this, use New Platform `inspector` plugin instead.
|
||||
*/
|
||||
open: npStart.plugins.inspector.open,
|
||||
};
|
||||
|
||||
export { Inspector };
|
||||
|
|
|
@ -18,46 +18,16 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* The interface that the adapters used to open an inspector have to fullfill.
|
||||
* Do not import these types from here, instead import them from `inspector` plugin.
|
||||
*
|
||||
* ```ts
|
||||
* import { InspectorViewDescription } from 'src/plugins/inspector/public';
|
||||
* ```
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export interface Adapters {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* The props interface that a custom inspector view component, that will be passed
|
||||
* to {@link InspectorViewDescription#component}, must use.
|
||||
*/
|
||||
export interface InspectorViewProps {
|
||||
/**
|
||||
* The adapters thta has been used to open the inspector.
|
||||
*/
|
||||
adapters: Adapters;
|
||||
/**
|
||||
* The title that the inspector is currently using e.g. a visualization name.
|
||||
*/
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* An object describing an inspector view.
|
||||
* @typedef {object} InspectorViewDescription
|
||||
* @property {string} title - The title that will be used to present that view.
|
||||
* @property {string} icon - An icon name to present this view. Must match an EUI icon.
|
||||
* @property {React.ComponentType<InspectorViewProps>} component - The actual React component to render that
|
||||
* that view. It should always return an `InspectorView` element at the toplevel.
|
||||
* @property {number} [order=9000] - An order for this view. Views are ordered from lower
|
||||
* order values to higher order values in the UI.
|
||||
* @property {string} [help=''] - An help text for this view, that gives a brief description
|
||||
* of this view.
|
||||
* @property {viewShouldShowFunc} [shouldShow] - A function, that determines whether
|
||||
* this view should be visible for a given collection of adapters. If not specified
|
||||
* the view will always be visible.
|
||||
*/
|
||||
export interface InspectorViewDescription {
|
||||
component: React.ComponentType<InspectorViewProps>;
|
||||
help?: string;
|
||||
order?: number;
|
||||
shouldShow?: (adapters: Adapters) => boolean;
|
||||
title: string;
|
||||
}
|
||||
export {
|
||||
Adapters,
|
||||
InspectorViewProps,
|
||||
InspectorViewDescription,
|
||||
} from '../../../../plugins/inspector/public';
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
@import './inspector';
|
|
@ -1,3 +0,0 @@
|
|||
.kbnInspectorView--flex {
|
||||
display: flex;
|
||||
}
|
|
@ -16,14 +16,12 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { ComponentClass } from 'react';
|
||||
|
||||
import { Adapters, InspectorViewDescription } from '../types';
|
||||
/* eslint-disable */
|
||||
|
||||
interface InspectorPanelProps {
|
||||
adapters: Adapters;
|
||||
title?: string;
|
||||
views: InspectorViewDescription[];
|
||||
}
|
||||
|
||||
export const InspectorPanel: ComponentClass<InspectorPanelProps>;
|
||||
/**
|
||||
* Do not use this, use NP `inspector` plugin instead.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export * from '../../../../../plugins/inspector/public/ui/inspector_panel';
|
|
@ -17,29 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import { EuiFlyoutBody } from '@elastic/eui';
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* The InspectorView component should be the top most element in every implemented
|
||||
* inspector view. It makes sure, that the appropriate stylings are applied to the
|
||||
* view.
|
||||
* Do not use this, use NP `inspector` plugin instead.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
const InspectorView: React.SFC<{ useFlex?: boolean }> = ({ useFlex, children }) => {
|
||||
const classes = classNames({
|
||||
'kbnInspectorView--flex': Boolean(useFlex),
|
||||
});
|
||||
return <EuiFlyoutBody className={classes}>{children}</EuiFlyoutBody>;
|
||||
};
|
||||
|
||||
InspectorView.propTypes = {
|
||||
/**
|
||||
* Set to true if the element should have display: flex set.
|
||||
*/
|
||||
useFlex: PropTypes.bool,
|
||||
};
|
||||
|
||||
export { InspectorView };
|
||||
export * from '../../../../../plugins/inspector/public/ui/inspector_view_chooser';
|
|
@ -17,66 +17,19 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { Adapters, InspectorViewDescription } from './types';
|
||||
import { npSetup } from 'ui/new_platform';
|
||||
export { InspectorViewDescription } from './types';
|
||||
|
||||
/**
|
||||
* @callback viewShouldShowFunc
|
||||
* @param {object} adapters - A list of adapters to check whether or not this view
|
||||
* should be shown for.
|
||||
* @returns {boolean} true - if this view should be shown for the given adapters.
|
||||
* Do not use this, instead use `inspector` plugin directly.
|
||||
*
|
||||
* ```ts
|
||||
* import { npSetup } from 'ui/new_platform';
|
||||
*
|
||||
* npSetup.plugins.inspector.registerView(view);
|
||||
* ```
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
/**
|
||||
* A registry that will hold inspector views.
|
||||
*/
|
||||
class InspectorViewRegistry extends EventEmitter {
|
||||
private views: InspectorViewDescription[] = [];
|
||||
|
||||
/**
|
||||
* Register a new inspector view to the registry. Check the README.md in the
|
||||
* inspector directory for more information of the object format to register
|
||||
* here. This will also emit a 'change' event on the registry itself.
|
||||
*
|
||||
* @param {InspectorViewDescription} view - The view description to add to the registry.
|
||||
*/
|
||||
public register(view: InspectorViewDescription): void {
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
this.views.push(view);
|
||||
// Keep registry sorted by the order property
|
||||
this.views.sort((a, b) => (a.order || Number.MAX_VALUE) - (b.order || Number.MAX_VALUE));
|
||||
this.emit('change');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all views currently registered with the registry.
|
||||
* @returns {InspectorViewDescription[]} A by `order` sorted list of all registered
|
||||
* inspector views.
|
||||
*/
|
||||
public getAll(): InspectorViewDescription[] {
|
||||
return this.views;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all registered views, that want to be visible for the specified adapters.
|
||||
* @param {object} adapters - an adapter configuration
|
||||
* @returns {InspectorViewDescription[]} All inespector view descriptions visible
|
||||
* for the specific adapters.
|
||||
*/
|
||||
public getVisible(adapters?: Adapters): InspectorViewDescription[] {
|
||||
if (!adapters) {
|
||||
return [];
|
||||
}
|
||||
return this.views.filter(view => !view.shouldShow || view.shouldShow(adapters));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The global view registry. In the long run this should be solved by a registry
|
||||
* system introduced by the new platform instead, to not keep global state like that.
|
||||
*/
|
||||
const viewRegistry = new InspectorViewRegistry();
|
||||
|
||||
export { viewRegistry, InspectorViewRegistry, InspectorViewDescription };
|
||||
export const viewRegistry =
|
||||
npSetup.plugins.inspector.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.views;
|
||||
|
|
|
@ -17,16 +17,20 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @kbn/eslint/no-restricted-paths */
|
||||
import { coreMock } from '../../../../../core/public/mocks';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { dataPluginMock } from '../../../../../plugins/data/public/mocks';
|
||||
import { inspectorPluginMock } from '../../../../../plugins/inspector/public/mocks';
|
||||
/* eslint-enable @kbn/eslint/no-restricted-paths */
|
||||
|
||||
export const pluginsMock = {
|
||||
createSetup: () => ({
|
||||
data: dataPluginMock.createSetupContract(),
|
||||
inspector: inspectorPluginMock.createSetupContract(),
|
||||
}),
|
||||
createStart: () => ({
|
||||
data: dataPluginMock.createStartContract(),
|
||||
inspector: inspectorPluginMock.createStartContract(),
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
|
@ -29,6 +29,14 @@ export const npSetup = {
|
|||
registerType: sinon.fake(),
|
||||
},
|
||||
},
|
||||
inspector: {
|
||||
registerView: () => undefined,
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
|
||||
views: {
|
||||
register: () => undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -36,6 +44,13 @@ export const npStart = {
|
|||
core: {},
|
||||
plugins: {
|
||||
data: {},
|
||||
inspector: {
|
||||
isAvailable: () => false,
|
||||
open: () => ({
|
||||
onClose: Promise.resolve(undefined),
|
||||
close: () => Promise.resolve(undefined),
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -18,13 +18,19 @@
|
|||
*/
|
||||
import { InternalCoreSetup, InternalCoreStart } from '../../../../core/public';
|
||||
import { Plugin as DataPlugin } from '../../../../plugins/data/public';
|
||||
import {
|
||||
Setup as InspectorSetup,
|
||||
Start as InspectorStart,
|
||||
} from '../../../../plugins/inspector/public';
|
||||
|
||||
export interface PluginsSetup {
|
||||
data: ReturnType<DataPlugin['setup']>;
|
||||
inspector: InspectorSetup;
|
||||
}
|
||||
|
||||
export interface PluginsStart {
|
||||
data: ReturnType<DataPlugin['start']>;
|
||||
inspector: InspectorStart;
|
||||
}
|
||||
|
||||
export const npSetup = {
|
||||
|
|
|
@ -36,8 +36,7 @@ import { dispatchRenderComplete } from '../../../render_complete';
|
|||
import { PipelineDataLoader } from '../pipeline_data_loader';
|
||||
import { VisualizeDataLoader } from '../visualize_data_loader';
|
||||
import { PersistedState } from '../../../persisted_state';
|
||||
import { DataAdapter } from '../../../inspector/adapters/data';
|
||||
import { RequestAdapter } from '../../../inspector/adapters/request';
|
||||
import { DataAdapter, RequestAdapter } from '../../../inspector/adapters';
|
||||
|
||||
describe('visualize loader', () => {
|
||||
|
||||
|
|
122
src/plugins/inspector/README.md
Normal file
122
src/plugins/inspector/README.md
Normal file
|
@ -0,0 +1,122 @@
|
|||
# Inspector
|
||||
|
||||
The inspector is a contextual tool to gain insights into different elements
|
||||
in Kibana, e.g. visualizations. It has the form of a flyout panel.
|
||||
|
||||
## Inspector Views
|
||||
|
||||
The "Inspector Panel" can have multiple so called "Inspector Views" inside of it.
|
||||
These views are used to gain different information into the element you are inspecting.
|
||||
There is a request inspector view to gain information in the requests done for this
|
||||
element or a data inspector view to inspect the underlying data. Whether or not
|
||||
a specific view is available depends on the used adapters.
|
||||
|
||||
## Inspector Adapters
|
||||
|
||||
Since the Inspector panel itself is not tied to a specific type of elements (visualizations,
|
||||
saved searches, etc.), everything you need to open the inspector is a collection
|
||||
of so called inspector adapters. A single adapter can be any type of JavaScript class.
|
||||
|
||||
Most likely an adapter offers some kind of logging capabilities for the element, that
|
||||
uses it e.g. the request adapter allows element (like visualizations) to log requests
|
||||
they make.
|
||||
|
||||
The corresponding inspector view will then use the information inside the adapter
|
||||
to present the data in the panel. That concept allows different types of elements
|
||||
to use the Inspector panel, while they can use completely or partial different adapters
|
||||
and inspector views than other elements.
|
||||
|
||||
For example a visualization could provide the request and data adapter while a saved
|
||||
search could only provide the request adapter and a Vega visualization could additionally
|
||||
provide a Vega adapter.
|
||||
|
||||
There is no 1 to 1 relationship between adapters and views. An adapter could be used
|
||||
by multiple views and a view can use data from multiple adapters. It's up to the
|
||||
view to decide whether or not it wants to be shown for a given adapters list.
|
||||
|
||||
## Develop custom inspectors
|
||||
|
||||
You can extend the inspector panel by adding custom inspector views and inspector
|
||||
adapters via a plugin.
|
||||
|
||||
### Develop inspector views
|
||||
|
||||
To develop custom inspector views you can define your
|
||||
inspector view as follows:
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import { viewRegistry } from 'ui/inspector';
|
||||
|
||||
function MyInspectorComponent(props) {
|
||||
// props.adapters is the object of all adapters and may vary depending
|
||||
// on who and where this inspector was opened. You should check for all
|
||||
// adapters you need, in the below shouldShow method, before accessing
|
||||
// them here.
|
||||
return (
|
||||
<>
|
||||
My custom view....
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const MyLittleInspectorView = {
|
||||
// Title shown to select this view
|
||||
title: 'Display Name',
|
||||
// An icon id from the EUI icon list
|
||||
icon: 'iconName',
|
||||
// An order to sort the views (lower means first)
|
||||
order: 10,
|
||||
// An additional helptext, that wil
|
||||
help: `And additional help text, that will be shown in the inspector help.`,
|
||||
shouldShow(adapters) {
|
||||
// Only show if `someAdapter` is available. Make sure to check for
|
||||
// all adapters that you want to access in your view later on and
|
||||
// any additional condition you want to be true to be shown.
|
||||
return adapters.someAdapter;
|
||||
},
|
||||
// A React component, that will be used for rendering
|
||||
component: MyInspectorComponent
|
||||
};
|
||||
```
|
||||
|
||||
Then register your view in *setup* life-cycle with `inspector` plugin.
|
||||
|
||||
```ts
|
||||
class MyPlugin extends Plugin {
|
||||
setup(core, { inspector }) {
|
||||
inspector.registerView(MyLittleInspectorView);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Develop custom adapters
|
||||
|
||||
An inspector adapter is just a plain JavaScript class, that can e.g. be attached
|
||||
to custom visualization types, so an inspector view can show additional information for this
|
||||
visualization.
|
||||
|
||||
To add additional adapters to your visualization type, use the `inspectorAdapters.custom`
|
||||
object when defining the visualization type:
|
||||
|
||||
```js
|
||||
class MyCustomInspectorAdapter {
|
||||
// ....
|
||||
}
|
||||
|
||||
// inside your visualization type description (usually passed to VisFactory.create...Type)
|
||||
{
|
||||
// ...
|
||||
inspectorAdapters: {
|
||||
custom: {
|
||||
someAdapter: MyCustomInspectorAdapter
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
An instance of MyCustomInspectorAdapter will now be available on each visualization
|
||||
of that type and can be accessed via `vis.API.inspectorAdapters.someInspector`.
|
||||
|
||||
Custom inspector views can now check for the presence of `adapters.someAdapter`
|
||||
in their `shouldShow` method and use this adapter in their component.
|
6
src/plugins/inspector/kibana.json
Normal file
6
src/plugins/inspector/kibana.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"id": "inspector",
|
||||
"version": "kibana",
|
||||
"server": false,
|
||||
"ui": true
|
||||
}
|
|
@ -17,4 +17,5 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { InspectorView } from './inspector_view';
|
||||
export { DataAdapter, FormattedData } from './data';
|
||||
export { RequestAdapter, RequestStatus } from './request';
|
|
@ -48,11 +48,11 @@ export class RequestResponder {
|
|||
const startDate = new Date(this.request.startTime);
|
||||
|
||||
this.request.stats.requestTimestamp = {
|
||||
label: i18n.translate('common.ui.inspector.reqTimestampKey', {
|
||||
label: i18n.translate('inspector.reqTimestampKey', {
|
||||
defaultMessage: 'Request timestamp',
|
||||
}),
|
||||
value: startDate.toISOString(),
|
||||
description: i18n.translate('common.ui.inspector.reqTimestampDescription', {
|
||||
description: i18n.translate('inspector.reqTimestampDescription', {
|
||||
defaultMessage: 'Time when the start of the request has been logged',
|
||||
}),
|
||||
};
|
28
src/plugins/inspector/public/index.ts
Normal file
28
src/plugins/inspector/public/index.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { PluginInitializerContext } from '../../../core/public';
|
||||
import { InspectorPublicPlugin } from './plugin';
|
||||
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new InspectorPublicPlugin(initializerContext);
|
||||
}
|
||||
|
||||
export { InspectorPublicPlugin as Plugin, Setup, Start } from './plugin';
|
||||
export * from './types';
|
78
src/plugins/inspector/public/mocks.ts
Normal file
78
src/plugins/inspector/public/mocks.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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 { Setup as PluginSetup, Start as PluginStart } from '.';
|
||||
import { InspectorViewRegistry } from './view_registry';
|
||||
import { plugin as pluginInitializer } from '.';
|
||||
// eslint-disable-next-line
|
||||
import { coreMock } from '../../../core/public/mocks';
|
||||
|
||||
export type Setup = jest.Mocked<PluginSetup>;
|
||||
export type Start = jest.Mocked<PluginStart>;
|
||||
|
||||
const createSetupContract = (): Setup => {
|
||||
const views = new InspectorViewRegistry();
|
||||
|
||||
const setupContract: Setup = {
|
||||
registerView: jest.fn(views.register.bind(views)),
|
||||
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
|
||||
views,
|
||||
},
|
||||
};
|
||||
return setupContract;
|
||||
};
|
||||
|
||||
const createStartContract = (): Start => {
|
||||
const startContract: Start = {
|
||||
isAvailable: jest.fn(),
|
||||
open: jest.fn(),
|
||||
};
|
||||
|
||||
const openResult = {
|
||||
onClose: Promise.resolve(undefined),
|
||||
close: jest.fn(() => Promise.resolve(undefined)),
|
||||
} as ReturnType<Start['open']>;
|
||||
startContract.open.mockImplementation(() => openResult);
|
||||
|
||||
return startContract;
|
||||
};
|
||||
|
||||
const createPlugin = async () => {
|
||||
const pluginInitializerContext = coreMock.createPluginInitializerContext();
|
||||
const coreSetup = coreMock.createSetup();
|
||||
const coreStart = coreMock.createStart();
|
||||
const plugin = pluginInitializer(pluginInitializerContext);
|
||||
const setup = await plugin.setup(coreSetup);
|
||||
|
||||
return {
|
||||
pluginInitializerContext,
|
||||
coreSetup,
|
||||
coreStart,
|
||||
plugin,
|
||||
setup,
|
||||
doStart: async () => await plugin.start(coreStart),
|
||||
};
|
||||
};
|
||||
|
||||
export const inspectorPluginMock = {
|
||||
createSetupContract,
|
||||
createStartContract,
|
||||
createPlugin,
|
||||
};
|
112
src/plugins/inspector/public/plugin.tsx
Normal file
112
src/plugins/inspector/public/plugin.tsx
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import * as React from 'react';
|
||||
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public';
|
||||
import { InspectorViewRegistry } from './view_registry';
|
||||
import { Adapters, InspectorOptions, InspectorSession } from './types';
|
||||
import { InspectorPanel } from './ui/inspector_panel';
|
||||
|
||||
export interface Setup {
|
||||
registerView: InspectorViewRegistry['register'];
|
||||
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
|
||||
views: InspectorViewRegistry;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Start {
|
||||
/**
|
||||
* Checks if a inspector panel could be shown based on the passed adapters.
|
||||
*
|
||||
* @param {object} adapters - An object of adapters. This should be the same
|
||||
* you would pass into `open`.
|
||||
* @returns {boolean} True, if a call to `open` with the same adapters
|
||||
* would have shown the inspector panel, false otherwise.
|
||||
*/
|
||||
isAvailable: (adapters?: Adapters) => boolean;
|
||||
|
||||
/**
|
||||
* Opens the inspector panel for the given adapters and close any previously opened
|
||||
* inspector panel. The previously panel will be closed also if no new panel will be
|
||||
* opened (e.g. because of the passed adapters no view is available). You can use
|
||||
* {@link InspectorSession#close} on the return value to close that opened panel again.
|
||||
*
|
||||
* @param {object} adapters - An object of adapters for which you want to show
|
||||
* the inspector panel.
|
||||
* @param {InspectorOptions} options - Options that configure the inspector. See InspectorOptions type.
|
||||
* @return {InspectorSession} The session instance for the opened inspector.
|
||||
* @throws {Error}
|
||||
*/
|
||||
open: (adapters: Adapters, options?: InspectorOptions) => InspectorSession;
|
||||
}
|
||||
|
||||
export class InspectorPublicPlugin implements Plugin<Setup, Start> {
|
||||
views: InspectorViewRegistry | undefined;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public async setup(core: CoreSetup) {
|
||||
this.views = new InspectorViewRegistry();
|
||||
|
||||
return {
|
||||
registerView: this.views!.register.bind(this.views),
|
||||
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
|
||||
views: this.views,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {
|
||||
const isAvailable: Start['isAvailable'] = adapters =>
|
||||
this.views!.getVisible(adapters).length > 0;
|
||||
|
||||
const closeButtonLabel = i18n.translate('inspector.closeButton', {
|
||||
defaultMessage: 'Close Inspector',
|
||||
});
|
||||
|
||||
const open: Start['open'] = (adapters, options = {}) => {
|
||||
const views = this.views!.getVisible(adapters);
|
||||
|
||||
// Don't open inspector if there are no views available for the passed adapters
|
||||
if (!views || views.length === 0) {
|
||||
throw new Error(`Tried to open an inspector without views being available.
|
||||
Make sure to call Inspector.isAvailable() with the same adapters before to check
|
||||
if an inspector can be shown.`);
|
||||
}
|
||||
|
||||
return core.overlays.openFlyout(
|
||||
<InspectorPanel views={views} adapters={adapters} title={options.title} />,
|
||||
{
|
||||
'data-test-subj': 'inspectorPanel',
|
||||
closeButtonAriaLabel: closeButtonLabel,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
isAvailable,
|
||||
open,
|
||||
};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
52
src/plugins/inspector/public/test/is_available.test.ts
Normal file
52
src/plugins/inspector/public/test/is_available.test.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 { inspectorPluginMock } from '../mocks';
|
||||
import { DataAdapter } from '../adapters/data/data_adapter';
|
||||
import { RequestAdapter } from '../adapters/request/request_adapter';
|
||||
|
||||
const adapter1 = new DataAdapter();
|
||||
const adapter2 = new RequestAdapter();
|
||||
|
||||
describe('inspector', () => {
|
||||
describe('isAvailable()', () => {
|
||||
it('should return false if no view would be available', async () => {
|
||||
const { doStart } = await inspectorPluginMock.createPlugin();
|
||||
const start = await doStart();
|
||||
expect(start.isAvailable({ adapter1 })).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true if views would be available, false otherwise', async () => {
|
||||
const { setup, doStart } = await inspectorPluginMock.createPlugin();
|
||||
|
||||
setup.registerView({
|
||||
title: 'title',
|
||||
help: 'help',
|
||||
shouldShow(adapters: any) {
|
||||
return 'adapter1' in adapters;
|
||||
},
|
||||
} as any);
|
||||
|
||||
const start = await doStart();
|
||||
|
||||
expect(start.isAvailable({ adapter1 })).toBe(true);
|
||||
expect(start.isAvailable({ adapter2 })).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
30
src/plugins/inspector/public/test/open.test.ts
Normal file
30
src/plugins/inspector/public/test/open.test.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { inspectorPluginMock } from '../mocks';
|
||||
|
||||
describe('inspector', () => {
|
||||
describe('open()', () => {
|
||||
it('should throw an error if no views available', async () => {
|
||||
const { doStart } = await inspectorPluginMock.createPlugin();
|
||||
const start = await doStart();
|
||||
expect(() => start.open({})).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
75
src/plugins/inspector/public/types.ts
Normal file
75
src/plugins/inspector/public/types.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 { OverlayRef } from '../../../core/public';
|
||||
|
||||
/**
|
||||
* The interface that the adapters used to open an inspector have to fullfill.
|
||||
*/
|
||||
export interface Adapters {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* The props interface that a custom inspector view component, that will be passed
|
||||
* to {@link InspectorViewDescription#component}, must use.
|
||||
*/
|
||||
export interface InspectorViewProps {
|
||||
/**
|
||||
* Adapters used to open the inspector.
|
||||
*/
|
||||
adapters: Adapters;
|
||||
/**
|
||||
* The title that the inspector is currently using e.g. a visualization name.
|
||||
*/
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* An object describing an inspector view.
|
||||
* @typedef {object} InspectorViewDescription
|
||||
* @property {string} title - The title that will be used to present that view.
|
||||
* @property {string} icon - An icon name to present this view. Must match an EUI icon.
|
||||
* @property {React.ComponentType<InspectorViewProps>} component - The actual React component to render that view.
|
||||
* @property {number} [order=9000] - An order for this view. Views are ordered from lower
|
||||
* order values to higher order values in the UI.
|
||||
* @property {string} [help=''] - An help text for this view, that gives a brief description
|
||||
* of this view.
|
||||
* @property {viewShouldShowFunc} [shouldShow] - A function, that determines whether
|
||||
* this view should be visible for a given collection of adapters. If not specified
|
||||
* the view will always be visible.
|
||||
*/
|
||||
export interface InspectorViewDescription {
|
||||
component: React.ComponentType<InspectorViewProps>;
|
||||
help?: string;
|
||||
order?: number;
|
||||
shouldShow?: (adapters: Adapters) => boolean;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options that can be specified when opening the inspector.
|
||||
* @property {string} title - An optional title, that will be shown in the header
|
||||
* of the inspector. Can be used to give more context about what is being inspected.
|
||||
*/
|
||||
export interface InspectorOptions {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export type InspectorSession = OverlayRef;
|
|
@ -112,7 +112,6 @@ exports[`InspectorPanel should render as expected 1`] = `
|
|||
"timeZone": null,
|
||||
}
|
||||
}
|
||||
onClose={[Function]}
|
||||
title="Inspector"
|
||||
views={
|
||||
Array [
|
||||
|
@ -217,7 +216,7 @@ exports[`InspectorPanel should render as expected 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="View: {viewName}"
|
||||
id="common.ui.inspector.view"
|
||||
id="inspector.view"
|
||||
values={
|
||||
Object {
|
||||
"viewName": "View 1",
|
||||
|
@ -298,7 +297,7 @@ exports[`InspectorPanel should render as expected 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="View: {viewName}"
|
||||
id="common.ui.inspector.view"
|
||||
id="inspector.view"
|
||||
values={
|
||||
Object {
|
||||
"viewName": "View 1",
|
||||
|
@ -322,20 +321,30 @@ exports[`InspectorPanel should render as expected 1`] = `
|
|||
</EuiFlexGroup>
|
||||
</div>
|
||||
</EuiFlyoutHeader>
|
||||
<component
|
||||
adapters={
|
||||
Object {
|
||||
"bardapter": Object {},
|
||||
"foodapter": Object {
|
||||
"foo": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
title="Inspector"
|
||||
>
|
||||
<h1>
|
||||
View 1
|
||||
</h1>
|
||||
</component>
|
||||
<EuiFlyoutBody>
|
||||
<div
|
||||
className="euiFlyoutBody"
|
||||
>
|
||||
<div
|
||||
className="euiFlyoutBody__overflow"
|
||||
>
|
||||
<component
|
||||
adapters={
|
||||
Object {
|
||||
"bardapter": Object {},
|
||||
"foodapter": Object {
|
||||
"foo": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
title="Inspector"
|
||||
>
|
||||
<h1>
|
||||
View 1
|
||||
</h1>
|
||||
</component>
|
||||
</div>
|
||||
</div>
|
||||
</EuiFlyoutBody>
|
||||
</InspectorPanel>
|
||||
`;
|
|
@ -18,65 +18,55 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { InspectorPanel } from './inspector_panel';
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { InspectorPanel } from './inspector_panel';
|
||||
import { Adapters, InspectorViewDescription } from '../types';
|
||||
|
||||
describe('InspectorPanel', () => {
|
||||
|
||||
let adapters;
|
||||
let views;
|
||||
let adapters: Adapters;
|
||||
let views: InspectorViewDescription[];
|
||||
|
||||
beforeEach(() => {
|
||||
adapters = {
|
||||
foodapter: {
|
||||
foo() { return 42; }
|
||||
foo() {
|
||||
return 42;
|
||||
},
|
||||
},
|
||||
bardapter: {
|
||||
|
||||
}
|
||||
bardapter: {},
|
||||
};
|
||||
views = [
|
||||
{
|
||||
title: 'View 1',
|
||||
order: 200,
|
||||
component: () => (<h1>View 1</h1>),
|
||||
}, {
|
||||
component: () => <h1>View 1</h1>,
|
||||
},
|
||||
{
|
||||
title: 'Foo View',
|
||||
order: 100,
|
||||
component: () => (<h1>Foo view</h1>),
|
||||
shouldShow(adapters) {
|
||||
return adapters.foodapter;
|
||||
}
|
||||
}, {
|
||||
component: () => <h1>Foo view</h1>,
|
||||
shouldShow(adapters2: Adapters) {
|
||||
return adapters2.foodapter;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Never',
|
||||
order: 200,
|
||||
component: () => null,
|
||||
shouldShow() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
it('should render as expected', () => {
|
||||
const component = mountWithIntl(
|
||||
<InspectorPanel
|
||||
adapters={adapters}
|
||||
onClose={() => true}
|
||||
views={views}
|
||||
/>
|
||||
);
|
||||
const component = mountWithIntl(<InspectorPanel adapters={adapters} views={views} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not allow updating adapters', () => {
|
||||
const component = mountWithIntl(
|
||||
<InspectorPanel
|
||||
adapters={adapters}
|
||||
onClose={() => true}
|
||||
views={views}
|
||||
/>
|
||||
);
|
||||
const component = mountWithIntl(<InspectorPanel adapters={adapters} views={views} />);
|
||||
adapters.notAllowed = {};
|
||||
expect(() => component.setProps({ adapters })).toThrow();
|
||||
});
|
|
@ -20,52 +20,73 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyoutHeader,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui';
|
||||
import { Adapters, InspectorViewDescription } from '../types';
|
||||
import { InspectorViewChooser } from './inspector_view_chooser';
|
||||
|
||||
function hasAdaptersChanged(oldAdapters, newAdapters) {
|
||||
return Object.keys(oldAdapters).length !== Object.keys(newAdapters).length
|
||||
|| Object.keys(oldAdapters).some(key => oldAdapters[key] !== newAdapters[key]);
|
||||
function hasAdaptersChanged(oldAdapters: Adapters, newAdapters: Adapters) {
|
||||
return (
|
||||
Object.keys(oldAdapters).length !== Object.keys(newAdapters).length ||
|
||||
Object.keys(oldAdapters).some(key => oldAdapters[key] !== newAdapters[key])
|
||||
);
|
||||
}
|
||||
|
||||
const inspectorTitle = i18n.translate('common.ui.inspector.title', {
|
||||
const inspectorTitle = i18n.translate('inspector.title', {
|
||||
defaultMessage: 'Inspector',
|
||||
});
|
||||
|
||||
class InspectorPanel extends Component {
|
||||
interface InspectorPanelProps {
|
||||
adapters: Adapters;
|
||||
title?: string;
|
||||
views: InspectorViewDescription[];
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedView: props.views[0],
|
||||
views: props.views,
|
||||
// Clone adapters array so we can validate that this prop never change
|
||||
adapters: { ...props.adapters },
|
||||
};
|
||||
}
|
||||
interface InspectorPanelState {
|
||||
selectedView: InspectorViewDescription;
|
||||
views: InspectorViewDescription[];
|
||||
adapters: Adapters;
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
export class InspectorPanel extends Component<InspectorPanelProps, InspectorPanelState> {
|
||||
static defaultProps = {
|
||||
title: inspectorTitle,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
adapters: PropTypes.object.isRequired,
|
||||
views: (props: InspectorPanelProps, propName: string, componentName: string) => {
|
||||
if (!Array.isArray(props.views) || props.views.length < 1) {
|
||||
throw new Error(
|
||||
`${propName} prop must be an array of at least one element in ${componentName}.`
|
||||
);
|
||||
}
|
||||
},
|
||||
title: PropTypes.string,
|
||||
};
|
||||
|
||||
state: InspectorPanelState = {
|
||||
selectedView: this.props.views[0],
|
||||
views: this.props.views,
|
||||
// Clone adapters array so we can validate that this prop never change
|
||||
adapters: { ...this.props.adapters },
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(nextProps: InspectorPanelProps, prevState: InspectorPanelState) {
|
||||
if (hasAdaptersChanged(prevState.adapters, nextProps.adapters)) {
|
||||
throw new Error('Adapters are not allowed to be changed on an open InspectorPanel.');
|
||||
}
|
||||
const selectedViewMustChange = nextProps.views !== prevState.views
|
||||
&& !nextProps.views.includes(prevState.selectedView);
|
||||
const selectedViewMustChange =
|
||||
nextProps.views !== prevState.views && !nextProps.views.includes(prevState.selectedView);
|
||||
return {
|
||||
views: nextProps.views,
|
||||
selectedView: selectedViewMustChange ? nextProps.views[0] : prevState.selectedView,
|
||||
};
|
||||
}
|
||||
|
||||
onViewSelected = (view) => {
|
||||
onViewSelected = (view: InspectorViewDescription) => {
|
||||
if (view !== this.state.selectedView) {
|
||||
this.setState({
|
||||
selectedView: view
|
||||
selectedView: view,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -74,7 +95,7 @@ class InspectorPanel extends Component {
|
|||
return (
|
||||
<this.state.selectedView.component
|
||||
adapters={this.props.adapters}
|
||||
title={this.props.title}
|
||||
title={this.props.title || ''}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -86,13 +107,10 @@ class InspectorPanel extends Component {
|
|||
return (
|
||||
<React.Fragment>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
alignItems="center"
|
||||
>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={true}>
|
||||
<EuiTitle size="s">
|
||||
<h1>{ title }</h1>
|
||||
<h1>{title}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -104,26 +122,8 @@ class InspectorPanel extends Component {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutHeader>
|
||||
{ this.renderSelectedPanel() }
|
||||
<EuiFlyoutBody>{this.renderSelectedPanel()}</EuiFlyoutBody>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InspectorPanel.defaultProps = {
|
||||
title: inspectorTitle,
|
||||
};
|
||||
|
||||
InspectorPanel.propTypes = {
|
||||
adapters: PropTypes.object.isRequired,
|
||||
views: (props, propName, componentName) => {
|
||||
if (!Array.isArray(props[propName]) || props[propName].length < 1) {
|
||||
throw new Error(
|
||||
`${propName} prop must be an array of at least one element in ${componentName}.`
|
||||
);
|
||||
}
|
||||
},
|
||||
title: PropTypes.string,
|
||||
};
|
||||
|
||||
export { InspectorPanel };
|
|
@ -20,7 +20,6 @@
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiContextMenuItem,
|
||||
|
@ -28,26 +27,42 @@ import {
|
|||
EuiPopover,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { InspectorViewDescription } from '../types';
|
||||
|
||||
class InspectorViewChooser extends Component {
|
||||
interface Props {
|
||||
views: InspectorViewDescription[];
|
||||
onViewSelected: (view: InspectorViewDescription) => void;
|
||||
selectedView: InspectorViewDescription;
|
||||
}
|
||||
|
||||
state = {
|
||||
isSelectorOpen: false
|
||||
interface State {
|
||||
isSelectorOpen: boolean;
|
||||
}
|
||||
|
||||
export class InspectorViewChooser extends Component<Props, State> {
|
||||
static propTypes = {
|
||||
views: PropTypes.array.isRequired,
|
||||
onViewSelected: PropTypes.func.isRequired,
|
||||
selectedView: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state: State = {
|
||||
isSelectorOpen: false,
|
||||
};
|
||||
|
||||
toggleSelector = () => {
|
||||
this.setState((prev) => ({
|
||||
isSelectorOpen: !prev.isSelectorOpen
|
||||
this.setState(prev => ({
|
||||
isSelectorOpen: !prev.isSelectorOpen,
|
||||
}));
|
||||
};
|
||||
|
||||
closeSelector = () => {
|
||||
this.setState({
|
||||
isSelectorOpen: false
|
||||
isSelectorOpen: false,
|
||||
});
|
||||
};
|
||||
|
||||
renderView = (view, index) => {
|
||||
renderView = (view: InspectorViewDescription, index: number) => {
|
||||
return (
|
||||
<EuiContextMenuItem
|
||||
key={index}
|
||||
|
@ -62,7 +77,7 @@ class InspectorViewChooser extends Component {
|
|||
{view.title}
|
||||
</EuiContextMenuItem>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderViewButton() {
|
||||
return (
|
||||
|
@ -74,7 +89,7 @@ class InspectorViewChooser extends Component {
|
|||
data-test-subj="inspectorViewChooser"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="common.ui.inspector.view"
|
||||
id="inspector.view"
|
||||
defaultMessage="View: {viewName}"
|
||||
values={{ viewName: this.props.selectedView.title }}
|
||||
/>
|
||||
|
@ -84,12 +99,9 @@ class InspectorViewChooser extends Component {
|
|||
|
||||
renderSingleView() {
|
||||
return (
|
||||
<EuiToolTip
|
||||
position="bottom"
|
||||
content={this.props.selectedView.help}
|
||||
>
|
||||
<EuiToolTip position="bottom" content={this.props.selectedView.help}>
|
||||
<FormattedMessage
|
||||
id="common.ui.inspector.view"
|
||||
id="inspector.view"
|
||||
defaultMessage="View: {viewName}"
|
||||
values={{ viewName: this.props.selectedView.title }}
|
||||
/>
|
||||
|
@ -117,18 +129,8 @@ class InspectorViewChooser extends Component {
|
|||
anchorPosition="downRight"
|
||||
repositionOnScroll
|
||||
>
|
||||
<EuiContextMenuPanel
|
||||
items={views.map(this.renderView)}
|
||||
/>
|
||||
<EuiContextMenuPanel items={views.map(this.renderView)} />
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InspectorViewChooser.propTypes = {
|
||||
views: PropTypes.array.isRequired,
|
||||
onViewSelected: PropTypes.func.isRequired,
|
||||
selectedView: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export { InspectorViewChooser };
|
|
@ -17,7 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { InspectorViewDescription, InspectorViewRegistry } from './view_registry';
|
||||
import { InspectorViewRegistry } from './view_registry';
|
||||
import { InspectorViewDescription } from './types';
|
||||
|
||||
import { Adapters } from './types';
|
||||
|
74
src/plugins/inspector/public/view_registry.ts
Normal file
74
src/plugins/inspector/public/view_registry.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 { EventEmitter } from 'events';
|
||||
import { Adapters, InspectorViewDescription } from './types';
|
||||
|
||||
/**
|
||||
* @callback viewShouldShowFunc
|
||||
* @param {object} adapters - A list of adapters to check whether or not this view
|
||||
* should be shown for.
|
||||
* @returns {boolean} true - if this view should be shown for the given adapters.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A registry that will hold inspector views.
|
||||
*/
|
||||
export class InspectorViewRegistry extends EventEmitter {
|
||||
private views: InspectorViewDescription[] = [];
|
||||
|
||||
/**
|
||||
* Register a new inspector view to the registry. Check the README.md in the
|
||||
* inspector directory for more information of the object format to register
|
||||
* here. This will also emit a 'change' event on the registry itself.
|
||||
*
|
||||
* @param {InspectorViewDescription} view - The view description to add to the registry.
|
||||
*/
|
||||
public register(view: InspectorViewDescription): void {
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
this.views.push(view);
|
||||
// Keep registry sorted by the order property
|
||||
this.views.sort((a, b) => (a.order || Number.MAX_VALUE) - (b.order || Number.MAX_VALUE));
|
||||
this.emit('change');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all views currently registered with the registry.
|
||||
* @returns {InspectorViewDescription[]} A by `order` sorted list of all registered
|
||||
* inspector views.
|
||||
*/
|
||||
public getAll(): InspectorViewDescription[] {
|
||||
return this.views;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all registered views, that want to be visible for the specified adapters.
|
||||
* @param {object} adapters - an adapter configuration
|
||||
* @returns {InspectorViewDescription[]} All inespector view descriptions visible
|
||||
* for the specific adapters.
|
||||
*/
|
||||
public getVisible(adapters?: Adapters): InspectorViewDescription[] {
|
||||
if (!adapters) {
|
||||
return [];
|
||||
}
|
||||
return this.views.filter(view => !view.shouldShow || view.shouldShow(adapters));
|
||||
}
|
||||
}
|
|
@ -23,8 +23,7 @@ import { render, unmountComponentAtNode } from 'react-dom';
|
|||
import { uiModules } from 'ui/modules';
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
import { RequestAdapter } from 'ui/inspector/adapters/request';
|
||||
import { DataAdapter } from 'ui/inspector/adapters/data';
|
||||
import { RequestAdapter, DataAdapter } from 'ui/inspector/adapters';
|
||||
import { runPipeline } from 'ui/visualize/loader/pipeline_helpers';
|
||||
import { visualizationLoader } from 'ui/visualize/loader/visualization_loader';
|
||||
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { InspectorView } from 'ui/inspector';
|
||||
import { MapDetails } from './map_details';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
|
@ -38,14 +36,12 @@ class MapViewComponent extends Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<InspectorView>
|
||||
<MapDetails
|
||||
centerLon={this.state.stats.center[0]}
|
||||
centerLat={this.state.stats.center[1]}
|
||||
zoom={this.state.stats.zoom}
|
||||
mapStyle={this.state.mapStyle}
|
||||
/>
|
||||
</InspectorView>
|
||||
<MapDetails
|
||||
centerLon={this.state.stats.center[0]}
|
||||
centerLat={this.state.stats.center[1]}
|
||||
zoom={this.state.stats.zoom}
|
||||
mapStyle={this.state.mapStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -481,11 +481,11 @@
|
|||
"common.ui.indexPattern.unableWriteLabel": "インデックスパターンを書き込めません!このインデックスパターンへの最新の変更を取得するにな、ページを更新してください。",
|
||||
"common.ui.indexPattern.unknownFieldErrorMessage": "インデックスパターン「{title}」のフィールド「{name}」が不明なフィールドタイプを使用しています。",
|
||||
"common.ui.indexPattern.unknownFieldHeader": "不明なフィールドタイプ {type}",
|
||||
"common.ui.inspector.closeButton": "インスペクターを閉じる",
|
||||
"common.ui.inspector.reqTimestampDescription": "リクエストの開始が記録された時刻です",
|
||||
"common.ui.inspector.reqTimestampKey": "リクエストのタイムスタンプ",
|
||||
"common.ui.inspector.title": "インスペクター",
|
||||
"common.ui.inspector.view": "{viewName} を表示",
|
||||
"inspector.closeButton": "インスペクターを閉じる",
|
||||
"inspector.reqTimestampDescription": "リクエストの開始が記録された時刻です",
|
||||
"inspector.reqTimestampKey": "リクエストのタイムスタンプ",
|
||||
"inspector.title": "インスペクター",
|
||||
"inspector.view": "{viewName} を表示",
|
||||
"common.ui.legacyBrowserMessage": "この Kibana インストレーションは、現在ご使用のブラウザが満たしていない厳格なセキュリティ要件が有効になっています。",
|
||||
"common.ui.legacyBrowserTitle": "ブラウザをアップグレードしてください",
|
||||
"common.ui.management.breadcrumb": "管理",
|
||||
|
|
|
@ -481,11 +481,11 @@
|
|||
"common.ui.indexPattern.unableWriteLabel": "无法写入索引模式!请刷新页面以获取此索引模式的最新更改。",
|
||||
"common.ui.indexPattern.unknownFieldErrorMessage": "indexPattern “{title}” 中的字段 “{name}” 使用未知字段类型。",
|
||||
"common.ui.indexPattern.unknownFieldHeader": "未知字段类型 {type}",
|
||||
"common.ui.inspector.closeButton": "关闭检查器",
|
||||
"common.ui.inspector.reqTimestampDescription": "记录请求启动的时间",
|
||||
"common.ui.inspector.reqTimestampKey": "请求时间戳",
|
||||
"common.ui.inspector.title": "检查器",
|
||||
"common.ui.inspector.view": "视图:{viewName}",
|
||||
"inspector.closeButton": "关闭检查器",
|
||||
"inspector.reqTimestampDescription": "记录请求启动的时间",
|
||||
"inspector.reqTimestampKey": "请求时间戳",
|
||||
"inspector.title": "检查器",
|
||||
"inspector.view": "视图:{viewName}",
|
||||
"common.ui.legacyBrowserMessage": "此 Kibana 安装启用了当前浏览器未满足的严格安全要求。",
|
||||
"common.ui.legacyBrowserTitle": "请升级您的浏览器",
|
||||
"common.ui.management.breadcrumb": "管理",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue