mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
Replace Inspector's EuiPopover with EuiComboBox (#113566)
* Replacing EuiPopover with EuiComboBox * The combobox will help alleviate issues when the list of options is very long * Refactoring the Combobox to listen for change events * Added an onChange handler * Renamed the method to render the combobox * Commented out additional blocks of code before final refactor * Finished refactoring the Request Selector to use EUI Combobox * Removed three helper methods for the EUIPopover. * `togglePopover()` * `closePopover()` * `renderRequestDropdownItem()` * Removed the local state object and interface (no longer needed) * Renamed the const `options` to `selectedOptions` in `handleSelectd()` method to better reflect where the options array was coming from. * Updating tests and translations * Fixed the inspector functional test to use comboBox service * Removed two unused translations * Updating Combobox options to pass data-test-sub string * Updated two tests for Combobox single option * Updated the test expectations to the default string * Both tests were looking for a named string instead of a default message * Adding error handling to Inspector combobox * Checking for the item status code * Adding a " (failed)" message if the status code returns `2` * Updating test to look for "Chart_data" instead of "Chartdata" * Updating two tests to validate single combobox options * Added helper method to check default text against combobox options * Added helper method to get the selected combobox option * Checking two inspector instances using helpers * Adding a defensive check to helper method. * Correct a type error in test return * Adding back translated failLabel Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Nathan L Smith <smith@nlsmith.com>
This commit is contained in:
parent
3ebfb029a2
commit
dba055c654
7 changed files with 85 additions and 119 deletions
|
@ -13,118 +13,73 @@ import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EuiBadge,
|
EuiBadge,
|
||||||
EuiButtonEmpty,
|
EuiComboBox,
|
||||||
EuiContextMenuPanel,
|
EuiComboBoxOptionOption,
|
||||||
EuiContextMenuItem,
|
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
EuiFlexItem,
|
EuiFlexItem,
|
||||||
EuiLoadingSpinner,
|
EuiLoadingSpinner,
|
||||||
EuiPopover,
|
|
||||||
EuiTextColor,
|
|
||||||
EuiToolTip,
|
EuiToolTip,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
|
||||||
import { RequestStatus } from '../../../../common/adapters';
|
import { RequestStatus } from '../../../../common/adapters';
|
||||||
import { Request } from '../../../../common/adapters/request/types';
|
import { Request } from '../../../../common/adapters/request/types';
|
||||||
|
|
||||||
interface RequestSelectorState {
|
|
||||||
isPopoverOpen: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RequestSelectorProps {
|
interface RequestSelectorProps {
|
||||||
requests: Request[];
|
requests: Request[];
|
||||||
selectedRequest: Request;
|
selectedRequest: Request;
|
||||||
onRequestChanged: Function;
|
onRequestChanged: (request: Request) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RequestSelector extends Component<RequestSelectorProps, RequestSelectorState> {
|
export class RequestSelector extends Component<RequestSelectorProps> {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
requests: PropTypes.array.isRequired,
|
requests: PropTypes.array.isRequired,
|
||||||
selectedRequest: PropTypes.object.isRequired,
|
selectedRequest: PropTypes.object.isRequired,
|
||||||
onRequestChanged: PropTypes.func,
|
onRequestChanged: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
handleSelected = (selectedOptions: Array<EuiComboBoxOptionOption<string>>) => {
|
||||||
isPopoverOpen: false,
|
const selectedOption = this.props.requests.find(
|
||||||
|
(request) => request.id === selectedOptions[0].value
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selectedOption) {
|
||||||
|
this.props.onRequestChanged(selectedOption);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
togglePopover = () => {
|
renderRequestCombobox() {
|
||||||
this.setState((prevState: RequestSelectorState) => ({
|
const options = this.props.requests.map((item) => {
|
||||||
isPopoverOpen: !prevState.isPopoverOpen,
|
const hasFailed = item.status === RequestStatus.ERROR;
|
||||||
}));
|
const testLabel = item.name.replace(/\s+/, '_');
|
||||||
};
|
|
||||||
|
|
||||||
closePopover = () => {
|
return {
|
||||||
this.setState({
|
'data-test-subj': `inspectorRequestChooser${testLabel}`,
|
||||||
isPopoverOpen: false,
|
label: hasFailed
|
||||||
|
? `${item.name} ${i18n.translate('inspector.requests.failedLabel', {
|
||||||
|
defaultMessage: ' (failed)',
|
||||||
|
})}`
|
||||||
|
: item.name,
|
||||||
|
value: item.id,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
renderRequestDropdownItem = (request: Request, index: number) => {
|
|
||||||
const hasFailed = request.status === RequestStatus.ERROR;
|
|
||||||
const inProgress = request.status === RequestStatus.PENDING;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiContextMenuItem
|
<EuiComboBox
|
||||||
key={index}
|
|
||||||
icon={request === this.props.selectedRequest ? 'check' : 'empty'}
|
|
||||||
onClick={() => {
|
|
||||||
this.props.onRequestChanged(request);
|
|
||||||
this.closePopover();
|
|
||||||
}}
|
|
||||||
toolTipContent={request.description}
|
|
||||||
toolTipPosition="left"
|
|
||||||
data-test-subj={`inspectorRequestChooser${request.name}`}
|
|
||||||
>
|
|
||||||
<EuiTextColor color={hasFailed ? 'danger' : 'default'}>
|
|
||||||
{request.name}
|
|
||||||
|
|
||||||
{hasFailed && (
|
|
||||||
<FormattedMessage id="inspector.requests.failedLabel" defaultMessage=" (failed)" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{inProgress && (
|
|
||||||
<EuiLoadingSpinner
|
|
||||||
size="s"
|
|
||||||
aria-label={i18n.translate('inspector.requests.requestInProgressAriaLabel', {
|
|
||||||
defaultMessage: 'Request in progress',
|
|
||||||
})}
|
|
||||||
className="insRequestSelector__menuSpinner"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</EuiTextColor>
|
|
||||||
</EuiContextMenuItem>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
renderRequestDropdown() {
|
|
||||||
const button = (
|
|
||||||
<EuiButtonEmpty
|
|
||||||
iconType="arrowDown"
|
|
||||||
iconSide="right"
|
|
||||||
size="s"
|
|
||||||
onClick={this.togglePopover}
|
|
||||||
data-test-subj="inspectorRequestChooser"
|
data-test-subj="inspectorRequestChooser"
|
||||||
>
|
fullWidth={true}
|
||||||
{this.props.selectedRequest.name}
|
|
||||||
</EuiButtonEmpty>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<EuiPopover
|
|
||||||
id="inspectorRequestChooser"
|
id="inspectorRequestChooser"
|
||||||
button={button}
|
isClearable={false}
|
||||||
isOpen={this.state.isPopoverOpen}
|
onChange={this.handleSelected}
|
||||||
closePopover={this.closePopover}
|
options={options}
|
||||||
panelPaddingSize="none"
|
prepend="Request"
|
||||||
anchorPosition="downLeft"
|
selectedOptions={[
|
||||||
repositionOnScroll
|
{
|
||||||
>
|
label: this.props.selectedRequest.name,
|
||||||
<EuiContextMenuPanel
|
value: this.props.selectedRequest.id,
|
||||||
items={this.props.requests.map(this.renderRequestDropdownItem)}
|
},
|
||||||
data-test-subj="inspectorRequestChooserMenuPanel"
|
]}
|
||||||
/>
|
singleSelection={{ asPlainText: true }}
|
||||||
</EuiPopover>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,23 +87,8 @@ export class RequestSelector extends Component<RequestSelectorProps, RequestSele
|
||||||
const { selectedRequest, requests } = this.props;
|
const { selectedRequest, requests } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup alignItems="center" gutterSize="xs">
|
<EuiFlexGroup alignItems="center">
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={true}>{requests.length && this.renderRequestCombobox()}</EuiFlexItem>
|
||||||
<strong>
|
|
||||||
<FormattedMessage id="inspector.requests.requestLabel" defaultMessage="Request:" />
|
|
||||||
</strong>
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={true}>
|
|
||||||
{requests.length <= 1 && (
|
|
||||||
<div
|
|
||||||
className="insRequestSelector__singleRequest"
|
|
||||||
data-test-subj="inspectorRequestName"
|
|
||||||
>
|
|
||||||
{selectedRequest.name}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{requests.length > 1 && this.renderRequestDropdown()}
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
{selectedRequest.status !== RequestStatus.PENDING && (
|
{selectedRequest.status !== RequestStatus.PENDING && (
|
||||||
<EuiToolTip
|
<EuiToolTip
|
||||||
|
|
|
@ -53,9 +53,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
await inspector.open();
|
await inspector.open();
|
||||||
await testSubjects.click('inspectorRequestChooser');
|
await testSubjects.click('inspectorRequestChooser');
|
||||||
let foundZero = false;
|
let foundZero = false;
|
||||||
for (const subj of ['Documents', 'Total hits', 'Charts']) {
|
for (const subj of ['Documents', 'Chart_data']) {
|
||||||
await testSubjects.click(`inspectorRequestChooser${subj}`);
|
await testSubjects.click(`inspectorRequestChooser${subj}`);
|
||||||
if (testSubjects.exists('inspectorRequestDetailStatistics', { timeout: 500 })) {
|
if (await testSubjects.exists('inspectorRequestDetailStatistics', { timeout: 500 })) {
|
||||||
await testSubjects.click(`inspectorRequestDetailStatistics`);
|
await testSubjects.click(`inspectorRequestDetailStatistics`);
|
||||||
const requestStatsTotalHits = getHitCount(await inspector.getTableData());
|
const requestStatsTotalHits = getHitCount(await inspector.getTableData());
|
||||||
if (requestStatsTotalHits === '0') {
|
if (requestStatsTotalHits === '0') {
|
||||||
|
|
|
@ -131,9 +131,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set the default query name if not given in the schema', async () => {
|
it('should set the default query name if not given in the schema', async () => {
|
||||||
const requests = await inspector.getRequestNames();
|
const singleExampleRequest = await inspector.hasSingleRequest();
|
||||||
|
const selectedExampleRequest = await inspector.getSelectedOption();
|
||||||
|
|
||||||
expect(requests).to.be('Unnamed request #0');
|
expect(singleExampleRequest).to.be(true);
|
||||||
|
expect(selectedExampleRequest).to.equal('Unnamed request #0');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should log the request statistic', async () => {
|
it('should log the request statistic', async () => {
|
||||||
|
|
|
@ -16,6 +16,7 @@ export class InspectorService extends FtrService {
|
||||||
private readonly flyout = this.ctx.getService('flyout');
|
private readonly flyout = this.ctx.getService('flyout');
|
||||||
private readonly testSubjects = this.ctx.getService('testSubjects');
|
private readonly testSubjects = this.ctx.getService('testSubjects');
|
||||||
private readonly find = this.ctx.getService('find');
|
private readonly find = this.ctx.getService('find');
|
||||||
|
private readonly comboBox = this.ctx.getService('comboBox');
|
||||||
|
|
||||||
private async getIsEnabled(): Promise<boolean> {
|
private async getIsEnabled(): Promise<boolean> {
|
||||||
const ariaDisabled = await this.testSubjects.getAttribute('openInspectorButton', 'disabled');
|
const ariaDisabled = await this.testSubjects.getAttribute('openInspectorButton', 'disabled');
|
||||||
|
@ -206,20 +207,29 @@ export class InspectorService extends FtrService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns request name as the comma-separated string
|
* Returns the selected option value from combobox
|
||||||
|
*/
|
||||||
|
public async getSelectedOption(): Promise<string> {
|
||||||
|
await this.openInspectorRequestsView();
|
||||||
|
const selectedOption = await this.comboBox.getComboBoxSelectedOptions(
|
||||||
|
'inspectorRequestChooser'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selectedOption.length !== 1) {
|
||||||
|
return 'Combobox has multiple options';
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedOption[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns request name as the comma-separated string from combobox
|
||||||
*/
|
*/
|
||||||
public async getRequestNames(): Promise<string> {
|
public async getRequestNames(): Promise<string> {
|
||||||
await this.openInspectorRequestsView();
|
await this.openInspectorRequestsView();
|
||||||
const requestChooserExists = await this.testSubjects.exists('inspectorRequestChooser');
|
|
||||||
if (requestChooserExists) {
|
|
||||||
await this.testSubjects.click('inspectorRequestChooser');
|
|
||||||
const menu = await this.testSubjects.find('inspectorRequestChooserMenuPanel');
|
|
||||||
const requestNames = await menu.getVisibleText();
|
|
||||||
return requestNames.trim().split('\n').join(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
const singleRequest = await this.testSubjects.find('inspectorRequestName');
|
const comboBoxOptions = await this.comboBox.getOptionsList('inspectorRequestChooser');
|
||||||
return await singleRequest.getVisibleText();
|
return comboBoxOptions.trim().split('\n').join(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
public getOpenRequestStatisticButton() {
|
public getOpenRequestStatisticButton() {
|
||||||
|
@ -233,4 +243,17 @@ export class InspectorService extends FtrService {
|
||||||
public getOpenRequestDetailResponseButton() {
|
public getOpenRequestDetailResponseButton() {
|
||||||
return this.testSubjects.find('inspectorRequestDetailResponse');
|
return this.testSubjects.find('inspectorRequestDetailResponse');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the value equals the combobox options list
|
||||||
|
* @param value default combobox single option text
|
||||||
|
*/
|
||||||
|
public async hasSingleRequest(
|
||||||
|
value: string = "You've selected all available options"
|
||||||
|
): Promise<boolean> {
|
||||||
|
await this.openInspectorRequestsView();
|
||||||
|
const comboBoxOptions = await this.comboBox.getOptionsList('inspectorRequestChooser');
|
||||||
|
|
||||||
|
return value === comboBoxOptions;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4162,7 +4162,6 @@
|
||||||
"inspector.requests.noRequestsLoggedTitle": "リクエストが記録されていません",
|
"inspector.requests.noRequestsLoggedTitle": "リクエストが記録されていません",
|
||||||
"inspector.requests.requestFailedTooltipTitle": "リクエストに失敗しました",
|
"inspector.requests.requestFailedTooltipTitle": "リクエストに失敗しました",
|
||||||
"inspector.requests.requestInProgressAriaLabel": "リクエストが進行中",
|
"inspector.requests.requestInProgressAriaLabel": "リクエストが進行中",
|
||||||
"inspector.requests.requestLabel": "リクエスト:",
|
|
||||||
"inspector.requests.requestsDescriptionTooltip": "データを収集したリクエストを表示します",
|
"inspector.requests.requestsDescriptionTooltip": "データを収集したリクエストを表示します",
|
||||||
"inspector.requests.requestsTitle": "リクエスト",
|
"inspector.requests.requestsTitle": "リクエスト",
|
||||||
"inspector.requests.requestSucceededTooltipTitle": "リクエスト成功",
|
"inspector.requests.requestSucceededTooltipTitle": "リクエスト成功",
|
||||||
|
|
|
@ -4201,7 +4201,6 @@
|
||||||
"inspector.requests.noRequestsLoggedTitle": "未记录任何请求",
|
"inspector.requests.noRequestsLoggedTitle": "未记录任何请求",
|
||||||
"inspector.requests.requestFailedTooltipTitle": "请求失败",
|
"inspector.requests.requestFailedTooltipTitle": "请求失败",
|
||||||
"inspector.requests.requestInProgressAriaLabel": "进行中的请求",
|
"inspector.requests.requestInProgressAriaLabel": "进行中的请求",
|
||||||
"inspector.requests.requestLabel": "请求:",
|
|
||||||
"inspector.requests.requestsDescriptionTooltip": "查看已收集数据的请求",
|
"inspector.requests.requestsDescriptionTooltip": "查看已收集数据的请求",
|
||||||
"inspector.requests.requestsTitle": "请求",
|
"inspector.requests.requestsTitle": "请求",
|
||||||
"inspector.requests.requestSucceededTooltipTitle": "请求成功",
|
"inspector.requests.requestSucceededTooltipTitle": "请求成功",
|
||||||
|
|
|
@ -77,9 +77,12 @@ export default function ({ getPageObjects, getService }) {
|
||||||
await inspector.close();
|
await inspector.close();
|
||||||
|
|
||||||
await dashboardPanelActions.openInspectorByTitle('geo grid vector grid example');
|
await dashboardPanelActions.openInspectorByTitle('geo grid vector grid example');
|
||||||
const gridExampleRequestNames = await inspector.getRequestNames();
|
const singleExampleRequest = await inspector.hasSingleRequest();
|
||||||
|
const selectedExampleRequest = await inspector.getSelectedOption();
|
||||||
await inspector.close();
|
await inspector.close();
|
||||||
expect(gridExampleRequestNames).to.equal('logstash-*');
|
|
||||||
|
expect(singleExampleRequest).to.be(true);
|
||||||
|
expect(selectedExampleRequest).to.equal('logstash-*');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should apply container state (time, query, filters) to embeddable when loaded', async () => {
|
it('should apply container state (time, query, filters) to embeddable when loaded', async () => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue