Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(TDOPS-4789/facetedSearch): callback support for BadgeMenu #5022

Merged
merged 6 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/purple-trains-hug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@talend/react-faceted-search': minor
---

feat(TDOPS-4789/facetedSearch): callback support for BadgeMenu
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { useMemo } from 'react';
import { isEmpty } from 'lodash';
import { useEffect, useMemo, useState } from 'react';

import isEmpty from 'lodash/isEmpty';
import isObject from 'lodash/isObject';
import PropTypes from 'prop-types';

import Badge from '@talend/react-components/lib/Badge';

import {
callbacksPropTypes,
operatorPropTypes,
operatorsPropTypes,
} from '../../facetedSearch.propTypes';
import { BadgeFaceted } from '../BadgeFaceted';
import { BadgeMenuForm } from './BadgeMenuForm.component';
import { operatorPropTypes, operatorsPropTypes } from '../../facetedSearch.propTypes';

const getSelectBadgeLabel = (value, t) => {
const labelAll = t('FACETED_SEARCH_VALUE_ALL', { defaultValue: 'All' });
Expand All @@ -31,10 +38,37 @@ export const BadgeMenu = ({
displayType,
filterBarPlaceholder,
t,
callbacks,
...rest
}) => {
const [options, setOptions] = useState(values);
const currentOperators = useMemo(() => operators, [operators]);
const currentOperator = operator || (currentOperators && currentOperators[0]);
const [isLoading, setIsLoading] = useState(true);
const callback = callbacks && callbacks[rest.attribute];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe better to set isLoading false by default? then no need for this line: https://github.com/Talend/ui/pull/5022/files#diff-9cd7307a01bfb12fcee969fa3d4f76874a44bf7dc27b1c37d95e3157942ee263R51

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

useEffect(() => {
if (!callback || !callback.getOptions) {
setIsLoading(false);
return;
}

setIsLoading(true);
callback
.getOptions()
.then(data => {
setOptions(
data.map(item => {
if (isObject(item)) {
return { id: item.id, label: item.label };
}
return { id: item, label: item };
}),
);
})
.finally(() => {
setIsLoading(false);
});
}, [callback]);
const badgeMenuId = `${id}-badge-menu`;
const badgeLabel = useMemo(() => getSelectBadgeLabel(value, t), [value, t]);
return (
Expand All @@ -60,8 +94,9 @@ export const BadgeMenu = ({
onChange={onChangeValue}
onSubmit={onSubmitBadge}
value={badgeValue}
values={values}
values={options}
filterBarPlaceholder={filterBarPlaceholder}
isLoading={isLoading}
t={t}
{...rest}
/>
Expand Down Expand Up @@ -89,6 +124,7 @@ BadgeMenu.propTypes = {
readOnly: PropTypes.bool,
removable: PropTypes.bool,
values: PropTypes.array,
callbacks: callbacksPropTypes,
t: PropTypes.func.isRequired,
displayType: PropTypes.oneOf(Object.values(Badge.TYPES)),
filterBarPlaceholder: PropTypes.string,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { render } from '@testing-library/react';
import { BadgeFacetedProvider } from '../../context/badgeFaceted.context';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { BadgeMenu } from './BadgeMenu.component';
import getDefaultT from '../../../translate';
import { BadgeFacetedProvider } from '../../context/badgeFaceted.context';
import { BadgeMenu } from './BadgeMenu.component';

const t = getDefaultT();

Expand All @@ -15,6 +16,7 @@ const badgeFacetedContextValue = {
onDeleteBadge: jest.fn(),
onHideOperator: jest.fn(),
onSubmitBadge: jest.fn(),
dispatch: jest.fn(),
};

const BadgeWithContext = props => (
Expand Down Expand Up @@ -53,4 +55,57 @@ describe('BadgeMenu', () => {
// Then
expect(document.querySelectorAll('span')[2]).toHaveTextContent('All');
});
it('should mount a badge with object data from callback', async () => {
// Given
const callbacks = {
id: {
getOptions: () => new Promise(resolve => resolve([{ id: '1234', label: 'production' }])),
},
};

const props = {
id: 'myId',
label: 'myLabel',
initialOperatorOpened: false,
initialValueOpened: true,
operators: ['in'],
callbacks,
values: [],
t: getDefaultT(),
attribute: 'id',
};

// When
render(
<BadgeFacetedProvider value={badgeFacetedContextValue}>
<BadgeMenu {...props} />
</BadgeFacetedProvider>,
);

// Then there is a checkbox with data taken from callback
await waitFor(() => {
expect(screen.getByRole('menuitem')).toBeVisible();
});
screen.logTestingPlaygroundURL();
// Then selecting an item should dispatch proper payload
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debugging line can be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

await userEvent.click(screen.getByRole('menuitem', { name: 'production' }));
await userEvent.click(
screen.getByRole('button', {
name: /apply/i,
}),
);
expect(badgeFacetedContextValue.dispatch).toHaveBeenCalledWith({
payload: {
badgeId: 'myId',
metadata: { isInCreation: false },
properties: {
initialOperatorOpened: false,
initialValueOpened: false,
operator: 'in',
value: { checked: false, id: '1234', label: 'production' },
},
},
type: 'UPDATE_BADGE',
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,8 @@ $popover-screen-width: tokens.$coral-sizing-maximal;
width: $popover-screen-width;
padding-left: 0;
}

:global(.tc-circular-progress) {
height: 100%;
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
/* eslint-disable jsx-a11y/no-autofocus */
import { useMemo, useState } from 'react';

import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
import PropTypes from 'prop-types';

import { DropdownButton } from '@talend/design-system';
import { Action } from '@talend/react-components/lib/Actions';
import FilterBar from '@talend/react-components/lib/FilterBar';
import Rich from '@talend/react-components/lib/Rich';
import { getTheme } from '@talend/react-components/lib/theme';
import CircularProgress from '@talend/react-components/src/CircularProgress';

import cssModule from './BadgeMenu.module.scss';
import { getDataAttributesFrom } from '../../../helpers/usage.helpers';

import cssModule from './BadgeMenu.module.scss';

const theme = getTheme(cssModule);

const createRowItemEntity = value => option => {
Expand Down Expand Up @@ -84,21 +88,25 @@ const BadgeMenuForm = ({
onSubmit={onSubmit}
>
<Rich.Layout.Body id={badgeMenuFormId} className={theme('fs-badge-menu-form-body')}>
{visibleItems.map(rowItem => {
return (
<DropdownButton
key={rowItem.id}
onClick={event => {
onChange(event, rowItem);
}}
checked={rowItem.checked}
data-testid={`badge-menu-form-item-${rowItem.id}`}
data-test={`badge-menu-form-item-${rowItem.id}`}
>
<span>{rowItem.label}</span>
</DropdownButton>
);
})}
{!rest.isLoading ? (
visibleItems.map(rowItem => {
return (
<DropdownButton
key={rowItem.id}
onClick={event => {
onChange(event, rowItem);
}}
checked={rowItem.checked}
data-testid={`badge-menu-form-item-${rowItem.id}`}
data-test={`badge-menu-form-item-${rowItem.id}`}
>
<span>{rowItem.label}</span>
</DropdownButton>
);
})
) : (
<CircularProgress />
)}
</Rich.Layout.Body>
<Rich.Layout.Footer id={id} className={theme('fs-badge-menu-form-footer')}>
<div>
Expand All @@ -119,6 +127,8 @@ const BadgeMenuForm = ({
type="submit"
label={t('APPLY', { defaultValue: 'Apply' })}
bsStyle="info"
disabled={rest.isLoading}
inProgress={rest.isLoading}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO no need set inProgress to true when loading options, just disable submit button is enough. set inProgress to true when it's submitting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

{...getDataAttributesFrom(rest)}
/>
</Rich.Layout.Footer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@ describe('BadgeMenuForm', () => {
label: 'Item One',
});
});
it('should show loader icon if items are loading', () => {
// Given
const props = {
id: 'myId',
values: menuItems,
isLoading: true,
value: {},
t,
};
// When
render(<BadgeMenuForm {...props} />);
// Then
expect(screen.getAllByTestId('circular-progress')).toHaveLength(2);
expect(screen.getByRole('button')).toHaveAttribute('type', 'submit');
expect(screen.getByRole('button')).toBeDisabled();
});
it('should display menuitem checked', () => {
// Given
const props = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const badgeFacetedPropTypes = PropTypes.shape({
isInCreation: PropTypes.bool,
entitiesPerBadge: PropTypes.string,
operators: PropTypes.arrayOf(PropTypes.string),
isLoading: PropTypes.bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to add isLoading to metadata?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

}),
});

Expand Down
Loading