From 37366ac84bccd609d52639c70cabe3b17d9088f9 Mon Sep 17 00:00:00 2001 From: Akansha Sakhre Date: Wed, 20 Nov 2024 23:30:17 +0530 Subject: [PATCH 1/3] added functionality to pin/unpin flows --- src/assets/images/icons/Pin/Active.svg | 28 +++--- .../Flow/FlowList/FlowList.test.tsx | 27 ++++- src/containers/Flow/FlowList/FlowList.tsx | 98 ++++++++++++------- src/graphql/mutations/Flow.ts | 15 +++ src/mocks/Flow.tsx | 24 +++++ 5 files changed, 141 insertions(+), 51 deletions(-) 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..085f4d6ee 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(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 ( + + 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, + }, + }, + }, + }, +}); From 23e1e9c0c54cc30bb8c8336df5c803bb0e8a16cd Mon Sep 17 00:00:00 2001 From: Akansha Sakhre Date: Mon, 25 Nov 2024 13:41:08 +0530 Subject: [PATCH 2/3] added documentation links --- src/common/HelpData.tsx | 5 ++--- .../Assistants/AssistantOptions/AssistantOptions.tsx | 8 +++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/common/HelpData.tsx b/src/common/HelpData.tsx index feed84a86..c6a90a4d8 100644 --- a/src/common/HelpData.tsx +++ b/src/common/HelpData.tsx @@ -24,8 +24,7 @@ export const triggerInfo: HelpDataProps = { }; export const searchInfo: HelpDataProps = { - heading: - 'Glific provides search functionality to NGO staff to find contacts from a large set of contacts list.', + heading: 'Glific provides search functionality to NGO staff to find contacts from a large set of contacts list.', link: 'https://glific.github.io/docs/docs/Product%20Features/Searches', }; @@ -105,5 +104,5 @@ export const blockedContactsInfo: HelpDataProps = { export const assistantsInfo: HelpDataProps = { heading: 'Assistants can call OpenAI’s models with specific instructions to tune their personality and capabilities. Assistants can access multiple tools in parallel. Assistants can access files in several formats as part of their creation. When using tools, Assistants can also create files (e.g., images, spreadsheets, etc) and cite files they reference in the Messages they create.', - link: 'https://glific.github.io/docs/docs/Product%20Features/Flows/Flow%20Variables/Flow%20variables%20vs%20Contact%20variables', // Replace with the actual Glific documentation link + link: 'https://glific.github.io/docs/docs/Integrations/RAG%20using%20OpenAI%20file%20search%20assistant', // Replace with the actual Glific documentation link }; diff --git a/src/containers/Assistants/AssistantOptions/AssistantOptions.tsx b/src/containers/Assistants/AssistantOptions/AssistantOptions.tsx index 297cf10ef..a0b085377 100644 --- a/src/containers/Assistants/AssistantOptions/AssistantOptions.tsx +++ b/src/containers/Assistants/AssistantOptions/AssistantOptions.tsx @@ -30,7 +30,8 @@ interface AssistantOptionsProps { const temperatureInfo = 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.'; - +const filesInfo = + 'Enables the assistant with knowledge from files that you or your users upload. Once a file is uploaded, the assistant automatically decides when to retrieve content based on user requests.'; export const AssistantOptions = ({ currentId, options, setOptions }: AssistantOptionsProps) => { const [showUploadDialog, setShowUploadDialog] = useState(false); const [files, setFiles] = useState([]); @@ -171,6 +172,11 @@ export const AssistantOptions = ({ currentId, options, setOptions }: AssistantOp
Files +