Skip to content

Commit

Permalink
Add new title query param to dashboard listing page (elastic#14760)
Browse files Browse the repository at this point in the history
* introduce a mechanism to load a dashboard by title is a single one is found, if not, to refill the search box on the listing page

* Make case insensitive and prevent listing page from "blipping" up on the screen before the redirect

* Add tests
  • Loading branch information
stacey-gammon authored and chrisronline committed Nov 20, 2017
1 parent c3ab4cc commit 145afa9
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 5 deletions.
28 changes: 27 additions & 1 deletion src/core_plugins/kibana/public/dashboard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { DashboardListingController } from './listing/dashboard_listing';
import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants';
import { SavedObjectNotFound } from 'ui/errors';
import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
import { SavedObjectsClientProvider } from 'ui/saved_objects';

uiRoutes
.defaults(/dashboard/, {
Expand All @@ -20,7 +21,32 @@ uiRoutes
.when(DashboardConstants.LANDING_PAGE_PATH, {
template: dashboardListingTemplate,
controller: DashboardListingController,
controllerAs: 'listingController'
controllerAs: 'listingController',
resolve: {
dash: function ($route, Private, courier, kbnUrl) {
const savedObjectsClient = Private(SavedObjectsClientProvider);
const title = $route.current.params.title;
if (title) {
return savedObjectsClient.find({
search: `"${title}"`,
search_fields: 'title',
type: 'dashboard',
}).then(results => {
// The search isn't an exact match, lets see if we can find a single exact match to use
const matchingDashboards = results.savedObjects.filter(
dashboard => dashboard.attributes.title.toLowerCase() === title.toLowerCase());
if (matchingDashboards.length === 1) {
kbnUrl.redirect(createDashboardEditUrl(matchingDashboards[0].id));
} else {
kbnUrl.redirect(`${DashboardConstants.LANDING_PAGE_PATH}?filter="${title}"`);
}
throw uiRoutes.WAIT_FOR_URL_CHANGE_TOKEN;
}).catch(courier.redirectWhenMissing({
'dashboard': DashboardConstants.LANDING_PAGE_PATH
}));
}
}
}
})
.when(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {
template: dashboardTemplate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { DashboardConstants, createDashboardEditUrl } from '../dashboard_constan
import { SortableProperties } from 'ui_framework/services';
import { ConfirmationButtonTypes } from 'ui/modals';

export function DashboardListingController($injector, $scope) {
export function DashboardListingController($injector, $scope, $location) {
const $filter = $injector.get('$filter');
const confirmModal = $injector.get('confirmModal');
const Notifier = $injector.get('Notifier');
Expand Down Expand Up @@ -69,7 +69,7 @@ export function DashboardListingController($injector, $scope) {
this.isFetchingItems = false;
this.items = [];
this.pageOfItems = [];
this.filter = '';
this.filter = ($location.search()).filter || '';

this.pager = pagerFactory.create(this.items.length, 20, 1);

Expand All @@ -78,6 +78,7 @@ export function DashboardListingController($injector, $scope) {
$scope.$watch(() => this.filter, () => {
deselectAll();
fetchItems();
$location.search('filter', this.filter);
});
this.isAscending = (name) => sortableProperties.isAscendingByName(name);
this.getSortedProperty = () => sortableProperties.getSortedProperty();
Expand Down
82 changes: 81 additions & 1 deletion test/functional/apps/dashboard/_dashboard_listing.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import expect from 'expect.js';

export default function ({ getPageObjects }) {
export default function ({ getService, getPageObjects }) {
const PageObjects = getPageObjects(['dashboard', 'header', 'common']);
const remote = getService('remote');

describe('dashboard listing page', function describeIndexTests() {
const dashboardName = 'Dashboard Listing Test';
Expand Down Expand Up @@ -104,5 +105,84 @@ export default function ({ getPageObjects }) {
expect(countOfDashboards).to.equal(1);
});
});

describe('search by title', function () {
it('loads a dashboard if title matches', async function () {
const currentUrl = await remote.getCurrentUrl();
const newUrl = currentUrl + '&title=Two%20Words';
// Only works on a hard refresh.
const useTimeStamp = true;
await remote.get(newUrl.toString(), useTimeStamp);

const onDashboardLandingPage = await PageObjects.dashboard.onDashboardLandingPage();
expect(onDashboardLandingPage).to.equal(false);
});

it('title match is case insensitive', async function () {
await PageObjects.dashboard.gotoDashboardLandingPage();
const currentUrl = await remote.getCurrentUrl();
const newUrl = currentUrl + '&title=two%20words';
// Only works on a hard refresh.
const useTimeStamp = true;
await remote.get(newUrl.toString(), useTimeStamp);

const onDashboardLandingPage = await PageObjects.dashboard.onDashboardLandingPage();
expect(onDashboardLandingPage).to.equal(false);
});

it('stays on listing page if title matches no dashboards', async function () {
await PageObjects.dashboard.gotoDashboardLandingPage();
const currentUrl = await remote.getCurrentUrl();
const newUrl = currentUrl + '&title=nodashboardsnamedme';
// Only works on a hard refresh.
const useTimeStamp = true;
await remote.get(newUrl.toString(), useTimeStamp);

await PageObjects.header.waitUntilLoadingHasFinished();
const onDashboardLandingPage = await PageObjects.dashboard.onDashboardLandingPage();
expect(onDashboardLandingPage).to.equal(true);
});

it('preloads search filter bar when there is no match', async function () {
const searchFilter = await PageObjects.dashboard.getSearchFilterValue();
expect(searchFilter).to.equal('"nodashboardsnamedme"');
});

it('stays on listing page if title matches two dashboards', async function () {
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.saveDashboard('two words', { needsConfirm: true });
await PageObjects.dashboard.gotoDashboardLandingPage();
const currentUrl = await remote.getCurrentUrl();
const newUrl = currentUrl + '&title=two%20words';
// Only works on a hard refresh.
const useTimeStamp = true;
await remote.get(newUrl.toString(), useTimeStamp);

await PageObjects.header.waitUntilLoadingHasFinished();
const onDashboardLandingPage = await PageObjects.dashboard.onDashboardLandingPage();
expect(onDashboardLandingPage).to.equal(true);
});

it('preloads search filter bar when there is more than one match', async function () {
const searchFilter = await PageObjects.dashboard.getSearchFilterValue();
expect(searchFilter).to.equal('"two words"');
});

it('matches a title with many special characters', async function () {
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.saveDashboard('i am !@#$%^&*()_+~`,.<>{}[]; so special');
await PageObjects.dashboard.gotoDashboardLandingPage();
const currentUrl = await remote.getCurrentUrl();
// Need to encode that one.
const newUrl = currentUrl + '&title=i%20am%20%21%40%23%24%25%5E%26%2A%28%29_%2B~%60%2C.%3C%3E%7B%7D%5B%5D%3B%20so%20special';
// Only works on a hard refresh.
const useTimeStamp = true;
await remote.get(newUrl.toString(), useTimeStamp);

await PageObjects.header.waitUntilLoadingHasFinished();
const onDashboardLandingPage = await PageObjects.dashboard.onDashboardLandingPage();
expect(onDashboardLandingPage).to.equal(false);
});
});
});
}
11 changes: 10 additions & 1 deletion test/functional/page_objects/dashboard_page.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,11 +292,15 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
/**
*
* @param dashName {String}
* @param saveOptions {{storeTimeWithDashboard: boolean, saveAsNew: boolean}}
* @param saveOptions {{storeTimeWithDashboard: boolean, saveAsNew: boolean, needsConfirm: false}}
*/
async saveDashboard(dashName, saveOptions = {}) {
await this.enterDashboardTitleAndClickSave(dashName, saveOptions);

if (saveOptions.needsConfirm) {
await PageObjects.common.clickConfirmOnModal();
}

await PageObjects.header.waitUntilLoadingHasFinished();

// verify that green message at the top of the page.
Expand Down Expand Up @@ -346,6 +350,11 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
});
}

async getSearchFilterValue() {
const searchFilter = await testSubjects.find('searchFilter');
return await searchFilter.getProperty('value');
}

async searchForDashboardWithName(dashName) {
log.debug(`searchForDashboardWithName: ${dashName}`);

Expand Down

0 comments on commit 145afa9

Please sign in to comment.