Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow creating filters from fields with null values in discover #70936

Merged
merged 11 commits into from Jul 9, 2020
Expand Up @@ -78,7 +78,12 @@ function decorateFlattenedWrapper(hit: Record<string, any>, metaFields: Record<s
// unwrap computed fields
_.forOwn(hit.fields, function (val, key: any) {
if (key[0] === '_' && !_.includes(metaFields, key)) return;
flattened[key] = Array.isArray(val) && val.length === 1 ? val[0] : val;
// Flatten an array with 0 or 1 elements to a single value.
if (Array.isArray(val) && val.length <= 1) {
flattened[key] = val[0];
} else {
flattened[key] = val;
}
});

return flattened;
Expand Down
Expand Up @@ -106,11 +106,15 @@ export function generateFilters(
// exists filter special case: fieldname = '_exists' and value = fieldname
const filterType = fieldName === '_exists_' ? FILTERS.EXISTS : FILTERS.PHRASE;
const actualFieldObj = fieldName === '_exists_' ? ({ name: value } as IFieldType) : fieldObj;

// Fix for #7189 - if value is empty, phrase filters become exists filters.
const isNullFilter = value === null || value === undefined;

filter = buildFilter(
tmpIndexPattern,
actualFieldObj,
filterType,
negate,
isNullFilter ? FILTERS.EXISTS : filterType,
isNullFilter ? !negate : negate,
false,
value,
null,
Expand Down
3 changes: 2 additions & 1 deletion src/plugins/data/public/ui/filter_bar/filter_item.tsx
Expand Up @@ -135,9 +135,10 @@ export function FilterItem(props: Props) {
const dataTestSubjValue = filter.meta.value
? `filter-value-${isValidLabel(labelConfig) ? labelConfig.title : labelConfig.status}`
: '';
const dataTestSubjNegated = filter.meta.negate ? 'filter-negated' : '';
const dataTestSubjDisabled = `filter-${isDisabled(labelConfig) ? 'disabled' : 'enabled'}`;
const dataTestSubjPinned = `filter-${isFilterPinned(filter) ? 'pinned' : 'unpinned'}`;
return `filter ${dataTestSubjDisabled} ${dataTestSubjKey} ${dataTestSubjValue} ${dataTestSubjPinned}`;
return `filter ${dataTestSubjDisabled} ${dataTestSubjKey} ${dataTestSubjValue} ${dataTestSubjPinned} ${dataTestSubjNegated}`;
}

function getPanels() {
Expand Down
Expand Up @@ -151,11 +151,7 @@ export function createTableRowDirective($compile: ng.ICompileService, $httpParam
}

$scope.columns.forEach(function (column: any) {
const isFilterable =
$scope.flattenedRow[column] !== undefined &&
mapping(column) &&
mapping(column).filterable &&
$scope.filter;
const isFilterable = mapping(column) && mapping(column).filterable && $scope.filter;

newHtmls.push(
cellTemplate({
Expand Down
31 changes: 29 additions & 2 deletions test/functional/apps/discover/_doc_navigation.js
Expand Up @@ -20,14 +20,19 @@
import expect from '@kbn/expect';

export default function ({ getService, getPageObjects }) {
const log = getService('log');
const docTable = getService('docTable');
const filterBar = getService('filterBar');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common', 'discover', 'timePicker']);
const PageObjects = getPageObjects(['common', 'discover', 'timePicker', 'context']);
const esArchiver = getService('esArchiver');
const retry = getService('retry');

describe('doc link in discover', function contextSize() {
before(async function () {
beforeEach(async function () {
log.debug('load kibana index with default index pattern');
await esArchiver.loadIfNeeded('discover');

await esArchiver.loadIfNeeded('logstash_functional');
await PageObjects.common.navigateToApp('discover');
await PageObjects.timePicker.setDefaultAbsoluteRange();
Expand All @@ -50,5 +55,27 @@ export default function ({ getService, getPageObjects }) {
const hasDocHit = await testSubjects.exists('doc-hit');
expect(hasDocHit).to.be(true);
});

it('add filter should create an exists filter if value is null (#7189)', async function () {
await PageObjects.discover.waitUntilSearchingHasFinished();
// Filter special document
await filterBar.addFilter('agent', 'is', 'Missing/Fields');
await PageObjects.discover.waitUntilSearchingHasFinished();

// navigate to the doc view
await docTable.clickRowToggle({ rowIndex: 0 });

const details = await docTable.getDetailsRow();
await docTable.addInclusiveFilter(details, 'referer');
await PageObjects.discover.waitUntilSearchingHasFinished();

const hasInclusiveFilter = await filterBar.hasFilter('referer', 'exists', true, false, true);
expect(hasInclusiveFilter).to.be(true);

await docTable.removeInclusiveFilter(details, 'referer');
await PageObjects.discover.waitUntilSearchingHasFinished();
const hasExcludeFilter = await filterBar.hasFilter('referer', 'exists', true, false, false);
expect(hasExcludeFilter).to.be(true);
});
});
}
Binary file not shown.
21 changes: 21 additions & 0 deletions test/functional/services/doc_table.ts
Expand Up @@ -58,6 +58,11 @@ export function DocTableProvider({ getService, getPageObjects }: FtrProviderCont
: (await this.getBodyRows())[options.rowIndex];
}

public async getDetailsRow(): Promise<WebElementWrapper> {
const table = await this.getTable();
return await table.findByCssSelector('[data-test-subj~="docTableDetailsRow"]');
}

public async getAnchorDetailsRow(): Promise<WebElementWrapper> {
const table = await this.getTable();
return await table.findByCssSelector(
Expand Down Expand Up @@ -133,6 +138,22 @@ export function DocTableProvider({ getService, getPageObjects }: FtrProviderCont
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
}

public async getRemoveInclusiveFilterButton(
tableDocViewRow: WebElementWrapper
): Promise<WebElementWrapper> {
return await tableDocViewRow.findByTestSubject(`~removeInclusiveFilterButton`);
}

public async removeInclusiveFilter(
detailsRow: WebElementWrapper,
fieldName: WebElementWrapper
): Promise<void> {
const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName);
const addInclusiveFilterButton = await this.getRemoveInclusiveFilterButton(tableDocViewRow);
await addInclusiveFilterButton.click();
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
}

public async getAddExistsFilterButton(
tableDocViewRow: WebElementWrapper
): Promise<WebElementWrapper> {
Expand Down
8 changes: 6 additions & 2 deletions test/functional/services/filter_bar.ts
Expand Up @@ -31,17 +31,21 @@ export function FilterBarProvider({ getService, getPageObjects }: FtrProviderCon
* @param key field name
* @param value filter value
* @param enabled filter status
* @param pinned filter pinned status
* @param negated filter including or excluding value
*/
public async hasFilter(
key: string,
value: string,
enabled: boolean = true,
pinned: boolean = false
pinned: boolean = false,
negated: boolean = false
): Promise<boolean> {
const filterActivationState = enabled ? 'enabled' : 'disabled';
const filterPinnedState = pinned ? 'pinned' : 'unpinned';
const filterNegatedState = negated ? 'filter-negated' : '';
return testSubjects.exists(
`filter filter-${filterActivationState} filter-key-${key} filter-value-${value} filter-${filterPinnedState}`,
`filter filter-${filterActivationState} filter-key-${key} filter-value-${value} filter-${filterPinnedState} ${filterNegatedState}`,
{
allowHidden: true,
}
Expand Down