From 6129444c5c3cead2075e36e5c6b16c15a99bbb5d Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Wed, 15 Nov 2023 15:46:06 +0100 Subject: [PATCH] [WIP] Whole FE migrated (#2517) * Wip * WIP * Removed concole log * Add relations to workspace init (#2511) * Add relations to workspace init * remove logs * update prefill * add missing isSystem * comment relation fields * Migrate v2 core models to graphql schema (#2509) * migrate v2 core models to graphql schema * Migrate to new workspace member schema * Continue work * migrated-main * Finished accountOwner nested field integration on companies * Introduce bug * Fix --------- Co-authored-by: Lucas Bordeau Co-authored-by: Weiko --- front/src/generated-metadata/gql.ts | 4 +- front/src/generated-metadata/graphql.ts | 66 ++- front/src/generated/graphql.tsx | 405 +++++------------- .../hooks/useOpenCreateActivityDrawer.ts | 6 +- .../tasks/hooks/useCurrentUserDueTaskCount.ts | 11 +- .../hooks/useOptimisticEffect.ts | 4 +- .../graphql/fragments/userQueryFragment.ts | 44 -- front/src/modules/auth/hooks/useAuth.ts | 99 ++++- front/src/modules/auth/hooks/useIsLogged.ts | 7 +- .../modules/auth/hooks/useOnboardingStatus.ts | 19 +- .../auth/sign-in-up/hooks/useSignInUp.tsx | 19 +- .../modules/auth/states/currentUserState.ts | 27 +- .../states/currentWorkspaceMemberState.ts | 18 + .../auth/states/currentWorkspaceState.ts | 13 + .../auth/states/isVerifyPendingState.ts | 6 + .../modules/auth/utils/getOnboardingStatus.ts | 12 +- .../object-metadata/graphql/queries.ts | 25 +- .../hooks/useFindManyObjectMetadataItems.ts | 4 +- .../hooks/useFindManyRelationMetadataItems.ts | 74 ++++ .../hooks/useFindOneObjectMetadataItem.ts | 54 +-- .../useMapFieldMetadataToGraphQLQuery.ts | 86 ++++ .../utils/mapFieldMetadataToGraphQLQuery.ts | 65 --- ...jectMetadataItemsToObjectMetadataItems.ts} | 4 +- .../mutation/createOneWorkspaceMember.ts | 11 + .../graphql/queries/findOneWorkspaceMember.ts | 15 + .../hooks/useDeleteOneObjectRecord.ts | 6 +- .../hooks/useFindManyObjectRecords.ts | 4 +- .../hooks/useUpdateOneObjectRecord.ts | 2 + .../utils/generateCreateOneObjectMutation.ts | 13 +- ...cts.ts => mapPaginatedObjectsToObjects.ts} | 2 +- ... => useGenerateDeleteOneObjectMutation.ts} | 11 +- ... useGenerateFindManyCustomObjectsQuery.ts} | 13 +- ...=> useGenerateFindOneCustomObjectQuery.ts} | 13 +- ... => useGenerateUpdateOneObjectMutation.ts} | 13 +- .../hooks/useFilteredSearchEntityQueryV2.ts | 149 +++++++ .../profile/components/NameFields.tsx | 25 +- .../components/ProfilePictureUploader.tsx | 7 +- .../profile/components/ToggleField.tsx | 6 +- .../workspace/components/NameField.tsx | 11 +- .../components/WorkspaceLogoUploader.tsx | 11 +- .../ui/display/chip/components/EntityChip.tsx | 2 +- .../components/SingleEntitySelectBase.tsx | 6 +- .../types/EntityTypeForSelect.ts | 1 + .../navbar/components/NavWorkspaceButton.tsx | 3 +- .../navbar/components/SupportChat.tsx | 24 +- .../ui/object/field/hooks/usePersistField.ts | 9 +- .../components/RelationFieldDisplay.tsx | 27 +- .../utils/getEntityChipFromFieldMetadata.ts | 32 ++ .../meta-types/hooks/useRelationField.ts | 5 + .../input/components/RelationFieldInput.tsx | 4 +- .../record-table/components/RecordTable.tsx | 2 + .../modules/ui/theme/hooks/useColorScheme.ts | 111 +---- .../modules/users/components/UserPicker.tsx | 41 +- .../modules/users/components/UserProvider.tsx | 72 +++- .../users/graphql/mutations/updateUser.ts | 46 -- .../users/graphql/queries/getCurrentUser.ts | 9 - .../workspace-member/types/WorkspaceMember.ts | 6 + .../components/WorkspaceMemberCard.tsx | 23 +- .../workspaceMemberFieldsFragment.ts | 42 -- .../mutations/removeWorkspaceMember.ts | 9 - .../mutations/updateWorkspaceMember.ts | 12 - .../graphql/queries/getCurrentWorkspace.ts | 11 + .../graphql/queries/getWorkspaceMembers.ts | 4 - front/src/pages/auth/CreateProfile.tsx | 6 +- front/src/pages/auth/VerifyEffect.tsx | 11 +- .../pages/impersonate/ImpersonateEffect.tsx | 14 +- .../settings/SettingsWorkspaceMembers.tsx | 85 ++-- front/src/pages/tasks/TasksEffect.tsx | 19 +- server/src/ability/ability.factory.ts | 7 +- server/src/app.module.ts | 2 - server/src/core/auth/auth.resolver.ts | 2 - .../controllers/google-auth.controller.ts | 5 - server/src/core/auth/services/auth.service.ts | 13 +- .../src/core/auth/services/token.service.ts | 9 +- server/src/core/core.module.ts | 6 + server/src/core/user/user.service.ts | 19 +- server/src/coreV2/core.module.ts | 31 -- .../refresh-token.auto-resolver-opts.ts | 4 +- .../refresh-token/refresh-token.entity.ts | 8 +- .../refresh-token/refresh-token.module.ts | 5 + .../src/coreV2/user/services/user.service.ts | 8 +- .../coreV2/user/user.auto-resolver-opts.ts | 11 +- server/src/coreV2/user/user.entity.ts | 6 +- server/src/coreV2/user/user.module.ts | 4 +- server/src/coreV2/user/user.resolver.ts | 23 +- .../migration.sql | 5 + .../migration.sql | 20 + .../migration.sql | 11 + server/src/database/schema.prisma | 13 +- server/src/database/seeds/users.ts | 39 +- .../field-metadata/workspace-member.ts | 19 +- .../typeorm-seeds/tenant/workspaceMember.ts | 8 +- .../dtos/relation-metadata.dto.ts | 13 +- .../relation-metadata.entity.ts | 7 - .../1697618026-addWorspaceMemberTable.ts | 5 + .../standard-objects-prefill-data/company.ts | 51 +++ .../standard-objects-prefill-data/person.ts | 62 +++ .../pipeline-step.ts | 41 ++ .../standard-objects-prefill-data.ts | 282 +----------- .../standard-objects-prefill-data/view.ts | 278 ++++++++++++ .../standard-objects/activity-target.ts | 55 +++ .../standard-objects/activity.ts | 155 +++++++ .../standard-objects/api-key.ts | 54 +++ .../standard-objects/attachment.ts | 107 +++++ .../standard-objects/comment.ts | 55 +++ .../companies/companies.metadata.ts | 57 --- .../standard-objects/company.ts | 192 +++++++++ .../standard-objects/favorite.ts | 68 +++ .../standard-objects/opportunity.ts | 107 +++++ .../tenant-manager/standard-objects/person.ts | 201 +++++++++ .../standard-objects/pipeline-step.ts | 66 +++ .../standard-objects/relations/activity.ts | 27 ++ .../standard-objects/relations/company.ts | 41 ++ .../standard-objects/relations/person.ts | 41 ++ .../relations/pipeline-step.ts | 13 + .../standard-objects/relations/view.ts | 27 ++ .../relations/workspace-member.ts | 48 +++ .../standard-object-metadata.ts | 42 +- .../standard-object-relation-metadata.ts | 15 + .../view-fields.metadata.ts => view-field.ts} | 60 ++- ...iew-filters.metadata.ts => view-filter.ts} | 50 ++- .../view-sorts.metadata.ts => view-sort.ts} | 48 ++- .../{views/views.metadata.ts => view.ts} | 39 +- .../standard-objects/webhook.ts | 41 ++ .../standard-objects/workspace-member.ts | 160 +++++++ .../tenant-manager/tenant-manager.module.ts | 2 + .../tenant-manager/tenant-manager.service.ts | 126 +++++- .../interfaces/pg-graphql.interface.ts | 1 + .../query-runner/query-runner.service.ts | 30 +- 129 files changed, 3468 insertions(+), 1497 deletions(-) create mode 100644 front/src/modules/auth/states/currentWorkspaceMemberState.ts create mode 100644 front/src/modules/auth/states/currentWorkspaceState.ts create mode 100644 front/src/modules/auth/states/isVerifyPendingState.ts create mode 100644 front/src/modules/object-metadata/hooks/useFindManyRelationMetadataItems.ts create mode 100644 front/src/modules/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery.ts delete mode 100644 front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts rename front/src/modules/object-metadata/utils/{formatPagedObjectMetadataItemsToObjectMetadataItems.ts => mapPaginatedObjectMetadataItemsToObjectMetadataItems.ts} (79%) create mode 100644 front/src/modules/object-record/graphql/mutation/createOneWorkspaceMember.ts create mode 100644 front/src/modules/object-record/graphql/queries/findOneWorkspaceMember.ts rename front/src/modules/object-record/utils/{formatPagedObjectsToObjects.ts => mapPaginatedObjectsToObjects.ts} (91%) rename front/src/modules/object-record/utils/{generateDeleteOneObjectMutation.ts => useGenerateDeleteOneObjectMutation.ts} (53%) rename front/src/modules/object-record/utils/{generateFindManyCustomObjectsQuery.ts => useGenerateFindManyCustomObjectsQuery.ts} (67%) rename front/src/modules/object-record/utils/{generateFindOneCustomObjectQuery.ts => useGenerateFindOneCustomObjectQuery.ts} (53%) rename front/src/modules/object-record/utils/{generateUpdateOneObjectMutation.ts => useGenerateUpdateOneObjectMutation.ts} (68%) create mode 100644 front/src/modules/search/hooks/useFilteredSearchEntityQueryV2.ts create mode 100644 front/src/modules/ui/object/field/meta-types/display/utils/getEntityChipFromFieldMetadata.ts create mode 100644 front/src/modules/workspace-member/types/WorkspaceMember.ts delete mode 100644 front/src/modules/workspace/graphql/fragments/workspaceMemberFieldsFragment.ts delete mode 100644 front/src/modules/workspace/graphql/mutations/removeWorkspaceMember.ts delete mode 100644 front/src/modules/workspace/graphql/mutations/updateWorkspaceMember.ts create mode 100644 front/src/modules/workspace/graphql/queries/getCurrentWorkspace.ts delete mode 100644 server/src/coreV2/core.module.ts create mode 100644 server/src/database/migrations/20231115084929_remove_user_workspace_member_relation/migration.sql create mode 100644 server/src/database/migrations/20231115123209_remove_user_settings/migration.sql create mode 100644 server/src/database/migrations/20231115130536_remove_user_settings/migration.sql create mode 100644 server/src/tenant-manager/standard-objects-prefill-data/company.ts create mode 100644 server/src/tenant-manager/standard-objects-prefill-data/person.ts create mode 100644 server/src/tenant-manager/standard-objects-prefill-data/pipeline-step.ts create mode 100644 server/src/tenant-manager/standard-objects-prefill-data/view.ts create mode 100644 server/src/tenant-manager/standard-objects/activity-target.ts create mode 100644 server/src/tenant-manager/standard-objects/activity.ts create mode 100644 server/src/tenant-manager/standard-objects/api-key.ts create mode 100644 server/src/tenant-manager/standard-objects/attachment.ts create mode 100644 server/src/tenant-manager/standard-objects/comment.ts delete mode 100644 server/src/tenant-manager/standard-objects/companies/companies.metadata.ts create mode 100644 server/src/tenant-manager/standard-objects/company.ts create mode 100644 server/src/tenant-manager/standard-objects/favorite.ts create mode 100644 server/src/tenant-manager/standard-objects/opportunity.ts create mode 100644 server/src/tenant-manager/standard-objects/person.ts create mode 100644 server/src/tenant-manager/standard-objects/pipeline-step.ts create mode 100644 server/src/tenant-manager/standard-objects/relations/activity.ts create mode 100644 server/src/tenant-manager/standard-objects/relations/company.ts create mode 100644 server/src/tenant-manager/standard-objects/relations/person.ts create mode 100644 server/src/tenant-manager/standard-objects/relations/pipeline-step.ts create mode 100644 server/src/tenant-manager/standard-objects/relations/view.ts create mode 100644 server/src/tenant-manager/standard-objects/relations/workspace-member.ts create mode 100644 server/src/tenant-manager/standard-objects/standard-object-relation-metadata.ts rename server/src/tenant-manager/standard-objects/{view-fields/view-fields.metadata.ts => view-field.ts} (62%) rename server/src/tenant-manager/standard-objects/{view-filters/view-filters.metadata.ts => view-filter.ts} (66%) rename server/src/tenant-manager/standard-objects/{view-sorts/view-sorts.metadata.ts => view-sort.ts} (61%) rename server/src/tenant-manager/standard-objects/{views/views.metadata.ts => view.ts} (51%) create mode 100644 server/src/tenant-manager/standard-objects/webhook.ts create mode 100644 server/src/tenant-manager/standard-objects/workspace-member.ts diff --git a/front/src/generated-metadata/gql.ts b/front/src/generated-metadata/gql.ts index 533c84926..9c5a2296a 100644 --- a/front/src/generated-metadata/gql.ts +++ b/front/src/generated-metadata/gql.ts @@ -19,7 +19,7 @@ const documents = { "\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: ID!\n $updatePayload: UpdateObjectInput!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n }\n }\n": types.UpdateOneObjectMetadataItemDocument, "\n mutation DeleteOneObjectMetadataItem($idToDelete: ID!) {\n deleteOneObject(input: { id: $idToDelete }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n }\n }\n": types.DeleteOneObjectMetadataItemDocument, "\n mutation DeleteOneFieldMetadataItem($idToDelete: ID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n placeholder\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n }\n }\n": types.DeleteOneFieldMetadataItemDocument, - "\n query ObjectMetadataItems($filter: objectFilter) {\n objects(paging: { first: 1000 }, filter: $filter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n fields(paging: { first: 1000 }) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n placeholder\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n }\n toRelationMetadata {\n id\n relationType\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n": types.ObjectMetadataItemsDocument, + "\n query ObjectMetadataItems($filter: objectFilter) {\n objects(paging: { first: 1000 }, filter: $filter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n fields(paging: { first: 1000 }) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n }\n toRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n": types.ObjectMetadataItemsDocument, }; /** @@ -63,7 +63,7 @@ export function graphql(source: "\n mutation DeleteOneFieldMetadataItem($idToDe /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query ObjectMetadataItems($filter: objectFilter) {\n objects(paging: { first: 1000 }, filter: $filter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n fields(paging: { first: 1000 }) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n placeholder\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n }\n toRelationMetadata {\n id\n relationType\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n"): (typeof documents)["\n query ObjectMetadataItems($filter: objectFilter) {\n objects(paging: { first: 1000 }, filter: $filter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n fields(paging: { first: 1000 }) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n placeholder\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n }\n toRelationMetadata {\n id\n relationType\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n"]; +export function graphql(source: "\n query ObjectMetadataItems($filter: objectFilter) {\n objects(paging: { first: 1000 }, filter: $filter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n fields(paging: { first: 1000 }) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n }\n toRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n"): (typeof documents)["\n query ObjectMetadataItems($filter: objectFilter) {\n objects(paging: { first: 1000 }, filter: $filter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n fields(paging: { first: 1000 }) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n }\n toRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n"]; export function graphql(source: string) { return (documents as any)[source] ?? {}; diff --git a/front/src/generated-metadata/graphql.ts b/front/src/generated-metadata/graphql.ts index 3c3415a4e..51e7b97f9 100644 --- a/front/src/generated-metadata/graphql.ts +++ b/front/src/generated-metadata/graphql.ts @@ -20,6 +20,8 @@ export type Scalars = { DateTime: { input: any; output: any; } /** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ JSON: { input: any; output: any; } + /** The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ + JSONObject: { input: any; output: any; } }; export type Activity = { @@ -695,22 +697,6 @@ export type QueryRelationsArgs = { paging?: CursorPaging; }; -export type RefreshToken = { - __typename?: 'RefreshToken'; - createdAt: Scalars['DateTime']['output']; - expiresAt: Scalars['DateTime']['output']; - id: Scalars['ID']['output']; - updatedAt: Scalars['DateTime']['output']; -}; - -export type RefreshTokenEdge = { - __typename?: 'RefreshTokenEdge'; - /** Cursor for this node. */ - cursor: Scalars['ConnectionCursor']['output']; - /** The node containing the RefreshToken */ - node: RefreshToken; -}; - export type RelationConnection = { __typename?: 'RelationConnection'; /** Array of edges. */ @@ -927,6 +913,22 @@ export type ObjectFilter = { or?: InputMaybe>; }; +export type RefreshTokenV2 = { + __typename?: 'refreshTokenV2'; + createdAt: Scalars['DateTime']['output']; + expiresAt: Scalars['DateTime']['output']; + id: Scalars['ID']['output']; + updatedAt: Scalars['DateTime']['output']; +}; + +export type RefreshTokenV2Edge = { + __typename?: 'refreshTokenV2Edge'; + /** Cursor for this node. */ + cursor: Scalars['ConnectionCursor']['output']; + /** The node containing the refreshTokenV2 */ + node: RefreshTokenV2; +}; + export type Relation = { __typename?: 'relation'; createdAt: Scalars['DateTime']['output']; @@ -949,6 +951,34 @@ export type RelationEdge = { node: Relation; }; +export type UserV2 = { + __typename?: 'userV2'; + avatarUrl: Scalars['String']['output']; + canImpersonate: Scalars['Boolean']['output']; + createdAt: Scalars['DateTime']['output']; + deletedAt?: Maybe; + disabled?: Maybe; + email: Scalars['String']['output']; + emailVerified: Scalars['Boolean']['output']; + firstName: Scalars['String']['output']; + id: Scalars['ID']['output']; + lastName: Scalars['String']['output']; + lastSeen?: Maybe; + locale: Scalars['String']['output']; + metadata?: Maybe; + passwordHash?: Maybe; + phoneNumber?: Maybe; + updatedAt: Scalars['DateTime']['output']; +}; + +export type UserV2Edge = { + __typename?: 'userV2Edge'; + /** Cursor for this node. */ + cursor: Scalars['ConnectionCursor']['output']; + /** The node containing the userV2 */ + node: UserV2; +}; + export type CreateOneObjectMetadataItemMutationVariables = Exact<{ input: CreateOneObjectInput; }>; @@ -998,7 +1028,7 @@ export type ObjectMetadataItemsQueryVariables = Exact<{ }>; -export type ObjectMetadataItemsQuery = { __typename?: 'Query', objects: { __typename?: 'ObjectConnection', totalCount: number, edges: Array<{ __typename?: 'objectEdge', node: { __typename?: 'object', id: string, dataSourceId: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isActive: boolean, isSystem: boolean, createdAt: any, updatedAt: any, fields: { __typename?: 'ObjectFieldsConnection', totalCount: number, edges: Array<{ __typename?: 'fieldEdge', node: { __typename?: 'field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, placeholder?: string | null, isCustom: boolean, isActive: boolean, isNullable: boolean, createdAt: any, updatedAt: any, fromRelationMetadata?: { __typename?: 'relation', id: string, relationType: RelationMetadataType } | null, toRelationMetadata?: { __typename?: 'relation', id: string, relationType: RelationMetadataType } | null } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } }; +export type ObjectMetadataItemsQuery = { __typename?: 'Query', objects: { __typename?: 'ObjectConnection', totalCount: number, edges: Array<{ __typename?: 'objectEdge', node: { __typename?: 'object', id: string, dataSourceId: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isActive: boolean, isSystem: boolean, createdAt: any, updatedAt: any, fields: { __typename?: 'ObjectFieldsConnection', totalCount: number, edges: Array<{ __typename?: 'fieldEdge', node: { __typename?: 'field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom: boolean, isActive: boolean, isNullable: boolean, createdAt: any, updatedAt: any, fromRelationMetadata?: { __typename?: 'relation', id: string, relationType: RelationMetadataType, toObjectMetadata: { __typename?: 'object', id: string, dataSourceId: string, nameSingular: string, namePlural: string }, fromObjectMetadata: { __typename?: 'object', id: string, dataSourceId: string, nameSingular: string, namePlural: string } } | null, toRelationMetadata?: { __typename?: 'relation', id: string, relationType: RelationMetadataType, toObjectMetadata: { __typename?: 'object', id: string, dataSourceId: string, nameSingular: string, namePlural: string }, fromObjectMetadata: { __typename?: 'object', id: string, dataSourceId: string, nameSingular: string, namePlural: string } } | null } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } }; export const CreateOneObjectMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOneObjectMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateOneObjectInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneObject"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"dataSourceId"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}},{"kind":"Field","name":{"kind":"Name","value":"labelSingular"}},{"kind":"Field","name":{"kind":"Name","value":"labelPlural"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; @@ -1007,4 +1037,4 @@ export const UpdateOneFieldMetadataItemDocument = {"kind":"Document","definition export const UpdateOneObjectMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateOneObjectMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"idToUpdate"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"updatePayload"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateObjectInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateOneObject"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"idToUpdate"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"update"},"value":{"kind":"Variable","name":{"kind":"Name","value":"updatePayload"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"dataSourceId"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}},{"kind":"Field","name":{"kind":"Name","value":"labelSingular"}},{"kind":"Field","name":{"kind":"Name","value":"labelPlural"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; export const DeleteOneObjectMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteOneObjectMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"idToDelete"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteOneObject"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"idToDelete"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"dataSourceId"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}},{"kind":"Field","name":{"kind":"Name","value":"labelSingular"}},{"kind":"Field","name":{"kind":"Name","value":"labelPlural"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; export const DeleteOneFieldMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteOneFieldMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"idToDelete"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteOneField"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"idToDelete"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"placeholder"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isNullable"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; -export const ObjectMetadataItemsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ObjectMetadataItems"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"objectFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"objects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"paging"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1000"}}]}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"dataSourceId"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}},{"kind":"Field","name":{"kind":"Name","value":"labelSingular"}},{"kind":"Field","name":{"kind":"Name","value":"labelPlural"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isSystem"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"fields"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"paging"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1000"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"placeholder"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isNullable"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"fromRelationMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"relationType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"toRelationMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"relationType"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file +export const ObjectMetadataItemsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ObjectMetadataItems"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"objectFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"objects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"paging"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1000"}}]}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"dataSourceId"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}},{"kind":"Field","name":{"kind":"Name","value":"labelSingular"}},{"kind":"Field","name":{"kind":"Name","value":"labelPlural"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isSystem"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"fields"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"paging"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1000"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isNullable"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"fromRelationMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"relationType"}},{"kind":"Field","name":{"kind":"Name","value":"toObjectMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"dataSourceId"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fromObjectMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"dataSourceId"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"toRelationMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"relationType"}},{"kind":"Field","name":{"kind":"Name","value":"toObjectMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"dataSourceId"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fromObjectMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"dataSourceId"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/front/src/generated/graphql.tsx b/front/src/generated/graphql.tsx index 6d8cf8ab7..1aff77149 100644 --- a/front/src/generated/graphql.tsx +++ b/front/src/generated/graphql.tsx @@ -16,6 +16,7 @@ export type Scalars = { ConnectionCursor: any; DateTime: string; JSON: any; + JSONObject: any; Upload: any; }; @@ -966,6 +967,15 @@ export type CompanyWhereUniqueInput = { id?: InputMaybe; }; +export type CreateOneRefreshTokenV2Input = { + /** The record to create */ + refreshTokenV2: CreateRefreshTokenInput; +}; + +export type CreateRefreshTokenInput = { + expiresAt: Scalars['DateTime']; +}; + export enum Currency { Aed = 'AED', Afn = 'AFN', @@ -1398,6 +1408,8 @@ export type Mutation = { createOnePerson: Person; createOnePipelineProgress: PipelineProgress; createOnePipelineStage: PipelineStage; + createOneRefreshTokenV2: RefreshTokenV2; + createOneRelation: Relation; createOneWebHook: WebHook; deleteCurrentWorkspace: Workspace; deleteFavorite: Favorite; @@ -1410,6 +1422,7 @@ export type Mutation = { deleteOnePipelineStage: PipelineStage; deleteOneWebHook: WebHook; deleteUserAccount: User; + deleteUserV2: UserV2; deleteWorkspaceMember: WorkspaceMember; impersonate: Verify; renewToken: AuthTokens; @@ -1430,6 +1443,7 @@ export type Mutation = { uploadImage: Scalars['String']; uploadPersonPicture: Scalars['String']; uploadProfilePicture: Scalars['String']; + uploadProfilePictureV2: Scalars['String']; uploadWorkspaceLogo: Scalars['String']; verify: Verify; }; @@ -1515,6 +1529,11 @@ export type MutationCreateOnePipelineStageArgs = { }; +export type MutationCreateOneRefreshTokenV2Args = { + input: CreateOneRefreshTokenV2Input; +}; + + export type MutationCreateOneWebHookArgs = { data: WebHookCreateInput; }; @@ -1660,6 +1679,11 @@ export type MutationUploadProfilePictureArgs = { }; +export type MutationUploadProfilePictureV2Args = { + file: Scalars['Upload']; +}; + + export type MutationUploadWorkspaceLogoArgs = { file: Scalars['Upload']; }; @@ -2409,6 +2433,7 @@ export type Query = { checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValid; clientConfig: ClientConfig; currentUser: User; + currentUserV2: UserV2; currentWorkspace: Workspace; field: Field; fields: FieldConnection; @@ -2428,6 +2453,8 @@ export type Query = { findWorkspaceFromInviteHash: Workspace; object: Object; objects: ObjectConnection; + relation: Relation; + relations: RelationConnection; }; @@ -2560,32 +2587,6 @@ export enum QueryMode { Insensitive = 'insensitive' } -export type RefreshToken = { - __typename?: 'RefreshToken'; - createdAt: Scalars['DateTime']; - expiresAt: Scalars['DateTime']; - id: Scalars['ID']; - updatedAt: Scalars['DateTime']; -}; - -export type RefreshTokenConnection = { - __typename?: 'RefreshTokenConnection'; - /** Array of edges. */ - edges: Array; - /** Paging information */ - pageInfo: PageInfo; - /** Fetch total count of records */ - totalCount: Scalars['Int']; -}; - -export type RefreshTokenEdge = { - __typename?: 'RefreshTokenEdge'; - /** Cursor for this node. */ - cursor: Scalars['ConnectionCursor']; - /** The node containing the RefreshToken */ - node: RefreshToken; -}; - export type RelationConnection = { __typename?: 'RelationConnection'; /** Array of edges. */ @@ -2644,15 +2645,6 @@ export type Support = { supportFrontChatId?: Maybe; }; -export type TUser = { - __typename?: 'TUser'; - email: Scalars['String']; - emailVerified: Scalars['Boolean']; - firstName?: Maybe; - id: Scalars['ID']; - lastName?: Maybe; -}; - export type Telemetry = { __typename?: 'Telemetry'; anonymizationEnabled: Scalars['Boolean']; @@ -2669,6 +2661,7 @@ export type User = { comments?: Maybe>; companies?: Maybe>; createdAt: Scalars['DateTime']; + defaultWorkspaceId?: Maybe; disabled: Scalars['Boolean']; displayName: Scalars['String']; email: Scalars['String']; @@ -2680,11 +2673,8 @@ export type User = { locale: Scalars['String']; metadata?: Maybe; phoneNumber?: Maybe; - settings: UserSettings; - settingsId: Scalars['String']; supportUserHash?: Maybe; updatedAt: Scalars['DateTime']; - workspaceMember?: Maybe; }; export type UserCreateNestedOneWithoutAssignedActivitiesInput = { @@ -2717,6 +2707,7 @@ export type UserOrderByWithRelationInput = { comments?: InputMaybe; companies?: InputMaybe; createdAt?: InputMaybe; + defaultWorkspaceId?: InputMaybe; disabled?: InputMaybe; email?: InputMaybe; emailVerified?: InputMaybe; @@ -2727,8 +2718,6 @@ export type UserOrderByWithRelationInput = { locale?: InputMaybe; metadata?: InputMaybe; phoneNumber?: InputMaybe; - settings?: InputMaybe; - settingsId?: InputMaybe; updatedAt?: InputMaybe; }; @@ -2741,6 +2730,7 @@ export enum UserScalarFieldEnum { AvatarUrl = 'avatarUrl', CanImpersonate = 'canImpersonate', CreatedAt = 'createdAt', + DefaultWorkspaceId = 'defaultWorkspaceId', DeletedAt = 'deletedAt', Disabled = 'disabled', Email = 'email', @@ -2753,7 +2743,6 @@ export enum UserScalarFieldEnum { Metadata = 'metadata', PasswordHash = 'passwordHash', PhoneNumber = 'phoneNumber', - SettingsId = 'settingsId', UpdatedAt = 'updatedAt' } @@ -2765,7 +2754,6 @@ export type UserSettings = { id: Scalars['ID']; locale: Scalars['String']; updatedAt: Scalars['DateTime']; - user?: Maybe; }; export type UserSettingsOrderByWithRelationInput = { @@ -2775,7 +2763,6 @@ export type UserSettingsOrderByWithRelationInput = { id?: InputMaybe; locale?: InputMaybe; updatedAt?: InputMaybe; - user?: InputMaybe; }; export type UserSettingsRelationFilter = { @@ -2783,24 +2770,11 @@ export type UserSettingsRelationFilter = { isNot?: InputMaybe; }; -export type UserSettingsUpdateOneRequiredWithoutUserNestedInput = { - update?: InputMaybe; -}; - export type UserSettingsUpdateOneWithoutWorkspaceMemberNestedInput = { connect?: InputMaybe; disconnect?: InputMaybe; }; -export type UserSettingsUpdateWithoutUserInput = { - WorkspaceMember?: InputMaybe; - colorScheme?: InputMaybe; - createdAt?: InputMaybe; - id?: InputMaybe; - locale?: InputMaybe; - updatedAt?: InputMaybe; -}; - export type UserSettingsWhereInput = { AND?: InputMaybe>; NOT?: InputMaybe>; @@ -2811,7 +2785,6 @@ export type UserSettingsWhereInput = { id?: InputMaybe; locale?: InputMaybe; updatedAt?: InputMaybe; - user?: InputMaybe; }; export type UserSettingsWhereUniqueInput = { @@ -2827,6 +2800,7 @@ export type UserUpdateInput = { comments?: InputMaybe; companies?: InputMaybe; createdAt?: InputMaybe; + defaultWorkspaceId?: InputMaybe; disabled?: InputMaybe; email?: InputMaybe; emailVerified?: InputMaybe; @@ -2837,7 +2811,6 @@ export type UserUpdateInput = { locale?: InputMaybe; metadata?: InputMaybe; phoneNumber?: InputMaybe; - settings?: InputMaybe; updatedAt?: InputMaybe; }; @@ -2845,10 +2818,6 @@ export type UserUpdateOneRequiredWithoutAuthoredActivitiesNestedInput = { connect?: InputMaybe; }; -export type UserUpdateOneRequiredWithoutWorkspaceMemberNestedInput = { - connect?: InputMaybe; -}; - export type UserUpdateOneWithoutAssignedActivitiesNestedInput = { connect?: InputMaybe; disconnect?: InputMaybe; @@ -2871,6 +2840,7 @@ export type UserWhereInput = { comments?: InputMaybe; companies?: InputMaybe; createdAt?: InputMaybe; + defaultWorkspaceId?: InputMaybe; disabled?: InputMaybe; email?: InputMaybe; emailVerified?: InputMaybe; @@ -2881,15 +2851,12 @@ export type UserWhereInput = { locale?: InputMaybe; metadata?: InputMaybe; phoneNumber?: InputMaybe; - settings?: InputMaybe; - settingsId?: InputMaybe; updatedAt?: InputMaybe; }; export type UserWhereUniqueInput = { email?: InputMaybe; id?: InputMaybe; - settingsId?: InputMaybe; }; export type Verify = { @@ -2996,7 +2963,6 @@ export type WorkspaceMember = { settings?: Maybe; settingsId?: Maybe; updatedAt: Scalars['DateTime']; - user: User; userId: Scalars['String']; workspace: Workspace; }; @@ -3040,7 +3006,6 @@ export type WorkspaceMemberOrderByWithRelationInput = { settings?: InputMaybe; settingsId?: InputMaybe; updatedAt?: InputMaybe; - user?: InputMaybe; userId?: InputMaybe; }; @@ -3072,13 +3037,7 @@ export type WorkspaceMemberUpdateInput = { id?: InputMaybe; settings?: InputMaybe; updatedAt?: InputMaybe; - user?: InputMaybe; -}; - -export type WorkspaceMemberUpdateManyWithoutSettingsNestedInput = { - connect?: InputMaybe>; - disconnect?: InputMaybe>; - set?: InputMaybe>; + userId?: InputMaybe; }; export type WorkspaceMemberUpdateManyWithoutWorkspaceNestedInput = { @@ -3118,7 +3077,6 @@ export type WorkspaceMemberWhereInput = { settings?: InputMaybe; settingsId?: InputMaybe; updatedAt?: InputMaybe; - user?: InputMaybe; userId?: InputMaybe; }; @@ -3207,6 +3165,22 @@ export type ObjectEdge = { node: Object; }; +export type RefreshTokenV2 = { + __typename?: 'refreshTokenV2'; + createdAt: Scalars['DateTime']; + expiresAt: Scalars['DateTime']; + id: Scalars['ID']; + updatedAt: Scalars['DateTime']; +}; + +export type RefreshTokenV2Edge = { + __typename?: 'refreshTokenV2Edge'; + /** Cursor for this node. */ + cursor: Scalars['ConnectionCursor']; + /** The node containing the refreshTokenV2 */ + node: RefreshTokenV2; +}; + export type Relation = { __typename?: 'relation'; createdAt: Scalars['DateTime']; @@ -3229,6 +3203,36 @@ export type RelationEdge = { node: Relation; }; +export type UserV2 = { + __typename?: 'userV2'; + avatarUrl: Scalars['String']; + canImpersonate: Scalars['Boolean']; + createdAt: Scalars['DateTime']; + deletedAt?: Maybe; + disabled?: Maybe; + displayName: Scalars['String']; + email: Scalars['String']; + emailVerified: Scalars['Boolean']; + firstName: Scalars['String']; + id: Scalars['ID']; + lastName: Scalars['String']; + lastSeen?: Maybe; + locale: Scalars['String']; + metadata?: Maybe; + passwordHash?: Maybe; + phoneNumber?: Maybe; + supportUserHash?: Maybe; + updatedAt: Scalars['DateTime']; +}; + +export type UserV2Edge = { + __typename?: 'userV2Edge'; + /** Cursor for this node. */ + cursor: Scalars['ConnectionCursor']; + /** The node containing the userV2 */ + node: UserV2; +}; + export type ActivityWithTargetsFragment = { __typename?: 'Activity', id: string, createdAt: string, updatedAt: string, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, createdAt: string, updatedAt: string, companyId?: string | null, personId?: string | null }> | null }; export type ActivityQueryFragmentFragment = { __typename?: 'Activity', id: string, createdAt: string, title?: string | null, body?: string | null, type: ActivityType, completedAt?: string | null, dueAt?: string | null, assignee?: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string, avatarUrl?: string | null } | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } }> | null, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, companyId?: string | null, personId?: string | null, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null, person?: { __typename?: 'Person', id: string, displayName: string, avatarUrl?: string | null } | null }> | null }; @@ -3329,7 +3333,7 @@ export type AuthTokenFragmentFragment = { __typename?: 'AuthToken', token: strin export type AuthTokensFragmentFragment = { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } }; -export type UserQueryFragmentFragment = { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null, avatarUrl?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null }, assignedActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredAttachments?: Array<{ __typename?: 'Attachment', id: string, name: string, type: AttachmentType }> | null, settings?: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } | null, companies?: Array<{ __typename?: 'Company', id: string, name: string, domainName: string }> | null, comments?: Array<{ __typename?: 'Comment', id: string, body: string }> | null } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }; +export type UserQueryFragmentFragment = { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null }; export type ChallengeMutationVariables = Exact<{ email: Scalars['String']; @@ -3344,7 +3348,7 @@ export type ImpersonateMutationVariables = Exact<{ }>; -export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null, avatarUrl?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null }, assignedActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredAttachments?: Array<{ __typename?: 'Attachment', id: string, name: string, type: AttachmentType }> | null, settings?: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } | null, companies?: Array<{ __typename?: 'Company', id: string, name: string, domainName: string }> | null, comments?: Array<{ __typename?: 'Comment', id: string, body: string }> | null } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; +export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; export type RenewTokenMutationVariables = Exact<{ refreshToken: Scalars['String']; @@ -3367,7 +3371,7 @@ export type VerifyMutationVariables = Exact<{ }>; -export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null, avatarUrl?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null }, assignedActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredAttachments?: Array<{ __typename?: 'Attachment', id: string, name: string, type: AttachmentType }> | null, settings?: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } | null, companies?: Array<{ __typename?: 'Company', id: string, name: string, domainName: string }> | null, comments?: Array<{ __typename?: 'Comment', id: string, body: string }> | null } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; +export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; export type CheckUserExistsQueryVariables = Exact<{ email: Scalars['String']; @@ -3744,20 +3748,18 @@ export type UpdateUserMutationVariables = Exact<{ }>; -export type UpdateUserMutation = { __typename?: 'Mutation', updateUser: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null }, assignedActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredAttachments?: Array<{ __typename?: 'Attachment', id: string, name: string, type: AttachmentType }> | null, settings?: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } | null, companies?: Array<{ __typename?: 'Company', id: string, name: string, domainName: string }> | null, comments?: Array<{ __typename?: 'Comment', id: string, body: string }> | null } | null, settings: { __typename?: 'UserSettings', id: string, locale: string, colorScheme: ColorScheme } } }; +export type UpdateUserMutation = { __typename?: 'Mutation', updateUser: { __typename?: 'User', id: string, email: string } }; export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; -export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', avatarUrl?: string | null, canImpersonate: boolean, supportUserHash?: string | null, id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null }, assignedActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredAttachments?: Array<{ __typename?: 'Attachment', id: string, name: string, type: AttachmentType }> | null, settings?: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } | null, companies?: Array<{ __typename?: 'Company', id: string, name: string, domainName: string }> | null, comments?: Array<{ __typename?: 'Comment', id: string, body: string }> | null } | null, settings: { __typename?: 'UserSettings', id: string, locale: string, colorScheme: ColorScheme } } }; +export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', canImpersonate: boolean, supportUserHash?: string | null, id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null } }; export type GetUsersQueryVariables = Exact<{ [key: string]: never; }>; export type GetUsersQuery = { __typename?: 'Query', findManyUser: Array<{ __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null }> }; -export type WorkspaceMemberFieldsFragmentFragment = { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null }, assignedActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredAttachments?: Array<{ __typename?: 'Attachment', id: string, name: string, type: AttachmentType }> | null, settings?: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } | null, companies?: Array<{ __typename?: 'Company', id: string, name: string, domainName: string }> | null, comments?: Array<{ __typename?: 'Comment', id: string, body: string }> | null }; - export type DeleteCurrentWorkspaceMutationVariables = Exact<{ [key: string]: never; }>; @@ -3768,13 +3770,6 @@ export type RemoveWorkspaceLogoMutationVariables = Exact<{ [key: string]: never; export type RemoveWorkspaceLogoMutation = { __typename?: 'Mutation', updateWorkspace: { __typename?: 'Workspace', id: string } }; -export type RemoveWorkspaceMemberMutationVariables = Exact<{ - where: WorkspaceMemberWhereUniqueInput; -}>; - - -export type RemoveWorkspaceMemberMutation = { __typename?: 'Mutation', deleteWorkspaceMember: { __typename?: 'WorkspaceMember', id: string } }; - export type UpdateWorkspaceMutationVariables = Exact<{ data: WorkspaceUpdateInput; }>; @@ -3789,13 +3784,10 @@ export type UploadWorkspaceLogoMutationVariables = Exact<{ export type UploadWorkspaceLogoMutation = { __typename?: 'Mutation', uploadWorkspaceLogo: string }; -export type UpdateOneWorkspaceMemberMutationVariables = Exact<{ - data: WorkspaceMemberUpdateInput; - where: WorkspaceMemberWhereUniqueInput; -}>; +export type GetCurrentWorkspaceQueryVariables = Exact<{ [key: string]: never; }>; -export type UpdateOneWorkspaceMemberMutation = { __typename?: 'Mutation', UpdateOneWorkspaceMember: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null }, assignedActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredActivities?: Array<{ __typename?: 'Activity', id: string, title?: string | null }> | null, authoredAttachments?: Array<{ __typename?: 'Attachment', id: string, name: string, type: AttachmentType }> | null, settings?: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } | null, companies?: Array<{ __typename?: 'Company', id: string, name: string, domainName: string }> | null, comments?: Array<{ __typename?: 'Comment', id: string, body: string }> | null } }; +export type GetCurrentWorkspaceQuery = { __typename?: 'Query', currentWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null } }; export type GetWorkspaceFromInviteHashQueryVariables = Exact<{ inviteHash: Scalars['String']; @@ -3809,7 +3801,7 @@ export type GetWorkspaceMembersQueryVariables = Exact<{ }>; -export type GetWorkspaceMembersQuery = { __typename?: 'Query', workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: string, user: { __typename?: 'User', avatarUrl?: string | null, id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null } }> }; +export type GetWorkspaceMembersQuery = { __typename?: 'Query', workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: string }> }; export const ActivityWithTargetsFragmentDoc = gql` fragment ActivityWithTargets on Activity { @@ -3918,50 +3910,6 @@ export const UserQueryFragmentFragmentDoc = gql` lastName canImpersonate supportUserHash - avatarUrl - workspaceMember { - id - allowImpersonation - workspace { - id - domainName - displayName - logo - inviteHash - } - assignedActivities { - id - title - } - authoredActivities { - id - title - } - authoredAttachments { - id - name - type - } - settings { - id - colorScheme - locale - } - companies { - id - name - domainName - } - comments { - id - body - } - } - settings { - id - colorScheme - locale - } } `; export const BaseAccountOwnerFragmentFragmentDoc = gql` @@ -4034,46 +3982,6 @@ export const UserFieldsFragmentFragmentDoc = gql` lastName } `; -export const WorkspaceMemberFieldsFragmentFragmentDoc = gql` - fragment workspaceMemberFieldsFragment on WorkspaceMember { - id - allowImpersonation - workspace { - id - domainName - displayName - logo - inviteHash - } - assignedActivities { - id - title - } - authoredActivities { - id - title - } - authoredAttachments { - id - name - type - } - settings { - id - colorScheme - locale - } - companies { - id - name - domainName - } - comments { - id - body - } -} - `; export const AddActivityTargetsOnActivityDocument = gql` mutation AddActivityTargetsOnActivity($activityId: String!, $activityTargetInputs: [ActivityTargetCreateManyActivityInput!]!) { updateOneActivity( @@ -6519,52 +6427,6 @@ export const UpdateUserDocument = gql` updateUser(data: $data, where: $where) { id email - displayName - firstName - lastName - avatarUrl - workspaceMember { - id - workspace { - id - domainName - displayName - logo - inviteHash - } - assignedActivities { - id - title - } - authoredActivities { - id - title - } - authoredAttachments { - id - name - type - } - settings { - id - colorScheme - locale - } - companies { - id - name - domainName - } - comments { - id - body - } - } - settings { - id - locale - colorScheme - } } } `; @@ -6599,21 +6461,11 @@ export const GetCurrentUserDocument = gql` query GetCurrentUser { currentUser { ...userFieldsFragment - avatarUrl canImpersonate - workspaceMember { - ...workspaceMemberFieldsFragment - } - settings { - id - locale - colorScheme - } supportUserHash } } - ${UserFieldsFragmentFragmentDoc} -${WorkspaceMemberFieldsFragmentFragmentDoc}`; + ${UserFieldsFragmentFragmentDoc}`; /** * __useGetCurrentUserQuery__ @@ -6739,39 +6591,6 @@ export function useRemoveWorkspaceLogoMutation(baseOptions?: Apollo.MutationHook export type RemoveWorkspaceLogoMutationHookResult = ReturnType; export type RemoveWorkspaceLogoMutationResult = Apollo.MutationResult; export type RemoveWorkspaceLogoMutationOptions = Apollo.BaseMutationOptions; -export const RemoveWorkspaceMemberDocument = gql` - mutation RemoveWorkspaceMember($where: WorkspaceMemberWhereUniqueInput!) { - deleteWorkspaceMember(where: $where) { - id - } -} - `; -export type RemoveWorkspaceMemberMutationFn = Apollo.MutationFunction; - -/** - * __useRemoveWorkspaceMemberMutation__ - * - * To run a mutation, you first call `useRemoveWorkspaceMemberMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useRemoveWorkspaceMemberMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [removeWorkspaceMemberMutation, { data, loading, error }] = useRemoveWorkspaceMemberMutation({ - * variables: { - * where: // value for 'where' - * }, - * }); - */ -export function useRemoveWorkspaceMemberMutation(baseOptions?: Apollo.MutationHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(RemoveWorkspaceMemberDocument, options); - } -export type RemoveWorkspaceMemberMutationHookResult = ReturnType; -export type RemoveWorkspaceMemberMutationResult = Apollo.MutationResult; -export type RemoveWorkspaceMemberMutationOptions = Apollo.BaseMutationOptions; export const UpdateWorkspaceDocument = gql` mutation UpdateWorkspace($data: WorkspaceUpdateInput!) { updateWorkspace(data: $data) { @@ -6839,40 +6658,42 @@ export function useUploadWorkspaceLogoMutation(baseOptions?: Apollo.MutationHook export type UploadWorkspaceLogoMutationHookResult = ReturnType; export type UploadWorkspaceLogoMutationResult = Apollo.MutationResult; export type UploadWorkspaceLogoMutationOptions = Apollo.BaseMutationOptions; -export const UpdateOneWorkspaceMemberDocument = gql` - mutation UpdateOneWorkspaceMember($data: WorkspaceMemberUpdateInput!, $where: WorkspaceMemberWhereUniqueInput!) { - UpdateOneWorkspaceMember(data: $data, where: $where) { - ...workspaceMemberFieldsFragment +export const GetCurrentWorkspaceDocument = gql` + query getCurrentWorkspace { + currentWorkspace { + id + displayName + logo } } - ${WorkspaceMemberFieldsFragmentFragmentDoc}`; -export type UpdateOneWorkspaceMemberMutationFn = Apollo.MutationFunction; + `; /** - * __useUpdateOneWorkspaceMemberMutation__ + * __useGetCurrentWorkspaceQuery__ * - * To run a mutation, you first call `useUpdateOneWorkspaceMemberMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useUpdateOneWorkspaceMemberMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution + * To run a query within a React component, call `useGetCurrentWorkspaceQuery` and pass it any options that fit your needs. + * When your component renders, `useGetCurrentWorkspaceQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; * * @example - * const [updateOneWorkspaceMemberMutation, { data, loading, error }] = useUpdateOneWorkspaceMemberMutation({ + * const { data, loading, error } = useGetCurrentWorkspaceQuery({ * variables: { - * data: // value for 'data' - * where: // value for 'where' * }, * }); */ -export function useUpdateOneWorkspaceMemberMutation(baseOptions?: Apollo.MutationHookOptions) { +export function useGetCurrentWorkspaceQuery(baseOptions?: Apollo.QueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(UpdateOneWorkspaceMemberDocument, options); + return Apollo.useQuery(GetCurrentWorkspaceDocument, options); } -export type UpdateOneWorkspaceMemberMutationHookResult = ReturnType; -export type UpdateOneWorkspaceMemberMutationResult = Apollo.MutationResult; -export type UpdateOneWorkspaceMemberMutationOptions = Apollo.BaseMutationOptions; +export function useGetCurrentWorkspaceLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(GetCurrentWorkspaceDocument, options); + } +export type GetCurrentWorkspaceQueryHookResult = ReturnType; +export type GetCurrentWorkspaceLazyQueryHookResult = ReturnType; +export type GetCurrentWorkspaceQueryResult = Apollo.QueryResult; export const GetWorkspaceFromInviteHashDocument = gql` query GetWorkspaceFromInviteHash($inviteHash: String!) { findWorkspaceFromInviteHash(inviteHash: $inviteHash) { @@ -6914,13 +6735,9 @@ export const GetWorkspaceMembersDocument = gql` query GetWorkspaceMembers($where: WorkspaceMemberWhereInput) { workspaceMembers: findManyWorkspaceMember(where: $where) { id - user { - ...userFieldsFragment - avatarUrl - } } } - ${UserFieldsFragmentFragmentDoc}`; + `; /** * __useGetWorkspaceMembersQuery__ diff --git a/front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts b/front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts index dd831f0c4..2dcc3d6ff 100644 --- a/front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts +++ b/front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts @@ -3,6 +3,7 @@ import { useRecoilState, useRecoilValue } from 'recoil'; import { v4 } from 'uuid'; import { currentUserState } from '@/auth/states/currentUserState'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { GET_COMPANIES } from '@/companies/graphql/queries/getCompanies'; import { GET_PEOPLE } from '@/people/graphql/queries/getPeople'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; @@ -23,6 +24,7 @@ export const useOpenCreateActivityDrawer = () => { const { openRightDrawer } = useRightDrawer(); const [createActivityMutation] = useCreateActivityMutation(); const currentUser = useRecoilValue(currentUserState); + const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const setHotkeyScope = useSetHotkeyScope(); const [, setActivityTargetableEntityArray] = useRecoilState( @@ -49,11 +51,11 @@ export const useOpenCreateActivityDrawer = () => { updatedAt: now, author: { connect: { id: currentUser?.id ?? '' } }, workspaceMemberAuthor: { - connect: { id: currentUser?.workspaceMember?.id ?? '' }, + connect: { id: currentWorkspaceMember?.id ?? '' }, }, assignee: { connect: { id: assigneeId ?? currentUser?.id ?? '' } }, workspaceMemberAssignee: { - connect: { id: currentUser?.workspaceMember?.id ?? '' }, + connect: { id: currentWorkspaceMember?.id ?? '' }, }, type: type, activityTargets: { diff --git a/front/src/modules/activities/tasks/hooks/useCurrentUserDueTaskCount.ts b/front/src/modules/activities/tasks/hooks/useCurrentUserDueTaskCount.ts index a6281c1e5..7da6aa332 100644 --- a/front/src/modules/activities/tasks/hooks/useCurrentUserDueTaskCount.ts +++ b/front/src/modules/activities/tasks/hooks/useCurrentUserDueTaskCount.ts @@ -1,7 +1,8 @@ import { DateTime } from 'luxon'; -import { useRecoilState } from 'recoil'; +import { useRecoilState, useRecoilValue } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { turnFilterIntoWhereClause } from '@/ui/object/object-filter-dropdown/utils/turnFilterIntoWhereClause'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { ActivityType, useGetActivitiesQuery } from '~/generated/graphql'; @@ -9,6 +10,7 @@ import { parseDate } from '~/utils/date-utils'; export const useCurrentUserTaskCount = () => { const [currentUser] = useRecoilState(currentUserState); + const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const { data } = useGetActivitiesQuery({ variables: { @@ -20,8 +22,11 @@ export const useCurrentUserTaskCount = () => { fieldMetadataId: 'assigneeId', value: currentUser.id, operand: ViewFilterOperand.Is, - displayValue: currentUser.displayName, - displayAvatarUrl: currentUser.avatarUrl ?? undefined, + displayValue: + currentWorkspaceMember?.firstName + + ' ' + + currentWorkspaceMember?.lastName, + displayAvatarUrl: currentWorkspaceMember?.avatarUrl ?? undefined, definition: { type: 'ENTITY', }, diff --git a/front/src/modules/apollo/optimistic-effect/hooks/useOptimisticEffect.ts b/front/src/modules/apollo/optimistic-effect/hooks/useOptimisticEffect.ts index b72fe9145..1f95e6dbd 100644 --- a/front/src/modules/apollo/optimistic-effect/hooks/useOptimisticEffect.ts +++ b/front/src/modules/apollo/optimistic-effect/hooks/useOptimisticEffect.ts @@ -9,7 +9,7 @@ import { useRecoilCallback } from 'recoil'; import { GET_COMPANIES } from '@/companies/graphql/queries/getCompanies'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { generateFindManyCustomObjectsQuery } from '@/object-record/utils/generateFindManyCustomObjectsQuery'; +import { useGenerateFindManyCustomObjectsQuery } from '@/object-record/utils/useGenerateFindManyCustomObjectsQuery'; import { GET_PEOPLE } from '@/people/graphql/queries/getPeople'; import { GET_API_KEYS } from '@/settings/developers/graphql/queries/getApiKeys'; import { @@ -54,7 +54,7 @@ export const useOptimisticEffect = () => { objectMetadataItem?: ObjectMetadataItem; }) => { if (isUsingFlexibleBackend && objectMetadataItem) { - const generatedQuery = generateFindManyCustomObjectsQuery({ + const generatedQuery = useGenerateFindManyCustomObjectsQuery({ objectMetadataItem, }); diff --git a/front/src/modules/auth/graphql/fragments/userQueryFragment.ts b/front/src/modules/auth/graphql/fragments/userQueryFragment.ts index 5e46d946c..f89b76831 100644 --- a/front/src/modules/auth/graphql/fragments/userQueryFragment.ts +++ b/front/src/modules/auth/graphql/fragments/userQueryFragment.ts @@ -9,49 +9,5 @@ export const USER_QUERY_FRAGMENT = gql` lastName canImpersonate supportUserHash - avatarUrl - workspaceMember { - id - allowImpersonation - workspace { - id - domainName - displayName - logo - inviteHash - } - assignedActivities { - id - title - } - authoredActivities { - id - title - } - authoredAttachments { - id - name - type - } - settings { - id - colorScheme - locale - } - companies { - id - name - domainName - } - comments { - id - body - } - } - settings { - id - colorScheme - locale - } } `; diff --git a/front/src/modules/auth/hooks/useAuth.ts b/front/src/modules/auth/hooks/useAuth.ts index 1912dc9a9..7024f33d5 100644 --- a/front/src/modules/auth/hooks/useAuth.ts +++ b/front/src/modules/auth/hooks/useAuth.ts @@ -4,12 +4,19 @@ import { snapshot_UNSTABLE, useGotoRecoilSnapshot, useRecoilState, + useSetRecoilState, } from 'recoil'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { isVerifyPendingState } from '@/auth/states/isVerifyPendingState'; +import { CREATE_ONE_WORKSPACE_MEMBER_V2 } from '@/object-record/graphql/mutation/createOneWorkspaceMember'; +import { FIND_ONE_WORKSPACE_MEMBER_V2 } from '@/object-record/graphql/queries/findOneWorkspaceMember'; import { REACT_APP_SERVER_AUTH_URL } from '~/config'; import { useChallengeMutation, useCheckUserExistsLazyQuery, + useGetCurrentWorkspaceLazyQuery, useSignUpMutation, useVerifyMutation, } from '~/generated/graphql'; @@ -19,13 +26,20 @@ import { tokenPairState } from '../states/tokenPairState'; export const useAuth = () => { const [, setTokenPair] = useRecoilState(tokenPairState); - const [, setCurrentUser] = useRecoilState(currentUserState); + const setCurrentUser = useSetRecoilState(currentUserState); + const setCurrentWorkspaceMember = useSetRecoilState( + currentWorkspaceMemberState, + ); + const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); + const setIsVerifyPendingState = useSetRecoilState(isVerifyPendingState); const [challenge] = useChallengeMutation(); const [signUp] = useSignUpMutation(); const [verify] = useVerifyMutation(); const [checkUserExistsQuery, { data: checkUserExistsData }] = useCheckUserExistsLazyQuery(); + const [getCurrentWorkspaceQuery, { data: getCurrentWorkspaceData }] = + useGetCurrentWorkspaceLazyQuery(); const client = useApolloClient(); @@ -67,36 +81,56 @@ export const useAuth = () => { throw new Error('No verify result'); } - if (!verifyResult.data?.verify.user.workspaceMember) { - throw new Error('No workspace member'); - } - - if (!verifyResult.data?.verify.user.workspaceMember.settings) { - throw new Error('No settings'); - } - - setCurrentUser({ - ...verifyResult.data?.verify.user, - workspaceMember: { - ...verifyResult.data?.verify.user.workspaceMember, - settings: verifyResult.data?.verify.user.workspaceMember.settings, + setTokenPair(verifyResult.data?.verify.tokens); + const workspaceMember = await client.query({ + query: FIND_ONE_WORKSPACE_MEMBER_V2, + variables: { + filter: { + userId: { eq: verifyResult.data?.verify.user.id }, + }, }, }); - setTokenPair(verifyResult.data?.verify.tokens); + const currentWorkspace = await getCurrentWorkspaceQuery(); - return verifyResult.data?.verify; + setCurrentUser(verifyResult.data?.verify.user); + setCurrentWorkspaceMember(workspaceMember.data?.findMany); + setCurrentWorkspace(currentWorkspace.data?.currentWorkspace ?? null); + return { + user: verifyResult.data?.verify.user, + workspaceMember: workspaceMember.data?.findMany, + workspace: currentWorkspace.data?.currentWorkspace, + tokens: verifyResult.data?.verify.tokens, + }; }, - [setTokenPair, verify, setCurrentUser], + [ + verify, + setTokenPair, + client, + getCurrentWorkspaceQuery, + setCurrentUser, + setCurrentWorkspaceMember, + setCurrentWorkspace, + ], ); const handleCrendentialsSignIn = useCallback( async (email: string, password: string) => { const { loginToken } = await handleChallenge(email, password); + setIsVerifyPendingState(true); - const { user } = await handleVerify(loginToken.token); - return user; + const { user, workspaceMember, workspace } = await handleVerify( + loginToken.token, + ); + + setIsVerifyPendingState(false); + + return { + user, + workspaceMember, + workspace, + }; }, - [handleChallenge, handleVerify], + [handleChallenge, handleVerify, setIsVerifyPendingState], ); const handleSignOut = useCallback(() => { @@ -110,6 +144,8 @@ export const useAuth = () => { const handleCredentialsSignUp = useCallback( async (email: string, password: string, workspaceInviteHash?: string) => { + setIsVerifyPendingState(true); + const signUpResult = await signUp({ variables: { email, @@ -126,13 +162,30 @@ export const useAuth = () => { throw new Error('No login token'); } - const { user } = await handleVerify( + const { user, workspace } = await handleVerify( signUpResult.data?.signUp.loginToken.token, ); - return user; + const workspaceMember = await client.mutate({ + mutation: CREATE_ONE_WORKSPACE_MEMBER_V2, + variables: { + input: { + firstName: user.firstName ?? '', + lastName: user.lastName ?? '', + colorScheme: 'Light', + userId: user.id, + allowImpersonation: true, + locale: 'en', + }, + }, + }); + setCurrentWorkspaceMember(workspaceMember.data?.createWorkspaceMemberV2); + + setIsVerifyPendingState(false); + + return { user, workspaceMember, workspace }; }, - [signUp, handleVerify], + [setIsVerifyPendingState, signUp, handleVerify, client], ); const handleGoogleLogin = useCallback((workspaceInviteHash?: string) => { diff --git a/front/src/modules/auth/hooks/useIsLogged.ts b/front/src/modules/auth/hooks/useIsLogged.ts index 1ec60a1cc..4399b564c 100644 --- a/front/src/modules/auth/hooks/useIsLogged.ts +++ b/front/src/modules/auth/hooks/useIsLogged.ts @@ -1,9 +1,12 @@ -import { useRecoilState } from 'recoil'; +import { useRecoilState, useRecoilValue } from 'recoil'; + +import { isVerifyPendingState } from '@/auth/states/isVerifyPendingState'; import { tokenPairState } from '../states/tokenPairState'; export const useIsLogged = (): boolean => { const [tokenPair] = useRecoilState(tokenPairState); + const isVerifyPending = useRecoilValue(isVerifyPendingState); - return !!tokenPair; + return !!tokenPair && !isVerifyPending; }; diff --git a/front/src/modules/auth/hooks/useOnboardingStatus.ts b/front/src/modules/auth/hooks/useOnboardingStatus.ts index 37d7579eb..1fe60e462 100644 --- a/front/src/modules/auth/hooks/useOnboardingStatus.ts +++ b/front/src/modules/auth/hooks/useOnboardingStatus.ts @@ -1,15 +1,26 @@ -import { useRecoilState } from 'recoil'; +import { useRecoilValue } from 'recoil'; + +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useIsLogged } from '../hooks/useIsLogged'; -import { currentUserState } from '../states/currentUserState'; import { getOnboardingStatus, OnboardingStatus, } from '../utils/getOnboardingStatus'; export const useOnboardingStatus = (): OnboardingStatus | undefined => { - const [currentUser] = useRecoilState(currentUserState); + const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspace = useRecoilValue(currentWorkspaceState); const isLoggedIn = useIsLogged(); - return getOnboardingStatus(isLoggedIn, currentUser); + console.log( + getOnboardingStatus(isLoggedIn, currentWorkspaceMember, currentWorkspace), + ); + + return getOnboardingStatus( + isLoggedIn, + currentWorkspaceMember, + currentWorkspace, + ); }; diff --git a/front/src/modules/auth/sign-in-up/hooks/useSignInUp.tsx b/front/src/modules/auth/sign-in-up/hooks/useSignInUp.tsx index 7d8212bdc..70ce84b18 100644 --- a/front/src/modules/auth/sign-in-up/hooks/useSignInUp.tsx +++ b/front/src/modules/auth/sign-in-up/hooks/useSignInUp.tsx @@ -62,7 +62,7 @@ export const useSignInUp = () => { }); const [showErrors, setShowErrors] = useState(false); - const { data: workspace } = useGetWorkspaceFromInviteHashQuery({ + const { data: workspaceFromInviteHash } = useGetWorkspaceFromInviteHashQuery({ variables: { inviteHash: workspaceInviteHash || '' }, }); @@ -119,20 +119,23 @@ export const useSignInUp = () => { if (!data.email || !data.password) { throw new Error('Email and password are required'); } - let user; + let currentWorkspace; + if (signInUpMode === SignInUpMode.SignIn) { - user = await signInWithCredentials( + const { workspace } = await signInWithCredentials( data.email.toLowerCase(), data.password, ); + currentWorkspace = workspace; } else { - user = await signUpWithCredentials( + const { workspace } = await signUpWithCredentials( data.email.toLowerCase(), data.password, workspaceInviteHash, ); + currentWorkspace = workspace; } - if (user?.workspaceMember?.workspace?.displayName) { + if (currentWorkspace?.displayName) { navigate('/'); } else { navigate('/create/workspace'); @@ -144,12 +147,12 @@ export const useSignInUp = () => { } }, [ - navigate, + signInUpMode, signInWithCredentials, signUpWithCredentials, workspaceInviteHash, + navigate, enqueueSnackBar, - signInUpMode, ], ); @@ -188,6 +191,6 @@ export const useSignInUp = () => { goBackToEmailStep, submitCredentials, form, - workspace: workspace?.findWorkspaceFromInviteHash, + workspace: workspaceFromInviteHash?.findWorkspaceFromInviteHash, }; }; diff --git a/front/src/modules/auth/states/currentUserState.ts b/front/src/modules/auth/states/currentUserState.ts index 7c32b1b20..db3ed7b67 100644 --- a/front/src/modules/auth/states/currentUserState.ts +++ b/front/src/modules/auth/states/currentUserState.ts @@ -1,32 +1,11 @@ import { atom } from 'recoil'; -import { - User, - UserSettings, - Workspace, - WorkspaceMember, -} from '~/generated/graphql'; +import { User } from '~/generated/graphql'; export type CurrentUser = Pick< User, - | 'id' - | 'email' - | 'displayName' - | 'firstName' - | 'lastName' - | 'avatarUrl' - | 'canImpersonate' - | 'supportUserHash' -> & { - workspaceMember: Pick & { - workspace: Pick< - Workspace, - 'id' | 'displayName' | 'domainName' | 'inviteHash' | 'logo' - >; - settings: Pick; - }; - settings: Pick; -}; + 'id' | 'email' | 'supportUserHash' | 'canImpersonate' +>; export const currentUserState = atom({ key: 'currentUserState', diff --git a/front/src/modules/auth/states/currentWorkspaceMemberState.ts b/front/src/modules/auth/states/currentWorkspaceMemberState.ts new file mode 100644 index 000000000..16db1f4a1 --- /dev/null +++ b/front/src/modules/auth/states/currentWorkspaceMemberState.ts @@ -0,0 +1,18 @@ +import { atom } from 'recoil'; + +import { ColorScheme } from '~/generated-metadata/graphql'; + +export type CurrentWorkspaceMember = { + id: string; + locale: string; + colorScheme: ColorScheme; + allowImpersonation: boolean; + firstName: string; + lastName: string; + avatarUrl: string; +}; + +export const currentWorkspaceMemberState = atom({ + key: 'currentWorkspaceMemberState', + default: null, +}); diff --git a/front/src/modules/auth/states/currentWorkspaceState.ts b/front/src/modules/auth/states/currentWorkspaceState.ts new file mode 100644 index 000000000..34e17a4ef --- /dev/null +++ b/front/src/modules/auth/states/currentWorkspaceState.ts @@ -0,0 +1,13 @@ +import { atom } from 'recoil'; + +import { Workspace } from '~/generated-metadata/graphql'; + +export type CurrentWorkspace = Pick< + Workspace, + 'id' | 'inviteHash' | 'logo' | 'displayName' +>; + +export const currentWorkspaceState = atom({ + key: 'currentWorkspaceState', + default: null, +}); diff --git a/front/src/modules/auth/states/isVerifyPendingState.ts b/front/src/modules/auth/states/isVerifyPendingState.ts new file mode 100644 index 000000000..6986efab0 --- /dev/null +++ b/front/src/modules/auth/states/isVerifyPendingState.ts @@ -0,0 +1,6 @@ +import { atom } from 'recoil'; + +export const isVerifyPendingState = atom({ + key: 'isVerifyPendingState', + default: false, +}); diff --git a/front/src/modules/auth/utils/getOnboardingStatus.ts b/front/src/modules/auth/utils/getOnboardingStatus.ts index 79fbac7fb..87b39743d 100644 --- a/front/src/modules/auth/utils/getOnboardingStatus.ts +++ b/front/src/modules/auth/utils/getOnboardingStatus.ts @@ -1,4 +1,5 @@ -import { CurrentUser } from '../states/currentUserState'; +import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState'; +import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState'; export enum OnboardingStatus { OngoingUserCreation = 'ongoing_user_creation', @@ -9,21 +10,22 @@ export enum OnboardingStatus { export const getOnboardingStatus = ( isLoggedIn: boolean, - currentUser: CurrentUser | null, + currentWorkspaceMember: CurrentWorkspaceMember | null, + currentWorkspace: CurrentWorkspace | null, ) => { if (!isLoggedIn) { return OnboardingStatus.OngoingUserCreation; } // if the user has not been fetched yet, we can't know the onboarding status - if (!currentUser) { + if (!currentWorkspaceMember) { return undefined; } - if (!currentUser.workspaceMember?.workspace.displayName) { + if (!currentWorkspace?.displayName) { return OnboardingStatus.OngoingWorkspaceCreation; } - if (!currentUser.firstName || !currentUser.lastName) { + if (!currentWorkspaceMember.firstName || !currentWorkspaceMember.lastName) { return OnboardingStatus.OngoingProfileCreation; } diff --git a/front/src/modules/object-metadata/graphql/queries.ts b/front/src/modules/object-metadata/graphql/queries.ts index f0622cf7d..c31b2fe5e 100644 --- a/front/src/modules/object-metadata/graphql/queries.ts +++ b/front/src/modules/object-metadata/graphql/queries.ts @@ -27,7 +27,6 @@ export const FIND_MANY_METADATA_OBJECTS = gql` label description icon - placeholder isCustom isActive isNullable @@ -36,10 +35,34 @@ export const FIND_MANY_METADATA_OBJECTS = gql` fromRelationMetadata { id relationType + toObjectMetadata { + id + dataSourceId + nameSingular + namePlural + } + fromObjectMetadata { + id + dataSourceId + nameSingular + namePlural + } } toRelationMetadata { id relationType + toObjectMetadata { + id + dataSourceId + nameSingular + namePlural + } + fromObjectMetadata { + id + dataSourceId + nameSingular + namePlural + } } } } diff --git a/front/src/modules/object-metadata/hooks/useFindManyObjectMetadataItems.ts b/front/src/modules/object-metadata/hooks/useFindManyObjectMetadataItems.ts index 4b26456ae..85228fa6c 100644 --- a/front/src/modules/object-metadata/hooks/useFindManyObjectMetadataItems.ts +++ b/front/src/modules/object-metadata/hooks/useFindManyObjectMetadataItems.ts @@ -10,7 +10,7 @@ import { import { logError } from '~/utils/logError'; import { FIND_MANY_METADATA_OBJECTS } from '../graphql/queries'; -import { formatPagedObjectMetadataItemsToObjectMetadataItems } from '../utils/formatPagedObjectMetadataItemsToObjectMetadataItems'; +import { mapPaginatedObjectMetadataItemsToObjectMetadataItems } from '../utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems'; import { useApolloMetadataClient } from './useApolloMetadataClient'; @@ -59,7 +59,7 @@ export const useFindManyObjectMetadataItems = ({ }); const objectMetadataItems = useMemo(() => { - return formatPagedObjectMetadataItemsToObjectMetadataItems({ + return mapPaginatedObjectMetadataItemsToObjectMetadataItems({ pagedObjectMetadataItems: data, }); }, [data]); diff --git a/front/src/modules/object-metadata/hooks/useFindManyRelationMetadataItems.ts b/front/src/modules/object-metadata/hooks/useFindManyRelationMetadataItems.ts new file mode 100644 index 000000000..85228fa6c --- /dev/null +++ b/front/src/modules/object-metadata/hooks/useFindManyRelationMetadataItems.ts @@ -0,0 +1,74 @@ +import { useMemo } from 'react'; +import { useQuery } from '@apollo/client'; + +import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar'; +import { + ObjectFilter, + ObjectMetadataItemsQuery, + ObjectMetadataItemsQueryVariables, +} from '~/generated-metadata/graphql'; +import { logError } from '~/utils/logError'; + +import { FIND_MANY_METADATA_OBJECTS } from '../graphql/queries'; +import { mapPaginatedObjectMetadataItemsToObjectMetadataItems } from '../utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems'; + +import { useApolloMetadataClient } from './useApolloMetadataClient'; + +// TODO: test fetchMore +export const useFindManyObjectMetadataItems = ({ + skip, + filter, +}: { skip?: boolean; filter?: ObjectFilter } = {}) => { + const apolloMetadataClient = useApolloMetadataClient(); + + const { enqueueSnackBar } = useSnackBar(); + + const { + data, + fetchMore: fetchMoreInternal, + loading, + error, + } = useQuery( + FIND_MANY_METADATA_OBJECTS, + { + variables: { + filter, + }, + client: apolloMetadataClient ?? undefined, + skip: skip || !apolloMetadataClient, + onError: (error) => { + logError('useFindManyObjectMetadataItems error : ' + error); + enqueueSnackBar( + `Error during useFindManyObjectMetadataItems, ${error.message}`, + { + variant: 'error', + }, + ); + }, + onCompleted: () => {}, + }, + ); + + const hasMore = data?.objects?.pageInfo?.hasNextPage; + + const fetchMore = () => + fetchMoreInternal({ + variables: { + afterCursor: data?.objects?.pageInfo?.endCursor, + }, + }); + + const objectMetadataItems = useMemo(() => { + return mapPaginatedObjectMetadataItemsToObjectMetadataItems({ + pagedObjectMetadataItems: data, + }); + }, [data]); + + return { + objectMetadataItems, + hasMore, + fetchMore, + loading, + error, + }; +}; diff --git a/front/src/modules/object-metadata/hooks/useFindOneObjectMetadataItem.ts b/front/src/modules/object-metadata/hooks/useFindOneObjectMetadataItem.ts index f0a5ff1f6..85c3427ec 100644 --- a/front/src/modules/object-metadata/hooks/useFindOneObjectMetadataItem.ts +++ b/front/src/modules/object-metadata/hooks/useFindOneObjectMetadataItem.ts @@ -1,10 +1,10 @@ import { gql } from '@apollo/client'; -import { generateCreateOneObjectMutation } from '@/object-record/utils/generateCreateOneObjectMutation'; -import { generateDeleteOneObjectMutation } from '@/object-record/utils/generateDeleteOneObjectMutation'; -import { generateFindManyCustomObjectsQuery } from '@/object-record/utils/generateFindManyCustomObjectsQuery'; -import { generateFindOneCustomObjectQuery } from '@/object-record/utils/generateFindOneCustomObjectQuery'; -import { generateUpdateOneObjectMutation } from '@/object-record/utils/generateUpdateOneObjectMutation'; +import { useGenerateCreateOneObjectMutation } from '@/object-record/utils/generateCreateOneObjectMutation'; +import { useGenerateDeleteOneObjectMutation } from '@/object-record/utils/useGenerateDeleteOneObjectMutation'; +import { useGenerateFindManyCustomObjectsQuery } from '@/object-record/utils/useGenerateFindManyCustomObjectsQuery'; +import { useGenerateFindOneCustomObjectQuery } from '@/object-record/utils/useGenerateFindOneCustomObjectQuery'; +import { useGenerateUpdateOneObjectMutation } from '@/object-record/utils/useGenerateUpdateOneObjectMutation'; import { useLazyLoadIcons } from '@/ui/input/hooks/useLazyLoadIcons'; import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata'; import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition'; @@ -16,13 +16,13 @@ import { formatFieldMetadataItemsAsSortDefinitions } from '../utils/formatFieldM import { useFindManyObjectMetadataItems } from './useFindManyObjectMetadataItems'; -const EMPTY_QUERY = gql` +export const EMPTY_QUERY = gql` query EmptyQuery { empty } `; -const EMPTY_MUTATION = gql` +export const EMPTY_MUTATION = gql` mutation EmptyMutation { empty } @@ -74,35 +74,25 @@ export const useFindOneObjectMetadataItem = ({ icons, }); - const findManyQuery = foundObjectMetadataItem - ? generateFindManyCustomObjectsQuery({ - objectMetadataItem: foundObjectMetadataItem, - }) - : EMPTY_QUERY; + const findManyQuery = useGenerateFindManyCustomObjectsQuery({ + objectMetadataItem: foundObjectMetadataItem, + }); - const findOneQuery = foundObjectMetadataItem - ? generateFindOneCustomObjectQuery({ - objectMetadataItem: foundObjectMetadataItem, - }) - : EMPTY_QUERY; + const findOneQuery = useGenerateFindOneCustomObjectQuery({ + objectMetadataItem: foundObjectMetadataItem, + }); - const createOneMutation = foundObjectMetadataItem - ? generateCreateOneObjectMutation({ - objectMetadataItem: foundObjectMetadataItem, - }) - : EMPTY_MUTATION; + const createOneMutation = useGenerateCreateOneObjectMutation({ + objectMetadataItem: foundObjectMetadataItem, + }); - const updateOneMutation = foundObjectMetadataItem - ? generateUpdateOneObjectMutation({ - objectMetadataItem: foundObjectMetadataItem, - }) - : EMPTY_MUTATION; + const updateOneMutation = useGenerateUpdateOneObjectMutation({ + objectMetadataItem: foundObjectMetadataItem, + }); - const deleteOneMutation = foundObjectMetadataItem - ? generateDeleteOneObjectMutation({ - objectMetadataItem: foundObjectMetadataItem, - }) - : EMPTY_MUTATION; + const deleteOneMutation = useGenerateDeleteOneObjectMutation({ + objectMetadataItem: foundObjectMetadataItem, + }); return { foundObjectMetadataItem, diff --git a/front/src/modules/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery.ts b/front/src/modules/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery.ts new file mode 100644 index 000000000..a005c83c4 --- /dev/null +++ b/front/src/modules/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery.ts @@ -0,0 +1,86 @@ +import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems'; +import { FieldType } from '@/ui/object/field/types/FieldType'; + +import { FieldMetadataItem } from '../types/FieldMetadataItem'; + +export const useMapFieldMetadataToGraphQLQuery = () => { + const { objectMetadataItems } = useFindManyObjectMetadataItems(); + + const mapFieldMetadataToGraphQLQuery = (field: FieldMetadataItem): any => { + // TODO: parse + const fieldType = field.type as FieldType; + + const fieldIsSimpleValue = ( + [ + 'TEXT', + 'PHONE', + 'DATE', + 'EMAIL', + 'NUMBER', + 'BOOLEAN', + 'DATE', + ] as FieldType[] + ).includes(fieldType); + + const fieldIsURL = fieldType === 'URL' || fieldType === 'URL_V2'; + + const fieldIsMoneyAmount = + fieldType === 'MONEY' || fieldType === 'MONEY_AMOUNT_V2'; + + if (fieldIsSimpleValue) { + return field.name; + } else if ( + fieldType === 'RELATION' && + field.toRelationMetadata?.relationType === 'ONE_TO_MANY' + ) { + console.log({ objectMetadataItems, field }); + + const relationMetadataItem = objectMetadataItems.find( + (objectMetadataItem) => + objectMetadataItem.id === + (field.toRelationMetadata as any)?.fromObjectMetadata?.id, + ); + + console.log({ relationMetadataItem }); + + return `${field.name} + { + id + ${relationMetadataItem?.fields + .filter((field) => field.type !== 'RELATION') + .map((field) => field.name) + .join('\n')} + }`; + } else if ( + fieldType === 'RELATION' && + field.fromRelationMetadata?.relationType === 'ONE_TO_MANY' + ) { + return `${field.name} + { + edges { + node { + id + } + } + }`; + } else if (fieldIsURL) { + return ` + ${field.name} + { + text + link + } + `; + } else if (fieldIsMoneyAmount) { + return ` + ${field.name} + { + amount + currency + } + `; + } + }; + + return mapFieldMetadataToGraphQLQuery; +}; diff --git a/front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts b/front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts deleted file mode 100644 index 2e26d39c9..000000000 --- a/front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { FieldType } from '@/ui/object/field/types/FieldType'; - -import { FieldMetadataItem } from '../types/FieldMetadataItem'; - -export const mapFieldMetadataToGraphQLQuery = (field: FieldMetadataItem) => { - // TODO: parse - const fieldType = field.type as FieldType; - - const fieldIsSimpleValue = ( - [ - 'TEXT', - 'PHONE', - 'DATE', - 'EMAIL', - 'NUMBER', - 'BOOLEAN', - 'DATE', - ] as FieldType[] - ).includes(fieldType); - - const fieldIsURL = fieldType === 'URL' || fieldType === 'URL_V2'; - - const fieldIsMoneyAmount = - fieldType === 'MONEY' || fieldType === 'MONEY_AMOUNT_V2'; - - if (fieldIsSimpleValue) { - return field.name; - } else if ( - fieldType === 'RELATION' && - field.toRelationMetadata?.relationType === 'ONE_TO_MANY' - ) { - return `${field.name} - { - id - }`; - } else if ( - fieldType === 'RELATION' && - field.fromRelationMetadata?.relationType === 'ONE_TO_MANY' - ) { - return `${field.name} - { - edges { - node { - id - } - } - }`; - } else if (fieldIsURL) { - return ` - ${field.name} - { - text - link - } - `; - } else if (fieldIsMoneyAmount) { - return ` - ${field.name} - { - amount - currency - } - `; - } -}; diff --git a/front/src/modules/object-metadata/utils/formatPagedObjectMetadataItemsToObjectMetadataItems.ts b/front/src/modules/object-metadata/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems.ts similarity index 79% rename from front/src/modules/object-metadata/utils/formatPagedObjectMetadataItemsToObjectMetadataItems.ts rename to front/src/modules/object-metadata/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems.ts index 41b69bdd1..9c714cb95 100644 --- a/front/src/modules/object-metadata/utils/formatPagedObjectMetadataItemsToObjectMetadataItems.ts +++ b/front/src/modules/object-metadata/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems.ts @@ -2,8 +2,8 @@ import { ObjectMetadataItemsQuery } from '~/generated-metadata/graphql'; import { ObjectMetadataItem } from '../types/ObjectMetadataItem'; -export const formatPagedObjectMetadataItemsToObjectMetadataItems = ({ - pagedObjectMetadataItems: pagedObjectMetadataItems, +export const mapPaginatedObjectMetadataItemsToObjectMetadataItems = ({ + pagedObjectMetadataItems, }: { pagedObjectMetadataItems: ObjectMetadataItemsQuery | undefined; }) => { diff --git a/front/src/modules/object-record/graphql/mutation/createOneWorkspaceMember.ts b/front/src/modules/object-record/graphql/mutation/createOneWorkspaceMember.ts new file mode 100644 index 000000000..038f2bcb5 --- /dev/null +++ b/front/src/modules/object-record/graphql/mutation/createOneWorkspaceMember.ts @@ -0,0 +1,11 @@ +import { gql } from '@apollo/client'; + +export const CREATE_ONE_WORKSPACE_MEMBER_V2 = gql` + mutation CreateOneWorkspaceMemberV2($input: WorkspaceMemberV2CreateInput!) { + createWorkspaceMemberV2(data: $input) { + id + firstName + lastName + } + } +`; diff --git a/front/src/modules/object-record/graphql/queries/findOneWorkspaceMember.ts b/front/src/modules/object-record/graphql/queries/findOneWorkspaceMember.ts new file mode 100644 index 000000000..6d89c04cc --- /dev/null +++ b/front/src/modules/object-record/graphql/queries/findOneWorkspaceMember.ts @@ -0,0 +1,15 @@ +import { gql } from '@apollo/client'; + +export const FIND_ONE_WORKSPACE_MEMBER_V2 = gql` + query FindManyWorkspaceMembersV2($filter: WorkspaceMemberV2FilterInput) { + workspaceMembersV2(filter: $filter) { + edges { + node { + id + firstName + lastName + } + } + } + } +`; diff --git a/front/src/modules/object-record/hooks/useDeleteOneObjectRecord.ts b/front/src/modules/object-record/hooks/useDeleteOneObjectRecord.ts index fe45e4064..39125bd96 100644 --- a/front/src/modules/object-record/hooks/useDeleteOneObjectRecord.ts +++ b/front/src/modules/object-record/hooks/useDeleteOneObjectRecord.ts @@ -20,12 +20,10 @@ export const useDeleteOneObjectRecord = ({ const [mutate] = useMutation(deleteOneMutation); const deleteOneObject = foundObjectMetadataItem - ? (input: Record) => { + ? (idToDelete: string) => { return mutate({ variables: { - input: { - ...input, - }, + idToDelete, }, refetchQueries: [getOperationName(findManyQuery) ?? ''], }); diff --git a/front/src/modules/object-record/hooks/useFindManyObjectRecords.ts b/front/src/modules/object-record/hooks/useFindManyObjectRecords.ts index 812cc0de0..a28dfeded 100644 --- a/front/src/modules/object-record/hooks/useFindManyObjectRecords.ts +++ b/front/src/modules/object-record/hooks/useFindManyObjectRecords.ts @@ -18,7 +18,7 @@ import { PaginatedObjectTypeEdge, PaginatedObjectTypeResults, } from '../types/PaginatedObjectTypeResults'; -import { formatPagedObjectsToObjects } from '../utils/formatPagedObjectsToObjects'; +import { mapPaginatedObjectsToObjects } from '../utils/mapPaginatedObjectsToObjects'; // TODO: test with a wrong name // TODO: add zod to validate that we have at least id on each object @@ -163,7 +163,7 @@ export const useFindManyObjectRecords = < const objects = useMemo( () => objectNamePlural - ? formatPagedObjectsToObjects({ + ? mapPaginatedObjectsToObjects({ pagedObjects: data, objectNamePlural, }) diff --git a/front/src/modules/object-record/hooks/useUpdateOneObjectRecord.ts b/front/src/modules/object-record/hooks/useUpdateOneObjectRecord.ts index 6ff8b8fbb..95576577b 100644 --- a/front/src/modules/object-record/hooks/useUpdateOneObjectRecord.ts +++ b/front/src/modules/object-record/hooks/useUpdateOneObjectRecord.ts @@ -16,6 +16,8 @@ export const useUpdateOneObjectRecord = ({ objectNameSingular, }); + console.log('update one object'); + // TODO: type this with a minimal type at least with Record const [mutate] = useMutation(updateOneMutation); diff --git a/front/src/modules/object-record/utils/generateCreateOneObjectMutation.ts b/front/src/modules/object-record/utils/generateCreateOneObjectMutation.ts index da6365555..0bd81ad52 100644 --- a/front/src/modules/object-record/utils/generateCreateOneObjectMutation.ts +++ b/front/src/modules/object-record/utils/generateCreateOneObjectMutation.ts @@ -1,14 +1,21 @@ import { gql } from '@apollo/client'; +import { EMPTY_MUTATION } from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; +import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery'; import { capitalize } from '~/utils/string/capitalize'; -export const generateCreateOneObjectMutation = ({ +export const useGenerateCreateOneObjectMutation = ({ objectMetadataItem, }: { - objectMetadataItem: ObjectMetadataItem; + objectMetadataItem: ObjectMetadataItem | undefined | null; }) => { + const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); + + if (!objectMetadataItem) { + return EMPTY_MUTATION; + } + const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular); return gql` diff --git a/front/src/modules/object-record/utils/formatPagedObjectsToObjects.ts b/front/src/modules/object-record/utils/mapPaginatedObjectsToObjects.ts similarity index 91% rename from front/src/modules/object-record/utils/formatPagedObjectsToObjects.ts rename to front/src/modules/object-record/utils/mapPaginatedObjectsToObjects.ts index 8e40202b7..7ba7d8f53 100644 --- a/front/src/modules/object-record/utils/formatPagedObjectsToObjects.ts +++ b/front/src/modules/object-record/utils/mapPaginatedObjectsToObjects.ts @@ -1,4 +1,4 @@ -export const formatPagedObjectsToObjects = < +export const mapPaginatedObjectsToObjects = < ObjectType extends { id: string } & Record, ObjectTypeQuery extends { [objectNamePlural: string]: { diff --git a/front/src/modules/object-record/utils/generateDeleteOneObjectMutation.ts b/front/src/modules/object-record/utils/useGenerateDeleteOneObjectMutation.ts similarity index 53% rename from front/src/modules/object-record/utils/generateDeleteOneObjectMutation.ts rename to front/src/modules/object-record/utils/useGenerateDeleteOneObjectMutation.ts index d17fe6106..3e0533c45 100644 --- a/front/src/modules/object-record/utils/generateDeleteOneObjectMutation.ts +++ b/front/src/modules/object-record/utils/useGenerateDeleteOneObjectMutation.ts @@ -1,14 +1,19 @@ import { gql } from '@apollo/client'; +import { EMPTY_MUTATION } from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { capitalize } from '~/utils/string/capitalize'; -export const generateDeleteOneObjectMutation = ({ +export const useGenerateDeleteOneObjectMutation = ({ objectMetadataItem, }: { - objectMetadataItem: ObjectMetadataItem; + objectMetadataItem: ObjectMetadataItem | undefined | null; }) => { - const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular); + if (!objectMetadataItem) { + return EMPTY_MUTATION; + } + + const capitalizedObjectName = capitalize(objectMetadataItem?.nameSingular); return gql` mutation DeleteOne${capitalizedObjectName}($idToDelete: ID!) { diff --git a/front/src/modules/object-record/utils/generateFindManyCustomObjectsQuery.ts b/front/src/modules/object-record/utils/useGenerateFindManyCustomObjectsQuery.ts similarity index 67% rename from front/src/modules/object-record/utils/generateFindManyCustomObjectsQuery.ts rename to front/src/modules/object-record/utils/useGenerateFindManyCustomObjectsQuery.ts index bd34c8fd3..62dde1aa9 100644 --- a/front/src/modules/object-record/utils/generateFindManyCustomObjectsQuery.ts +++ b/front/src/modules/object-record/utils/useGenerateFindManyCustomObjectsQuery.ts @@ -1,14 +1,21 @@ import { gql } from '@apollo/client'; +import { EMPTY_QUERY } from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; +import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery'; import { capitalize } from '~/utils/string/capitalize'; -export const generateFindManyCustomObjectsQuery = ({ +export const useGenerateFindManyCustomObjectsQuery = ({ objectMetadataItem, }: { - objectMetadataItem: ObjectMetadataItem; + objectMetadataItem: ObjectMetadataItem | undefined | null; }) => { + const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); + + if (!objectMetadataItem) { + return EMPTY_QUERY; + } + return gql` query FindMany${capitalize( objectMetadataItem.namePlural, diff --git a/front/src/modules/object-record/utils/generateFindOneCustomObjectQuery.ts b/front/src/modules/object-record/utils/useGenerateFindOneCustomObjectQuery.ts similarity index 53% rename from front/src/modules/object-record/utils/generateFindOneCustomObjectQuery.ts rename to front/src/modules/object-record/utils/useGenerateFindOneCustomObjectQuery.ts index 5608f7679..6b7d45efb 100644 --- a/front/src/modules/object-record/utils/generateFindOneCustomObjectQuery.ts +++ b/front/src/modules/object-record/utils/useGenerateFindOneCustomObjectQuery.ts @@ -1,13 +1,20 @@ import { gql } from '@apollo/client'; +import { EMPTY_QUERY } from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; +import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery'; -export const generateFindOneCustomObjectQuery = ({ +export const useGenerateFindOneCustomObjectQuery = ({ objectMetadataItem, }: { - objectMetadataItem: ObjectMetadataItem; + objectMetadataItem: ObjectMetadataItem | null | undefined; }) => { + const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); + + if (!objectMetadataItem) { + return EMPTY_QUERY; + } + return gql` query FindOne${objectMetadataItem.nameSingular}($objectMetadataId: UUID!) { ${objectMetadataItem.nameSingular}(filter: { diff --git a/front/src/modules/object-record/utils/generateUpdateOneObjectMutation.ts b/front/src/modules/object-record/utils/useGenerateUpdateOneObjectMutation.ts similarity index 68% rename from front/src/modules/object-record/utils/generateUpdateOneObjectMutation.ts rename to front/src/modules/object-record/utils/useGenerateUpdateOneObjectMutation.ts index c7eb6d804..b25f60e32 100644 --- a/front/src/modules/object-record/utils/generateUpdateOneObjectMutation.ts +++ b/front/src/modules/object-record/utils/useGenerateUpdateOneObjectMutation.ts @@ -1,7 +1,8 @@ import { gql } from '@apollo/client'; +import { EMPTY_MUTATION } from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; +import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery'; import { capitalize } from '~/utils/string/capitalize'; export const getUpdateOneObjectMutationGraphQLField = ({ @@ -12,11 +13,17 @@ export const getUpdateOneObjectMutationGraphQLField = ({ return `update${capitalize(objectNameSingular)}`; }; -export const generateUpdateOneObjectMutation = ({ +export const useGenerateUpdateOneObjectMutation = ({ objectMetadataItem, }: { - objectMetadataItem: ObjectMetadataItem; + objectMetadataItem: ObjectMetadataItem | undefined | null; }) => { + const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); + + if (!objectMetadataItem) { + return EMPTY_MUTATION; + } + const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular); const graphQLFieldForUpdateOneObjectMutation = diff --git a/front/src/modules/search/hooks/useFilteredSearchEntityQueryV2.ts b/front/src/modules/search/hooks/useFilteredSearchEntityQueryV2.ts new file mode 100644 index 000000000..bc5995253 --- /dev/null +++ b/front/src/modules/search/hooks/useFilteredSearchEntityQueryV2.ts @@ -0,0 +1,149 @@ +import { QueryHookOptions, QueryResult } from '@apollo/client'; + +import { mapPaginatedObjectsToObjects } from '@/object-record/utils/mapPaginatedObjectsToObjects'; +import { EntitiesForMultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect'; +import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; +import { QueryMode, SortOrder } from '~/generated/graphql'; + +type SelectStringKeys = NonNullable< + { + [K in keyof T]: K extends '__typename' + ? never + : T[K] extends string | undefined | null + ? K + : never; + }[keyof T] +>; + +type ExtractEntityTypeFromQueryResponse = T extends { + searchResults: Array; +} + ? U + : never; + +type SearchFilter = { fieldNames: string[]; filter: string | number }; + +const DEFAULT_SEARCH_REQUEST_LIMIT = 10; + +// TODO: use this for all search queries, because we need selectedEntities and entitiesToSelect each time we want to search +// Filtered entities to select are +export const useFilteredSearchEntityQueryV2 = ({ + queryHook, + orderByField, + filters, + sortOrder = SortOrder.Asc, + selectedIds, + mappingFunction, + limit, + excludeEntityIds = [], + objectNamePlural, +}: { + queryHook: ( + queryOptions?: QueryHookOptions, + ) => QueryResult; + orderByField: string; + filters: SearchFilter[]; + sortOrder?: SortOrder; + selectedIds: string[]; + mappingFunction: (entity: any) => EntityForSelect; + limit?: number; + excludeEntityIds?: string[]; + objectNamePlural: string; +}): EntitiesForMultipleEntitySelect => { + const { loading: selectedEntitiesLoading, data: selectedEntitiesData } = + queryHook({ + variables: { + where: { + id: { + in: selectedIds, + }, + }, + orderBy: { + [orderByField]: sortOrder, + }, + } as any, + }); + + const searchFilter = filters.map(({ fieldNames, filter }) => { + return { + OR: fieldNames.map((fieldName) => ({ + [fieldName]: { + startsWith: `%${filter}%`, + mode: QueryMode.Insensitive, + }, + })), + }; + }); + + const { + loading: filteredSelectedEntitiesLoading, + data: filteredSelectedEntitiesData, + } = queryHook({ + variables: { + where: { + AND: [ + { + AND: searchFilter, + }, + { + id: { + in: selectedIds, + }, + }, + ], + }, + orderBy: { + [orderByField]: sortOrder, + }, + } as any, + }); + + const { loading: entitiesToSelectLoading, data: entitiesToSelectData } = + queryHook({ + variables: { + where: { + AND: [ + { + AND: searchFilter, + }, + { + id: { + notIn: [...selectedIds, ...excludeEntityIds], + }, + }, + ], + }, + limit: limit ?? DEFAULT_SEARCH_REQUEST_LIMIT, + orderBy: { + [orderByField]: sortOrder, + }, + } as any, + }); + + console.log({ + selectedEntitiesData, + test: mapPaginatedObjectsToObjects({ + objectNamePlural: objectNamePlural, + pagedObjects: selectedEntitiesData, + }), + }); + + return { + selectedEntities: mapPaginatedObjectsToObjects({ + objectNamePlural: objectNamePlural, + pagedObjects: selectedEntitiesData, + }).map(mappingFunction), + filteredSelectedEntities: mapPaginatedObjectsToObjects({ + objectNamePlural: objectNamePlural, + pagedObjects: filteredSelectedEntitiesData, + }).map(mappingFunction), + entitiesToSelect: mapPaginatedObjectsToObjects({ + objectNamePlural: objectNamePlural, + pagedObjects: entitiesToSelectData, + }).map(mappingFunction), + loading: + entitiesToSelectLoading || + filteredSelectedEntitiesLoading || + selectedEntitiesLoading, + }; +}; diff --git a/front/src/modules/settings/profile/components/NameFields.tsx b/front/src/modules/settings/profile/components/NameFields.tsx index 16ef32796..2e507bb6e 100644 --- a/front/src/modules/settings/profile/components/NameFields.tsx +++ b/front/src/modules/settings/profile/components/NameFields.tsx @@ -5,6 +5,7 @@ import debounce from 'lodash.debounce'; import { useRecoilValue } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { TextInput } from '@/ui/input/components/TextInput'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; import { useUpdateUserMutation } from '~/generated/graphql'; @@ -30,9 +31,14 @@ export const NameFields = ({ onLastNameUpdate, }: NameFieldsProps) => { const currentUser = useRecoilValue(currentUserState); + const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); - const [firstName, setFirstName] = useState(currentUser?.firstName ?? ''); - const [lastName, setLastName] = useState(currentUser?.lastName ?? ''); + const [firstName, setFirstName] = useState( + currentWorkspaceMember?.firstName ?? '', + ); + const [lastName, setLastName] = useState( + currentWorkspaceMember?.lastName ?? '', + ); const [updateUser] = useUpdateUserMutation(); @@ -69,13 +75,13 @@ export const NameFields = ({ }, 500); useEffect(() => { - if (!currentUser) { + if (!currentWorkspaceMember) { return; } if ( - currentUser.firstName !== firstName || - currentUser.lastName !== lastName + currentWorkspaceMember.firstName !== firstName || + currentWorkspaceMember.lastName !== lastName ) { debouncedUpdate(); } @@ -83,7 +89,14 @@ export const NameFields = ({ return () => { debouncedUpdate.cancel(); }; - }, [firstName, lastName, currentUser, debouncedUpdate, autoSave]); + }, [ + firstName, + lastName, + currentUser, + debouncedUpdate, + autoSave, + currentWorkspaceMember, + ]); return ( diff --git a/front/src/modules/settings/profile/components/ProfilePictureUploader.tsx b/front/src/modules/settings/profile/components/ProfilePictureUploader.tsx index 0256ad8eb..ac773dde4 100644 --- a/front/src/modules/settings/profile/components/ProfilePictureUploader.tsx +++ b/front/src/modules/settings/profile/components/ProfilePictureUploader.tsx @@ -1,8 +1,9 @@ import { useState } from 'react'; import { getOperationName } from '@apollo/client/utilities'; -import { useRecoilState } from 'recoil'; +import { useRecoilState, useRecoilValue } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { ImageInput } from '@/ui/input/components/ImageInput'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; import { getImageAbsoluteURIOrBase64 } from '@/users/utils/getProfilePictureAbsoluteURI'; @@ -16,6 +17,8 @@ export const ProfilePictureUploader = () => { useUploadProfilePictureMutation(); const [removePicture] = useRemoveProfilePictureMutation(); const [currentUser] = useRecoilState(currentUserState); + const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const [uploadController, setUploadController] = useState(null); const [errorMessage, setErrorMessage] = useState(null); @@ -69,7 +72,7 @@ export const ProfilePictureUploader = () => { return ( { const { enqueueSnackBar } = useSnackBar(); - const currentUser = useRecoilValue(currentUserState); + const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const [updateAllowImpersonation] = useUpdateAllowImpersonationMutation(); @@ -32,7 +32,7 @@ export const ToggleField = () => { return ( ); diff --git a/front/src/modules/settings/workspace/components/NameField.tsx b/front/src/modules/settings/workspace/components/NameField.tsx index 8f0401eaf..47aba0a3b 100644 --- a/front/src/modules/settings/workspace/components/NameField.tsx +++ b/front/src/modules/settings/workspace/components/NameField.tsx @@ -2,9 +2,9 @@ import { useCallback, useEffect, useState } from 'react'; import { getOperationName } from '@apollo/client/utilities'; import styled from '@emotion/styled'; import debounce from 'lodash.debounce'; -import { useRecoilState } from 'recoil'; +import { useRecoilValue } from 'recoil'; -import { currentUserState } from '@/auth/states/currentUserState'; +import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { TextInput } from '@/ui/input/components/TextInput'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; import { useUpdateWorkspaceMutation } from '~/generated/graphql'; @@ -27,10 +27,11 @@ export const NameField = ({ autoSave = true, onNameUpdate, }: NameFieldProps) => { - const [currentUser] = useRecoilState(currentUserState); - const workspace = currentUser?.workspaceMember?.workspace; + const currentWorkspace = useRecoilValue(currentWorkspaceState); - const [displayName, setDisplayName] = useState(workspace?.displayName ?? ''); + const [displayName, setDisplayName] = useState( + currentWorkspace?.displayName ?? '', + ); const [updateWorkspace] = useUpdateWorkspaceMutation(); diff --git a/front/src/modules/settings/workspace/components/WorkspaceLogoUploader.tsx b/front/src/modules/settings/workspace/components/WorkspaceLogoUploader.tsx index e68bdf02f..4caf951b7 100644 --- a/front/src/modules/settings/workspace/components/WorkspaceLogoUploader.tsx +++ b/front/src/modules/settings/workspace/components/WorkspaceLogoUploader.tsx @@ -1,7 +1,7 @@ import { getOperationName } from '@apollo/client/utilities'; -import { useRecoilState } from 'recoil'; +import { useRecoilValue } from 'recoil'; -import { currentUserState } from '@/auth/states/currentUserState'; +import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { ImageInput } from '@/ui/input/components/ImageInput'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; import { getImageAbsoluteURIOrBase64 } from '@/users/utils/getProfilePictureAbsoluteURI'; @@ -13,7 +13,8 @@ import { export const WorkspaceLogoUploader = () => { const [uploadLogo] = useUploadWorkspaceLogoMutation(); const [removeLogo] = useRemoveWorkspaceLogoMutation(); - const [currentUser] = useRecoilState(currentUserState); + const currentWorkspace = useRecoilValue(currentWorkspaceState); + const onUpload = async (file: File) => { if (!file) { return; @@ -34,9 +35,7 @@ export const WorkspaceLogoUploader = () => { return ( diff --git a/front/src/modules/ui/display/chip/components/EntityChip.tsx b/front/src/modules/ui/display/chip/components/EntityChip.tsx index 574be1ba4..3eee413b3 100644 --- a/front/src/modules/ui/display/chip/components/EntityChip.tsx +++ b/front/src/modules/ui/display/chip/components/EntityChip.tsx @@ -8,7 +8,7 @@ import { Avatar, AvatarType } from '@/users/components/Avatar'; import { Chip, ChipVariant } from './Chip'; -type EntityChipProps = { +export type EntityChipProps = { linkToEntity?: string; entityId: string; name: string; diff --git a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx index b6e177cb5..94aaaa470 100644 --- a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx +++ b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx @@ -62,9 +62,13 @@ export const SingleEntitySelectBase = < const entitiesInDropdown = [selectedEntity, ...entitiesToSelect].filter( (entity): entity is CustomEntityForSelect => - assertNotNull(entity) && isNonEmptyString(entity.name.trim()), + assertNotNull(entity) && isNonEmptyString(entity.name), ); + console.log({ + entitiesInDropdown, + }); + const { preselectedOptionId, resetScroll } = useEntitySelectScroll({ selectableOptionIds: [ EmptyButtonId, diff --git a/front/src/modules/ui/input/relation-picker/types/EntityTypeForSelect.ts b/front/src/modules/ui/input/relation-picker/types/EntityTypeForSelect.ts index 80d845d6b..2f063042b 100644 --- a/front/src/modules/ui/input/relation-picker/types/EntityTypeForSelect.ts +++ b/front/src/modules/ui/input/relation-picker/types/EntityTypeForSelect.ts @@ -5,6 +5,7 @@ export enum Entity { Company = 'Company', Person = 'Person', User = 'User', + WorkspaceMember = 'WorkspaceMember', } export type EntityTypeForSelect = diff --git a/front/src/modules/ui/navigation/navbar/components/NavWorkspaceButton.tsx b/front/src/modules/ui/navigation/navbar/components/NavWorkspaceButton.tsx index b6209c205..cc9cb566f 100644 --- a/front/src/modules/ui/navigation/navbar/components/NavWorkspaceButton.tsx +++ b/front/src/modules/ui/navigation/navbar/components/NavWorkspaceButton.tsx @@ -2,6 +2,7 @@ import styled from '@emotion/styled'; import { useRecoilValue } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; +import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { getImageAbsoluteURIOrBase64 } from '@/users/utils/getProfilePictureAbsoluteURI'; import NavCollapseButton from './NavCollapseButton'; @@ -53,8 +54,8 @@ const NavWorkspaceButton = ({ showCollapseButton, }: NavWorkspaceButtonProps) => { const currentUser = useRecoilValue(currentUserState); + const currentWorkspace = useRecoilValue(currentWorkspaceState); - const currentWorkspace = currentUser?.workspaceMember?.workspace; const DEFAULT_LOGO = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACb0lEQVR4nO2VO4taQRTHr3AblbjxEVlwCwVhg7BoqqCIjy/gAyyFWNlYBOxsfH0KuxgQGwXRUkGuL2S7i1barGAgiwbdW93SnGOc4BonPiKahf3DwXFmuP/fPM4ZlvmlTxAhCBdzHnEQWYiv7Mr4C3NeuVYhQYDPzOUUQgDLBQGcLHNhvQK8DACPx8PTxiqVyvISG43GbyaT6Qfpn06n0m63e/tPAPF4vJ1MJu8kEsnWTCkWi1yr1RKGw+GDRqPBOTfr44vFQvD7/Q/lcpmaaVQAr9fLp1IpO22c47hGOBz+MB6PH+Vy+VYDAL8qlUoGtVotzOfzq4MAgsHgE/6KojiQyWR/bKVSqbSszHFM8Pl8z1YK48JsNltCOBwOnrYLO+8AAIjb+nHbycoTiUQfDJ7tFq4YAHiVSmXBxcD41u8flQU8z7fhzO0r83atVns3Go3u9Xr9x0O/RQXo9/tsIBBg6vX606a52Wz+bZ7P5/WwG29gxSJzhKgA6XTaDoFNF+krFAocmC//4yWEcSf2wTm7mCO19xFgSsKOLI16vV7b7XY7mRNoLwA0JymJ5uQIzgIAuX5PzDElT2m+E8BqtQ4ymcx7Yq7T6a6ZE4sKgOadTucaCwkxp1UzlEKh0GDxIXOwDWHAdi6Xe3swQDQa/Q7mywoolUpvsaptymazDWKxmBHTlWXZm405BFZoNpuGgwEmk4mE2SGtVivii4f1AO7J3ZopkQCQj7Ar1FeRChCJRJzVapX6DKNIfSc1Ax+wtQWQ55h6bH8FWDfYV4fO3wlwDr0C/BcADYiTPCxHqIEA2QsCZAkAKnRGkMbKN/sTX5YHPQ1e7SkAAAAASUVORK5CYII='; diff --git a/front/src/modules/ui/navigation/navbar/components/SupportChat.tsx b/front/src/modules/ui/navigation/navbar/components/SupportChat.tsx index a1119b06e..c08605bf8 100644 --- a/front/src/modules/ui/navigation/navbar/components/SupportChat.tsx +++ b/front/src/modules/ui/navigation/navbar/components/SupportChat.tsx @@ -3,6 +3,10 @@ import styled from '@emotion/styled'; import { useRecoilValue } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; +import { + CurrentWorkspaceMember, + currentWorkspaceMemberState, +} from '@/auth/states/currentWorkspaceMemberState'; import { supportChatState } from '@/client-config/states/supportChatState'; import { IconHelpCircle } from '@/ui/display/icon'; import { Button } from '@/ui/input/button/components/Button'; @@ -30,13 +34,18 @@ const insertScript = ({ const SupportChat = () => { const currentUser = useRecoilValue(currentUserState); + const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const supportChat = useRecoilValue(supportChatState); const [isFrontChatLoaded, setIsFrontChatLoaded] = useState(false); const configureFront = useCallback( ( chatId: string, - currentUser: Pick, + currentUser: Pick, + currentWorkspaceMember: Pick< + CurrentWorkspaceMember, + 'firstName' | 'lastName' + >, ) => { const url = 'https://chat-assets.frontapp.com/v1/chat.bundle.js'; const script = document.querySelector(`script[src="${url}"]`); @@ -49,7 +58,10 @@ const SupportChat = () => { chatId, useDefaultLauncher: false, email: currentUser.email, - name: currentUser.displayName, + name: + currentWorkspaceMember.firstName + + ' ' + + currentWorkspaceMember.lastName, userHash: currentUser?.supportUserHash, }); setIsFrontChatLoaded(true); @@ -65,9 +77,14 @@ const SupportChat = () => { supportChat?.supportDriver === 'front' && supportChat.supportFrontChatId && currentUser?.email && + currentWorkspaceMember && !isFrontChatLoaded ) { - configureFront(supportChat.supportFrontChatId, currentUser); + configureFront( + supportChat.supportFrontChatId, + currentUser, + currentWorkspaceMember, + ); } }, [ configureFront, @@ -75,6 +92,7 @@ const SupportChat = () => { isFrontChatLoaded, supportChat?.supportDriver, supportChat.supportFrontChatId, + currentWorkspaceMember, ]); return isFrontChatLoaded ? ( diff --git a/front/src/modules/ui/object/field/hooks/usePersistField.ts b/front/src/modules/ui/object/field/hooks/usePersistField.ts index 83f8d9552..853f2a70b 100644 --- a/front/src/modules/ui/object/field/hooks/usePersistField.ts +++ b/front/src/modules/ui/object/field/hooks/usePersistField.ts @@ -105,13 +105,16 @@ export const usePersistField = () => { valueToPersist, ); + console.log({ + fieldName, + valueToPersist, + }); + updateEntity?.({ variables: { where: { id: entityId }, data: { - [fieldName]: valueToPersist - ? { connect: { id: valueToPersist.id } } - : { disconnect: true }, + [fieldName]: valueToPersist?.id, }, }, }); diff --git a/front/src/modules/ui/object/field/meta-types/display/components/RelationFieldDisplay.tsx b/front/src/modules/ui/object/field/meta-types/display/components/RelationFieldDisplay.tsx index 2d1b705f0..9b6806a63 100644 --- a/front/src/modules/ui/object/field/meta-types/display/components/RelationFieldDisplay.tsx +++ b/front/src/modules/ui/object/field/meta-types/display/components/RelationFieldDisplay.tsx @@ -1,24 +1,27 @@ import { EntityChip } from '@/ui/display/chip/components/EntityChip'; +import { getEntityChipFromFieldMetadata } from '@/ui/object/field/meta-types/display/utils/getEntityChipFromFieldMetadata'; import { useRelationField } from '../../hooks/useRelationField'; export const RelationFieldDisplay = () => { const { fieldValue, fieldDefinition } = useRelationField(); - const { entityChipDisplayMapper } = fieldDefinition; - if (!entityChipDisplayMapper) { - throw new Error( - "Missing entityChipDisplayMapper in FieldContext. Please provide it in the FieldContextProvider's value prop.", - ); - } - const { name, pictureUrl, avatarType } = - entityChipDisplayMapper?.(fieldValue); + + console.log({ + fieldDefinition, + fieldValue, + }); + + const entityChipProps = getEntityChipFromFieldMetadata( + fieldDefinition, + fieldValue, + ); return ( ); }; diff --git a/front/src/modules/ui/object/field/meta-types/display/utils/getEntityChipFromFieldMetadata.ts b/front/src/modules/ui/object/field/meta-types/display/utils/getEntityChipFromFieldMetadata.ts new file mode 100644 index 000000000..aab6fc140 --- /dev/null +++ b/front/src/modules/ui/object/field/meta-types/display/utils/getEntityChipFromFieldMetadata.ts @@ -0,0 +1,32 @@ +import { EntityChipProps } from '@/ui/display/chip/components/EntityChip'; +import { FieldDefinition } from '@/ui/object/field/types/FieldDefinition'; +import { FieldRelationMetadata } from '@/ui/object/field/types/FieldMetadata'; + +export const getEntityChipFromFieldMetadata = ( + fieldDefinition: FieldDefinition, + fieldValue: any, +) => { + const { fieldName } = fieldDefinition.metadata; + + const chipValue: Pick< + EntityChipProps, + 'name' | 'pictureUrl' | 'avatarType' | 'entityId' + > = { + name: '', + pictureUrl: '', + avatarType: 'rounded', + entityId: fieldValue?.id, + }; + + console.log({ + fieldName, + fieldValue, + }); + + // TODO: use every + if (fieldName === 'accountOwner' && fieldValue) { + chipValue.name = fieldValue.firstName + ' ' + fieldValue.lastName; + } + + return chipValue; +}; diff --git a/front/src/modules/ui/object/field/meta-types/hooks/useRelationField.ts b/front/src/modules/ui/object/field/meta-types/hooks/useRelationField.ts index a48f60a26..3457e2a3a 100644 --- a/front/src/modules/ui/object/field/meta-types/hooks/useRelationField.ts +++ b/front/src/modules/ui/object/field/meta-types/hooks/useRelationField.ts @@ -22,6 +22,11 @@ export const useRelationField = () => { }), ); + console.log({ + fieldDefinition, + fieldValue, + }); + const fieldInitialValue = useFieldInitialValue(); const initialSearchValue = fieldInitialValue?.isEmpty diff --git a/front/src/modules/ui/object/field/meta-types/input/components/RelationFieldInput.tsx b/front/src/modules/ui/object/field/meta-types/input/components/RelationFieldInput.tsx index 6d0d41990..6add485cd 100644 --- a/front/src/modules/ui/object/field/meta-types/input/components/RelationFieldInput.tsx +++ b/front/src/modules/ui/object/field/meta-types/input/components/RelationFieldInput.tsx @@ -40,7 +40,7 @@ export const RelationFieldInput = ({ return ( - {fieldDefinition.metadata.relationType === Entity.Person ? ( + {fieldDefinition.metadata.fieldName === 'person' ? ( - ) : fieldDefinition.metadata.relationType === Entity.User ? ( + ) : fieldDefinition.metadata.fieldName === 'accountOwner' ? ( { const tableBodyRef = useRef(null); + console.log('record table'); + const { leaveTableFocus, setRowSelectedState, diff --git a/front/src/modules/ui/theme/hooks/useColorScheme.ts b/front/src/modules/ui/theme/hooks/useColorScheme.ts index a4f251a79..5529460a8 100644 --- a/front/src/modules/ui/theme/hooks/useColorScheme.ts +++ b/front/src/modules/ui/theme/hooks/useColorScheme.ts @@ -1,105 +1,32 @@ import { useCallback } from 'react'; import { useRecoilState } from 'recoil'; -import { currentUserState } from '@/auth/states/currentUserState'; -import { - ColorScheme, - useUpdateOneWorkspaceMemberMutation, - useUpdateUserMutation, -} from '~/generated/graphql'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord'; +import { ColorScheme } from '~/generated/graphql'; export const useColorScheme = () => { - const [currentUser, setCurrentUser] = useRecoilState(currentUserState); + const [currentWorkspaceMember] = useRecoilState(currentWorkspaceMemberState); - const [updateUser] = useUpdateUserMutation(); - const [updateWorkspaceMember] = useUpdateOneWorkspaceMemberMutation(); - - const colorScheme = - !currentUser?.workspaceMember.settings?.colorScheme && - !currentUser?.settings?.colorScheme - ? ColorScheme.System - : currentUser.workspaceMember.settings?.colorScheme ?? - currentUser.settings.colorScheme; + const { updateOneObject: updateOneWorkspaceMember } = + useUpdateOneObjectRecord({ + objectNamePlural: 'workspaceMembersV2', + }); + const colorScheme = currentWorkspaceMember?.colorScheme ?? ColorScheme.System; const setColorScheme = useCallback( async (value: ColorScheme) => { - try { - // connect settings to workspace member if not already connected - await updateWorkspaceMember({ - variables: { - where: { id: currentUser?.workspaceMember.id }, - data: { settings: { connect: { id: currentUser?.settings.id } } }, - }, - }); - - const result = await updateUser({ - variables: { - where: { - id: currentUser?.id, - }, - data: { - settings: { - update: { - colorScheme: value, - }, - }, - }, - }, - optimisticResponse: currentUser - ? { - __typename: 'Mutation', - updateUser: { - __typename: 'User', - ...currentUser, - workspaceMember: { - ...currentUser.workspaceMember, - settings: { - __typename: 'UserSettings', - id: currentUser.settings.id, - colorScheme: value, - locale: currentUser.settings.locale, - }, - }, - settings: { - __typename: 'UserSettings', - id: currentUser.settings.id, - colorScheme: value, - locale: currentUser.settings.locale, - }, - }, - } - : undefined, - update: (_cache, { data }) => { - if ( - data?.updateUser.workspaceMember?.settings?.colorScheme && - currentUser - ) { - setCurrentUser({ - ...currentUser, - workspaceMember: { - ...currentUser.workspaceMember, - settings: { - ...currentUser.workspaceMember.settings, - colorScheme: - data.updateUser.workspaceMember.settings.colorScheme, - }, - }, - settings: { - ...currentUser.settings, - colorScheme: - data.updateUser.workspaceMember.settings.colorScheme, - }, - }); - } - }, - }); - - if (!result.data || result.errors) { - throw result.errors; - } - } catch (err) {} + if (!currentWorkspaceMember) { + return; + } + await updateOneWorkspaceMember?.({ + idToUpdate: currentWorkspaceMember?.id, + input: { + colorScheme: value, + }, + }); }, - [updateWorkspaceMember, currentUser, updateUser, setCurrentUser], + [currentWorkspaceMember, updateOneWorkspaceMember], ); return { diff --git a/front/src/modules/users/components/UserPicker.tsx b/front/src/modules/users/components/UserPicker.tsx index 1b24dac37..73e53f28f 100644 --- a/front/src/modules/users/components/UserPicker.tsx +++ b/front/src/modules/users/components/UserPicker.tsx @@ -1,13 +1,14 @@ import { useEffect } from 'react'; +import { useQuery } from '@apollo/client'; -import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery'; +import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; +import { useFilteredSearchEntityQueryV2 } from '@/search/hooks/useFilteredSearchEntityQueryV2'; import { IconUserCircle } from '@/ui/display/icon'; import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect'; import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; -import { useSearchUserQuery } from '~/generated/graphql'; export type UserPickerProps = { userId: string; @@ -18,7 +19,7 @@ export type UserPickerProps = { }; type UserForSelect = EntityForSelect & { - entityType: Entity.User; + entityType: Entity.WorkspaceMember; }; export const UserPicker = ({ @@ -35,29 +36,39 @@ export const UserPicker = ({ setRelationPickerSearchFilter(initialSearchFilter ?? ''); }, [initialSearchFilter, setRelationPickerSearchFilter]); - const users = useFilteredSearchEntityQuery({ - queryHook: useSearchUserQuery, + const { findManyQuery } = useFindOneObjectMetadataItem({ + objectNamePlural: 'workspaceMembersV2', + }); + + const useFindManyWorkspaceMembers = () => useQuery(findManyQuery, {}); + + // TODO: put workspace member + const users = useFilteredSearchEntityQueryV2({ + queryHook: useFindManyWorkspaceMembers, filters: [ { fieldNames: ['firstName', 'lastName'], filter: relationPickerSearchFilter, }, ], - orderByField: 'firstName', - mappingFunction: (user) => ({ - entityType: Entity.User, - id: user.id, - name: user.displayName, + orderByField: '', + mappingFunction: (workspaceMember) => ({ + entityType: Entity.WorkspaceMember, + id: workspaceMember.id, + name: workspaceMember.firstName, avatarType: 'rounded', - avatarUrl: user.avatarUrl ?? '', - originalEntity: user, + avatarUrl: '', + originalEntity: workspaceMember, }), selectedIds: userId ? [userId] : [], + objectNamePlural: 'workspaceMembersV2', }); - const handleEntitySelected = async ( - selectedUser: UserForSelect | null | undefined, - ) => { + console.log({ + users, + }); + + const handleEntitySelected = async (selectedUser: any | null | undefined) => { onSubmit(selectedUser ?? null); }; diff --git a/front/src/modules/users/components/UserProvider.tsx b/front/src/modules/users/components/UserProvider.tsx index df3fdd6df..ed38cc221 100644 --- a/front/src/modules/users/components/UserProvider.tsx +++ b/front/src/modules/users/components/UserProvider.tsx @@ -1,29 +1,71 @@ import { useEffect, useState } from 'react'; -import { useRecoilState } from 'recoil'; +import { useApolloClient } from '@apollo/client'; +import { useSetRecoilState } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; -import { useGetCurrentUserQuery } from '~/generated/graphql'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { FIND_ONE_WORKSPACE_MEMBER_V2 } from '@/object-record/graphql/queries/findOneWorkspaceMember'; +import { + useGetCurrentUserQuery, + useGetCurrentWorkspaceQuery, +} from '~/generated/graphql'; export const UserProvider = ({ children }: React.PropsWithChildren) => { - const [, setCurrentUser] = useRecoilState(currentUserState); const [isLoading, setIsLoading] = useState(true); + const [isWorkspaceMemberLoading, setIsWorkspaceMemberLoading] = + useState(true); + const apolloClient = useApolloClient(); - const { data, loading } = useGetCurrentUserQuery(); + const setCurrentUser = useSetRecoilState(currentUserState); + const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); + const setCurrentWorkspaceMember = useSetRecoilState( + currentWorkspaceMemberState, + ); - useEffect(() => { - if (!loading) { - setIsLoading(false); - } - if (data?.currentUser?.workspaceMember?.settings) { - setCurrentUser({ - ...data.currentUser, - workspaceMember: { - ...data.currentUser.workspaceMember, - settings: data.currentUser.workspaceMember.settings, + const { data: userData, loading: userLoading } = useGetCurrentUserQuery({ + onCompleted: async (data) => { + const workspaceMember = await apolloClient.query({ + query: FIND_ONE_WORKSPACE_MEMBER_V2, + variables: { + filter: { + userId: { eq: data.currentUser.id }, + }, }, }); + setCurrentWorkspaceMember( + workspaceMember.data.workspaceMembersV2.edges[0].node, + ); + setIsWorkspaceMemberLoading(false); + }, + onError: () => { + setIsWorkspaceMemberLoading(false); + }, + }); + + const { data: workspaceData, loading: workspaceLoading } = + useGetCurrentWorkspaceQuery(); + + useEffect(() => { + if (!userLoading && !workspaceLoading && !isWorkspaceMemberLoading) { + setIsLoading(false); } - }, [setCurrentUser, data, isLoading, loading]); + if (userData?.currentUser) { + setCurrentUser(userData.currentUser); + } + if (workspaceData?.currentWorkspace) { + setCurrentWorkspace(workspaceData.currentWorkspace); + } + }, [ + setCurrentUser, + isLoading, + userLoading, + workspaceLoading, + userData?.currentUser, + workspaceData?.currentWorkspace, + setCurrentWorkspace, + isWorkspaceMemberLoading, + ]); return isLoading ? <> : <>{children}; }; diff --git a/front/src/modules/users/graphql/mutations/updateUser.ts b/front/src/modules/users/graphql/mutations/updateUser.ts index c741349b6..8dfde4aac 100644 --- a/front/src/modules/users/graphql/mutations/updateUser.ts +++ b/front/src/modules/users/graphql/mutations/updateUser.ts @@ -5,52 +5,6 @@ export const UPDATE_USER = gql` updateUser(data: $data, where: $where) { id email - displayName - firstName - lastName - avatarUrl - workspaceMember { - id - workspace { - id - domainName - displayName - logo - inviteHash - } - assignedActivities { - id - title - } - authoredActivities { - id - title - } - authoredAttachments { - id - name - type - } - settings { - id - colorScheme - locale - } - companies { - id - name - domainName - } - comments { - id - body - } - } - settings { - id - locale - colorScheme - } } } `; diff --git a/front/src/modules/users/graphql/queries/getCurrentUser.ts b/front/src/modules/users/graphql/queries/getCurrentUser.ts index 4a1e4f925..4e5efb9f6 100644 --- a/front/src/modules/users/graphql/queries/getCurrentUser.ts +++ b/front/src/modules/users/graphql/queries/getCurrentUser.ts @@ -4,16 +4,7 @@ export const GET_CURRENT_USER = gql` query GetCurrentUser { currentUser { ...userFieldsFragment - avatarUrl canImpersonate - workspaceMember { - ...workspaceMemberFieldsFragment - } - settings { - id - locale - colorScheme - } supportUserHash } } diff --git a/front/src/modules/workspace-member/types/WorkspaceMember.ts b/front/src/modules/workspace-member/types/WorkspaceMember.ts new file mode 100644 index 000000000..617443bff --- /dev/null +++ b/front/src/modules/workspace-member/types/WorkspaceMember.ts @@ -0,0 +1,6 @@ +export type WorkspaceMember = { + id: string; + firstName: string; + lastName: string; + avatarUrl: string; +}; diff --git a/front/src/modules/workspace/components/WorkspaceMemberCard.tsx b/front/src/modules/workspace/components/WorkspaceMemberCard.tsx index 94f15a341..eb0115154 100644 --- a/front/src/modules/workspace/components/WorkspaceMemberCard.tsx +++ b/front/src/modules/workspace/components/WorkspaceMemberCard.tsx @@ -2,7 +2,7 @@ import styled from '@emotion/styled'; import { OverflowingTextWithTooltip } from '@/ui/display/tooltip/OverflowingTextWithTooltip'; import { Avatar } from '@/users/components/Avatar'; -import { User } from '~/generated/graphql'; +import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; const StyledContainer = styled.div` background: ${({ theme }) => theme.background.secondary}; @@ -29,12 +29,7 @@ const StyledEmailText = styled.span` `; type WorkspaceMemberCardProps = { - workspaceMember: { - user: Pick< - User, - 'id' | 'firstName' | 'lastName' | 'displayName' | 'avatarUrl' | 'email' - >; - }; + workspaceMember: WorkspaceMember; accessory?: React.ReactNode; }; @@ -44,15 +39,19 @@ export const WorkspaceMemberCard = ({ }: WorkspaceMemberCardProps) => ( - - {workspaceMember.user.email} + + + {workspaceMember.firstName + ' ' + workspaceMember.lastName} + {accessory} diff --git a/front/src/modules/workspace/graphql/fragments/workspaceMemberFieldsFragment.ts b/front/src/modules/workspace/graphql/fragments/workspaceMemberFieldsFragment.ts deleted file mode 100644 index c0186ef90..000000000 --- a/front/src/modules/workspace/graphql/fragments/workspaceMemberFieldsFragment.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { gql } from '@apollo/client'; - -export const WORKSPACE_MEMBER_FIELDS_FRAGMENT = gql` - fragment workspaceMemberFieldsFragment on WorkspaceMember { - id - allowImpersonation - workspace { - id - domainName - displayName - logo - inviteHash - } - assignedActivities { - id - title - } - authoredActivities { - id - title - } - authoredAttachments { - id - name - type - } - settings { - id - colorScheme - locale - } - companies { - id - name - domainName - } - comments { - id - body - } - } -`; diff --git a/front/src/modules/workspace/graphql/mutations/removeWorkspaceMember.ts b/front/src/modules/workspace/graphql/mutations/removeWorkspaceMember.ts deleted file mode 100644 index 4dcad80d3..000000000 --- a/front/src/modules/workspace/graphql/mutations/removeWorkspaceMember.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { gql } from '@apollo/client'; - -export const REMOVE_WORKSPACE_MEMBER = gql` - mutation RemoveWorkspaceMember($where: WorkspaceMemberWhereUniqueInput!) { - deleteWorkspaceMember(where: $where) { - id - } - } -`; diff --git a/front/src/modules/workspace/graphql/mutations/updateWorkspaceMember.ts b/front/src/modules/workspace/graphql/mutations/updateWorkspaceMember.ts deleted file mode 100644 index cbb96ab7a..000000000 --- a/front/src/modules/workspace/graphql/mutations/updateWorkspaceMember.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { gql } from '@apollo/client'; - -export const UPDATE_WORKSPACE_MEMBER = gql` - mutation UpdateOneWorkspaceMember( - $data: WorkspaceMemberUpdateInput! - $where: WorkspaceMemberWhereUniqueInput! - ) { - UpdateOneWorkspaceMember(data: $data, where: $where) { - ...workspaceMemberFieldsFragment - } - } -`; diff --git a/front/src/modules/workspace/graphql/queries/getCurrentWorkspace.ts b/front/src/modules/workspace/graphql/queries/getCurrentWorkspace.ts new file mode 100644 index 000000000..78b10c558 --- /dev/null +++ b/front/src/modules/workspace/graphql/queries/getCurrentWorkspace.ts @@ -0,0 +1,11 @@ +import { gql } from '@apollo/client'; + +export const GET_CURRENT_WORKSPACE = gql` + query getCurrentWorkspace { + currentWorkspace { + id + displayName + logo + } + } +`; diff --git a/front/src/modules/workspace/graphql/queries/getWorkspaceMembers.ts b/front/src/modules/workspace/graphql/queries/getWorkspaceMembers.ts index b245a4c64..f886e1970 100644 --- a/front/src/modules/workspace/graphql/queries/getWorkspaceMembers.ts +++ b/front/src/modules/workspace/graphql/queries/getWorkspaceMembers.ts @@ -4,10 +4,6 @@ export const GET_WORKSPACE_MEMBERS = gql` query GetWorkspaceMembers($where: WorkspaceMemberWhereInput) { workspaceMembers: findManyWorkspaceMember(where: $where) { id - user { - ...userFieldsFragment - avatarUrl - } } } `; diff --git a/front/src/pages/auth/CreateProfile.tsx b/front/src/pages/auth/CreateProfile.tsx index 63df14b0d..ad1ef45dc 100644 --- a/front/src/pages/auth/CreateProfile.tsx +++ b/front/src/pages/auth/CreateProfile.tsx @@ -11,6 +11,7 @@ import { z } from 'zod'; import { SubTitle } from '@/auth/components/SubTitle'; import { Title } from '@/auth/components/Title'; import { currentUserState } from '@/auth/states/currentUserState'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader'; import { PageHotkeyScope } from '@/types/PageHotkeyScope'; import { H2Title } from '@/ui/display/typography/components/H2Title'; @@ -57,6 +58,7 @@ export const CreateProfile = () => { const { enqueueSnackBar } = useSnackBar(); const [currentUser] = useRecoilState(currentUserState); + const [currentWorkspaceMember] = useRecoilState(currentWorkspaceMemberState); const [updateUser] = useUpdateUserMutation(); @@ -69,8 +71,8 @@ export const CreateProfile = () => { } = useForm
({ mode: 'onChange', defaultValues: { - firstName: currentUser?.firstName ?? '', - lastName: currentUser?.lastName ?? '', + firstName: currentWorkspaceMember?.firstName ?? '', + lastName: currentWorkspaceMember?.lastName ?? '', }, resolver: zodResolver(validationSchema), }); diff --git a/front/src/pages/auth/VerifyEffect.tsx b/front/src/pages/auth/VerifyEffect.tsx index 5f97ffbce..171e4e415 100644 --- a/front/src/pages/auth/VerifyEffect.tsx +++ b/front/src/pages/auth/VerifyEffect.tsx @@ -1,15 +1,18 @@ import { useEffect } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { isNonEmptyString } from '@sniptt/guards'; +import { useRecoilValue } from 'recoil'; import { useAuth } from '@/auth/hooks/useAuth'; import { useIsLogged } from '@/auth/hooks/useIsLogged'; +import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { AppPath } from '../../modules/types/AppPath'; export const VerifyEffect = () => { const [searchParams] = useSearchParams(); const loginToken = searchParams.get('loginToken'); + const currentWorkspace = useRecoilValue(currentWorkspaceState); const isLogged = useIsLogged(); const navigate = useNavigate(); @@ -21,13 +24,9 @@ export const VerifyEffect = () => { if (!loginToken) { navigate(AppPath.SignIn); } else { - const verifyResponse = await verify(loginToken); + await verify(loginToken); - if ( - isNonEmptyString( - verifyResponse.user.workspaceMember?.workspace.displayName, - ) - ) { + if (isNonEmptyString(currentWorkspace?.displayName)) { navigate(AppPath.Index); } else { navigate(AppPath.CreateWorkspace); diff --git a/front/src/pages/impersonate/ImpersonateEffect.tsx b/front/src/pages/impersonate/ImpersonateEffect.tsx index 66c0a8fa8..d54409382 100644 --- a/front/src/pages/impersonate/ImpersonateEffect.tsx +++ b/front/src/pages/impersonate/ImpersonateEffect.tsx @@ -38,21 +38,9 @@ export const ImpersonateEffect = () => { throw new Error('No impersonate result'); } - if (!impersonateResult.data?.impersonate.user.workspaceMember) { - throw new Error('No workspace member'); - } - - if (!impersonateResult.data?.impersonate.user.workspaceMember.settings) { - throw new Error('No workspace member settings'); - } - setCurrentUser({ ...impersonateResult.data.impersonate.user, - workspaceMember: { - ...impersonateResult.data.impersonate.user.workspaceMember, - settings: - impersonateResult.data.impersonate.user.workspaceMember.settings, - }, + // Todo also set WorkspaceMember }); setTokenPair(impersonateResult.data?.impersonate.tokens); diff --git a/front/src/pages/settings/SettingsWorkspaceMembers.tsx b/front/src/pages/settings/SettingsWorkspaceMembers.tsx index 20be414cc..4c6b36b85 100644 --- a/front/src/pages/settings/SettingsWorkspaceMembers.tsx +++ b/front/src/pages/settings/SettingsWorkspaceMembers.tsx @@ -1,8 +1,10 @@ import { useState } from 'react'; import styled from '@emotion/styled'; -import { useRecoilState } from 'recoil'; +import { useRecoilValue } from 'recoil'; -import { currentUserState } from '@/auth/states/currentUserState'; +import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useDeleteOneObjectRecord } from '@/object-record/hooks/useDeleteOneObjectRecord'; +import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { IconSettings, IconTrash } from '@/ui/display/icon'; import { H1Title } from '@/ui/display/typography/components/H1Title'; @@ -13,10 +15,7 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer' import { Section } from '@/ui/layout/section/components/Section'; import { WorkspaceInviteLink } from '@/workspace/components/WorkspaceInviteLink'; import { WorkspaceMemberCard } from '@/workspace/components/WorkspaceMemberCard'; -import { - useGetWorkspaceMembersQuery, - useRemoveWorkspaceMemberMutation, -} from '~/generated/graphql'; +import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; const StyledH1Title = styled(H1Title)` margin-bottom: 0; @@ -31,51 +30,22 @@ const StyledButtonContainer = styled.div` export const SettingsWorkspaceMembers = () => { const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false); - const [userToDelete, setUserToDelete] = useState(); + const [workspaceMemberToDelete, setWorkspaceMemberToDelete] = useState< + string | undefined + >(); - const [currentUser] = useRecoilState(currentUserState); - const workspace = currentUser?.workspaceMember?.workspace; - - const { data } = useGetWorkspaceMembersQuery(); - - const [removeWorkspaceMember] = useRemoveWorkspaceMemberMutation(); - - const handleRemoveWorkspaceMember = async (userId: string) => { - await removeWorkspaceMember({ - variables: { - where: { - userId, - }, - }, - optimisticResponse: { - __typename: 'Mutation', - deleteWorkspaceMember: { - __typename: 'WorkspaceMember', - id: userId, - }, - }, - update: (cache, { data: responseData }) => { - if (!responseData) { - return; - } - - cache.evict({ - id: cache.identify({ - id: responseData.deleteWorkspaceMember.id, - __typename: 'WorkspaceMember', - }), - }); - - cache.evict({ - id: cache.identify({ - id: userId, - __typename: 'User', - }), - }); - - cache.gc(); - }, + const { objects: workspaceMembers } = + useFindManyObjectRecords({ + objectNamePlural: 'workspaceMembersV2', }); + const { deleteOneObject: deleteOneWorkspaceMember } = + useDeleteOneObjectRecord({ + objectNamePlural: 'workspaceMembersV2', + }); + const currentWorkspace = useRecoilValue(currentWorkspaceState); + + const handleRemoveWorkspaceMember = async (workspaceMemberId: string) => { + await deleteOneWorkspaceMember?.(workspaceMemberId); setIsConfirmationModalOpen(false); }; @@ -83,14 +53,14 @@ export const SettingsWorkspaceMembers = () => { - {workspace?.inviteHash && ( + {currentWorkspace?.inviteHash && (
)} @@ -99,17 +69,17 @@ export const SettingsWorkspaceMembers = () => { title="Members" description="Manage the members of your space here" /> - {data?.workspaceMembers?.map((member) => ( + {workspaceMembers?.map((member) => ( { setIsConfirmationModalOpen(true); - setUserToDelete(member.user.id); + setWorkspaceMemberToDelete(member.id); }} variant="tertiary" size="medium" @@ -133,7 +103,8 @@ export const SettingsWorkspaceMembers = () => { } onConfirmClick={() => - userToDelete && handleRemoveWorkspaceMember(userToDelete) + workspaceMemberToDelete && + handleRemoveWorkspaceMember(workspaceMemberToDelete) } deleteButtonText="Delete account" /> diff --git a/front/src/pages/tasks/TasksEffect.tsx b/front/src/pages/tasks/TasksEffect.tsx index 566577531..6652c9bf6 100644 --- a/front/src/pages/tasks/TasksEffect.tsx +++ b/front/src/pages/tasks/TasksEffect.tsx @@ -1,14 +1,14 @@ import { useEffect } from 'react'; -import { useRecoilState } from 'recoil'; +import { useRecoilValue } from 'recoil'; -import { currentUserState } from '@/auth/states/currentUserState'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useFilter } from '@/ui/object/object-filter-dropdown/hooks/useFilter'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { tasksFilterDefinitions } from './tasks-filter-definitions'; export const TasksEffect = () => { - const [currentUser] = useRecoilState(currentUserState); + const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const { setSelectedFilter, setAvailableFilterDefinitions } = useFilter(); useEffect(() => { @@ -16,16 +16,19 @@ export const TasksEffect = () => { }, [setAvailableFilterDefinitions]); useEffect(() => { - if (currentUser) { + if (currentWorkspaceMember) { setSelectedFilter({ fieldMetadataId: 'assigneeId', - value: currentUser.id, + value: currentWorkspaceMember.id, operand: ViewFilterOperand.Is, - displayValue: currentUser.displayName, - displayAvatarUrl: currentUser.avatarUrl ?? undefined, + displayValue: + currentWorkspaceMember.firstName + + ' ' + + currentWorkspaceMember.lastName, + displayAvatarUrl: currentWorkspaceMember.avatarUrl ?? undefined, definition: tasksFilterDefinitions[0], }); } - }, [currentUser, setSelectedFilter]); + }, [currentWorkspaceMember, setSelectedFilter]); return <>; }; diff --git a/server/src/ability/ability.factory.ts b/server/src/ability/ability.factory.ts index c31d4f49b..57c8c9cfc 100644 --- a/server/src/ability/ability.factory.ts +++ b/server/src/ability/ability.factory.ts @@ -58,12 +58,9 @@ export class AbilityFactory { ); // User - can(AbilityAction.Read, 'User', { - workspaceMember: { - workspaceId: workspace.id, - }, - }); + if (user) { + can(AbilityAction.Read, 'User', { id: user.id }); can(AbilityAction.Update, 'User', { id: user.id }); can(AbilityAction.Delete, 'User', { id: user.id }); } else { diff --git a/server/src/app.module.ts b/server/src/app.module.ts index ae4d07121..959c97163 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -12,7 +12,6 @@ import { TokenExpiredError, JsonWebTokenError, verify } from 'jsonwebtoken'; import { AppService } from './app.service'; import { CoreModule } from './core/core.module'; -import { CoreV2Module } from './coreV2/core.module'; import { IntegrationsModule } from './integrations/integrations.module'; import { PrismaModule } from './database/prisma.module'; import { HealthModule } from './health/health.module'; @@ -103,7 +102,6 @@ import { ExceptionFilter } from './filters/exception.filter'; AbilityModule, IntegrationsModule, CoreModule, - CoreV2Module, TenantModule, ], providers: [ diff --git a/server/src/core/auth/auth.resolver.ts b/server/src/core/auth/auth.resolver.ts index 3f2add630..d242b0989 100644 --- a/server/src/core/auth/auth.resolver.ts +++ b/server/src/core/auth/auth.resolver.ts @@ -130,7 +130,6 @@ export class AuthResolver { defaultFields: { User: { id: true, - workspaceMember: { select: { allowImpersonation: true } }, }, }, }) @@ -140,7 +139,6 @@ export class AuthResolver { assert(user.canImpersonate, 'User cannot impersonate', ForbiddenException); const select = prismaSelect.valueOf('user') as Prisma.UserSelect & { id: true; - workspaceMember: { select: { allowImpersonation: true } }; }; return this.authService.impersonate(impersonateInput.userId, select); diff --git a/server/src/core/auth/controllers/google-auth.controller.ts b/server/src/core/auth/controllers/google-auth.controller.ts index a9ba1c208..96c83e258 100644 --- a/server/src/core/auth/controllers/google-auth.controller.ts +++ b/server/src/core/auth/controllers/google-auth.controller.ts @@ -63,11 +63,6 @@ export class GoogleAuthController { firstName: firstName ?? '', lastName: lastName ?? '', locale: 'en', - settings: { - create: { - locale: 'en', - }, - }, }, }, workspaceId, diff --git a/server/src/core/auth/services/auth.service.ts b/server/src/core/auth/services/auth.service.ts index e68556228..e575327b0 100644 --- a/server/src/core/auth/services/auth.service.ts +++ b/server/src/core/auth/services/auth.service.ts @@ -88,7 +88,6 @@ export class AuthService { data: { email: signUpInput.email, passwordHash, - locale: 'en', }, } as Prisma.UserCreateArgs, workspace.id, @@ -160,11 +159,6 @@ export class AuthService { userId: string, select: Prisma.UserSelect & { id: true; - workspaceMember: { - select: { - allowImpersonation: true; - }; - }; }, ) { const user = await this.userService.findUnique({ @@ -175,11 +169,8 @@ export class AuthService { }); assert(user, "This user doesn't exist", NotFoundException); - assert( - user.workspaceMember?.allowImpersonation, - 'Impersonation not allowed', - ForbiddenException, - ); + + // Todo: check if workspace member can be impersonated const accessToken = await this.tokenService.generateAccessToken(user.id); const refreshToken = await this.tokenService.generateRefreshToken(user.id); diff --git a/server/src/core/auth/services/token.service.ts b/server/src/core/auth/services/token.service.ts index c4674daa3..13943c4a1 100644 --- a/server/src/core/auth/services/token.service.ts +++ b/server/src/core/auth/services/token.service.ts @@ -33,22 +33,19 @@ export class TokenService { const user = await this.prismaService.client.user.findUnique({ where: { id: userId }, - include: { - workspaceMember: true, - }, }); if (!user) { throw new NotFoundException('User is not found'); } - if (!user.workspaceMember) { - throw new ForbiddenException('User is not associated to a workspace'); + if (!user.defaultWorkspaceId) { + throw new NotFoundException('User does not have a default workspace'); } const jwtPayload: JwtPayload = { sub: user.id, - workspaceId: user.workspaceMember.workspaceId, + workspaceId: user.defaultWorkspaceId, }; return { diff --git a/server/src/core/core.module.ts b/server/src/core/core.module.ts index 09b5d0a79..678d9134e 100644 --- a/server/src/core/core.module.ts +++ b/server/src/core/core.module.ts @@ -1,6 +1,8 @@ import { Module } from '@nestjs/common'; import { WebHookModule } from 'src/core/web-hook/web-hook.module'; +import { UserModule as UserV2Module } from 'src/coreV2/user/user.module'; +import { RefreshTokenModule as RefreshTokenV2Module } from 'src/coreV2/refresh-token/refresh-token.module'; import { UserModule } from './user/user.module'; import { CommentModule } from './comment/comment.module'; @@ -34,6 +36,8 @@ import { ApiKeyModule } from './api-key/api-key.module'; FavoriteModule, ApiKeyModule, WebHookModule, + UserV2Module, + RefreshTokenV2Module, ], exports: [ AuthModule, @@ -48,6 +52,8 @@ import { ApiKeyModule } from './api-key/api-key.module'; FavoriteModule, ApiKeyModule, WebHookModule, + UserV2Module, + RefreshTokenV2Module, ], }) export class CoreModule {} diff --git a/server/src/core/user/user.service.ts b/server/src/core/user/user.service.ts index d28d95509..6d2afe01f 100644 --- a/server/src/core/user/user.service.ts +++ b/server/src/core/user/user.service.ts @@ -66,13 +66,6 @@ export class UserService { : await this.workspaceService.createDefaultWorkspace(); assert(workspace, 'workspace is missing', BadRequestException); - - const userSettings = await this.prismaService.client.userSettings.create({ - data: { locale: 'en' }, - }); - - const settings = { connect: { id: userSettings.id } }; - // Create user const user = await this.prismaService.client.user.upsert({ where: { @@ -80,17 +73,7 @@ export class UserService { }, create: { ...(args.data as Prisma.UserCreateInput), - settings, - - workspaceMember: { - create: { - workspace: { - connect: { id: workspace.id }, - }, - settings, - }, - }, - locale: 'en', + defaultWorkspaceId: workspace.id, }, update: {}, ...(args.select ? { select: args.select } : {}), diff --git a/server/src/coreV2/core.module.ts b/server/src/coreV2/core.module.ts deleted file mode 100644 index c70030b2b..000000000 --- a/server/src/coreV2/core.module.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { GraphQLModule } from '@nestjs/graphql'; - -import { YogaDriverConfig, YogaDriver } from '@graphql-yoga/nestjs'; -import GraphQLJSON from 'graphql-type-json'; - -// eslint-disable-next-line no-restricted-imports -import config from '../../ormconfig'; - -import { UserModule } from './user/user.module'; -import { RefreshTokenModule } from './refresh-token/refresh-token.module'; - -@Module({ - imports: [ - TypeOrmModule.forRoot(config), - GraphQLModule.forRoot({ - context: ({ req }) => ({ req }), - driver: YogaDriver, - autoSchemaFile: true, - include: [CoreV2Module], - resolvers: { JSON: GraphQLJSON }, - plugins: [], - path: '/graphqlv2', - }), - UserModule, - RefreshTokenModule, - ], - exports: [UserModule], -}) -export class CoreV2Module {} diff --git a/server/src/coreV2/refresh-token/refresh-token.auto-resolver-opts.ts b/server/src/coreV2/refresh-token/refresh-token.auto-resolver-opts.ts index 174863620..5b19f5eb2 100644 --- a/server/src/coreV2/refresh-token/refresh-token.auto-resolver-opts.ts +++ b/server/src/coreV2/refresh-token/refresh-token.auto-resolver-opts.ts @@ -3,7 +3,6 @@ import { PagingStrategies, ReadResolverOpts, } from '@ptc-org/nestjs-query-graphql'; -import { SortDirection } from '@ptc-org/nestjs-query-core'; import { JwtAuthGuard } from 'src/guards/jwt.auth.guard'; @@ -26,7 +25,8 @@ export const refreshTokenAutoResolverOpts: AutoResolverOpts< enableTotalCount: true, pagingStrategy: PagingStrategies.CURSOR, read: { - defaultSort: [{ field: 'id', direction: SortDirection.DESC }], + many: { disabled: true }, + one: { disabled: true }, }, create: { many: { disabled: true }, diff --git a/server/src/coreV2/refresh-token/refresh-token.entity.ts b/server/src/coreV2/refresh-token/refresh-token.entity.ts index 6a5ff8f04..fc4a774ff 100644 --- a/server/src/coreV2/refresh-token/refresh-token.entity.ts +++ b/server/src/coreV2/refresh-token/refresh-token.entity.ts @@ -15,12 +15,12 @@ import { IDField, } from '@ptc-org/nestjs-query-graphql'; -import { User } from 'src/coreV2/user/user.entity'; +import { UserV2 } from 'src/coreV2/user/user.entity'; import { BeforeCreateOneRefreshToken } from './hooks/before-create-one-refresh-token.hook'; @Entity('refresh_tokens') -@ObjectType('RefreshToken') +@ObjectType('refreshTokenV2') @BeforeCreateOne(BeforeCreateOneRefreshToken) @Authorize({ authorize: (context: any) => ({ @@ -32,9 +32,9 @@ export class RefreshToken { @PrimaryGeneratedColumn('uuid') id: string; - @ManyToOne(() => User, (user) => user.refreshTokens) + @ManyToOne(() => UserV2, (user) => user.refreshTokens) @JoinColumn({ name: 'userId' }) - user: User; + user: UserV2; @Column() userId: string; diff --git a/server/src/coreV2/refresh-token/refresh-token.module.ts b/server/src/coreV2/refresh-token/refresh-token.module.ts index b0315c5c7..d3d616559 100644 --- a/server/src/coreV2/refresh-token/refresh-token.module.ts +++ b/server/src/coreV2/refresh-token/refresh-token.module.ts @@ -1,8 +1,12 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql'; import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; +// eslint-disable-next-line no-restricted-imports +import config from '../../../ormconfig'; + import { RefreshToken } from './refresh-token.entity'; import { refreshTokenAutoResolverOpts } from './refresh-token.auto-resolver-opts'; @@ -10,6 +14,7 @@ import { RefreshTokenService } from './services/refresh-token.service'; @Module({ imports: [ + TypeOrmModule.forRoot(config), NestjsQueryGraphQLModule.forFeature({ imports: [NestjsQueryTypeOrmModule.forFeature([RefreshToken])], services: [RefreshTokenService], diff --git a/server/src/coreV2/user/services/user.service.ts b/server/src/coreV2/user/services/user.service.ts index bf08f3db6..1a4b0983f 100644 --- a/server/src/coreV2/user/services/user.service.ts +++ b/server/src/coreV2/user/services/user.service.ts @@ -4,12 +4,12 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; import { Repository } from 'typeorm'; import { assert } from 'src/utils/assert'; -import { User } from 'src/coreV2/user/user.entity'; +import { UserV2 } from 'src/coreV2/user/user.entity'; -export class UserService extends TypeOrmQueryService { +export class UserService extends TypeOrmQueryService { constructor( - @InjectRepository(User) - private readonly userRepository: Repository, + @InjectRepository(UserV2) + private readonly userRepository: Repository, ) { super(userRepository); } diff --git a/server/src/coreV2/user/user.auto-resolver-opts.ts b/server/src/coreV2/user/user.auto-resolver-opts.ts index 942832586..edc6c4887 100644 --- a/server/src/coreV2/user/user.auto-resolver-opts.ts +++ b/server/src/coreV2/user/user.auto-resolver-opts.ts @@ -1,14 +1,12 @@ -import { SortDirection } from '@ptc-org/nestjs-query-core'; import { AutoResolverOpts, ReadResolverOpts, PagingStrategies, } from '@ptc-org/nestjs-query-graphql'; +import { UserV2 } from 'src/coreV2/user/user.entity'; import { JwtAuthGuard } from 'src/guards/jwt.auth.guard'; -import { User } from './user.entity'; - export const userAutoResolverOpts: AutoResolverOpts< any, any, @@ -18,12 +16,13 @@ export const userAutoResolverOpts: AutoResolverOpts< PagingStrategies >[] = [ { - EntityClass: User, - DTOClass: User, + EntityClass: UserV2, + DTOClass: UserV2, enableTotalCount: true, pagingStrategy: PagingStrategies.CURSOR, read: { - defaultSort: [{ field: 'id', direction: SortDirection.DESC }], + many: { disabled: true }, + one: { disabled: true }, }, create: { many: { disabled: true }, diff --git a/server/src/coreV2/user/user.entity.ts b/server/src/coreV2/user/user.entity.ts index efb3e6e02..3474992a6 100644 --- a/server/src/coreV2/user/user.entity.ts +++ b/server/src/coreV2/user/user.entity.ts @@ -13,8 +13,8 @@ import { GraphQLJSONObject } from 'graphql-type-json'; import { RefreshToken } from 'src/coreV2/refresh-token/refresh-token.entity'; -@Entity('users') -@ObjectType('user') +@Entity('userV2') +@ObjectType('userV2') // @Authorize({ // authorize: (context: any) => ({ // // FIXME: We do not have this relation in the database @@ -23,7 +23,7 @@ import { RefreshToken } from 'src/coreV2/refresh-token/refresh-token.entity'; // }, // }), // }) -export class User { +export class UserV2 { @IDField(() => ID) @PrimaryGeneratedColumn('uuid') id: string; diff --git a/server/src/coreV2/user/user.module.ts b/server/src/coreV2/user/user.module.ts index 4aa07c3fe..745206c88 100644 --- a/server/src/coreV2/user/user.module.ts +++ b/server/src/coreV2/user/user.module.ts @@ -5,8 +5,8 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; import { AbilityModule } from 'src/ability/ability.module'; import { FileModule } from 'src/core/file/file.module'; +import { UserV2 } from 'src/coreV2/user/user.entity'; -import { User } from './user.entity'; import { UserResolver } from './user.resolver'; import { userAutoResolverOpts } from './user.auto-resolver-opts'; @@ -15,7 +15,7 @@ import { UserService } from './services/user.service'; @Module({ imports: [ NestjsQueryGraphQLModule.forFeature({ - imports: [NestjsQueryTypeOrmModule.forFeature([User])], + imports: [NestjsQueryTypeOrmModule.forFeature([UserV2])], services: [UserService], resolvers: userAutoResolverOpts, }), diff --git a/server/src/coreV2/user/user.resolver.ts b/server/src/coreV2/user/user.resolver.ts index 606f149ed..469120791 100644 --- a/server/src/coreV2/user/user.resolver.ts +++ b/server/src/coreV2/user/user.resolver.ts @@ -26,8 +26,7 @@ import { FileUploadService } from 'src/core/file/services/file-upload.service'; import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator'; import { assert } from 'src/utils/assert'; import { JwtAuthGuard } from 'src/guards/jwt.auth.guard'; - -import { User } from './user.entity'; +import { UserV2 } from 'src/coreV2/user/user.entity'; import { UserService } from './services/user.service'; @@ -39,7 +38,7 @@ const getHMACKey = (email?: string, key?: string | null) => { }; @UseGuards(JwtAuthGuard) -@Resolver(() => User) +@Resolver(() => UserV2) export class UserResolver { constructor( private readonly userService: UserService, @@ -47,8 +46,8 @@ export class UserResolver { private readonly fileUploadService: FileUploadService, ) {} - @Query(() => User) - async currentUser(@AuthUser() { id }: User) { + @Query(() => UserV2) + async currentUserV2(@AuthUser() { id }: UserV2) { const user = await this.userService.findById(id); assert(user, 'User not found'); return user; @@ -57,14 +56,14 @@ export class UserResolver { @ResolveField(() => String, { nullable: false, }) - displayName(@Parent() parent: User): string { + displayName(@Parent() parent: UserV2): string { return `${parent.firstName ?? ''} ${parent.lastName ?? ''}`; } @ResolveField(() => String, { nullable: true, }) - supportUserHash(@Parent() parent: User): string | null { + supportUserHash(@Parent() parent: UserV2): string | null { if (this.environmentService.getSupportDriver() !== SupportDriver.Front) { return null; } @@ -73,8 +72,8 @@ export class UserResolver { } @Mutation(() => String) - async uploadProfilePicture( - @AuthUser() { id }: User, + async uploadProfilePictureV2( + @AuthUser() { id }: UserV2, @Args({ name: 'file', type: () => GraphQLUpload }) { createReadStream, filename, mimetype }: FileUpload, ): Promise { @@ -96,11 +95,11 @@ export class UserResolver { return paths[0]; } - @Mutation(() => User) + @Mutation(() => UserV2) @UseGuards(AbilityGuard) @CheckAbilities(DeleteUserAbilityHandler) - async deleteUserAccount( - @AuthUser() { id: userId }: User, + async deleteUserV2( + @AuthUser() { id: userId }: UserV2, @AuthWorkspace() { id: workspaceId }: Workspace, ) { return this.userService.deleteUser({ userId, workspaceId }); diff --git a/server/src/database/migrations/20231115084929_remove_user_workspace_member_relation/migration.sql b/server/src/database/migrations/20231115084929_remove_user_workspace_member_relation/migration.sql new file mode 100644 index 000000000..9ff25723b --- /dev/null +++ b/server/src/database/migrations/20231115084929_remove_user_workspace_member_relation/migration.sql @@ -0,0 +1,5 @@ +-- DropForeignKey +ALTER TABLE "workspace_members" DROP CONSTRAINT "workspace_members_userId_fkey"; + +-- AlterTable +ALTER TABLE "users" ADD COLUMN "defaultWorkspaceId" TEXT; diff --git a/server/src/database/migrations/20231115123209_remove_user_settings/migration.sql b/server/src/database/migrations/20231115123209_remove_user_settings/migration.sql new file mode 100644 index 000000000..0674f52fe --- /dev/null +++ b/server/src/database/migrations/20231115123209_remove_user_settings/migration.sql @@ -0,0 +1,20 @@ +/* + Warnings: + + - You are about to drop the column `settingsId` on the `users` table. All the data in the column will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "users" DROP CONSTRAINT "users_settingsId_fkey"; + +-- DropIndex +DROP INDEX "users_settingsId_key"; + +-- AlterTable +ALTER TABLE "user_settings" ADD COLUMN "userId" TEXT; + +-- AlterTable +ALTER TABLE "users" DROP COLUMN "settingsId"; + +-- AddForeignKey +ALTER TABLE "user_settings" ADD CONSTRAINT "user_settings_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/server/src/database/migrations/20231115130536_remove_user_settings/migration.sql b/server/src/database/migrations/20231115130536_remove_user_settings/migration.sql new file mode 100644 index 000000000..c2804c5eb --- /dev/null +++ b/server/src/database/migrations/20231115130536_remove_user_settings/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - You are about to drop the column `userId` on the `user_settings` table. All the data in the column will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "user_settings" DROP CONSTRAINT "user_settings_userId_fkey"; + +-- AlterTable +ALTER TABLE "user_settings" DROP COLUMN "userId"; diff --git a/server/src/database/schema.prisma b/server/src/database/schema.prisma index 3485908da..c028db125 100644 --- a/server/src/database/schema.prisma +++ b/server/src/database/schema.prisma @@ -102,18 +102,15 @@ model User { /// @Validator.IsOptional() canImpersonate Boolean @default(false) - /// @TypeGraphQL.omit(input: true) - workspaceMember WorkspaceMember? - companies Company[] + companies Company[] /// @TypeGraphQL.omit(input: true, output: true) - refreshTokens RefreshToken[] - comments Comment[] + refreshTokens RefreshToken[] + comments Comment[] + defaultWorkspaceId String? authoredActivities Activity[] @relation(name: "authoredActivities") assignedActivities Activity[] @relation(name: "assignedActivities") authoredAttachments Attachment[] @relation(name: "authoredAttachments") - settings UserSettings @relation(fields: [settingsId], references: [id]) - settingsId String @unique /// @TypeGraphQL.omit(input: true, output: true) deletedAt DateTime? @@ -138,7 +135,6 @@ model UserSettings { /// @Validator.IsString() locale String - user User? WorkspaceMember WorkspaceMember[] createdAt DateTime @default(now()) @@ -195,7 +191,6 @@ model WorkspaceMember { /// @Validator.IsOptional() allowImpersonation Boolean @default(true) - user User @relation(fields: [userId], references: [id]) userId String @unique /// @TypeGraphQL.omit(input: true, output: false) workspace Workspace @relation(fields: [workspaceId], references: [id]) diff --git a/server/src/database/seeds/users.ts b/server/src/database/seeds/users.ts index 23855f647..a4bebb379 100644 --- a/server/src/database/seeds/users.ts +++ b/server/src/database/seeds/users.ts @@ -19,19 +19,8 @@ export const seedUsers = async (prisma: PrismaClient) => { locale: 'en', passwordHash: '$2b$10$66d.6DuQExxnrfI9rMqOg.U1XIYpagr6Lv05uoWLYbYmtK0HDIvS6', // Applecar2025 - settingsId: 'twenty-ge256b39-3ec3-4fe3-8997-9dcb1084c109', avatarUrl: null, - workspaceMember: { - connectOrCreate: { - where: { - id: '20202020-0687-4c41-b707-ed1bfca972a7', - }, - create: { - workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', - settingsId: 'twenty-ge256b39-3ec3-4fe3-8997-9dcb1084c109', - }, - }, - }, + defaultWorkspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', }, }); @@ -52,15 +41,8 @@ export const seedUsers = async (prisma: PrismaClient) => { lastName: 'Ive', email: 'jony.ive@apple.dev', locale: 'en', - settingsId: 'twenty-ge256b39-3ec3-4fe3-8997-2c4a2035a215', avatarUrl: null, - workspaceMember: { - create: { - id: '20202020-77d5-4cb6-b60a-f4a835a85d61', - workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', - settingsId: 'twenty-ge256b39-3ec3-4fe3-8997-2c4a2035a215', - }, - }, + defaultWorkspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', }, }); @@ -81,15 +63,8 @@ export const seedUsers = async (prisma: PrismaClient) => { lastName: 'Schiler', email: 'phil.schiler@apple.dev', locale: 'en', - settingsId: 'twenty-ge256b39-3ec3-4fe3-8997-8e1f2097b328', avatarUrl: null, - workspaceMember: { - create: { - id: '20202020-1553-45c6-a028-5a9064cce07f', - workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', - settingsId: 'twenty-ge256b39-3ec3-4fe3-8997-8e1f2097b328', - }, - }, + defaultWorkspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', }, }); @@ -110,14 +85,6 @@ export const seedUsers = async (prisma: PrismaClient) => { lastName: 'Bochet', email: 'charles@twenty.dev', locale: 'en', - settingsId: 'twenty-ge256b39-3ec3-4fe3-8997-5e2d1049c430', - workspaceMember: { - create: { - id: 'twenty-dev-7ed9d213-1c25-4d02-bf35-6aeccf7oa419', - workspaceId: 'twenty-dev-7ed9d212-1c25-4d02-bf25-6aeccf7ea420', - settingsId: 'twenty-ge256b39-3ec3-4fe3-8997-5e2d1049c430', - }, - }, }, }); }; diff --git a/server/src/database/typeorm-seeds/metadata/field-metadata/workspace-member.ts b/server/src/database/typeorm-seeds/metadata/field-metadata/workspace-member.ts index 5433ef086..6960cab4b 100644 --- a/server/src/database/typeorm-seeds/metadata/field-metadata/workspace-member.ts +++ b/server/src/database/typeorm-seeds/metadata/field-metadata/workspace-member.ts @@ -8,6 +8,7 @@ const fieldMetadataTableName = 'fieldMetadata'; export enum SeedWorkspaceMemberFieldMetadataIds { FirstName = '20202020-1fa8-4d38-9fa4-0cf696909298', LastName = '20202020-8c37-4163-ba06-1dada334ce3e', + AvatarUrl = '20202020-7ba6-40d5-934b-17146183a212', Locale = '20202020-10f6-4df9-8d6f-a760b65bd800', ColorScheme = '20202020-83f2-4c5f-96b0-0c51ecc160e3', AllowImpersonation = '20202020-bb19-44a1-8156-8866f87a5f42', @@ -77,6 +78,22 @@ export const seedWorkspaceMemberFieldMetadata = async ( icon: 'IconCircleUser', isNullable: false, }, + { + id: SeedWorkspaceMemberFieldMetadataIds.AvatarUrl, + objectMetadataId: SeedObjectMetadataIds.WorkspaceMember, + isCustom: false, + workspaceId: SeedWorkspaceId, + isActive: true, + type: 'TEXT', + name: 'avatarUrl', + label: 'Avatar Url', + targetColumnMap: { + value: 'avatarUrl', + }, + description: 'Workspace member avatar', + icon: 'IconFileUpload', + isNullable: true, + }, { id: SeedWorkspaceMemberFieldMetadataIds.UserId, objectMetadataId: SeedObjectMetadataIds.WorkspaceMember, @@ -123,7 +140,7 @@ export const seedWorkspaceMemberFieldMetadata = async ( }, description: 'Preferred color scheme', icon: 'IconColorSwatch', - isNullable: false, + isNullable: true, }, { id: SeedWorkspaceMemberFieldMetadataIds.Locale, diff --git a/server/src/database/typeorm-seeds/tenant/workspaceMember.ts b/server/src/database/typeorm-seeds/tenant/workspaceMember.ts index f38b17c2b..17b1e1770 100644 --- a/server/src/database/typeorm-seeds/tenant/workspaceMember.ts +++ b/server/src/database/typeorm-seeds/tenant/workspaceMember.ts @@ -37,7 +37,7 @@ export const seedWorkspaceMember = async ( firstName: 'Tim', lastName: 'Apple', locale: 'en', - colorScheme: 'light', + colorScheme: 'Light', allowImpersonation: true, userId: WorkspaceMemberUserIds.Tim, }, @@ -46,7 +46,7 @@ export const seedWorkspaceMember = async ( firstName: 'Jony', lastName: 'Ive', locale: 'en', - colorScheme: 'light', + colorScheme: 'Light', allowImpersonation: true, userId: WorkspaceMemberUserIds.Jony, }, @@ -55,9 +55,9 @@ export const seedWorkspaceMember = async ( firstName: 'Phil', lastName: 'Shiler', locale: 'en', - colorScheme: 'light', + colorScheme: 'Light', allowImpersonation: true, - userId: WorkspaceMemberUserIds.Phil, + userId: WorkspaceMemberUserIds.Tim, }, ]) .execute(); diff --git a/server/src/metadata/relation-metadata/dtos/relation-metadata.dto.ts b/server/src/metadata/relation-metadata/dtos/relation-metadata.dto.ts index cb14e0b99..b49b2c6bc 100644 --- a/server/src/metadata/relation-metadata/dtos/relation-metadata.dto.ts +++ b/server/src/metadata/relation-metadata/dtos/relation-metadata.dto.ts @@ -1,4 +1,10 @@ -import { ObjectType, ID, Field, HideField } from '@nestjs/graphql'; +import { + ObjectType, + ID, + Field, + HideField, + registerEnumType, +} from '@nestjs/graphql'; import { CreateDateColumn, UpdateDateColumn } from 'typeorm'; import { @@ -11,6 +17,11 @@ import { import { ObjectMetadataDTO } from 'src/metadata/object-metadata/dtos/object-metadata.dto'; import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; +registerEnumType(RelationMetadataType, { + name: 'RelationMetadataType', + description: 'Type of the relation', +}); + @ObjectType('relation') @Authorize({ authorize: (context: any) => ({ diff --git a/server/src/metadata/relation-metadata/relation-metadata.entity.ts b/server/src/metadata/relation-metadata/relation-metadata.entity.ts index dfed09a47..95c2c5687 100644 --- a/server/src/metadata/relation-metadata/relation-metadata.entity.ts +++ b/server/src/metadata/relation-metadata/relation-metadata.entity.ts @@ -1,5 +1,3 @@ -import { registerEnumType } from '@nestjs/graphql'; - import { Column, CreateDateColumn, @@ -22,11 +20,6 @@ export enum RelationMetadataType { MANY_TO_MANY = 'MANY_TO_MANY', } -registerEnumType(RelationMetadataType, { - name: 'RelationMetadataType', - description: 'Type of the relation', -}); - @Entity('relationMetadata') export class RelationMetadataEntity implements RelationMetadataInterface { @PrimaryGeneratedColumn('uuid') diff --git a/server/src/metadata/tenant-migration/migrations/1697618026-addWorspaceMemberTable.ts b/server/src/metadata/tenant-migration/migrations/1697618026-addWorspaceMemberTable.ts index 567a8cad0..1de71122b 100644 --- a/server/src/metadata/tenant-migration/migrations/1697618026-addWorspaceMemberTable.ts +++ b/server/src/metadata/tenant-migration/migrations/1697618026-addWorspaceMemberTable.ts @@ -22,6 +22,11 @@ export const addWorkspaceMemberTable: TenantMigrationTableAction[] = [ columnType: 'varchar', action: TenantMigrationColumnActionType.CREATE, }, + { + columnName: 'avatarUrl', + columnType: 'varchar', + action: TenantMigrationColumnActionType.CREATE, + }, { columnName: 'colorScheme', columnType: 'varchar', diff --git a/server/src/tenant-manager/standard-objects-prefill-data/company.ts b/server/src/tenant-manager/standard-objects-prefill-data/company.ts new file mode 100644 index 000000000..814615457 --- /dev/null +++ b/server/src/tenant-manager/standard-objects-prefill-data/company.ts @@ -0,0 +1,51 @@ +import { EntityManager } from 'typeorm'; + +export const companyPrefillData = async ( + entityManager: EntityManager, + schemaName: string, +) => { + await entityManager + .createQueryBuilder() + .insert() + .into(`${schemaName}.company`, [ + 'name', + 'domainName', + 'address', + 'employees', + ]) + .orIgnore() + .values([ + { + name: 'Airbnb', + domainName: 'airbnb.com', + address: 'San Francisco', + employees: 5000, + }, + { + name: 'Qonto', + domainName: 'qonto.com', + address: 'San Francisco', + employees: 800, + }, + { + name: 'Stripe', + domainName: 'stripe.com', + address: 'San Francisco', + employees: 8000, + }, + { + name: 'Figma', + domainName: 'figma.com', + address: 'San Francisco', + employees: 800, + }, + { + name: 'Notion', + domainName: 'notion.com', + address: 'San Francisco', + employees: 400, + }, + ]) + .returning('*') + .execute(); +}; diff --git a/server/src/tenant-manager/standard-objects-prefill-data/person.ts b/server/src/tenant-manager/standard-objects-prefill-data/person.ts new file mode 100644 index 000000000..b645b6b1d --- /dev/null +++ b/server/src/tenant-manager/standard-objects-prefill-data/person.ts @@ -0,0 +1,62 @@ +import { EntityManager } from 'typeorm'; + +export const personPrefillData = async ( + entityManager: EntityManager, + schemaName: string, +) => { + await entityManager + .createQueryBuilder() + .insert() + .into(`${schemaName}.person`, [ + 'firstName', + 'lastName', + 'city', + 'email', + 'avatarUrl', + ]) + .orIgnore() + .values([ + { + firstName: 'Brian', + lastName: 'Chesky', + city: 'San Francisco', + email: 'chesky@airbnb.com', + avatarUrl: + 'data:image/jpeg;base64,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', + }, + { + firstName: 'Alexandre', + lastName: 'Prot', + city: 'Paris', + email: 'prot@qonto.com', + avatarUrl: + 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABIAAAAAQAAAEgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAADygAwAEAAAAAQAAADwAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJTQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/AABEIADwAPAMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2wBDAAkGBxAQEBUQEBAVFRUVFRUVFRUVEBAVFRUQFRUWFhUVFRUYHSggGBolGxUVITEhJSkrLi4uFx8zODMtNygtLiv/2wBDAQoKCg4NDhcQEBctHR0dLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS3/3QAEAAT/2gAMAwEAAhEDEQA/APZ6KSitCRc1Sn1e3Q7XmQEdRuHH19K4n4keK3i3WluSHwu9lJBBbkID24GSfevOILa6lO4GSQ4x8qsRjuARUOVjSMLn0RDMrgMpBB7g5FPrwzRdavLaQJ+9jPZXVtjEdsEdcelex6DqQuYFlxgkYYejjqKadxSjY0aKSlpkH//Q9mooFFaEnj2vWQk1xoGUsHfzH5IBTYuB9K9HtkVU2qAABgAYGB7CsrU9Jj+2i5YfOA8ZbJyyPgqPouMD6moF8PokvnAnr1LPnPPv71zvdnbCNkT6xaxSfLKUweBuYDk9CKk+G4xbSLzlJWRg3Xenyn88A575zWVfaPG9zI7qCeDzyQhGML6DK/nXR+FQv78rjBmABB5+WNQc++c0Qeoqyurm/RSZpM1ucZ//0fZaWkqOWZV6n8O9aJXIvYzvEIjEe5iA2RtyeTjkgDucZ/KsOaR2X5QCAOu4gg+orX1aES7XIBKNuXPYEFT+hNc7dCaAkw4Zf7hOCPofT2rOrCzR1YepoZIMvmO7yM4YY5OTnoFUAACvTbWMKijAHAzgY5wMn61wGgXjSXSm5CxhWAVSR80pBK8/h+legbqVKO7JxE7tJEmaM1HmjNanOf/S9blnJ6cD171VI9afu4pjV2xVjmbuMuruOGNnlYBV6k989AAOpPTArnRc74jIFO0jcAww+09Nw7EdxW7cQq+A6hsHIyM4PqKrXVgjAkZU/wCyxGfqOholSU1bqOFZ03focTPpkszoFBGJlkPvwQB+pP4Cu8tLrDmIZ3KqliRwd2eh79OR2rLvrkxRIygE8ZJHXBxzj6VNYpnkscvyxyM8+noKcKajCxNSo5TbOhhnDdxkdcVLmsy3GJQBwNjcfRlq/msJqzNoO6P/2Q==', + }, + { + firstName: 'Patrick', + lastName: 'Collison', + city: 'San Francisco', + email: 'collison@stripe.com', + avatarUrl: + 'data:image/jpeg;base64,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', + }, + { + firstName: 'Dylan', + lastName: 'Field', + city: 'San Francisco', + email: 'field@figma.com', + avatarUrl: + 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQABLAEsAAD/4QSQRXhpZgAATU0AKgAAAAgADAEOAAIAAADlAAAAngEPAAIAAAAGAAABhAEQAAIAAAANAAABigESAAMAAAABAAEAAAEaAAUAAAABAAABmAEbAAUAAAABAAABoAEoAAMAAAABAAIAAAExAAIAAAAhAAABqAEyAAIAAAAUAAABygE7AAIAAAAPAAAB3oKYAAIAAAASAAAB7odpAAQAAAABAAACAAAAAABTQU4gRlJBTkNJU0NPLCBDQUxJRk9STklBIC0gT0NUT0JFUiAyMDogQ28tZm91bmRlciAmIENFTyBvZsKgRmlnbWEgRHlsYW4gRmllbGQgc3BlYWtzIG9uc3RhZ2UgZHVyaW5nIFRlY2hDcnVuY2ggRGlzcnVwdCAyMDIyIG9uIE9jdG9iZXIgMjAsIDIwMjIgaW4gU2FuIEZyYW5jaXNjbywgQ2FsaWZvcm5pYS4gKFBob3RvIGJ5IEtpbWJlcmx5IFdoaXRlL0dldHR5IEltYWdlcyBmb3IgVGVjaENydW5jaCkAAENhbm9uAENhbm9uIEVPUyBSNQAAAAABLAAAAAEAAAEsAAAAAUFkb2JlIFBob3Rvc2hvcCAyMy41IChNYWNpbnRvc2gpAAAyMDIyOjEwOjIwIDEwOjU2OjU4AEtpbWJlcmx5IFdoaXRlAAAyMDIyIEdldHR5IEltYWdlcwAAI4KaAAUAAAABAAADqoKdAAUAAAABAAADsogiAAMAAAABAAEAAIgnAAMAAAABCcQAAIgwAAMAAAABAAIAAIgyAAQAAAABAAAJxJAAAAcAAAAEMDIzMZADAAIAAAAUAAADupAEAAIAAAAUAAADzpAQAAIAAAAHAAAD4pARAAIAAAAHAAAD6pASAAIAAAAHAAAD8pIBAAoAAAABAAAD+pICAAUAAAABAAAEApIEAAoAAAABAAAECpIFAAUAAAABAAAEEpIHAAMAAAABAAUAAJIJAAMAAAABAAAAAJIKAAUAAAABAAAEGpKQAAIAAAADNzgAAJKRAAIAAAADNzgAAJKSAAIAAAADNzgAAKACAAQAAAABAAAAPKADAAQAAAABAAAAUKIOAAUAAAABAAAEIqIPAAUAAAABAAAEKqIQAAMAAAABAAIAAKQBAAMAAAABAAAAAKQCAAMAAAABAAEAAKQDAAMAAAABAAEAAKQGAAMAAAABAAAAAKQxAAIAAAANAAAEMqQyAAUAAAAEAAAEQKQ0AAIAAAAcAAAEYKQ1AAIAAAALAAAEfAAAAAAAAAABAAAB9AAAAAUAAAABMjAyMjoxMDoyMCAxMjo0ODozMgAyMDIyOjEwOjIwIDEyOjQ4OjMyAC0wNTowMAAALTA1OjAwAAAtMDU6MDAAAAAAdbYAAA0hAAQwyQAA5wMAAAAAAAAAAQAAAAMAAAABAAAAbgAAAAEAFT2AAAAB2QAlDXsAAAM6MDUyMDIxMDA0MTE5AAAAAABGAAAAAQAAAMgAAAABAAAAAAAAAAEAAAAAAAAAAUVGNzAtMjAwbW0gZi8yLjhMIElTIElJIFVTTQAwMDAwNDBjNzVjAAD/4ROOaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bWxuczpHZXR0eUltYWdlc0dJRlQ9Imh0dHA6Ly94bXAuZ2V0dHlpbWFnZXMuY29tL2dpZnQvMS4wLyIgeG1sbnM6SXB0YzR4bXBDb3JlPSJodHRwOi8vaXB0Yy5vcmcvc3RkL0lwdGM0eG1wQ29yZS8xLjAveG1sbnMvIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnBsdXM9Imh0dHA6Ly9ucy51c2VwbHVzLm9yZy9sZGYveG1wLzEuMC8iIHhtbG5zOnhtcFJpZ2h0cz0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3JpZ2h0cy8iIHhtbG5zOklwdGM0eG1wRXh0PSJodHRwOi8vaXB0Yy5vcmcvc3RkL0lwdGM0eG1wRXh0LzIwMDgtMDItMjkvIiBwaG90b3Nob3A6Q2l0eT0iU2FuIEZyYW5jaXNjbyIgcGhvdG9zaG9wOkRhdGVDcmVhdGVkPSIyMDIyLTEwLTIwVDEyOjQ4OjMyLjc4IiBwaG90b3Nob3A6SGVhZGxpbmU9IlRlY2hDcnVuY2ggRGlzcnVwdCAyMDIyIC0gRGF5IDMiIHBob3Rvc2hvcDpDb3VudHJ5PSJVbml0ZWQgU3RhdGVzIiBwaG90b3Nob3A6Q29weXJpZ2h0RmxhZz0idHJ1ZSIgcGhvdG9zaG9wOkNhdGVnb3J5PSJFIiBwaG90b3Nob3A6U291cmNlPSJHZXR0eSBJbWFnZXMgTm9ydGggQW1lcmljYSIgcGhvdG9zaG9wOlVyZ2VuY3k9IjMiIHBob3Rvc2hvcDpBdXRob3JzUG9zaXRpb249IlN0cmluZ2VyIiBwaG90b3Nob3A6VHJhbnNtaXNzaW9uUmVmZXJlbmNlPSI3NzU4ODQzODQiIHBob3Rvc2hvcDpVUkw9Imh0dHBzOi8vd3d3LmdldHR5aW1hZ2VzLmNvbSIgcGhvdG9zaG9wOlN0YXRlPSJDYWxpZm9ybmlhIiBwaG90b3Nob3A6Q3JlZGl0PSJHZXR0eSBJbWFnZXMgZm9yIFRlY2hDcnVuY2giIHBob3Rvc2hvcDpDYXB0aW9uV3JpdGVyPSJFRCAvIEVEIiBkYzpSaWdodHM9IjIwMjIgR2V0dHkgSW1hZ2VzIiBHZXR0eUltYWdlc0dJRlQ6SW1hZ2VSYW5rPSIzIiBHZXR0eUltYWdlc0dJRlQ6RGxyZWY9ImZjNkRiREMzK0ZHWG5PMXpiVzFKYkE9PSIgR2V0dHlJbWFnZXNHSUZUOkFzc2V0SUQ9IjE0MzUwODg1NjciIElwdGM0eG1wQ29yZTpDb3VudHJ5Q29kZT0iVVNBIiBJcHRjNHhtcENvcmU6TG9jYXRpb249Ik1vc2NvbmUgQ2VudGVyIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyMi0xMC0yMFQxMjo0ODozMi43OCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjMuNSAoTWFjaW50b3NoKSIgeG1wOk1vZGlmeURhdGU9IjIwMjItMTAtMjBUMTA6NTY6NTguNzgiIHBsdXM6SW1hZ2VTdXBwbGllckltYWdlSUQ9IjE0MzUwODg1NjciIHhtcFJpZ2h0czpXZWJTdGF0ZW1lbnQ9Imh0dHBzOi8vd3d3LmdldHR5aW1hZ2VzLmNvbS9ldWxhP3V0bV9tZWRpdW09b3JnYW5pYyZhbXA7dXRtX3NvdXJjZT1nb29nbGUmYW1wO3V0bV9jYW1wYWlnbj1pcHRjdXJsIiBJcHRjNHhtcEV4dDpIZWFkbGluZT0iVGVjaENydW5jaCBEaXNydXB0IDIwMjIgLSBEYXkgMyI+IDxwaG90b3Nob3A6U3VwcGxlbWVudGFsQ2F0ZWdvcmllcz4gPHJkZjpCYWc+IDxyZGY6bGk+QUNFPC9yZGY6bGk+IDxyZGY6bGk+RU5UPC9yZGY6bGk+IDwvcmRmOkJhZz4gPC9waG90b3Nob3A6U3VwcGxlbWVudGFsQ2F0ZWdvcmllcz4gPGRjOnJpZ2h0cz4gPHJkZjpBbHQ+IDxyZGY6bGkgeG1sOmxhbmc9IngtZGVmYXVsdCI+MjAyMiBHZXR0eSBJbWFnZXM8L3JkZjpsaT4gPC9yZGY6QWx0PiA8L2RjOnJpZ2h0cz4gPGRjOnN1YmplY3Q+IDxyZGY6QmFnPiA8cmRmOmxpPmFydHMgY3VsdHVyZSBhbmQgZW50ZXJ0YWlubWVudDwvcmRmOmxpPiA8L3JkZjpCYWc+IDwvZGM6c3ViamVjdD4gPGRjOmNyZWF0b3I+IDxyZGY6U2VxPiA8cmRmOmxpPktpbWJlcmx5IFdoaXRlPC9yZGY6bGk+IDwvcmRmOlNlcT4gPC9kYzpjcmVhdG9yPiA8ZGM6dGl0bGU+IDxyZGY6QWx0PiA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPjE0MzUwODg1Njc8L3JkZjpsaT4gPC9yZGY6QWx0PiA8L2RjOnRpdGxlPiA8ZGM6ZGVzY3JpcHRpb24+IDxyZGY6QWx0PiA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPlNBTiBGUkFOQ0lTQ08sIENBTElGT1JOSUEgLSBPQ1RPQkVSIDIwOiBDby1mb3VuZGVyICZhbXA7IENFTyBvZsKgRmlnbWEgRHlsYW4gRmllbGQgc3BlYWtzIG9uc3RhZ2UgZHVyaW5nIFRlY2hDcnVuY2ggRGlzcnVwdCAyMDIyIG9uIE9jdG9iZXIgMjAsIDIwMjIgaW4gU2FuIEZyYW5jaXNjbywgQ2FsaWZvcm5pYS4gKFBob3RvIGJ5IEtpbWJlcmx5IFdoaXRlL0dldHR5IEltYWdlcyBmb3IgVGVjaENydW5jaCk8L3JkZjpsaT4gPC9yZGY6QWx0PiA8L2RjOmRlc2NyaXB0aW9uPiA8cGx1czpMaWNlbnNvcj4gPHJkZjpTZXE+IDxyZGY6bGkgcGx1czpMaWNlbnNvclVSTD0iaHR0cHM6Ly93d3cuZ2V0dHlpbWFnZXMuY29tL2RldGFpbC8xNDM1MDg4NTY3P3V0bV9tZWRpdW09b3JnYW5pYyZhbXA7dXRtX3NvdXJjZT1nb29nbGUmYW1wO3V0bV9jYW1wYWlnbj1pcHRjdXJsIi8+IDwvcmRmOlNlcT4gPC9wbHVzOkxpY2Vuc29yPiA8SXB0YzR4bXBFeHQ6UGVyc29uSW5JbWFnZT4gPHJkZjpCYWc+IDxyZGY6bGk+RHlsYW4gRmllbGQ8L3JkZjpsaT4gPC9yZGY6QmFnPiA8L0lwdGM0eG1wRXh0OlBlcnNvbkluSW1hZ2U+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDw/eHBhY2tldCBlbmQ9InciPz4A/+0CsFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAJ3HAFaAAMbJUccAgAAAgACHAI8AAYxMjQ4MzIcAngA5FNBTiBGUkFOQ0lTQ08sIENBTElGT1JOSUEgLSBPQ1RPQkVSIDIwOiBD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', + }, + { + firstName: 'Ivan', + lastName: 'Zhao', + city: 'San Francisco', + email: 'zhao@notion.com', + avatarUrl: + 'data:image/jpeg;base64,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', + }, + ]) + .returning('*') + .execute(); +}; diff --git a/server/src/tenant-manager/standard-objects-prefill-data/pipeline-step.ts b/server/src/tenant-manager/standard-objects-prefill-data/pipeline-step.ts new file mode 100644 index 000000000..707676105 --- /dev/null +++ b/server/src/tenant-manager/standard-objects-prefill-data/pipeline-step.ts @@ -0,0 +1,41 @@ +import { EntityManager } from 'typeorm'; + +export const pipelineStepPrefillData = async ( + entityManager: EntityManager, + schemaName: string, +) => { + await entityManager + .createQueryBuilder() + .insert() + .into(`${schemaName}.pipelineStep`, ['name', 'color', 'position']) + .orIgnore() + .values([ + { + name: 'New', + color: 'red', + position: 0, + }, + { + name: 'Screening', + color: 'purple', + position: 1, + }, + { + name: 'Meeting', + color: 'sky', + position: 2, + }, + { + name: 'Proposal', + color: 'turquoise', + position: 3, + }, + { + name: 'Customer', + color: 'yellow', + position: 4, + }, + ]) + .returning('*') + .execute(); +}; diff --git a/server/src/tenant-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts b/server/src/tenant-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts index 2fcca6d85..e98737f62 100644 --- a/server/src/tenant-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts +++ b/server/src/tenant-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts @@ -1,269 +1,31 @@ import { DataSource, EntityManager } from 'typeorm'; +import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; +import { viewPrefillData } from 'src/tenant-manager/standard-objects-prefill-data/view'; +import { companyPrefillData } from 'src/tenant-manager/standard-objects-prefill-data/company'; +import { personPrefillData } from 'src/tenant-manager/standard-objects-prefill-data/person'; +import { pipelineStepPrefillData } from 'src/tenant-manager/standard-objects-prefill-data/pipeline-step'; + export const standardObjectsPrefillData = async ( workspaceDataSource: DataSource, schemaName: string, + objectMetadata: ObjectMetadataEntity[], ) => { + const objectMetadataMap = objectMetadata.reduce((acc, object) => { + acc[object.nameSingular] = { + id: object.id, + fields: object.fields.reduce((acc, field) => { + acc[field.name] = field.id; + return acc; + }, {}), + }; + return acc; + }, {}); + workspaceDataSource.transaction(async (entityManager: EntityManager) => { - const createdCompanies = await entityManager - .createQueryBuilder() - .insert() - .into(`${schemaName}.company`, [ - 'name', - 'domainName', - 'address', - 'employees', - ]) - .orIgnore() - .values([ - { - name: 'Airbnb', - domainName: 'airbnb.com', - address: 'San Francisco', - employees: 5000, - }, - { - name: 'Qonto', - domainName: 'qonto.com', - address: 'San Francisco', - employees: 800, - }, - { - name: 'Stripe', - domainName: 'stripe.com', - address: 'San Francisco', - employees: 8000, - }, - { - name: 'Figma', - domainName: 'figma.com', - address: 'San Francisco', - employees: 800, - }, - { - name: 'Notion', - domainName: 'notion.com', - address: 'San Francisco', - employees: 400, - }, - ]) - .returning('*') - .execute(); - - const companyIdMap = createdCompanies.raw.reduce((acc, view) => { - acc[view.name] = view.id; - return acc; - }, {}); - - const createdViews = await entityManager - .createQueryBuilder() - .insert() - .into(`${schemaName}.view`, ['name', 'objectMetadataId', 'type']) - .orIgnore() - .values([ - { - name: 'All companies', - objectMetadataId: 'company', - type: 'table', - }, - { - name: 'All people', - objectMetadataId: 'person', - type: 'table', - }, - { - name: 'All opportunities', - objectMetadataId: 'company', - type: 'kanban', - }, - { - name: 'All Companies (V2)', - objectMetadataId: companyIdMap['Airbnb'], - type: 'table', - }, - ]) - .returning('*') - .execute(); - - const viewIdMap = createdViews.raw.reduce((acc, view) => { - acc[view.name] = view.id; - return acc; - }, {}); - - await entityManager - .createQueryBuilder() - .insert() - .into(`${schemaName}.viewField`, [ - 'fieldMetadataId', - 'viewId', - 'position', - 'isVisible', - 'size', - ]) - .orIgnore() - .values([ - { - fieldMetadataId: 'name', - viewId: viewIdMap['All Companies (V2)'], - position: 0, - isVisible: true, - size: 180, - }, - { - fieldMetadataId: 'name', - viewId: viewIdMap['All companies'], - position: 0, - isVisible: true, - size: 180, - }, - { - fieldMetadataId: 'domainName', - viewId: viewIdMap['All companies'], - position: 1, - isVisible: true, - size: 100, - }, - { - fieldMetadataId: 'accountOwner', - viewId: viewIdMap['All companies'], - position: 2, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: 'createdAt', - viewId: viewIdMap['All companies'], - position: 3, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: 'employees', - viewId: viewIdMap['All companies'], - position: 4, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: 'linkedin', - viewId: viewIdMap['All companies'], - position: 5, - isVisible: true, - size: 170, - }, - { - fieldMetadataId: 'address', - viewId: viewIdMap['All companies'], - position: 6, - isVisible: true, - size: 170, - }, - { - fieldMetadataId: 'displayName', - viewId: viewIdMap['All people'], - position: 0, - isVisible: true, - size: 210, - }, - { - fieldMetadataId: 'email', - viewId: viewIdMap['All people'], - position: 1, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: 'company', - viewId: viewIdMap['All people'], - position: 2, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: 'phone', - viewId: viewIdMap['All people'], - position: 3, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: 'createdAt', - viewId: viewIdMap['All people'], - position: 4, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: 'city', - viewId: viewIdMap['All people'], - position: 5, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: 'jobTitle', - viewId: viewIdMap['All people'], - position: 6, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: 'linkedin', - viewId: viewIdMap['All people'], - position: 7, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: 'x', - viewId: viewIdMap['All people'], - position: 8, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: 'amount', - viewId: viewIdMap['All opportunities'], - position: 0, - isVisible: true, - size: 180, - }, - { - fieldMetadataId: 'probability', - viewId: viewIdMap['All opportunities'], - position: 1, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: 'closeDate', - viewId: viewIdMap['All opportunities'], - position: 2, - isVisible: true, - size: 100, - }, - { - fieldMetadataId: 'company', - viewId: viewIdMap['All opportunities'], - position: 3, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: 'createdAt', - viewId: viewIdMap['All opportunities'], - position: 4, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: 'pointOfContact', - viewId: viewIdMap['All opportunities'], - position: 5, - isVisible: true, - size: 150, - }, - ]) - .execute(); + await companyPrefillData(entityManager, schemaName); + await personPrefillData(entityManager, schemaName); + await viewPrefillData(entityManager, schemaName, objectMetadataMap); + await pipelineStepPrefillData(entityManager, schemaName); }); }; diff --git a/server/src/tenant-manager/standard-objects-prefill-data/view.ts b/server/src/tenant-manager/standard-objects-prefill-data/view.ts new file mode 100644 index 000000000..981caabfc --- /dev/null +++ b/server/src/tenant-manager/standard-objects-prefill-data/view.ts @@ -0,0 +1,278 @@ +import { EntityManager } from 'typeorm'; + +import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; + +export const viewPrefillData = async ( + entityManager: EntityManager, + schemaName: string, + objectMetadataMap: Record, +) => { + // Creating views + const createdViews = await entityManager + .createQueryBuilder() + .insert() + .into(`${schemaName}.view`, ['name', 'objectMetadataId', 'type']) + .orIgnore() + .values([ + { + name: 'All companies', + objectMetadataId: 'company', + type: 'table', + }, + { + name: 'All people', + objectMetadataId: 'person', + type: 'table', + }, + { + name: 'All opportunities', + objectMetadataId: 'company', + type: 'kanban', + }, + { + name: 'All Companies (V2)', + objectMetadataId: objectMetadataMap['companyV2'].id, + type: 'table', + }, + { + name: 'All People (V2)', + objectMetadataId: objectMetadataMap['personV2'].id, + type: 'table', + }, + { + name: 'All Opportunities (V2)', + objectMetadataId: objectMetadataMap['companyV2'].id, + type: 'kanban', + }, + ]) + .returning('*') + .execute(); + + const viewIdMap = createdViews.raw.reduce((acc, view) => { + acc[view.name] = view.id; + return acc; + }, {}); + + // Creating viewFields + await entityManager + .createQueryBuilder() + .insert() + .into(`${schemaName}.viewField`, [ + 'fieldMetadataId', + 'viewId', + 'position', + 'isVisible', + 'size', + ]) + .orIgnore() + .values([ + // CompanyV2 + { + fieldMetadataId: objectMetadataMap['companyV2'].fields['name'], + viewId: viewIdMap['All Companies (V2)'], + position: 0, + isVisible: true, + size: 180, + }, + { + fieldMetadataId: objectMetadataMap['companyV2'].fields['domainName'], + viewId: viewIdMap['All Companies (V2)'], + position: 1, + isVisible: true, + size: 100, + }, + // { + // fieldMetadataId: objectMetadataMap['companyV2'].fields['accountOwner'], + // viewId: viewIdMap['All Companies (V2)'], + // position: 2, + // isVisible: true, + // size: 150, + // }, + // { + // fieldMetadataId: 'createdAt', + // viewId: viewIdMap['All Companies (V2)'], + // position: 3, + // isVisible: true, + // size: 150, + // }, + { + fieldMetadataId: objectMetadataMap['companyV2'].fields['employees'], + viewId: viewIdMap['All Companies (V2)'], + position: 4, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['companyV2'].fields['linkedinUrl'], + viewId: viewIdMap['All Companies (V2)'], + position: 5, + isVisible: true, + size: 170, + }, + { + fieldMetadataId: objectMetadataMap['companyV2'].fields['address'], + viewId: viewIdMap['All Companies (V2)'], + position: 6, + isVisible: true, + size: 170, + }, + // PeopleV2 + { + fieldMetadataId: objectMetadataMap['personV2'].fields['firstName'], // TODO: change to displayName once we have name field type + viewId: viewIdMap['All People (V2)'], + position: 0, + isVisible: true, + size: 210, + }, + { + fieldMetadataId: objectMetadataMap['personV2'].fields['email'], + viewId: viewIdMap['All People (V2)'], + position: 1, + isVisible: true, + size: 150, + }, + // { + // fieldMetadataId: objectMetadataMap['personV2'].fields['company'], + // viewId: viewIdMap['All People (V2)'], + // position: 2, + // isVisible: true, + // size: 150, + // }, + { + fieldMetadataId: objectMetadataMap['personV2'].fields['phone'], + viewId: viewIdMap['All People (V2)'], + position: 3, + isVisible: true, + size: 150, + }, + // { + // fieldMetadataId: 'createdAt', + // viewId: viewIdMap['All People (V2)'], + // position: 4, + // isVisible: true, + // size: 150, + // }, + { + fieldMetadataId: objectMetadataMap['personV2'].fields['city'], + viewId: viewIdMap['All People (V2)'], + position: 5, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['personV2'].fields['jobTitle'], + viewId: viewIdMap['All People (V2)'], + position: 6, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['personV2'].fields['linkedinUrl'], + viewId: viewIdMap['All People (V2)'], + position: 7, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['personV2'].fields['xUrl'], + viewId: viewIdMap['All People (V2)'], + position: 8, + isVisible: true, + size: 150, + }, + // Companies + { + fieldMetadataId: 'name', + viewId: viewIdMap['All companies'], + position: 0, + isVisible: true, + size: 180, + }, + { + fieldMetadataId: 'domainName', + viewId: viewIdMap['All companies'], + position: 1, + isVisible: true, + size: 100, + }, + { + fieldMetadataId: 'accountOwner', + viewId: viewIdMap['All companies'], + position: 2, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: 'createdAt', + viewId: viewIdMap['All companies'], + position: 3, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: 'employees', + viewId: viewIdMap['All companies'], + position: 4, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: 'linkedin', + viewId: viewIdMap['All companies'], + position: 5, + isVisible: true, + size: 170, + }, + { + fieldMetadataId: 'address', + viewId: viewIdMap['All companies'], + position: 6, + isVisible: true, + size: 170, + }, + // Opportunities + { + fieldMetadataId: 'amount', + viewId: viewIdMap['All opportunities'], + position: 0, + isVisible: true, + size: 180, + }, + { + fieldMetadataId: 'probability', + viewId: viewIdMap['All opportunities'], + position: 1, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: 'closeDate', + viewId: viewIdMap['All opportunities'], + position: 2, + isVisible: true, + size: 100, + }, + { + fieldMetadataId: 'company', + viewId: viewIdMap['All opportunities'], + position: 3, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: 'createdAt', + viewId: viewIdMap['All opportunities'], + position: 4, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: 'pointOfContact', + viewId: viewIdMap['All opportunities'], + position: 5, + isVisible: true, + size: 150, + }, + ]) + .execute(); +}; diff --git a/server/src/tenant-manager/standard-objects/activity-target.ts b/server/src/tenant-manager/standard-objects/activity-target.ts new file mode 100644 index 000000000..312f10507 --- /dev/null +++ b/server/src/tenant-manager/standard-objects/activity-target.ts @@ -0,0 +1,55 @@ +const activityTargetMetadata = { + nameSingular: 'activityTargetV2', + namePlural: 'activityTargetsV2', + labelSingular: 'Activity Target', + labelPlural: 'Activity Targets', + targetTableName: 'activityTarget', + description: 'An activity target', + icon: 'IconCheckbox', + isActive: true, + isSystem: true, + fields: [ + { + // Relations + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'activity', + label: 'Activity', + targetColumnMap: { + value: 'activityId', + }, + description: 'ActivityTarget activity', + icon: 'IconCheckbox', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'person', + label: 'Person', + targetColumnMap: { + value: 'personId', + }, + description: 'ActivityTarget person', + icon: 'IconUser', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'company', + label: 'Company', + targetColumnMap: { + value: 'companyId', + }, + description: 'ActivityTarget company', + icon: 'IconBuildingSkyscraper', + isNullable: true, + }, + ], +}; + +export default activityTargetMetadata; diff --git a/server/src/tenant-manager/standard-objects/activity.ts b/server/src/tenant-manager/standard-objects/activity.ts new file mode 100644 index 000000000..d4bb813dc --- /dev/null +++ b/server/src/tenant-manager/standard-objects/activity.ts @@ -0,0 +1,155 @@ +const activityMetadata = { + nameSingular: 'activityV2', + namePlural: 'activitiesV2', + labelSingular: 'Activity', + labelPlural: 'Activities', + targetTableName: 'activity', + description: 'An activity', + icon: 'IconCheckbox', + isActive: true, + isSystem: true, + fields: [ + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'title', + label: 'Title', + targetColumnMap: { + value: 'title', + }, + description: 'Activity title', + icon: 'IconNotes', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'body', + label: 'Body', + targetColumnMap: { + value: 'body', + }, + description: 'Activity body', + icon: 'IconList', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'type', + label: 'Type', + targetColumnMap: { + value: 'type', + }, + description: 'Activity type', + icon: 'IconCheckbox', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'DATE', + name: 'reminderAt', + label: 'Reminder Date', + targetColumnMap: { + value: 'reminderAt', + }, + description: 'Activity reminder date', + icon: 'IconCalendarEvent', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'DATE', + name: 'dueAt', + label: 'Due Date', + targetColumnMap: { + value: 'dueAt', + }, + description: 'Activity due date', + icon: 'IconCalendarEvent', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'DATE', + name: 'completedAt', + label: 'Completion Date', + targetColumnMap: { + value: 'completedAt', + }, + description: 'Activity completion date', + icon: 'IconCheck', + isNullable: true, + }, + // Relations + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'activityTargets', + label: 'Targets', + targetColumnMap: {}, + description: 'Activity targets', + icon: 'IconCheckbox', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'attachments', + label: 'Attachments', + targetColumnMap: {}, + description: 'Activity attachments', + icon: 'IconFileImport', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'comments', + label: 'Comments', + targetColumnMap: {}, + description: 'Activity comments', + icon: 'IconComment', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'author', + label: 'Author', + targetColumnMap: { + value: 'authorId', + }, + description: + 'Activity author. This is the person who created the activity', + icon: 'IconUserCircle', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'assignee', + label: 'Assignee', + targetColumnMap: { + value: 'assigneeId', + }, + description: + 'Acitivity assignee. This is the workspace member assigned to the activity ', + icon: 'IconUserCircle', + isNullable: true, + }, + ], +}; + +export default activityMetadata; diff --git a/server/src/tenant-manager/standard-objects/api-key.ts b/server/src/tenant-manager/standard-objects/api-key.ts new file mode 100644 index 000000000..8a9afbdac --- /dev/null +++ b/server/src/tenant-manager/standard-objects/api-key.ts @@ -0,0 +1,54 @@ +const apiKeyMetadata = { + nameSingular: 'apiKeyV2', + namePlural: 'apiKeysV2', + labelSingular: 'Api Key', + labelPlural: 'Api Keys', + targetTableName: 'apiKey', + description: 'An api key', + icon: 'IconRobot', + isActive: true, + isSystem: true, + fields: [ + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'name', + label: 'Name', + targetColumnMap: { + value: 'name', + }, + description: 'ApiKey name', + icon: 'IconLink', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'DATE', + name: 'expiresAt', + label: 'Expiration date', + targetColumnMap: { + value: 'expiresAt', + }, + description: 'ApiKey expiration date', + icon: 'IconCalendar', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'DATE', + name: 'revokedAt', + label: 'Revocation date', + targetColumnMap: { + value: 'revokedAt', + }, + description: 'ApiKey revocation date', + icon: 'IconCalendar', + isNullable: true, + }, + ], +}; + +export default apiKeyMetadata; diff --git a/server/src/tenant-manager/standard-objects/attachment.ts b/server/src/tenant-manager/standard-objects/attachment.ts new file mode 100644 index 000000000..972205027 --- /dev/null +++ b/server/src/tenant-manager/standard-objects/attachment.ts @@ -0,0 +1,107 @@ +const attachmentMetadata = { + nameSingular: 'attachmentV2', + namePlural: 'attachmentsV2', + labelSingular: 'Attachment', + labelPlural: 'Attachments', + targetTableName: 'attachment', + description: 'An attachment', + icon: 'IconFileImport', + isActive: true, + isSystem: true, + fields: [ + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'name', + label: 'Name', + targetColumnMap: { + value: 'name', + }, + description: 'Attachment name', + icon: 'IconFileUpload', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'fullPath', + label: 'Full path', + targetColumnMap: { + value: 'fullPath', + }, + description: 'Attachment full path', + icon: 'IconLink', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'type', + label: 'Type', + targetColumnMap: { + value: 'type', + }, + description: 'Attachment type', + icon: 'IconList', + isNullable: false, + }, + // Relations + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'author', + label: 'Author', + targetColumnMap: { + value: 'authorId', + }, + description: 'Attachment author', + icon: 'IconCircleUser', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'activity', + label: 'Activity', + targetColumnMap: { + value: 'activityId', + }, + description: 'Attachment activity', + icon: 'IconNotes', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'person', + label: 'Person', + targetColumnMap: { + value: 'personId', + }, + description: 'Attachment person', + icon: 'IconUser', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'company', + label: 'Company', + targetColumnMap: { + value: 'companyId', + }, + description: 'Attachment company', + icon: 'IconBuildingSkyscraper', + isNullable: false, + }, + ], +}; + +export default attachmentMetadata; diff --git a/server/src/tenant-manager/standard-objects/comment.ts b/server/src/tenant-manager/standard-objects/comment.ts new file mode 100644 index 000000000..d5f5169b7 --- /dev/null +++ b/server/src/tenant-manager/standard-objects/comment.ts @@ -0,0 +1,55 @@ +const commentMetadata = { + nameSingular: 'commentV2', + namePlural: 'commentsV2', + labelSingular: 'Comment', + labelPlural: 'Comments', + targetTableName: 'comment', + description: 'A comment', + icon: 'IconMessageCircle', + isActive: true, + isSystem: true, + fields: [ + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'body', + label: 'Body', + targetColumnMap: { + value: 'body', + }, + description: 'Comment body', + icon: 'IconLink', + isNullable: false, + }, + // Relations + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'author', + label: 'Author', + targetColumnMap: { + value: 'authorId', + }, + description: 'Comment author', + icon: 'IconCircleUser', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'activity', + label: 'Activity', + targetColumnMap: { + value: 'activityId', + }, + description: 'Comment activity', + icon: 'IconNotes', + isNullable: false, + }, + ], +}; + +export default commentMetadata; diff --git a/server/src/tenant-manager/standard-objects/companies/companies.metadata.ts b/server/src/tenant-manager/standard-objects/companies/companies.metadata.ts deleted file mode 100644 index f7394e471..000000000 --- a/server/src/tenant-manager/standard-objects/companies/companies.metadata.ts +++ /dev/null @@ -1,57 +0,0 @@ -const companiesMetadata = { - nameSingular: 'companyV2', - namePlural: 'companiesV2', - labelSingular: 'Company', - labelPlural: 'Companies', - targetTableName: 'company', - description: 'A company', - icon: 'IconBuildingSkyscraper', - fields: [ - { - type: 'TEXT', - name: 'name', - label: 'Name', - targetColumnMap: { - value: 'name', - }, - description: 'Name of the company', - icon: 'IconBuildingSkyscraper', - isNullable: false, - }, - { - type: 'TEXT', - name: 'domainName', - label: 'Domain Name', - targetColumnMap: { - value: 'domainName', - }, - description: 'Domain name of the company', - icon: 'IconLink', - isNullable: true, - }, - { - type: 'TEXT', - name: 'address', - label: 'Address', - targetColumnMap: { - value: 'address', - }, - description: 'Address of the company', - icon: 'IconMap', - isNullable: true, - }, - { - type: 'NUMBER', - name: 'employees', - label: 'Employees', - targetColumnMap: { - value: 'employees', - }, - description: 'Number of employees', - icon: 'IconUsers', - isNullable: true, - }, - ], -}; - -export default companiesMetadata; diff --git a/server/src/tenant-manager/standard-objects/company.ts b/server/src/tenant-manager/standard-objects/company.ts new file mode 100644 index 000000000..4821ecfe1 --- /dev/null +++ b/server/src/tenant-manager/standard-objects/company.ts @@ -0,0 +1,192 @@ +const companyMetadata = { + nameSingular: 'companyV2', + namePlural: 'companiesV2', + labelSingular: 'Company', + labelPlural: 'Companies', + targetTableName: 'company', + description: 'A company', + icon: 'IconBuildingSkyscraper', + isActive: true, + isSystem: false, + fields: [ + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'name', + label: 'Name', + targetColumnMap: { + value: 'name', + }, + description: 'The company name', + icon: 'IconBuildingSkyscraper', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'domainName', + label: 'Domain Name', + targetColumnMap: { + value: 'domainName', + }, + description: + 'The company website URL. We use this url to fetch the company icon', + icon: 'IconLink', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'address', + label: 'Address', + targetColumnMap: { + value: 'address', + }, + description: 'The company address', + icon: 'IconMap', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'NUMBER', + name: 'employees', + label: 'Employees', + targetColumnMap: { + value: 'employees', + }, + description: 'Number of employees in the company', + icon: 'IconUsers', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'linkedinUrl', + label: 'Linkedin', + targetColumnMap: { + value: 'linkedinUrl', + }, + description: 'The company Linkedin account', + icon: 'IconBrandLinkedin', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'xUrl', + label: 'X', + targetColumnMap: { + value: 'xUrl', + }, + description: 'The company Twitter/X account', + icon: 'IconBrandX', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'NUMBER', + name: 'annualRecurringRevenue', + label: 'ARR', + targetColumnMap: { + value: 'annualRecurringRevenue', + }, + description: + 'Annual Recurring Revenue: The actual or estimated annual revenue of the company', + icon: 'IconMoneybag', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'BOOLEAN', + name: 'idealCustomerProfile', + label: 'ICP', + targetColumnMap: { + value: 'idealCustomerProfile', + }, + description: + 'Ideal Customer Profile: Indicates whether the company is the most suitable and valuable customer for you', + icon: 'IconTarget', + isNullable: true, + }, + // Relations + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'people', + label: 'People', + targetColumnMap: {}, + description: 'People linked to the company.', + icon: 'IconUsers', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'accountOwner', + label: 'Account Owner', + targetColumnMap: { + value: 'accountOwnerId', + }, + description: + 'Your team member responsible for managing the company account', + icon: 'IconUserCircle', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'activityTargets', + label: 'Activities', + targetColumnMap: {}, + description: 'Activities tied to the company', + icon: 'IconCheckbox', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'opportunities', + label: 'Opportunities', + targetColumnMap: {}, + description: 'Opportunities linked to the company.', + icon: 'IconTargetArrow', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'favorites', + label: 'Favorites', + targetColumnMap: {}, + description: 'Favorites linked to the company', + icon: 'IconHeart', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'attachments', + label: 'Attachments', + targetColumnMap: {}, + description: 'Attachments linked to the company.', + icon: 'IconFileImport', + isNullable: true, + }, + ], +}; + +export default companyMetadata; diff --git a/server/src/tenant-manager/standard-objects/favorite.ts b/server/src/tenant-manager/standard-objects/favorite.ts new file mode 100644 index 000000000..39d3293f7 --- /dev/null +++ b/server/src/tenant-manager/standard-objects/favorite.ts @@ -0,0 +1,68 @@ +const favoriteMetadata = { + nameSingular: 'favoriteV2', + namePlural: 'favoritesV2', + labelSingular: 'Favorite', + labelPlural: 'Favorites', + targetTableName: 'favorite', + description: 'A favorite', + icon: 'IconHeart', + isActive: true, + isSystem: true, + fields: [ + { + isCustom: false, + isActive: true, + type: 'NUMBER', + name: 'position', + label: 'Position', + targetColumnMap: { + value: 'position', + }, + description: 'Favorite position', + icon: 'IconList', + isNullable: false, + }, + // Relations + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'workspaceMember', + label: 'Workspace Member', + targetColumnMap: { + value: 'workspaceMemberId', + }, + description: 'Favorite workspace member', + icon: 'IconCircleUser', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'person', + label: 'Person', + targetColumnMap: { + value: 'personId', + }, + description: 'Favorite person', + icon: 'IconUser', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'company', + label: 'Company', + targetColumnMap: { + value: 'companyId', + }, + description: 'Favorite company', + icon: 'IconBuildingSkyscraper', + isNullable: false, + }, + ], +}; + +export default favoriteMetadata; diff --git a/server/src/tenant-manager/standard-objects/opportunity.ts b/server/src/tenant-manager/standard-objects/opportunity.ts new file mode 100644 index 000000000..698ba9513 --- /dev/null +++ b/server/src/tenant-manager/standard-objects/opportunity.ts @@ -0,0 +1,107 @@ +const opportunityMetadata = { + nameSingular: 'opportunityV2', + namePlural: 'opportunitiesV2', + labelSingular: 'Opportunity', + labelPlural: 'Opportunities', + targetTableName: 'opportunity', + description: 'An opportunity', + icon: 'IconTargetArrow', + isActive: true, + isSystem: true, + fields: [ + { + isCustom: false, + isActive: true, + type: 'NUMBER', + name: 'amount', + label: 'Amount', + targetColumnMap: { + value: 'amount', + }, + description: 'Opportunity amount', + icon: 'IconCurrencyDollar', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'DATE', + name: 'closeDate', + label: 'Close date', + targetColumnMap: { + value: 'closeDate', + }, + description: 'Opportunity close date', + icon: 'IconCalendarEvent', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'probability', + label: 'Probability', + targetColumnMap: { + value: 'probability', + }, + description: 'Opportunity amount', + icon: 'IconProgressCheck', + isNullable: true, + }, + // Relations + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'pipelineStep', + label: 'Pipeline Step', + targetColumnMap: { + value: 'pipelineStepId', + }, + description: 'Opportunity pipeline step', + icon: 'IconKanban', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'pointOfContact', + label: 'Point of Contact', + targetColumnMap: { + value: 'pointOfContactId', + }, + description: 'Opportunity point of contact', + icon: 'IconUser', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'person', + label: 'Person', + targetColumnMap: { + value: 'personId', + }, + description: 'Opportunity person', + icon: 'IconUser', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'company', + label: 'Company', + targetColumnMap: { + value: 'companyId', + }, + description: 'Opportunity company', + icon: 'IconBuildingSkyscraper', + isNullable: true, + }, + ], +}; + +export default opportunityMetadata; diff --git a/server/src/tenant-manager/standard-objects/person.ts b/server/src/tenant-manager/standard-objects/person.ts new file mode 100644 index 000000000..22706ee64 --- /dev/null +++ b/server/src/tenant-manager/standard-objects/person.ts @@ -0,0 +1,201 @@ +const personMetadata = { + nameSingular: 'personV2', + namePlural: 'peopleV2', + labelSingular: 'Person', + labelPlural: 'People', + targetTableName: 'person', + description: 'A person', + icon: 'IconUser', + isActive: true, + isSystem: false, + fields: [ + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'firstName', + label: 'First name', + targetColumnMap: { + value: 'firstName', + }, + description: 'Contact’s first name', + icon: 'IconUser', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'lastName', + label: 'Last name', + targetColumnMap: { + value: 'lastName', + }, + description: 'Contact’s last name', + icon: 'IconUser', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'EMAIL', + name: 'email', + label: 'Email', + targetColumnMap: { + value: 'email', + }, + description: 'Contact’s Email', + icon: 'IconMail', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'URL', + name: 'linkedinUrl', + label: 'Linkedin', + targetColumnMap: { + value: 'linkedinUrl', + }, + description: 'Contact’s Linkedin account', + icon: 'IconBrandLinkedin', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'URL', + name: 'xUrl', + label: 'X', + targetColumnMap: { + value: 'xUrl', + }, + description: 'Contact’s X/Twitter account', + icon: 'IconUser', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'jobTitle', + label: 'Job Title', + targetColumnMap: { + value: 'jobTitle', + }, + description: 'Contact’s job title', + icon: 'IconBriefcase', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'phone', + label: 'Phone', + targetColumnMap: { + value: 'phone', + }, + description: 'Contact’s phone number', + icon: 'IconPhone', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'city', + label: 'City', + targetColumnMap: { + value: 'city', + }, + description: 'Contact’s city', + icon: 'IconMap', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'avatarUrl', + label: 'Avatar', + targetColumnMap: { + value: 'avatarUrl', + }, + description: 'Contact’s avatar', + icon: 'IconFileUpload', + isNullable: false, + }, + // Relations + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'company', + label: 'Company', + targetColumnMap: { + value: 'companyId', + }, + description: 'Contact’s company', + icon: 'IconBuildingSkyscraper', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'pointOfContactForOpportunities', + label: 'POC for Opportunities', + targetColumnMap: {}, + description: 'Point of Contact for Opportunities', + icon: 'IconArrowTarget', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'activityTargets', + label: 'Activities', + targetColumnMap: {}, + description: 'Activities tied to the contact', + icon: 'IconCheckbox', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'opportunities', + label: 'Opportunities', + targetColumnMap: {}, + description: 'Opportunities linked to the contact.', + icon: 'IconTargetArrow', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'favorites', + label: 'Favorites', + targetColumnMap: {}, + description: 'Favorites linked to the contact', + icon: 'IconHeart', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'attachments', + label: 'Attachments', + targetColumnMap: {}, + description: 'Attachments linked to the contact.', + icon: 'IconFileImport', + isNullable: true, + }, + ], +}; + +export default personMetadata; diff --git a/server/src/tenant-manager/standard-objects/pipeline-step.ts b/server/src/tenant-manager/standard-objects/pipeline-step.ts new file mode 100644 index 000000000..7285915e2 --- /dev/null +++ b/server/src/tenant-manager/standard-objects/pipeline-step.ts @@ -0,0 +1,66 @@ +const pipelineStepMetadata = { + nameSingular: 'pipelineStepV2', + namePlural: 'pipelineStepsV2', + labelSingular: 'Pipeline Step', + labelPlural: 'Pipeline Steps', + targetTableName: 'pipelineStep', + description: 'A pipeline step', + icon: 'IconLayoutKanban', + isActive: true, + isSystem: true, + fields: [ + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'name', + label: 'Name', + targetColumnMap: { + value: 'name', + }, + description: 'Pipeline Step name', + icon: 'IconCurrencyDollar', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'color', + label: 'Color', + targetColumnMap: { + value: 'color', + }, + description: 'Pipeline Step color', + icon: 'IconColorSwatch', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'NUMBER', + name: 'position', + label: 'Position', + targetColumnMap: { + value: 'position', + }, + description: 'Pipeline Step position', + icon: 'IconHierarchy2', + isNullable: false, + }, + // Relations + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'opportunities', + label: 'Opportunities', + targetColumnMap: {}, + description: 'Opportunities linked to the step.', + icon: 'IconTargetArrow', + isNullable: true, + }, + ], +}; + +export default pipelineStepMetadata; diff --git a/server/src/tenant-manager/standard-objects/relations/activity.ts b/server/src/tenant-manager/standard-objects/relations/activity.ts new file mode 100644 index 000000000..6e05b4709 --- /dev/null +++ b/server/src/tenant-manager/standard-objects/relations/activity.ts @@ -0,0 +1,27 @@ +import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; + +const activityRelationMetadata = [ + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'activityV2', + toObjectNameSingular: 'activityTargetV2', + fromFieldMetadataName: 'activityTargets', + toFieldMetadataName: 'activity', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'activityV2', + toObjectNameSingular: 'attachmentV2', + fromFieldMetadataName: 'attachments', + toFieldMetadataName: 'activity', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'activityV2', + toObjectNameSingular: 'commentV2', + fromFieldMetadataName: 'comments', + toFieldMetadataName: 'activity', + }, +]; + +export default activityRelationMetadata; diff --git a/server/src/tenant-manager/standard-objects/relations/company.ts b/server/src/tenant-manager/standard-objects/relations/company.ts new file mode 100644 index 000000000..7af62eb2c --- /dev/null +++ b/server/src/tenant-manager/standard-objects/relations/company.ts @@ -0,0 +1,41 @@ +import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; + +const companyRelationMetadata = [ + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'companyV2', + toObjectNameSingular: 'personV2', + fromFieldMetadataName: 'people', + toFieldMetadataName: 'company', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'companyV2', + toObjectNameSingular: 'favoriteV2', + fromFieldMetadataName: 'favorites', + toFieldMetadataName: 'company', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'companyV2', + toObjectNameSingular: 'attachmentV2', + fromFieldMetadataName: 'attachments', + toFieldMetadataName: 'company', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'companyV2', + toObjectNameSingular: 'opportunityV2', + fromFieldMetadataName: 'opportunities', + toFieldMetadataName: 'company', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'companyV2', + toObjectNameSingular: 'activityTargetV2', + fromFieldMetadataName: 'activityTargets', + toFieldMetadataName: 'company', + }, +]; + +export default companyRelationMetadata; diff --git a/server/src/tenant-manager/standard-objects/relations/person.ts b/server/src/tenant-manager/standard-objects/relations/person.ts new file mode 100644 index 000000000..eb1dfed1c --- /dev/null +++ b/server/src/tenant-manager/standard-objects/relations/person.ts @@ -0,0 +1,41 @@ +import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; + +const personRelationMetadata = [ + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'personV2', + toObjectNameSingular: 'favoriteV2', + fromFieldMetadataName: 'favorites', + toFieldMetadataName: 'person', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'personV2', + toObjectNameSingular: 'attachmentV2', + fromFieldMetadataName: 'attachments', + toFieldMetadataName: 'person', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'personV2', + toObjectNameSingular: 'opportunityV2', + fromFieldMetadataName: 'opportunities', + toFieldMetadataName: 'person', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'personV2', + toObjectNameSingular: 'opportunityV2', + fromFieldMetadataName: 'pointOfContactForOpportunities', + toFieldMetadataName: 'pointOfContact', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'personV2', + toObjectNameSingular: 'activityTargetV2', + fromFieldMetadataName: 'activityTargets', + toFieldMetadataName: 'person', + }, +]; + +export default personRelationMetadata; diff --git a/server/src/tenant-manager/standard-objects/relations/pipeline-step.ts b/server/src/tenant-manager/standard-objects/relations/pipeline-step.ts new file mode 100644 index 000000000..70194ad01 --- /dev/null +++ b/server/src/tenant-manager/standard-objects/relations/pipeline-step.ts @@ -0,0 +1,13 @@ +import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; + +const pipelineStepRelationMetadata = [ + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'pipelineStepV2', + toObjectNameSingular: 'opportunityV2', + fromFieldMetadataName: 'opportunities', + toFieldMetadataName: 'pipelineStep', + }, +]; + +export default pipelineStepRelationMetadata; diff --git a/server/src/tenant-manager/standard-objects/relations/view.ts b/server/src/tenant-manager/standard-objects/relations/view.ts new file mode 100644 index 000000000..b58ee8400 --- /dev/null +++ b/server/src/tenant-manager/standard-objects/relations/view.ts @@ -0,0 +1,27 @@ +import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; + +const viewRelationMetadata = [ + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'viewV2', + toObjectNameSingular: 'viewFieldV2', + fromFieldMetadataName: 'viewFields', + toFieldMetadataName: 'view', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'viewV2', + toObjectNameSingular: 'viewFilterV2', + fromFieldMetadataName: 'viewFilters', + toFieldMetadataName: 'view', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'viewV2', + toObjectNameSingular: 'viewSortV2', + fromFieldMetadataName: 'viewSorts', + toFieldMetadataName: 'view', + }, +]; + +export default viewRelationMetadata; diff --git a/server/src/tenant-manager/standard-objects/relations/workspace-member.ts b/server/src/tenant-manager/standard-objects/relations/workspace-member.ts new file mode 100644 index 000000000..1aa3acec7 --- /dev/null +++ b/server/src/tenant-manager/standard-objects/relations/workspace-member.ts @@ -0,0 +1,48 @@ +import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; + +const workspaceMemberRelationMetadata = [ + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'workspaceMemberV2', + toObjectNameSingular: 'companyV2', + fromFieldMetadataName: 'accountOwnerForCompanies', + toFieldMetadataName: 'accountOwner', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'workspaceMemberV2', + toObjectNameSingular: 'favoriteV2', + fromFieldMetadataName: 'favorites', + toFieldMetadataName: 'workspaceMember', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'workspaceMemberV2', + toObjectNameSingular: 'activityV2', + fromFieldMetadataName: 'authoredActivities', + toFieldMetadataName: 'author', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'workspaceMemberV2', + toObjectNameSingular: 'activityV2', + fromFieldMetadataName: 'assignedActivities', + toFieldMetadataName: 'assignee', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'workspaceMemberV2', + toObjectNameSingular: 'commentV2', + fromFieldMetadataName: 'authoredComments', + toFieldMetadataName: 'author', + }, + { + type: RelationMetadataType.ONE_TO_MANY, + fromObjectNameSingular: 'workspaceMemberV2', + toObjectNameSingular: 'attachmentV2', + fromFieldMetadataName: 'authoredAttachments', + toFieldMetadataName: 'author', + }, +]; + +export default workspaceMemberRelationMetadata; diff --git a/server/src/tenant-manager/standard-objects/standard-object-metadata.ts b/server/src/tenant-manager/standard-objects/standard-object-metadata.ts index 6cc8925e0..3ee1b6499 100644 --- a/server/src/tenant-manager/standard-objects/standard-object-metadata.ts +++ b/server/src/tenant-manager/standard-objects/standard-object-metadata.ts @@ -1,13 +1,35 @@ -import companiesMetadata from './companies/companies.metadata'; -import viewFieldsMetadata from './view-fields/view-fields.metadata'; -import viewFiltersMetadata from './view-filters/view-filters.metadata'; -import viewSortsMetadata from './view-sorts/view-sorts.metadata'; -import viewsMetadata from './views/views.metadata'; +import activityTargetMetadata from 'src/tenant-manager/standard-objects/activity-target'; +import activityMetadata from 'src/tenant-manager/standard-objects/activity'; +import apiKeyMetadata from 'src/tenant-manager/standard-objects/api-key'; +import attachmentMetadata from 'src/tenant-manager/standard-objects/attachment'; +import commentMetadata from 'src/tenant-manager/standard-objects/comment'; +import favoriteMetadata from 'src/tenant-manager/standard-objects/favorite'; +import opportunityMetadata from 'src/tenant-manager/standard-objects/opportunity'; +import personMetadata from 'src/tenant-manager/standard-objects/person'; +import viewMetadata from 'src/tenant-manager/standard-objects/view'; +import viewFieldMetadata from 'src/tenant-manager/standard-objects/view-field'; +import viewFilterMetadata from 'src/tenant-manager/standard-objects/view-filter'; +import viewSortMetadata from 'src/tenant-manager/standard-objects/view-sort'; +import webhookMetadata from 'src/tenant-manager/standard-objects/webhook'; +import pipelineStepMetadata from 'src/tenant-manager/standard-objects/pipeline-step'; +import companyMetadata from 'src/tenant-manager/standard-objects/company'; +import workspaceMemberMetadata from 'src/tenant-manager/standard-objects/workspace-member'; export const standardObjectsMetadata = { - companyV2: companiesMetadata, - viewV2: viewsMetadata, - viewFieldV2: viewFieldsMetadata, - viewFilterV2: viewFiltersMetadata, - viewSortV2: viewSortsMetadata, + activityTargetV2: activityTargetMetadata, + activityV2: activityMetadata, + apiKeyV2: apiKeyMetadata, + attachmentV2: attachmentMetadata, + commentV2: commentMetadata, + companyV2: companyMetadata, + favoriteV2: favoriteMetadata, + opportunityV2: opportunityMetadata, + personV2: personMetadata, + pipelineStepV2: pipelineStepMetadata, + viewFieldV2: viewFieldMetadata, + viewFilterV2: viewFilterMetadata, + viewSortV2: viewSortMetadata, + viewV2: viewMetadata, + webhookV2: webhookMetadata, + workspaceMemberV2: workspaceMemberMetadata, }; diff --git a/server/src/tenant-manager/standard-objects/standard-object-relation-metadata.ts b/server/src/tenant-manager/standard-objects/standard-object-relation-metadata.ts new file mode 100644 index 000000000..c8c9ef9c4 --- /dev/null +++ b/server/src/tenant-manager/standard-objects/standard-object-relation-metadata.ts @@ -0,0 +1,15 @@ +import activityRelationMetadata from 'src/tenant-manager/standard-objects/relations/activity'; +import companyRelationMetadata from 'src/tenant-manager/standard-objects/relations/company'; +import personRelationMetadata from 'src/tenant-manager/standard-objects/relations/person'; +import pipelineStepRelationMetadata from 'src/tenant-manager/standard-objects/relations/pipeline-step'; +import viewRelationMetadata from 'src/tenant-manager/standard-objects/relations/view'; +import workspaceMemberRelationMetadata from 'src/tenant-manager/standard-objects/relations/workspace-member'; + +export const standardObjectRelationMetadata = [ + ...activityRelationMetadata, + ...companyRelationMetadata, + ...personRelationMetadata, + ...pipelineStepRelationMetadata, + ...viewRelationMetadata, + ...workspaceMemberRelationMetadata, +]; diff --git a/server/src/tenant-manager/standard-objects/view-fields/view-fields.metadata.ts b/server/src/tenant-manager/standard-objects/view-field.ts similarity index 62% rename from server/src/tenant-manager/standard-objects/view-fields/view-fields.metadata.ts rename to server/src/tenant-manager/standard-objects/view-field.ts index db8407df8..7902d1968 100644 --- a/server/src/tenant-manager/standard-objects/view-fields/view-fields.metadata.ts +++ b/server/src/tenant-manager/standard-objects/view-field.ts @@ -1,13 +1,17 @@ -const viewFieldsMetadata = { +const viewFieldMetadata = { nameSingular: 'viewFieldV2', namePlural: 'viewFieldsV2', labelSingular: 'View Field', labelPlural: 'View Fields', targetTableName: 'viewField', description: '(System) View Fields', - icon: 'IconColumns3', + icon: 'IconTag', + isActive: true, + isSystem: true, fields: [ { + isCustom: false, + isActive: true, type: 'TEXT', name: 'fieldMetadataId', label: 'Field Metadata Id', @@ -15,21 +19,12 @@ const viewFieldsMetadata = { value: 'fieldMetadataId', }, description: 'View Field target field', - icon: null, - isNullable: false, - }, - { - type: 'TEXT', - name: 'viewId', - label: 'View Id', - targetColumnMap: { - value: 'viewId', - }, - description: 'View Field related view', - icon: null, + icon: 'IconTag', isNullable: false, }, { + isCustom: false, + isActive: true, type: 'BOOLEAN', name: 'isVisible', label: 'Visible', @@ -37,10 +32,12 @@ const viewFieldsMetadata = { value: 'isVisible', }, description: 'View Field visibility', - icon: null, + icon: 'IconEye', isNullable: false, }, { + isCustom: false, + isActive: true, type: 'NUMBER', name: 'size', label: 'Size', @@ -48,10 +45,12 @@ const viewFieldsMetadata = { value: 'size', }, description: 'View Field size', - icon: null, + icon: 'IconEye', isNullable: false, }, { + isCustom: false, + isActive: true, type: 'NUMBER', name: 'position', label: 'Position', @@ -59,10 +58,35 @@ const viewFieldsMetadata = { value: 'position', }, description: 'View Field position', - icon: null, + icon: 'IconList', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'view', + label: 'View', + targetColumnMap: { value: 'viewId' }, + description: 'View Field related view', + icon: 'IconLayoutCollage', + isNullable: false, + }, + // Temporary hack? + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'viewId', + label: 'View Id', + targetColumnMap: { + value: 'viewId', + }, + description: 'View field related view', + icon: 'IconLayoutCollage', isNullable: false, }, ], }; -export default viewFieldsMetadata; +export default viewFieldMetadata; diff --git a/server/src/tenant-manager/standard-objects/view-filters/view-filters.metadata.ts b/server/src/tenant-manager/standard-objects/view-filter.ts similarity index 66% rename from server/src/tenant-manager/standard-objects/view-filters/view-filters.metadata.ts rename to server/src/tenant-manager/standard-objects/view-filter.ts index 4508e848b..9c4c0ffcc 100644 --- a/server/src/tenant-manager/standard-objects/view-filters/view-filters.metadata.ts +++ b/server/src/tenant-manager/standard-objects/view-filter.ts @@ -1,4 +1,4 @@ -const viewFiltersMetadata = { +const viewFilterMetadata = { nameSingular: 'viewFilterV2', namePlural: 'viewFiltersV2', labelSingular: 'View Filter', @@ -6,8 +6,12 @@ const viewFiltersMetadata = { targetTableName: 'viewFilter', description: '(System) View Filters', icon: 'IconFilterBolt', + isActive: true, + isSystem: true, fields: [ { + isCustom: false, + isActive: true, type: 'TEXT', name: 'fieldMetadataId', label: 'Field Metadata Id', @@ -16,20 +20,11 @@ const viewFiltersMetadata = { }, description: 'View Filter target field', icon: null, - isNullable: true, - }, - { - type: 'TEXT', - name: 'viewId', - label: 'View Id', - targetColumnMap: { - value: 'viewId', - }, - description: 'View Filter related view', - icon: null, isNullable: false, }, { + isCustom: false, + isActive: true, type: 'TEXT', name: 'operand', label: 'Operand', @@ -41,6 +36,8 @@ const viewFiltersMetadata = { isNullable: false, }, { + isCustom: false, + isActive: true, type: 'TEXT', name: 'value', label: 'Value', @@ -52,6 +49,8 @@ const viewFiltersMetadata = { isNullable: false, }, { + isCustom: false, + isActive: true, type: 'TEXT', name: 'displayValue', label: 'Display Value', @@ -62,7 +61,32 @@ const viewFiltersMetadata = { icon: null, isNullable: false, }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'view', + label: 'View', + targetColumnMap: { value: 'viewId' }, + description: 'View Filter related view', + icon: 'IconLayoutCollage', + isNullable: false, + }, + // Temporary hack? + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'viewId', + label: 'View Id', + targetColumnMap: { + value: 'viewId', + }, + description: 'View Filter related view', + icon: 'IconLayoutCollage', + isNullable: false, + }, ], }; -export default viewFiltersMetadata; +export default viewFilterMetadata; diff --git a/server/src/tenant-manager/standard-objects/view-sorts/view-sorts.metadata.ts b/server/src/tenant-manager/standard-objects/view-sort.ts similarity index 61% rename from server/src/tenant-manager/standard-objects/view-sorts/view-sorts.metadata.ts rename to server/src/tenant-manager/standard-objects/view-sort.ts index f00f2b1b2..4101d9460 100644 --- a/server/src/tenant-manager/standard-objects/view-sorts/view-sorts.metadata.ts +++ b/server/src/tenant-manager/standard-objects/view-sort.ts @@ -1,4 +1,4 @@ -const viewSortsMetadata = { +const viewSortMetadata = { nameSingular: 'viewSortV2', namePlural: 'viewSortsV2', labelSingular: 'View Sort', @@ -6,8 +6,12 @@ const viewSortsMetadata = { targetTableName: 'viewSort', description: '(System) View Sorts', icon: 'IconArrowsSort', + isActive: true, + isSystem: true, fields: [ { + isCustom: false, + isActive: true, type: 'TEXT', name: 'fieldMetadataId', label: 'Field Metadata Id', @@ -19,17 +23,8 @@ const viewSortsMetadata = { isNullable: false, }, { - type: 'TEXT', - name: 'viewId', - label: 'View Id', - targetColumnMap: { - value: 'viewId', - }, - description: 'View Sort related view', - icon: null, - isNullable: false, - }, - { + isCustom: false, + isActive: true, type: 'TEXT', name: 'direction', label: 'Direction', @@ -40,7 +35,34 @@ const viewSortsMetadata = { icon: null, isNullable: false, }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'view', + label: 'View', + targetColumnMap: { + value: 'viewId', + }, + description: 'View Sort related view', + icon: 'IconLayoutCollage', + isNullable: false, + }, + // Temporary Hack? + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'viewId', + label: 'View Id', + targetColumnMap: { + value: 'viewId', + }, + description: 'View Sort related view', + icon: 'IconLayoutCollage', + isNullable: false, + }, ], }; -export default viewSortsMetadata; +export default viewSortMetadata; diff --git a/server/src/tenant-manager/standard-objects/views/views.metadata.ts b/server/src/tenant-manager/standard-objects/view.ts similarity index 51% rename from server/src/tenant-manager/standard-objects/views/views.metadata.ts rename to server/src/tenant-manager/standard-objects/view.ts index 9290c142f..08046b257 100644 --- a/server/src/tenant-manager/standard-objects/views/views.metadata.ts +++ b/server/src/tenant-manager/standard-objects/view.ts @@ -1,4 +1,4 @@ -const viewsMetadata = { +const viewMetadata = { nameSingular: 'viewV2', namePlural: 'viewsV2', labelSingular: 'View', @@ -6,6 +6,8 @@ const viewsMetadata = { targetTableName: 'view', description: '(System) Views', icon: 'IconLayoutCollage', + isActive: true, + isSystem: true, fields: [ { type: 'TEXT', @@ -40,7 +42,40 @@ const viewsMetadata = { icon: null, isNullable: false, }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'viewFields', + label: 'View Fields', + targetColumnMap: {}, + description: 'View Fields', + icon: 'IconTag', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'viewSorts', + label: 'View Sorts', + targetColumnMap: {}, + description: 'View Sorts', + icon: 'IconArrowsSort', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'viewFilters', + label: 'View Filters', + targetColumnMap: {}, + description: 'View Filters', + icon: 'IconFilterBolt', + isNullable: true, + }, ], }; -export default viewsMetadata; +export default viewMetadata; diff --git a/server/src/tenant-manager/standard-objects/webhook.ts b/server/src/tenant-manager/standard-objects/webhook.ts new file mode 100644 index 000000000..0f2dd0f42 --- /dev/null +++ b/server/src/tenant-manager/standard-objects/webhook.ts @@ -0,0 +1,41 @@ +const webhookMetadata = { + nameSingular: 'webhookV2', + namePlural: 'webhooksV2', + labelSingular: 'Webhook', + labelPlural: 'Webhooks', + targetTableName: 'webhook', + description: 'A webhook', + icon: 'IconRobot', + isActive: true, + isSystem: true, + fields: [ + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'targetUrl', + label: 'Target Url', + targetColumnMap: { + value: 'targetUrl', + }, + description: 'Webhook target url', + icon: 'IconLink', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'operation', + label: 'Operation', + targetColumnMap: { + value: 'operation', + }, + description: 'Webhook operation', + icon: 'IconCheckbox', + isNullable: false, + }, + ], +}; + +export default webhookMetadata; diff --git a/server/src/tenant-manager/standard-objects/workspace-member.ts b/server/src/tenant-manager/standard-objects/workspace-member.ts new file mode 100644 index 000000000..1af4e6bf8 --- /dev/null +++ b/server/src/tenant-manager/standard-objects/workspace-member.ts @@ -0,0 +1,160 @@ +const workspaceMemberMetadata = { + nameSingular: 'workspaceMemberV2', + namePlural: 'workspaceMembersV2', + labelSingular: 'Workspace Member', + labelPlural: 'Workspace Members', + targetTableName: 'workspaceMember', + description: 'A workspace member', + icon: 'IconUserCircle', + isActive: true, + isSystem: true, + fields: [ + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'firstName', + label: 'First name', + targetColumnMap: { + value: 'firstName', + }, + description: 'Workspace member first name', + icon: 'IconCircleUser', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'lastName', + label: 'Last name', + targetColumnMap: { + value: 'lastName', + }, + description: 'Workspace member last name', + icon: 'IconCircleUser', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'UUID', + name: 'userId', + label: 'User Id', + targetColumnMap: { + value: 'userId', + }, + description: 'Associated User Id', + icon: 'IconCircleUsers', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'BOOLEAN', + name: 'allowImpersonation', + label: 'Admin Access', + targetColumnMap: { + value: 'allowImpersonation', + }, + description: 'Allow Admin Access', + icon: 'IconEye', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'colorScheme', + label: 'Color Scheme', + targetColumnMap: { + value: 'colorScheme', + }, + description: 'Preferred color scheme', + icon: 'IconColorSwatch', + isNullable: false, + }, + { + isCustom: false, + isActive: true, + type: 'TEXT', + name: 'locale', + label: 'Language', + targetColumnMap: { + value: 'locale', + }, + description: 'Preferred language', + icon: 'IconLanguage', + isNullable: false, + }, + // Relations + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'authoredActivities', + label: 'Authored activities', + targetColumnMap: {}, + description: 'Activities created by the workspace member', + icon: 'IconCheckbox', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'assignedActivities', + label: 'Assigned activities', + targetColumnMap: {}, + description: 'Activities assigned to the workspace member', + icon: 'IconCheckbox', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'favorites', + label: 'Favorites', + targetColumnMap: {}, + description: 'Favorites linked to the workspace member', + icon: 'IconHeart', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'accountOwnerForCompanies', + label: 'Account Owner For Companies', + targetColumnMap: {}, + description: 'Account owner for companies', + icon: 'IconBriefcase', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'authoredAttachments', + label: 'Authored attachments', + targetColumnMap: {}, + description: 'Attachments created by the workspace member', + icon: 'IconFileImport', + isNullable: true, + }, + { + isCustom: false, + isActive: true, + type: 'RELATION', + name: 'authoredComments', + label: 'Authored comments', + targetColumnMap: {}, + description: 'Authored comments', + icon: 'IconComment', + isNullable: true, + }, + ], +}; + +export default workspaceMemberMetadata; diff --git a/server/src/tenant-manager/tenant-manager.module.ts b/server/src/tenant-manager/tenant-manager.module.ts index 050541091..b56f22ab7 100644 --- a/server/src/tenant-manager/tenant-manager.module.ts +++ b/server/src/tenant-manager/tenant-manager.module.ts @@ -6,6 +6,7 @@ import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metada import { TenantMigrationModule } from 'src/metadata/tenant-migration/tenant-migration.module'; import { TenantMigrationRunnerModule } from 'src/tenant-migration-runner/tenant-migration-runner.module'; import { TenantDataSourceModule } from 'src/tenant-datasource/tenant-datasource.module'; +import { RelationMetadataModule } from 'src/metadata/relation-metadata/relation-metadata.module'; import { TenantManagerService } from './tenant-manager.service'; @@ -17,6 +18,7 @@ import { TenantManagerService } from './tenant-manager.service'; ObjectMetadataModule, FieldMetadataModule, DataSourceModule, + RelationMetadataModule, ], exports: [TenantManagerService], providers: [TenantManagerService], diff --git a/server/src/tenant-manager/tenant-manager.service.ts b/server/src/tenant-manager/tenant-manager.service.ts index 9c240b6b0..aa0e8a6cf 100644 --- a/server/src/tenant-manager/tenant-manager.service.ts +++ b/server/src/tenant-manager/tenant-manager.service.ts @@ -8,6 +8,13 @@ import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-mig import { standardObjectsPrefillData } from 'src/tenant-manager/standard-objects-prefill-data/standard-objects-prefill-data'; import { TenantDataSourceService } from 'src/tenant-datasource/tenant-datasource.service'; import { DataSourceEntity } from 'src/metadata/data-source/data-source.entity'; +import { RelationMetadataService } from 'src/metadata/relation-metadata/relation-metadata.service'; +import { standardObjectRelationMetadata } from 'src/tenant-manager/standard-objects/standard-object-relation-metadata'; +import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; +import { + FieldMetadataEntity, + FieldMetadataType, +} from 'src/metadata/field-metadata/field-metadata.entity'; import { standardObjectsMetadata } from './standard-objects/standard-object-metadata'; @@ -20,6 +27,7 @@ export class TenantManagerService { private readonly objectMetadataService: ObjectMetadataService, private readonly fieldMetadataService: FieldMetadataService, private readonly dataSourceService: DataSourceService, + private readonly relationMetadataService: RelationMetadataService, ) {} /** @@ -43,14 +51,16 @@ export class TenantManagerService { workspaceId, ); - await this.createStandardObjectsAndFieldsMetadata( - dataSourceMetadata.id, - workspaceId, - ); + const createdObjectMetadata = + await this.createStandardObjectsAndFieldsMetadata( + dataSourceMetadata.id, + workspaceId, + ); await this.prefillWorkspaceWithStandardObjects( dataSourceMetadata, workspaceId, + createdObjectMetadata, ); } @@ -64,8 +74,8 @@ export class TenantManagerService { public async createStandardObjectsAndFieldsMetadata( dataSourceId: string, workspaceId: string, - ) { - await this.objectMetadataService.createMany( + ): Promise { + const createdObjectMetadata = await this.objectMetadataService.createMany( Object.values(standardObjectsMetadata).map((objectMetadata) => ({ ...objectMetadata, dataSourceId, @@ -80,6 +90,103 @@ export class TenantManagerService { })), })), ); + + await this.relationMetadataService.createMany( + Object.values(standardObjectRelationMetadata).map((relationMetadata) => + this.createStandardObjectRelations( + workspaceId, + createdObjectMetadata, + relationMetadata, + ), + ), + ); + + return createdObjectMetadata; + } + + /** + * + * @param workspaceId + * @param createdObjectMetadata + * @param relationMetadata + * @returns Partial + */ + private createStandardObjectRelations( + workspaceId: string, + createdObjectMetadata: ObjectMetadataEntity[], + relationMetadata: any, + ) { + const createdObjectMetadataByNameSingular = createdObjectMetadata.reduce( + (acc, curr) => { + acc[curr.nameSingular] = curr; + return acc; + }, + {}, + ); + + const fromObjectMetadata = + createdObjectMetadataByNameSingular[ + relationMetadata.fromObjectNameSingular + ]; + const toObjectMetadata = + createdObjectMetadataByNameSingular[ + relationMetadata.toObjectNameSingular + ]; + + if (!fromObjectMetadata) { + throw new Error( + `Could not find created object metadata with + fromObjectNameSingular: ${relationMetadata.fromObjectNameSingular}`, + ); + } + + if (!toObjectMetadata) { + throw new Error( + `Could not find created object metadata with + toObjectNameSingular: ${relationMetadata.toObjectNameSingular}`, + ); + } + + const fromFieldMetadata = createdObjectMetadataByNameSingular[ + relationMetadata.fromObjectNameSingular + ]?.fields.find( + (field: FieldMetadataEntity) => + field.type === FieldMetadataType.RELATION && + field.name === relationMetadata.fromFieldMetadataName, + ); + + const toFieldMetadata = createdObjectMetadataByNameSingular[ + relationMetadata.toObjectNameSingular + ]?.fields.find( + (field: FieldMetadataEntity) => + field.type === FieldMetadataType.RELATION && + field.name === relationMetadata.toFieldMetadataName, + ); + + if (!fromFieldMetadata) { + throw new Error( + `Could not find created field metadata with + fromFieldMetadataName: ${relationMetadata.fromFieldMetadataName} + for object: ${relationMetadata.fromObjectNameSingular}`, + ); + } + + if (!toFieldMetadata) { + throw new Error( + `Could not find created field metadata with + toFieldMetadataName: ${relationMetadata.toFieldMetadataName} + for object: ${relationMetadata.toObjectNameSingular}`, + ); + } + + return { + fromObjectMetadataId: fromObjectMetadata.id, + toObjectMetadataId: toObjectMetadata.id, + workspaceId, + relationType: relationMetadata.type, + fromFieldMetadataId: fromFieldMetadata.id, + toFieldMetadataId: toFieldMetadata.id, + }; } /** @@ -113,6 +220,7 @@ export class TenantManagerService { private async prefillWorkspaceWithStandardObjects( dataSourceMetadata: DataSourceEntity, workspaceId: string, + createdObjectMetadata: ObjectMetadataEntity[], ) { const workspaceDataSource = await this.tenantDataSourceService.connectToWorkspaceDataSource( @@ -123,7 +231,11 @@ export class TenantManagerService { throw new Error('Could not connect to workspace data source'); } - standardObjectsPrefillData(workspaceDataSource, dataSourceMetadata.schema); + standardObjectsPrefillData( + workspaceDataSource, + dataSourceMetadata.schema, + createdObjectMetadata, + ); } /** diff --git a/server/src/tenant/query-runner/interfaces/pg-graphql.interface.ts b/server/src/tenant/query-runner/interfaces/pg-graphql.interface.ts index dd56ec83d..d69637ca2 100644 --- a/server/src/tenant/query-runner/interfaces/pg-graphql.interface.ts +++ b/server/src/tenant/query-runner/interfaces/pg-graphql.interface.ts @@ -3,6 +3,7 @@ import { Record as IRecord } from 'src/tenant/query-builder/interfaces/record.in export interface PGGraphQLResponse { resolve: { data: Data; + errors: any[]; }; } diff --git a/server/src/tenant/query-runner/query-runner.service.ts b/server/src/tenant/query-runner/query-runner.service.ts index a3944ee60..aeba1ca9f 100644 --- a/server/src/tenant/query-runner/query-runner.service.ts +++ b/server/src/tenant/query-runner/query-runner.service.ts @@ -100,9 +100,19 @@ export class QueryRunnerService { options: QueryRunnerOptions, ): Promise { const { workspaceId, targetTableName } = options; + + console.log({ + workspaceId, + targetTableName, + }); const query = this.queryBuilderFactory.updateOne(args, options); + + console.log({ query }); + const result = await this.execute(query, workspaceId); + console.log('HEY'); + return this.parseResult>( result, targetTableName, @@ -139,15 +149,20 @@ export class QueryRunnerService { workspaceId, )}; `); - console.log('ho'); - console.log(query); - console.log('ha'); - return workspaceDataSource?.query(` + const results = await workspaceDataSource?.query(` SELECT graphql.resolve($$ ${query} $$); `); + + console.log( + JSON.stringify({ + results, + }), + ); + + return results; } private parseResult( @@ -157,6 +172,13 @@ export class QueryRunnerService { ): Result { const entityKey = `${command}${targetTableName}Collection`; const result = graphqlResult?.[0]?.resolve?.data?.[entityKey]; + const errors = graphqlResult?.[0]?.resolve?.errors; + + console.log('Result : ', graphqlResult?.[0]?.resolve); + + if (Array.isArray(errors) && errors.length > 0) { + console.error('GraphQL errors', errors); + } if (!result) { throw new BadRequestException('Malformed result from GraphQL query');