From 6e9ff1d7c758f7e0fb2a3c34e59c16086e078b8a Mon Sep 17 00:00:00 2001 From: yatheesh Date: Thu, 30 Nov 2023 15:30:23 +0100 Subject: [PATCH 1/4] feat(TDOPS-4789/facetedSearch): callback support for BadgeMenu --- .changeset/purple-trains-hug.md | 5 ++ .../Badges/BadgeMenu/BadgeMenu.component.js | 42 ++++++++++++- .../BadgeMenu/BadgeMenu.component.test.js | 61 ++++++++++++++++++- .../Badges/BadgeMenu/BadgeMenu.module.scss | 4 ++ .../BadgeMenu/BadgeMenuForm.component.js | 42 ++++++++----- .../BadgeMenu/BadgeMenuForm.component.test.js | 16 +++++ .../src/components/facetedSearch.propTypes.js | 1 + 7 files changed, 149 insertions(+), 22 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..839b597953d 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 { useEffect, useMemo, useState } from 'react'; + import { isEmpty } from 'lodash'; +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..f9fde447019 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,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( + + + , + ); + + // 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 + 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..ef3d7ca8a1a 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,8 @@ const BadgeMenuForm = ({ type="submit" label={t('APPLY', { defaultValue: 'Apply' })} bsStyle="info" + disabled={rest.isLoading} + inProgress={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..20894f86ecc 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.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 = { diff --git a/packages/faceted-search/src/components/facetedSearch.propTypes.js b/packages/faceted-search/src/components/facetedSearch.propTypes.js index 3026bf925ca..341b6bc0adc 100644 --- a/packages/faceted-search/src/components/facetedSearch.propTypes.js +++ b/packages/faceted-search/src/components/facetedSearch.propTypes.js @@ -39,6 +39,7 @@ const badgeFacetedPropTypes = PropTypes.shape({ isInCreation: PropTypes.bool, entitiesPerBadge: PropTypes.string, operators: PropTypes.arrayOf(PropTypes.string), + isLoading: PropTypes.bool, }), }); From 034e6c627b386ae227f1b92c378103ba2d85030c Mon Sep 17 00:00:00 2001 From: Inna Ivashchuk Date: Thu, 30 Nov 2023 18:33:23 +0200 Subject: [PATCH 2/4] fix: isEmpty import --- .../src/components/Badges/BadgeMenu/BadgeMenu.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 839b597953d..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,6 +1,6 @@ import { useEffect, useMemo, useState } from 'react'; -import { isEmpty } from 'lodash'; +import isEmpty from 'lodash/isEmpty'; import isObject from 'lodash/isObject'; import PropTypes from 'prop-types'; From ab217a4a75c1a2b34a607af37c6c1da797990732 Mon Sep 17 00:00:00 2001 From: yatheesh Date: Tue, 5 Dec 2023 09:09:51 +0100 Subject: [PATCH 3/4] feat(TDOPS-4789/facetedSearch): callback support for BadgeMenu --- .../Badges/BadgeMenu/BadgeMenu.component.test.js | 1 - .../Badges/BadgeMenu/BadgeMenuForm.component.js | 1 - .../components/Badges/BadgeTags/BadgeTags.component.js | 9 ++++++--- .../src/components/facetedSearch.propTypes.js | 1 - 4 files changed, 6 insertions(+), 6 deletions(-) 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 f9fde447019..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 @@ -86,7 +86,6 @@ describe('BadgeMenu', () => { await waitFor(() => { expect(screen.getByRole('menuitem')).toBeVisible(); }); - screen.logTestingPlaygroundURL(); // Then selecting an item should dispatch proper payload await userEvent.click(screen.getByRole('menuitem', { name: 'production' })); await userEvent.click( 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 ef3d7ca8a1a..536a90f23d5 100644 --- a/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenuForm.component.js +++ b/packages/faceted-search/src/components/Badges/BadgeMenu/BadgeMenuForm.component.js @@ -128,7 +128,6 @@ const BadgeMenuForm = ({ label={t('APPLY', { defaultValue: 'Apply' })} bsStyle="info" disabled={rest.isLoading} - inProgress={rest.isLoading} {...getDataAttributesFrom(rest)} /> 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); diff --git a/packages/faceted-search/src/components/facetedSearch.propTypes.js b/packages/faceted-search/src/components/facetedSearch.propTypes.js index 341b6bc0adc..3026bf925ca 100644 --- a/packages/faceted-search/src/components/facetedSearch.propTypes.js +++ b/packages/faceted-search/src/components/facetedSearch.propTypes.js @@ -39,7 +39,6 @@ const badgeFacetedPropTypes = PropTypes.shape({ isInCreation: PropTypes.bool, entitiesPerBadge: PropTypes.string, operators: PropTypes.arrayOf(PropTypes.string), - isLoading: PropTypes.bool, }), }); From 5fe4f639f7b745024b6efce8359f82633a79f35d Mon Sep 17 00:00:00 2001 From: yatheesh Date: Tue, 5 Dec 2023 09:23:35 +0100 Subject: [PATCH 4/4] feat(TDOPS-4789/facetedSearch): callback support for BadgeMenu --- .../components/Badges/BadgeMenu/BadgeMenuForm.component.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 20894f86ecc..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 @@ -76,7 +76,7 @@ describe('BadgeMenuForm', () => { // When render(); // Then - expect(screen.getAllByTestId('circular-progress')).toHaveLength(2); + expect(screen.getByTestId('circular-progress')).toBeVisible(); expect(screen.getByRole('button')).toHaveAttribute('type', 'submit'); expect(screen.getByRole('button')).toBeDisabled(); });