Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added functionality to pin/unpin flows from the flows list #3137

Merged
merged 5 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(true);
setNotification('Flow pinned successfully');
},
});
} else {
updatePinned({
variables: {
updateFlowId,
input: {
isPinned: false,
},
},
onCompleted: () => {
setRefreshList(true);
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,
},
},
},
},
});
Loading