diff --git a/src/assets/images/icons/Pin/Active.svg b/src/assets/images/icons/Pin/Active.svg index 6444d4135..95ed20dd1 100644 --- a/src/assets/images/icons/Pin/Active.svg +++ b/src/assets/images/icons/Pin/Active.svg @@ -1,16 +1,12 @@ - - - Pinned - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + diff --git a/src/containers/Flow/FlowList/FlowList.test.tsx b/src/containers/Flow/FlowList/FlowList.test.tsx index 686ed895e..73d4dba1f 100644 --- a/src/containers/Flow/FlowList/FlowList.test.tsx +++ b/src/containers/Flow/FlowList/FlowList.test.tsx @@ -13,6 +13,7 @@ import { exportFlow, releaseFlow, filterTemplateFlows, + pinFlowQuery, } from 'mocks/Flow'; import { getOrganizationQuery } from 'mocks/Organization'; import testJSON from 'mocks/ImportFlow.json'; @@ -21,6 +22,7 @@ import { FlowList } from './FlowList'; import { Flow } from '../Flow'; import { getFilterTagQuery } from 'mocks/Tag'; import { getRoleNameQuery } from 'mocks/Role'; +import * as Notification from 'common/notification'; const isActiveFilter = { isActive: true, isTemplate: false }; @@ -44,6 +46,8 @@ const mocks = [ getRoleNameQuery, getFlowCountQuery({ isTemplate: true }), filterTemplateFlows, + pinFlowQuery('2', true), + pinFlowQuery('1'), ...getOrganizationQuery, ]; @@ -69,6 +73,7 @@ vi.mock('react-router-dom', async () => { setUserSession(JSON.stringify({ roles: [{ id: '1', label: 'Admin' }] })); setOrganizationServices('{"__typename":"OrganizationServicesResult","rolesAndPermission":true}'); +const notificationSpy = vi.spyOn(Notification, 'setNotification'); describe('', () => { test('should render Flow', async () => { @@ -79,7 +84,7 @@ describe('', () => { }); }); - test('should search flow and check if flow keywprds are present below the name', async () => { + test('should search flow and check if flow keywords are present below the name', async () => { const { getByText, getByTestId, queryByPlaceholderText } = render(flowList); await waitFor(() => { // type "Help Workflow" in search box and enter @@ -193,6 +198,26 @@ describe('', () => { expect(mockedUsedNavigate).toHaveBeenCalled(); }); }); + + test('it should pin/unpin the flows', async () => { + render(flowList); + + await waitFor(() => { + expect(screen.getByText('Flows')).toBeInTheDocument(); + }); + + fireEvent.click(screen.getAllByTestId('pin-button')[0]); + + await waitFor(() => { + expect(notificationSpy).toHaveBeenCalled(); + }); + + fireEvent.click(screen.getAllByTestId('unpin-button')[0]); + + await waitFor(() => { + expect(notificationSpy).toHaveBeenCalled(); + }); + }); }); describe('Template flows', () => { diff --git a/src/containers/Flow/FlowList/FlowList.tsx b/src/containers/Flow/FlowList/FlowList.tsx index e1aa59562..4c650102b 100644 --- a/src/containers/Flow/FlowList/FlowList.tsx +++ b/src/containers/Flow/FlowList/FlowList.tsx @@ -4,22 +4,24 @@ import dayjs from 'dayjs'; import { useTranslation } from 'react-i18next'; import { useLazyQuery, useMutation, useQuery } from '@apollo/client'; -import { FormControl, MenuItem, Select } from '@mui/material'; +import { FormControl, IconButton, MenuItem, Select } from '@mui/material'; import FlowIcon from 'assets/images/icons/Flow/Dark.svg?react'; import DuplicateIcon from 'assets/images/icons/Duplicate.svg?react'; import ExportIcon from 'assets/images/icons/Flow/Export.svg?react'; import ConfigureIcon from 'assets/images/icons/Configure/UnselectedDark.svg?react'; -import PinIcon from 'assets/images/icons/Pin/Active.svg?react'; +import PinIcon from 'assets/images/icons/Pin/Pin.svg?react'; +import ActivePinIcon from 'assets/images/icons/Pin/Active.svg?react'; import ViewIcon from 'assets/images/icons/ViewLight.svg?react'; import { FILTER_FLOW, GET_FLOW_COUNT, EXPORT_FLOW, RELEASE_FLOW } from 'graphql/queries/Flow'; -import { DELETE_FLOW, IMPORT_FLOW } from 'graphql/mutations/Flow'; +import { DELETE_FLOW, IMPORT_FLOW, PIN_FLOW } from 'graphql/mutations/Flow'; import { List } from 'containers/List/List'; import { ImportButton } from 'components/UI/ImportButton/ImportButton'; import { STANDARD_DATE_TIME_FORMAT } from 'common/constants'; import { exportFlowMethod, organizationHasDynamicRole } from 'common/utils'; import styles from './FlowList.module.css'; import { GET_TAGS } from 'graphql/queries/Tags'; +import Tooltip from 'components/UI/Tooltip/Tooltip'; import { AutoComplete } from 'components/UI/Form/AutoComplete/AutoComplete'; import { flowInfo } from 'common/HelpData'; import { DialogBox } from 'components/UI/DialogBox/DialogBox'; @@ -34,17 +36,13 @@ const getName = (text: string, keywordsList: any, roles: any) => { {text}
{keywords.join(', ')} - {hasDynamicRole && ( - {accessRoles && accessRoles.join(', ')} - )} + {hasDynamicRole && {accessRoles && accessRoles.join(', ')} } ); }; const getDate = (date: string, fallback: string = '') => ( -
- {date ? dayjs(date).format(STANDARD_DATE_TIME_FORMAT) : fallback} -
+
{date ? dayjs(date).format(STANDARD_DATE_TIME_FORMAT) : fallback}
); const getLastPublished = (date: string, fallback: string = '') => @@ -55,21 +53,7 @@ const getLastPublished = (date: string, fallback: string = '') => ); const getLabel = (tag: any) =>
{tag.label}
; -const displayPinned = (isPinned: boolean) => { - if (isPinned) { - return ; - } - return ''; -}; - -const columnStyles = [ - styles.Pinned, - styles.Name, - styles.DateColumn, - styles.Label, - styles.DateColumn, - styles.Actions, -]; +const columnStyles = [styles.Pinned, styles.Name, styles.DateColumn, styles.Label, styles.DateColumn, styles.Actions]; const flowIcon = ; const queries = { @@ -91,6 +75,7 @@ export const FlowList = () => { const [importing, setImporting] = useState(false); const [importStatus, setImportStatus] = useState([]); const [showDialog, setShowDialog] = useState(false); + const [refreshList, setRefreshList] = useState(false); const [releaseFlow] = useLazyQuery(RELEASE_FLOW); @@ -122,6 +107,8 @@ export const FlowList = () => { }, }); + const [updatePinned] = useMutation(PIN_FLOW); + const handleCopy = (id: any) => { navigate(`/flow/${id}/edit`, { state: 'copy' }); }; @@ -130,8 +117,58 @@ export const FlowList = () => { setFlowName(item.name); exportFlowMutation({ variables: { id } }); }; + + const handlePin = (updateFlowId: any, pin: boolean = false) => { + if (pin) { + updatePinned({ + variables: { + updateFlowId, + input: { + isPinned: true, + }, + }, + onCompleted: () => { + setRefreshList(!refreshList); + setNotification('Flow pinned successfully'); + }, + }); + } else { + updatePinned({ + variables: { + updateFlowId, + input: { + isPinned: false, + }, + }, + onCompleted: () => { + setRefreshList(!refreshList); + setNotification('Flow unpinned successfully'); + }, + }); + } + }; + let dialog; + const displayPinned = (isPinned: boolean, id: any) => { + if (isPinned) { + return ( + + handlePin(id)}> + + + + ); + } + return ( + + handlePin(id, true)}> + + + + ); + }; + if (importStatus.length > 0) { dialog = ( { const additionalAction = () => (filter === 'isTemplate' ? templateFlowActions : actions); - const getColumns = ({ - name, - keywords, - lastChangedAt, - lastPublishedAt, - tag, - roles, - isPinned, - }: any) => ({ - pin: displayPinned(isPinned), + const getColumns = ({ name, keywords, lastChangedAt, lastPublishedAt, tag, roles, isPinned, id }: any) => ({ + pin: displayPinned(isPinned, id), name: getName(name, keywords, roles), lastPublishedAt: getLastPublished(lastPublishedAt, t('Not published yet')), label: tag ? getLabel(tag) : '', @@ -360,6 +389,7 @@ export const FlowList = () => { filterList={activeFilter} loadingList={importing} restrictedAction={restrictedAction} + refreshList={refreshList} /> ); diff --git a/src/graphql/mutations/Flow.ts b/src/graphql/mutations/Flow.ts index 7465c8fc8..aad737027 100644 --- a/src/graphql/mutations/Flow.ts +++ b/src/graphql/mutations/Flow.ts @@ -167,3 +167,18 @@ export const TERMINATE_FLOW = gql` } } `; + +export const PIN_FLOW = gql` + mutation UpdateFlow($updateFlowId: ID!, $input: FlowInput) { + updateFlow(id: $updateFlowId, input: $input) { + errors { + key + message + } + flow { + id + isPinned + } + } + } +`; diff --git a/src/mocks/Flow.tsx b/src/mocks/Flow.tsx index 64d370f16..36ba8d61e 100644 --- a/src/mocks/Flow.tsx +++ b/src/mocks/Flow.tsx @@ -21,6 +21,7 @@ import { IMPORT_FLOW_LOCALIZATIONS, ADD_FLOW_TO_WA_GROUP, CREATE_FLOW, + PIN_FLOW, } from 'graphql/mutations/Flow'; import { GET_ORGANIZATION_SERVICES } from 'graphql/queries/Organization'; import json from './ImportFlow.json'; @@ -753,3 +754,26 @@ export const createTagQuery = { }, }, }; + +export const pinFlowQuery = (updateFlowId: string, pin: boolean = false) => ({ + request: { + query: PIN_FLOW, + variables: { + updateFlowId, + input: { + isPinned: pin, + }, + }, + }, + result: { + data: { + updateFlow: { + errors: null, + flow: { + id: '2', + isPinned: pin, + }, + }, + }, + }, +});