Skip to content

Commit

Permalink
[Vis: Default editor] EUIficate time interval control (#34991) (#36006)
Browse files Browse the repository at this point in the history
* EUIficate time interval control

* Update tests

* Remove empty option, update fix tests

* Bind vis to scope for react component only

* Combine two interval inputs into one EuiCombobox

* Add error message

* Add migration script; remove unused translations

* Update fuctional tests

* Update unit test

* Update tests; refactoring

* Use flow to invoke several functions

* Update test

* Refactoring

* Reset options when timeBase

* Add type for editorConfig prop

* Add placeholder

* Fix lint errors

* Call write after interval changing

* Fix code review comments

* Make replace for model name global

* Revert error catch

* Remove old dependency

* Add unit test for migration test

* Fix message

* Fix code review comments

* Update functional test
  • Loading branch information
sulemanof authored May 3, 2019
1 parent 813b6ef commit aba6d62
Show file tree
Hide file tree
Showing 23 changed files with 524 additions and 258 deletions.
120 changes: 81 additions & 39 deletions src/legacy/core_plugins/kibana/migrations.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import { cloneDeep, get, omit, has } from 'lodash';
import { cloneDeep, get, omit, has, flow } from 'lodash';

function migrateIndexPattern(doc) {
const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON');
Expand Down Expand Up @@ -57,6 +57,85 @@ function migrateIndexPattern(doc) {
doc.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify(searchSource);
}

// [TSVB] Migrate percentile-rank aggregation (value -> values)
const migratePercentileRankAggregation = doc => {
const visStateJSON = get(doc, 'attributes.visState');
let visState;

if (visStateJSON) {
try {
visState = JSON.parse(visStateJSON);
} catch (e) {
// Let it go, the data is invalid and we'll leave it as is
}
if (visState && visState.type === 'metrics') {
const series = get(visState, 'params.series') || [];

series.forEach(part => {
(part.metrics || []).forEach(metric => {
if (metric.type === 'percentile_rank' && has(metric, 'value')) {
metric.values = [metric.value];

delete metric.value;
}
});
});
return {
...doc,
attributes: {
...doc.attributes,
visState: JSON.stringify(visState),
},
};
}
}
return doc;
};

// Migrate date histogram aggregation (remove customInterval)
const migrateDateHistogramAggregation = doc => {
const visStateJSON = get(doc, 'attributes.visState');
let visState;

if (visStateJSON) {
try {
visState = JSON.parse(visStateJSON);
} catch (e) {
// Let it go, the data is invalid and we'll leave it as is
}

if (visState && visState.aggs) {
visState.aggs.forEach(agg => {
if (agg.type === 'date_histogram' && agg.params) {
if (agg.params.interval === 'custom') {
agg.params.interval = agg.params.customInterval;
}
delete agg.params.customInterval;
}

if (get(agg, 'params.customBucket.type', null) === 'date_histogram'
&& agg.params.customBucket.params
) {
if (agg.params.customBucket.params.interval === 'custom') {
agg.params.customBucket.params.interval = agg.params.customBucket.params.customInterval;
}
delete agg.params.customBucket.params.customInterval;
}
});
return {
...doc,
attributes: {
...doc.attributes,
visState: JSON.stringify(visState),
}
};
}
}
return doc;
};

const executeMigrations710 = flow(migratePercentileRankAggregation, migrateDateHistogramAggregation);

function removeDateHistogramTimeZones(doc) {
const visStateJSON = get(doc, 'attributes.visState');
if (visStateJSON) {
Expand Down Expand Up @@ -185,44 +264,7 @@ export const migrations = {
}
},
'7.0.1': removeDateHistogramTimeZones,
'7.1.0': doc => {
// [TSVB] Migrate percentile-rank aggregation (value -> values)
const migratePercentileRankAggregation = doc => {
const visStateJSON = get(doc, 'attributes.visState');
let visState;

if (visStateJSON) {
try {
visState = JSON.parse(visStateJSON);
} catch (e) {
// Let it go, the data is invalid and we'll leave it as is
}
if (visState && visState.type === 'metrics') {
const series = get(visState, 'params.series') || [];

series.forEach(part => {
(part.metrics || []).forEach(metric => {
if (metric.type === 'percentile_rank' && has(metric, 'value')) {
metric.values = [metric.value];

delete metric.value;
}
});
});
return {
...doc,
attributes: {
...doc.attributes,
visState: JSON.stringify(visState),
},
};
}
}
return doc;
};

return migratePercentileRankAggregation(doc);
}
'7.1.0': doc => executeMigrations710(doc)
},
dashboard: {
'7.0.0': (doc) => {
Expand Down
127 changes: 127 additions & 0 deletions src/legacy/core_plugins/kibana/migrations.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,133 @@ Object {
expect(() => migrate(doc)).toThrowError(/My Vis/);
});
});

describe('date histogram custom interval removal', () => {
const migrate = doc => migrations.visualization['7.1.0'](doc);
let doc;
beforeEach(() => {
doc = {
attributes: {
visState: JSON.stringify({
aggs: [
{
'enabled': true,
'id': '1',
'params': {
'customInterval': '1h'
},
'schema': 'metric',
'type': 'count'
},
{
'enabled': true,
'id': '2',
'params': {
'customInterval': '2h',
'drop_partials': false,
'extended_bounds': {},
'field': 'timestamp',
'interval': 'auto',
'min_doc_count': 1,
'useNormalizedEsInterval': true
},
'schema': 'segment',
'type': 'date_histogram'
},
{
'enabled': true,
'id': '4',
'params': {
'customInterval': '2h',
'drop_partials': false,
'extended_bounds': {},
'field': 'timestamp',
'interval': 'custom',
'min_doc_count': 1,
'useNormalizedEsInterval': true
},
'schema': 'segment',
'type': 'date_histogram'
},
{
'enabled': true,
'id': '3',
'params': {
'customBucket': {
'enabled': true,
'id': '1-bucket',
'params': {
'customInterval': '2h',
'drop_partials': false,
'extended_bounds': {},
'field': 'timestamp',
'interval': 'custom',
'min_doc_count': 1,
'useNormalizedEsInterval': true
},
'type': 'date_histogram'
},
'customMetric': {
'enabled': true,
'id': '1-metric',
'params': {},
'type': 'count'
}
},
'schema': 'metric',
'type': 'max_bucket'
},
]
}),
}
};
});

it('should remove customInterval from date_histogram aggregations', () => {
const migratedDoc = migrate(doc);
const aggs = JSON.parse(migratedDoc.attributes.visState).aggs;
expect(aggs[1]).not.toHaveProperty('params.customInterval');
});

it('should not change interval from date_histogram aggregations', () => {
const migratedDoc = migrate(doc);
const aggs = JSON.parse(migratedDoc.attributes.visState).aggs;
expect(aggs[1].params.interval).toBe(JSON.parse(doc.attributes.visState).aggs[1].params.interval);
});

it('should not remove customInterval from non date_histogram aggregations', () => {
const migratedDoc = migrate(doc);
const aggs = JSON.parse(migratedDoc.attributes.visState).aggs;
expect(aggs[0]).toHaveProperty('params.customInterval');
});

it('should set interval with customInterval value and remove customInterval when interval equals "custom"', () => {
const migratedDoc = migrate(doc);
const aggs = JSON.parse(migratedDoc.attributes.visState).aggs;
expect(aggs[2].params.interval).toBe(JSON.parse(doc.attributes.visState).aggs[2].params.customInterval);
expect(aggs[2]).not.toHaveProperty('params.customInterval');
});

it('should remove customInterval from nested aggregations', () => {
const migratedDoc = migrate(doc);
const aggs = JSON.parse(migratedDoc.attributes.visState).aggs;
expect(aggs[3]).not.toHaveProperty('params.customBucket.params.customInterval');
});

it('should remove customInterval from nested aggregations and set interval with customInterval value', () => {
const migratedDoc = migrate(doc);
const aggs = JSON.parse(migratedDoc.attributes.visState).aggs;
expect(aggs[3].params.customBucket.params.interval)
.toBe(JSON.parse(doc.attributes.visState).aggs[3].params.customBucket.params.customInterval);
expect(aggs[3]).not.toHaveProperty('params.customBucket.params.customInterval');
});

it('should not fail on date histograms without a customInterval', () => {
const migratedDoc = migrate(doc);
const aggs = JSON.parse(migratedDoc.attributes.visState).aggs;
expect(aggs[3]).not.toHaveProperty('params.customInterval');
});
});
});

