From fc7d4e2a1e1648df00fb75d52fd45abd73318f4d Mon Sep 17 00:00:00 2001 From: ybaskaran <34709414+ybaskaran@users.noreply.github.com> Date: Tue, 5 Dec 2023 09:40:09 +0100 Subject: [PATCH] feat(TDOPS-4789/facetedSearch): callback support for BadgeMenu (#5022) * feat(TDOPS-4789/facetedSearch): callback support for BadgeMenu * fix: isEmpty import * feat(TDOPS-4789/facetedSearch): callback support for BadgeMenu * feat(TDOPS-4789/facetedSearch): callback support for BadgeMenu --------- Co-authored-by: Inna Ivashchuk --- .changeset/purple-trains-hug.md | 5 ++ .../Badges/BadgeMenu/BadgeMenu.component.js | 44 ++++++++++++-- .../BadgeMenu/BadgeMenu.component.test.js | 60 ++++++++++++++++++- .../Badges/BadgeMenu/BadgeMenu.module.scss | 4 ++ .../BadgeMenu/BadgeMenuForm.component.js | 41 ++++++++----- .../BadgeMenu/BadgeMenuForm.component.test.js | 16 +++++ .../Badges/BadgeTags/BadgeTags.component.js | 9 ++- 7 files changed, 153 insertions(+), 26 deletions(-) create mode 100644 .changeset/purple-trains-hug.md diff --git a/.changeset/purple-trains-hug.md b/.changeset/purple-trains-hug.md new file mode 100644 index 00000000000..0f27925b5e2 --- /dev/null +++ b/.changeset/purple-trains-hug.md @@ -0,0 +1,5 @@ +--- +'@talend/react-faceted-search': minor +--- + +feat(TDOPS-4789/facetedSearch): callback support for BadgeMenu diff --git a/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenu.component.js b/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenu.component.js index 0b1fbdc9a43..a42023d593f 100644 --- a/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenu.component.js +++ b/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenu.component.js @@ -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' }); @@ -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]; + 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 ( @@ -60,8 +94,9 @@ export const BadgeMenu = ({ onChange={onChangeValue} onSubmit={onSubmitBadge} value={badgeValue} - values={values} + values={options} filterBarPlaceholder={filterBarPlaceholder} + isLoading={isLoading} t={t} {...rest} /> @@ -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, diff --git a/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenu.component.test.js b/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenu.component.test.js index 4d72e5985d3..00dfd3b1d62 100644 --- a/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenu.component.test.js +++ b/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenu.component.test.js @@ -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(); @@ -15,6 +16,7 @@ const badgeFacetedContextValue = { onDeleteBadge: jest.fn(), onHideOperator: jest.fn(), onSubmitBadge: jest.fn(), + dispatch: jest.fn(), }; const BadgeWithContext = props => ( @@ -53,4 +55,56 @@ 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( + + + , + ); + + // Then there is a checkbox with data taken from callback + await waitFor(() => { + expect(screen.getByRole('menuitem')).toBeVisible(); + }); + // Then selecting an item should dispatch proper payload + 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', + }); + }); }); diff --git a/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenu.module.scss b/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenu.module.scss index 8b1cb63fbd8..bd2ad978253 100644 --- a/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenu.module.scss +++ b/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenu.module.scss @@ -48,4 +48,8 @@ $popover-screen-width: tokens.$coral-sizing-maximal; width: $popover-screen-width; padding-left: 0; } + + :global(.tc-circular-progress) { + height: 100%; + } } diff --git a/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenuForm.component.js b/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenuForm.component.js index 90cd3c4a10b..536a90f23d5 100644 --- a/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenuForm.component.js +++ b/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenuForm.component.js @@ -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 => { @@ -84,21 +88,25 @@ const BadgeMenuForm = ({ onSubmit={onSubmit} > - {visibleItems.map(rowItem => { - return ( - { - onChange(event, rowItem); - }} - checked={rowItem.checked} - data-testid={`badge-menu-form-item-${rowItem.id}`} - data-test={`badge-menu-form-item-${rowItem.id}`} - > - {rowItem.label} - - ); - })} + {!rest.isLoading ? ( + visibleItems.map(rowItem => { + return ( + { + onChange(event, rowItem); + }} + checked={rowItem.checked} + data-testid={`badge-menu-form-item-${rowItem.id}`} + data-test={`badge-menu-form-item-${rowItem.id}`} + > + {rowItem.label} + + ); + }) + ) : ( + + )}
@@ -119,6 +127,7 @@ const BadgeMenuForm = ({ type="submit" label={t('APPLY', { defaultValue: 'Apply' })} bsStyle="info" + disabled={rest.isLoading} {...getDataAttributesFrom(rest)} /> diff --git a/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenuForm.component.test.js b/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenuForm.component.test.js index 9597262e9c5..9ee31558a75 100644 --- a/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenuForm.component.test.js +++ b/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenuForm.component.test.js @@ -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(); + // Then + expect(screen.getByTestId('circular-progress')).toBeVisible(); + expect(screen.getByRole('button')).toHaveAttribute('type', 'submit'); + expect(screen.getByRole('button')).toBeDisabled(); + }); it('should display menuitem checked', () => { // Given const props = { diff --git a/packages/faceted-search/src/components/Badges/BadgeTags/BadgeTags.component.js b/packages/faceted-search/src/components/Badges/BadgeTags/BadgeTags.component.js index deef59700e4..152a0bbac1a 100644 --- a/packages/faceted-search/src/components/Badges/BadgeTags/BadgeTags.component.js +++ b/packages/faceted-search/src/components/Badges/BadgeTags/BadgeTags.component.js @@ -1,14 +1,17 @@ import { useEffect, useMemo, useState } from 'react'; + import isObject from 'lodash/isObject'; import PropTypes from 'prop-types'; + import Badge from '@talend/react-components/lib/Badge'; -import { BadgeTagsForm } from './BadgeTagsForm.component'; -import { BadgeFaceted } from '../BadgeFaceted'; + import { callbacksPropTypes, operatorPropTypes, operatorsPropTypes, } from '../../facetedSearch.propTypes'; +import { BadgeFaceted } from '../BadgeFaceted'; +import { BadgeTagsForm } from './BadgeTagsForm.component'; const getSelectBadgeLabel = (value, t) => { const labelAll = t('FACETED_SEARCH_VALUE_ALL', { defaultValue: 'All' }); @@ -46,7 +49,7 @@ export const BadgeTags = ({ ...rest }) => { const [tagsValues, setTagsValues] = useState([]); - const [isLoading, setIsLoading] = useState(true); + const [isLoading, setIsLoading] = useState(false); useEffect(() => { if (!callbacks || !callbacks.getTags) { setIsLoading(false);