mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-25 21:13:01 +03:00
Setup relations for remote objects (#5149)
New strategy: - add settings field on FieldMetadata. Contains a boolean isIdField and for numbers, a precision - if idField, the graphql scalar returned will be a GraphQL id. This will allow the app to work even for ids that are not uuid - remove globals dateScalar and numberScalar modes. These were not used - set limit as Integer - check manually in query runner mutations that we send a valid id Todo left: - remove WorkspaceBuildSchemaOptions since this is not used anymore. Will do in another PR --------- Co-authored-by: Thomas Trompette <thomast@twenty.com> Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
parent
dc576d0818
commit
224c8d361b
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -861,6 +861,7 @@ export type Field = {
|
|||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
options?: Maybe<Scalars['JSON']>;
|
options?: Maybe<Scalars['JSON']>;
|
||||||
relationDefinition?: Maybe<RelationDefinition>;
|
relationDefinition?: Maybe<RelationDefinition>;
|
||||||
|
settings?: Maybe<Scalars['JSON']>;
|
||||||
toRelationMetadata?: Maybe<Relation>;
|
toRelationMetadata?: Maybe<Relation>;
|
||||||
type: FieldMetadataType;
|
type: FieldMetadataType;
|
||||||
updatedAt: Scalars['DateTime'];
|
updatedAt: Scalars['DateTime'];
|
||||||
|
@ -51,7 +51,7 @@ const mocks: MockedResponse[] = [
|
|||||||
$filter: ActivityTargetFilterInput
|
$filter: ActivityTargetFilterInput
|
||||||
$orderBy: ActivityTargetOrderByInput
|
$orderBy: ActivityTargetOrderByInput
|
||||||
$lastCursor: String
|
$lastCursor: String
|
||||||
$limit: Float
|
$limit: Int
|
||||||
) {
|
) {
|
||||||
activityTargets(
|
activityTargets(
|
||||||
filter: $filter
|
filter: $filter
|
||||||
@ -105,7 +105,7 @@ const mocks: MockedResponse[] = [
|
|||||||
$filter: ActivityFilterInput
|
$filter: ActivityFilterInput
|
||||||
$orderBy: ActivityOrderByInput
|
$orderBy: ActivityOrderByInput
|
||||||
$lastCursor: String
|
$lastCursor: String
|
||||||
$limit: Float
|
$limit: Int
|
||||||
) {
|
) {
|
||||||
activities(
|
activities(
|
||||||
filter: $filter
|
filter: $filter
|
||||||
|
@ -36,7 +36,7 @@ const mocks: MockedResponse[] = [
|
|||||||
$filter: ActivityTargetFilterInput
|
$filter: ActivityTargetFilterInput
|
||||||
$orderBy: ActivityTargetOrderByInput
|
$orderBy: ActivityTargetOrderByInput
|
||||||
$lastCursor: String
|
$lastCursor: String
|
||||||
$limit: Float
|
$limit: Int
|
||||||
) {
|
) {
|
||||||
activityTargets(
|
activityTargets(
|
||||||
filter: $filter
|
filter: $filter
|
||||||
|
@ -16,7 +16,7 @@ const mocks: MockedResponse[] = [
|
|||||||
{
|
{
|
||||||
request: {
|
request: {
|
||||||
query: gql`
|
query: gql`
|
||||||
query FindOneWorkspaceMember($objectRecordId: UUID!) {
|
query FindOneWorkspaceMember($objectRecordId: ID!) {
|
||||||
workspaceMember(filter: { id: { eq: $objectRecordId } }) {
|
workspaceMember(filter: { id: { eq: $objectRecordId } }) {
|
||||||
__typename
|
__typename
|
||||||
colorScheme
|
colorScheme
|
||||||
|
@ -17,7 +17,7 @@ const mocks: MockedResponse[] = [
|
|||||||
request: {
|
request: {
|
||||||
query: gql`
|
query: gql`
|
||||||
mutation UpdateOneActivity(
|
mutation UpdateOneActivity(
|
||||||
$idToUpdate: UUID!
|
$idToUpdate: ID!
|
||||||
$input: ActivityUpdateInput!
|
$input: ActivityUpdateInput!
|
||||||
) {
|
) {
|
||||||
updateActivity(id: $idToUpdate, data: $input) {
|
updateActivity(id: $idToUpdate, data: $input) {
|
||||||
|
@ -177,7 +177,7 @@ export const mocks = [
|
|||||||
{
|
{
|
||||||
request: {
|
request: {
|
||||||
query: gql`
|
query: gql`
|
||||||
mutation DeleteOneFavorite($idToDelete: UUID!) {
|
mutation DeleteOneFavorite($idToDelete: ID!) {
|
||||||
deleteFavorite(id: $idToDelete) {
|
deleteFavorite(id: $idToDelete) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
@ -197,7 +197,7 @@ export const mocks = [
|
|||||||
request: {
|
request: {
|
||||||
query: gql`
|
query: gql`
|
||||||
mutation UpdateOneFavorite(
|
mutation UpdateOneFavorite(
|
||||||
$idToUpdate: UUID!
|
$idToUpdate: ID!
|
||||||
$input: FavoriteUpdateInput!
|
$input: FavoriteUpdateInput!
|
||||||
) {
|
) {
|
||||||
updateFavorite(id: $idToUpdate, data: $input) {
|
updateFavorite(id: $idToUpdate, data: $input) {
|
||||||
|
@ -48,6 +48,7 @@ export const FIND_MANY_OBJECT_METADATA_ITEMS = gql`
|
|||||||
nameSingular
|
nameSingular
|
||||||
namePlural
|
namePlural
|
||||||
isSystem
|
isSystem
|
||||||
|
isRemote
|
||||||
}
|
}
|
||||||
toFieldMetadataId
|
toFieldMetadataId
|
||||||
}
|
}
|
||||||
@ -60,6 +61,7 @@ export const FIND_MANY_OBJECT_METADATA_ITEMS = gql`
|
|||||||
nameSingular
|
nameSingular
|
||||||
namePlural
|
namePlural
|
||||||
isSystem
|
isSystem
|
||||||
|
isRemote
|
||||||
}
|
}
|
||||||
fromFieldMetadataId
|
fromFieldMetadataId
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ export const findManyViewsQuery = gql`
|
|||||||
$filter: ViewFilterInput
|
$filter: ViewFilterInput
|
||||||
$orderBy: ViewOrderByInput
|
$orderBy: ViewOrderByInput
|
||||||
$lastCursor: String
|
$lastCursor: String
|
||||||
$limit: Float
|
$limit: Int
|
||||||
) {
|
) {
|
||||||
views(
|
views(
|
||||||
filter: $filter
|
filter: $filter
|
||||||
|
@ -21,6 +21,7 @@ export type FieldMetadataItem = Omit<
|
|||||||
| 'toRelationMetadata'
|
| 'toRelationMetadata'
|
||||||
| 'defaultValue'
|
| 'defaultValue'
|
||||||
| 'options'
|
| 'options'
|
||||||
|
| 'settings'
|
||||||
| 'relationDefinition'
|
| 'relationDefinition'
|
||||||
> & {
|
> & {
|
||||||
__typename?: string;
|
__typename?: string;
|
||||||
@ -28,7 +29,7 @@ export type FieldMetadataItem = Omit<
|
|||||||
| (Pick<Relation, 'id' | 'toFieldMetadataId' | 'relationType'> & {
|
| (Pick<Relation, 'id' | 'toFieldMetadataId' | 'relationType'> & {
|
||||||
toObjectMetadata: Pick<
|
toObjectMetadata: Pick<
|
||||||
Relation['toObjectMetadata'],
|
Relation['toObjectMetadata'],
|
||||||
'id' | 'nameSingular' | 'namePlural' | 'isSystem'
|
'id' | 'nameSingular' | 'namePlural' | 'isSystem' | 'isRemote'
|
||||||
>;
|
>;
|
||||||
})
|
})
|
||||||
| null;
|
| null;
|
||||||
@ -36,7 +37,7 @@ export type FieldMetadataItem = Omit<
|
|||||||
| (Pick<Relation, 'id' | 'fromFieldMetadataId' | 'relationType'> & {
|
| (Pick<Relation, 'id' | 'fromFieldMetadataId' | 'relationType'> & {
|
||||||
fromObjectMetadata: Pick<
|
fromObjectMetadata: Pick<
|
||||||
Relation['fromObjectMetadata'],
|
Relation['fromObjectMetadata'],
|
||||||
'id' | 'nameSingular' | 'namePlural' | 'isSystem'
|
'id' | 'nameSingular' | 'namePlural' | 'isSystem' | 'isRemote'
|
||||||
>;
|
>;
|
||||||
})
|
})
|
||||||
| null;
|
| null;
|
||||||
|
@ -2,10 +2,15 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
|||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
|
||||||
export const isObjectMetadataAvailableForRelation = (
|
export const isObjectMetadataAvailableForRelation = (
|
||||||
objectMetadataItem: Pick<ObjectMetadataItem, 'isSystem' | 'nameSingular'>,
|
objectMetadataItem: Pick<
|
||||||
|
ObjectMetadataItem,
|
||||||
|
'isSystem' | 'nameSingular' | 'isRemote'
|
||||||
|
>,
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
!objectMetadataItem.isSystem ||
|
(!objectMetadataItem.isSystem ||
|
||||||
objectMetadataItem.nameSingular === CoreObjectNameSingular.WorkspaceMember
|
objectMetadataItem.nameSingular ===
|
||||||
|
CoreObjectNameSingular.WorkspaceMember) &&
|
||||||
|
!objectMetadataItem.isRemote
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { useContext } from 'react';
|
|
||||||
import { EntityChip, EntityChipVariant } from 'twenty-ui';
|
import { EntityChip, EntityChipVariant } from 'twenty-ui';
|
||||||
|
|
||||||
import { useMapToObjectRecordIdentifier } from '@/object-metadata/hooks/useMapToObjectRecordIdentifier';
|
import { useMapToObjectRecordIdentifier } from '@/object-metadata/hooks/useMapToObjectRecordIdentifier';
|
||||||
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
|
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
|
||||||
|
|
||||||
@ -25,16 +23,8 @@ export const RecordChip = ({
|
|||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Will only exists if the chip is inside a record table.
|
|
||||||
// This is temporary until we have the show page for remote objects.
|
|
||||||
const { isReadOnly } = useContext(RecordTableRowContext);
|
|
||||||
|
|
||||||
const objectRecordIdentifier = mapToObjectRecordIdentifier(record);
|
const objectRecordIdentifier = mapToObjectRecordIdentifier(record);
|
||||||
|
|
||||||
const linkToEntity = isReadOnly
|
|
||||||
? undefined
|
|
||||||
: objectRecordIdentifier.linkToShowPage;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityChip
|
<EntityChip
|
||||||
entityId={record.id}
|
entityId={record.id}
|
||||||
@ -43,7 +33,7 @@ export const RecordChip = ({
|
|||||||
avatarUrl={
|
avatarUrl={
|
||||||
getImageAbsoluteURIOrBase64(objectRecordIdentifier.avatarUrl) || ''
|
getImageAbsoluteURIOrBase64(objectRecordIdentifier.avatarUrl) || ''
|
||||||
}
|
}
|
||||||
linkToEntity={linkToEntity}
|
linkToEntity={objectRecordIdentifier.linkToShowPage}
|
||||||
maxWidth={maxWidth}
|
maxWidth={maxWidth}
|
||||||
className={className}
|
className={className}
|
||||||
variant={variant}
|
variant={variant}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
export const query = gql`
|
export const query = gql`
|
||||||
mutation DeleteOnePerson($idToDelete: UUID!) {
|
mutation DeleteOnePerson($idToDelete: ID!) {
|
||||||
deletePerson(id: $idToDelete) {
|
deletePerson(id: $idToDelete) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { gql } from '@apollo/client';
|
|||||||
export { responseData } from './useUpdateOneRecord';
|
export { responseData } from './useUpdateOneRecord';
|
||||||
|
|
||||||
export const query = gql`
|
export const query = gql`
|
||||||
mutation ExecuteQuickActionOnOnePerson($idToExecuteQuickActionOn: UUID!) {
|
mutation ExecuteQuickActionOnOnePerson($idToExecuteQuickActionOn: ID!) {
|
||||||
executeQuickActionOnPerson(id: $idToExecuteQuickActionOn) {
|
executeQuickActionOnPerson(id: $idToExecuteQuickActionOn) {
|
||||||
__typename
|
__typename
|
||||||
xLink {
|
xLink {
|
||||||
|
@ -5,7 +5,7 @@ export const query = gql`
|
|||||||
$filter: PersonFilterInput
|
$filter: PersonFilterInput
|
||||||
$orderBy: PersonOrderByInput
|
$orderBy: PersonOrderByInput
|
||||||
$lastCursor: String
|
$lastCursor: String
|
||||||
$limit: Float
|
$limit: Int
|
||||||
) {
|
) {
|
||||||
people(
|
people(
|
||||||
filter: $filter
|
filter: $filter
|
||||||
|
@ -3,7 +3,7 @@ import { gql } from '@apollo/client';
|
|||||||
import { responseData as person } from './useUpdateOneRecord';
|
import { responseData as person } from './useUpdateOneRecord';
|
||||||
|
|
||||||
export const query = gql`
|
export const query = gql`
|
||||||
query FindOnePerson($objectRecordId: UUID!) {
|
query FindOnePerson($objectRecordId: ID!) {
|
||||||
person(filter: { id: { eq: $objectRecordId } }) {
|
person(filter: { id: { eq: $objectRecordId } }) {
|
||||||
__typename
|
__typename
|
||||||
xLink {
|
xLink {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
export const query = gql`
|
export const query = gql`
|
||||||
mutation UpdateOnePerson($idToUpdate: UUID!, $input: PersonUpdateInput!) {
|
mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) {
|
||||||
updatePerson(id: $idToUpdate, data: $input) {
|
updatePerson(id: $idToUpdate, data: $input) {
|
||||||
__typename
|
__typename
|
||||||
xLink {
|
xLink {
|
||||||
|
@ -26,7 +26,7 @@ export const useDeleteOneRecordMutation = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const deleteOneRecordMutation = gql`
|
const deleteOneRecordMutation = gql`
|
||||||
mutation DeleteOne${capitalizedObjectName}($idToDelete: UUID!) {
|
mutation DeleteOne${capitalizedObjectName}($idToDelete: ID!) {
|
||||||
${mutationResponseField}(id: $idToDelete) {
|
${mutationResponseField}(id: $idToDelete) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ export const useExecuteQuickActionOnOneRecordMutation = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const executeQuickActionOnOneRecordMutation = gql`
|
const executeQuickActionOnOneRecordMutation = gql`
|
||||||
mutation ExecuteQuickActionOnOne${capitalizedObjectName}($idToExecuteQuickActionOn: UUID!) {
|
mutation ExecuteQuickActionOnOne${capitalizedObjectName}($idToExecuteQuickActionOn: ID!) {
|
||||||
${graphQLFieldForExecuteQuickActionOnOneRecordMutation}(id: $idToExecuteQuickActionOn) ${mapObjectMetadataToGraphQLQuery(
|
${graphQLFieldForExecuteQuickActionOnOneRecordMutation}(id: $idToExecuteQuickActionOn) ${mapObjectMetadataToGraphQLQuery(
|
||||||
{
|
{
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
@ -23,7 +23,7 @@ export const useFindDuplicateRecordsQuery = ({
|
|||||||
const findDuplicateRecordsQuery = gql`
|
const findDuplicateRecordsQuery = gql`
|
||||||
query FindDuplicate${capitalize(
|
query FindDuplicate${capitalize(
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataItem.nameSingular,
|
||||||
)}($id: UUID) {
|
)}($id: ID!) {
|
||||||
${getFindDuplicateRecordsQueryResponseField(
|
${getFindDuplicateRecordsQueryResponseField(
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataItem.nameSingular,
|
||||||
)}(id: $id) {
|
)}(id: $id) {
|
||||||
|
@ -22,7 +22,7 @@ export const useFindOneRecordQuery = ({
|
|||||||
const findOneRecordQuery = gql`
|
const findOneRecordQuery = gql`
|
||||||
query FindOne${capitalize(
|
query FindOne${capitalize(
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataItem.nameSingular,
|
||||||
)}($objectRecordId: UUID!) {
|
)}($objectRecordId: ID!) {
|
||||||
${objectMetadataItem.nameSingular}(filter: {
|
${objectMetadataItem.nameSingular}(filter: {
|
||||||
id: {
|
id: {
|
||||||
eq: $objectRecordId
|
eq: $objectRecordId
|
||||||
@ -32,7 +32,7 @@ export const useFindOneRecordQuery = ({
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
depth,
|
depth,
|
||||||
})}
|
})}
|
||||||
}
|
},
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -35,7 +35,7 @@ export const useUpdateOneRecordMutation = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const updateOneRecordMutation = gql`
|
const updateOneRecordMutation = gql`
|
||||||
mutation UpdateOne${capitalizedObjectName}($idToUpdate: UUID!, $input: ${capitalizedObjectName}UpdateInput!) {
|
mutation UpdateOne${capitalizedObjectName}($idToUpdate: ID!, $input: ${capitalizedObjectName}UpdateInput!) {
|
||||||
${mutationResponseField}(id: $idToUpdate, data: $input) ${mapObjectMetadataToGraphQLQuery(
|
${mutationResponseField}(id: $idToUpdate, data: $input) ${mapObjectMetadataToGraphQLQuery(
|
||||||
{
|
{
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
@ -47,7 +47,7 @@ export const useGenerateFindManyRecordsForMultipleMetadataItemsQuery = ({
|
|||||||
const limitPerMetadataItemArray = capitalizedObjectNameSingulars
|
const limitPerMetadataItemArray = capitalizedObjectNameSingulars
|
||||||
.map(
|
.map(
|
||||||
(capitalizedObjectNameSingular) =>
|
(capitalizedObjectNameSingular) =>
|
||||||
`$limit${capitalizedObjectNameSingular}: Float`,
|
`$limit${capitalizedObjectNameSingular}: Int`,
|
||||||
)
|
)
|
||||||
.join(', ');
|
.join(', ');
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
|
|||||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
|
|
||||||
const query = gql`
|
const query = gql`
|
||||||
mutation UpdateOnePerson($idToUpdate: UUID!, $input: PersonUpdateInput!) {
|
mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) {
|
||||||
updatePerson(id: $idToUpdate, data: $input) {
|
updatePerson(id: $idToUpdate, data: $input) {
|
||||||
__typename
|
__typename
|
||||||
xLink {
|
xLink {
|
||||||
|
@ -21,7 +21,7 @@ const mocks: MockedResponse[] = [
|
|||||||
request: {
|
request: {
|
||||||
query: gql`
|
query: gql`
|
||||||
mutation UpdateOneCompany(
|
mutation UpdateOneCompany(
|
||||||
$idToUpdate: UUID!
|
$idToUpdate: ID!
|
||||||
$input: CompanyUpdateInput!
|
$input: CompanyUpdateInput!
|
||||||
) {
|
) {
|
||||||
updateCompany(id: $idToUpdate, data: $input) {
|
updateCompany(id: $idToUpdate, data: $input) {
|
||||||
|
@ -8,7 +8,7 @@ import { isFieldDateValue } from '@/object-record/record-field/types/guards/isFi
|
|||||||
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
|
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
|
||||||
import { isFieldFullNameValue } from '@/object-record/record-field/types/guards/isFieldFullNameValue';
|
import { isFieldFullNameValue } from '@/object-record/record-field/types/guards/isFieldFullNameValue';
|
||||||
import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect';
|
import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect';
|
||||||
import { isFieldMultiSelectValue } from '@/object-record/record-field/types/guards/isFieldMultiSelectValue';
|
import { isFieldMultiSelectValue } from '@/object-record/record-field/types/guards/isFieldMultiSelectValue.ts';
|
||||||
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
|
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
|
||||||
import { isFieldRawJsonValue } from '@/object-record/record-field/types/guards/isFieldRawJsonValue';
|
import { isFieldRawJsonValue } from '@/object-record/record-field/types/guards/isFieldRawJsonValue';
|
||||||
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
||||||
|
@ -13,7 +13,7 @@ const query = gql`
|
|||||||
$filterNameSingular: NameSingularFilterInput
|
$filterNameSingular: NameSingularFilterInput
|
||||||
$orderByNameSingular: NameSingularOrderByInput
|
$orderByNameSingular: NameSingularOrderByInput
|
||||||
$lastCursorNameSingular: String
|
$lastCursorNameSingular: String
|
||||||
$limitNameSingular: Float
|
$limitNameSingular: Int
|
||||||
) {
|
) {
|
||||||
namePlural(
|
namePlural(
|
||||||
filter: $filterNameSingular
|
filter: $filterNameSingular
|
||||||
|
@ -24,7 +24,7 @@ query FindMany${capitalize(
|
|||||||
objectMetadataItem.nameSingular,
|
objectMetadataItem.nameSingular,
|
||||||
)}FilterInput, $orderBy: ${capitalize(
|
)}FilterInput, $orderBy: ${capitalize(
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataItem.nameSingular,
|
||||||
)}OrderByInput, $lastCursor: String, $limit: Float) {
|
)}OrderByInput, $lastCursor: String, $limit: Int) {
|
||||||
${
|
${
|
||||||
objectMetadataItem.namePlural
|
objectMetadataItem.namePlural
|
||||||
}(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){
|
}(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){
|
||||||
|
@ -5,7 +5,7 @@ export const query = gql`
|
|||||||
$filter: PersonFilterInput
|
$filter: PersonFilterInput
|
||||||
$orderBy: PersonOrderByInput
|
$orderBy: PersonOrderByInput
|
||||||
$lastCursor: String
|
$lastCursor: String
|
||||||
$limit: Float = 60
|
$limit: Int = 60
|
||||||
) {
|
) {
|
||||||
people(
|
people(
|
||||||
filter: $filter
|
filter: $filter
|
||||||
|
@ -65,7 +65,12 @@ export const ShowPageRightContainer = ({
|
|||||||
const { activeTabIdState } = useTabList(TAB_LIST_COMPONENT_ID);
|
const { activeTabIdState } = useTabList(TAB_LIST_COMPONENT_ID);
|
||||||
const activeTabId = useRecoilValue(activeTabIdState);
|
const activeTabId = useRecoilValue(activeTabIdState);
|
||||||
|
|
||||||
const shouldDisplayCalendarTab = useIsFeatureEnabled('IS_CALENDAR_ENABLED');
|
const shouldDisplayCalendarTab =
|
||||||
|
useIsFeatureEnabled('IS_CALENDAR_ENABLED') &&
|
||||||
|
(targetableObject.targetObjectNameSingular ===
|
||||||
|
CoreObjectNameSingular.Company ||
|
||||||
|
targetableObject.targetObjectNameSingular ===
|
||||||
|
CoreObjectNameSingular.Person);
|
||||||
const shouldDisplayLogTab = useIsFeatureEnabled('IS_EVENT_OBJECT_ENABLED');
|
const shouldDisplayLogTab = useIsFeatureEnabled('IS_EVENT_OBJECT_ENABLED');
|
||||||
|
|
||||||
const shouldDisplayEmailsTab =
|
const shouldDisplayEmailsTab =
|
||||||
|
@ -96,11 +96,6 @@ export const RecordShowPage = () => {
|
|||||||
? `${pageName} - ${capitalize(objectNameSingular)}`
|
? `${pageName} - ${capitalize(objectNameSingular)}`
|
||||||
: capitalize(objectNameSingular);
|
: capitalize(objectNameSingular);
|
||||||
|
|
||||||
// Temporarily since we don't have relations for remote objects yet
|
|
||||||
if (objectMetadataItem.isRemote) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<PageTitle title={pageTitle} />
|
<PageTitle title={pageTitle} />
|
||||||
|
@ -88,6 +88,8 @@ export const SettingsObjectDetail = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const shouldDisplayAddFieldButton = !activeObjectMetadataItem.isRemote;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||||
<SettingsPageContainer>
|
<SettingsPageContainer>
|
||||||
@ -207,6 +209,7 @@ export const SettingsObjectDetail = () => {
|
|||||||
</TableSection>
|
</TableSection>
|
||||||
)}
|
)}
|
||||||
</Table>
|
</Table>
|
||||||
|
{shouldDisplayAddFieldButton && (
|
||||||
<StyledDiv>
|
<StyledDiv>
|
||||||
<Button
|
<Button
|
||||||
Icon={IconPlus}
|
Icon={IconPlus}
|
||||||
@ -222,6 +225,7 @@ export const SettingsObjectDetail = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</StyledDiv>
|
</StyledDiv>
|
||||||
|
)}
|
||||||
</Section>
|
</Section>
|
||||||
</SettingsPageContainer>
|
</SettingsPageContainer>
|
||||||
</SubMenuTopBarContainer>
|
</SubMenuTopBarContainer>
|
||||||
|
@ -9,6 +9,7 @@ export const mockedClientConfig: ClientConfig = {
|
|||||||
google: true,
|
google: true,
|
||||||
password: true,
|
password: true,
|
||||||
magicLink: false,
|
magicLink: false,
|
||||||
|
microsoft: false,
|
||||||
__typename: 'AuthProviders',
|
__typename: 'AuthProviders',
|
||||||
},
|
},
|
||||||
telemetry: {
|
telemetry: {
|
||||||
|
@ -63,6 +63,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
|
|||||||
nameSingular: 'person',
|
nameSingular: 'person',
|
||||||
namePlural: 'people',
|
namePlural: 'people',
|
||||||
isSystem: false,
|
isSystem: false,
|
||||||
|
isRemote: false,
|
||||||
},
|
},
|
||||||
toFieldMetadataId: 'c756f6ff-8c00-4fe5-a923-c6cfc7b1ac4a',
|
toFieldMetadataId: 'c756f6ff-8c00-4fe5-a923-c6cfc7b1ac4a',
|
||||||
},
|
},
|
||||||
@ -91,6 +92,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
|
|||||||
nameSingular: 'opportunity',
|
nameSingular: 'opportunity',
|
||||||
namePlural: 'opportunities',
|
namePlural: 'opportunities',
|
||||||
isSystem: false,
|
isSystem: false,
|
||||||
|
isRemote: false,
|
||||||
},
|
},
|
||||||
toFieldMetadataId: '00468e2a-a601-4635-ae9c-a9bb826cc860',
|
toFieldMetadataId: '00468e2a-a601-4635-ae9c-a9bb826cc860',
|
||||||
},
|
},
|
||||||
@ -119,6 +121,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
|
|||||||
nameSingular: 'activityTarget',
|
nameSingular: 'activityTarget',
|
||||||
namePlural: 'activityTargets',
|
namePlural: 'activityTargets',
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
|
isRemote: false,
|
||||||
},
|
},
|
||||||
toFieldMetadataId: 'bba19feb-c248-487b-92d7-98df54c51e44',
|
toFieldMetadataId: 'bba19feb-c248-487b-92d7-98df54c51e44',
|
||||||
},
|
},
|
||||||
@ -221,6 +224,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
|
|||||||
nameSingular: 'attachment',
|
nameSingular: 'attachment',
|
||||||
namePlural: 'attachments',
|
namePlural: 'attachments',
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
|
isRemote: false,
|
||||||
},
|
},
|
||||||
toFieldMetadataId: '0880dac5-37d2-43a6-b143-722126d4923f',
|
toFieldMetadataId: '0880dac5-37d2-43a6-b143-722126d4923f',
|
||||||
},
|
},
|
||||||
@ -331,6 +335,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
|
|||||||
nameSingular: 'workspaceMember',
|
nameSingular: 'workspaceMember',
|
||||||
namePlural: 'workspaceMembers',
|
namePlural: 'workspaceMembers',
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
|
isRemote: false,
|
||||||
},
|
},
|
||||||
fromFieldMetadataId: '0f3e456f-3bb4-4261-a436-95246dc0e159',
|
fromFieldMetadataId: '0f3e456f-3bb4-4261-a436-95246dc0e159',
|
||||||
},
|
},
|
||||||
@ -378,6 +383,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
|
|||||||
nameSingular: 'favorite',
|
nameSingular: 'favorite',
|
||||||
namePlural: 'favorites',
|
namePlural: 'favorites',
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
|
isRemote: false,
|
||||||
},
|
},
|
||||||
toFieldMetadataId: '8fd8965b-bd4e-4a9b-90e9-c75652dadda1',
|
toFieldMetadataId: '8fd8965b-bd4e-4a9b-90e9-c75652dadda1',
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddSettingsColumnToFieldMetadata1713793656356
|
||||||
|
implements MigrationInterface
|
||||||
|
{
|
||||||
|
name = 'AddSettingsColumnToFieldMetadata1713793656356';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "metadata"."fieldMetadata" ADD "settings" jsonb`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "metadata"."remoteServer" DROP COLUMN "foreignDataWrapperType"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "metadata"."remoteServer" ADD "foreignDataWrapperType" text`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "metadata"."remoteServer" DROP COLUMN "foreignDataWrapperType"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "metadata"."remoteServer" ADD "foreignDataWrapperType" character varying`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "metadata"."fieldMetadata" DROP COLUMN "settings"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -78,14 +78,16 @@ export class FindDuplicatesQueryFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildQueryForExistingRecord(
|
buildQueryForExistingRecord(
|
||||||
id: string,
|
id: string | number,
|
||||||
options: WorkspaceQueryBuilderOptions,
|
options: WorkspaceQueryBuilderOptions,
|
||||||
) {
|
) {
|
||||||
|
const idQueryField = typeof id === 'string' ? `"${id}"` : id;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
query {
|
query {
|
||||||
${computeObjectTargetTable(
|
${computeObjectTargetTable(
|
||||||
options.objectMetadataItem,
|
options.objectMetadataItem,
|
||||||
)}Collection(filter: { id: { eq: "${id}" }}){
|
)}Collection(filter: { id: { eq: ${idQueryField} }}){
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
__typename
|
__typename
|
||||||
|
@ -77,7 +77,7 @@ export class WorkspaceQueryBuilderFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findDuplicatesExistingRecord(
|
findDuplicatesExistingRecord(
|
||||||
id: string,
|
id: string | number,
|
||||||
options: WorkspaceQueryBuilderOptions,
|
options: WorkspaceQueryBuilderOptions,
|
||||||
): string {
|
): string {
|
||||||
return this.findDuplicatesQueryFactory.buildQueryForExistingRecord(
|
return this.findDuplicatesQueryFactory.buildQueryForExistingRecord(
|
||||||
|
@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
|
|
||||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||||
|
import { ResolverArgsType } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
|
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
|
||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
@ -14,6 +15,7 @@ describe('QueryRunnerArgsFactory', () => {
|
|||||||
const options = {
|
const options = {
|
||||||
fieldMetadataCollection: [
|
fieldMetadataCollection: [
|
||||||
{ name: 'position', type: FieldMetadataType.POSITION },
|
{ name: 'position', type: FieldMetadataType.POSITION },
|
||||||
|
{ name: 'testNumber', type: FieldMetadataType.NUMBER },
|
||||||
] as FieldMetadataInterface[],
|
] as FieldMetadataInterface[],
|
||||||
objectMetadataItem: { isCustom: true, nameSingular: 'test' },
|
objectMetadataItem: { isCustom: true, nameSingular: 'test' },
|
||||||
} as WorkspaceQueryRunnerOptions;
|
} as WorkspaceQueryRunnerOptions;
|
||||||
@ -45,18 +47,92 @@ describe('QueryRunnerArgsFactory', () => {
|
|||||||
const args = {
|
const args = {
|
||||||
data: [],
|
data: [],
|
||||||
};
|
};
|
||||||
const result = await factory.create(args, options);
|
const result = await factory.create(
|
||||||
|
args,
|
||||||
|
options,
|
||||||
|
ResolverArgsType.CreateMany,
|
||||||
|
);
|
||||||
|
|
||||||
expect(result).toEqual(args);
|
expect(result).toEqual(args);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should override args when of type array', async () => {
|
it('createMany type should override data position and number', async () => {
|
||||||
const args = { data: [{ id: 1 }, { position: 'last' }] };
|
const args = {
|
||||||
|
id: 'uuid',
|
||||||
|
data: [{ position: 'last', testNumber: '1' }],
|
||||||
|
};
|
||||||
|
|
||||||
const result = await factory.create(args, options);
|
const result = await factory.create(
|
||||||
|
args,
|
||||||
|
options,
|
||||||
|
ResolverArgsType.CreateMany,
|
||||||
|
);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
data: [{ id: 1 }, { position: 2 }],
|
id: 'uuid',
|
||||||
|
data: [{ position: 2, testNumber: 1 }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('findMany type should override data position and number', async () => {
|
||||||
|
const args = {
|
||||||
|
id: 'uuid',
|
||||||
|
filter: { testNumber: { eq: '1' }, otherField: { eq: 'test' } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await factory.create(
|
||||||
|
args,
|
||||||
|
options,
|
||||||
|
ResolverArgsType.FindMany,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
id: 'uuid',
|
||||||
|
filter: { testNumber: { eq: 1 }, otherField: { eq: 'test' } },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('findOne type should override number in filter', async () => {
|
||||||
|
const args = {
|
||||||
|
id: 'uuid',
|
||||||
|
filter: { testNumber: { eq: '1' }, otherField: { eq: 'test' } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await factory.create(
|
||||||
|
args,
|
||||||
|
options,
|
||||||
|
ResolverArgsType.FindOne,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
id: 'uuid',
|
||||||
|
filter: { testNumber: { eq: 1 }, otherField: { eq: 'test' } },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('findDuplicates type should override number in data and id', async () => {
|
||||||
|
const optionsDuplicate = {
|
||||||
|
fieldMetadataCollection: [
|
||||||
|
{ name: 'id', type: FieldMetadataType.NUMBER },
|
||||||
|
{ name: 'testNumber', type: FieldMetadataType.NUMBER },
|
||||||
|
] as FieldMetadataInterface[],
|
||||||
|
objectMetadataItem: { isCustom: true, nameSingular: 'test' },
|
||||||
|
} as WorkspaceQueryRunnerOptions;
|
||||||
|
|
||||||
|
const args = {
|
||||||
|
id: '123',
|
||||||
|
data: { testNumber: '1', otherField: 'test' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await factory.create(
|
||||||
|
args,
|
||||||
|
optionsDuplicate,
|
||||||
|
ResolverArgsType.FindDuplicates,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
id: 123,
|
||||||
|
data: { testNumber: 1, otherField: 'test' },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,6 +2,15 @@ import { Injectable } from '@nestjs/common';
|
|||||||
|
|
||||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||||
|
import {
|
||||||
|
CreateManyResolverArgs,
|
||||||
|
FindDuplicatesResolverArgs,
|
||||||
|
FindManyResolverArgs,
|
||||||
|
FindOneResolverArgs,
|
||||||
|
ResolverArgs,
|
||||||
|
ResolverArgsType,
|
||||||
|
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
import { RecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||||
|
|
||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
|
||||||
@ -12,8 +21,9 @@ export class QueryRunnerArgsFactory {
|
|||||||
constructor(private readonly recordPositionFactory: RecordPositionFactory) {}
|
constructor(private readonly recordPositionFactory: RecordPositionFactory) {}
|
||||||
|
|
||||||
async create(
|
async create(
|
||||||
args: Record<string, any>,
|
args: ResolverArgs,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
resolverArgsType: ResolverArgsType,
|
||||||
) {
|
) {
|
||||||
const fieldMetadataCollection = options.fieldMetadataCollection;
|
const fieldMetadataCollection = options.fieldMetadataCollection;
|
||||||
|
|
||||||
@ -24,21 +34,62 @@ export class QueryRunnerArgsFactory {
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
switch (resolverArgsType) {
|
||||||
|
case ResolverArgsType.CreateMany:
|
||||||
return {
|
return {
|
||||||
|
...args,
|
||||||
data: await Promise.all(
|
data: await Promise.all(
|
||||||
args.data.map((arg) =>
|
(args as CreateManyResolverArgs).data.map((arg) =>
|
||||||
this.overrideArgByFieldMetadata(arg, options, fieldMetadataMap),
|
this.overrideDataByFieldMetadata(arg, options, fieldMetadataMap),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
} satisfies CreateManyResolverArgs;
|
||||||
|
case ResolverArgsType.FindOne:
|
||||||
|
return {
|
||||||
|
...args,
|
||||||
|
filter: await this.overrideFilterByFieldMetadata(
|
||||||
|
(args as FindOneResolverArgs).filter,
|
||||||
|
fieldMetadataMap,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
case ResolverArgsType.FindMany:
|
||||||
|
return {
|
||||||
|
...args,
|
||||||
|
filter: await this.overrideFilterByFieldMetadata(
|
||||||
|
(args as FindManyResolverArgs).filter,
|
||||||
|
fieldMetadataMap,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
case ResolverArgsType.FindDuplicates:
|
||||||
|
return {
|
||||||
|
...args,
|
||||||
|
id: await this.overrideValueByFieldMetadata(
|
||||||
|
'id',
|
||||||
|
(args as FindDuplicatesResolverArgs).id,
|
||||||
|
fieldMetadataMap,
|
||||||
|
),
|
||||||
|
data: await this.overrideDataByFieldMetadata(
|
||||||
|
(args as FindDuplicatesResolverArgs).data,
|
||||||
|
options,
|
||||||
|
fieldMetadataMap,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return args;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async overrideArgByFieldMetadata(
|
private async overrideDataByFieldMetadata(
|
||||||
arg: Record<string, any>,
|
data: Record<string, any> | undefined,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
fieldMetadataMap: Map<string, FieldMetadataInterface>,
|
fieldMetadataMap: Map<string, FieldMetadataInterface>,
|
||||||
) {
|
) {
|
||||||
const createArgPromiseByArgKey = Object.entries(arg).map(
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createArgPromiseByArgKey = Object.entries(data).map(
|
||||||
async ([key, value]) => {
|
async ([key, value]) => {
|
||||||
const fieldMetadata = fieldMetadataMap.get(key);
|
const fieldMetadata = fieldMetadataMap.get(key);
|
||||||
|
|
||||||
@ -59,6 +110,8 @@ export class QueryRunnerArgsFactory {
|
|||||||
options.workspaceId,
|
options.workspaceId,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
case FieldMetadataType.NUMBER:
|
||||||
|
return [key, await Promise.resolve(Number(value))];
|
||||||
default:
|
default:
|
||||||
return [key, await Promise.resolve(value)];
|
return [key, await Promise.resolve(value)];
|
||||||
}
|
}
|
||||||
@ -69,4 +122,57 @@ export class QueryRunnerArgsFactory {
|
|||||||
|
|
||||||
return Object.fromEntries(newArgEntries);
|
return Object.fromEntries(newArgEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private overrideFilterByFieldMetadata(
|
||||||
|
filter: RecordFilter | undefined,
|
||||||
|
fieldMetadataMap: Map<string, FieldMetadataInterface>,
|
||||||
|
) {
|
||||||
|
if (!filter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createArgPromiseByArgKey = Object.entries(filter).map(
|
||||||
|
([key, value]) => {
|
||||||
|
const fieldMetadata = fieldMetadataMap.get(key);
|
||||||
|
|
||||||
|
if (!fieldMetadata) {
|
||||||
|
return [key, value];
|
||||||
|
}
|
||||||
|
|
||||||
|
const createFilterByKey = Object.entries(value).map(
|
||||||
|
([filterKey, filterValue]) => {
|
||||||
|
switch (fieldMetadata.type) {
|
||||||
|
case FieldMetadataType.NUMBER:
|
||||||
|
return [filterKey, Number(filterValue)];
|
||||||
|
default:
|
||||||
|
return [filterKey, filterValue];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return [key, Object.fromEntries(createFilterByKey)];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return Object.fromEntries(createArgPromiseByArgKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async overrideValueByFieldMetadata(
|
||||||
|
key: string,
|
||||||
|
value: any,
|
||||||
|
fieldMetadataMap: Map<string, FieldMetadataInterface>,
|
||||||
|
) {
|
||||||
|
const fieldMetadata = fieldMetadataMap.get(key);
|
||||||
|
|
||||||
|
if (!fieldMetadata) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (fieldMetadata.type) {
|
||||||
|
case FieldMetadataType.NUMBER:
|
||||||
|
return Number(value);
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
import { BadRequestException } from '@nestjs/common';
|
||||||
|
|
||||||
|
export const assertIsValidUuid = (value: string) => {
|
||||||
|
const isValid =
|
||||||
|
/^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test(
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
throw new BadRequestException(`Value "${value}" is not a valid UUID`);
|
||||||
|
}
|
||||||
|
};
|
@ -22,6 +22,7 @@ import {
|
|||||||
FindDuplicatesResolverArgs,
|
FindDuplicatesResolverArgs,
|
||||||
FindManyResolverArgs,
|
FindManyResolverArgs,
|
||||||
FindOneResolverArgs,
|
FindOneResolverArgs,
|
||||||
|
ResolverArgsType,
|
||||||
UpdateManyResolverArgs,
|
UpdateManyResolverArgs,
|
||||||
UpdateOneResolverArgs,
|
UpdateOneResolverArgs,
|
||||||
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
@ -48,6 +49,7 @@ import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-r
|
|||||||
import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters.factory';
|
import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters.factory';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||||
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assertIsValidUuid.util';
|
||||||
|
|
||||||
import { WorkspaceQueryRunnerOptions } from './interfaces/query-runner-option.interface';
|
import { WorkspaceQueryRunnerOptions } from './interfaces/query-runner-option.interface';
|
||||||
import {
|
import {
|
||||||
@ -83,9 +85,15 @@ export class WorkspaceQueryRunnerService {
|
|||||||
const { workspaceId, userId, objectMetadataItem } = options;
|
const { workspaceId, userId, objectMetadataItem } = options;
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
|
|
||||||
const query = await this.workspaceQueryBuilderFactory.findMany(
|
const computedArgs = (await this.queryRunnerArgsFactory.create(
|
||||||
args,
|
args,
|
||||||
options,
|
options,
|
||||||
|
ResolverArgsType.FindMany,
|
||||||
|
)) as FindManyResolverArgs<Filter, OrderBy>;
|
||||||
|
|
||||||
|
const query = await this.workspaceQueryBuilderFactory.findMany(
|
||||||
|
computedArgs,
|
||||||
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.workspacePreQueryHookService.executePreHooks(
|
await this.workspacePreQueryHookService.executePreHooks(
|
||||||
@ -123,9 +131,16 @@ export class WorkspaceQueryRunnerService {
|
|||||||
throw new BadRequestException('Missing filter argument');
|
throw new BadRequestException('Missing filter argument');
|
||||||
}
|
}
|
||||||
const { workspaceId, userId, objectMetadataItem } = options;
|
const { workspaceId, userId, objectMetadataItem } = options;
|
||||||
const query = await this.workspaceQueryBuilderFactory.findOne(
|
|
||||||
|
const computedArgs = (await this.queryRunnerArgsFactory.create(
|
||||||
args,
|
args,
|
||||||
options,
|
options,
|
||||||
|
ResolverArgsType.FindOne,
|
||||||
|
)) as FindOneResolverArgs<Filter>;
|
||||||
|
|
||||||
|
const query = await this.workspaceQueryBuilderFactory.findOne(
|
||||||
|
computedArgs,
|
||||||
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.workspacePreQueryHookService.executePreHooks(
|
await this.workspacePreQueryHookService.executePreHooks(
|
||||||
@ -164,12 +179,18 @@ export class WorkspaceQueryRunnerService {
|
|||||||
|
|
||||||
const { workspaceId, userId, objectMetadataItem } = options;
|
const { workspaceId, userId, objectMetadataItem } = options;
|
||||||
|
|
||||||
|
const computedArgs = (await this.queryRunnerArgsFactory.create(
|
||||||
|
args,
|
||||||
|
options,
|
||||||
|
ResolverArgsType.FindDuplicates,
|
||||||
|
)) as FindDuplicatesResolverArgs<TRecord>;
|
||||||
|
|
||||||
let existingRecord: Record<string, unknown> | undefined;
|
let existingRecord: Record<string, unknown> | undefined;
|
||||||
|
|
||||||
if (args.id) {
|
if (computedArgs.id) {
|
||||||
const existingRecordQuery =
|
const existingRecordQuery =
|
||||||
this.workspaceQueryBuilderFactory.findDuplicatesExistingRecord(
|
this.workspaceQueryBuilderFactory.findDuplicatesExistingRecord(
|
||||||
args.id,
|
computedArgs.id,
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -192,7 +213,7 @@ export class WorkspaceQueryRunnerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const query = await this.workspaceQueryBuilderFactory.findDuplicates(
|
const query = await this.workspaceQueryBuilderFactory.findDuplicates(
|
||||||
args,
|
computedArgs,
|
||||||
options,
|
options,
|
||||||
existingRecord,
|
existingRecord,
|
||||||
);
|
);
|
||||||
@ -202,7 +223,7 @@ export class WorkspaceQueryRunnerService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataItem.nameSingular,
|
||||||
'findDuplicates',
|
'findDuplicates',
|
||||||
args,
|
computedArgs,
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = await this.execute(query, workspaceId);
|
const result = await this.execute(query, workspaceId);
|
||||||
@ -222,10 +243,17 @@ export class WorkspaceQueryRunnerService {
|
|||||||
|
|
||||||
assertMutationNotOnRemoteObject(objectMetadataItem);
|
assertMutationNotOnRemoteObject(objectMetadataItem);
|
||||||
|
|
||||||
const computedArgs = await this.queryRunnerArgsFactory.create(
|
args.data.forEach((record) => {
|
||||||
|
if (record.id) {
|
||||||
|
assertIsValidUuid(record.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const computedArgs = (await this.queryRunnerArgsFactory.create(
|
||||||
args,
|
args,
|
||||||
options,
|
options,
|
||||||
);
|
ResolverArgsType.CreateMany,
|
||||||
|
)) as CreateManyResolverArgs<Record>;
|
||||||
|
|
||||||
await this.workspacePreQueryHookService.executePreHooks(
|
await this.workspacePreQueryHookService.executePreHooks(
|
||||||
userId,
|
userId,
|
||||||
@ -288,6 +316,7 @@ export class WorkspaceQueryRunnerService {
|
|||||||
const { workspaceId, userId, objectMetadataItem } = options;
|
const { workspaceId, userId, objectMetadataItem } = options;
|
||||||
|
|
||||||
assertMutationNotOnRemoteObject(objectMetadataItem);
|
assertMutationNotOnRemoteObject(objectMetadataItem);
|
||||||
|
assertIsValidUuid(args.id);
|
||||||
|
|
||||||
const existingRecord = await this.findOne(
|
const existingRecord = await this.findOne(
|
||||||
{ filter: { id: { eq: args.id } } } as FindOneResolverArgs,
|
{ filter: { id: { eq: args.id } } } as FindOneResolverArgs,
|
||||||
@ -337,6 +366,7 @@ export class WorkspaceQueryRunnerService {
|
|||||||
const { workspaceId, objectMetadataItem } = options;
|
const { workspaceId, objectMetadataItem } = options;
|
||||||
|
|
||||||
assertMutationNotOnRemoteObject(objectMetadataItem);
|
assertMutationNotOnRemoteObject(objectMetadataItem);
|
||||||
|
assertIsValidUuid(args.data.id);
|
||||||
|
|
||||||
const maximumRecordAffected = this.environmentService.get(
|
const maximumRecordAffected = this.environmentService.get(
|
||||||
'MUTATION_MAXIMUM_RECORD_AFFECTED',
|
'MUTATION_MAXIMUM_RECORD_AFFECTED',
|
||||||
|
@ -10,6 +10,19 @@ import { workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/work
|
|||||||
|
|
||||||
export type Resolver<Args = any> = GraphQLFieldResolver<any, any, Args>;
|
export type Resolver<Args = any> = GraphQLFieldResolver<any, any, Args>;
|
||||||
|
|
||||||
|
export enum ResolverArgsType {
|
||||||
|
FindMany = 'FindMany',
|
||||||
|
FindOne = 'FindOne',
|
||||||
|
FindDuplicates = 'FindDuplicates',
|
||||||
|
CreateOne = 'CreateOne',
|
||||||
|
CreateMany = 'CreateMany',
|
||||||
|
UpdateOne = 'UpdateOne',
|
||||||
|
UpdateMany = 'UpdateMany',
|
||||||
|
DeleteOne = 'DeleteOne',
|
||||||
|
DeleteMany = 'DeleteMany',
|
||||||
|
ExecuteQuickActionOnOne = 'ExecuteQuickActionOnOne',
|
||||||
|
}
|
||||||
|
|
||||||
export interface FindManyResolverArgs<
|
export interface FindManyResolverArgs<
|
||||||
Filter extends RecordFilter = RecordFilter,
|
Filter extends RecordFilter = RecordFilter,
|
||||||
OrderBy extends RecordOrderBy = RecordOrderBy,
|
OrderBy extends RecordOrderBy = RecordOrderBy,
|
||||||
|
@ -32,27 +32,7 @@ export class ArgsFactory {
|
|||||||
|
|
||||||
// Argument is a scalar type
|
// Argument is a scalar type
|
||||||
if (arg.type) {
|
if (arg.type) {
|
||||||
const fieldType = this.typeMapperService.mapToScalarType(
|
const gqlType = this.typeMapperService.mapToGqlType(arg.type, {
|
||||||
arg.type,
|
|
||||||
options.dateScalarMode,
|
|
||||||
options.numberScalarMode,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!fieldType) {
|
|
||||||
this.logger.error(
|
|
||||||
`Could not find a GraphQL type for ${arg.type.toString()}`,
|
|
||||||
{
|
|
||||||
arg,
|
|
||||||
options,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
`Could not find a GraphQL type for ${arg.type.toString()}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const gqlType = this.typeMapperService.mapToGqlType(fieldType, {
|
|
||||||
defaultValue: arg.defaultValue,
|
defaultValue: arg.defaultValue,
|
||||||
nullable: arg.isNullable,
|
nullable: arg.isNullable,
|
||||||
isArray: arg.isArray,
|
isArray: arg.isArray,
|
||||||
|
@ -102,6 +102,8 @@ export class InputTypeDefinitionFactory {
|
|||||||
? fieldMetadata.type.toString()
|
? fieldMetadata.type.toString()
|
||||||
: fieldMetadata.id;
|
: fieldMetadata.id;
|
||||||
|
|
||||||
|
const isIdField = fieldMetadata.name === 'id';
|
||||||
|
|
||||||
const type = this.inputTypeFactory.create(
|
const type = this.inputTypeFactory.create(
|
||||||
target,
|
target,
|
||||||
fieldMetadata.type,
|
fieldMetadata.type,
|
||||||
@ -111,6 +113,8 @@ export class InputTypeDefinitionFactory {
|
|||||||
nullable: fieldMetadata.isNullable,
|
nullable: fieldMetadata.isNullable,
|
||||||
defaultValue: fieldMetadata.defaultValue,
|
defaultValue: fieldMetadata.defaultValue,
|
||||||
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
|
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
|
||||||
|
settings: fieldMetadata.settings,
|
||||||
|
isIdField,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -41,8 +41,8 @@ export class InputTypeFactory {
|
|||||||
case InputTypeDefinitionKind.Update:
|
case InputTypeDefinitionKind.Update:
|
||||||
inputType = this.typeMapperService.mapToScalarType(
|
inputType = this.typeMapperService.mapToScalarType(
|
||||||
type,
|
type,
|
||||||
buildOptions.dateScalarMode,
|
typeOptions.settings,
|
||||||
buildOptions.numberScalarMode,
|
typeOptions.isIdField,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
/**
|
/**
|
||||||
@ -54,8 +54,8 @@ export class InputTypeFactory {
|
|||||||
} else {
|
} else {
|
||||||
inputType = this.typeMapperService.mapToFilterType(
|
inputType = this.typeMapperService.mapToFilterType(
|
||||||
type,
|
type,
|
||||||
buildOptions.dateScalarMode,
|
typeOptions.settings,
|
||||||
buildOptions.numberScalarMode,
|
typeOptions.isIdField,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +69,9 @@ export class ObjectTypeDefinitionFactory {
|
|||||||
{
|
{
|
||||||
nullable: fieldMetadata.isNullable,
|
nullable: fieldMetadata.isNullable,
|
||||||
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
|
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
|
||||||
|
settings: fieldMetadata.settings,
|
||||||
|
// Scalar type is already defined in the entity itself.
|
||||||
|
isIdField: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -32,8 +32,8 @@ export class OutputTypeFactory {
|
|||||||
let gqlType: GraphQLOutputType | undefined =
|
let gqlType: GraphQLOutputType | undefined =
|
||||||
this.typeMapperService.mapToScalarType(
|
this.typeMapperService.mapToScalarType(
|
||||||
type,
|
type,
|
||||||
buildOtions.dateScalarMode,
|
typeOptions.settings,
|
||||||
buildOtions.numberScalarMode,
|
typeOptions.isIdField,
|
||||||
);
|
);
|
||||||
|
|
||||||
gqlType ??= this.typeDefinitionsStorage.getOutputTypeByKey(target, kind);
|
gqlType ??= this.typeDefinitionsStorage.getOutputTypeByKey(target, kind);
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import { GraphQLID, GraphQLInputObjectType, GraphQLList } from 'graphql';
|
||||||
|
|
||||||
|
import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type';
|
||||||
|
|
||||||
|
export const IDFilterType = new GraphQLInputObjectType({
|
||||||
|
name: 'IDFilter',
|
||||||
|
fields: {
|
||||||
|
eq: { type: GraphQLID },
|
||||||
|
gt: { type: GraphQLID },
|
||||||
|
gte: { type: GraphQLID },
|
||||||
|
in: { type: new GraphQLList(GraphQLID) },
|
||||||
|
lt: { type: GraphQLID },
|
||||||
|
lte: { type: GraphQLID },
|
||||||
|
neq: { type: GraphQLID },
|
||||||
|
is: { type: FilterIs },
|
||||||
|
},
|
||||||
|
});
|
@ -1,9 +1,10 @@
|
|||||||
|
import { GraphQLScalarType } from 'graphql';
|
||||||
|
|
||||||
import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
|
import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
|
||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
|
||||||
|
|
||||||
export interface ArgMetadata<T = any> {
|
export interface ArgMetadata<T = any> {
|
||||||
kind?: InputTypeDefinitionKind;
|
kind?: InputTypeDefinitionKind;
|
||||||
type?: FieldMetadataType;
|
type?: GraphQLScalarType;
|
||||||
isNullable?: boolean;
|
isNullable?: boolean;
|
||||||
isArray?: boolean;
|
isArray?: boolean;
|
||||||
defaultValue?: T;
|
defaultValue?: T;
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { GraphQLISODateTime, GraphQLTimestamp } from '@nestjs/graphql';
|
import { GraphQLISODateTime } from '@nestjs/graphql';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GraphQLBoolean,
|
GraphQLBoolean,
|
||||||
GraphQLEnumType,
|
GraphQLEnumType,
|
||||||
GraphQLFloat,
|
GraphQLFloat,
|
||||||
|
GraphQLID,
|
||||||
GraphQLInputObjectType,
|
GraphQLInputObjectType,
|
||||||
GraphQLInputType,
|
GraphQLInputType,
|
||||||
GraphQLInt,
|
GraphQLInt,
|
||||||
@ -15,22 +16,17 @@ import {
|
|||||||
GraphQLType,
|
GraphQLType,
|
||||||
} from 'graphql';
|
} from 'graphql';
|
||||||
|
|
||||||
import {
|
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||||
DateScalarMode,
|
|
||||||
NumberScalarMode,
|
|
||||||
} from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
|
|
||||||
|
|
||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
import {
|
import {
|
||||||
UUIDFilterType,
|
|
||||||
StringFilterType,
|
StringFilterType,
|
||||||
DatetimeFilterType,
|
|
||||||
DateFilterType,
|
DateFilterType,
|
||||||
FloatFilterType,
|
FloatFilterType,
|
||||||
IntFilterType,
|
|
||||||
BooleanFilterType,
|
BooleanFilterType,
|
||||||
BigFloatFilterType,
|
BigFloatFilterType,
|
||||||
RawJsonFilterType,
|
RawJsonFilterType,
|
||||||
|
IntFilterType,
|
||||||
} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input';
|
} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input';
|
||||||
import { OrderByDirectionType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/enum';
|
import { OrderByDirectionType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/enum';
|
||||||
import {
|
import {
|
||||||
@ -39,38 +35,46 @@ import {
|
|||||||
} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||||
import { PositionScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/position.scalar';
|
import { PositionScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/position.scalar';
|
||||||
import { JsonScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/json.scalar';
|
import { JsonScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/json.scalar';
|
||||||
|
import { IDFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/id-filter.input-type';
|
||||||
|
|
||||||
export interface TypeOptions<T = any> {
|
export interface TypeOptions<T = any> {
|
||||||
nullable?: boolean;
|
nullable?: boolean;
|
||||||
isArray?: boolean;
|
isArray?: boolean;
|
||||||
arrayDepth?: number;
|
arrayDepth?: number;
|
||||||
defaultValue?: T;
|
defaultValue?: T;
|
||||||
|
settings?: FieldMetadataSettings<FieldMetadataType | 'default'>;
|
||||||
|
isIdField?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TypeMapperService {
|
export class TypeMapperService {
|
||||||
mapToScalarType(
|
mapToScalarType(
|
||||||
fieldMetadataType: FieldMetadataType,
|
fieldMetadataType: FieldMetadataType,
|
||||||
dateScalarMode: DateScalarMode = 'isoDate',
|
settings?: FieldMetadataSettings<FieldMetadataType | 'default'>,
|
||||||
numberScalarMode: NumberScalarMode = 'float',
|
isIdField?: boolean,
|
||||||
): GraphQLScalarType | undefined {
|
): GraphQLScalarType | undefined {
|
||||||
const dateScalar =
|
if (isIdField || settings?.isForeignKey) {
|
||||||
dateScalarMode === 'timestamp' ? GraphQLTimestamp : GraphQLISODateTime;
|
return GraphQLID;
|
||||||
|
}
|
||||||
|
|
||||||
const numberScalar =
|
const numberScalar =
|
||||||
numberScalarMode === 'float' ? GraphQLFloat : GraphQLInt;
|
fieldMetadataType === FieldMetadataType.NUMBER &&
|
||||||
|
(settings as FieldMetadataSettings<FieldMetadataType.NUMBER>)
|
||||||
|
?.precision === 0
|
||||||
|
? GraphQLInt
|
||||||
|
: GraphQLFloat;
|
||||||
|
|
||||||
const typeScalarMapping = new Map<FieldMetadataType, GraphQLScalarType>([
|
const typeScalarMapping = new Map<FieldMetadataType, GraphQLScalarType>([
|
||||||
[FieldMetadataType.UUID, UUIDScalarType],
|
[FieldMetadataType.UUID, UUIDScalarType],
|
||||||
[FieldMetadataType.TEXT, GraphQLString],
|
[FieldMetadataType.TEXT, GraphQLString],
|
||||||
[FieldMetadataType.PHONE, GraphQLString],
|
[FieldMetadataType.PHONE, GraphQLString],
|
||||||
[FieldMetadataType.EMAIL, GraphQLString],
|
[FieldMetadataType.EMAIL, GraphQLString],
|
||||||
[FieldMetadataType.DATE_TIME, dateScalar],
|
[FieldMetadataType.DATE_TIME, GraphQLISODateTime],
|
||||||
[FieldMetadataType.DATE, dateScalar],
|
[FieldMetadataType.DATE, GraphQLISODateTime],
|
||||||
[FieldMetadataType.BOOLEAN, GraphQLBoolean],
|
[FieldMetadataType.BOOLEAN, GraphQLBoolean],
|
||||||
[FieldMetadataType.NUMBER, numberScalar],
|
[FieldMetadataType.NUMBER, numberScalar],
|
||||||
[FieldMetadataType.NUMERIC, BigFloatScalarType],
|
[FieldMetadataType.NUMERIC, BigFloatScalarType],
|
||||||
[FieldMetadataType.PROBABILITY, GraphQLFloat],
|
[FieldMetadataType.PROBABILITY, GraphQLFloat],
|
||||||
[FieldMetadataType.RELATION, UUIDScalarType],
|
|
||||||
[FieldMetadataType.POSITION, PositionScalarType],
|
[FieldMetadataType.POSITION, PositionScalarType],
|
||||||
[FieldMetadataType.RAW_JSON, JsonScalarType],
|
[FieldMetadataType.RAW_JSON, JsonScalarType],
|
||||||
]);
|
]);
|
||||||
@ -80,29 +84,34 @@ export class TypeMapperService {
|
|||||||
|
|
||||||
mapToFilterType(
|
mapToFilterType(
|
||||||
fieldMetadataType: FieldMetadataType,
|
fieldMetadataType: FieldMetadataType,
|
||||||
dateScalarMode: DateScalarMode = 'isoDate',
|
settings?: FieldMetadataSettings<FieldMetadataType | 'default'>,
|
||||||
numberScalarMode: NumberScalarMode = 'float',
|
isIdField?: boolean,
|
||||||
): GraphQLInputObjectType | GraphQLScalarType | undefined {
|
): GraphQLInputObjectType | GraphQLScalarType | undefined {
|
||||||
const dateFilter =
|
if (isIdField || settings?.isForeignKey) {
|
||||||
dateScalarMode === 'timestamp' ? DatetimeFilterType : DateFilterType;
|
return IDFilterType;
|
||||||
|
}
|
||||||
|
|
||||||
const numberScalar =
|
const numberScalar =
|
||||||
numberScalarMode === 'float' ? FloatFilterType : IntFilterType;
|
fieldMetadataType === FieldMetadataType.NUMBER &&
|
||||||
|
(settings as FieldMetadataSettings<FieldMetadataType.NUMBER>)
|
||||||
|
?.precision === 0
|
||||||
|
? IntFilterType
|
||||||
|
: FloatFilterType;
|
||||||
|
|
||||||
const typeFilterMapping = new Map<
|
const typeFilterMapping = new Map<
|
||||||
FieldMetadataType,
|
FieldMetadataType,
|
||||||
GraphQLInputObjectType | GraphQLScalarType
|
GraphQLInputObjectType | GraphQLScalarType
|
||||||
>([
|
>([
|
||||||
[FieldMetadataType.UUID, UUIDFilterType],
|
[FieldMetadataType.UUID, IDFilterType],
|
||||||
[FieldMetadataType.TEXT, StringFilterType],
|
[FieldMetadataType.TEXT, StringFilterType],
|
||||||
[FieldMetadataType.PHONE, StringFilterType],
|
[FieldMetadataType.PHONE, StringFilterType],
|
||||||
[FieldMetadataType.EMAIL, StringFilterType],
|
[FieldMetadataType.EMAIL, StringFilterType],
|
||||||
[FieldMetadataType.DATE_TIME, dateFilter],
|
[FieldMetadataType.DATE_TIME, DateFilterType],
|
||||||
[FieldMetadataType.DATE, DateFilterType],
|
[FieldMetadataType.DATE, DateFilterType],
|
||||||
[FieldMetadataType.BOOLEAN, BooleanFilterType],
|
[FieldMetadataType.BOOLEAN, BooleanFilterType],
|
||||||
[FieldMetadataType.NUMBER, numberScalar],
|
[FieldMetadataType.NUMBER, numberScalar],
|
||||||
[FieldMetadataType.NUMERIC, BigFloatFilterType],
|
[FieldMetadataType.NUMERIC, BigFloatFilterType],
|
||||||
[FieldMetadataType.PROBABILITY, FloatFilterType],
|
[FieldMetadataType.PROBABILITY, FloatFilterType],
|
||||||
[FieldMetadataType.RELATION, UUIDFilterType],
|
|
||||||
[FieldMetadataType.POSITION, FloatFilterType],
|
[FieldMetadataType.POSITION, FloatFilterType],
|
||||||
[FieldMetadataType.RAW_JSON, RawJsonFilterType],
|
[FieldMetadataType.RAW_JSON, RawJsonFilterType],
|
||||||
]);
|
]);
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
|
import { GraphQLID, GraphQLInt, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
|
||||||
import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
|
import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
|
||||||
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
|
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
|
||||||
|
|
||||||
describe('getResolverArgs', () => {
|
describe('getResolverArgs', () => {
|
||||||
const expectedOutputs = {
|
const expectedOutputs = {
|
||||||
findMany: {
|
findMany: {
|
||||||
first: { type: FieldMetadataType.NUMBER, isNullable: true },
|
first: { type: GraphQLInt, isNullable: true },
|
||||||
last: { type: FieldMetadataType.NUMBER, isNullable: true },
|
last: { type: GraphQLInt, isNullable: true },
|
||||||
before: { type: FieldMetadataType.TEXT, isNullable: true },
|
before: { type: GraphQLString, isNullable: true },
|
||||||
after: { type: FieldMetadataType.TEXT, isNullable: true },
|
after: { type: GraphQLString, isNullable: true },
|
||||||
filter: { kind: InputTypeDefinitionKind.Filter, isNullable: true },
|
filter: { kind: InputTypeDefinitionKind.Filter, isNullable: true },
|
||||||
orderBy: { kind: InputTypeDefinitionKind.OrderBy, isNullable: true },
|
orderBy: { kind: InputTypeDefinitionKind.OrderBy, isNullable: true },
|
||||||
|
limit: { type: GraphQLInt, isNullable: true },
|
||||||
},
|
},
|
||||||
findOne: {
|
findOne: {
|
||||||
filter: { kind: InputTypeDefinitionKind.Filter, isNullable: false },
|
filter: { kind: InputTypeDefinitionKind.Filter, isNullable: false },
|
||||||
@ -28,14 +30,14 @@ describe('getResolverArgs', () => {
|
|||||||
data: { kind: InputTypeDefinitionKind.Create, isNullable: false },
|
data: { kind: InputTypeDefinitionKind.Create, isNullable: false },
|
||||||
},
|
},
|
||||||
updateOne: {
|
updateOne: {
|
||||||
id: { type: FieldMetadataType.UUID, isNullable: false },
|
id: { type: GraphQLID, isNullable: false },
|
||||||
data: { kind: InputTypeDefinitionKind.Update, isNullable: false },
|
data: { kind: InputTypeDefinitionKind.Update, isNullable: false },
|
||||||
},
|
},
|
||||||
deleteOne: {
|
deleteOne: {
|
||||||
id: { type: FieldMetadataType.UUID, isNullable: false },
|
id: { type: GraphQLID, isNullable: false },
|
||||||
},
|
},
|
||||||
executeQuickActionOnOne: {
|
executeQuickActionOnOne: {
|
||||||
id: { type: FieldMetadataType.UUID, isNullable: false },
|
id: { type: GraphQLID, isNullable: false },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
import { GraphQLString, GraphQLInt, GraphQLID } from 'graphql';
|
||||||
|
|
||||||
import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
import { ArgMetadata } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/param-metadata.interface';
|
import { ArgMetadata } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/param-metadata.interface';
|
||||||
|
|
||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
|
||||||
import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
|
import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
|
||||||
|
|
||||||
export const getResolverArgs = (
|
export const getResolverArgs = (
|
||||||
@ -11,19 +12,23 @@ export const getResolverArgs = (
|
|||||||
case 'findMany':
|
case 'findMany':
|
||||||
return {
|
return {
|
||||||
first: {
|
first: {
|
||||||
type: FieldMetadataType.NUMBER,
|
type: GraphQLInt,
|
||||||
isNullable: true,
|
isNullable: true,
|
||||||
},
|
},
|
||||||
last: {
|
last: {
|
||||||
type: FieldMetadataType.NUMBER,
|
type: GraphQLInt,
|
||||||
isNullable: true,
|
isNullable: true,
|
||||||
},
|
},
|
||||||
before: {
|
before: {
|
||||||
type: FieldMetadataType.TEXT,
|
type: GraphQLString,
|
||||||
isNullable: true,
|
isNullable: true,
|
||||||
},
|
},
|
||||||
after: {
|
after: {
|
||||||
type: FieldMetadataType.TEXT,
|
type: GraphQLString,
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: GraphQLInt,
|
||||||
isNullable: true,
|
isNullable: true,
|
||||||
},
|
},
|
||||||
filter: {
|
filter: {
|
||||||
@ -61,7 +66,7 @@ export const getResolverArgs = (
|
|||||||
case 'updateOne':
|
case 'updateOne':
|
||||||
return {
|
return {
|
||||||
id: {
|
id: {
|
||||||
type: FieldMetadataType.UUID,
|
type: GraphQLID,
|
||||||
isNullable: false,
|
isNullable: false,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
@ -72,7 +77,7 @@ export const getResolverArgs = (
|
|||||||
case 'findDuplicates':
|
case 'findDuplicates':
|
||||||
return {
|
return {
|
||||||
id: {
|
id: {
|
||||||
type: FieldMetadataType.UUID,
|
type: GraphQLID,
|
||||||
isNullable: true,
|
isNullable: true,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
@ -83,14 +88,14 @@ export const getResolverArgs = (
|
|||||||
case 'deleteOne':
|
case 'deleteOne':
|
||||||
return {
|
return {
|
||||||
id: {
|
id: {
|
||||||
type: FieldMetadataType.UUID,
|
type: GraphQLID,
|
||||||
isNullable: false,
|
isNullable: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
case 'executeQuickActionOnOne':
|
case 'executeQuickActionOnOne':
|
||||||
return {
|
return {
|
||||||
id: {
|
id: {
|
||||||
type: FieldMetadataType.UUID,
|
type: GraphQLID,
|
||||||
isNullable: false,
|
isNullable: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -16,7 +16,7 @@ export class FindManyQueryFactory {
|
|||||||
$filter: ${objectNameSingular}FilterInput,
|
$filter: ${objectNameSingular}FilterInput,
|
||||||
$orderBy: ${objectNameSingular}OrderByInput,
|
$orderBy: ${objectNameSingular}OrderByInput,
|
||||||
$lastCursor: String,
|
$lastCursor: String,
|
||||||
$limit: Float = 60
|
$limit: Int = 60
|
||||||
) {
|
) {
|
||||||
${objectNamePlural}(
|
${objectNamePlural}(
|
||||||
filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor
|
filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor
|
||||||
|
@ -22,7 +22,6 @@ export enum FeatureFlagKeys {
|
|||||||
IsAirtableIntegrationEnabled = 'IS_AIRTABLE_INTEGRATION_ENABLED',
|
IsAirtableIntegrationEnabled = 'IS_AIRTABLE_INTEGRATION_ENABLED',
|
||||||
IsPostgreSQLIntegrationEnabled = 'IS_POSTGRESQL_INTEGRATION_ENABLED',
|
IsPostgreSQLIntegrationEnabled = 'IS_POSTGRESQL_INTEGRATION_ENABLED',
|
||||||
IsMultiSelectEnabled = 'IS_MULTI_SELECT_ENABLED',
|
IsMultiSelectEnabled = 'IS_MULTI_SELECT_ENABLED',
|
||||||
IsRelationForRemoteObjectsEnabled = 'IS_RELATION_FOR_REMOTE_OBJECTS_ENABLED',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity({ name: 'featureFlag', schema: 'core' })
|
@Entity({ name: 'featureFlag', schema: 'core' })
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
|
|
||||||
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
||||||
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||||
|
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||||
|
|
||||||
import { RelationMetadataDTO } from 'src/engine/metadata-modules/relation-metadata/dtos/relation-metadata.dto';
|
import { RelationMetadataDTO } from 'src/engine/metadata-modules/relation-metadata/dtos/relation-metadata.dto';
|
||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
@ -120,6 +121,10 @@ export class FieldMetadataDTO<
|
|||||||
@Field(() => GraphQLJSON, { nullable: true })
|
@Field(() => GraphQLJSON, { nullable: true })
|
||||||
options?: FieldMetadataOptions<T>;
|
options?: FieldMetadataOptions<T>;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@Field(() => GraphQLJSON, { nullable: true })
|
||||||
|
settings?: FieldMetadataSettings<T>;
|
||||||
|
|
||||||
@HideField()
|
@HideField()
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||||
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||||
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
||||||
|
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||||
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||||
@ -87,6 +88,9 @@ export class FieldMetadataEntity<
|
|||||||
@Column('jsonb', { nullable: true })
|
@Column('jsonb', { nullable: true })
|
||||||
options: FieldMetadataOptions<T>;
|
options: FieldMetadataOptions<T>;
|
||||||
|
|
||||||
|
@Column('jsonb', { nullable: true })
|
||||||
|
settings?: FieldMetadataSettings<T>;
|
||||||
|
|
||||||
@Column({ default: false })
|
@Column({ default: false })
|
||||||
isCustom: boolean;
|
isCustom: boolean;
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ import { IsFieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-m
|
|||||||
import { FieldMetadataResolver } from 'src/engine/metadata-modules/field-metadata/field-metadata.resolver';
|
import { FieldMetadataResolver } from 'src/engine/metadata-modules/field-metadata/field-metadata.resolver';
|
||||||
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
||||||
import { IsFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-options.validator';
|
import { IsFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-options.validator';
|
||||||
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
|
||||||
|
|
||||||
import { FieldMetadataService } from './field-metadata.service';
|
import { FieldMetadataService } from './field-metadata.service';
|
||||||
import { FieldMetadataEntity } from './field-metadata.entity';
|
import { FieldMetadataEntity } from './field-metadata.entity';
|
||||||
@ -29,10 +28,7 @@ import { UpdateFieldInput } from './dtos/update-field.input';
|
|||||||
imports: [
|
imports: [
|
||||||
NestjsQueryGraphQLModule.forFeature({
|
NestjsQueryGraphQLModule.forFeature({
|
||||||
imports: [
|
imports: [
|
||||||
NestjsQueryTypeOrmModule.forFeature(
|
NestjsQueryTypeOrmModule.forFeature([FieldMetadataEntity], 'metadata'),
|
||||||
[FieldMetadataEntity, RelationMetadataEntity],
|
|
||||||
'metadata',
|
|
||||||
),
|
|
||||||
WorkspaceMigrationModule,
|
WorkspaceMigrationModule,
|
||||||
WorkspaceMigrationRunnerModule,
|
WorkspaceMigrationRunnerModule,
|
||||||
ObjectMetadataModule,
|
ObjectMetadataModule,
|
||||||
|
@ -58,8 +58,6 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
private readonly metadataDataSource: DataSource,
|
private readonly metadataDataSource: DataSource,
|
||||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
@InjectRepository(FieldMetadataEntity, 'metadata')
|
||||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||||
@InjectRepository(RelationMetadataEntity, 'metadata')
|
|
||||||
private readonly relationMetadataRepository: Repository<RelationMetadataEntity>,
|
|
||||||
private readonly objectMetadataService: ObjectMetadataService,
|
private readonly objectMetadataService: ObjectMetadataService,
|
||||||
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
|
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
|
||||||
private readonly workspaceMigrationService: WorkspaceMigrationService,
|
private readonly workspaceMigrationService: WorkspaceMigrationService,
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
|
||||||
|
type FieldMetadataDefaultSettings = {
|
||||||
|
isForeignKey?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FieldMetadataNumberSettings = {
|
||||||
|
precision: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FieldMetadataSettingsMapping = {
|
||||||
|
[FieldMetadataType.NUMBER]: FieldMetadataNumberSettings;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SettingsByFieldMetadata<T extends FieldMetadataType | 'default'> =
|
||||||
|
T extends keyof FieldMetadataSettingsMapping
|
||||||
|
? FieldMetadataSettingsMapping[T] & FieldMetadataDefaultSettings
|
||||||
|
: T extends 'default'
|
||||||
|
? FieldMetadataDefaultSettings
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type FieldMetadataSettings<
|
||||||
|
T extends FieldMetadataType | 'default' = 'default',
|
||||||
|
> = SettingsByFieldMetadata<T>;
|
@ -1,5 +1,6 @@
|
|||||||
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||||
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
||||||
|
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||||
|
|
||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||||
@ -13,6 +14,7 @@ export interface FieldMetadataInterface<
|
|||||||
label: string;
|
label: string;
|
||||||
defaultValue?: FieldMetadataDefaultValue<T>;
|
defaultValue?: FieldMetadataDefaultValue<T>;
|
||||||
options?: FieldMetadataOptions<T>;
|
options?: FieldMetadataOptions<T>;
|
||||||
|
settings?: FieldMetadataSettings<T>;
|
||||||
objectMetadataId: string;
|
objectMetadataId: string;
|
||||||
workspaceId?: string;
|
workspaceId?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
@ -8,9 +8,13 @@ import {
|
|||||||
IsString,
|
IsString,
|
||||||
IsUUID,
|
IsUUID,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
|
import GraphQLJSON from 'graphql-type-json';
|
||||||
|
|
||||||
|
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||||
|
|
||||||
import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator';
|
import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator';
|
||||||
import { BeforeCreateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-create-one-object.hook';
|
import { BeforeCreateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-create-one-object.hook';
|
||||||
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
|
||||||
@InputType()
|
@InputType()
|
||||||
@BeforeCreateOne(BeforeCreateOneObject)
|
@BeforeCreateOne(BeforeCreateOneObject)
|
||||||
@ -70,5 +74,11 @@ export class CreateObjectInput {
|
|||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Field({ nullable: true })
|
@Field({ nullable: true })
|
||||||
remoteTablePrimaryKeyColumnType?: string;
|
primaryKeyColumnType?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@Field(() => GraphQLJSON, { nullable: true })
|
||||||
|
primaryKeyFieldMetadataSettings?: FieldMetadataSettings<
|
||||||
|
FieldMetadataType | 'default'
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@ import {
|
|||||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||||
import { Query, QueryOptions } from '@ptc-org/nestjs-query-core';
|
import { Query, QueryOptions } from '@ptc-org/nestjs-query-core';
|
||||||
|
|
||||||
|
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||||
|
|
||||||
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
|
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
|
||||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
||||||
import {
|
import {
|
||||||
@ -54,12 +56,10 @@ import {
|
|||||||
import { createWorkspaceMigrationsForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-custom-object.util';
|
import { createWorkspaceMigrationsForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-custom-object.util';
|
||||||
import { createWorkspaceMigrationsForRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object.util';
|
import { createWorkspaceMigrationsForRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object.util';
|
||||||
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||||
import {
|
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||||
FeatureFlagEntity,
|
|
||||||
FeatureFlagKeys,
|
|
||||||
} from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
|
||||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||||
import { validateObjectMetadataInput } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util';
|
import { validateObjectMetadataInput } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util';
|
||||||
|
import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/remote-postgres-table/utils/remote-postgres-table.util';
|
||||||
|
|
||||||
import { ObjectMetadataEntity } from './object-metadata.entity';
|
import { ObjectMetadataEntity } from './object-metadata.entity';
|
||||||
|
|
||||||
@ -469,35 +469,38 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
workspaceDataSource: DataSource | undefined,
|
workspaceDataSource: DataSource | undefined,
|
||||||
isRemoteObject = false,
|
isRemoteObject = false,
|
||||||
) {
|
) {
|
||||||
const isRelationEnabledForRemoteObjects =
|
|
||||||
await this.isRelationEnabledForRemoteObjects(
|
|
||||||
objectMetadataInput.workspaceId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isRemoteObject && !isRelationEnabledForRemoteObjects) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { timelineActivityObjectMetadata } =
|
const { timelineActivityObjectMetadata } =
|
||||||
await this.createTimelineActivityRelation(
|
await this.createTimelineActivityRelation(
|
||||||
objectMetadataInput.workspaceId,
|
objectMetadataInput.workspaceId,
|
||||||
createdObjectMetadata,
|
createdObjectMetadata,
|
||||||
|
mapUdtNameToFieldType(
|
||||||
|
objectMetadataInput.primaryKeyColumnType ?? 'uuid',
|
||||||
|
),
|
||||||
|
objectMetadataInput.primaryKeyFieldMetadataSettings,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { activityTargetObjectMetadata } =
|
const { activityTargetObjectMetadata } =
|
||||||
await this.createActivityTargetRelation(
|
await this.createActivityTargetRelation(
|
||||||
objectMetadataInput.workspaceId,
|
objectMetadataInput.workspaceId,
|
||||||
createdObjectMetadata,
|
createdObjectMetadata,
|
||||||
|
mapUdtNameToFieldType(
|
||||||
|
objectMetadataInput.primaryKeyColumnType ?? 'uuid',
|
||||||
|
),
|
||||||
|
objectMetadataInput.primaryKeyFieldMetadataSettings,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { favoriteObjectMetadata } = await this.createFavoriteRelation(
|
const { favoriteObjectMetadata } = await this.createFavoriteRelation(
|
||||||
objectMetadataInput.workspaceId,
|
objectMetadataInput.workspaceId,
|
||||||
createdObjectMetadata,
|
createdObjectMetadata,
|
||||||
|
mapUdtNameToFieldType(objectMetadataInput.primaryKeyColumnType ?? 'uuid'),
|
||||||
|
objectMetadataInput.primaryKeyFieldMetadataSettings,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { attachmentObjectMetadata } = await this.createAttachmentRelation(
|
const { attachmentObjectMetadata } = await this.createAttachmentRelation(
|
||||||
objectMetadataInput.workspaceId,
|
objectMetadataInput.workspaceId,
|
||||||
createdObjectMetadata,
|
createdObjectMetadata,
|
||||||
|
mapUdtNameToFieldType(objectMetadataInput.primaryKeyColumnType ?? 'uuid'),
|
||||||
|
objectMetadataInput.primaryKeyFieldMetadataSettings,
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.workspaceMigrationService.createCustomMigration(
|
return this.workspaceMigrationService.createCustomMigration(
|
||||||
@ -511,7 +514,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
timelineActivityObjectMetadata,
|
timelineActivityObjectMetadata,
|
||||||
favoriteObjectMetadata,
|
favoriteObjectMetadata,
|
||||||
lastDataSourceMetadata.schema,
|
lastDataSourceMetadata.schema,
|
||||||
objectMetadataInput.remoteTablePrimaryKeyColumnType ?? 'uuid',
|
objectMetadataInput.primaryKeyColumnType ?? 'uuid',
|
||||||
workspaceDataSource,
|
workspaceDataSource,
|
||||||
)
|
)
|
||||||
: createWorkspaceMigrationsForCustomObject(
|
: createWorkspaceMigrationsForCustomObject(
|
||||||
@ -527,6 +530,10 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
private async createActivityTargetRelation(
|
private async createActivityTargetRelation(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
createdObjectMetadata: ObjectMetadataEntity,
|
createdObjectMetadata: ObjectMetadataEntity,
|
||||||
|
objectPrimaryKeyType: FieldMetadataType,
|
||||||
|
objectPrimaryKeyFieldSettings:
|
||||||
|
| FieldMetadataSettings<FieldMetadataType | 'default'>
|
||||||
|
| undefined,
|
||||||
) {
|
) {
|
||||||
const activityTargetObjectMetadata =
|
const activityTargetObjectMetadata =
|
||||||
await this.objectMetadataRepository.findOneByOrFail({
|
await this.objectMetadataRepository.findOneByOrFail({
|
||||||
@ -577,7 +584,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
isCustom: false,
|
isCustom: false,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
type: FieldMetadataType.UUID,
|
type: objectPrimaryKeyType,
|
||||||
name: `${createdObjectMetadata.nameSingular}Id`,
|
name: `${createdObjectMetadata.nameSingular}Id`,
|
||||||
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
|
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
|
||||||
description: `ActivityTarget ${createdObjectMetadata.labelSingular} id foreign key`,
|
description: `ActivityTarget ${createdObjectMetadata.labelSingular} id foreign key`,
|
||||||
@ -585,6 +592,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
isNullable: true,
|
isNullable: true,
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
defaultValue: undefined,
|
defaultValue: undefined,
|
||||||
|
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -622,6 +630,10 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
private async createAttachmentRelation(
|
private async createAttachmentRelation(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
createdObjectMetadata: ObjectMetadataEntity,
|
createdObjectMetadata: ObjectMetadataEntity,
|
||||||
|
objectPrimaryKeyType: FieldMetadataType,
|
||||||
|
objectPrimaryKeyFieldSettings:
|
||||||
|
| FieldMetadataSettings<FieldMetadataType | 'default'>
|
||||||
|
| undefined,
|
||||||
) {
|
) {
|
||||||
const attachmentObjectMetadata =
|
const attachmentObjectMetadata =
|
||||||
await this.objectMetadataRepository.findOneByOrFail({
|
await this.objectMetadataRepository.findOneByOrFail({
|
||||||
@ -672,7 +684,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
isCustom: false,
|
isCustom: false,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
type: FieldMetadataType.UUID,
|
type: objectPrimaryKeyType,
|
||||||
name: `${createdObjectMetadata.nameSingular}Id`,
|
name: `${createdObjectMetadata.nameSingular}Id`,
|
||||||
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
|
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
|
||||||
description: `Attachment ${createdObjectMetadata.labelSingular} id foreign key`,
|
description: `Attachment ${createdObjectMetadata.labelSingular} id foreign key`,
|
||||||
@ -680,6 +692,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
isNullable: true,
|
isNullable: true,
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
defaultValue: undefined,
|
defaultValue: undefined,
|
||||||
|
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -715,6 +728,10 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
private async createTimelineActivityRelation(
|
private async createTimelineActivityRelation(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
createdObjectMetadata: ObjectMetadataEntity,
|
createdObjectMetadata: ObjectMetadataEntity,
|
||||||
|
objectPrimaryKeyType: FieldMetadataType,
|
||||||
|
objectPrimaryKeyFieldSettings:
|
||||||
|
| FieldMetadataSettings<FieldMetadataType | 'default'>
|
||||||
|
| undefined,
|
||||||
) {
|
) {
|
||||||
const timelineActivityObjectMetadata =
|
const timelineActivityObjectMetadata =
|
||||||
await this.objectMetadataRepository.findOneByOrFail({
|
await this.objectMetadataRepository.findOneByOrFail({
|
||||||
@ -765,7 +782,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
isCustom: false,
|
isCustom: false,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
type: FieldMetadataType.UUID,
|
type: objectPrimaryKeyType,
|
||||||
name: `${createdObjectMetadata.nameSingular}Id`,
|
name: `${createdObjectMetadata.nameSingular}Id`,
|
||||||
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
|
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
|
||||||
description: `Timeline Activity ${createdObjectMetadata.labelSingular} id foreign key`,
|
description: `Timeline Activity ${createdObjectMetadata.labelSingular} id foreign key`,
|
||||||
@ -773,6 +790,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
isNullable: true,
|
isNullable: true,
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
defaultValue: undefined,
|
defaultValue: undefined,
|
||||||
|
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -810,6 +828,10 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
private async createFavoriteRelation(
|
private async createFavoriteRelation(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
createdObjectMetadata: ObjectMetadataEntity,
|
createdObjectMetadata: ObjectMetadataEntity,
|
||||||
|
objectPrimaryKeyType: FieldMetadataType,
|
||||||
|
objectPrimaryKeyFieldSettings:
|
||||||
|
| FieldMetadataSettings<FieldMetadataType | 'default'>
|
||||||
|
| undefined,
|
||||||
) {
|
) {
|
||||||
const favoriteObjectMetadata =
|
const favoriteObjectMetadata =
|
||||||
await this.objectMetadataRepository.findOneByOrFail({
|
await this.objectMetadataRepository.findOneByOrFail({
|
||||||
@ -861,7 +883,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
isCustom: false,
|
isCustom: false,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
type: FieldMetadataType.UUID,
|
type: objectPrimaryKeyType,
|
||||||
name: `${createdObjectMetadata.nameSingular}Id`,
|
name: `${createdObjectMetadata.nameSingular}Id`,
|
||||||
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
|
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
|
||||||
description: `Favorite ${createdObjectMetadata.labelSingular} id foreign key`,
|
description: `Favorite ${createdObjectMetadata.labelSingular} id foreign key`,
|
||||||
@ -869,6 +891,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
isNullable: true,
|
isNullable: true,
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
defaultValue: undefined,
|
defaultValue: undefined,
|
||||||
|
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -900,14 +923,4 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
|
|
||||||
return { favoriteObjectMetadata };
|
return { favoriteObjectMetadata };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async isRelationEnabledForRemoteObjects(workspaceId: string) {
|
|
||||||
const featureFlag = await this.featureFlagRepository.findOneBy({
|
|
||||||
workspaceId,
|
|
||||||
key: FeatureFlagKeys.IsRelationForRemoteObjectsEnabled,
|
|
||||||
value: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return featureFlag && featureFlag.value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
import { BadRequestException } from '@nestjs/common';
|
||||||
|
|
||||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||||
|
|
||||||
export const assertMutationNotOnRemoteObject = (
|
export const assertMutationNotOnRemoteObject = (
|
||||||
objectMetadataItem: ObjectMetadataInterface,
|
objectMetadataItem: ObjectMetadataInterface,
|
||||||
) => {
|
) => {
|
||||||
if (objectMetadataItem.isRemote) {
|
if (objectMetadataItem.isRemote) {
|
||||||
throw new Error('Remote objects are read-only');
|
throw new BadRequestException('Remote objects are read-only');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -54,7 +54,7 @@ export const createWorkspaceMigrationsForRemoteObject = async (
|
|||||||
eventObjectMetadata: ObjectMetadataEntity,
|
eventObjectMetadata: ObjectMetadataEntity,
|
||||||
favoriteObjectMetadata: ObjectMetadataEntity,
|
favoriteObjectMetadata: ObjectMetadataEntity,
|
||||||
schema: string,
|
schema: string,
|
||||||
remoteTablePrimaryKeyColumnType: string,
|
primaryKeyColumnType: string,
|
||||||
workspaceDataSource: DataSource | undefined,
|
workspaceDataSource: DataSource | undefined,
|
||||||
): Promise<WorkspaceMigrationTableAction[]> => {
|
): Promise<WorkspaceMigrationTableAction[]> => {
|
||||||
const createdObjectName = createdObjectMetadata.nameSingular;
|
const createdObjectName = createdObjectMetadata.nameSingular;
|
||||||
@ -69,7 +69,7 @@ export const createWorkspaceMigrationsForRemoteObject = async (
|
|||||||
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
||||||
isForeignKey: true,
|
isForeignKey: true,
|
||||||
}),
|
}),
|
||||||
columnType: remoteTablePrimaryKeyColumnType,
|
columnType: primaryKeyColumnType,
|
||||||
isNullable: true,
|
isNullable: true,
|
||||||
} satisfies WorkspaceMigrationColumnCreate,
|
} satisfies WorkspaceMigrationColumnCreate,
|
||||||
],
|
],
|
||||||
@ -99,7 +99,7 @@ export const createWorkspaceMigrationsForRemoteObject = async (
|
|||||||
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
||||||
isForeignKey: true,
|
isForeignKey: true,
|
||||||
}),
|
}),
|
||||||
columnType: remoteTablePrimaryKeyColumnType,
|
columnType: primaryKeyColumnType,
|
||||||
isNullable: true,
|
isNullable: true,
|
||||||
} satisfies WorkspaceMigrationColumnCreate,
|
} satisfies WorkspaceMigrationColumnCreate,
|
||||||
],
|
],
|
||||||
@ -129,7 +129,7 @@ export const createWorkspaceMigrationsForRemoteObject = async (
|
|||||||
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
||||||
isForeignKey: true,
|
isForeignKey: true,
|
||||||
}),
|
}),
|
||||||
columnType: remoteTablePrimaryKeyColumnType,
|
columnType: primaryKeyColumnType,
|
||||||
isNullable: true,
|
isNullable: true,
|
||||||
} satisfies WorkspaceMigrationColumnCreate,
|
} satisfies WorkspaceMigrationColumnCreate,
|
||||||
],
|
],
|
||||||
@ -159,7 +159,7 @@ export const createWorkspaceMigrationsForRemoteObject = async (
|
|||||||
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
||||||
isForeignKey: true,
|
isForeignKey: true,
|
||||||
}),
|
}),
|
||||||
columnType: remoteTablePrimaryKeyColumnType,
|
columnType: primaryKeyColumnType,
|
||||||
isNullable: true,
|
isNullable: true,
|
||||||
} satisfies WorkspaceMigrationColumnCreate,
|
} satisfies WorkspaceMigrationColumnCreate,
|
||||||
],
|
],
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { Repository } from 'typeorm/repository/Repository';
|
import { Repository } from 'typeorm/repository/Repository';
|
||||||
|
|
||||||
|
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||||
|
|
||||||
import { decryptText } from 'src/engine/core-modules/auth/auth.util';
|
import { decryptText } from 'src/engine/core-modules/auth/auth.util';
|
||||||
import {
|
import {
|
||||||
FeatureFlagEntity,
|
FeatureFlagEntity,
|
||||||
@ -42,11 +44,32 @@ export const mapUdtNameToFieldType = (udtName: string): FieldMetadataType => {
|
|||||||
case 'timestamp':
|
case 'timestamp':
|
||||||
case 'timestamptz':
|
case 'timestamptz':
|
||||||
return FieldMetadataType.DATE_TIME;
|
return FieldMetadataType.DATE_TIME;
|
||||||
|
case 'integer':
|
||||||
|
case 'int2':
|
||||||
|
case 'int4':
|
||||||
|
case 'int8':
|
||||||
|
return FieldMetadataType.NUMBER;
|
||||||
default:
|
default:
|
||||||
return FieldMetadataType.TEXT;
|
return FieldMetadataType.TEXT;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mapUdtNameToSettings = (
|
||||||
|
udtName: string,
|
||||||
|
): FieldMetadataSettings<FieldMetadataType> | undefined => {
|
||||||
|
switch (udtName) {
|
||||||
|
case 'integer':
|
||||||
|
case 'int2':
|
||||||
|
case 'int4':
|
||||||
|
case 'int8':
|
||||||
|
return {
|
||||||
|
precision: 0,
|
||||||
|
} satisfies FieldMetadataSettings<FieldMetadataType.NUMBER>;
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const isPostgreSQLIntegrationEnabled = async (
|
export const isPostgreSQLIntegrationEnabled = async (
|
||||||
featureFlagRepository: Repository<FeatureFlagEntity>,
|
featureFlagRepository: Repository<FeatureFlagEntity>,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
|
@ -12,6 +12,7 @@ import { RemoteTableStatus } from 'src/engine/metadata-modules/remote-server/rem
|
|||||||
import {
|
import {
|
||||||
isPostgreSQLIntegrationEnabled,
|
isPostgreSQLIntegrationEnabled,
|
||||||
mapUdtNameToFieldType,
|
mapUdtNameToFieldType,
|
||||||
|
mapUdtNameToSettings,
|
||||||
} from 'src/engine/metadata-modules/remote-server/remote-table/remote-postgres-table/utils/remote-postgres-table.util';
|
} from 'src/engine/metadata-modules/remote-server/remote-table/remote-postgres-table/utils/remote-postgres-table.util';
|
||||||
import { RemoteTableInput } from 'src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table-input';
|
import { RemoteTableInput } from 'src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table-input';
|
||||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||||
@ -429,7 +430,11 @@ export class RemoteTableService {
|
|||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
icon: 'IconPlug',
|
icon: 'IconPlug',
|
||||||
isRemote: true,
|
isRemote: true,
|
||||||
remoteTablePrimaryKeyColumnType: remoteTableIdColumn.udtName,
|
primaryKeyColumnType: remoteTableIdColumn.udtName,
|
||||||
|
// TODO: function should work for other types than Postgres
|
||||||
|
primaryKeyFieldMetadataSettings: mapUdtNameToSettings(
|
||||||
|
remoteTableIdColumn.udtName,
|
||||||
|
),
|
||||||
} satisfies CreateObjectInput);
|
} satisfies CreateObjectInput);
|
||||||
|
|
||||||
for (const column of remoteTableColumns) {
|
for (const column of remoteTableColumns) {
|
||||||
@ -444,6 +449,8 @@ export class RemoteTableService {
|
|||||||
isRemoteCreation: true,
|
isRemoteCreation: true,
|
||||||
isNullable: true,
|
isNullable: true,
|
||||||
icon: 'IconPlug',
|
icon: 'IconPlug',
|
||||||
|
// TODO: function should work for other types than Postgres
|
||||||
|
settings: mapUdtNameToSettings(column.udtName),
|
||||||
} satisfies CreateFieldInput);
|
} satisfies CreateFieldInput);
|
||||||
|
|
||||||
if (column.columnName === 'id') {
|
if (column.columnName === 'id') {
|
||||||
|
@ -59,7 +59,6 @@ export class AddStandardIdCommand extends CommandRunner {
|
|||||||
IS_AIRTABLE_INTEGRATION_ENABLED: true,
|
IS_AIRTABLE_INTEGRATION_ENABLED: true,
|
||||||
IS_POSTGRESQL_INTEGRATION_ENABLED: true,
|
IS_POSTGRESQL_INTEGRATION_ENABLED: true,
|
||||||
IS_MULTI_SELECT_ENABLED: false,
|
IS_MULTI_SELECT_ENABLED: false,
|
||||||
IS_RELATION_FOR_REMOTE_OBJECTS_ENABLED: false,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const standardFieldMetadataCollection = this.standardFieldFactory.create(
|
const standardFieldMetadataCollection = this.standardFieldFactory.create(
|
||||||
@ -75,7 +74,6 @@ export class AddStandardIdCommand extends CommandRunner {
|
|||||||
IS_AIRTABLE_INTEGRATION_ENABLED: true,
|
IS_AIRTABLE_INTEGRATION_ENABLED: true,
|
||||||
IS_POSTGRESQL_INTEGRATION_ENABLED: true,
|
IS_POSTGRESQL_INTEGRATION_ENABLED: true,
|
||||||
IS_MULTI_SELECT_ENABLED: false,
|
IS_MULTI_SELECT_ENABLED: false,
|
||||||
IS_RELATION_FOR_REMOTE_OBJECTS_ENABLED: false,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ export function FieldMetadata<T extends FieldMetadataType>(
|
|||||||
description: `${restParams.description} id foreign key`,
|
description: `${restParams.description} id foreign key`,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
options: undefined,
|
options: undefined,
|
||||||
|
settings: undefined,
|
||||||
},
|
},
|
||||||
joinColumn,
|
joinColumn,
|
||||||
isNullable,
|
isNullable,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||||
import { GateDecoratorParams } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/gate-decorator.interface';
|
import { GateDecoratorParams } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/gate-decorator.interface';
|
||||||
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
||||||
|
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||||
|
|
||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
@ -16,12 +17,13 @@ export interface FieldMetadataDecoratorParams<
|
|||||||
defaultValue?: FieldMetadataDefaultValue<T>;
|
defaultValue?: FieldMetadataDefaultValue<T>;
|
||||||
joinColumn?: string;
|
joinColumn?: string;
|
||||||
options?: FieldMetadataOptions<T>;
|
options?: FieldMetadataOptions<T>;
|
||||||
|
settings?: FieldMetadataSettings<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ReflectFieldMetadata {
|
export interface ReflectFieldMetadata {
|
||||||
[key: string]: Omit<
|
[key: string]: Omit<
|
||||||
FieldMetadataDecoratorParams<'default'>,
|
FieldMetadataDecoratorParams<'default'>,
|
||||||
'defaultValue' | 'type' | 'options'
|
'defaultValue' | 'type' | 'options' | 'settings'
|
||||||
> & {
|
> & {
|
||||||
name: string;
|
name: string;
|
||||||
type: FieldMetadataType;
|
type: FieldMetadataType;
|
||||||
@ -31,5 +33,6 @@ export interface ReflectFieldMetadata {
|
|||||||
defaultValue: FieldMetadataDefaultValue<'default'> | null;
|
defaultValue: FieldMetadataDefaultValue<'default'> | null;
|
||||||
gate?: GateDecoratorParams;
|
gate?: GateDecoratorParams;
|
||||||
options?: FieldMetadataOptions<'default'> | null;
|
options?: FieldMetadataOptions<'default'> | null;
|
||||||
|
settings?: FieldMetadataSettings<'default'> | null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -241,7 +241,9 @@ export class WorkspaceMetadataUpdaterService {
|
|||||||
manager: EntityManager,
|
manager: EntityManager,
|
||||||
entityClass: EntityTarget<Entity>,
|
entityClass: EntityTarget<Entity>,
|
||||||
updateCollection: Array<
|
updateCollection: Array<
|
||||||
DeepPartial<Omit<Entity, 'fields' | 'options'>> & { id: string }
|
DeepPartial<Omit<Entity, 'fields' | 'options' | 'settings'>> & {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
>,
|
>,
|
||||||
keysToOmit: (keyof Entity)[] = [],
|
keysToOmit: (keyof Entity)[] = [],
|
||||||
): Promise<{ current: Entity; altered: Entity }[]> {
|
): Promise<{ current: Entity; altered: Entity }[]> {
|
||||||
|
Loading…
Reference in New Issue
Block a user