Convert tests to jest in vis_type_timeseries/public & common folders (#55023)

* Convert tests to jest in vis_type_timeseries/public & common folders

* Remove unused translation
This commit is contained in:
Daniil Suleiman 2020-01-17 17:04:00 +03:00 committed by GitHub
parent d740ec34b3
commit b9161e7682
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 174 additions and 209 deletions

View file

@ -1,61 +0,0 @@
/*
* 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 { expect } from 'chai';
import { createOptions, isBasicAgg } from '../agg_lookup';
describe('aggLookup', () => {
describe('isBasicAgg(metric)', () => {
it('returns true for a basic metric (count)', () => {
expect(isBasicAgg({ type: 'count' })).to.equal(true);
});
it('returns false for a pipeline metric (derivative)', () => {
expect(isBasicAgg({ type: 'derivative' })).to.equal(false);
});
});
describe('createOptions(type, siblings)', () => {
it('returns options for all aggs', () => {
const options = createOptions();
expect(options).to.have.length(30);
options.forEach(option => {
expect(option).to.have.property('label');
expect(option).to.have.property('value');
expect(option).to.have.property('disabled');
});
});
it('returns options for basic', () => {
const options = createOptions('basic');
expect(options).to.have.length(15);
expect(options.every(opt => isBasicAgg({ type: opt.value }))).to.equal(true);
});
it('returns options for pipeline', () => {
const options = createOptions('pipeline');
expect(options).to.have.length(15);
expect(options.every(opt => !isBasicAgg({ type: opt.value }))).to.equal(true);
});
it('returns options for all if given unknown key', () => {
const options = createOptions('foo');
expect(options).to.have.length(30);
});
});
});

View file

@ -127,25 +127,3 @@ const byType = {
export function isBasicAgg(item) {
return _.includes(Object.keys(byType.basic), item.type);
}
export function createOptions(type = '_all', siblings = []) {
let aggs = byType[type];
if (!aggs) aggs = byType._all;
let enablePipelines = siblings.some(isBasicAgg);
if (siblings.length <= 1) enablePipelines = false;
return _(aggs)
.map((label, value) => {
const disabled = _.includes(pipeline, value) ? !enablePipelines : false;
return {
label: disabled
? i18n.translate('visTypeTimeseries.aggLookup.addPipelineAggDescription', {
defaultMessage: '{label} (use the "+" button to add this pipeline agg)',
values: { label },
})
: label,
value,
disabled,
};
})
.value();
}

View file

@ -0,0 +1,31 @@
/*
* 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 { isBasicAgg } from './agg_lookup';
describe('aggLookup', () => {
describe('isBasicAgg(metric)', () => {
test('returns true for a basic metric (count)', () => {
expect(isBasicAgg({ type: 'count' })).toEqual(true);
});
test('returns false for a pipeline metric (derivative)', () => {
expect(isBasicAgg({ type: 'derivative' })).toEqual(false);
});
});
});

View file

@ -17,51 +17,50 @@
* under the License.
*/
import { expect } from 'chai';
import { calculateLabel } from '../calculate_label';
import { calculateLabel } from './calculate_label';
describe('calculateLabel(metric, metrics)', () => {
it('returns "Unknown" for empty metric', () => {
expect(calculateLabel()).to.equal('Unknown');
test('returns "Unknown" for empty metric', () => {
expect(calculateLabel()).toEqual('Unknown');
});
it('returns the metric.alias if set', () => {
expect(calculateLabel({ alias: 'Example' })).to.equal('Example');
test('returns the metric.alias if set', () => {
expect(calculateLabel({ alias: 'Example' })).toEqual('Example');
});
it('returns "Count" for a count metric', () => {
expect(calculateLabel({ type: 'count' })).to.equal('Count');
test('returns "Count" for a count metric', () => {
expect(calculateLabel({ type: 'count' })).toEqual('Count');
});
it('returns "Calculation" for a bucket script metric', () => {
expect(calculateLabel({ type: 'calculation' })).to.equal('Bucket Script');
test('returns "Calculation" for a bucket script metric', () => {
expect(calculateLabel({ type: 'calculation' })).toEqual('Bucket Script');
});
it('returns formated label for series_agg', () => {
test('returns formated label for series_agg', () => {
const label = calculateLabel({ type: 'series_agg', function: 'max' });
expect(label).to.equal('Series Agg (max)');
expect(label).toEqual('Series Agg (max)');
});
it('returns formated label for basic aggs', () => {
test('returns formated label for basic aggs', () => {
const label = calculateLabel({ type: 'avg', field: 'memory' });
expect(label).to.equal('Average of memory');
expect(label).toEqual('Average of memory');
});
it('returns formated label for pipeline aggs', () => {
test('returns formated label for pipeline aggs', () => {
const metric = { id: 2, type: 'derivative', field: 1 };
const metrics = [{ id: 1, type: 'max', field: 'network.out.bytes' }, metric];
const label = calculateLabel(metric, metrics);
expect(label).to.equal('Derivative of Max of network.out.bytes');
expect(label).toEqual('Derivative of Max of network.out.bytes');
});
it('returns formated label for derivative of percentile', () => {
test('returns formated label for derivative of percentile', () => {
const metric = { id: 2, type: 'derivative', field: '1[50.0]' };
const metrics = [{ id: 1, type: 'percentile', field: 'network.out.bytes' }, metric];
const label = calculateLabel(metric, metrics);
expect(label).to.equal('Derivative of Percentile of network.out.bytes (50.0)');
expect(label).toEqual('Derivative of Percentile of network.out.bytes (50.0)');
});
it('returns formated label for pipeline aggs (deep)', () => {
test('returns formated label for pipeline aggs (deep)', () => {
const metric = { id: 3, type: 'derivative', field: 2 };
const metrics = [
{ id: 1, type: 'max', field: 'network.out.bytes' },
@ -69,16 +68,16 @@ describe('calculateLabel(metric, metrics)', () => {
metric,
];
const label = calculateLabel(metric, metrics);
expect(label).to.equal('Derivative of Moving Average of Max of network.out.bytes');
expect(label).toEqual('Derivative of Moving Average of Max of network.out.bytes');
});
it('returns formated label for pipeline aggs uses alias for field metric', () => {
test('returns formated label for pipeline aggs uses alias for field metric', () => {
const metric = { id: 2, type: 'derivative', field: 1 };
const metrics = [
{ id: 1, type: 'max', field: 'network.out.bytes', alias: 'Outbound Traffic' },
metric,
];
const label = calculateLabel(metric, metrics);
expect(label).to.equal('Derivative of Outbound Traffic');
expect(label).toEqual('Derivative of Outbound Traffic');
});
});

View file

@ -17,11 +17,10 @@
* under the License.
*/
import { calculateSiblings } from '../calculate_siblings';
import { expect } from 'chai';
import { calculateSiblings } from './calculate_siblings';
describe('calculateSiblings(metrics, metric)', () => {
it('should return all siblings', () => {
test('should return all siblings', () => {
const metrics = [
{ id: 1, type: 'max', field: 'network.bytes' },
{ id: 2, type: 'derivative', field: 1 },
@ -30,7 +29,7 @@ describe('calculateSiblings(metrics, metric)', () => {
{ id: 5, type: 'count' },
];
const siblings = calculateSiblings(metrics, { id: 2 });
expect(siblings).to.eql([
expect(siblings).toEqual([
{ id: 1, type: 'max', field: 'network.bytes' },
{ id: 5, type: 'count' },
]);

View file

@ -17,37 +17,35 @@
* under the License.
*/
import sinon from 'sinon';
import { expect } from 'chai';
import { handleChange, handleAdd, handleDelete } from '../collection_actions';
import { handleChange, handleAdd, handleDelete } from './collection_actions';
describe('collection actions', () => {
it('handleChange() calls props.onChange() with updated collection', () => {
const fn = sinon.spy();
test('handleChange() calls props.onChange() with updated collection', () => {
const fn = jest.fn();
const props = {
model: { test: [{ id: 1, title: 'foo' }] },
name: 'test',
onChange: fn,
};
handleChange.call(null, props, { id: 1, title: 'bar' });
expect(fn.calledOnce).to.equal(true);
expect(fn.firstCall.args[0]).to.eql({
expect(fn.mock.calls.length).toEqual(1);
expect(fn.mock.calls[0][0]).toEqual({
test: [{ id: 1, title: 'bar' }],
});
});
it('handleAdd() calls props.onChange() with update collection', () => {
const newItemFn = sinon.stub().returns({ id: 2, title: 'example' });
const fn = sinon.spy();
test('handleAdd() calls props.onChange() with update collection', () => {
const newItemFn = jest.fn(() => ({ id: 2, title: 'example' }));
const fn = jest.fn();
const props = {
model: { test: [{ id: 1, title: 'foo' }] },
name: 'test',
onChange: fn,
};
handleAdd.call(null, props, newItemFn);
expect(fn.calledOnce).to.equal(true);
expect(newItemFn.calledOnce).to.equal(true);
expect(fn.firstCall.args[0]).to.eql({
expect(fn.mock.calls.length).toEqual(1);
expect(newItemFn.mock.calls.length).toEqual(1);
expect(fn.mock.calls[0][0]).toEqual({
test: [
{ id: 1, title: 'foo' },
{ id: 2, title: 'example' },
@ -55,16 +53,16 @@ describe('collection actions', () => {
});
});
it('handleDelete() calls props.onChange() with update collection', () => {
const fn = sinon.spy();
test('handleDelete() calls props.onChange() with update collection', () => {
const fn = jest.fn();
const props = {
model: { test: [{ id: 1, title: 'foo' }] },
name: 'test',
onChange: fn,
};
handleDelete.call(null, props, { id: 1 });
expect(fn.calledOnce).to.equal(true);
expect(fn.firstCall.args[0]).to.eql({
expect(fn.mock.calls.length).toEqual(1);
expect(fn.mock.calls[0][0]).toEqual({
test: [],
});
});

View file

@ -18,5 +18,5 @@
*/
describe('convertSeriesToVars(series, model)', () => {
it('returns and object', () => {});
test('returns and object', () => {});
});

View file

@ -17,9 +17,7 @@
* under the License.
*/
import sinon from 'sinon';
import { expect } from 'chai';
import { createNumberHandler } from '../create_number_handler';
import { createNumberHandler } from './create_number_handler';
describe('createNumberHandler()', () => {
let handleChange;
@ -27,17 +25,17 @@ describe('createNumberHandler()', () => {
let event;
beforeEach(() => {
handleChange = sinon.spy();
handleChange = jest.fn();
changeHandler = createNumberHandler(handleChange);
event = { preventDefault: sinon.spy(), target: { value: '1' } };
event = { preventDefault: jest.fn(), target: { value: '1' } };
const fn = changeHandler('test');
fn(event);
});
it('calls handleChange() function with partial', () => {
expect(event.preventDefault.calledOnce).to.equal(true);
expect(handleChange.calledOnce).to.equal(true);
expect(handleChange.firstCall.args[0]).to.eql({
test('calls handleChange() function with partial', () => {
expect(event.preventDefault.mock.calls.length).toEqual(1);
expect(handleChange.mock.calls.length).toEqual(1);
expect(handleChange.mock.calls[0][0]).toEqual({
test: 1,
});
});

View file

@ -17,24 +17,22 @@
* under the License.
*/
import sinon from 'sinon';
import { expect } from 'chai';
import { createSelectHandler } from '../create_select_handler';
import { createSelectHandler } from './create_select_handler';
describe('createSelectHandler()', () => {
let handleChange;
let changeHandler;
beforeEach(() => {
handleChange = sinon.spy();
handleChange = jest.fn();
changeHandler = createSelectHandler(handleChange);
const fn = changeHandler('test');
fn([{ value: 'foo' }]);
});
it('calls handleChange() function with partial', () => {
expect(handleChange.calledOnce).to.equal(true);
expect(handleChange.firstCall.args[0]).to.eql({
test('calls handleChange() function with partial', () => {
expect(handleChange.mock.calls.length).toEqual(1);
expect(handleChange.mock.calls[0][0]).toEqual({
test: 'foo',
});
});

View file

@ -17,9 +17,7 @@
* under the License.
*/
import sinon from 'sinon';
import { expect } from 'chai';
import { createTextHandler } from '../create_text_handler';
import { createTextHandler } from './create_text_handler';
describe('createTextHandler()', () => {
let handleChange;
@ -27,17 +25,17 @@ describe('createTextHandler()', () => {
let event;
beforeEach(() => {
handleChange = sinon.spy();
handleChange = jest.fn();
changeHandler = createTextHandler(handleChange);
event = { preventDefault: sinon.spy(), target: { value: 'foo' } };
event = { preventDefault: jest.fn(), target: { value: 'foo' } };
const fn = changeHandler('test');
fn(event);
});
it('calls handleChange() function with partial', () => {
expect(event.preventDefault.calledOnce).to.equal(true);
expect(handleChange.calledOnce).to.equal(true);
expect(handleChange.firstCall.args[0]).to.eql({
test('calls handleChange() function with partial', () => {
expect(event.preventDefault.mock.calls.length).toEqual(1);
expect(handleChange.mock.calls.length).toEqual(1);
expect(handleChange.mock.calls[0][0]).toEqual({
test: 'foo',
});
});

View file

@ -17,17 +17,18 @@
* under the License.
*/
import { expect } from 'chai';
import { getAxisLabelString } from '../get_axis_label_string';
import { getAxisLabelString } from './get_axis_label_string';
jest.mock('ui/new_platform');
describe('getAxisLabelString(interval)', () => {
it('should return a valid label for 10 seconds', () => {
expect(getAxisLabelString(10000)).to.equal('per 10 seconds');
test('should return a valid label for 10 seconds', () => {
expect(getAxisLabelString(10000)).toEqual('per 10 seconds');
});
it('should return a valid label for 2 minutes', () => {
expect(getAxisLabelString(120000)).to.equal('per 2 minutes');
test('should return a valid label for 2 minutes', () => {
expect(getAxisLabelString(120000)).toEqual('per 2 minutes');
});
it('should return a valid label for 2 hour', () => {
expect(getAxisLabelString(7200000)).to.equal('per 2 hours');
test('should return a valid label for 2 hour', () => {
expect(getAxisLabelString(7200000)).toEqual('per 2 hours');
});
});

View file

@ -18,24 +18,24 @@
*/
import uuid from 'uuid';
import { expect } from 'chai';
import { reIdSeries } from '../re_id_series';
import { reIdSeries } from './re_id_series';
describe('reIdSeries()', () => {
it('reassign ids for series with just basic metrics', () => {
test('reassign ids for series with just basic metrics', () => {
const series = {
id: uuid.v1(),
metrics: [{ id: uuid.v1() }, { id: uuid.v1() }],
};
const newSeries = reIdSeries(series);
expect(newSeries).to.not.equal(series);
expect(newSeries.id).to.not.equal(series.id);
expect(newSeries).not.toEqual(series);
expect(newSeries.id).not.toEqual(series.id);
newSeries.metrics.forEach((val, key) => {
expect(val.id).to.not.equal(series.metrics[key].id);
expect(val.id).not.toEqual(series.metrics[key].id);
});
});
it('reassign ids for series with just basic metrics and group by', () => {
test('reassign ids for series with just basic metrics and group by', () => {
const firstMetricId = uuid.v1();
const series = {
id: uuid.v1(),
@ -43,27 +43,27 @@ describe('reIdSeries()', () => {
terms_order_by: firstMetricId,
};
const newSeries = reIdSeries(series);
expect(newSeries).to.not.equal(series);
expect(newSeries.id).to.not.equal(series.id);
expect(newSeries).not.toEqual(series);
expect(newSeries.id).not.toEqual(series.id);
newSeries.metrics.forEach((val, key) => {
expect(val.id).to.not.equal(series.metrics[key].id);
expect(val.id).not.toEqual(series.metrics[key].id);
});
expect(newSeries.terms_order_by).to.equal(newSeries.metrics[0].id);
expect(newSeries.terms_order_by).toEqual(newSeries.metrics[0].id);
});
it('reassign ids for series with pipeline metrics', () => {
test('reassign ids for series with pipeline metrics', () => {
const firstMetricId = uuid.v1();
const series = {
id: uuid.v1(),
metrics: [{ id: firstMetricId }, { id: uuid.v1(), field: firstMetricId }],
};
const newSeries = reIdSeries(series);
expect(newSeries).to.not.equal(series);
expect(newSeries.id).to.not.equal(series.id);
expect(newSeries.metrics[0].id).to.equal(newSeries.metrics[1].field);
expect(newSeries).not.toEqual(series);
expect(newSeries.id).not.toEqual(series.id);
expect(newSeries.metrics[0].id).toEqual(newSeries.metrics[1].field);
});
it('reassign ids for series with calculation vars', () => {
test('reassign ids for series with calculation vars', () => {
const firstMetricId = uuid.v1();
const series = {
id: uuid.v1(),
@ -77,8 +77,8 @@ describe('reIdSeries()', () => {
],
};
const newSeries = reIdSeries(series);
expect(newSeries).to.not.equal(series);
expect(newSeries.id).to.not.equal(series.id);
expect(newSeries.metrics[1].variables[0].field).to.equal(newSeries.metrics[0].id);
expect(newSeries).not.toEqual(series);
expect(newSeries.id).not.toEqual(series.id);
expect(newSeries.metrics[1].variables[0].field).toEqual(newSeries.metrics[0].id);
});
});

View file

@ -17,26 +17,25 @@
* under the License.
*/
import { expect } from 'chai';
import { replaceVars } from '../replace_vars';
import { replaceVars } from './replace_vars';
describe('replaceVars(str, args, vars)', () => {
it('replaces vars with values', () => {
test('replaces vars with values', () => {
const vars = { total: 100 };
const args = { host: 'test-01' };
const template = '# {{args.host}} {{total}}';
expect(replaceVars(template, args, vars)).to.equal('# test-01 100');
expect(replaceVars(template, args, vars)).toEqual('# test-01 100');
});
it('replaces args override vars', () => {
test('replaces args override vars', () => {
const vars = { total: 100, args: { test: 'foo-01' } };
const args = { test: 'bar-01' };
const template = '# {{args.test}} {{total}}';
expect(replaceVars(template, args, vars)).to.equal('# bar-01 100');
expect(replaceVars(template, args, vars)).toEqual('# bar-01 100');
});
it('returns original string if error', () => {
test('returns original string if error', () => {
const vars = { total: 100 };
const args = { host: 'test-01' };
const template = '# {{args.host}} {{total';
expect(replaceVars(template, args, vars)).to.equal('# {{args.host}} {{total');
expect(replaceVars(template, args, vars)).toEqual('# {{args.host}} {{total');
});
});

View file

@ -17,64 +17,93 @@
* under the License.
*/
import { expect } from 'chai';
import { createTickFormatter } from '../tick_formatter';
import { npStart } from 'ui/new_platform';
import { createTickFormatter } from './tick_formatter';
import { getFieldFormatsRegistry } from '../../../../../../test_utils/public/stub_field_formats';
const mockUiSettings = {
get: item => {
return mockUiSettings[item];
},
getUpdate$: () => ({
subscribe: jest.fn(),
}),
'query:allowLeadingWildcards': true,
'query:queryString:options': {},
'courier:ignoreFilterIfFieldNotInIndex': true,
'dateFormat:tz': 'Browser',
'format:defaultTypeMap': {},
};
const mockCore = {
chrome: {},
uiSettings: mockUiSettings,
http: {
basePath: {
get: jest.fn(),
},
},
};
describe('createTickFormatter(format, template)', () => {
it('returns a number with two decimal place by default', () => {
npStart.plugins.data = {
fieldFormats: getFieldFormatsRegistry(mockCore),
};
test('returns a number with two decimal place by default', () => {
const fn = createTickFormatter();
expect(fn(1.5556)).to.equal('1.56');
expect(fn(1.5556)).toEqual('1.56');
});
it('returns a percent with percent formatter', () => {
test('returns a percent with percent formatter', () => {
const config = {
'format:percent:defaultPattern': '0.[00]%',
};
const fn = createTickFormatter('percent', null, key => config[key]);
expect(fn(0.5556)).to.equal('55.56%');
expect(fn(0.5556)).toEqual('55.56%');
});
it('returns a byte formatted string with byte formatter', () => {
test('returns a byte formatted string with byte formatter', () => {
const config = {
'format:bytes:defaultPattern': '0.0b',
};
const fn = createTickFormatter('bytes', null, key => config[key]);
expect(fn(1500 ^ 10)).to.equal('1.5KB');
expect(fn(1500 ^ 10)).toEqual('1.5KB');
});
it('returns a custom formatted string with custom formatter', () => {
test('returns a custom formatted string with custom formatter', () => {
const fn = createTickFormatter('0.0a');
expect(fn(1500)).to.equal('1.5k');
expect(fn(1500)).toEqual('1.5k');
});
it('returns a located string with custom locale setting', () => {
test('returns a located string with custom locale setting', () => {
const config = {
'format:number:defaultLocale': 'fr',
};
const fn = createTickFormatter('0,0.0', null, key => config[key]);
expect(fn(1500)).to.equal('1 500,0');
expect(fn(1500)).toEqual('1 500,0');
});
it('returns a custom formatted string with custom formatter and template', () => {
test('returns a custom formatted string with custom formatter and template', () => {
const fn = createTickFormatter('0.0a', '{{value}}/s');
expect(fn(1500)).to.equal('1.5k/s');
expect(fn(1500)).toEqual('1.5k/s');
});
it('returns "foo" if passed a string', () => {
test('returns "foo" if passed a string', () => {
const fn = createTickFormatter();
expect(fn('foo')).to.equal('foo');
expect(fn('foo')).toEqual('foo');
});
it('returns value if passed a bad formatter', () => {
test('returns value if passed a bad formatter', () => {
const fn = createTickFormatter('102');
expect(fn(100)).to.equal('100');
expect(fn(100)).toEqual('100');
});
it('returns formatted value if passed a bad template', () => {
test('returns formatted value if passed a bad template', () => {
const config = {
'format:number:defaultPattern': '0,0.[00]',
};
const fn = createTickFormatter('number', '{{value', key => config[key]);
expect(fn(1.5556)).to.equal('1.56');
expect(fn(1.5556)).toEqual('1.56');
});
});

View file

@ -3213,7 +3213,6 @@
"visTypeTimeseries.addDeleteButtons.deleteButtonDefaultTooltip": "削除",
"visTypeTimeseries.addDeleteButtons.reEnableTooltip": "再度有効にする",
"visTypeTimeseries.addDeleteButtons.temporarilyDisableTooltip": "一時的に無効にする",
"visTypeTimeseries.aggLookup.addPipelineAggDescription": "{label} (「+」ボタンでこのパイプライン集約を追加します)",
"visTypeTimeseries.aggLookup.averageLabel": "平均",
"visTypeTimeseries.aggLookup.calculationLabel": "計算",
"visTypeTimeseries.aggLookup.cardinalityLabel": "基数",

View file

@ -3213,7 +3213,6 @@
"visTypeTimeseries.addDeleteButtons.deleteButtonDefaultTooltip": "删除",
"visTypeTimeseries.addDeleteButtons.reEnableTooltip": "重新启用",
"visTypeTimeseries.addDeleteButtons.temporarilyDisableTooltip": "暂时禁用",
"visTypeTimeseries.aggLookup.addPipelineAggDescription": "{label}(使用“+”按钮添加此管道聚合)",
"visTypeTimeseries.aggLookup.averageLabel": "平均值",
"visTypeTimeseries.aggLookup.calculationLabel": "计算",
"visTypeTimeseries.aggLookup.cardinalityLabel": "基数",