[8.8] [SecuritySolution] Allow query when "Group By Top" field is empty (#156968) (#157051)

# Backport

This will backport the following commits from `main` to `8.8`:
- [[SecuritySolution] Allow query when "Group By Top" field is empty
(#156968)](https://github.com/elastic/kibana/pull/156968)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Angela
Chuang","email":"6295984+angorayc@users.noreply.github.com"},"sourceCommit":{"committedDate":"2023-05-08T19:16:24Z","message":"[SecuritySolution]
Allow query when \"Group By Top\" field is empty (#156968)\n\n##
Summary\r\n\r\nFixes
236834448-74dd8a03-3d0a-4171-aa30-513244999491.mov\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"8b45e76d8fa526b63de6de1529cfc593ecf8723a","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","Team:Threat
Hunting","Team: SecuritySolution","Team:Threat
Hunting:Explore","v8.8.0","Feature:Lens
Charts","v8.9.0"],"number":156968,"url":"https://github.com/elastic/kibana/pull/156968","mergeCommit":{"message":"[SecuritySolution]
Allow query when \"Group By Top\" field is empty (#156968)\n\n##
Summary\r\n\r\nFixes
236834448-74dd8a03-3d0a-4171-aa30-513244999491.mov\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"8b45e76d8fa526b63de6de1529cfc593ecf8723a"}},"sourceBranch":"main","suggestedTargetBranches":["8.8"],"targetPullRequestStates":[{"branch":"8.8","label":"v8.8.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/156968","number":156968,"mergeCommit":{"message":"[SecuritySolution]
Allow query when \"Group By Top\" field is empty (#156968)\n\n##
Summary\r\n\r\nFixes
236834448-74dd8a03-3d0a-4171-aa30-513244999491.mov\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"8b45e76d8fa526b63de6de1529cfc593ecf8723a"}}]}]
BACKPORT-->

Co-authored-by: Angela Chuang <6295984+angorayc@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2023-05-08 17:11:09 -04:00 committed by GitHub
parent 17b34f6aad
commit acc1e2ca63
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 268 additions and 231 deletions

View file

