Skip to content

Commit

Permalink
Merge pull request #3137 from glific/enhancement/pin-flows
Browse files Browse the repository at this point in the history
Added functionality to pin/unpin flows from the flows list
  • Loading branch information
kurund authored Nov 25, 2024
2 parents 78d8b55 + c1c9ff5 commit 87c2bf8
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 51 deletions.
28 changes: 12 additions & 16 deletions src/assets/images/icons/Pin/Active.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 26 additions & 1 deletion src/containers/Flow/FlowList/FlowList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
exportFlow,
releaseFlow,
filterTemplateFlows,
pinFlowQuery,
} from 'mocks/Flow';
import { getOrganizationQuery } from 'mocks/Organization';
import testJSON from 'mocks/ImportFlow.json';
Expand All @@ -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 };

Expand All @@ -44,6 +46,8 @@ const mocks = [
getRoleNameQuery,
getFlowCountQuery({ isTemplate: true }),
filterTemplateFlows,
pinFlowQuery('2', true),
pinFlowQuery('1'),
...getOrganizationQuery,
];

Expand All @@ -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('<FlowList />', () => {
test('should render Flow', async () => {
Expand All @@ -79,7 +84,7 @@ describe('<FlowList />', () => {
});
});

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
Expand Down Expand Up @@ -193,6 +198,26 @@ describe('<FlowList />', () => {
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', () => {
Expand Down
98 changes: 64 additions & 34 deletions src/containers/Flow/FlowList/FlowList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -34,17 +36,13 @@ const getName = (text: string, keywordsList: any, roles: any) => {
{text}
<br />
<span className={styles.Keyword}>{keywords.join(', ')}</span>
{hasDynamicRole && (
<span className={styles.Roles}>{accessRoles && accessRoles.join(', ')} </span>
)}
{hasDynamicRole && <span className={styles.Roles}>{accessRoles && accessRoles.join(', ')} </span>}
</div>
);
};

const getDate = (date: string, fallback: string = '') => (
<div className={styles.LastPublished}>
{date ? dayjs(date).format(STANDARD_DATE_TIME_FORMAT) : fallback}
</div>
<div className={styles.LastPublished}>{date ? dayjs(date).format(STANDARD_DATE_TIME_FORMAT) : fallback}</div>
);

const getLastPublished = (date: string, fallback: string = '') =>
Expand All @@ -55,21 +53,7 @@ const getLastPublished = (date: string, fallback: string = '') =>
);
const getLabel = (tag: any) => <div className={styles.LabelButton}>{tag.label}</div>;

const displayPinned = (isPinned: boolean) => {
if (isPinned) {
return <PinIcon />;
}
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 = <FlowIcon className={styles.FlowIcon} />;

const queries = {
Expand All @@ -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);

Expand Down Expand Up @@ -122,6 +107,8 @@ export const FlowList = () => {
},
});

const [updatePinned] = useMutation(PIN_FLOW);

const handleCopy = (id: any) => {
navigate(`/flow/${id}/edit`, { state: 'copy' });
};
Expand All @@ -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 (
<Tooltip title={'Unpin'} placement={'top-start'}>
<IconButton data-testid="unpin-button" onClick={() => handlePin(id)}>
<ActivePinIcon />
</IconButton>
</Tooltip>
);
}
return (
<Tooltip title={'Pin'} placement={'top-start'}>
<IconButton data-testid="pin-button" onClick={() => handlePin(id, true)}>
<PinIcon />
</IconButton>
</Tooltip>
);
};

if (importStatus.length > 0) {
dialog = (
<DialogBox
Expand Down Expand Up @@ -206,16 +243,8 @@ export const FlowList = () => {

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) : '',
Expand Down Expand Up @@ -360,6 +389,7 @@ export const FlowList = () => {
filterList={activeFilter}
loadingList={importing}
restrictedAction={restrictedAction}
refreshList={refreshList}
/>
</>
);
Expand Down
15 changes: 15 additions & 0 deletions src/graphql/mutations/Flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
`;
24 changes: 24 additions & 0 deletions src/mocks/Flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
},
},
},
},
});

0 comments on commit 87c2bf8

Please sign in to comment.