describe('dashboard', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ describe('editor', function () {
beforeEach(ngMock.inject(function () {
field = _.sample(indexPattern.fields);
interval = _.sample(intervalOptions);
params = render({ field: field, interval: interval });
params = render({ field: field, interval: interval.val });
}));

it('renders the field editor', function () {
Expand All @@ -112,11 +112,11 @@ describe('editor', function () {
});

it('renders the interval editor', function () {
expect(agg.params.interval).to.be(interval);
expect(agg.params.interval).to.be(interval.val);

expect(params).to.have.property('interval');
expect(params.interval).to.have.property('$el');
expect(params.interval.modelValue()).to.be(interval);
expect($scope.agg.params.interval).to.be(interval.val);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,8 @@ describe('date_histogram params', function () {
expect(output.params).to.have.property('interval', '1d');
});

it('ignores invalid intervals', function () {
const output = writeInterval('foo');
expect(output.params).to.have.property('interval', '0ms');
it('throws error when interval is invalid', function () {
expect(() => writeInterval('foo')).to.throw('TypeError: "foo" is not a valid interval.');
});

it('automatically picks an interval', function () {
Expand Down
9 changes: 8 additions & 1 deletion src/legacy/ui/public/agg_types/agg_param.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,17 @@ import { AggConfig } from '../vis';
interface AggParam {
type: string;
name: string;
options?: AggParamOption[];
required?: boolean;
displayName?: string;
onChange?(agg: AggConfig): void;
shouldShow?(agg: AggConfig): boolean;
}

export { AggParam };
interface AggParamOption {
val: string;
display: string;
enabled?(agg: AggConfig): void;
}

export { AggParam, AggParamOption };
6 changes: 0 additions & 6 deletions src/legacy/ui/public/agg_types/buckets/_interval_options.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,5 @@ export const intervalOptions = [
defaultMessage: 'Yearly',
}),
val: 'y'
},
{
display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.customDisplayName', {
defaultMessage: 'Custom',
}),
val: 'custom'
}
];
Loading

0 comments on commit aba6d62

Please sign in to comment.