[input controls] Horizontal layout (#14918)

* input controls horizontal layout

* fix controlWidth calculation

* add functional test to ensure panel resizing changes layout

* use all caps for consts, add more comments about where values came from

* replace sleeps in functional tests with retry

* use KuiFlexGroup with wrap option instead of manually calculating widths

* remove no longer used min width constants
This commit is contained in:
Nathan Reese 2017-11-29 10:28:07 -07:00 committed by GitHub
parent cd92cff665
commit c579677bf9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 269 additions and 136 deletions

View file

@ -2,6 +2,7 @@
exports[`renders ListControl 1`] = `
<Component
controlIndex={0}
id="mock-list-control"
label="list control"
>

View file

@ -2,6 +2,7 @@
exports[`renders RangeControl 1`] = `
<Component
controlIndex={0}
id="mock-range-control"
label="range control"
>

View file

@ -4,37 +4,49 @@ exports[`Apply and Cancel change btns enabled when there are changes 1`] = `
<div
className="inputControlVis"
>
<div
data-test-subj="inputControl0"
<KuiFlexGroup
alignItems="stretch"
gutterSize="large"
justifyContent="flexStart"
wrap={true}
>
<ListControl
control={
<KuiFlexItem
grow={true}
style={
Object {
"getMultiSelectDelimiter": [Function],
"id": "mock-list-control",
"label": "list control",
"options": Object {
"multiselect": true,
"type": "terms",
},
"selectOptions": Array [
Object {
"label": "choice1",
"value": "choice1",
},
Object {
"label": "choice2",
"value": "choice2",
},
],
"type": "list",
"value": "",
"minWidth": "250px",
}
}
controlIndex={0}
stageFilter={[Function]}
/>
</div>
>
<ListControl
control={
Object {
"getMultiSelectDelimiter": [Function],
"id": "mock-list-control",
"label": "list control",
"options": Object {
"multiselect": true,
"type": "terms",
},
"selectOptions": Array [
Object {
"label": "choice1",
"value": "choice1",
},
Object {
"label": "choice2",
"value": "choice2",
},
],
"type": "list",
"value": "",
}
}
controlIndex={0}
stageFilter={[Function]}
/>
</KuiFlexItem>
</KuiFlexGroup>
<KuiFieldGroup
className="actions"
isAlignedTop={false}
@ -86,37 +98,49 @@ exports[`Clear btns enabled when there are values 1`] = `
<div
className="inputControlVis"
>
<div
data-test-subj="inputControl0"
<KuiFlexGroup
alignItems="stretch"
gutterSize="large"
justifyContent="flexStart"
wrap={true}
>
<ListControl
control={
<KuiFlexItem
grow={true}
style={
Object {
"getMultiSelectDelimiter": [Function],
"id": "mock-list-control",
"label": "list control",
"options": Object {
"multiselect": true,
"type": "terms",
},
"selectOptions": Array [
Object {
"label": "choice1",
"value": "choice1",
},
Object {
"label": "choice2",
"value": "choice2",
},
],
"type": "list",
"value": "",
"minWidth": "250px",
}
}
controlIndex={0}
stageFilter={[Function]}
/>
</div>
>
<ListControl
control={
Object {
"getMultiSelectDelimiter": [Function],
"id": "mock-list-control",
"label": "list control",
"options": Object {
"multiselect": true,
"type": "terms",
},
"selectOptions": Array [
Object {
"label": "choice1",
"value": "choice1",
},
Object {
"label": "choice2",
"value": "choice2",
},
],
"type": "list",
"value": "",
}
}
controlIndex={0}
stageFilter={[Function]}
/>
</KuiFlexItem>
</KuiFlexGroup>
<KuiFieldGroup
className="actions"
isAlignedTop={false}
@ -168,37 +192,49 @@ exports[`Renders list control 1`] = `
<div
className="inputControlVis"
>
<div
data-test-subj="inputControl0"
<KuiFlexGroup
alignItems="stretch"
gutterSize="large"
justifyContent="flexStart"
wrap={true}
>
<ListControl
control={
<KuiFlexItem
grow={true}
style={
Object {
"getMultiSelectDelimiter": [Function],
"id": "mock-list-control",
"label": "list control",
"options": Object {
"multiselect": true,
"type": "terms",
},
"selectOptions": Array [
Object {
"label": "choice1",
"value": "choice1",
},
Object {
"label": "choice2",
"value": "choice2",
},
],
"type": "list",
"value": "",
"minWidth": "250px",
}
}
controlIndex={0}
stageFilter={[Function]}
/>
</div>
>
<ListControl
control={
Object {
"getMultiSelectDelimiter": [Function],
"id": "mock-list-control",
"label": "list control",
"options": Object {
"multiselect": true,
"type": "terms",
},
"selectOptions": Array [
Object {
"label": "choice1",
"value": "choice1",
},
Object {
"label": "choice2",
"value": "choice2",
},
],
"type": "list",
"value": "",
}
}
controlIndex={0}
stageFilter={[Function]}
/>
</KuiFlexItem>
</KuiFlexGroup>
<KuiFieldGroup
className="actions"
isAlignedTop={false}
@ -250,31 +286,43 @@ exports[`Renders range control 1`] = `
<div
className="inputControlVis"
>
<div
data-test-subj="inputControl0"
<KuiFlexGroup
alignItems="stretch"
gutterSize="large"
justifyContent="flexStart"
wrap={true}
>
<RangeControl
control={
<KuiFlexItem
grow={true}
style={
Object {
"id": "mock-range-control",
"label": "ragne control",
"max": 100,
"min": 0,
"options": Object {
"decimalPlaces": 0,
"step": 1,
},
"type": "range",
"value": Object {
"max": 0,
"min": 0,
},
"minWidth": "250px",
}
}
controlIndex={0}
stageFilter={[Function]}
/>
</div>
>
<RangeControl
control={
Object {
"id": "mock-range-control",
"label": "ragne control",
"max": 100,
"min": 0,
"options": Object {
"decimalPlaces": 0,
"step": 1,
},
"type": "range",
"value": Object {
"max": 0,
"min": 0,
},
}
}
controlIndex={0}
stageFilter={[Function]}
/>
</KuiFlexItem>
</KuiFlexGroup>
<KuiFieldGroup
className="actions"
isAlignedTop={false}

View file

@ -2,7 +2,10 @@ import PropTypes from 'prop-types';
import React from 'react';
export const FormRow = (props) => (
<div className="kuiVerticalRhythm">
<div
className="kuiVerticalRhythm"
data-test-subj={'inputControl' + props.controlIndex}
>
<label className="kuiLabel kuiVerticalRhythmSmall" htmlFor={props.id}>
{props.label}
</label>
@ -15,5 +18,6 @@ export const FormRow = (props) => (
FormRow.propTypes = {
label: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired
children: PropTypes.node.isRequired,
controlIndex: PropTypes.number.isRequired
};

View file

@ -31,6 +31,7 @@ export class ListControl extends Component {
<FormRow
id={this.props.control.id}
label={this.props.control.label}
controlIndex={this.props.controlIndex}
>
<Select
className="list-control-react-select"

View file

@ -84,6 +84,7 @@ export class RangeControl extends Component {
<FormRow
id={this.props.control.id}
label={this.props.control.label}
controlIndex={this.props.controlIndex}
>
<input
id={`${this.props.control.id}_min`}

View file

@ -2,7 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { RangeControl } from './range_control';
import { ListControl } from './list_control';
import { KuiFieldGroup, KuiFieldGroupSection, KuiButton } from 'ui_framework/components';
import {
KuiFieldGroup,
KuiFieldGroupSection,
KuiButton,
KuiFlexGroup,
KuiFlexItem
} from 'ui_framework/components';
export class InputControlVis extends Component {
constructor(props) {
@ -26,6 +32,7 @@ export class InputControlVis extends Component {
}
renderControls() {
return this.props.controls.map((control, index) => {
let controlComponent = null;
switch (control.type) {
@ -51,12 +58,9 @@ export class InputControlVis extends Component {
throw new Error(`Unhandled control type ${control.type}`);
}
return (
<div
key={control.id}
data-test-subj={'inputControl' + index}
>
<KuiFlexItem key={control.id} style={{ minWidth: '250px' }}>
{controlComponent}
</div>
</KuiFlexItem>
);
});
}
@ -109,7 +113,9 @@ export class InputControlVis extends Component {
return (
<div className="inputControlVis">
{this.renderControls()}
<KuiFlexGroup wrap>
{this.renderControls()}
</KuiFlexGroup>
{stagingButtons}
</div>
);

View file

@ -45,4 +45,3 @@ visualization.input_control_vis {
}

View file

@ -7,5 +7,5 @@
data-title="{{sharedItemTitle}}"
data-description="{{savedObj.description}}"
render-counter
class="panel-content">
class="panel-content {{savedObj.vis.type.name}}">
</visualize>

View file

@ -1,45 +1,117 @@
import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const retry = getService('retry');
const find = getService('find');
const remote = getService('remote');
const PageObjects = getPageObjects(['dashboard', 'header']);
const PageObjects = getPageObjects(['dashboard', 'header', 'common']);
describe('dashboard grid', function describeIndexTests() {
before(async function () {
describe('dashboard grid', () => {
before(async () => {
return PageObjects.dashboard.initTests();
});
after(async function () {
after(async () => {
// avoids any 'Object with id x not found' errors when switching tests.
await PageObjects.header.clickDashboard();
await PageObjects.dashboard.gotoDashboardLandingPage();
});
// Specific test after https://github.com/elastic/kibana/issues/14764 fix
it('Can move panel from bottom to top row', async function addVisualizations() {
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.addVisualizations([
PageObjects.dashboard.getTestVisualizationNames()[0],
PageObjects.dashboard.getTestVisualizationNames()[1],
PageObjects.dashboard.getTestVisualizationNames()[2],
]);
describe('move panel', () => {
// Specific test after https://github.com/elastic/kibana/issues/14764 fix
it('Can move panel from bottom to top row', async () => {
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.addVisualizations([
PageObjects.dashboard.getTestVisualizationNames()[0],
PageObjects.dashboard.getTestVisualizationNames()[1],
PageObjects.dashboard.getTestVisualizationNames()[2],
]);
const panels = await find.allByCssSelector('.panel-title');
const panels = await find.allByCssSelector('.panel-title');
const thirdPanel = panels[2];
const position1 = await thirdPanel.getPosition();
const thirdPanel = panels[2];
const position1 = await thirdPanel.getPosition();
remote
.moveMouseTo(thirdPanel)
.pressMouseButton()
.moveMouseTo(null, -20, -400)
.releaseMouseButton();
remote
.moveMouseTo(thirdPanel)
.pressMouseButton()
.moveMouseTo(null, -20, -400)
.releaseMouseButton();
const panelsMoved = await find.allByCssSelector('.panel-title');
const position2 = await panelsMoved[2].getPosition();
const panelsMoved = await find.allByCssSelector('.panel-title');
const position2 = await panelsMoved[2].getPosition();
expect(position1.y).to.be.greaterThan(position2.y);
expect(position1.y).to.be.greaterThan(position2.y);
});
});
describe('resize panel', () => {
describe('input control panel', () => {
before(async () => {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.addVisualizations(['Visualization InputControl']);
});
it('Should position controls in horizontal layout when panel is short and long', async () => {
const resizeIcons = await find.allByCssSelector('.react-resizable-handle');
expect(resizeIcons.length).to.equal(1);
remote
.moveMouseTo(resizeIcons[0])
.pressMouseButton()
.moveMouseTo(null, 300, 0)
.releaseMouseButton();
await retry.try(async () => {
const controls = await find.allByCssSelector('.inputControlVis .kuiFlexItem');
expect(controls.length).to.equal(3);
const control0Position = await controls[0].getPosition();
const control1Position = await controls[1].getPosition();
const control2Position = await controls[2].getPosition();
expect(control0Position.y).to.equal(control1Position.y);
expect(control1Position.y).to.equal(control2Position.y);
});
});
it('Should position controls in vertical layout when panel is tall and skinny', async () => {
const resizeIcons = await find.allByCssSelector('.react-resizable-handle');
expect(resizeIcons.length).to.equal(1);
remote
.moveMouseTo(resizeIcons[0])
.pressMouseButton()
.moveMouseTo(null, -400, 200)
.releaseMouseButton();
await retry.try(async () => {
const controls = await find.allByCssSelector('.inputControlVis .kuiFlexItem');
expect(controls.length).to.equal(3);
const control0Position = await controls[0].getPosition();
const control1Position = await controls[1].getPosition();
const control2Position = await controls[2].getPosition();
expect(control2Position.y).to.be.greaterThan(control1Position.y);
expect(control1Position.y).to.be.greaterThan(control0Position.y);
});
});
it('Should position controls inside panel', async () => {
const controls = await find.allByCssSelector('.inputControlVis .kuiFlexItem');
expect(controls.length).to.equal(3);
const control0Size = await controls[0].getSize();
const control1Size = await controls[1].getSize();
const control2Size = await controls[2].getSize();
const panels = await find.allByCssSelector('.dashboard-panel');
expect(panels.length).to.equal(1);
const panelSize = await panels[0].getSize();
expect(control0Size.width).to.be.lessThan(panelSize.width);
expect(control1Size.width).to.be.lessThan(panelSize.width);
expect(control2Size.width).to.be.lessThan(panelSize.width);
});
});
});
});
}