Skip to content

Commit

Permalink
[Lens]Adds selected field accordion to the fields list (#143175)
Browse files Browse the repository at this point in the history
* [Lens]Add selected field accordion to the fields list

* Adds some tests
  • Loading branch information
stratoula authored Oct 12, 2022
1 parent 64472d2 commit e877fbf
Show file tree
Hide file tree
Showing 11 changed files with 303 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,31 @@ describe('FormBased Data Panel', () => {
}),
};
});

it('should list all selected fields if exist', async () => {
const newProps = {
...props,
layerFields: ['bytes'],
};
const wrapper = mountWithIntl(<InnerFormBasedDataPanel {...newProps} />);
expect(
wrapper
.find('[data-test-subj="lnsIndexPatternSelectedFields"]')
.find(FieldItem)
.map((fieldItem) => fieldItem.prop('field').name)
).toEqual(['bytes']);
});

it('should not list the selected fields accordion if no fields given', async () => {
const wrapper = mountWithIntl(<InnerFormBasedDataPanel {...props} />);
expect(
wrapper
.find('[data-test-subj="lnsIndexPatternSelectedFields"]')
.find(FieldItem)
.map((fieldItem) => fieldItem.prop('field').name)
).toEqual([]);
});

it('should list all supported fields in the pattern sorted alphabetically in groups', async () => {
const wrapper = mountWithIntl(<InnerFormBasedDataPanel {...props} />);
expect(wrapper.find(FieldItem).first().prop('field').displayName).toEqual('Records');
Expand Down
20 changes: 20 additions & 0 deletions x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export type Props = Omit<
frame: FramePublicAPI;
indexPatternService: IndexPatternServiceAPI;
onIndexPatternRefresh: () => void;
layerFields?: string[];
};

function sortFields(fieldA: IndexPatternField, fieldB: IndexPatternField) {
Expand Down Expand Up @@ -144,6 +145,7 @@ export function FormBasedDataPanel({
frame,
onIndexPatternRefresh,
usedIndexPatterns,
layerFields,
}: Props) {
const { indexPatterns, indexPatternRefs, existingFields, isFirstExistenceFetch } =
frame.dataViews;
Expand Down Expand Up @@ -234,6 +236,7 @@ export function FormBasedDataPanel({
indexPatternService={indexPatternService}
onIndexPatternRefresh={onIndexPatternRefresh}
frame={frame}
layerFields={layerFields}
/>
)}
</>
Expand Down Expand Up @@ -282,6 +285,7 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({
indexPatternService,
frame,
onIndexPatternRefresh,
layerFields,
}: Omit<
DatasourceDataPanelProps,
'state' | 'setState' | 'showNoDataPopover' | 'core' | 'onChangeIndexPattern' | 'usedIndexPatterns'
Expand All @@ -296,6 +300,7 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({
frame: FramePublicAPI;
indexPatternFieldEditor: IndexPatternFieldEditorStart;
onIndexPatternRefresh: () => void;
layerFields?: string[];
}) {
const [localState, setLocalState] = useState<DataPanelState>({
nameFilter: '',
Expand Down Expand Up @@ -345,6 +350,7 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({
const allSupportedTypesFields = allFields.filter((field) =>
supportedFieldTypes.has(field.type)
);
const usedByLayersFields = allFields.filter((field) => layerFields?.includes(field.name));
const sorted = allSupportedTypesFields.sort(sortFields);
const groupedFields = {
...defaultFieldGroups,
Expand Down Expand Up @@ -372,6 +378,19 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({
title: '',
hideDetails: true,
},
SelectedFields: {
fields: usedByLayersFields,
fieldCount: usedByLayersFields.length,
isInitiallyOpen: true,
showInAccordion: true,
title: i18n.translate('xpack.lens.indexPattern.selectedFieldsLabel', {
defaultMessage: 'Selected fields',
}),
isAffectedByGlobalFilter: !!filters.length,
isAffectedByTimeFilter: true,
hideDetails: false,
hideIfEmpty: true,
},
AvailableFields: {
fields: groupedFields.availableFields,
fieldCount: groupedFields.availableFields.length,
Expand Down Expand Up @@ -451,6 +470,7 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({
existenceFetchTimeout,
currentIndexPattern,
existingFieldsForIndexPattern,
layerFields,
]);

const fieldGroups: FieldGroups = useMemo(() => {
Expand Down
102 changes: 53 additions & 49 deletions x-pack/plugins/lens/public/datasources/form_based/field_list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type FieldGroups = Record<
isAffectedByTimeFilter: boolean;
hideDetails?: boolean;
defaultNoFieldsMessage?: string;
hideIfEmpty?: boolean;
}
>;

Expand Down Expand Up @@ -161,55 +162,58 @@ export const FieldList = React.memo(function FieldList({
)}
</ul>
<EuiSpacer size="s" />
{fieldGroupsToShow.map(([key, fieldGroup], index) => (
<Fragment key={key}>
<FieldsAccordion
dropOntoWorkspace={dropOntoWorkspace}
hasSuggestionForField={hasSuggestionForField}
initialIsOpen={Boolean(accordionState[key])}
key={key}
id={`lnsIndexPattern${key}`}
label={fieldGroup.title}
helpTooltip={fieldGroup.helpText}
exists={exists}
editField={editField}
removeField={removeField}
hideDetails={fieldGroup.hideDetails}
hasLoaded={!!hasSyncedExistingFields}
fieldsCount={fieldGroup.fields.length}
isFiltered={fieldGroup.fieldCount !== fieldGroup.fields.length}
paginatedFields={paginatedFields[key]}
fieldProps={fieldProps}
groupIndex={index + 1}
onToggle={(open) => {
setAccordionState((s) => ({
...s,
[key]: open,
}));
const displayedFieldLength = getDisplayedFieldsLength(fieldGroups, {
...accordionState,
[key]: open,
});
setPageSize(
Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength))
);
}}
showExistenceFetchError={existenceFetchFailed}
showExistenceFetchTimeout={existenceFetchTimeout}
renderCallout={
<NoFieldsCallout
isAffectedByGlobalFilter={fieldGroup.isAffectedByGlobalFilter}
isAffectedByTimerange={fieldGroup.isAffectedByTimeFilter}
isAffectedByFieldFilter={fieldGroup.fieldCount !== fieldGroup.fields.length}
existFieldsInIndex={!!existFieldsInIndex}
defaultNoFieldsMessage={fieldGroup.defaultNoFieldsMessage}
/>
}
uiActions={uiActions}
/>
<EuiSpacer size="m" />
</Fragment>
))}
{fieldGroupsToShow.map(([key, fieldGroup], index) => {
if (Boolean(fieldGroup.hideIfEmpty) && !fieldGroup.fields.length) return null;
return (
<Fragment key={key}>
<FieldsAccordion
dropOntoWorkspace={dropOntoWorkspace}
hasSuggestionForField={hasSuggestionForField}
initialIsOpen={Boolean(accordionState[key])}
key={key}
id={`lnsIndexPattern${key}`}
label={fieldGroup.title}
helpTooltip={fieldGroup.helpText}
exists={exists}
editField={editField}
removeField={removeField}
hideDetails={fieldGroup.hideDetails}
hasLoaded={!!hasSyncedExistingFields}
fieldsCount={fieldGroup.fields.length}
isFiltered={fieldGroup.fieldCount !== fieldGroup.fields.length}
paginatedFields={paginatedFields[key]}
fieldProps={fieldProps}
groupIndex={index + 1}
onToggle={(open) => {
setAccordionState((s) => ({
...s,
[key]: open,
}));
const displayedFieldLength = getDisplayedFieldsLength(fieldGroups, {
...accordionState,
[key]: open,
});
setPageSize(
Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength))
);
}}
showExistenceFetchError={existenceFetchFailed}
showExistenceFetchTimeout={existenceFetchTimeout}
renderCallout={
<NoFieldsCallout
isAffectedByGlobalFilter={fieldGroup.isAffectedByGlobalFilter}
isAffectedByTimerange={fieldGroup.isAffectedByTimeFilter}
isAffectedByFieldFilter={fieldGroup.fieldCount !== fieldGroup.fields.length}
existFieldsInIndex={!!existFieldsInIndex}
defaultNoFieldsMessage={fieldGroup.defaultNoFieldsMessage}
/>
}
uiActions={uiActions}
/>
<EuiSpacer size="m" />
</Fragment>
);
})}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,20 @@ describe('IndexPattern Data Source', () => {
});
});

describe('#getSelectedFields', () => {
it('should return the fields used per layer', async () => {
expect(FormBasedDatasource?.getSelectedFields?.(baseState)).toEqual(['op']);
});

it('should return empty array for empty layers', async () => {
const state = {
...baseState,
layers: {},
};
expect(FormBasedDatasource?.getSelectedFields?.(state)).toEqual([]);
});
});

describe('#toExpression', () => {
it('should generate an empty expression when no columns are selected', async () => {
const state = FormBasedDatasource.initialize();
Expand Down
16 changes: 16 additions & 0 deletions x-pack/plugins/lens/public/datasources/form_based/form_based.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,26 @@ export function getFormBasedDatasource({
});
},

getSelectedFields(state) {
const fields: string[] = [];
Object.values(state?.layers)?.forEach((l) => {
const { columns } = l;
Object.values(columns).forEach((c) => {
if ('sourceField' in c) {
fields.push(c.sourceField);
}
});
});
return fields;
},

toExpression: (state, layerId, indexPatterns) =>
toExpression(state, layerId, indexPatterns, uiSettings),

renderDataPanel(domElement: Element, props: DatasourceDataPanelProps<FormBasedPrivateState>) {
const { onChangeIndexPattern, ...otherProps } = props;
const layerFields = formBasedDatasource?.getSelectedFields?.(props.state);

render(
<KibanaThemeProvider theme$={core.theme.theme$}>
<I18nProvider>
Expand All @@ -292,6 +307,7 @@ export function getFormBasedDatasource({
core={core}
uiActions={uiActions}
onIndexPatternRefresh={onRefreshIndexPattern}
layerFields={layerFields}
/>
</KibanaContextProvider>
</I18nProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,20 @@ describe('TextBased Query Languages Data Panel', () => {
).toEqual(['timestamp', 'bytes', 'memory']);
});

it('should not display the selected fields accordion if there are no fields displayed', async () => {
const wrapper = mountWithIntl(<TextBasedDataPanel {...defaultProps} />);
expect(wrapper.find('[data-test-subj="lnsSelectedFieldsTextBased"]').length).toEqual(0);
});

it('should display the selected fields accordion if there are fields displayed', async () => {
const props = {
...defaultProps,
layerFields: ['memory'],
};
const wrapper = mountWithIntl(<TextBasedDataPanel {...props} />);
expect(wrapper.find('[data-test-subj="lnsSelectedFieldsTextBased"]').length).not.toEqual(0);
});

it('should list all supported fields in the pattern that match the search input', async () => {
const wrapper = mountWithIntl(<TextBasedDataPanel {...defaultProps} />);
const searchBox = wrapper.find('[data-test-subj="lnsTextBasedLangugesFieldSearch"]');
Expand Down
Loading

0 comments on commit e877fbf

Please sign in to comment.