feat: multi-workspace (frontend) (#4232)

* select workspace component

* generateJWT mutation

* workspaces state and hooks

* requested changes

* mutation fix

* requested changes

* user workpsace delete call

* migration to drop and createt user workspace

* revert select props

* add DropdownMenu

* seperate multi-workspace dropdown as component

* Signup button displayed accurately

* update seed data for multi-workspace

* lint fix

* lint fix

* css fix

* lint fix

* state fix

* isDefined check

* refactor

* add default workspace constants for logo and name

* update migration

* lint fix

* isInviteMode check on sign-in/up

* removeWorkspaceMember mutation

* import fixes

* prop name fix

* backfill migration

* handle edge cases

* refactor

* remove migration query

* delete user on no-workspace found condition

* emit workspaceMember.deleted

* Fix event class and unrelated fix linked to a previously missing dependency

* Edit migration (I did it in prod manually)

* Revert changes

* Fix tests

* Fix conflicts

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
Aditya Pimpalkar
2024-03-20 13:43:41 +00:00
committed by GitHub
parent 352192a63f
commit da12710fe9
29 changed files with 726 additions and 134 deletions

View File

@ -15,6 +15,10 @@ import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/work
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
import { seedCalendarEvents } from 'src/database/typeorm-seeds/workspace/calendar-events';
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
import {
SeedAppleWorkspaceId,
SeedTwentyWorkspaceId,
} from 'src/database/typeorm-seeds/core/workspaces';
// TODO: implement dry-run
@Command({
@ -23,7 +27,7 @@ import { EnvironmentService } from 'src/engine/integrations/environment/environm
'Seed workspace with initial data. This command is intended for development only.',
})
export class DataSeedWorkspaceCommand extends CommandRunner {
workspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419';
workspaceIds = [SeedAppleWorkspaceId, SeedTwentyWorkspaceId];
constructor(
private readonly environmentService: EnvironmentService,
@ -45,79 +49,88 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
schema: 'core',
});
await dataSource.initialize();
for (const workspaceId of this.workspaceIds) {
await dataSource.initialize();
await seedCoreSchema(dataSource, this.workspaceId);
await seedCoreSchema(dataSource, workspaceId);
await dataSource.destroy();
await dataSource.destroy();
const schemaName =
await this.workspaceDataSourceService.createWorkspaceDBSchema(
this.workspaceId,
);
const schemaName =
await this.workspaceDataSourceService.createWorkspaceDBSchema(
workspaceId,
);
const dataSourceMetadata =
await this.dataSourceService.createDataSourceMetadata(
this.workspaceId,
schemaName,
);
const dataSourceMetadata =
await this.dataSourceService.createDataSourceMetadata(
workspaceId,
schemaName,
);
await this.workspaceSyncMetadataService.synchronize({
workspaceId: this.workspaceId,
dataSourceId: dataSourceMetadata.id,
});
await this.workspaceSyncMetadataService.synchronize({
workspaceId: workspaceId,
dataSourceId: dataSourceMetadata.id,
});
}
} catch (error) {
console.error(error);
return;
}
const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
this.workspaceId,
);
const workspaceDataSource =
await this.typeORMService.connectToDataSource(dataSourceMetadata);
if (!workspaceDataSource) {
throw new Error('Could not connect to workspace data source');
}
try {
const objectMetadata =
await this.objectMetadataService.findManyWithinWorkspace(
this.workspaceId,
for (const workspaceId of this.workspaceIds) {
const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
workspaceId,
);
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;
}, {}),
};
const workspaceDataSource =
await this.typeORMService.connectToDataSource(dataSourceMetadata);
return acc;
}, {});
if (!workspaceDataSource) {
throw new Error('Could not connect to workspace data source');
}
await seedCompanies(workspaceDataSource, dataSourceMetadata.schema);
await seedPeople(workspaceDataSource, dataSourceMetadata.schema);
await seedPipelineStep(workspaceDataSource, dataSourceMetadata.schema);
await seedOpportunity(workspaceDataSource, dataSourceMetadata.schema);
await seedCalendarEvents(workspaceDataSource, dataSourceMetadata.schema);
try {
const objectMetadata =
await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
const objectMetadataMap = objectMetadata.reduce((acc, object) => {
acc[object.nameSingular] = {
id: object.id,
fields: object.fields.reduce((acc, field) => {
acc[field.name] = field.id;
await seedViews(
workspaceDataSource,
dataSourceMetadata.schema,
objectMetadataMap,
);
await seedWorkspaceMember(workspaceDataSource, dataSourceMetadata.schema);
} catch (error) {
console.error(error);
return acc;
}, {}),
};
return acc;
}, {});
await seedCompanies(workspaceDataSource, dataSourceMetadata.schema);
await seedPeople(workspaceDataSource, dataSourceMetadata.schema);
await seedPipelineStep(workspaceDataSource, dataSourceMetadata.schema);
await seedOpportunity(workspaceDataSource, dataSourceMetadata.schema);
await seedCalendarEvents(
workspaceDataSource,
dataSourceMetadata.schema,
);
await seedViews(
workspaceDataSource,
dataSourceMetadata.schema,
objectMetadataMap,
);
await seedWorkspaceMember(
workspaceDataSource,
dataSourceMetadata.schema,
workspaceId,
);
} catch (error) {
console.error(error);
}
await this.typeORMService.disconnectFromDataSource(dataSourceMetadata.id);
}
await this.typeORMService.disconnectFromDataSource(dataSourceMetadata.id);
}
}

View File

