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

KQL in TSVB #36784

Merged
merged 25 commits into from
Jun 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
608dd82
Squashed commit of the following:
TinaHeiligers May 21, 2019
2a62e2b
Changes QueryBar to QueryBarInput in TimeseriesPanelConfigUi
TinaHeiligers May 22, 2019
3c0d1eb
Squashed commit of the following:
TinaHeiligers May 23, 2019
169ead8
Removes quotes around index pattern strings passed into fetch_index_p…
TinaHeiligers May 24, 2019
fe6d0fc
queryBar to queryBarInput
TinaHeiligers May 28, 2019
07f527e
Deletes unused code
TinaHeiligers May 28, 2019
a532c0e
Fixes migrations test
TinaHeiligers May 28, 2019
027017a
compiles visualizations migrations for version 7.3.0
TinaHeiligers May 29, 2019
6ae9a58
temporary change to query_bar_input fetch_index_patterns file
TinaHeiligers May 29, 2019
6a62dd0
Formatting
TinaHeiligers May 30, 2019
3ce1bf0
Merge branch 'master' of github.com:elastic/kibana into KQL_in_TSVB
TinaHeiligers May 31, 2019
ae800d8
Uses series index pattern when override_index_pattern is selected and…
TinaHeiligers May 31, 2019
08d35bb
Removes empty object as alternative to localStorage
TinaHeiligers May 31, 2019
be1b9e5
Removes handelQueryChange from where it is not needed
TinaHeiligers May 31, 2019
1bd18cc
extracts retrieval of the default query language from uiSettings into…
TinaHeiligers May 31, 2019
bcf47cc
Merge branch 'master' of github.com:elastic/kibana into KQL_in_TSVB
TinaHeiligers Jun 4, 2019
f591233
Resolves some PR comments
TinaHeiligers Jun 4, 2019
b72f77d
Handles Query Error from an invalid syntax query
TinaHeiligers Jun 4, 2019
ac1aa6d
Converts string queries into objects with kuery as the language in sa…
TinaHeiligers Jun 4, 2019
7015e00
Deletes unused translation items
TinaHeiligers Jun 4, 2019
8365673
Adds optional id prop to the query_bar_input and makes the screenTitl…
TinaHeiligers Jun 4, 2019
32f3eda
Merge branch 'master' of github.com:elastic/kibana into KQL_in_TSVB
TinaHeiligers Jun 5, 2019
7d9a7bc
Updates [eCommerce] Sold Products per Day
TinaHeiligers Jun 5, 2019
1bd40e6
Removes screenTitle from component QueryBarInput
TinaHeiligers Jun 5, 2019
8506fd5
Wraps index pattern strings in a double quote to allow for names such…
TinaHeiligers Jun 5, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,6 @@ describe('QueryBarInput', () => {
intl={null as any}
/>
);

expect(mockFetchIndexPatterns).toHaveBeenCalledWith(['logstash-*']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ interface Props {
intl: InjectedIntl;
query: Query;
appName: string;
id?: string;
disableAutoFocus?: boolean;
screenTitle: string;
screenTitle?: string;
prepend?: any;
store: Storage;
persistedLog?: PersistedLog;
Expand Down Expand Up @@ -434,6 +435,7 @@ export class QueryBarInputUI extends Component<Props, State> {
<div role="search">
<div className="kuiLocalSearchAssistedInput">
<EuiFieldText
id={this.props.id}
placeholder={this.props.intl.formatMessage({
id: 'data.query.queryBar.searchInputPlaceholder',
defaultMessage: 'Search',
Expand All @@ -452,17 +454,21 @@ export class QueryBarInputUI extends Component<Props, State> {
}}
autoComplete="off"
spellCheck={false}
aria-label={this.props.intl.formatMessage(
{
id: 'data.query.queryBar.searchInputAriaLabel',
defaultMessage:
'You are on search box of {previouslyTranslatedPageTitle} page. Start typing to search and filter the {pageType}',
},
{
previouslyTranslatedPageTitle: this.props.screenTitle,
pageType: this.props.appName,
}
)}
aria-label={
this.props.screenTitle
? this.props.intl.formatMessage(
{
id: 'data.query.queryBar.searchInputAriaLabel',
defaultMessage:
'You are on search box of {previouslyTranslatedPageTitle} page. Start typing to search and filter the {pageType}',
},
{
previouslyTranslatedPageTitle: this.props.screenTitle,
pageType: this.props.appName,
}
)
: undefined
}
type="text"
data-test-subj="queryInput"
aria-autocomplete="list"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export async function fetchIndexPatterns(indexPatternStrings: string[]) {
return [];
}

const searchString = indexPatternStrings.join(' | ');
const searchString = indexPatternStrings.map(string => `"${string}"`).join(' | ');
const indexPatternsFromSavedObjects = await chrome.getSavedObjectsClient().find({
type: 'index-pattern',
fields: ['title', 'fields'],
Expand Down
97 changes: 94 additions & 3 deletions src/legacy/core_plugins/kibana/migrations.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,6 @@ const migrateDateHistogramAggregation = doc => {
return doc;
};

const executeMigrations720 = flow(migratePercentileRankAggregation, migrateDateHistogramAggregation);

function removeDateHistogramTimeZones(doc) {
const visStateJSON = get(doc, 'attributes.visState');
if (visStateJSON) {
Expand Down Expand Up @@ -189,6 +187,99 @@ function migrateGaugeVerticalSplitToAlignment(doc) {
}
return doc;
}
// Migrate filters (string -> { query: string, language: lucene })
/*
Enabling KQL in TSVB causes problems with savedObject visualizations when these are saved with filters.
In a visualisation type of saved object, if the visState param is of type metric, the filter is saved as a string that is not interpretted correctly as a lucene query in the visualization itself.
We need to transform the filter string into an object containing the original string as a query and specify the query language as lucene.
For Metrics visualizations (param.type === "metric"), filters can be applied to each series object in the series array within the SavedObject.visState.params object.
Path to the series array is thus:
attributes.visState.
*/
function transformFilterStringToQueryObject(doc) {
// Migrate filters
// If any filters exist and they are a string, we assume it to be lucene and transform the filter into an object accordingly
const newDoc = cloneDeep(doc);
const visStateJSON = get(doc, 'attributes.visState');
if (visStateJSON) {
let visState;
try {
visState = JSON.parse(visStateJSON);
} catch (e) {
// let it go, the data is invalid and we'll leave it as is
}
if (visState) {
const visType = get(visState, 'params.type');
const tsvbTypes = ['metric', 'markdown', 'top_n', 'gauge', 'table', 'timeseries'];
if (tsvbTypes.indexOf(visType) === -1) {
// skip
return doc;
}
// migrate the params fitler
const params = get(visState, 'params');
if (params.filter && typeof params.filter === 'string') {
const paramsFilterObject = {
query: params.filter,
language: 'lucene',
};
params.filter = paramsFilterObject;
}

// migrate the annotations query string:
const annotations = get(visState, 'params.annotations') || [];
annotations.forEach((item) => {
if (!item.query_string) {
// we don't need to transform anything if there isn't a filter at all
return;
}
if (typeof item.query_string === 'string') {
const itemQueryStringObject = {
query: item.query_string,
language: 'lucene',
};
item.query_string = itemQueryStringObject;
}
});
// migrate the series filters
const series = get(visState, 'params.series') || [];
series.forEach((item) => {
if (!item.filter) {
// we don't need to transform anything if there isn't a filter at all
return;
}
// series item filter
if (typeof item.filter === 'string') {
const itemfilterObject = {
query: item.filter,
language: 'lucene',
};
item.filter = itemfilterObject;
}
// series item split filters filter
if (item.split_filters) {
const splitFilters = get(item, 'split_filters') || [];
splitFilters.forEach((filter) => {
if (!filter.filter) {
// we don't need to transform anything if there isn't a filter at all
return;
}
if (typeof filter.filter === 'string') {
const filterfilterObject = {
query: filter.filter,
language: 'lucene',
};
filter.filter = filterfilterObject;
}
});
}
});
newDoc.attributes.visState = JSON.stringify(visState);
}
}
return newDoc;
}
const executeMigrations720 = flow(migratePercentileRankAggregation, migrateDateHistogramAggregation);
const executeMigrations730 = flow(migrateGaugeVerticalSplitToAlignment, transformFilterStringToQueryObject);

export const migrations = {
'index-pattern': {
Expand Down Expand Up @@ -292,7 +383,7 @@ export const migrations = {
},
'7.0.1': removeDateHistogramTimeZones,
'7.2.0': doc => executeMigrations720(doc),
'7.3.0': migrateGaugeVerticalSplitToAlignment
'7.3.0': executeMigrations730
},
dashboard: {
'7.0.0': (doc) => {
Expand Down
73 changes: 73 additions & 0 deletions src/legacy/core_plugins/kibana/migrations.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,79 @@ Object {
`);
});
});
describe('7.3.0 tsvb', () => {
const migrate = doc => migrations.visualization['7.3.0'](doc);
const generateDoc = ({ params }) => ({
attributes: {
title: 'My Vis',
description: 'This is my super cool vis.',
visState: JSON.stringify({ params }),
uiStateJSON: '{}',
version: 1,
kibanaSavedObjectMeta: {
searchSourceJSON: '{}',
},
},
});
it('should change series item filters from a string into an object', () => {
const params = { type: 'metric', series: [{ filter: 'Filter Bytes Test:>1000' }] };
const testDoc1 = generateDoc({ params });
const migratedTestDoc1 = migrate(testDoc1);
const series = JSON.parse(migratedTestDoc1.attributes.visState).params.series;
expect(series[0].filter).toHaveProperty('query');
expect(series[0].filter).toHaveProperty('language');
});
it('should not change a series item filter string in the object after migration', () => {
const markdownParams = {
type: 'markdown',
series: [
{
filter: 'Filter Bytes Test:>1000',
split_filters: [{ filter: 'bytes:>1000' }],
}
]
};
const markdownDoc = generateDoc({ params: markdownParams });
const migratedMarkdownDoc = migrate(markdownDoc);
const markdownSeries = JSON.parse(migratedMarkdownDoc.attributes.visState).params.series;
expect(markdownSeries[0].filter.query).toBe(JSON.parse(markdownDoc.attributes.visState).params.series[0].filter);
expect(markdownSeries[0].split_filters[0].filter.query)
.toBe(JSON.parse(markdownDoc.attributes.visState).params.series[0].split_filters[0].filter);
});
it('should change series item filters from a string into an object for all filters', () => {
const params = {
type: 'timeseries',
filter: 'bytes:>1000',
series: [
{
filter: 'Filter Bytes Test:>1000',
split_filters: [{ filter: 'bytes:>1000' }],
}
],
annotations: [{ query_string: 'bytes:>1000' }],
};
const timeSeriesDoc = generateDoc({ params: params });
const migratedtimeSeriesDoc = migrate(timeSeriesDoc);
const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params;
expect(Object.keys(timeSeriesParams.series[0].filter)).toEqual(expect.arrayContaining(['query', 'language']));
expect(Object.keys(timeSeriesParams.series[0].split_filters[0].filter)).toEqual(expect.arrayContaining(['query', 'language']));
expect(Object.keys(timeSeriesParams.annotations[0].query_string)).toEqual(expect.arrayContaining(['query', 'language']));
});
it('should not fail on a metric visualization without a filter in a series item', () => {
const params = { type: 'metric', series: [{}, {}, {}] };
const testDoc1 = generateDoc({ params });
const migratedTestDoc1 = migrate(testDoc1);
const series = JSON.parse(migratedTestDoc1.attributes.visState).params.series;
expect(series[2]).not.toHaveProperty('filter.query');
});
it('should not migrate a visualization of unknown type', () => {
const params = { type: 'unknown', series: [{ filter: 'foo:bar' }] };
const doc = generateDoc({ params });
const migratedDoc = migrate(doc);
const series = JSON.parse(migratedDoc.attributes.visState).params.series;
expect(series[0].filter).toEqual(params.series[0].filter);
});
});
});

describe('dashboard', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import { FieldSelect } from './aggs/field_select';
import uuid from 'uuid';
import { IconSelect } from './icon_select';
import { YesNo } from './yes_no';
import { Storage } from 'ui/storage';
import { data } from 'plugins/data';
const { QueryBarInput } = data.query.ui;
import { getDefaultQueryLanguage } from './lib/get_default_query_language';

import {
htmlIdGenerator,
Expand Down Expand Up @@ -58,8 +62,8 @@ function newAnnotation() {

const RESTRICT_FIELDS = [ES_TYPES.DATE];

const localStorage = new Storage(window.localStorage);
export class AnnotationsEditor extends Component {

constructor(props) {
super(props);
this.renderRow = this.renderRow.bind(this);
Expand All @@ -73,9 +77,20 @@ export class AnnotationsEditor extends Component {
handleChange(_.assign({}, item, part));
};
}

handleQueryChange = (model, filter) => {
const part = { query_string: filter };
collectionActions.handleChange(this.props, {
...model,
...part
});
};
renderRow(row) {
const defaults = { fields: '', template: '', index_pattern: '*', query_string: '' };
const defaults = {
fields: '',
template: '',
index_pattern: '*',
query_string: { query: '', language: getDefaultQueryLanguage() }
};
const model = { ...defaults, ...row };
const handleChange = (part) => {
const fn = collectionActions.handleChange.bind(null, this.props);
Expand Down Expand Up @@ -154,10 +169,16 @@ export class AnnotationsEditor extends Component {
/>)}
fullWidth
>
<EuiFieldText
onChange={this.handleChange(model, 'query_string')}
value={model.query_string}
fullWidth
<QueryBarInput
query={{
language: model.query_string.language || getDefaultQueryLanguage(),
query: model.query_string.query || '',
}}
onChange={query => this.handleQueryChange(model, query)}
appName={'VisEditor'}
indexPatterns={[model.index_pattern]}
store={localStorage}
showDatePicker={false}
/>
</EuiFormRow>
</EuiFlexItem>
Expand Down Expand Up @@ -250,7 +271,6 @@ export class AnnotationsEditor extends Component {
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>

</EuiFlexItem>

<EuiFlexItem grow={false}>
Expand Down Expand Up @@ -312,7 +332,6 @@ export class AnnotationsEditor extends Component {
</div>
);
}

}

AnnotationsEditor.defaultProps = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/


import chrome from 'ui/chrome';

export function getDefaultQueryLanguage() {
return chrome.getUiSettingsClient().get('search:queryLanguage');
}
Loading