@ -6,7 +6,7 @@ Object {
"references": Array [
Object {
"id": "security-solution-my-test",
"name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b",
"name": "indexpattern-datasource-layer-mockLayerId",
"type": "index-pattern",
},
],
@ -15,39 +15,25 @@ Object {
"datasourceStates": Object {
"formBased": Object {
"layers": Object {
"4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object {
"mockLayerId": Object {
"columnOrder": Array [
"2881fedd-54b7-42ba-8c97-5175dec86166",
"75ce269b-ee9c-4c7d-a14e-9226ba0fe059",
"f04a71a3-399f-4d32-9efc-8a005e989991",
"mockTopValuesOfStackByFieldColumnId",
"mockTopValuesOfBreakdownFieldColumnId",
"mockCountColumnId",
],
"columns": Object {
"2881fedd-54b7-42ba-8c97-5175dec86166": Object {
"dataType": "string",
"isBucketed": true,
"label": "Top values of event.category",
"operationType": "terms",
"mockCountColumnId": Object {
"dataType": "number",
"isBucketed": false,
"label": "Count of agent.type",
"operationType": "count",
"params": Object {
"exclude": Array [],
"excludeIsRegex": false,
"include": Array [],
"includeIsRegex": false,
"missingBucket": false,
"orderBy": Object {
"columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
"type": "column",
},
"orderDirection": "desc",
"otherBucket": true,
"parentFormat": Object {
"id": "terms",
},
"size": 1000,
"emptyAsNull": true,
},
"scale": "ordinal",
"sourceField": "event.category",
"scale": "ratio",
"sourceField": "agent.type",
},
"75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object {
"mockTopValuesOfBreakdownFieldColumnId": Object {
"dataType": "string",
"isBucketed": true,
"label": "Top values of agent.type",
@ -59,7 +45,7 @@ Object {
"includeIsRegex": false,
"missingBucket": false,
"orderBy": Object {
"columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
"columnId": "mockCountColumnId",
"type": "column",
},
"orderDirection": "desc",
@ -72,16 +58,30 @@ Object {
"scale": "ordinal",
"sourceField": "agent.type",
},
"f04a71a3-399f-4d32-9efc-8a005e989991": Object {
"dataType": "number",
"isBucketed": false,
"label": "Count of agent.type",
"operationType": "count",
"mockTopValuesOfStackByFieldColumnId": Object {
"dataType": "string",
"isBucketed": true,
"label": "Top values of event.category",
"operationType": "terms",
"params": Object {
"emptyAsNull": true,
"exclude": Array [],
"excludeIsRegex": false,
"include": Array [],
"includeIsRegex": false,
"missingBucket": false,
"orderBy": Object {
"columnId": "mockCountColumnId",
"type": "column",
},
"orderDirection": "desc",
"otherBucket": true,
"parentFormat": Object {
"id": "terms",
},
"size": 1000,
},
"scale": "ratio",
"sourceField": "agent.type",
"scale": "ordinal",
"sourceField": "event.category",
},
},
"incompleteColumns": Object {},
@ -144,20 +144,20 @@ Object {
"visualization": Object {
"columns": Array [
Object {
"columnId": "2881fedd-54b7-42ba-8c97-5175dec86166",
"columnId": "mockTopValuesOfStackByFieldColumnId",
"isTransposed": false,
"width": 362,
},
Object {
"columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
"columnId": "mockCountColumnId",
"isTransposed": false,
},
Object {
"columnId": "75ce269b-ee9c-4c7d-a14e-9226ba0fe059",
"columnId": "mockTopValuesOfBreakdownFieldColumnId",
"isTransposed": false,
},
],
"layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b",
"layerId": "mockLayerId",
"layerType": "data",
},
},
@ -172,7 +172,7 @@ Object {
"references": Array [
Object {
"id": "security-solution-my-test",
"name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b",
"name": "indexpattern-datasource-layer-mockLayerId",
"type": "index-pattern",
},
],
@ -181,14 +181,24 @@ Object {
"datasourceStates": Object {
"formBased": Object {
"layers": Object {
"4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object {
"mockLayerId": Object {
"columnOrder": Array [
"2881fedd-54b7-42ba-8c97-5175dec86166",
"75ce269b-ee9c-4c7d-a14e-9226ba0fe059",
"f04a71a3-399f-4d32-9efc-8a005e989991",
"mockTopValuesOfStackByFieldColumnId",
"mockCountColumnId",
],
"columns": Object {
"2881fedd-54b7-42ba-8c97-5175dec86166": Object {
"mockCountColumnId": Object {
"dataType": "number",
"isBucketed": false,
"label": "Count of event.category",
"operationType": "count",
"params": Object {
"emptyAsNull": true,
},
"scale": "ratio",
"sourceField": "event.category",
},
"mockTopValuesOfStackByFieldColumnId": Object {
"dataType": "string",
"isBucketed": true,
"label": "Top values of event.category",
@ -200,7 +210,7 @@ Object {
"includeIsRegex": false,
"missingBucket": false,
"orderBy": Object {
"columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
"columnId": "mockCountColumnId",
"type": "column",
},
"orderDirection": "desc",
@ -213,42 +223,6 @@ Object {
"scale": "ordinal",
"sourceField": "event.category",
},
"75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object {
"dataType": "string",
"isBucketed": true,
"label": "Top values of undefined",
"operationType": "terms",
"params": Object {
"exclude": Array [],
"excludeIsRegex": false,
"include": Array [],
"includeIsRegex": false,
"missingBucket": false,
"orderBy": Object {
"columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
"type": "column",
},
"orderDirection": "desc",
"otherBucket": true,
"parentFormat": Object {
"id": "terms",
},
"size": 1000,
},
"scale": "ordinal",
"sourceField": undefined,
},
"f04a71a3-399f-4d32-9efc-8a005e989991": Object {
"dataType": "number",
"isBucketed": false,
"label": "Count of undefined",
"operationType": "count",
"params": Object {
"emptyAsNull": true,
},
"scale": "ratio",
"sourceField": undefined,
},
},
"incompleteColumns": Object {},
"sampling": 1,
@ -334,20 +308,16 @@ Object {
"visualization": Object {
"columns": Array [
Object {
"columnId": "2881fedd-54b7-42ba-8c97-5175dec86166",
"columnId": "mockTopValuesOfStackByFieldColumnId",
"isTransposed": false,
"width": 362,
},
Object {
"columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
"isTransposed": false,
},
Object {
"columnId": "75ce269b-ee9c-4c7d-a14e-9226ba0fe059",
"columnId": "mockCountColumnId",
"isTransposed": false,
},
],
"layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b",
"layerId": "mockLayerId",
"layerType": "data",
},
},
@ -362,7 +332,7 @@ Object {
"references": Array [
Object {
"id": "security-solution-my-test",
"name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b",
"name": "indexpattern-datasource-layer-mockLayerId",
"type": "index-pattern",
},
],
@ -371,14 +341,24 @@ Object {
"datasourceStates": Object {
"formBased": Object {
"layers": Object {
"4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object {
"mockLayerId": Object {
"columnOrder": Array [
"2881fedd-54b7-42ba-8c97-5175dec86166",
"75ce269b-ee9c-4c7d-a14e-9226ba0fe059",
"f04a71a3-399f-4d32-9efc-8a005e989991",
"mockTopValuesOfStackByFieldColumnId",
"mockCountColumnId",
],
"columns": Object {
"2881fedd-54b7-42ba-8c97-5175dec86166": Object {
"mockCountColumnId": Object {
"dataType": "number",
"isBucketed": false,
"label": "Count of event.category",
"operationType": "count",
"params": Object {
"emptyAsNull": true,
},
"scale": "ratio",
"sourceField": "event.category",
},
"mockTopValuesOfStackByFieldColumnId": Object {
"dataType": "string",
"isBucketed": true,
"label": "Top values of event.category",
@ -390,7 +370,7 @@ Object {
"includeIsRegex": false,
"missingBucket": false,
"orderBy": Object {
"columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
"columnId": "mockCountColumnId",
"type": "column",
},
"orderDirection": "desc",
@ -403,42 +383,6 @@ Object {
"scale": "ordinal",
"sourceField": "event.category",
},
"75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object {
"dataType": "string",
"isBucketed": true,
"label": "Top values of undefined",
"operationType": "terms",
"params": Object {
"exclude": Array [],
"excludeIsRegex": false,
"include": Array [],
"includeIsRegex": false,
"missingBucket": false,
"orderBy": Object {
"columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
"type": "column",
},
"orderDirection": "desc",
"otherBucket": true,
"parentFormat": Object {
"id": "terms",
},
"size": 1000,
},
"scale": "ordinal",
"sourceField": undefined,
},
"f04a71a3-399f-4d32-9efc-8a005e989991": Object {
"dataType": "number",
"isBucketed": false,
"label": "Count of undefined",
"operationType": "count",
"params": Object {
"emptyAsNull": true,
},
"scale": "ratio",
"sourceField": undefined,
},
},
"incompleteColumns": Object {},
"sampling": 1,
@ -500,20 +444,16 @@ Object {
"visualization": Object {
"columns": Array [
Object {
"columnId": "2881fedd-54b7-42ba-8c97-5175dec86166",
"columnId": "mockTopValuesOfStackByFieldColumnId",
"isTransposed": false,
"width": 362,
},
Object {
"columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
"isTransposed": false,
},
Object {
"columnId": "75ce269b-ee9c-4c7d-a14e-9226ba0fe059",
"columnId": "mockCountColumnId",
"isTransposed": false,
},
],
"layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b",
"layerId": "mockLayerId",
"layerType": "data",
},
},

View file

@ -11,8 +11,20 @@ import { useLensAttributes } from '../../../use_lens_attributes';
import { getAlertsTableLensAttributes } from './alerts_table';
interface VisualizationState {
visualization: { columns: {} };
datasourceStates: {
formBased: { layers: Record<string, { columns: {}; columnOrder: string[] }> };
};
}
jest.mock('uuid', () => ({
v4: jest.fn().mockReturnValue('4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b'),
v4: jest
.fn()
.mockReturnValueOnce('mockLayerId')
.mockReturnValueOnce('mockTopValuesOfStackByFieldColumnId')
.mockReturnValueOnce('mockCountColumnId')
.mockReturnValueOnce('mockTopValuesOfBreakdownFieldColumnId'),
}));
jest.mock('../../../../../containers/sourcerer', () => ({
@ -95,6 +107,49 @@ describe('getAlertsTableLensAttributes', () => {
{ wrapper }
);
const state = result?.current?.state as VisualizationState;
expect(result?.current).toMatchSnapshot();
expect(state.datasourceStates.formBased.layers.mockLayerId.columnOrder).toMatchInlineSnapshot(`
Array [
"mockTopValuesOfStackByFieldColumnId",
"mockTopValuesOfBreakdownFieldColumnId",
"mockCountColumnId",
]
`);
});
it('should render Without extra options - breakdownField', () => {
const { result } = renderHook(
() =>
useLensAttributes({
extraOptions: { breakdownField: '' },
getLensAttributes: getAlertsTableLensAttributes,
stackByField: 'event.category',
}),
{ wrapper }
);
const state = result?.current?.state as VisualizationState;
expect(state.visualization?.columns).toMatchInlineSnapshot(`
Array [
Object {
"columnId": "mockTopValuesOfStackByFieldColumnId",
"isTransposed": false,
"width": 362,
},
Object {
"columnId": "mockCountColumnId",
"isTransposed": false,
},
]
`);
expect(state.datasourceStates.formBased.layers.mockLayerId.columnOrder).toMatchInlineSnapshot(`
Array [
"mockTopValuesOfStackByFieldColumnId",
"mockCountColumnId",
]
`);
});
});

View file

@ -5,35 +5,127 @@
* 2.0.
*/
import { v4 as uuidv4 } from 'uuid';
import type { GetLensAttributes } from '../../../types';
import { isEmpty } from 'lodash';
import type { GetLensAttributes, LensEmbeddableDataTableColumn } from '../../../types';
import { COUNT_OF, TOP_VALUE } from '../../../translations';
const layerId = uuidv4();
const topValuesOfStackByFieldColumnId = uuidv4();
const countColumnId = uuidv4();
const topValuesOfBreakdownFieldColumnId = uuidv4();
const defaultColumns = [
{
columnId: topValuesOfStackByFieldColumnId,
isTransposed: false,
width: 362,
},
{
columnId: countColumnId,
isTransposed: false,
},
];
const breakdownFieldColumns = [
{
columnId: topValuesOfBreakdownFieldColumnId,
isTransposed: false,
},
];
const defaultColumnOrder = [topValuesOfStackByFieldColumnId];
const getTopValuesOfBreakdownFieldColumnSettings = (
breakdownField: string
): Record<string, LensEmbeddableDataTableColumn> => ({
[topValuesOfBreakdownFieldColumnId]: {
label: TOP_VALUE(breakdownField),
dataType: 'string',
operationType: 'terms',
scale: 'ordinal',
sourceField: breakdownField,
isBucketed: true,
params: {
size: 1000,
orderBy: {
type: 'column',
columnId: countColumnId,
},
orderDirection: 'desc',
otherBucket: true,
missingBucket: false,
parentFormat: {
id: 'terms',
},
include: [],
exclude: [],
includeIsRegex: false,
excludeIsRegex: false,
},
},
});
export const getAlertsTableLensAttributes: GetLensAttributes = (
stackByField = 'kibana.alert.rule.name',
extraOptions
) => {
const breakdownFieldProvided = !isEmpty(extraOptions?.breakdownField);
const countField =
extraOptions?.breakdownField && breakdownFieldProvided
? extraOptions?.breakdownField
: stackByField;
const columnOrder = breakdownFieldProvided
? [...defaultColumnOrder, topValuesOfBreakdownFieldColumnId, countColumnId]
: [...defaultColumnOrder, countColumnId];
const columnSettings: Record<string, LensEmbeddableDataTableColumn> = {
[topValuesOfStackByFieldColumnId]: {
label: TOP_VALUE(stackByField),
dataType: 'string',
operationType: 'terms',
scale: 'ordinal',
sourceField: stackByField,
isBucketed: true,
params: {
size: 1000,
orderBy: {
type: 'column',
columnId: countColumnId,
},
orderDirection: 'desc',
otherBucket: true,
missingBucket: false,
parentFormat: {
id: 'terms',
},
include: [],
exclude: [],
includeIsRegex: false,
excludeIsRegex: false,
},
},
[countColumnId]: {
label: COUNT_OF(countField),
dataType: 'number',
operationType: 'count',
isBucketed: false,
scale: 'ratio',
sourceField: countField,
params: {
emptyAsNull: true,
},
},
...(extraOptions?.breakdownField && breakdownFieldProvided
? getTopValuesOfBreakdownFieldColumnSettings(extraOptions?.breakdownField)
: {}),
};
return {
title: 'Alerts',
description: '',
visualizationType: 'lnsDatatable',
state: {
visualization: {
columns: [
{
columnId: '2881fedd-54b7-42ba-8c97-5175dec86166',
isTransposed: false,
width: 362,
},
{
columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991',
isTransposed: false,
},
{
columnId: '75ce269b-ee9c-4c7d-a14e-9226ba0fe059',
isTransposed: false,
},
],
columns: breakdownFieldProvided
? [...defaultColumns, ...breakdownFieldColumns]
: defaultColumns,
layerId,
layerType: 'data',
},
@ -46,74 +138,16 @@ export const getAlertsTableLensAttributes: GetLensAttributes = (
formBased: {
layers: {
[layerId]: {
columns: {
'2881fedd-54b7-42ba-8c97-5175dec86166': {
label: `Top values of ${stackByField}`,
dataType: 'string',
operationType: 'terms',
scale: 'ordinal',
sourceField: stackByField,
isBucketed: true,
params: {
size: 1000,
orderBy: {
type: 'column',
columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991',
},
orderDirection: 'desc',
otherBucket: true,
missingBucket: false,
parentFormat: {
id: 'terms',
},
include: [],
exclude: [],
includeIsRegex: false,
excludeIsRegex: false,
},
columns: columnOrder.reduce<Record<string, LensEmbeddableDataTableColumn>>(
(acc, colId) => {
if (colId && columnSettings[colId]) {
acc[colId] = columnSettings[colId];
}
return acc;
},
'f04a71a3-399f-4d32-9efc-8a005e989991': {
label: `Count of ${extraOptions?.breakdownField}`,
dataType: 'number',
operationType: 'count',
isBucketed: false,
scale: 'ratio',
sourceField: extraOptions?.breakdownField,
params: {
emptyAsNull: true,
},
},
'75ce269b-ee9c-4c7d-a14e-9226ba0fe059': {
label: `Top values of ${extraOptions?.breakdownField}`,
dataType: 'string',
operationType: 'terms',
scale: 'ordinal',
sourceField: extraOptions?.breakdownField,
isBucketed: true,
params: {
size: 1000,
orderBy: {
type: 'column',
columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991',
},
orderDirection: 'desc',
otherBucket: true,
missingBucket: false,
parentFormat: {
id: 'terms',
},
include: [],
exclude: [],
includeIsRegex: false,
excludeIsRegex: false,
},
},
},
columnOrder: [
'2881fedd-54b7-42ba-8c97-5175dec86166',
'75ce269b-ee9c-4c7d-a14e-9226ba0fe059',
'f04a71a3-399f-4d32-9efc-8a005e989991',
],
{}
),
columnOrder,
sampling: 1,
incompleteColumns: {},
},

View file

@ -105,3 +105,9 @@ export const TOP_VALUE = (field: string) =>
export const COUNT = i18n.translate('xpack.securitySolution.visualizationActions.countLabel', {
defaultMessage: 'Count of records',
});
export const COUNT_OF = (field: string) =>
i18n.translate('xpack.securitySolution.visualizationActions.countOfFieldLabel', {
values: { field },
defaultMessage: 'Count of {field}',
});

View file

@ -7,6 +7,7 @@
import type {
DatatableVisualizationState,
FieldBasedIndexPatternColumn,
FormBasedPersistedState,
TypedLensByValueInput,
} from '@kbn/lens-plugin/public';
@ -181,3 +182,8 @@ export interface LensDataTableEmbeddable {
id: string;
timeRange: { from: string; to: string; fromStr: string; toStr: string };
}
export interface LensEmbeddableDataTableColumn extends FieldBasedIndexPatternColumn {
operationType: string;
params?: unknown;
}

View file

@ -81,10 +81,7 @@ export const useLensAttributes = ({
const lensAttrsWithInjectedData = useMemo(() => {
if (
lensAttributes == null &&
(getLensAttributes == null ||
stackByField == null ||
stackByField?.length === 0 ||
(extraOptions?.breakdownField != null && extraOptions?.breakdownField.length === 0))
(getLensAttributes == null || stackByField == null || stackByField?.length === 0)
) {
return null;
}
@ -113,7 +110,6 @@ export const useLensAttributes = ({
applyGlobalQueriesAndFilters,
attrs,
dataViewId,
extraOptions?.breakdownField,
filters,
getLensAttributes,
hasAdHocDataViews,

View file

@ -37,7 +37,7 @@ const ChartContentComponent = ({
}: ChartContentProps) => {
return isChartEmbeddablesEnabled ? (
<VisualizationEmbeddable
data-test-subj="embeddable-matrix-histogram"
data-test-subj="embeddable-alerts-count"
extraActions={extraActions}
extraOptions={extraOptions}
getLensAttributes={getLensAttributes}