mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Lens][Discover] Visualize text based query (#141928)
* [Lens] Enable text-based languages-sql * Display data * Chart switcher and further fixes * Drag and drop fixes * Small fix * Multiple improvements * Errors implementation and save and exit * Some cleanup * Fix types failures * Revert change * Fix underlying data error * Fix jest test * Fixes app test * Rename datasource to textBased * display the dataview picker component but disabled * Fix functional test * Refactoring * Populate the dataview to theembeddable * Load sync * sync load of the new dtsource * Fix * Fix bug when the dtaview is not found * Refactoring * Add some unit tests * Add some unit tests * Add more unit tests * Add a functional test * Add all files * Update lens limit * Fixes bug * Bump lens size * Fix flakiness * Further fixes * Fix check * More fixes * Fix test * Wait for query to run * More changes * Fix * Fix the function input to fetch from variable * Initial implementation of visualizing fields * Text based languages Visualization in Lens * Fix due to bad conflict * Fix types * Revert from main * Fix jest test * Add unit tests * Adds a functional test * Visualize lens field * Add a unit test * Move switch datasource logic to loadInitial * Cleanup
This commit is contained in:
parent
b885d9381d
commit
802039ec34
12 changed files with 294 additions and 9 deletions
|
@ -194,4 +194,14 @@ describe('discover sidebar', function () {
|
|||
const createDataViewButton = findTestSubject(compWithPickerInViewerMode, 'dataview-create-new');
|
||||
expect(createDataViewButton.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should render the Visualize in Lens button in text based languages mode', () => {
|
||||
const compInViewerMode = mountWithIntl(
|
||||
<KibanaContextProvider services={mockDiscoverServices}>
|
||||
<DiscoverSidebar {...props} onAddFilter={undefined} />
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
const visualizeField = findTestSubject(compInViewerMode, 'textBased-visualize');
|
||||
expect(visualizeField.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
useResizeObserver,
|
||||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
import { isOfAggregateQueryType } from '@kbn/es-query';
|
||||
import useShallowCompareEffect from 'react-use/lib/useShallowCompareEffect';
|
||||
import { isEqual } from 'lodash';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
@ -39,6 +40,7 @@ import { DiscoverSidebarResponsiveProps } from './discover_sidebar_responsive';
|
|||
import { VIEW_MODE } from '../../../../components/view_mode_toggle';
|
||||
import { DISCOVER_TOUR_STEP_ANCHOR_IDS } from '../../../../components/discover_tour';
|
||||
import type { DataTableRecord } from '../../../../types';
|
||||
import { triggerVisualizeActionsTextBasedLanguages } from './lib/visualize_trigger_utils';
|
||||
|
||||
/**
|
||||
* Default number of available fields displayed and added on scroll
|
||||
|
@ -309,6 +311,12 @@ export function DiscoverSidebarComponent({
|
|||
|
||||
const filterChanged = useMemo(() => isEqual(fieldFilter, getDefaultFieldFilter()), [fieldFilter]);
|
||||
|
||||
const visualizeAggregateQuery = useCallback(() => {
|
||||
const aggregateQuery =
|
||||
state.query && isOfAggregateQueryType(state.query) ? state.query : undefined;
|
||||
triggerVisualizeActionsTextBasedLanguages(columns, selectedDataView, aggregateQuery);
|
||||
}, [columns, selectedDataView, state.query]);
|
||||
|
||||
if (!selectedDataView) {
|
||||
return null;
|
||||
}
|
||||
|
@ -532,6 +540,20 @@ export function DiscoverSidebarComponent({
|
|||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{isPlainRecord && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
iconType="lensApp"
|
||||
data-test-subj="textBased-visualize"
|
||||
onClick={visualizeAggregateQuery}
|
||||
size="s"
|
||||
>
|
||||
{i18n.translate('discover.textBasedLanguages.visualize.label', {
|
||||
defaultMessage: 'Visualize in Lens',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiPageSideBar>
|
||||
);
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
visualizeFieldTrigger,
|
||||
visualizeGeoFieldTrigger,
|
||||
} from '@kbn/ui-actions-plugin/public';
|
||||
import type { AggregateQuery } from '@kbn/es-query';
|
||||
import type { DataViewField, DataView } from '@kbn/data-views-plugin/public';
|
||||
import { KBN_FIELD_TYPES } from '@kbn/data-plugin/public';
|
||||
import { getUiActions } from '../../../../../kibana_services';
|
||||
|
@ -59,6 +60,22 @@ export function triggerVisualizeActions(
|
|||
getUiActions().getTrigger(trigger).exec(triggerOptions);
|
||||
}
|
||||
|
||||
export function triggerVisualizeActionsTextBasedLanguages(
|
||||
contextualFields: string[],
|
||||
dataView?: DataView,
|
||||
query?: AggregateQuery
|
||||
) {
|
||||
if (!dataView) return;
|
||||
const triggerOptions = {
|
||||
dataViewSpec: dataView.toSpec(false),
|
||||
fieldName: '',
|
||||
contextualFields,
|
||||
originatingApp: PLUGIN_ID,
|
||||
query,
|
||||
};
|
||||
getUiActions().getTrigger(VISUALIZE_FIELD_TRIGGER).exec(triggerOptions);
|
||||
}
|
||||
|
||||
export interface VisualizeInformation {
|
||||
field: DataViewField;
|
||||
href?: string;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { AggregateQuery } from '@kbn/es-query';
|
||||
import type { DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||
import { ActionInternal } from './actions/action_internal';
|
||||
import { TriggerInternal } from './triggers/trigger_internal';
|
||||
|
@ -19,6 +20,7 @@ export interface VisualizeFieldContext {
|
|||
dataViewSpec: DataViewSpec;
|
||||
contextualFields?: string[];
|
||||
originatingApp?: string;
|
||||
query?: AggregateQuery;
|
||||
}
|
||||
|
||||
export const ACTION_VISUALIZE_FIELD = 'ACTION_VISUALIZE_FIELD';
|
||||
|
|
|
@ -205,6 +205,12 @@ export function getVisualizeFieldSuggestions({
|
|||
context: visualizeTriggerFieldContext,
|
||||
});
|
||||
}
|
||||
// suggestions for visualizing textbased languages
|
||||
if (visualizeTriggerFieldContext && 'query' in visualizeTriggerFieldContext) {
|
||||
if (visualizeTriggerFieldContext.query) {
|
||||
return suggestions.find((s) => s.datasourceId === 'textBasedLanguages');
|
||||
}
|
||||
}
|
||||
|
||||
if (suggestions.length) {
|
||||
return suggestions.find((s) => s.visualizationId === activeVisualization?.id) || suggestions[0];
|
||||
|
|
|
@ -115,6 +115,11 @@ export function loadInitial(
|
|||
defaultIndexPatternId: lensServices.uiSettings.get('defaultIndex'),
|
||||
};
|
||||
|
||||
let activeDatasourceId: string | undefined;
|
||||
if (initialContext && 'query' in initialContext) {
|
||||
activeDatasourceId = 'textBasedLanguages';
|
||||
}
|
||||
|
||||
if (
|
||||
!initialInput ||
|
||||
(attributeService.inputIsRefType(initialInput) &&
|
||||
|
@ -141,6 +146,7 @@ export function loadInitial(
|
|||
...emptyState,
|
||||
dataViews: getInitialDataViewsObject(indexPatterns, indexPatternRefs),
|
||||
searchSessionId: data.search.session.getSessionId() || data.search.session.start(),
|
||||
...(activeDatasourceId && { activeDatasourceId }),
|
||||
datasourceStates: Object.entries(datasourceStates).reduce(
|
||||
(state, [datasourceId, datasourceState]) => ({
|
||||
...state,
|
||||
|
|
|
@ -12,6 +12,7 @@ import { TextBasedLanguagesPersistedState, TextBasedLanguagesPrivateState } from
|
|||
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { getTextBasedLanguagesDatasource } from './text_based_languages';
|
||||
import { generateId } from '../id_generator';
|
||||
import { DatasourcePublicAPI, Datasource } from '../types';
|
||||
|
||||
jest.mock('../id_generator');
|
||||
|
@ -272,6 +273,97 @@ describe('IndexPattern Data Source', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('#getDatasourceSuggestionsForVisualizeField', () => {
|
||||
(generateId as jest.Mock).mockReturnValue(`newid`);
|
||||
it('should create the correct layers', () => {
|
||||
const state = {
|
||||
layers: {},
|
||||
initialContext: {
|
||||
contextualFields: ['bytes', 'dest'],
|
||||
query: { sql: 'SELECT * FROM "foo"' },
|
||||
dataViewSpec: {
|
||||
title: 'foo',
|
||||
id: '1',
|
||||
name: 'Foo',
|
||||
},
|
||||
},
|
||||
} as unknown as TextBasedLanguagesPrivateState;
|
||||
const suggestions = textBasedLanguagesDatasource.getDatasourceSuggestionsForVisualizeField(
|
||||
state,
|
||||
'1',
|
||||
'',
|
||||
indexPatterns
|
||||
);
|
||||
expect(suggestions[0].state).toEqual({
|
||||
...state,
|
||||
layers: {
|
||||
newid: {
|
||||
allColumns: [
|
||||
{
|
||||
columnId: 'newid',
|
||||
fieldName: 'bytes',
|
||||
meta: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
{
|
||||
columnId: 'newid',
|
||||
fieldName: 'dest',
|
||||
meta: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
columnId: 'newid',
|
||||
fieldName: 'bytes',
|
||||
meta: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
{
|
||||
columnId: 'newid',
|
||||
fieldName: 'dest',
|
||||
meta: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
index: 'foo',
|
||||
query: {
|
||||
sql: 'SELECT * FROM "foo"',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(suggestions[0].table).toEqual({
|
||||
changeType: 'initial',
|
||||
columns: [
|
||||
{
|
||||
columnId: 'newid',
|
||||
operation: {
|
||||
dataType: 'number',
|
||||
isBucketed: false,
|
||||
label: 'bytes',
|
||||
},
|
||||
},
|
||||
{
|
||||
columnId: 'newid',
|
||||
operation: {
|
||||
dataType: 'string',
|
||||
isBucketed: true,
|
||||
label: 'dest',
|
||||
},
|
||||
},
|
||||
],
|
||||
isMultiRow: false,
|
||||
layerId: 'newid',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getErrorMessages', () => {
|
||||
it('should use the results of getErrorMessages directly when single layer', () => {
|
||||
const state = {
|
||||
|
|
|
@ -14,7 +14,7 @@ import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
|
|||
import type { AggregateQuery } from '@kbn/es-query';
|
||||
import type { SavedObjectReference } from '@kbn/core/public';
|
||||
import { EuiButtonEmpty, EuiFormRow } from '@elastic/eui';
|
||||
import type { ExpressionsStart } from '@kbn/expressions-plugin/public';
|
||||
import type { ExpressionsStart, DatatableColumnType } from '@kbn/expressions-plugin/public';
|
||||
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import {
|
||||
|
@ -36,7 +36,7 @@ import type {
|
|||
TextBasedLanguageField,
|
||||
} from './types';
|
||||
import { FieldSelect } from './field_select';
|
||||
import { Datasource } from '../types';
|
||||
import type { Datasource, IndexPatternMap } from '../types';
|
||||
import { LayerPanel } from './layerpanel';
|
||||
|
||||
function getLayerReferenceName(layerId: string) {
|
||||
|
@ -82,6 +82,77 @@ export function getTextBasedLanguagesDatasource({
|
|||
};
|
||||
});
|
||||
};
|
||||
const getSuggestionsForVisualizeField = (
|
||||
state: TextBasedLanguagesPrivateState,
|
||||
indexPatternId: string,
|
||||
fieldName: string,
|
||||
indexPatterns: IndexPatternMap
|
||||
) => {
|
||||
const context = state.initialContext;
|
||||
if (context && 'dataViewSpec' in context && context.dataViewSpec.title) {
|
||||
const newLayerId = generateId();
|
||||
const indexPattern = indexPatterns[indexPatternId];
|
||||
|
||||
const contextualFields = context.contextualFields;
|
||||
const newColumns = contextualFields?.map((c) => {
|
||||
let field = indexPattern?.getFieldByName(c);
|
||||
if (!field) {
|
||||
field = indexPattern?.fields.find((f) => f.name.includes(c));
|
||||
}
|
||||
const newId = generateId();
|
||||
const type = field?.type ?? 'number';
|
||||
return {
|
||||
columnId: newId,
|
||||
fieldName: c,
|
||||
meta: {
|
||||
type: type as DatatableColumnType,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const index = context.dataViewSpec.title;
|
||||
const query = context.query;
|
||||
const updatedState = {
|
||||
...state,
|
||||
layers: {
|
||||
...state.layers,
|
||||
[newLayerId]: {
|
||||
index,
|
||||
query,
|
||||
columns: newColumns ?? [],
|
||||
allColumns: newColumns ?? [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
state: {
|
||||
...updatedState,
|
||||
},
|
||||
table: {
|
||||
changeType: 'initial' as TableChangeType,
|
||||
isMultiRow: false,
|
||||
layerId: newLayerId,
|
||||
columns:
|
||||
newColumns?.map((f) => {
|
||||
return {
|
||||
columnId: f.columnId,
|
||||
operation: {
|
||||
dataType: f?.meta?.type as DataType,
|
||||
label: f.fieldName,
|
||||
isBucketed: Boolean(f?.meta?.type !== 'number'),
|
||||
},
|
||||
};
|
||||
}) ?? [],
|
||||
},
|
||||
keptLayerIds: [newLayerId],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
const TextBasedLanguagesDatasource: Datasource<
|
||||
TextBasedLanguagesPrivateState,
|
||||
TextBasedLanguagesPersistedState
|
||||
|
@ -137,6 +208,7 @@ export function getTextBasedLanguagesDatasource({
|
|||
...initState,
|
||||
fieldList: [],
|
||||
indexPatternRefs: refs,
|
||||
initialContext: context,
|
||||
};
|
||||
},
|
||||
onRefreshIndexPattern() {},
|
||||
|
@ -291,7 +363,11 @@ export function getTextBasedLanguagesDatasource({
|
|||
const columnExists = props.state.fieldList.some((f) => f.name === selectedField?.fieldName);
|
||||
|
||||
render(
|
||||
<EuiButtonEmpty color={columnExists ? 'primary' : 'danger'} onClick={() => {}}>
|
||||
<EuiButtonEmpty
|
||||
color={columnExists ? 'primary' : 'danger'}
|
||||
onClick={() => {}}
|
||||
data-test-subj="lns-dimensionTrigger-textBased"
|
||||
>
|
||||
{customLabel ??
|
||||
i18n.translate('xpack.lens.textBasedLanguages.missingField', {
|
||||
defaultMessage: 'Missing field',
|
||||
|
@ -564,7 +640,7 @@ export function getTextBasedLanguagesDatasource({
|
|||
});
|
||||
return [];
|
||||
},
|
||||
getDatasourceSuggestionsForVisualizeField: getSuggestionsForState,
|
||||
getDatasourceSuggestionsForVisualizeField: getSuggestionsForVisualizeField,
|
||||
getDatasourceSuggestionsFromCurrentState: getSuggestionsForState,
|
||||
getDatasourceSuggestionsForVisualizeCharts: getSuggestionsForState,
|
||||
isEqual: () => true,
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
import type { DatatableColumn } from '@kbn/expressions-plugin/public';
|
||||
import type { AggregateQuery } from '@kbn/es-query';
|
||||
import type { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public';
|
||||
import type { VisualizeEditorContext } from '../types';
|
||||
|
||||
export interface TextBasedLanguagesLayerColumn {
|
||||
columnId: string;
|
||||
|
@ -34,6 +36,7 @@ export interface TextBasedLanguagesPersistedState {
|
|||
export type TextBasedLanguagesPrivateState = TextBasedLanguagesPersistedState & {
|
||||
indexPatternRefs: IndexPatternRef[];
|
||||
fieldList: DatatableColumn[];
|
||||
initialContext?: VisualizeFieldContext | VisualizeEditorContext;
|
||||
};
|
||||
|
||||
export interface IndexPatternRef {
|
||||
|
|
|
@ -84,6 +84,18 @@ describe('Text based languages utils', () => {
|
|||
index: '',
|
||||
},
|
||||
},
|
||||
indexPatternRefs: [],
|
||||
fieldList: [],
|
||||
initialContext: {
|
||||
contextualFields: ['bytes', 'dest'],
|
||||
query: { sql: 'SELECT * FROM "foo"' },
|
||||
fieldName: '',
|
||||
dataViewSpec: {
|
||||
title: 'foo',
|
||||
id: '1',
|
||||
name: 'Foo',
|
||||
},
|
||||
},
|
||||
};
|
||||
const dataViewsMock = dataViewPluginMocks.createStartContract();
|
||||
const dataMock = dataPluginMock.createStartContract();
|
||||
|
@ -113,6 +125,16 @@ describe('Text based languages utils', () => {
|
|||
);
|
||||
|
||||
expect(updatedState).toStrictEqual({
|
||||
initialContext: {
|
||||
contextualFields: ['bytes', 'dest'],
|
||||
query: { sql: 'SELECT * FROM "foo"' },
|
||||
fieldName: '',
|
||||
dataViewSpec: {
|
||||
title: 'foo',
|
||||
id: '1',
|
||||
name: 'Foo',
|
||||
},
|
||||
},
|
||||
fieldList: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
|
|
|
@ -15,7 +15,7 @@ import { fetchDataFromAggregateQuery } from './fetch_data_from_aggregate_query';
|
|||
|
||||
import type {
|
||||
IndexPatternRef,
|
||||
TextBasedLanguagesPersistedState,
|
||||
TextBasedLanguagesPrivateState,
|
||||
TextBasedLanguagesLayerColumn,
|
||||
} from './types';
|
||||
|
||||
|
@ -36,7 +36,7 @@ export async function loadIndexPatternRefs(
|
|||
}
|
||||
|
||||
export async function getStateFromAggregateQuery(
|
||||
state: TextBasedLanguagesPersistedState,
|
||||
state: TextBasedLanguagesPrivateState,
|
||||
query: AggregateQuery,
|
||||
dataViews: DataViewsPublicPluginStart,
|
||||
data: DataPublicPluginStart,
|
||||
|
@ -45,13 +45,14 @@ export async function getStateFromAggregateQuery(
|
|||
const indexPatternRefs: IndexPatternRef[] = await loadIndexPatternRefs(dataViews);
|
||||
const errors: Error[] = [];
|
||||
const layerIds = Object.keys(state.layers);
|
||||
const context = state.initialContext;
|
||||
const newLayerId = layerIds.length > 0 ? layerIds[0] : generateId();
|
||||
// fetch the pattern from the query
|
||||
const indexPattern = getIndexPatternFromTextBasedQuery(query);
|
||||
// get the id of the dataview
|
||||
const index = indexPatternRefs.find((r) => r.title === indexPattern)?.id ?? '';
|
||||
let columnsFromQuery: DatatableColumn[] = [];
|
||||
let columns: TextBasedLanguagesLayerColumn[] = [];
|
||||
let allColumns: TextBasedLanguagesLayerColumn[] = [];
|
||||
let timeFieldName;
|
||||
try {
|
||||
const table = await fetchDataFromAggregateQuery(query, dataViews, data, expressions);
|
||||
|
@ -59,7 +60,8 @@ export async function getStateFromAggregateQuery(
|
|||
timeFieldName = dataView.timeFieldName;
|
||||
columnsFromQuery = table?.columns ?? [];
|
||||
const existingColumns = state.layers[newLayerId].allColumns;
|
||||
columns = [
|
||||
|
||||
allColumns = [
|
||||
...existingColumns,
|
||||
...columnsFromQuery.map((c) => ({ columnId: c.id, fieldName: c.id, meta: c.meta })),
|
||||
];
|
||||
|
@ -73,7 +75,7 @@ export async function getStateFromAggregateQuery(
|
|||
index,
|
||||
query,
|
||||
columns: state.layers[newLayerId].columns ?? [],
|
||||
allColumns: columns,
|
||||
allColumns,
|
||||
timeField: timeFieldName,
|
||||
errors,
|
||||
},
|
||||
|
@ -84,6 +86,7 @@ export async function getStateFromAggregateQuery(
|
|||
...tempState,
|
||||
fieldList: columnsFromQuery ?? [],
|
||||
indexPatternRefs,
|
||||
initialContext: context,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -26,12 +26,20 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
'spaceSelector',
|
||||
'header',
|
||||
]);
|
||||
const monacoEditor = getService('monacoEditor');
|
||||
|
||||
const defaultSettings = {
|
||||
'discover:enableSql': true,
|
||||
};
|
||||
|
||||
async function setDiscoverTimeRange() {
|
||||
await PageObjects.timePicker.setDefaultAbsoluteRange();
|
||||
}
|
||||
|
||||
describe('discover field visualize button', () => {
|
||||
before(async () => {
|
||||
await kibanaServer.uiSettings.replace(defaultSettings);
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional');
|
||||
await kibanaServer.importExport.load(
|
||||
|
@ -95,5 +103,23 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
expect(selectedPattern).to.eql('logst*');
|
||||
});
|
||||
});
|
||||
|
||||
it('should visualize correctly text based language queries', async () => {
|
||||
await PageObjects.discover.selectTextBaseLang('SQL');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await monacoEditor.setCodeEditorValue(
|
||||
'SELECT extension, AVG("bytes") as average FROM "logstash-*" GROUP BY extension'
|
||||
);
|
||||
await testSubjects.click('querySubmitButton');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
await testSubjects.click('textBased-visualize');
|
||||
|
||||
await retry.try(async () => {
|
||||
const dimensions = await testSubjects.findAll('lns-dimensionTrigger-textBased');
|
||||
expect(dimensions).to.have.length(2);
|
||||
expect(await dimensions[1].getVisibleText()).to.be('average');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue