Split components into object-metadata and object-record (#2425)

* Split components into object-metadata and object-record

* Fix seed
This commit is contained in:
Charles Bochet
2023-11-10 15:54:32 +01:00
committed by GitHub
parent 04c618284f
commit 54d7acd518
93 changed files with 209 additions and 266 deletions

View File

@ -0,0 +1,37 @@
/* eslint-disable no-console */
import { useMemo } from 'react';
import { ApolloClient, InMemoryCache } from '@apollo/client';
import { useRecoilState } from 'recoil';
import { tokenPairState } from '@/auth/states/tokenPairState';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { ApolloMetadataClientContext } from '../context/ApolloClientMetadataContext';
export const ApolloMetadataClientProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const [tokenPair] = useRecoilState(tokenPairState);
const apolloMetadataClient = useMemo(() => {
if (tokenPair?.accessToken.token) {
return new ApolloClient({
uri: `${REACT_APP_SERVER_BASE_URL}/metadata`,
cache: new InMemoryCache(),
headers: {
Authorization: `Bearer ${tokenPair.accessToken.token}`,
},
});
} else {
return null;
}
}, [tokenPair]);
return (
<ApolloMetadataClientContext.Provider value={apolloMetadataClient}>
{children}
</ApolloMetadataClientContext.Provider>
);
};

View File

@ -0,0 +1,42 @@
import { useNavigate } from 'react-router-dom';
import { Icon123 } from '@/ui/input/constants/icons';
import { useLazyLoadIcons } from '@/ui/input/hooks/useLazyLoadIcons';
import NavItem from '@/ui/navigation/navbar/components/NavItem';
import { useFindManyObjectMetadataItems } from '../hooks/useFindManyObjectMetadataItems';
export const ObjectMetadataNavItems = () => {
const { objectMetadataItems } = useFindManyObjectMetadataItems();
const navigate = useNavigate();
const { icons } = useLazyLoadIcons();
return (
<>
{objectMetadataItems
.filter(
(objectMetadataItem) =>
objectMetadataItem.isActive &&
!objectMetadataItem.namePlural.endsWith('V2'),
)
.map((objectMetadataItem) => {
return (
<NavItem
key={objectMetadataItem.id}
label={objectMetadataItem.labelPlural}
to={`/objects/${objectMetadataItem.namePlural}`}
Icon={
objectMetadataItem.icon
? icons[objectMetadataItem.icon]
: Icon123
}
onClick={() => {
navigate(`/objects/${objectMetadataItem.namePlural}`);
}}
/>
);
})}
</>
);
};

View File

@ -0,0 +1,5 @@
import { createContext } from 'react';
import { ApolloClient, NormalizedCacheObject } from '@apollo/client';
export const ApolloMetadataClientContext =
createContext<ApolloClient<NormalizedCacheObject> | null>(null);

View File

@ -0,0 +1,121 @@
import { gql } from '@apollo/client';
export const CREATE_ONE_METADATA_OBJECT = gql`
mutation CreateOneObjectMetadataItem($input: CreateOneObjectInput!) {
createOneObject(input: $input) {
id
dataSourceId
nameSingular
namePlural
labelSingular
labelPlural
description
icon
isCustom
isActive
createdAt
updatedAt
}
}
`;
export const CREATE_ONE_METADATA_FIELD = gql`
mutation CreateOneFieldMetadataItem($input: CreateOneFieldInput!) {
createOneField(input: $input) {
id
type
name
label
description
icon
placeholder
isCustom
isActive
isNullable
createdAt
updatedAt
}
}
`;
export const UPDATE_ONE_METADATA_FIELD = gql`
mutation UpdateOneFieldMetadataItem(
$idToUpdate: ID!
$updatePayload: UpdateFieldInput!
) {
updateOneField(input: { id: $idToUpdate, update: $updatePayload }) {
id
type
name
label
description
icon
placeholder
isCustom
isActive
isNullable
createdAt
updatedAt
}
}
`;
export const UPDATE_ONE_METADATA_OBJECT = gql`
mutation UpdateOneObjectMetadataItem(
$idToUpdate: ID!
$updatePayload: UpdateObjectInput!
) {
updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {
id
dataSourceId
nameSingular
namePlural
labelSingular
labelPlural
description
icon
isCustom
isActive
createdAt
updatedAt
}
}
`;
export const DELETE_ONE_METADATA_OBJECT = gql`
mutation DeleteOneObjectMetadataItem($idToDelete: ID!) {
deleteOneObject(input: { id: $idToDelete }) {
id
dataSourceId
nameSingular
namePlural
labelSingular
labelPlural
description
icon
isCustom
isActive
createdAt
updatedAt
}
}
`;
export const DELETE_ONE_METADATA_FIELD = gql`
mutation DeleteOneFieldMetadataItem($idToDelete: ID!) {
deleteOneField(input: { id: $idToDelete }) {
id
type
name
label
description
icon
placeholder
isCustom
isActive
isNullable
createdAt
updatedAt
}
}
`;

