Fix bug where the loading indicator was wider than the screen (#8854)

* Fix bug where the loading indicator was wider than the screen, allowing you to drag a dashboard panel very wide and break the UI.
* Refactor kbnLoadingIndicator to be a standalone component.
* Make loadingIndicator fixed position so it doesn't disrupt the layout when it appears and disappears.
* Uncouple from .spinner styles.
* Set a max-width on it.
* Rename Common PageObject getSpinnerDone method to isGlobalLoadingIndicatorHidden.
This commit is contained in:
CJ Cenizal 2016-10-31 16:08:38 -07:00 committed by GitHub
parent 25e995eb60
commit 59d65fd672
18 changed files with 114 additions and 86 deletions

View file

@ -3,8 +3,7 @@ import expect from 'expect.js';
import uiModules from 'ui/modules';
import $ from 'jquery';
import '../directives/kbn_loading_indicator';
import '../directives/loading_indicator/loading_indicator';
describe('kbnLoadingIndicator', function () {
let compile;
@ -14,23 +13,24 @@ describe('kbnLoadingIndicator', function () {
ngMock.inject(function ($compile, $rootScope) {
compile = function (hasActiveConnections) {
$rootScope.chrome = {
httpActive: (hasActiveConnections ? [1] : [])
httpActive: hasActiveConnections ? [1] : []
};
const $el = $('<kbn-loading-indicator></kbn-loading-indicator>');
$rootScope.$apply();
$compile($el)($rootScope);
$rootScope.$apply();
return $el;
};
});
});
it('injects a loading .spinner into the element', function () {
const $el = compile();
expect($el.find('.spinner')).to.have.length(1);
it(`doesn't have ng-hide class when there are connections`, function () {
const $el = compile(true);
expect($el.hasClass('ng-hide')).to.be(false);
});
it('applies removes ng-hide class when there are connections', function () {
const $el = compile(true);
expect($el.find('.spinner.ng-hide')).to.have.length(0);
it('has ng-hide class when there are no connections', function () {
const $el = compile(false);
expect($el.hasClass('ng-hide')).to.be(true);
});
});

View file

@ -3,7 +3,7 @@ import './global_nav';
import kbnChromeProv from './kbn_chrome';
import kbnChromeNavControlsProv from './append_nav_controls';
import './kbn_loading_indicator';
import './loading_indicator/loading_indicator';
export default function (chrome, internals) {
kbnChromeProv(chrome, internals);

View file

@ -0,0 +1,7 @@
<div
class="loadingIndicator"
ng-show="chrome.httpActive.length"
data-test-subj="globalLoadingIndicator"
>
<div class="loadingIndicator__bar"></div>
</div>

View file

@ -1,13 +1,14 @@
import UiModules from 'ui/modules';
import angular from 'angular';
const spinnerTemplate = '<div class="spinner" ng-show="chrome.httpActive.length"><div class="spinner-bar"></div></div>';
import template from './loading_indicator.html';
import './loading_indicator.less';
UiModules
.get('ui/kibana')
.directive('kbnLoadingIndicator', function ($compile) {
return {
restrict: 'E',
template: spinnerTemplate,
replace: true,
template,
};
});

View file

@ -0,0 +1,53 @@
@import "~ui/styles/variables";
@loadingIndicatorBackgroundSize: 400px;
@loadingIndicatorHeight: 2px;
@loadingIndcatorColor1: @kibanaPink1;
@loadingIndcatorColor2: @kibanaPink2;
/**
* 1. Position this loader on top of the content.
* 2. Make sure indicator isn't wider than the screen.
*/
.loadingIndicator {
position: fixed; // 1
top: 0; // 1
left: 0; // 1
right: 0; // 1
z-index: 1; // 1
overflow: hidden; // 2
height: @loadingIndicatorHeight;
&.ng-hide {
visibility: hidden;
opacity: 0;
transition-delay: 0.25s;
}
}
.loadingIndicator__bar {
top: 0;
left: 0;
right: 0;
bottom: 0;
position: absolute;
z-index: 10;
visibility: visible;
display: block;
animation: animate-loading-indcator 2s linear infinite;
background-color: @kibanaPink2;
background-image: linear-gradient(to right,
@loadingIndcatorColor1 0%,
@loadingIndcatorColor1 50%,
@loadingIndcatorColor2 50%,
@loadingIndcatorColor2 100%
);
background-repeat: repeat-x;
background-size: @loadingIndicatorBackgroundSize @loadingIndicatorBackgroundSize;
width: 200%;
}
@keyframes animate-loading-indcator {
from { transform: translateX(0); }
to { transform: translateX(-@loadingIndicatorBackgroundSize); }
}

View file

@ -1,39 +1,6 @@
@import "~ui/styles/variables";
@size: 400px;
@color1: @kibanaPink1;
@color2: @kibanaPink2;
.spinner.ng-hide {
visibility: hidden;
display: block !important;
opacity: 0;
transition-delay: 0.25s;
}
.spinner-bar {
top: 0;
left: 0;
right: 0;
height: 2px;
position: absolute;
z-index: 10;
visibility: visible;
display: block;
animation: move 2s linear infinite;
background-color: @kibanaPink2;
background-image: linear-gradient(to right,
@color1 0%,
@color1 50%,
@color2 50%,
@color2 100%
);
background-repeat: repeat-x;
background-size: @size @size;
width: 200%;
}
@keyframes move {
from { transform: translateX(0); }
to { transform: translateX(-@size); }
}

View file

@ -68,7 +68,7 @@ bdd.describe('dashboard tab', function describeIndexTests() {
PageObjects.common.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"');
return PageObjects.header.setAbsoluteRange(fromTime, toTime)
.then(function () {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
.then(function takeScreenshot() {
PageObjects.common.saveScreenshot('Dashboard-set-timepicker');

View file

@ -53,9 +53,9 @@ bdd.describe('visualize app', function describeIndexTests() {
.then(function clickGo() {
return PageObjects.visualize.clickGo();
})
.then(function getSpinnerDone() {
.then(function isGlobalLoadingIndicatorHidden() {
PageObjects.common.debug('Waiting...');
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
});

View file

@ -47,7 +47,7 @@ bdd.describe('visualize app', function describeIndexTests() {
return PageObjects.visualize.clickGo();
})
.then(function () {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
});

View file

@ -46,7 +46,7 @@ bdd.describe('visualize app', function describeIndexTests() {
return PageObjects.visualize.clickGo();
})
.then(function () {
return PageObjects.header.getSpinnerDone(); // only matches the hidden spinner
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
});

View file

@ -39,7 +39,7 @@ bdd.describe('visualize app', function describeIndexTests() {
return PageObjects.visualize.selectField('memory');
})
.then(function () {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
.then(function sleep() {
return PageObjects.common.sleep(1003);
@ -53,7 +53,7 @@ bdd.describe('visualize app', function describeIndexTests() {
return PageObjects.visualize.clickGo();
})
.then(function () {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
});

View file

@ -45,7 +45,7 @@ bdd.describe('visualize app', function describeIndexTests() {
return PageObjects.visualize.clickGo();
})
.then(function () {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
});

View file

@ -43,7 +43,7 @@ bdd.describe('visualize app', function describeIndexTests() {
return PageObjects.visualize.clickGo();
})
.then(function () {
return PageObjects.header.getSpinnerDone(); // only matches the hidden spinner
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
.then(function waitForVisualization() {
return PageObjects.visualize.waitForVisualization();
@ -66,7 +66,7 @@ bdd.describe('visualize app', function describeIndexTests() {
return PageObjects.visualize.loadSavedVisualization(vizName1);
})
.then(function () {
return PageObjects.header.getSpinnerDone(); // only matches the hidden spinner
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
.then(function waitForVisualization() {
return PageObjects.visualize.waitForVisualization();

View file

@ -74,7 +74,7 @@ export default class DashboardPage {
.findByCssSelector('[aria-label="Save Dashboard"]')
.click()
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
.then(() => {
return PageObjects.common.sleep(1000);
@ -86,7 +86,7 @@ export default class DashboardPage {
.type(dashName);
})
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
.then(() => {
return PageObjects.common.sleep(1000);
@ -101,7 +101,7 @@ export default class DashboardPage {
});
})
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
// verify that green message at the top of the page.
// it's only there for about 5 seconds
@ -136,7 +136,7 @@ export default class DashboardPage {
.type(dashName.replace('-',' '));
})
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
.then(() => {
return PageObjects.common.sleep(1000);
@ -146,7 +146,7 @@ export default class DashboardPage {
.clickDashboardByLinkText(dashName);
})
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
}

View file

@ -52,7 +52,7 @@ export default class DiscoverPage {
this.findTimeout.findByLinkText(searchName).click();
})
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
}
@ -80,7 +80,7 @@ export default class DiscoverPage {
}
getBarChartData() {
return PageObjects.header.getSpinnerDone()
return PageObjects.header.isGlobalLoadingIndicatorHidden()
.then(() => {
return this.findTimeout
.findAllByCssSelector('rect[data-label="Count"]');
@ -134,12 +134,12 @@ export default class DiscoverPage {
.click();
})
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
}
getHitCount() {
return PageObjects.header.getSpinnerDone()
return PageObjects.header.isGlobalLoadingIndicatorHidden()
.then(() => {
return PageObjects.common.findTestSubject('discoverQueryHits')
.getVisibleText();
@ -157,7 +157,7 @@ export default class DiscoverPage {
.click();
})
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
}

View file

@ -78,7 +78,7 @@ export default class HeaderPage {
.findByClassName('kbn-timepicker-go')
.click()
.then(function () {
return self.getSpinnerDone();
return self.isGlobalLoadingIndicatorHidden();
});
}
@ -101,7 +101,7 @@ export default class HeaderPage {
return this.clickGoButton();
})
.then(() => {
return this.getSpinnerDone();
return this.isGlobalLoadingIndicatorHidden();
})
.then(() => {
return this.clickTimepicker();
@ -125,9 +125,9 @@ export default class HeaderPage {
.click();
}
getSpinnerDone() {
isGlobalLoadingIndicatorHidden() {
return this.remote.setFindTimeout(defaultFindTimeout * 10)
.findByCssSelector('.spinner.ng-hide');
.findByCssSelector('[data-test-subj="globalLoadingIndicator"].ng-hide');
}
}

View file

@ -42,7 +42,7 @@ export default class SettingsPage {
return PageObjects.common.findTestSubject('advancedSetting-' + propertyName + '-editButton')
.click()
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
.then(() => {
return PageObjects.common.sleep(1000);
@ -53,14 +53,14 @@ export default class SettingsPage {
.click();
})
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
.then(function setAdvancedSettingsClickSaveButton() {
return PageObjects.common.findTestSubject('advancedSetting-' + propertyName + '-saveButton')
.click();
})
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
}
@ -105,7 +105,7 @@ export default class SettingsPage {
return this.getTimeFieldNameField().click();
})
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
.then(() => {
return PageObjects.common.try(() => {
@ -134,7 +134,7 @@ export default class SettingsPage {
return this.remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('button.btn.btn-success.ng-scope').click()
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
}
@ -167,7 +167,7 @@ export default class SettingsPage {
if (chartString === columnName) {
return chart.click()
.then(function () {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
}
});
@ -240,7 +240,7 @@ export default class SettingsPage {
(pageNum + 1) + ') a.ng-binding')
.click()
.then(function () {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
}
@ -262,7 +262,7 @@ export default class SettingsPage {
.findByCssSelector('button.btn.btn-default[aria-label="Plus"]')
.click()
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
}
@ -279,7 +279,7 @@ export default class SettingsPage {
.findByCssSelector('button.btn.btn-primary[aria-label="Cancel"]')
.click()
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
}
@ -288,7 +288,7 @@ export default class SettingsPage {
.findByCssSelector('button.btn.btn-success.ng-binding[aria-label="Update Field"]')
.click()
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
}
@ -297,7 +297,7 @@ export default class SettingsPage {
.findByCssSelector('form.form-inline.pagination-size.ng-scope.ng-pristine.ng-valid div.form-group option[label="' + size + '"]')
.click()
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
}
@ -315,7 +315,7 @@ export default class SettingsPage {
});
})
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
.then(() => {
return PageObjects.common.try(() => {

View file

@ -288,7 +288,7 @@ export default class VisualizePage {
.findByCssSelector('.btn-success')
.click()
.then(function () {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
}
@ -323,7 +323,7 @@ export default class VisualizePage {
.click();
})
.then(function () {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
})
// verify that green message at the top of the page.
// it's only there for about 5 seconds
@ -704,7 +704,7 @@ export default class VisualizePage {
return PageObjects.common.sleep(1000);
})
.then(() => {
return PageObjects.header.getSpinnerDone();
return PageObjects.header.isGlobalLoadingIndicatorHidden();
});
}