#19406: supported the task filter on landing page widget (#19431)

* supported the task filter on landing page widget

* added playwright for the test
This commit is contained in:
Ashish Gupta 2025-01-20 22:45:17 +05:30 committed by GitHub
parent 3d9e9dd9ab
commit 57ed033703
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 317 additions and 29 deletions

View File

@ -590,6 +590,100 @@ test.describe('Activity feed', () => {
}
);
});
test('Check Task Filter in Landing Page Widget', async ({ browser }) => {
const { page: page1, afterAction: afterActionUser1 } =
await performUserLogin(browser, user1);
const { page: page2, afterAction: afterActionUser2 } =
await performUserLogin(browser, user2);
await base.step('Create and Assign Task to User 2', async () => {
await redirectToHomePage(page1);
await entity.visitEntityPage(page1);
// Create task for the user 2
await page1.getByTestId('request-description').click();
await createDescriptionTask(page1, {
term: entity.entity.displayName,
assignee: user2.responseData.name,
});
await afterActionUser1();
});
await base.step('Create and Validate Task as per Filters', async () => {
await redirectToHomePage(page2);
await entity.visitEntityPage(page2);
// Create task for the user 1
await page2.getByTestId('request-entity-tags').click();
await createTagTask(page2, {
term: entity.entity.displayName,
tag: 'PII.None',
assignee: user1.responseData.name,
});
await redirectToHomePage(page2);
const taskResponse = page2.waitForResponse(
'/api/v1/feed?type=Task&filterType=OWNER&taskStatus=Open&userId=*'
);
await page2
.getByTestId('activity-feed-widget')
.getByText('Tasks')
.click();
await taskResponse;
await expect(
page2.locator(
'[data-testid="activity-feed-widget"] [data-testid="no-data-placeholder"]'
)
).not.toBeVisible();
// Check the Task based on ALL task filter
await expect(page2.getByTestId('message-container')).toHaveCount(2);
// Check the Task based on Assigned task filter
await page2.getByTestId('filter-button').click();
await page2.waitForSelector('.ant-popover ', { state: 'visible' });
const taskAssignedResponse = page2.waitForResponse(
'/api/v1/feed?type=Task&filterType=ASSIGNED_TO&taskStatus=Open&userId=*'
);
await page2.getByText('Assigned').click();
await page2.getByTestId('selectable-list-update-btn').click();
await taskAssignedResponse;
await expect(page2.getByTestId('message-container')).toHaveCount(1);
await expect(page2.getByTestId('owner-link')).toContainText(
user2.responseData.displayName
);
// Check the Task based on Created by me task filter
await page2.getByTestId('filter-button').click();
await page2.waitForSelector('.ant-popover ', { state: 'visible' });
const taskCreatedByResponse = page2.waitForResponse(
'/api/v1/feed?type=Task&filterType=ASSIGNED_BY&taskStatus=Open&userId=*'
);
await page2.getByText('Created By').click();
await page2.getByTestId('selectable-list-update-btn').click();
await taskCreatedByResponse;
await expect(page2.getByTestId('message-container')).toHaveCount(1);
await expect(page2.getByTestId('owner-link')).toContainText(
user1.responseData.displayName
);
await afterActionUser2();
});
});
});
base.describe('Activity feed with Data Consumer User', () => {

View File

@ -20,6 +20,7 @@ import { Link, useHistory } from 'react-router-dom';
import { PAGE_SIZE_MEDIUM, ROUTES } from '../../../../constants/constants';
import { FEED_COUNT_INITIAL_DATA } from '../../../../constants/entity.constants';
import { mockFeedData } from '../../../../constants/mockTourData.constants';
import { TAB_SUPPORTED_FILTER } from '../../../../constants/Widgets.constant';
import { useTourProvider } from '../../../../context/TourProvider/TourProvider';
import { EntityTabs, EntityType } from '../../../../enums/entity.enum';
import { FeedFilter } from '../../../../enums/mydata.enum';
@ -76,7 +77,7 @@ const FeedsWidget = ({
getFeedData(FeedFilter.MENTIONS);
} else if (activeTab === ActivityFeedTabs.TASKS) {
getFeedData(
FeedFilter.OWNER,
defaultFilter,
undefined,
ThreadType.Task,
undefined,
@ -106,7 +107,12 @@ const FeedsWidget = ({
[count.openTaskCount, activeTab]
);
const onTabChange = (key: string) => setActiveTab(key as ActivityFeedTabs);
const onTabChange = (key: string) => {
if (key === ActivityFeedTabs.TASKS) {
setDefaultFilter(FeedFilter.OWNER);
}
setActiveTab(key as ActivityFeedTabs);
};
const redirectToUserPage = useCallback(() => {
history.push(
@ -259,13 +265,10 @@ const FeedsWidget = ({
]}
tabBarExtraContent={
<Space>
{activeTab === ActivityFeedTabs.ALL && (
{TAB_SUPPORTED_FILTER.includes(activeTab) && (
<FeedsFilterPopover
defaultFilter={
currentUser?.isAdmin
? FeedFilter.ALL
: FeedFilter.OWNER_OR_FOLLOWS
}
defaultFilter={defaultFilter}
feedTab={activeTab}
onUpdate={onFilterUpdate}
/>
)}

View File

@ -82,7 +82,16 @@ jest.mock(
jest.mock(
'../../../common/FeedsFilterPopover/FeedsFilterPopover.component',
() => jest.fn().mockImplementation(({ children }) => <p>{children}</p>)
() =>
jest.fn().mockImplementation(({ onUpdate }) => (
<div>
<button onClick={() => onUpdate('ALL')}>all_button</button>
<button onClick={() => onUpdate('ASSIGNED_TO')}>assigned_button</button>
<button onClick={() => onUpdate('ASSIGNED_BY')}>
created_by_button
</button>
</div>
))
);
jest.mock('../../../../rest/feedsAPI', () => ({
@ -193,4 +202,56 @@ describe('FeedsWidget', () => {
expect(mockHandleRemoveWidget).toHaveBeenCalledWith(widgetProps.widgetKey);
});
it('should call api with correct parameters based on the tab selected', () => {
render(<FeedsWidget {...widgetProps} />);
const tabs = screen.getAllByRole('tab');
const conversationTab = tabs[0];
fireEvent.click(conversationTab);
// initial API call for the Feed
expect(conversationTab.getAttribute('aria-selected')).toBe('true');
expect(mockUseActivityFeedProviderValue.getFeedData).toHaveBeenCalledWith(
'OWNER_OR_FOLLOWS',
undefined,
'Conversation',
undefined,
undefined,
undefined,
25
);
// Reset mock between checks
mockUseActivityFeedProviderValue.getFeedData.mockReset();
// Testing for "Task Tab", to call API with OWNER filter parameters
const taskTab = tabs[2];
fireEvent.click(taskTab);
expect(taskTab.getAttribute('aria-selected')).toBe('true');
expect(mockUseActivityFeedProviderValue.getFeedData).toHaveBeenCalledWith(
'OWNER',
undefined,
'Task',
undefined,
undefined,
'Open'
);
// Reset mock for the next check
mockUseActivityFeedProviderValue.getFeedData.mockReset();
// Applying the filter for the assigned button
const assignedFilterButton = screen.getByText('assigned_button');
fireEvent.click(assignedFilterButton);
expect(mockUseActivityFeedProviderValue.getFeedData).toHaveBeenCalledWith(
'ASSIGNED_TO',
undefined,
'Task',
undefined,
undefined,
'Open'
);
});
});

View File

@ -25,10 +25,12 @@ import { ReactComponent as FilterIcon } from '../../../assets/svg/ic-feeds-filte
import { FeedFilter } from '../../../enums/mydata.enum';
import { useApplicationStore } from '../../../hooks/useApplicationStore';
import { getFeedFilterWidgets } from '../../../utils/LandingPageWidget/WidgetsUtils';
import './feeds-filter-popover.less';
import { FeedsFilterPopoverProps } from './FeedsFilterPopover.interface';
const FeedsFilterPopover = ({
feedTab,
defaultFilter,
onUpdate,
}: FeedsFilterPopoverProps) => {
@ -39,26 +41,8 @@ const FeedsFilterPopover = ({
useState<FeedFilter>(defaultFilter);
const items = useMemo(
() => [
{
title: t('label.all'),
key: currentUser?.isAdmin
? FeedFilter.ALL
: FeedFilter.OWNER_OR_FOLLOWS,
description: t('message.feed-filter-all'),
},
{
title: t('label.my-data'),
key: FeedFilter.OWNER,
description: t('message.feed-filter-owner'),
},
{
title: t('label.following'),
key: FeedFilter.FOLLOWS,
description: t('message.feed-filter-following'),
},
],
[currentUser]
() => getFeedFilterWidgets(feedTab, currentUser?.isAdmin),
[currentUser?.isAdmin, feedTab]
);
const onFilterUpdate = useCallback(() => {

View File

@ -11,8 +11,10 @@
* limitations under the License.
*/
import { FeedFilter } from '../../../enums/mydata.enum';
import { ActivityFeedTabs } from '../../ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface';
export interface FeedsFilterPopoverProps {
feedTab: ActivityFeedTabs;
defaultFilter: FeedFilter;
onUpdate: (value: FeedFilter) => void;
}

View File

@ -13,10 +13,12 @@
import { act, fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { FeedFilter } from '../../../enums/mydata.enum';
import { ActivityFeedTabs } from '../../ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface';
import FeedsFilterPopover from './FeedsFilterPopover.component';
const onUpdateMock = jest.fn();
const mockProps = {
feedTab: ActivityFeedTabs.ALL,
defaultFilter: FeedFilter.ALL,
onUpdate: onUpdateMock,
};

View File

@ -0,0 +1,51 @@
/*
* Copyright 2025 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ActivityFeedTabs } from '../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface';
import { FeedFilter } from '../enums/mydata.enum';
import i18n from '../utils/i18next/LocalUtil';
export const TAB_SUPPORTED_FILTER = [
ActivityFeedTabs.ALL,
ActivityFeedTabs.TASKS,
];
export const TASK_FEED_FILTER_LIST = [
{
title: i18n.t('label.all'),
key: FeedFilter.OWNER,
description: i18n.t('message.feed-filter-all'),
},
{
title: i18n.t('label.assigned'),
key: FeedFilter.ASSIGNED_TO,
description: i18n.t('message.feed-filter-owner'),
},
{
title: i18n.t('label.created-by'),
key: FeedFilter.ASSIGNED_BY,
description: i18n.t('message.feed-filter-following'),
},
];
export const ACTIVITY_FEED_FILTER_LIST = [
{
title: i18n.t('label.my-data'),
key: FeedFilter.OWNER,
description: i18n.t('message.feed-filter-owner'),
},
{
title: i18n.t('label.following'),
key: FeedFilter.FOLLOWS,
description: i18n.t('message.feed-filter-following'),
},
];

View File

@ -0,0 +1,56 @@
/*
* Copyright 2025 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ActivityFeedTabs } from '../../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface';
import {
ACTIVITY_FEED_FILTER_LIST,
TASK_FEED_FILTER_LIST,
} from '../../constants/Widgets.constant';
import { FeedFilter } from '../../enums/mydata.enum';
import i18n from '../i18next/LocalUtil';
import { getFeedFilterWidgets } from './WidgetsUtils';
describe('Widgets Utils', () => {
describe('getFeedFilterWidgets', () => {
it('should return list for Feed Filters', () => {
const response = getFeedFilterWidgets(ActivityFeedTabs.ALL);
expect(response).toEqual([
{
title: i18n.t('label.all'),
key: FeedFilter.OWNER_OR_FOLLOWS,
description: i18n.t('message.feed-filter-all'),
},
...ACTIVITY_FEED_FILTER_LIST,
]);
});
it('should return list for Feed Filters for Admin', () => {
const response = getFeedFilterWidgets(ActivityFeedTabs.ALL, true);
expect(response).toEqual([
{
title: i18n.t('label.all'),
key: FeedFilter.ALL,
description: i18n.t('message.feed-filter-all'),
},
...ACTIVITY_FEED_FILTER_LIST,
]);
});
it('should return list for Task Filters', () => {
const response = getFeedFilterWidgets(ActivityFeedTabs.TASKS);
expect(response).toEqual(TASK_FEED_FILTER_LIST);
});
});
});

View File

@ -0,0 +1,35 @@
/*
* Copyright 2025 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ActivityFeedTabs } from '../../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface';
import {
ACTIVITY_FEED_FILTER_LIST,
TASK_FEED_FILTER_LIST,
} from '../../constants/Widgets.constant';
import { FeedFilter } from '../../enums/mydata.enum';
import i18n from '../i18next/LocalUtil';
export const getFeedFilterWidgets = (
tab: ActivityFeedTabs,
isAdmin?: boolean
) => {
return tab === ActivityFeedTabs.TASKS
? TASK_FEED_FILTER_LIST
: [
{
title: i18n.t('label.all'),
key: isAdmin ? FeedFilter.ALL : FeedFilter.OWNER_OR_FOLLOWS,
description: i18n.t('message.feed-filter-all'),
},
...ACTIVITY_FEED_FILTER_LIST,
];
};