View File

@ -0,0 +1,56 @@
import { gql } from '@apollo/client';
export const FIND_MANY_METADATA_OBJECTS = gql`
query ObjectMetadataItems {
objects(paging: { first: 1000 }) {
edges {
node {
id
dataSourceId
nameSingular
namePlural
labelSingular
labelPlural
description
icon
isCustom
isActive
createdAt
updatedAt
fields(paging: { first: 1000 }) {
edges {
node {
id
type
name
label
description
icon
placeholder
isCustom
isActive
isNullable
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
}
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
}
}
`;

View File

@ -0,0 +1,9 @@
import { useContext } from 'react';
import { ApolloMetadataClientContext } from '../context/ApolloClientMetadataContext';
export const useApolloMetadataClient = () => {
const apolloMetadataClient = useContext(ApolloMetadataClientContext);
return apolloMetadataClient;
};

View File

@ -0,0 +1,53 @@
import { ApolloClient, useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import { FieldType } from '@/ui/object/field/types/FieldType';
import { FieldMetadataType } from '~/generated/graphql';
import {
CreateOneFieldMetadataItemMutation,
CreateOneFieldMetadataItemMutationVariables,
} from '~/generated-metadata/graphql';
import { CREATE_ONE_METADATA_FIELD } from '../graphql/mutations';
import { FIND_MANY_METADATA_OBJECTS } from '../graphql/queries';
import { useApolloMetadataClient } from './useApolloMetadataClient';
type CreateOneFieldMetadataItemArgs = Omit<
CreateOneFieldMetadataItemMutationVariables['input']['field'],
'type'
> & {
type: FieldType;
};
export const useCreateOneFieldMetadataItem = () => {
const apolloMetadataClient = useApolloMetadataClient();
const [mutate] = useMutation<
CreateOneFieldMetadataItemMutation,
CreateOneFieldMetadataItemMutationVariables
>(CREATE_ONE_METADATA_FIELD, {
client: apolloMetadataClient ?? ({} as ApolloClient<any>),
});
const createOneFieldMetadataItem = async (
input: CreateOneFieldMetadataItemArgs,
) => {
return await mutate({
variables: {
input: {
field: {
...input,
type: input.type as FieldMetadataType, // Todo improve typing once we have aligned backend and frontend
},
},
},
awaitRefetchQueries: true,
refetchQueries: [getOperationName(FIND_MANY_METADATA_OBJECTS) ?? ''],
});
};
return {
createOneFieldMetadataItem,
};
};

View File

@ -0,0 +1,43 @@
import { ApolloClient, useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import {
CreateOneObjectMetadataItemMutation,
CreateOneObjectMetadataItemMutationVariables,
} from '~/generated-metadata/graphql';
import { CREATE_ONE_METADATA_OBJECT } from '../graphql/mutations';
import { FIND_MANY_METADATA_OBJECTS } from '../graphql/queries';
import { useApolloMetadataClient } from './useApolloMetadataClient';
export const useCreateOneObjectRecordMetadataItem = () => {
const apolloMetadataClient = useApolloMetadataClient();
const [mutate] = useMutation<
CreateOneObjectMetadataItemMutation,
CreateOneObjectMetadataItemMutationVariables
>(CREATE_ONE_METADATA_OBJECT, {
client: apolloMetadataClient ?? ({} as ApolloClient<any>),
});
const createOneObjectMetadataItem = async (
input: CreateOneObjectMetadataItemMutationVariables['input']['object'],
) => {
return await mutate({
variables: {
input: {
object: {
...input,
},
},
},
awaitRefetchQueries: true,
refetchQueries: [getOperationName(FIND_MANY_METADATA_OBJECTS) ?? ''],
});
};
return {
createOneObjectMetadataItem,
};
};

View File

@ -0,0 +1,39 @@
import { ApolloClient, useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import {
DeleteOneFieldMetadataItemMutation,
DeleteOneFieldMetadataItemMutationVariables,
} from '~/generated-metadata/graphql';
import { DELETE_ONE_METADATA_FIELD } from '../graphql/mutations';
import { FIND_MANY_METADATA_OBJECTS } from '../graphql/queries';
import { useApolloMetadataClient } from './useApolloMetadataClient';
export const useDeleteOneFieldMetadataItem = () => {
const apolloMetadataClient = useApolloMetadataClient();
const [mutate] = useMutation<
DeleteOneFieldMetadataItemMutation,
DeleteOneFieldMetadataItemMutationVariables
>(DELETE_ONE_METADATA_FIELD, {
client: apolloMetadataClient ?? ({} as ApolloClient<any>),
});
const deleteOneFieldMetadataItem = async (
idToDelete: DeleteOneFieldMetadataItemMutationVariables['idToDelete'],
) => {
return await mutate({
variables: {
idToDelete,
},
awaitRefetchQueries: true,
refetchQueries: [getOperationName(FIND_MANY_METADATA_OBJECTS) ?? ''],
});
};
return {
deleteOneFieldMetadataItem,
};
};

View File

@ -0,0 +1,39 @@
import { ApolloClient, useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import {
DeleteOneObjectMetadataItemMutation,
DeleteOneObjectMetadataItemMutationVariables,
} from '~/generated-metadata/graphql';
import { DELETE_ONE_METADATA_OBJECT } from '../graphql/mutations';
import { FIND_MANY_METADATA_OBJECTS } from '../graphql/queries';
import { useApolloMetadataClient } from './useApolloMetadataClient';
export const useDeleteOneObjectMetadataItem = () => {
const apolloMetadataClient = useApolloMetadataClient();
const [mutate] = useMutation<
DeleteOneObjectMetadataItemMutation,
DeleteOneObjectMetadataItemMutationVariables
>(DELETE_ONE_METADATA_OBJECT, {
client: apolloMetadataClient ?? ({} as ApolloClient<any>),
});
const deleteOneObjectMetadataItem = async (
idToDelete: DeleteOneObjectMetadataItemMutationVariables['idToDelete'],
) => {
return await mutate({
variables: {
idToDelete,
},
awaitRefetchQueries: true,
refetchQueries: [getOperationName(FIND_MANY_METADATA_OBJECTS) ?? ''],
});
};
return {
deleteOneObjectMetadataItem,
};
};

View File

@ -0,0 +1,57 @@
import { MetadataFieldDataType } from '@/settings/data-model/types/ObjectFieldDataType';
import { Field } from '~/generated/graphql';
import { formatFieldMetadataItemInput } from '../utils/formatFieldMetadataItemInput';
import { useCreateOneFieldMetadataItem } from './useCreateOneFieldMetadataItem';
import { useDeleteOneFieldMetadataItem } from './useDeleteOneFieldMetadataItem';
import { useUpdateOneFieldMetadataItem } from './useUpdateOneFieldMetadataItem';
export const useFieldMetadataItem = () => {
const { createOneFieldMetadataItem } = useCreateOneFieldMetadataItem();
const { updateOneFieldMetadataItem } = useUpdateOneFieldMetadataItem();
const { deleteOneFieldMetadataItem } = useDeleteOneFieldMetadataItem();
const createMetadataField = (
input: Pick<Field, 'label' | 'icon' | 'description'> & {
objectMetadataId: string;
type: MetadataFieldDataType;
},
) =>
createOneFieldMetadataItem({
...formatFieldMetadataItemInput(input),
objectMetadataId: input.objectMetadataId,
type: input.type,
});
const editMetadataField = (
input: Pick<Field, 'id' | 'label' | 'icon' | 'description'>,
) =>
updateOneFieldMetadataItem({
fieldMetadataIdToUpdate: input.id,
updatePayload: formatFieldMetadataItemInput(input),
});
const activateMetadataField = (metadataField: Field) =>
updateOneFieldMetadataItem({
fieldMetadataIdToUpdate: metadataField.id,
updatePayload: { isActive: true },
});
const disableMetadataField = (metadataField: Field) =>
updateOneFieldMetadataItem({
fieldMetadataIdToUpdate: metadataField.id,
updatePayload: { isActive: false },
});
const eraseMetadataField = (metadataField: Field) =>
deleteOneFieldMetadataItem(metadataField.id);
return {
activateMetadataField,
createMetadataField,
disableMetadataField,
eraseMetadataField,
editMetadataField,
};
};

View File

@ -0,0 +1,69 @@
import { useMemo } from 'react';
import { useQuery } from '@apollo/client';
import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar';
import {
ObjectMetadataItemsQuery,
ObjectMetadataItemsQueryVariables,
} from '~/generated-metadata/graphql';
import { logError } from '~/utils/logError';
import { FIND_MANY_METADATA_OBJECTS } from '../graphql/queries';
import { formatPagedObjectMetadataItemsToObjectMetadataItems } from '../utils/formatPagedObjectMetadataItemsToObjectMetadataItems';
import { useApolloMetadataClient } from './useApolloMetadataClient';
// TODO: test fetchMore
export const useFindManyObjectMetadataItems = ({
skip,
}: { skip?: boolean } = {}) => {
const apolloMetadataClient = useApolloMetadataClient();
const { enqueueSnackBar } = useSnackBar();
const {
data,
fetchMore: fetchMoreInternal,
loading,
error,
} = useQuery<ObjectMetadataItemsQuery, ObjectMetadataItemsQueryVariables>(
FIND_MANY_METADATA_OBJECTS,
{
client: apolloMetadataClient ?? undefined,
skip: skip || !apolloMetadataClient,
onError: (error) => {
logError('useFindManyObjectMetadataItems error : ' + error);
enqueueSnackBar(
`Error during useFindManyObjectMetadataItems, ${error.message}`,
{
variant: 'error',
},
);
},
onCompleted: () => {},
},
);
const hasMore = data?.objects?.pageInfo?.hasNextPage;
const fetchMore = () =>
fetchMoreInternal({
variables: {
afterCursor: data?.objects?.pageInfo?.endCursor,
},
});
const objectMetadataItems = useMemo(() => {
return formatPagedObjectMetadataItemsToObjectMetadataItems({
pagedObjectMetadataItems: data,
});
}, [data]);
return {
objectMetadataItems,
hasMore,
fetchMore,
loading,
error,
};
};

View File

@ -0,0 +1,126 @@
import { gql } from '@apollo/client';
import { generateCreateOneObjectMutation } from '@/object-record/utils/generateCreateOneObjectMutation';
import { generateDeleteOneObjectMutation } from '@/object-record/utils/generateDeleteOneObjectMutation';
import { generateFindManyCustomObjectsQuery } from '@/object-record/utils/generateFindManyCustomObjectsQuery';
import { generateFindOneCustomObjectQuery } from '@/object-record/utils/generateFindOneCustomObjectQuery';
import { generateUpdateOneObjectMutation } from '@/object-record/utils/generateUpdateOneObjectMutation';
import { useLazyLoadIcons } from '@/ui/input/hooks/useLazyLoadIcons';
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
import { FilterDefinition } from '@/ui/object/object-filter-dropdown/types/FilterDefinition';
import { SortDefinition } from '@/ui/object/object-sort-dropdown/types/SortDefinition';
import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition';
import { ObjectMetadataItemIdentifier } from '../types/ObjectMetadataItemIdentifier';
import { formatFieldMetadataItemAsColumnDefinition } from '../utils/formatFieldMetadataItemAsColumnDefinition';
import { formatFieldMetadataItemAsFilterDefinition } from '../utils/formatFieldMetadataItemAsFilterDefinition';
import { formatFieldMetadataItemAsSortDefinition } from '../utils/formatFieldMetadataItemAsSortDefinition';
import { useFindManyObjectMetadataItems } from './useFindManyObjectMetadataItems';
const EMPTY_QUERY = gql`
query EmptyQuery {
empty
}
`;
const EMPTY_MUTATION = gql`
mutation EmptyMutation {
empty
}
`;
export const useFindOneObjectMetadataItem = ({
objectNamePlural,
objectNameSingular,
skip,
}: ObjectMetadataItemIdentifier & { skip?: boolean }) => {
const { objectMetadataItems, loading } = useFindManyObjectMetadataItems({
skip,
});
const foundObjectMetadataItem = objectMetadataItems.find(
(object) =>
object.namePlural === objectNamePlural ||
object.nameSingular === objectNameSingular,
);
const { icons } = useLazyLoadIcons();
const objectNotFoundInMetadata =
objectMetadataItems.length === 0 ||
(objectMetadataItems.length > 0 && !foundObjectMetadataItem);
const activeFields =
foundObjectMetadataItem?.fields.filter(({ isActive }) => isActive) ?? [];
const columnDefinitions: ColumnDefinition<FieldMetadata>[] =
foundObjectMetadataItem
? activeFields.map((field, index) =>
formatFieldMetadataItemAsColumnDefinition({
position: index,
field,
objectMetadataItem: foundObjectMetadataItem,
icons,
}),
)
: [];
const filterDefinitions: FilterDefinition[] = activeFields.map((field) =>
formatFieldMetadataItemAsFilterDefinition({
field,
icons,
}),
);
const sortDefinitions: SortDefinition[] = activeFields.map((field) =>
formatFieldMetadataItemAsSortDefinition({
field,
icons,
}),
);
const findManyQuery = foundObjectMetadataItem
? generateFindManyCustomObjectsQuery({
objectMetadataItem: foundObjectMetadataItem,
})
: EMPTY_QUERY;
const findOneQuery = foundObjectMetadataItem
? generateFindOneCustomObjectQuery({
objectMetadataItem: foundObjectMetadataItem,
})
: EMPTY_QUERY;
const createOneMutation = foundObjectMetadataItem
? generateCreateOneObjectMutation({
objectMetadataItem: foundObjectMetadataItem,
})
: EMPTY_MUTATION;
const updateOneMutation = foundObjectMetadataItem
? generateUpdateOneObjectMutation({
objectMetadataItem: foundObjectMetadataItem,
})
: EMPTY_MUTATION;
const deleteOneMutation = foundObjectMetadataItem
? generateDeleteOneObjectMutation({
objectMetadataItem: foundObjectMetadataItem,
})
: EMPTY_MUTATION;
return {
foundObjectMetadataItem,
objectNotFoundInMetadata,
columnDefinitions,
filterDefinitions,
sortDefinitions,
findManyQuery,
findOneQuery,
createOneMutation,
updateOneMutation,
deleteOneMutation,
loading,
};
};

View File

@ -0,0 +1,80 @@
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
import { formatObjectMetadataItemInput } from '../utils/formatObjectMetadataItemInput';
import { getObjectSlug } from '../utils/getObjectSlug';
import { useCreateOneObjectRecordMetadataItem } from './useCreateOneObjectMetadataItem';
import { useDeleteOneObjectMetadataItem } from './useDeleteOneObjectMetadataItem';
import { useFindManyObjectMetadataItems } from './useFindManyObjectMetadataItems';
import { useUpdateOneObjectMetadataItem } from './useUpdateOneObjectMetadataItem';
export const useObjectMetadataItemForSettings = () => {
const { objectMetadataItems, loading } = useFindManyObjectMetadataItems();
const activeObjectMetadataItems = objectMetadataItems.filter(
({ isActive }) => isActive,
);
const disabledObjectMetadataItems = objectMetadataItems.filter(
({ isActive }) => !isActive,
);
const findActiveObjectMetadataItemBySlug = (slug: string) =>
activeObjectMetadataItems.find(
(activeObjectMetadataItem) =>
getObjectSlug(activeObjectMetadataItem) === slug,
);
const { createOneObjectMetadataItem } =
useCreateOneObjectRecordMetadataItem();
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
const { deleteOneObjectMetadataItem } = useDeleteOneObjectMetadataItem();
const createObjectMetadataItem = (
input: Pick<
ObjectMetadataItem,
'labelPlural' | 'labelSingular' | 'icon' | 'description'
>,
) => createOneObjectMetadataItem(formatObjectMetadataItemInput(input));
const editObjectMetadataItem = (
input: Pick<
ObjectMetadataItem,
'id' | 'labelPlural' | 'labelSingular' | 'icon' | 'description'
>,
) =>
updateOneObjectMetadataItem({
idToUpdate: input.id,
updatePayload: formatObjectMetadataItemInput(input),
});
const activateObjectMetadataItem = (
objectMetadataItem: Pick<ObjectMetadataItem, 'id'>,
) =>
updateOneObjectMetadataItem({
idToUpdate: objectMetadataItem.id,
updatePayload: { isActive: true },
});
const disableObjectMetadataItem = (
objectMetadataItem: Pick<ObjectMetadataItem, 'id'>,
) =>
updateOneObjectMetadataItem({
idToUpdate: objectMetadataItem.id,
updatePayload: { isActive: false },
});
const eraseObjectMetadataItem = (
objectMetadataItem: Pick<ObjectMetadataItem, 'id'>,
) => deleteOneObjectMetadataItem(objectMetadataItem.id);
return {
activateObjectMetadataItem,
activeObjectMetadataItems,
createObjectMetadataItem,
disabledObjectMetadataItems,
disableObjectMetadataItem,
editObjectMetadataItem,
eraseObjectMetadataItem,
findActiveObjectMetadataItemBySlug,
loading,
};
};

View File

@ -0,0 +1,50 @@
import { useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import {
UpdateOneFieldMetadataItemMutation,
UpdateOneFieldMetadataItemMutationVariables,
} from '~/generated-metadata/graphql';
import { UPDATE_ONE_METADATA_FIELD } from '../graphql/mutations';
import { FIND_MANY_METADATA_OBJECTS } from '../graphql/queries';
import { useApolloMetadataClient } from './useApolloMetadataClient';
export const useUpdateOneFieldMetadataItem = () => {
const apolloMetadataClient = useApolloMetadataClient();
const [mutate] = useMutation<
UpdateOneFieldMetadataItemMutation,
UpdateOneFieldMetadataItemMutationVariables
>(UPDATE_ONE_METADATA_FIELD, {
client: apolloMetadataClient ?? undefined,
});
const updateOneFieldMetadataItem = async ({
fieldMetadataIdToUpdate,
updatePayload,
}: {
fieldMetadataIdToUpdate: UpdateOneFieldMetadataItemMutationVariables['idToUpdate'];
updatePayload: Pick<
UpdateOneFieldMetadataItemMutationVariables['updatePayload'],
'description' | 'icon' | 'isActive' | 'label' | 'name'
>;
}) => {
return await mutate({
variables: {
idToUpdate: fieldMetadataIdToUpdate,
updatePayload: {
...updatePayload,
label: updatePayload.label ?? undefined,
},
},
awaitRefetchQueries: true,
refetchQueries: [getOperationName(FIND_MANY_METADATA_OBJECTS) ?? ''],
});
};
return {
updateOneFieldMetadataItem,
};
};

View File

@ -0,0 +1,54 @@
import { useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import {
UpdateOneObjectMetadataItemMutation,
UpdateOneObjectMetadataItemMutationVariables,
} from '~/generated-metadata/graphql';
import { UPDATE_ONE_METADATA_OBJECT } from '../graphql/mutations';
import { FIND_MANY_METADATA_OBJECTS } from '../graphql/queries';
import { useApolloMetadataClient } from './useApolloMetadataClient';
// TODO: Slice the Apollo store synchronously in the update function instead of subscribing, so we can use update after read in the same function call
export const useUpdateOneObjectMetadataItem = () => {
const apolloClientMetadata = useApolloMetadataClient();
const [mutate] = useMutation<
UpdateOneObjectMetadataItemMutation,
UpdateOneObjectMetadataItemMutationVariables
>(UPDATE_ONE_METADATA_OBJECT, {
client: apolloClientMetadata ?? undefined,
});
const updateOneObjectMetadataItem = async ({
idToUpdate,
updatePayload,
}: {
idToUpdate: UpdateOneObjectMetadataItemMutationVariables['idToUpdate'];
updatePayload: Pick<
UpdateOneObjectMetadataItemMutationVariables['updatePayload'],
| 'description'
| 'icon'
| 'isActive'
| 'labelPlural'
| 'labelSingular'
| 'namePlural'
| 'nameSingular'
>;
}) => {
return await mutate({
variables: {
idToUpdate,
updatePayload,
},
awaitRefetchQueries: true,
refetchQueries: [getOperationName(FIND_MANY_METADATA_OBJECTS) ?? ''],
});
};
return {
updateOneObjectMetadataItem,
};
};

View File

@ -0,0 +1,11 @@
import { atom } from 'recoil';
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
/**
* @deprecated Use `useFindManyObjectMetadataItems` instead.
*/
export const objectMetadataItemsState = atom<ObjectMetadataItem[]>({
key: 'objectMetadataItemsState',
default: [],
});

View File

@ -0,0 +1,12 @@
import { selector } from 'recoil';
import { ObjectMetadataItem } from '../../types/ObjectMetadataItem';
import { objectMetadataItemsState } from '../objectMetadataItemsState';
export const activeObjectMetadataItemsSelector = selector<ObjectMetadataItem[]>(
{
key: 'activeObjectMetadataItemsSelector',
get: ({ get }) =>
get(objectMetadataItemsState).filter(({ isActive }) => isActive),
},
);

View File

@ -0,0 +1,12 @@
import { selector } from 'recoil';
import { ObjectMetadataItem } from '../../types/ObjectMetadataItem';
import { objectMetadataItemsState } from '../objectMetadataItemsState';
export const disabledObjectMetadataItemsSelector = selector<
ObjectMetadataItem[]
>({
key: 'disabledObjectMetadataItemsSelector',
get: ({ get }) =>
get(objectMetadataItemsState).filter(({ isActive }) => !isActive),
});

View File

@ -0,0 +1,8 @@
import { Field, Object as GeneratedObject } from '~/generated-metadata/graphql';
export type ObjectMetadataItem = Omit<
GeneratedObject,
'fields' | 'dataSourceId'
> & {
fields: Field[];
};

View File

@ -0,0 +1,4 @@
export type ObjectMetadataItemIdentifier = {
objectNamePlural?: string;
objectNameSingular?: string;
};

View File

@ -0,0 +1,33 @@
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition';
import { Field } from '~/generated-metadata/graphql';
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
import { parseFieldType } from './parseFieldType';
export const formatFieldMetadataItemAsColumnDefinition = ({
position,
field,
objectMetadataItem,
icons,
}: {
position: number;
field: Field;
objectMetadataItem: Omit<ObjectMetadataItem, 'fields'>;
icons: Record<string, IconComponent>;
}): ColumnDefinition<FieldMetadata> => ({
position,
fieldMetadataId: field.id,
label: field.label,
size: 100,
type: parseFieldType(field.type),
metadata: {
fieldName: field.name,
placeHolder: field.label,
},
Icon: icons[field.icon ?? 'Icon123'],
isVisible: true,
basePathToShowPage: `/object/${objectMetadataItem.nameSingular}/`,
});

View File

@ -0,0 +1,16 @@
import { FilterDefinition } from '@/ui/object/object-filter-dropdown/types/FilterDefinition';
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
export const formatFieldMetadataItemAsFilterDefinition = ({
field,
icons,
}: {
field: ObjectMetadataItem['fields'][0];
icons: Record<string, any>;
}): FilterDefinition => ({
fieldMetadataId: field.id,
label: field.label,
Icon: icons[field.icon ?? 'Icon123'],
type: 'TEXT',
});

View File

@ -0,0 +1,15 @@
import { SortDefinition } from '@/ui/object/object-sort-dropdown/types/SortDefinition';
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
export const formatFieldMetadataItemAsSortDefinition = ({
field,
icons,
}: {
field: ObjectMetadataItem['fields'][0];
icons: Record<string, any>;
}): SortDefinition => ({
fieldMetadataId: field.id,
label: field.label,
Icon: icons[field.icon ?? 'Icon123'],
});

View File

@ -0,0 +1,12 @@
import toCamelCase from 'lodash.camelcase';
import { Field } from '~/generated-metadata/graphql';
export const formatFieldMetadataItemInput = (
input: Pick<Field, 'label' | 'icon' | 'description'>,
) => ({
description: input.description?.trim() ?? null,
icon: input.icon,
label: input.label.trim(),
name: toCamelCase(input.label.trim()),
});

View File

@ -0,0 +1,17 @@
import toCamelCase from 'lodash.camelcase';
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
export const formatObjectMetadataItemInput = (
input: Pick<
ObjectMetadataItem,
'labelPlural' | 'labelSingular' | 'icon' | 'description'
>,
) => ({
description: input.description?.trim() ?? null,
icon: input.icon,
labelPlural: input.labelPlural.trim(),
labelSingular: input.labelSingular.trim(),
namePlural: toCamelCase(input.labelPlural.trim()),
nameSingular: toCamelCase(input.labelSingular.trim()),
});

View File

@ -0,0 +1,17 @@
import { ObjectMetadataItemsQuery } from '~/generated-metadata/graphql';
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
export const formatPagedObjectMetadataItemsToObjectMetadataItems = ({
pagedObjectMetadataItems: pagedObjectMetadataItems,
}: {
pagedObjectMetadataItems: ObjectMetadataItemsQuery | undefined;
}) => {
const formattedObjects: ObjectMetadataItem[] =
pagedObjectMetadataItems?.objects.edges.map((object) => ({
...object.node,
fields: object.node.fields.edges.map((field) => field.node),
})) ?? [];
return formattedObjects;
};

View File

@ -0,0 +1,6 @@
import toKebabCase from 'lodash.kebabcase';
import { Field } from '~/generated-metadata/graphql';
export const getFieldSlug = (metadataField: Pick<Field, 'label'>) =>
toKebabCase(metadataField.label);

View File

@ -0,0 +1,7 @@
import toKebabCase from 'lodash.kebabcase';
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
export const getObjectSlug = (
objectMetadataItem: Pick<ObjectMetadataItem, 'labelPlural'>,
) => toKebabCase(objectMetadataItem.labelPlural);

View File

@ -0,0 +1,44 @@
import { FieldType } from '@/ui/object/field/types/FieldType';
import { Field } from '~/generated/graphql';
export const mapFieldMetadataToGraphQLQuery = (field: Field) => {
// TODO: parse
const fieldType = field.type as FieldType;
const fieldIsSimpleValue = (
[
'TEXT',
'PHONE',
'DATE',
'EMAIL',
'NUMBER',
'BOOLEAN',
'DATE',
] as FieldType[]
).includes(fieldType);
const fieldIsURL = fieldType === 'URL' || fieldType === 'URL_V2';
const fieldIsMoneyAmount =
fieldType === 'MONEY' || fieldType === 'MONEY_AMOUNT_V2';
if (fieldIsSimpleValue) {
return field.name;
} else if (fieldIsURL) {
return `
${field.name}
{
text
link
}
`;
} else if (fieldIsMoneyAmount) {
return `
${field.name}
{
amount
currency
}
`;
}
};

View File

@ -0,0 +1,14 @@
import { FieldType } from '@/ui/object/field/types/FieldType';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export const parseFieldType = (fieldType: FieldMetadataType): FieldType => {
if (fieldType === FieldMetadataType.Url) {
return 'URL_V2';
}
if (fieldType === FieldMetadataType.Money) {
return 'MONEY_AMOUNT_V2';
}
return fieldType as FieldType;
};

View File

@ -0,0 +1,4 @@
const metadataLabelValidationPattern = /^[a-zA-Z][a-zA-Z0-9 ]*$/;
export const validateMetadataLabel = (value: string) =>
!!value.match(metadataLabelValidationPattern);