[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 <bordeau.lucas@gmail.com>
Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
Charles Bochet
2023-11-15 15:46:06 +01:00
committed by GitHub
parent 1f49ed2acf
commit 6129444c5c
129 changed files with 3468 additions and 1497 deletions

View File

@ -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 {

View File

@ -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: [

View File

@ -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);

View File

@ -63,11 +63,6 @@ export class GoogleAuthController {
firstName: firstName ?? '',
lastName: lastName ?? '',
locale: 'en',
settings: {
create: {
locale: 'en',
},
},
},
},
workspaceId,

View File

@ -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);

View File

@ -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 {

View File

@ -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 {}

View File

@ -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 } : {}),

View File

@ -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<YogaDriverConfig>({
context: ({ req }) => ({ req }),
driver: YogaDriver,
autoSchemaFile: true,
include: [CoreV2Module],
resolvers: { JSON: GraphQLJSON },
plugins: [],
path: '/graphqlv2',
}),
UserModule,
RefreshTokenModule,
],
exports: [UserModule],
})
export class CoreV2Module {}

View File

@ -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 },

View File

@ -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;

View File

@ -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],

View File

@ -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<User> {
export class UserService extends TypeOrmQueryService<UserV2> {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
@InjectRepository(UserV2)
private readonly userRepository: Repository<UserV2>,
) {
super(userRepository);
}

View File

@ -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 },

View File

@ -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;

View File

@ -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,
}),

View File

@ -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<string> {
@ -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 });

View File

@ -0,0 +1,5 @@
-- DropForeignKey
ALTER TABLE "workspace_members" DROP CONSTRAINT "workspace_members_userId_fkey";
-- AlterTable
ALTER TABLE "users" ADD COLUMN "defaultWorkspaceId" TEXT;

View File

@ -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;

View File

@ -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";

View File

@ -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])

View File

@ -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',
},
},
},
});
};

View File

@ -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,

View File

@ -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();

View File

@ -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) => ({

View File

@ -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')

View File

@ -22,6 +22,11 @@ export const addWorkspaceMemberTable: TenantMigrationTableAction[] = [
columnType: 'varchar',
action: TenantMigrationColumnActionType.CREATE,
},
{
columnName: 'avatarUrl',
columnType: 'varchar',
action: TenantMigrationColumnActionType.CREATE,
},
{
columnName: 'colorScheme',
columnType: 'varchar',

View File

@ -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();
};

File diff suppressed because one or more lines are too long

View File

@ -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();
};

View File

@ -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);
});
};

View File

@ -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<string, ObjectMetadataEntity>,
) => {
// 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();
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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: 'Contacts first name',
icon: 'IconUser',
isNullable: false,
},
{
isCustom: false,
isActive: true,
type: 'TEXT',
name: 'lastName',
label: 'Last name',
targetColumnMap: {
value: 'lastName',
},
description: 'Contacts last name',
icon: 'IconUser',
isNullable: false,
},
{
isCustom: false,
isActive: true,
type: 'EMAIL',
name: 'email',
label: 'Email',
targetColumnMap: {
value: 'email',
},
description: 'Contacts Email',
icon: 'IconMail',
isNullable: true,
},
{
isCustom: false,
isActive: true,
type: 'URL',
name: 'linkedinUrl',
label: 'Linkedin',
targetColumnMap: {
value: 'linkedinUrl',
},
description: 'Contacts Linkedin account',
icon: 'IconBrandLinkedin',
isNullable: true,
},
{
isCustom: false,
isActive: true,
type: 'URL',
name: 'xUrl',
label: 'X',
targetColumnMap: {
value: 'xUrl',
},
description: 'Contacts X/Twitter account',
icon: 'IconUser',
isNullable: true,
},
{
isCustom: false,
isActive: true,
type: 'TEXT',
name: 'jobTitle',
label: 'Job Title',
targetColumnMap: {
value: 'jobTitle',
},
description: 'Contacts job title',
icon: 'IconBriefcase',
isNullable: true,
},
{
isCustom: false,
isActive: true,
type: 'TEXT',
name: 'phone',
label: 'Phone',
targetColumnMap: {
value: 'phone',
},
description: 'Contacts phone number',
icon: 'IconPhone',
isNullable: true,
},
{
isCustom: false,
isActive: true,
type: 'TEXT',
name: 'city',
label: 'City',
targetColumnMap: {
value: 'city',
},
description: 'Contacts city',
icon: 'IconMap',
isNullable: true,
},
{
isCustom: false,
isActive: true,
type: 'TEXT',
name: 'avatarUrl',
label: 'Avatar',
targetColumnMap: {
value: 'avatarUrl',
},
description: 'Contacts avatar',
icon: 'IconFileUpload',
isNullable: false,
},
// Relations
{
isCustom: false,
isActive: true,
type: 'RELATION',
name: 'company',
label: 'Company',
targetColumnMap: {
value: 'companyId',
},
description: 'Contacts 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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,
};

View File

@ -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,
];

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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],

View File

@ -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<ObjectMetadataEntity[]> {
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<RelationMetadataEntity>
*/
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,
);
}
/**

View File

@ -3,6 +3,7 @@ import { Record as IRecord } from 'src/tenant/query-builder/interfaces/record.in
export interface PGGraphQLResponse<Data = any> {
resolve: {
data: Data;
errors: any[];
};
}

View File

@ -100,9 +100,19 @@ export class QueryRunnerService {
options: QueryRunnerOptions,
): Promise<Record | undefined> {
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<PGGraphQLMutation<Record>>(
result,
targetTableName,
@ -139,15 +149,20 @@ export class QueryRunnerService {
workspaceId,
)};
`);
console.log('ho');
console.log(query);
console.log('ha');
return workspaceDataSource?.query<PGGraphQLResult>(`
const results = await workspaceDataSource?.query<PGGraphQLResult>(`
SELECT graphql.resolve($$
${query}
$$);
`);
console.log(
JSON.stringify({
results,
}),
);
return results;
}
private parseResult<Result>(
@ -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');