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:
Trevor Pierce 2021-10-18 11:41:53 -05:00 committed by GitHub
parent 3ebfb029a2
commit dba055c654
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 85 additions and 119 deletions

View file

@ -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

View file

@ -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') {

View file

@ -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 () => {

View file

@ -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;
}
} }

View file

@ -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": "リクエスト成功",

View file

@ -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": "请求成功",

View file

@ -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 () => {