diff --git a/ui-packages/packages/common/src/graphql/queries.tsx b/ui-packages/packages/common/src/graphql/queries.tsx index 2980b79dd0..21970bf813 100644 --- a/ui-packages/packages/common/src/graphql/queries.tsx +++ b/ui-packages/packages/common/src/graphql/queries.tsx @@ -289,6 +289,7 @@ const GET_TASKS_FOR_USER = gql` $groups: [String!] $offset: Int $limit: Int + $orderBy: UserTaskInstanceOrderBy ) { UserTaskInstances( where: { @@ -299,6 +300,7 @@ const GET_TASKS_FOR_USER = gql` ] } pagination: { offset: $offset, limit: $limit } + orderBy: $orderBy ) { id name diff --git a/ui-packages/packages/common/src/graphql/types.tsx b/ui-packages/packages/common/src/graphql/types.tsx index c6954e713c..d8d0eb1bb6 100644 --- a/ui-packages/packages/common/src/graphql/types.tsx +++ b/ui-packages/packages/common/src/graphql/types.tsx @@ -1051,6 +1051,7 @@ export namespace GraphQL { groups?: Maybe>; offset?: Maybe; limit?: Maybe; + orderBy?: Maybe; }>; export type GetTasksForUserQuery = { __typename?: 'Query' } & { @@ -1886,6 +1887,7 @@ export namespace GraphQL { $groups: [String!] $offset: Int $limit: Int + $orderBy: UserTaskInstanceOrderBy ) { UserTaskInstances( where: { @@ -1896,6 +1898,7 @@ export namespace GraphQL { ] } pagination: { offset: $offset, limit: $limit } + orderBy: $orderBy ) { id name @@ -1939,6 +1942,7 @@ export namespace GraphQL { * groups: // value for 'groups' * offset: // value for 'offset' * limit: // value for 'limit' + * orderBy: // value for 'orderBy' * }, * }); */ diff --git a/ui-packages/packages/task-console/src/components/Organisms/TaskInbox/TaskInbox.tsx b/ui-packages/packages/task-console/src/components/Organisms/TaskInbox/TaskInbox.tsx index 854275786f..0a519ed4f3 100644 --- a/ui-packages/packages/task-console/src/components/Organisms/TaskInbox/TaskInbox.tsx +++ b/ui-packages/packages/task-console/src/components/Organisms/TaskInbox/TaskInbox.tsx @@ -31,6 +31,7 @@ import TaskConsoleContext, { } from '../../../context/TaskConsoleContext/TaskConsoleContext'; import Columns from '../../../util/Columns'; import UserTaskInstance = GraphQL.UserTaskInstance; +import _ from 'lodash'; const UserTaskLoadingComponent = ( @@ -39,6 +40,7 @@ const UserTaskLoadingComponent = ( ); const TaskInbox: React.FC = props => { + const context: IContext = useContext(TaskConsoleContext); const [defaultPageSize] = useState(10); const [isLoaded, setIsLoaded] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -46,24 +48,29 @@ const TaskInbox: React.FC = props => { const [pageSize, setPageSize] = useState(defaultPageSize); const [isLoadingMore, setIsLoadingMore] = useState(false); const [tableData, setTableData] = useState([]); - - const context: IContext = useContext(TaskConsoleContext); + const [sorting, setSorting] = useState(false); const [ getUserTasks, { loading, error, data, refetch, networkStatus } ] = GraphQL.useGetTasksForUserLazyQuery({ fetchPolicy: 'network-only', - notifyOnNetworkStatusChange: true, - variables: { - user: context.getUser().id, - groups: context.getUser().groups, - offset: queryOffset, - limit: pageSize - } + notifyOnNetworkStatusChange: true }); + const columns: DataTableColumn[] = [ + Columns.getTaskDescriptionColumn(true), + Columns.getDefaultColumn('processId', 'Process', false), + Columns.getDefaultColumn('priority', 'Priority', true), + Columns.getTaskStateColumn(true), + Columns.getDateColumn('started', 'Started', true), + Columns.getDateColumn('lastUpdate', 'Last update', true) + ]; + const onGetMoreInstances = (_queryOffset, _pageSize, _loadMore) => { + let newQueryLimit = _pageSize; + let newQueryOffset = _queryOffset; + setIsLoadingMore(_loadMore); if (_queryOffset !== queryOffset) { @@ -74,41 +81,93 @@ const TaskInbox: React.FC = props => { setPageSize(_pageSize); } + if (!_.isEmpty(context.getActiveQueryInfo().sortBy)) { + newQueryOffset = 0; + newQueryLimit = tableData.length + newQueryLimit; + } + + context.getActiveQueryInfo().offset = newQueryOffset; + context.getActiveQueryInfo().maxElements += _pageSize; + + fetchUserTasks(newQueryOffset, newQueryLimit); + }; + const fetchUserTasks = (_queryOffset, _queryLimit) => { getUserTasks({ variables: { user: context.getUser().id, groups: context.getUser().groups, offset: _queryOffset, - limit: _pageSize + limit: _queryLimit, + orderBy: getSortByObject() } }); }; - useEffect(() => { - onGetMoreInstances(queryOffset, pageSize, false); + if (!context.getActiveQueryInfo().maxElements) { + context.getActiveQueryInfo().maxElements = pageSize; + } + if (context.getActiveQueryInfo().offset) { + setOffset(context.getActiveQueryInfo().offset); + } + fetchUserTasks(0, context.getActiveQueryInfo().maxElements); }, []); useEffect(() => { if (isLoadingMore === undefined || !isLoadingMore) { setIsLoading(loading); } + if (!loading && data !== undefined) { - const newData = tableData.concat(data.UserTaskInstances); - setTableData(newData); + setSorting(false); + + if (_.isEmpty(context.getActiveQueryInfo().sortBy)) { + const newData = tableData.concat(data.UserTaskInstances); + setTableData(newData); + } else { + setTableData(data.UserTaskInstances); + } + if (queryOffset > 0 && tableData.length > 0) { setIsLoadingMore(false); } + if (!isLoaded) { setIsLoaded(true); } } }, [data]); + const onSorting = (index, direction) => { + setSorting(true); + if (direction) { + context.getActiveQueryInfo().sortBy = { index, direction }; + } else { + context.getActiveQueryInfo().sortBy = null; + } + }; + + const getSortByObject = () => { + if (!_.isEmpty(context.getActiveQueryInfo().sortBy)) { + return _.set( + {}, + columns[context.getActiveQueryInfo().sortBy.index].path, + context.getActiveQueryInfo().sortBy.direction.toUpperCase() + ); + } + return null; + }; + + useEffect(() => { + if (!_.isEmpty(context.getActiveQueryInfo().sortBy)) { + fetchUserTasks(0, context.getActiveQueryInfo().maxElements); + } + }, [context.getActiveQueryInfo().sortBy]); + if (error) { return ; } - if (!isLoaded) { + if (!isLoaded || sorting) { return UserTaskLoadingComponent; } @@ -121,19 +180,9 @@ const TaskInbox: React.FC = props => { /> ); } - - const columns: DataTableColumn[] = [ - Columns.getTaskDescriptionColumn(), - Columns.getDefaultColumn('processId', 'Process'), - Columns.getDefaultColumn('priority', 'Priority'), - Columns.getTaskStateColumn(), - Columns.getDateColumn('started', 'Started'), - Columns.getDateColumn('lastUpdate', 'Last update') - ]; - const mustShowMore = - isLoadingMore || (data && data.UserTaskInstances.length === pageSize); - + isLoadingMore || + context.getActiveQueryInfo().maxElements === tableData.length; return ( { error={error} refetch={refetch} LoadingComponent={UserTaskLoadingComponent} + onSorting={onSorting} + sortBy={context.getActiveQueryInfo().sortBy} /> {mustShowMore && ( { return <>; @@ -86,7 +86,8 @@ describe('TaskInbox tests', () => { user: testUser.id, groups: testUser.groups, offset: 0, - limit: 10 + limit: 10, + orderBy: null } }, result: { @@ -96,11 +97,8 @@ describe('TaskInbox tests', () => { } } ]; - const wrapper = await getWrapper(mocks); - expect(wrapper).toMatchSnapshot(); - const emptyState = wrapper.find(KogitoEmptyState); expect(emptyState.exists()).toBeTruthy(); @@ -115,7 +113,8 @@ describe('TaskInbox tests', () => { user: testUser.id, groups: testUser.groups, offset: 0, - limit: 10 + limit: 10, + orderBy: null } }, result: { @@ -149,7 +148,8 @@ describe('TaskInbox tests', () => { user: testUser.id, groups: testUser.groups, offset: 0, - limit: 10 + limit: 10, + orderBy: null } }, result: { @@ -165,7 +165,8 @@ describe('TaskInbox tests', () => { user: testUser.id, groups: testUser.groups, offset: 10, - limit: 10 + limit: 10, + orderBy: null } }, result: { @@ -184,14 +185,13 @@ describe('TaskInbox tests', () => { expect(dataTable.exists()).toBeTruthy(); expect(dataTable.props().data).toHaveLength(10); - let loadMore = wrapper.find(LoadMore); expect(loadMore.exists()).toBeTruthy(); await act(async () => { wrapper - .find(DropdownToggle) + .find(DropdownToggleAction) .find('button') .at(0) .simulate('click'); @@ -218,7 +218,8 @@ describe('TaskInbox tests', () => { user: testUser.id, groups: testUser.groups, offset: 0, - limit: 10 + limit: 10, + orderBy: null } }, error: { @@ -240,4 +241,87 @@ describe('TaskInbox tests', () => { expect(serverError.exists()).toBeTruthy(); }); + + it('test sorting -> with direction', async () => { + const mocks = [ + { + request: { + query: GraphQL.GetTasksForUserDocument, + variables: { + user: testUser.id, + groups: testUser.groups, + offset: 0, + limit: 10, + orderBy: null + } + }, + result: { + data: { + UserTaskInstances: userTasks.slice(0, 10) + } + } + }, + { + request: { + query: GraphQL.GetTasksForUserDocument, + variables: { + user: testUser.id, + groups: testUser.groups, + offset: 0, + limit: 10, + orderBy: { state: GraphQL.OrderBy.Asc } + } + }, + result: { + data: { + UserTaskInstances: userTasks.slice(0, 10) + } + } + }, + { + request: { + query: GraphQL.GetTasksForUserDocument, + variables: { + user: testUser.id, + groups: testUser.groups, + offset: 0, + limit: 10, + orderBy: { state: GraphQL.OrderBy.Asc } + } + }, + result: { + data: { + UserTaskInstances: userTasks.slice(0, 20) + } + } + } + ]; + /* tslint:disable:no-string-literal no-unexpected-multiline*/ + let wrapper = await getWrapper(mocks); + // sortby value check + await act(async () => { + wrapper + .find(DataTable) + .props() + ['onSorting'](3, 'asc'); + }); + await wait(10); + wrapper = wrapper.update(); + expect(wrapper.find('DataTable').props()['sortBy']).toEqual({ + index: 3, + direction: 'asc' + }); + // after loadmore click - sortby value exists + await act(async () => { + wrapper + .find(DropdownToggleAction) + .find('button') + .simulate('click'); + }); + await wait(10); + expect(wrapper.find('DataTable').props()['sortBy']).toEqual({ + index: 3, + direction: 'asc' + }); + }); }); diff --git a/ui-packages/packages/task-console/src/components/Organisms/TaskInbox/tests/__snapshots__/TaskInbox.test.tsx.snap b/ui-packages/packages/task-console/src/components/Organisms/TaskInbox/tests/__snapshots__/TaskInbox.test.tsx.snap index 265df71b8c..76b1319e64 100644 --- a/ui-packages/packages/task-console/src/components/Organisms/TaskInbox/tests/__snapshots__/TaskInbox.test.tsx.snap +++ b/ui-packages/packages/task-console/src/components/Organisms/TaskInbox/tests/__snapshots__/TaskInbox.test.tsx.snap @@ -26,29 +26,35 @@ exports[`TaskInbox tests Test load data with LoadMore 1`] = ` Array [ Object { "bodyCellTransformer": [Function], + "isSortable": true, "label": "Name", "path": "referenceName", }, Object { + "isSortable": false, "label": "Process", "path": "processId", }, Object { + "isSortable": true, "label": "Priority", "path": "priority", }, Object { "bodyCellTransformer": [Function], + "isSortable": true, "label": "State", "path": "state", }, Object { "bodyCellTransformer": [Function], + "isSortable": true, "label": "Started", "path": "started", }, Object { "bodyCellTransformer": [Function], + "isSortable": true, "label": "Last update", "path": "lastUpdate", }, @@ -312,6 +318,7 @@ exports[`TaskInbox tests Test load data with LoadMore 1`] = ` } isLoading={false} networkStatus={7} + onSorting={[Function]} refetch={[Function]} > @@ -763,29 +770,35 @@ exports[`TaskInbox tests Test load data with LoadMore 2`] = ` Array [ Object { "bodyCellTransformer": [Function], + "isSortable": true, "label": "Name", "path": "referenceName", }, Object { + "isSortable": false, "label": "Process", "path": "processId", }, Object { + "isSortable": true, "label": "Priority", "path": "priority", }, Object { "bodyCellTransformer": [Function], + "isSortable": true, "label": "State", "path": "state", }, Object { "bodyCellTransformer": [Function], + "isSortable": true, "label": "Started", "path": "started", }, Object { "bodyCellTransformer": [Function], + "isSortable": true, "label": "Last update", "path": "lastUpdate", }, @@ -1309,6 +1322,7 @@ exports[`TaskInbox tests Test load data with LoadMore 2`] = ` } isLoading={false} networkStatus={7} + onSorting={[Function]} refetch={[Function]} > @@ -1771,29 +1785,35 @@ exports[`TaskInbox tests Test load data without LoadMore 1`] = ` Array [ Object { "bodyCellTransformer": [Function], + "isSortable": true, "label": "Name", "path": "referenceName", }, Object { + "isSortable": false, "label": "Process", "path": "processId", }, Object { + "isSortable": true, "label": "Priority", "path": "priority", }, Object { "bodyCellTransformer": [Function], + "isSortable": true, "label": "State", "path": "state", }, Object { "bodyCellTransformer": [Function], + "isSortable": true, "label": "Started", "path": "started", }, Object { "bodyCellTransformer": [Function], + "isSortable": true, "label": "Last update", "path": "lastUpdate", }, @@ -1927,6 +1947,7 @@ exports[`TaskInbox tests Test load data without LoadMore 1`] = ` } isLoading={false} networkStatus={7} + onSorting={[Function]} refetch={[Function]} > diff --git a/ui-packages/packages/task-console/src/components/Templates/DataListContainer/DataListContainer.tsx b/ui-packages/packages/task-console/src/components/Templates/DataListContainer/DataListContainer.tsx deleted file mode 100755 index 861518fc99..0000000000 --- a/ui-packages/packages/task-console/src/components/Templates/DataListContainer/DataListContainer.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { Card, Grid, GridItem, PageSection } from '@patternfly/react-core'; -import React, { useState, useEffect } from 'react'; -import UserTaskPageHeader from '../../Molecules/UserTaskPageHeader/UserTaskPageHeader'; -import DataToolbarComponent from '../../Molecules/DataListToolbar/DataListToolbar'; -import './DataList.css'; -import TaskList from '../../Organisms/TaskList/TaskList'; -import { - ouiaPageTypeAndObjectId, - KogitoEmptyState, - KogitoEmptyStateType, - GraphQL, - OUIAProps, - componentOuiaProps -} from '@kogito-apps/common'; - -const DataListContainer: React.FC = ({ ouiaId, ouiaSafe }) => { - const [initData, setInitData] = useState([]); - const [checkedArray, setCheckedArray] = useState(['Ready']); - const [isLoading, setIsLoading] = useState(false); - const [isError, setIsError] = useState(false); - const [isStatusSelected, setIsStatusSelected] = useState(true); - const [filters, setFilters] = useState(checkedArray); - - const [ - getProcessInstances, - { loading, data } - ] = GraphQL.useGetUserTasksByStatesLazyQuery({ fetchPolicy: 'network-only' }); - - const onFilterClick = (arr = checkedArray) => { - setIsLoading(true); - setIsError(false); - setIsStatusSelected(true); - getProcessInstances({ variables: { state: arr } }); - }; - - useEffect(() => { - setIsLoading(loading); - setInitData(data); - }, [data]); - - useEffect(() => { - return ouiaPageTypeAndObjectId('user-tasks', 'true'); - }); - - const resetClick = () => { - setCheckedArray(['Ready']); - setFilters({ ...filters, status: ['Ready'] }); - onFilterClick(['Ready']); - }; - - return ( - -
- - - - - - {!isError && ( - - )} - {isStatusSelected ? ( - - ) : ( - - )} - - - - -
-
- ); -}; - -export default DataListContainer; diff --git a/ui-packages/packages/task-console/src/context/TaskConsoleContext/TaskConsoleContext.tsx b/ui-packages/packages/task-console/src/context/TaskConsoleContext/TaskConsoleContext.tsx index 920250182b..4d68097d5f 100644 --- a/ui-packages/packages/task-console/src/context/TaskConsoleContext/TaskConsoleContext.tsx +++ b/ui-packages/packages/task-console/src/context/TaskConsoleContext/TaskConsoleContext.tsx @@ -1,15 +1,29 @@ import React from 'react'; import { User } from '@kogito-apps/common'; +export interface SortBy { + index: number; + direction: string; +} export interface IContext { getUser(): User; - setActiveItem(item: any); - getActiveItem(): any; + setActiveItem(item: T); + getActiveItem(): T; + getActiveQueryInfo(): IQueryInfo; +} + +export interface IQueryInfo { + sortBy?: SortBy; + offset?: number; + maxElements?: number; } export class DefaultContext implements IContext { private user: User; private item: T; + private readonly queryInfo: IQueryInfo = { + maxElements: 0 + }; constructor(user: User) { this.user = user; @@ -26,6 +40,10 @@ export class DefaultContext implements IContext { setActiveItem(item: T) { this.item = item; } + + getActiveQueryInfo(): IQueryInfo { + return this.queryInfo; + } } const TaskConsoleContext = React.createContext>(null); diff --git a/ui-packages/packages/task-console/src/util/Columns.tsx b/ui-packages/packages/task-console/src/util/Columns.tsx index eb67e463ed..909547ba76 100644 --- a/ui-packages/packages/task-console/src/util/Columns.tsx +++ b/ui-packages/packages/task-console/src/util/Columns.tsx @@ -23,42 +23,48 @@ import Moment from 'react-moment'; export default class Columns { static getDefaultColumn = ( columnPath: string, - columnLabel: string + columnLabel: string, + isSortable: boolean ): DataTableColumn => { return { path: columnPath, - label: columnLabel + label: columnLabel, + isSortable }; }; static getDateColumn( columnPath: string, - columnLabel: string + columnLabel: string, + isSortable: boolean ): DataTableColumn { return { label: columnLabel, path: columnPath, bodyCellTransformer: value => ( {new Date(`${value}`)} - ) + ), + isSortable }; } - static getTaskDescriptionColumn(): DataTableColumn { + static getTaskDescriptionColumn(isSortable: boolean): DataTableColumn { return { label: 'Name', path: 'referenceName', bodyCellTransformer: (cellValue, rowTask) => ( - ) + ), + isSortable }; } - static getTaskStateColumn(): DataTableColumn { + static getTaskStateColumn(isSortable: boolean): DataTableColumn { return { label: 'State', path: 'state', - bodyCellTransformer: (cellValue, rowTask) => + bodyCellTransformer: (cellValue, rowTask) => , + isSortable }; } } diff --git a/ui-packages/packages/task-console/src/util/tests/Columns.test.tsx b/ui-packages/packages/task-console/src/util/tests/Columns.test.tsx index 654eb315c5..638f1ffef4 100644 --- a/ui-packages/packages/task-console/src/util/tests/Columns.test.tsx +++ b/ui-packages/packages/task-console/src/util/tests/Columns.test.tsx @@ -18,7 +18,7 @@ import Columns from '../Columns'; describe('Columns testing', () => { it('Test default column', () => { - const column = Columns.getDefaultColumn('path', 'Label'); + const column = Columns.getDefaultColumn('path', 'Label', true); expect(column).not.toBeNull(); expect(column.path).toBe('path'); @@ -27,7 +27,7 @@ describe('Columns testing', () => { }); it('Test task description column', async () => { - const column = Columns.getDateColumn('path', 'Date Column'); + const column = Columns.getDateColumn('path', 'Date Column', true); expect(column).not.toBeNull(); expect(column.path).toBe('path'); @@ -36,7 +36,7 @@ describe('Columns testing', () => { }); it('Test task description column', async () => { - const column = Columns.getTaskDescriptionColumn(); + const column = Columns.getTaskDescriptionColumn(true); expect(column).not.toBeNull(); expect(column.path).toBe('referenceName'); @@ -45,7 +45,7 @@ describe('Columns testing', () => { }); it('Test task state column', async () => { - const column = Columns.getTaskStateColumn(); + const column = Columns.getTaskStateColumn(true); expect(column).not.toBeNull(); expect(column.path).toBe('state');