Basic data enrichment (#3023)

* Add Enrich to frontend

* Naive backend implementation

* Add work email check

* Rename Enrich to Quick Action

* Refactor logic to a separate service

* Refacto to separate IntelligenceService

* Small fixes

* Missing Break statement

* Address PR comments

* Create company interface

* Improve edge case handling

* Use httpService instead of Axios

* Fix server tests
This commit is contained in:
Félix Malfait
2023-12-18 15:45:30 +01:00
committed by GitHub
parent 576492f3c0
commit fff51a2d91
38 changed files with 16928 additions and 27 deletions

View File

@ -0,0 +1,50 @@
import { useCallback } from 'react';
import { useApolloClient } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { capitalize } from '~/utils/string/capitalize';
type useExecuteQuickActionOnOneRecordProps = {
objectNameSingular: string;
};
export const useExecuteQuickActionOnOneRecord = <T>({
objectNameSingular,
}: useExecuteQuickActionOnOneRecordProps) => {
const {
objectMetadataItem,
executeQuickActionOnOneRecordMutation,
findManyRecordsQuery,
} = useObjectMetadataItem({
objectNameSingular,
});
const apolloClient = useApolloClient();
const executeQuickActionOnOneRecord = useCallback(
async (idToExecuteQuickActionOn: string) => {
const executeQuickActionOnRecord = await apolloClient.mutate({
mutation: executeQuickActionOnOneRecordMutation,
variables: {
idToExecuteQuickActionOn,
},
refetchQueries: [getOperationName(findManyRecordsQuery) ?? ''],
});
return executeQuickActionOnRecord.data[
`executeQuickActionOn${capitalize(objectMetadataItem.nameSingular)}`
] as T;
},
[
objectMetadataItem.nameSingular,
apolloClient,
executeQuickActionOnOneRecordMutation,
findManyRecordsQuery,
],
);
return {
executeQuickActionOnOneRecord,
};
};

View File

@ -0,0 +1,44 @@
import { gql } from '@apollo/client';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { capitalize } from '~/utils/string/capitalize';
export const getExecuteQuickActionOnOneRecordMutationGraphQLField = ({
objectNameSingular,
}: {
objectNameSingular: string;
}) => {
return `executeQuickActionOn${capitalize(objectNameSingular)}`;
};
export const useGenerateExecuteQuickActionOnOneRecordMutation = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
if (!objectMetadataItem) {
return EMPTY_MUTATION;
}
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
const graphQLFieldForExecuteQuickActionOnOneRecordMutation =
getExecuteQuickActionOnOneRecordMutationGraphQLField({
objectNameSingular: objectMetadataItem.nameSingular,
});
return gql`
mutation ExecuteQuickActionOnOne${capitalizedObjectName}($idToExecuteQuickActionOn: ID!) {
${graphQLFieldForExecuteQuickActionOnOneRecordMutation}(id: $idToExecuteQuickActionOn) {
id
${objectMetadataItem.fields
.map((field) => mapFieldMetadataToGraphQLQuery(field))
.join('\n')}
}
}
`;
};

View File

@ -5,14 +5,21 @@ import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { useExecuteQuickActionOnOneRecord } from '@/object-record/hooks/useExecuteQuickActionOnOneRecord';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
import { selectedRowIdsSelector } from '@/object-record/record-table/states/selectors/selectedRowIdsSelector';
import { IconHeart, IconHeartOff, IconTrash } from '@/ui/display/icon';
import {
IconHeart,
IconHeartOff,
IconTrash,
IconWand,
} from '@/ui/display/icon';
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState';
import { ContextMenuEntry } from '@/ui/navigation/context-menu/types/ContextMenuEntry';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
type useRecordTableContextMenuEntriesProps = {
recordTableScopeId?: string;
@ -68,6 +75,10 @@ export const useRecordTableContextMenuEntries = (
objectNameSingular,
});
const { executeQuickActionOnOneRecord } = useExecuteQuickActionOnOneRecord({
objectNameSingular,
});
const handleDeleteClick = useRecoilCallback(
({ snapshot }) =>
async () => {
@ -85,6 +96,27 @@ export const useRecordTableContextMenuEntries = (
[deleteOneRecord, resetTableRowSelection],
);
const handleExecuteQuickActionOnClick = useRecoilCallback(
({ snapshot }) =>
async () => {
const rowIdsToExecuteQuickActionOn = snapshot
.getLoadable(selectedRowIdsSelector)
.getValue();
resetTableRowSelection();
await Promise.all(
rowIdsToExecuteQuickActionOn.map(async (rowId) => {
await executeQuickActionOnOneRecord(rowId);
}),
);
},
[executeQuickActionOnOneRecord, resetTableRowSelection],
);
const dataExecuteQuickActionOnmentEnabled = useIsFeatureEnabled(
'IS_QUICK_ACTIONS_ENABLED',
);
return {
setContextMenuEntries: useCallback(() => {
const selectedRowId =
@ -143,6 +175,15 @@ export const useRecordTableContextMenuEntries = (
// Icon: IconNotes,
// onClick: () => {},
// },
...(dataExecuteQuickActionOnmentEnabled
? [
{
label: 'Quick Action',
Icon: IconWand,
onClick: () => handleExecuteQuickActionOnClick(),
},
]
: []),
{
label: 'Delete',
Icon: IconTrash,