[Inspector Views] [Request View] - Migrate inspector_views to new platform (#43191)
* [Inspector Views] [Request View] - Migrate inspector_views to new platform * fix i18n keys * rename scss files * fix PR comments
This commit is contained in:
parent
c19a2d5b57
commit
9f0de8dc6c
|
@ -8,4 +8,6 @@
|
|||
// insChart__legend-isLoading
|
||||
|
||||
@import 'data/index';
|
||||
@import 'requests/index';
|
||||
|
||||
// Temporary reference
|
||||
@import '../../../../plugins/inspector/public/views/index';
|
||||
|
|
|
@ -18,9 +18,6 @@
|
|||
*/
|
||||
|
||||
import { DataView } from './data/data_view';
|
||||
import { RequestsView } from './requests/requests_view';
|
||||
|
||||
import { viewRegistry } from 'ui/inspector';
|
||||
|
||||
viewRegistry.register(DataView);
|
||||
viewRegistry.register(RequestsView);
|
||||
|
|
|
@ -1,125 +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 React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
EuiTab,
|
||||
EuiTabs,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import {
|
||||
RequestDetailsRequest,
|
||||
RequestDetailsResponse,
|
||||
RequestDetailsStats,
|
||||
} from './details';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
const DETAILS = [
|
||||
{
|
||||
name: 'Statistics',
|
||||
label: i18n.translate('inspectorViews.requests.statisticsTabLabel', {
|
||||
defaultMessage: 'Statistics'
|
||||
}),
|
||||
component: RequestDetailsStats
|
||||
},
|
||||
{
|
||||
name: 'Request',
|
||||
label: i18n.translate('inspectorViews.requests.requestTabLabel', {
|
||||
defaultMessage: 'Request'
|
||||
}),
|
||||
component: RequestDetailsRequest
|
||||
},
|
||||
{
|
||||
name: 'Response',
|
||||
label: i18n.translate('inspectorViews.requests.responseTabLabel', {
|
||||
defaultMessage: 'Response'
|
||||
}),
|
||||
component: RequestDetailsResponse
|
||||
},
|
||||
];
|
||||
|
||||
class RequestDetails extends Component {
|
||||
|
||||
state = {
|
||||
availableDetails: [],
|
||||
selectedDetail: null,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
const selectedDetail = prevState && prevState.selectedDetail;
|
||||
const availableDetails = DETAILS.filter(detail =>
|
||||
!detail.component.shouldShow || detail.component.shouldShow(nextProps.request)
|
||||
);
|
||||
// If the previously selected detail is still available we want to stay
|
||||
// on this tab and not set another selectedDetail.
|
||||
if (selectedDetail && availableDetails.includes(selectedDetail)) {
|
||||
return { availableDetails };
|
||||
}
|
||||
|
||||
return {
|
||||
availableDetails: availableDetails,
|
||||
selectedDetail: availableDetails[0]
|
||||
};
|
||||
}
|
||||
|
||||
selectDetailsTab = (detail) => {
|
||||
if (detail !== this.state.selectedDetail) {
|
||||
this.setState({
|
||||
selectedDetail: detail
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderDetailTab = (detail) => {
|
||||
return (
|
||||
<EuiTab
|
||||
key={detail.name}
|
||||
isSelected={detail === this.state.selectedDetail}
|
||||
onClick={() => this.selectDetailsTab(detail)}
|
||||
data-test-subj={`inspectorRequestDetail${detail.name}`}
|
||||
>
|
||||
{detail.label}
|
||||
</EuiTab>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.availableDetails.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const DetailComponent = this.state.selectedDetail.component;
|
||||
return (
|
||||
<div>
|
||||
<EuiTabs size="s">
|
||||
{ this.state.availableDetails.map(this.renderDetailTab) }
|
||||
</EuiTabs>
|
||||
<DetailComponent
|
||||
request={this.props.request}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RequestDetails.propTypes = {
|
||||
request: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export { RequestDetails };
|
|
@ -52,11 +52,13 @@ export interface RequestParams {
|
|||
}
|
||||
|
||||
export interface RequestStatistics {
|
||||
[key: string]: {
|
||||
label: string;
|
||||
description?: string;
|
||||
value: any;
|
||||
};
|
||||
[key: string]: RequestStatistic;
|
||||
}
|
||||
|
||||
export interface RequestStatistic {
|
||||
label: string;
|
||||
description?: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
|
|
|
@ -24,6 +24,8 @@ import { InspectorViewRegistry } from './view_registry';
|
|||
import { Adapters, InspectorOptions, InspectorSession } from './types';
|
||||
import { InspectorPanel } from './ui/inspector_panel';
|
||||
|
||||
import { RequestsView } from './views';
|
||||
|
||||
export interface Setup {
|
||||
registerView: InspectorViewRegistry['register'];
|
||||
|
||||
|
@ -66,6 +68,8 @@ export class InspectorPublicPlugin implements Plugin<Setup, Start> {
|
|||
public async setup(core: CoreSetup) {
|
||||
this.views = new InspectorViewRegistry();
|
||||
|
||||
this.views.register(RequestsView);
|
||||
|
||||
return {
|
||||
registerView: this.views!.register.bind(this.views),
|
||||
|
||||
|
|
1
src/plugins/inspector/public/views/_index.scss
Normal file
1
src/plugins/inspector/public/views/_index.scss
Normal file
|
@ -0,0 +1 @@
|
|||
@import './requests/index';
|
|
@ -17,6 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export * from './req_details_request';
|
||||
export * from './req_details_response';
|
||||
export * from './req_details_stats';
|
||||
export { RequestsView } from './requests';
|
|
@ -17,24 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
EuiCodeBlock,
|
||||
} from '@elastic/eui';
|
||||
|
||||
function RequestDetailsResponse(props) {
|
||||
return (
|
||||
<EuiCodeBlock
|
||||
language="json"
|
||||
paddingSize="s"
|
||||
isCopyable
|
||||
data-test-subj="inspectorResponseBody"
|
||||
>
|
||||
{ JSON.stringify(props.request.response.json, null, 2) }
|
||||
</EuiCodeBlock>
|
||||
);
|
||||
}
|
||||
|
||||
RequestDetailsResponse.shouldShow = (request) => request.response && request.response.json;
|
||||
|
||||
export { RequestDetailsResponse };
|
||||
export { RequestDetailsRequest } from './req_details_request';
|
||||
export { RequestDetailsResponse } from './req_details_response';
|
||||
export { RequestDetailsStats } from './req_details_stats';
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiCodeBlock } from '@elastic/eui';
|
||||
import { Request } from '../../../../adapters/request/types';
|
||||
import { RequestDetailsProps } from '../types';
|
||||
|
||||
export class RequestDetailsRequest extends Component<RequestDetailsProps> {
|
||||
static propTypes = {
|
||||
request: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static shouldShow = (request: Request) => Boolean(request && request.json);
|
||||
|
||||
render() {
|
||||
const { json } = this.props.request;
|
||||
|
||||
if (!json) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiCodeBlock
|
||||
language="json"
|
||||
paddingSize="s"
|
||||
isCopyable
|
||||
data-test-subj="inspectorRequestBody"
|
||||
>
|
||||
{JSON.stringify(json, null, 2)}
|
||||
</EuiCodeBlock>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiCodeBlock } from '@elastic/eui';
|
||||
import { Request } from '../../../../adapters/request/types';
|
||||
import { RequestDetailsProps } from '../types';
|
||||
|
||||
export class RequestDetailsResponse extends Component<RequestDetailsProps> {
|
||||
static propTypes = {
|
||||
request: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static shouldShow = (request: Request) =>
|
||||
Boolean(RequestDetailsResponse.getResponseJson(request));
|
||||
|
||||
static getResponseJson = (request: Request) => (request.response ? request.response.json : null);
|
||||
|
||||
render() {
|
||||
const responseJSON = RequestDetailsResponse.getResponseJson(this.props.request);
|
||||
|
||||
if (!responseJSON) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiCodeBlock
|
||||
language="json"
|
||||
paddingSize="s"
|
||||
isCopyable
|
||||
data-test-subj="inspectorResponseBody"
|
||||
>
|
||||
{JSON.stringify(responseJSON, null, 2)}
|
||||
</EuiCodeBlock>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -28,58 +28,62 @@ import {
|
|||
EuiTableRowCell,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Request, RequestStatistic } from '../../../../adapters/request/types';
|
||||
import { RequestDetailsProps } from '../types';
|
||||
|
||||
class RequestDetailsStats extends Component {
|
||||
static shouldShow = (request) => !!request.stats && Object.keys(request.stats).length;
|
||||
// TODO: Replace by property once available
|
||||
interface RequestDetailsStatRow extends RequestStatistic {
|
||||
id: string;
|
||||
}
|
||||
|
||||
renderStatRow = (stat) => {
|
||||
export class RequestDetailsStats extends Component<RequestDetailsProps> {
|
||||
static propTypes = {
|
||||
request: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static shouldShow = (request: Request) =>
|
||||
Boolean(request.stats && Object.keys(request.stats).length);
|
||||
|
||||
renderStatRow = (stat: RequestDetailsStatRow) => {
|
||||
return [
|
||||
<EuiTableRow
|
||||
key={stat.id}
|
||||
>
|
||||
<EuiTableRow key={stat.id}>
|
||||
<EuiTableRowCell>
|
||||
<span className="insRequestDetailsStats__icon">
|
||||
{ stat.description &&
|
||||
{stat.description ? (
|
||||
<EuiIconTip
|
||||
aria-label={i18n.translate('inspectorViews.requests.descriptionRowIconAriaLabel', {
|
||||
defaultMessage: 'Description'
|
||||
aria-label={i18n.translate('inspector.requests.descriptionRowIconAriaLabel', {
|
||||
defaultMessage: 'Description',
|
||||
})}
|
||||
type="questionInCircle"
|
||||
color="subdued"
|
||||
content={stat.description}
|
||||
/>
|
||||
}
|
||||
{ !stat.description &&
|
||||
<EuiIcon
|
||||
type="empty"
|
||||
/>
|
||||
}
|
||||
) : (
|
||||
<EuiIcon type="empty" />
|
||||
)}
|
||||
</span>
|
||||
{stat.label}
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell>{stat.value}</EuiTableRowCell>
|
||||
</EuiTableRow>
|
||||
</EuiTableRow>,
|
||||
];
|
||||
};
|
||||
|
||||
render() {
|
||||
const { stats } = this.props.request;
|
||||
const sortedStats = Object.keys(stats).sort().map(id => ({ id, ...stats[id] }));
|
||||
// TODO: Replace by property once available
|
||||
|
||||
if (!stats) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sortedStats = Object.keys(stats)
|
||||
.sort()
|
||||
.map(id => ({ id, ...stats[id] } as RequestDetailsStatRow));
|
||||
|
||||
return (
|
||||
<EuiTable
|
||||
responsive={false}
|
||||
>
|
||||
<EuiTableBody>
|
||||
{ sortedStats.map(this.renderStatRow) }
|
||||
</EuiTableBody>
|
||||
<EuiTable responsive={false}>
|
||||
<EuiTableBody>{sortedStats.map(this.renderStatRow)}</EuiTableBody>
|
||||
</EuiTable>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RequestDetailsStats.propTypes = {
|
||||
request: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export { RequestDetailsStats };
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiTab, EuiTabs } from '@elastic/eui';
|
||||
|
||||
import { RequestDetailsRequest, RequestDetailsResponse, RequestDetailsStats } from './details';
|
||||
import { RequestDetailsProps } from './types';
|
||||
|
||||
interface RequestDetailsState {
|
||||
availableDetails: DetailViewData[];
|
||||
selectedDetail: DetailViewData | null;
|
||||
}
|
||||
|
||||
export interface DetailViewData {
|
||||
name: string;
|
||||
label: string;
|
||||
component: any;
|
||||
}
|
||||
|
||||
const DETAILS: DetailViewData[] = [
|
||||
{
|
||||
name: 'Statistics',
|
||||
label: i18n.translate('inspector.requests.statisticsTabLabel', {
|
||||
defaultMessage: 'Statistics',
|
||||
}),
|
||||
component: RequestDetailsStats,
|
||||
},
|
||||
{
|
||||
name: 'Request',
|
||||
label: i18n.translate('inspector.requests.requestTabLabel', {
|
||||
defaultMessage: 'Request',
|
||||
}),
|
||||
component: RequestDetailsRequest,
|
||||
},
|
||||
{
|
||||
name: 'Response',
|
||||
label: i18n.translate('inspector.requests.responseTabLabel', {
|
||||
defaultMessage: 'Response',
|
||||
}),
|
||||
component: RequestDetailsResponse,
|
||||
},
|
||||
];
|
||||
|
||||
export class RequestDetails extends Component<RequestDetailsProps, RequestDetailsState> {
|
||||
static propTypes = {
|
||||
request: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
availableDetails: [],
|
||||
selectedDetail: null,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(nextProps: RequestDetailsProps, prevState: RequestDetailsState) {
|
||||
const selectedDetail = prevState && prevState.selectedDetail;
|
||||
const availableDetails = DETAILS.filter(
|
||||
(detail: DetailViewData) =>
|
||||
!detail.component.shouldShow || detail.component.shouldShow(nextProps.request)
|
||||
);
|
||||
// If the previously selected detail is still available we want to stay
|
||||
// on this tab and not set another selectedDetail.
|
||||
if (selectedDetail && availableDetails.includes(selectedDetail)) {
|
||||
return { availableDetails };
|
||||
}
|
||||
|
||||
return {
|
||||
availableDetails,
|
||||
selectedDetail: availableDetails[0],
|
||||
};
|
||||
}
|
||||
|
||||
selectDetailsTab = (detail: DetailViewData) => {
|
||||
if (detail !== this.state.selectedDetail) {
|
||||
this.setState({
|
||||
selectedDetail: detail,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
static getSelectedDetailComponent(detail: DetailViewData | null) {
|
||||
return detail ? detail.component : null;
|
||||
}
|
||||
|
||||
renderDetailTab = (detail: DetailViewData) => {
|
||||
return (
|
||||
<EuiTab
|
||||
key={detail.name}
|
||||
isSelected={detail === this.state.selectedDetail}
|
||||
onClick={() => this.selectDetailsTab(detail)}
|
||||
data-test-subj={`inspectorRequestDetail${detail.name}`}
|
||||
>
|
||||
{detail.label}
|
||||
</EuiTab>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { selectedDetail, availableDetails } = this.state;
|
||||
const DetailComponent = RequestDetails.getSelectedDetailComponent(selectedDetail);
|
||||
|
||||
if (!availableDetails.length || !DetailComponent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiTabs size="s">{this.state.availableDetails.map(this.renderDetailTab)}</EuiTabs>
|
||||
<DetailComponent request={this.props.request} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -18,7 +18,9 @@
|
|||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
EuiBadge,
|
||||
|
@ -33,29 +35,43 @@ import {
|
|||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { RequestStatus } from '../../../adapters';
|
||||
import { Request } from '../../../adapters/request/types';
|
||||
|
||||
import { RequestStatus } from 'ui/inspector/adapters';
|
||||
interface RequestSelectorState {
|
||||
isPopoverOpen: boolean;
|
||||
}
|
||||
|
||||
interface RequestSelectorProps {
|
||||
requests: Request[];
|
||||
selectedRequest: Request;
|
||||
onRequestChanged: Function;
|
||||
}
|
||||
|
||||
export class RequestSelector extends Component<RequestSelectorProps, RequestSelectorState> {
|
||||
static propTypes = {
|
||||
requests: PropTypes.array.isRequired,
|
||||
selectedRequest: PropTypes.object.isRequired,
|
||||
onRequestChanged: PropTypes.func,
|
||||
};
|
||||
|
||||
class RequestSelector extends Component {
|
||||
state = {
|
||||
isPopoverOpen: false,
|
||||
};
|
||||
|
||||
togglePopover = () => {
|
||||
this.setState((prevState) => ({
|
||||
this.setState((prevState: RequestSelectorState) => ({
|
||||
isPopoverOpen: !prevState.isPopoverOpen,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
closePopover = () => {
|
||||
this.setState({
|
||||
isPopoverOpen: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderRequestDropdownItem = (request, index) => {
|
||||
renderRequestDropdownItem = (request: Request, index: number) => {
|
||||
const hasFailed = request.status === RequestStatus.ERROR;
|
||||
const inProgress = request.status === RequestStatus.PENDING;
|
||||
|
||||
|
@ -73,23 +89,24 @@ class RequestSelector extends Component {
|
|||
>
|
||||
<EuiTextColor color={hasFailed ? 'danger' : 'default'}>
|
||||
{request.name}
|
||||
{ hasFailed && <FormattedMessage
|
||||
id="inspectorViews.requests.failedLabel"
|
||||
defaultMessage=" (failed)"
|
||||
/>}
|
||||
{ inProgress &&
|
||||
|
||||
{hasFailed && (
|
||||
<FormattedMessage id="inspector.requests.failedLabel" defaultMessage=" (failed)" />
|
||||
)}
|
||||
|
||||
{inProgress && (
|
||||
<EuiLoadingSpinner
|
||||
size="s"
|
||||
aria-label={i18n.translate('inspectorViews.requests.requestInProgressAriaLabel', {
|
||||
defaultMessage: 'Request in progress'
|
||||
aria-label={i18n.translate('inspector.requests.requestInProgressAriaLabel', {
|
||||
defaultMessage: 'Request in progress',
|
||||
})}
|
||||
className="insRequestSelector__menuSpinner"
|
||||
/>
|
||||
}
|
||||
)}
|
||||
</EuiTextColor>
|
||||
</EuiContextMenuItem>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderRequestDropdown() {
|
||||
const button = (
|
||||
|
@ -124,78 +141,71 @@ class RequestSelector extends Component {
|
|||
|
||||
render() {
|
||||
const { selectedRequest, requests } = this.props;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="xs"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="inspectorViews.requests.requestLabel"
|
||||
defaultMessage="Request:"
|
||||
/>
|
||||
<FormattedMessage id="inspector.requests.requestLabel" defaultMessage="Request:" />
|
||||
</strong>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={true}>
|
||||
{requests.length <= 1 &&
|
||||
<div className="insRequestSelector__singleRequest" data-test-subj="inspectorRequestName">
|
||||
{requests.length <= 1 && (
|
||||
<div
|
||||
className="insRequestSelector__singleRequest"
|
||||
data-test-subj="inspectorRequestName"
|
||||
>
|
||||
{selectedRequest.name}
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
{requests.length > 1 && this.renderRequestDropdown()}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{ selectedRequest.status !== RequestStatus.PENDING &&
|
||||
{selectedRequest.status !== RequestStatus.PENDING && (
|
||||
<EuiToolTip
|
||||
position="left"
|
||||
title={selectedRequest.status === RequestStatus.OK ?
|
||||
title={
|
||||
selectedRequest.status === RequestStatus.OK ? (
|
||||
<FormattedMessage
|
||||
id="inspector.requests.requestSucceededTooltipTitle"
|
||||
defaultMessage="Request succeeded"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="inspector.requests.requestFailedTooltipTitle"
|
||||
defaultMessage="Request failed"
|
||||
/>
|
||||
)
|
||||
}
|
||||
content={
|
||||
<FormattedMessage
|
||||
id="inspectorViews.requests.requestSucceededTooltipTitle"
|
||||
defaultMessage="Request succeeded"
|
||||
/> :
|
||||
<FormattedMessage
|
||||
id="inspectorViews.requests.requestFailedTooltipTitle"
|
||||
defaultMessage="Request failed"
|
||||
id="inspector.requests.requestTooltipDescription"
|
||||
defaultMessage="The total time the request took."
|
||||
/>
|
||||
}
|
||||
content={<FormattedMessage
|
||||
id="inspectorViews.requests.requestTooltipDescription"
|
||||
defaultMessage="The total time the request took."
|
||||
/>}
|
||||
>
|
||||
<EuiBadge
|
||||
color={selectedRequest.status === RequestStatus.OK ? 'secondary' : 'danger'}
|
||||
iconType={selectedRequest.status === RequestStatus.OK ? 'check' : 'cross'}
|
||||
>
|
||||
|
||||
<FormattedMessage
|
||||
id="inspectorViews.requests.requestTimeLabel"
|
||||
id="inspector.requests.requestTimeLabel"
|
||||
defaultMessage="{requestTime}ms"
|
||||
values={{ requestTime: selectedRequest.time }}
|
||||
/>
|
||||
</EuiBadge>
|
||||
</EuiToolTip>
|
||||
}
|
||||
{ selectedRequest.status === RequestStatus.PENDING &&
|
||||
)}
|
||||
{selectedRequest.status === RequestStatus.PENDING && (
|
||||
<EuiLoadingSpinner
|
||||
size="m"
|
||||
aria-label={i18n.translate('inspectorViews.requests.requestInProgressAriaLabel', {
|
||||
defaultMessage: 'Request in progress'
|
||||
aria-label={i18n.translate('inspector.requests.requestInProgressAriaLabel', {
|
||||
defaultMessage: 'Request in progress',
|
||||
})}
|
||||
/>
|
||||
}
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RequestSelector.propTypes = {
|
||||
requests: PropTypes.array.isRequired,
|
||||
selectedRequest: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export { RequestSelector };
|
|
@ -19,60 +19,67 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
EuiEmptyPrompt,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTextColor,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiEmptyPrompt, EuiSpacer, EuiText, EuiTextColor } from '@elastic/eui';
|
||||
|
||||
import { RequestStatus } from 'ui/inspector/adapters';
|
||||
import { RequestStatus } from '../../../adapters';
|
||||
import { Request } from '../../../adapters/request/types';
|
||||
import { InspectorViewProps } from '../../../types';
|
||||
|
||||
import { RequestSelector } from './request_selector';
|
||||
import { RequestDetails } from './request_details';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
class RequestsViewComponent extends Component {
|
||||
interface RequestSelectorState {
|
||||
requests: Request[];
|
||||
request: Request;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
export class RequestsViewComponent extends Component<InspectorViewProps, RequestSelectorState> {
|
||||
static propTypes = {
|
||||
adapters: PropTypes.object.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
constructor(props: InspectorViewProps) {
|
||||
super(props);
|
||||
|
||||
props.adapters.requests.on('change', this._onRequestsChange);
|
||||
|
||||
const requests = props.adapters.requests.getRequests();
|
||||
this.state = {
|
||||
requests: requests,
|
||||
request: requests.length ? requests[0] : null
|
||||
requests,
|
||||
request: requests.length ? requests[0] : null,
|
||||
};
|
||||
}
|
||||
|
||||
_onRequestsChange = () => {
|
||||
const requests = this.props.adapters.requests.getRequests();
|
||||
const newState = { requests };
|
||||
const newState = { requests } as RequestSelectorState;
|
||||
|
||||
if (!requests.includes(this.state.request)) {
|
||||
newState.request = requests.length ? requests[0] : null;
|
||||
}
|
||||
this.setState(newState);
|
||||
}
|
||||
};
|
||||
|
||||
selectRequest = (request) => {
|
||||
selectRequest = (request: Request) => {
|
||||
if (request !== this.state.request) {
|
||||
this.setState({ request });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.adapters.requests.removeListener('change', this._onRequestsChange);
|
||||
}
|
||||
|
||||
renderEmptyRequests() {
|
||||
static renderEmptyRequests() {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
data-test-subj="inspectorNoRequestsMessage"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="inspectorViews.requests.noRequestsLoggedTitle"
|
||||
id="inspector.requests.noRequestsLoggedTitle"
|
||||
defaultMessage="No requests logged"
|
||||
/>
|
||||
</h2>
|
||||
|
@ -81,13 +88,13 @@ class RequestsViewComponent extends Component {
|
|||
<React.Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="inspectorViews.requests.noRequestsLoggedDescription.elementHasNotLoggedAnyRequestsText"
|
||||
id="inspector.requests.noRequestsLoggedDescription.elementHasNotLoggedAnyRequestsText"
|
||||
defaultMessage="The element hasn't logged any requests (yet)."
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="inspectorViews.requests.noRequestsLoggedDescription.whatDoesItUsuallyMeanText"
|
||||
id="inspector.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."
|
||||
/>
|
||||
|
@ -100,11 +107,11 @@ class RequestsViewComponent extends Component {
|
|||
|
||||
render() {
|
||||
if (!this.state.requests || !this.state.requests.length) {
|
||||
return this.renderEmptyRequests();
|
||||
return RequestsViewComponent.renderEmptyRequests();
|
||||
}
|
||||
|
||||
const failedCount = this.state.requests.filter(
|
||||
req => req.status === RequestStatus.ERROR
|
||||
(req: Request) => req.status === RequestStatus.ERROR
|
||||
).length;
|
||||
|
||||
return (
|
||||
|
@ -112,64 +119,44 @@ class RequestsViewComponent extends Component {
|
|||
<EuiText size="xs">
|
||||
<p role="status" aria-live="polite" aria-atomic="true">
|
||||
<FormattedMessage
|
||||
id="inspectorViews.requests.requestWasMadeDescription"
|
||||
id="inspector.requests.requestWasMadeDescription"
|
||||
defaultMessage="{requestsCount, plural, one {# request was} other {# requests were} } made{failedRequests}"
|
||||
values={{
|
||||
requestsCount: this.state.requests.length,
|
||||
failedRequests: (
|
||||
failedRequests:
|
||||
failedCount > 0 ? (
|
||||
<EuiTextColor color="danger">
|
||||
<FormattedMessage
|
||||
id="inspectorViews.requests.requestWasMadeDescription.requestHadFailureText"
|
||||
id="inspector.requests.requestWasMadeDescription.requestHadFailureText"
|
||||
defaultMessage=", {failedCount} had a failure"
|
||||
values={{ failedCount }}
|
||||
/>
|
||||
</EuiTextColor>
|
||||
) : ''
|
||||
)
|
||||
) : (
|
||||
''
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="xs"/>
|
||||
<EuiSpacer size="xs" />
|
||||
<RequestSelector
|
||||
requests={this.state.requests}
|
||||
selectedRequest={this.state.request}
|
||||
onRequestChanged={this.selectRequest}
|
||||
/>
|
||||
<EuiSpacer size="xs"/>
|
||||
{ this.state.request && this.state.request.description &&
|
||||
<EuiSpacer size="xs" />
|
||||
|
||||
{this.state.request && this.state.request.description && (
|
||||
<EuiText size="xs">
|
||||
<p>{this.state.request.description}</p>
|
||||
</EuiText>
|
||||
}
|
||||
)}
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
{ this.state.request &&
|
||||
<RequestDetails
|
||||
request={this.state.request}
|
||||
/>
|
||||
}
|
||||
|
||||
{this.state.request && <RequestDetails request={this.state.request} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RequestsViewComponent.propTypes = {
|
||||
adapters: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
const RequestsView = {
|
||||
title: i18n.translate('inspectorViews.requests.requestsTitle', {
|
||||
defaultMessage: 'Requests'
|
||||
}),
|
||||
order: 20,
|
||||
help: i18n.translate('inspectorViews.requests.requestsDescriptionTooltip', {
|
||||
defaultMessage: 'View the requests that collected the data'
|
||||
}),
|
||||
shouldShow(adapters) {
|
||||
return Boolean(adapters.requests);
|
||||
},
|
||||
component: RequestsViewComponent
|
||||
};
|
||||
|
||||
export { RequestsView };
|
|
@ -16,25 +16,8 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { Request } from '../../../adapters/request/types';
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
EuiCodeBlock,
|
||||
} from '@elastic/eui';
|
||||
|
||||
function RequestDetailsRequest(props) {
|
||||
return (
|
||||
<EuiCodeBlock
|
||||
language="json"
|
||||
paddingSize="s"
|
||||
isCopyable
|
||||
data-test-subj="inspectorRequestBody"
|
||||
>
|
||||
{ JSON.stringify(props.request.json, null, 2) }
|
||||
</EuiCodeBlock>
|
||||
);
|
||||
export interface RequestDetailsProps {
|
||||
request: Request;
|
||||
}
|
||||
|
||||
RequestDetailsRequest.shouldShow = (request) => !!request.json;
|
||||
|
||||
export { RequestDetailsRequest };
|
36
src/plugins/inspector/public/views/requests/index.ts
Normal file
36
src/plugins/inspector/public/views/requests/index.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 { RequestsViewComponent } from './components/requests_view';
|
||||
import { Adapters, InspectorViewDescription } from '../../types';
|
||||
|
||||
export const RequestsView: InspectorViewDescription = {
|
||||
title: i18n.translate('inspector.requests.requestsTitle', {
|
||||
defaultMessage: 'Requests',
|
||||
}),
|
||||
order: 20,
|
||||
help: i18n.translate('inspector.requests.requestsDescriptionTooltip', {
|
||||
defaultMessage: 'View the requests that collected the data',
|
||||
}),
|
||||
shouldShow(adapters: Adapters) {
|
||||
return Boolean(adapters.requests);
|
||||
},
|
||||
component: RequestsViewComponent,
|
||||
};
|
|
@ -887,24 +887,24 @@
|
|||
"inspectorViews.data.noDataAvailableTitle": "利用可能なデータがありません",
|
||||
"inspectorViews.data.rawCSVButtonLabel": "CSV",
|
||||
"inspectorViews.data.rawCSVButtonTooltip": "日付をタイムスタンプとしてなど、提供されたデータをそのままダウンロードします",
|
||||
"inspectorViews.requests.descriptionRowIconAriaLabel": "説明",
|
||||
"inspectorViews.requests.failedLabel": " (失敗)",
|
||||
"inspectorViews.requests.noRequestsLoggedDescription.elementHasNotLoggedAnyRequestsText": "エレメントが (まだ) リクエストを記録していません。",
|
||||
"inspectorViews.requests.noRequestsLoggedDescription.whatDoesItUsuallyMeanText": "これは通常、データを取得する必要がないか、エレメントがまだデータの取得を開始していないことを意味します。",
|
||||
"inspectorViews.requests.noRequestsLoggedTitle": "リクエストが記録されていません",
|
||||
"inspectorViews.requests.requestFailedTooltipTitle": "リクエストに失敗しました",
|
||||
"inspectorViews.requests.requestInProgressAriaLabel": "リクエスト進行中",
|
||||
"inspectorViews.requests.requestLabel": "リクエスト",
|
||||
"inspectorViews.requests.requestsDescriptionTooltip": "データを収集したリクエストを表示します",
|
||||
"inspectorViews.requests.requestsTitle": "リクエスト",
|
||||
"inspectorViews.requests.requestSucceededTooltipTitle": "リクエスト成功",
|
||||
"inspectorViews.requests.requestTabLabel": "リクエスト",
|
||||
"inspectorViews.requests.requestTimeLabel": "{requestTime}ms",
|
||||
"inspectorViews.requests.requestTooltipDescription": "リクエストの合計所要時間です。",
|
||||
"inspectorViews.requests.requestWasMadeDescription": "{requestsCount, plural, one {# リクエストが} other {# リクエストが} } 行われました{failedRequests}",
|
||||
"inspectorViews.requests.requestWasMadeDescription.requestHadFailureText": "、{failedCount} 件に失敗がありました",
|
||||
"inspectorViews.requests.responseTabLabel": "応答",
|
||||
"inspectorViews.requests.statisticsTabLabel": "統計",
|
||||
"inspector.requests.descriptionRowIconAriaLabel": "説明",
|
||||
"inspector.requests.failedLabel": " (失敗)",
|
||||
"inspector.requests.noRequestsLoggedDescription.elementHasNotLoggedAnyRequestsText": "エレメントが (まだ) リクエストを記録していません。",
|
||||
"inspector.requests.noRequestsLoggedDescription.whatDoesItUsuallyMeanText": "これは通常、データを取得する必要がないか、エレメントがまだデータの取得を開始していないことを意味します。",
|
||||
"inspector.requests.noRequestsLoggedTitle": "リクエストが記録されていません",
|
||||
"inspector.requests.requestFailedTooltipTitle": "リクエストに失敗しました",
|
||||
"inspector.requests.requestInProgressAriaLabel": "リクエスト進行中",
|
||||
"inspector.requests.requestLabel": "リクエスト",
|
||||
"inspector.requests.requestsDescriptionTooltip": "データを収集したリクエストを表示します",
|
||||
"inspector.requests.requestsTitle": "リクエスト",
|
||||
"inspector.requests.requestSucceededTooltipTitle": "リクエスト成功",
|
||||
"inspector.requests.requestTabLabel": "リクエスト",
|
||||
"inspector.requests.requestTimeLabel": "{requestTime}ms",
|
||||
"inspector.requests.requestTooltipDescription": "リクエストの合計所要時間です。",
|
||||
"inspector.requests.requestWasMadeDescription": "{requestsCount, plural, one {# リクエストが} other {# リクエストが} } 行われました{failedRequests}",
|
||||
"inspector.requests.requestWasMadeDescription.requestHadFailureText": "、{failedCount} 件に失敗がありました",
|
||||
"inspector.requests.responseTabLabel": "応答",
|
||||
"inspector.requests.statisticsTabLabel": "統計",
|
||||
"interpreter.function.visDimension.accessor.help": "使用するデータセット内の列 (列インデックスまたは列名)",
|
||||
"interpreter.function.visDimension.error.accessor": "入力された列名は無効です。",
|
||||
"interpreter.function.visDimension.help": "visConfig ディメンションオブジェクトを生成します",
|
||||
|
|
|
@ -887,24 +887,24 @@
|
|||
"inspectorViews.data.noDataAvailableTitle": "没有可用数据",
|
||||
"inspectorViews.data.rawCSVButtonLabel": "原始 CSV",
|
||||
"inspectorViews.data.rawCSVButtonTooltip": "按原样下载数据,例如将日期作为时间戳下载",
|
||||
"inspectorViews.requests.descriptionRowIconAriaLabel": "描述",
|
||||
"inspectorViews.requests.failedLabel": " (失败)",
|
||||
"inspectorViews.requests.noRequestsLoggedDescription.elementHasNotLoggedAnyRequestsText": "该元素尚未记录任何请求。",
|
||||
"inspectorViews.requests.noRequestsLoggedDescription.whatDoesItUsuallyMeanText": "这通常表示无需提取任何数据,或该元素尚未开始提取数据。",
|
||||
"inspectorViews.requests.noRequestsLoggedTitle": "未记录任何请求",
|
||||
"inspectorViews.requests.requestFailedTooltipTitle": "请求失败",
|
||||
"inspectorViews.requests.requestInProgressAriaLabel": "请求进行中",
|
||||
"inspectorViews.requests.requestLabel": "请求:",
|
||||
"inspectorViews.requests.requestsDescriptionTooltip": "查看已收集数据的请求",
|
||||
"inspectorViews.requests.requestsTitle": "请求",
|
||||
"inspectorViews.requests.requestSucceededTooltipTitle": "请求成功",
|
||||
"inspectorViews.requests.requestTabLabel": "请求",
|
||||
"inspectorViews.requests.requestTimeLabel": "{requestTime}ms",
|
||||
"inspectorViews.requests.requestTooltipDescription": "请求所花费的总时间。",
|
||||
"inspectorViews.requests.requestWasMadeDescription": "{requestsCount, plural, one {# 个请求已} other {# 个请求已} }发出{failedRequests}",
|
||||
"inspectorViews.requests.requestWasMadeDescription.requestHadFailureText": ",{failedCount} 个失败",
|
||||
"inspectorViews.requests.responseTabLabel": "响应",
|
||||
"inspectorViews.requests.statisticsTabLabel": "统计信息",
|
||||
"inspector.requests.descriptionRowIconAriaLabel": "描述",
|
||||
"inspector.requests.failedLabel": " (失败)",
|
||||
"inspector.requests.noRequestsLoggedDescription.elementHasNotLoggedAnyRequestsText": "该元素尚未记录任何请求。",
|
||||
"inspector.requests.noRequestsLoggedDescription.whatDoesItUsuallyMeanText": "这通常表示无需提取任何数据,或该元素尚未开始提取数据。",
|
||||
"inspector.requests.noRequestsLoggedTitle": "未记录任何请求",
|
||||
"inspector.requests.requestFailedTooltipTitle": "请求失败",
|
||||
"inspector.requests.requestInProgressAriaLabel": "请求进行中",
|
||||
"inspector.requests.requestLabel": "请求:",
|
||||
"inspector.requests.requestsDescriptionTooltip": "查看已收集数据的请求",
|
||||
"inspector.requests.requestsTitle": "请求",
|
||||
"inspector.requests.requestSucceededTooltipTitle": "请求成功",
|
||||
"inspector.requests.requestTabLabel": "请求",
|
||||
"inspector.requests.requestTimeLabel": "{requestTime}ms",
|
||||
"inspector.requests.requestTooltipDescription": "请求所花费的总时间。",
|
||||
"inspector.requests.requestWasMadeDescription": "{requestsCount, plural, one {# 个请求已} other {# 个请求已} }发出{failedRequests}",
|
||||
"inspector.requests.requestWasMadeDescription.requestHadFailureText": ",{failedCount} 个失败",
|
||||
"inspector.requests.responseTabLabel": "响应",
|
||||
"inspector.requests.statisticsTabLabel": "统计信息",
|
||||
"interpreter.function.visDimension.accessor.help": "数据集中要使用的列(列索引或列名称)",
|
||||
"interpreter.function.visDimension.error.accessor": "提供的列名称无效",
|
||||
"interpreter.function.visDimension.help": "生成 visConfig 维度对象",
|
||||
|
|
Loading…
Reference in a new issue