@ -1,5 +1,11 @@
import { DataSource } from 'typeorm';
import {
SeedAppleWorkspaceId,
SeedTwentyWorkspaceId,
} from 'src/database/typeorm-seeds/core/workspaces';
import { UserWorkspace } from 'src/engine/modules/user-workspace/user-workspace.entity';
// import { SeedWorkspaceId } from 'src/database/typeorm-seeds/core/workspaces';
const tableName = 'userWorkspace';
@ -15,12 +21,10 @@ export const seedUserWorkspaces = async (
schemaName: string,
workspaceId: string,
) => {
await workspaceDataSource
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, ['userId', 'workspaceId'])
.orIgnore()
.values([
let userWorkspaces: Pick<UserWorkspace, 'userId' | 'workspaceId'>[] = [];
if (workspaceId === SeedAppleWorkspaceId) {
userWorkspaces = [
{
userId: SeedUserIds.Tim,
workspaceId,
@ -33,7 +37,23 @@ export const seedUserWorkspaces = async (
userId: SeedUserIds.Phil,
workspaceId,
},
])
];
}
if (workspaceId === SeedTwentyWorkspaceId) {
userWorkspaces = [
{
userId: SeedUserIds.Tim,
workspaceId,
},
];
}
await workspaceDataSource
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, ['userId', 'workspaceId'])
.orIgnore()
.values(userWorkspaces)
.execute();
};

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,11 @@
import { DataSource } from 'typeorm';
import { SeedUserIds } from 'src/database/typeorm-seeds/core/users';
import {
SeedAppleWorkspaceId,
SeedTwentyWorkspaceId,
} from 'src/database/typeorm-seeds/core/workspaces';
import { WorkspaceMember } from 'src/engine/modules/user/dtos/workspace-member.dto';
const tableName = 'workspaceMember';
@ -10,24 +15,25 @@ const WorkspaceMemberIds = {
Phil: '20202020-1553-45c6-a028-5a9064cce07f',
};
type WorkspaceMembers = Pick<
WorkspaceMember,
'id' | 'locale' | 'colorScheme'
> & {
nameFirstName: string;
nameLastName: string;
userEmail: string;
userId: string;
};
export const seedWorkspaceMember = async (
workspaceDataSource: DataSource,
schemaName: string,
workspaceId: string,
) => {
await workspaceDataSource
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, [
'id',
'nameFirstName',
'nameLastName',
'locale',
'colorScheme',
'userEmail',
'userId',
])
.orIgnore()
.values([
let workspaceMembers: WorkspaceMembers[] = [];
if (workspaceId === SeedAppleWorkspaceId) {
workspaceMembers = [
{
id: WorkspaceMemberIds.Tim,
nameFirstName: 'Tim',
@ -55,6 +61,35 @@ export const seedWorkspaceMember = async (
userEmail: 'phil.schiler@apple.dev',
userId: SeedUserIds.Phil,
},
];
}
if (workspaceId === SeedTwentyWorkspaceId) {
workspaceMembers = [
{
id: WorkspaceMemberIds.Tim,
nameFirstName: 'Tim',
nameLastName: 'Apple',
locale: 'en',
colorScheme: 'Light',
userEmail: 'tim@apple.dev',
userId: SeedUserIds.Tim,
},
];
}
await workspaceDataSource
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, [
'id',
'nameFirstName',
'nameLastName',
'locale',
'colorScheme',
'userEmail',
'userId',
])
.orIgnore()
.values(workspaceMembers)
.execute();
};

View File

@ -14,10 +14,6 @@ export class AddUserWorkspaces1707778127558 implements MigrationInterface {
"deletedAt" TIMESTAMP
)`,
);
await queryRunner.query(
`ALTER TABLE "core"."user" DROP CONSTRAINT "FK_2ec910029395fa7655621c88908"`,
);
}
public async down(): Promise<void> {}

View File

@ -0,0 +1,44 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class updateUserWorkspaceColumnConstraints1709680520888
implements MigrationInterface
{
name = 'updateUserWorkspaceColumnConstraints1709680520888';
public async up(queryRunner: QueryRunner): Promise<void> {
// ----------------- WARNING ------------------------
// Dropping constraints and adding them back is NOT a recommended and should be AVOIDED,
// since it can affect data integrity and cause downtime and unintentional data loss.
await queryRunner.query(
`ALTER TABLE "core"."userWorkspace" DROP CONSTRAINT "userWorkspace_userId_fkey"`,
);
await queryRunner.query(
`ALTER TABLE "core"."userWorkspace" DROP CONSTRAINT "userWorkspace_workspaceId_fkey"`,
);
await queryRunner.query(
`ALTER TABLE "core"."userWorkspace" DROP CONSTRAINT "FK_cb488f32c6a0827b938edadf221"`,
);
await queryRunner.query(
`ALTER TABLE "core"."userWorkspace" DROP CONSTRAINT "FK_37fdc7357af701e595c5c3a9bd6"`,
);
await queryRunner.query(`
ALTER TABLE "core"."userWorkspace"
ADD CONSTRAINT "FK_37fdc7357af701e595c5c3a9bd6"
FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id")
ON DELETE CASCADE ON UPDATE NO ACTION
`);
await queryRunner.query(`
ALTER TABLE "core"."userWorkspace"
ADD CONSTRAINT "FK_cb488f32c6a0827b938edadf221"
FOREIGN KEY ("userId") REFERENCES "core"."user"("id")
ON DELETE CASCADE ON UPDATE NO ACTION
`);
}
public async down(): Promise<void> {}
}